基于scratch构建轻量快速镜像

设计背景:

由于项目所限定的运行平台生态所限,没有良好的容器镜像支持无父镜像可以依赖(不像X86、ARM平台),且项目对容器镜像的启动时间和镜像大小的要求,所以需要从零开始构建镜像。

docker的镜像结构如下所示,是通过分层来叠加构建的。

img{512x368}

镜像里面到底装了些什么?

首先我们先编写一个构建image的Dockerfile,下面的Dockerfile是一个基于ubuntu构建出的拥有python flask框架环境的镜像,镜像的构建速度跟网络环境有关,这里我在构建过程中替换了软件源以加快构建速度。

FROM ubuntu:16.04
COPY sources.list sources.list
RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak \
    && mv sources.list /etc/apt/
RUN apt-get update && apt-get install -y --no-install-recommends \
    python2.7 \ 
    python-pip \ 
    curl && \
    pip install flask && \
rm -rf /var/lib/apt/lists/*

sources.list软件源如下:

deb http://mirrors.ustc.edu.cn/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.ustc.edu.cn/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu/ xenial-proposed main restricted universe multiverse
deb-src http://mirrors.ustc.edu.cn/ubuntu/ xenial-backports main restricted universe multiverse

通过docker命令构建镜像,命令如下所示,构建出来的镜像名为plserver_ubuntu。
 

docker build -f Dockerfile -t plserver_ubuntu .

我们来看看这个构建后的镜像的大小,大小为169MB,会发现要实现一个功能很简单的镜像都需要很大的size。

# docker images | grep plserver_ubuntu

REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
plserver_ubuntu      latest             3da5e5f86c81          2 days ago           169MB

我们知道这个镜像里面有很多我们使用过程中用不到的东西,而这些东西都构建在了镜像里,占据了很大的空间。有没有什么办法可以压缩镜像大小呢?一个Dockerfile最终生产出的一个镜像。Dockerfile由若干个Command组成,每个Command执行结果都会单独形成一个layer。

接下来我们分析一下构建出来的镜像:

# docker history 3da5e5f86c81

IMAGE          CREATED                 CREATED BY                            SIZE   COMMENT
3da5e5f86c81   2 days ago   /bin/sh -c apt-get update && apt-get install…   46.6MB              
11045feb4abc   2 days ago   /bin/sh -c mv /etc/apt/sources.list /etc/apt…   3.68kB              
5896bd36d281   2 days ago   /bin/sh -c #(nop) COPY file:0261bb62aae7979e…   913B                
b9409899fe86   2 weeks ago  /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>      2 weeks ago  /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
<missing>      2 weeks ago  /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B                
<missing>      2 weeks ago  /bin/sh -c rm -rf /var/lib/apt/lists/*          0B                  
<missing>      2 weeks ago  /bin/sh -c #(nop) ADD file:6d0a70c6da1ad3872…   122MB

去掉那些Size为0的layer,我们看到还有很多size占比较大的layer,其中最大的占用为base image,即为ubuntu:16.04
 

# docker images | grep ubuntu

REPOSITORY               TAG             IMAGE ID            CREATED             SIZE
 ubuntu                16.04           b9409899fe86        2 weeks ago          122MB

镜像分层构成见下图:

追求最小镜像

同样对比最精简的操作系统Alpine Linux(仅包含musl libc  busybox ,还有包管理工具apk),它的大小虽然只有5MB,但是里面还有些东西可能是我们用不到的,而且这些操作系统都有一个局限,依赖于底层指令集架构。

一般应用开发者不会从scratch镜像从头构建自己的base image以及目标镜像的,开发者会挑选适合的base image。一些“蝇量级”甚至是“草量级”的官方base image的出现为这种情况提供了条件。

img{512x368}

对于Go应用来说,我们可以采用静态编译的程序,但一旦采用静态编译,也就意味着我们将失去一些libc提供的原生能力,比如:在linux上,你无法使用系统提供的DNS解析能力,只能使用Go自实现的DNS解析器。

我们还可以采用基于alpine的builder image,golang base image就提供了alpine 版本。 我们就用这种方式构建出一个基于alpine base image的极小目标镜像。

分步构建

img{512x368}

我们新建两个用于 alpine 版本目标镜像构建的 Dockerfile:Dockerfile.build.alpine 和Dockerfile.target.alpine

//Dockerfile.build.alpine
FROM golang:alpine

WORKDIR /go/src
COPY ./httpserver.go .

RUN go build -o httpd ./httpserver.go

// Dockerfile.target.alpine
From alpine

COPY ./httpd /root/httpd
RUN chmod +x /root/httpd

WORKDIR /root
ENTRYPOINT ["/root/httpd"]

构建builder镜像:

#  docker build -t repodemo/httpd-alpine-builder:latest -f Dockerfile.build.alpine .

# docker images

REPOSITORY                        TAG         IMAGE ID            CREATED          SIZE
repodemo/httpd-alpine-builder    latest     d5b5f8813d77      About a minute ago   275MB

执行“胶水”命令:

# docker create --name extract-httpserver repodemo/httpd-alpine-builder
# docker cp extract-httpserver:/go/src/httpd ./httpd
# docker rm -f extract-httpserver
# docker rmi repodemo/httpd-alpine-builder

构建目标镜像:

# docker build -t repodemo/httpd-alpine -f Dockerfile.target.alpine .

# docker images

REPOSITORY                 TAG           IMAGE ID            CREATED             SIZE
repodemo/httpd-alpine     latest       895de7f785dd        13 seconds ago      16.2MB

16.2MB!目标镜像的Size降为不到原来的十分之一。

多阶段构建

由于上面的整个构建过程十分繁琐,我们需要准备两个Dockerfile、需要准备“胶水”命令、需要清理中间产物等。但幸运的是,从Docker 17.05.0-ce以后就有了Docker引擎对多阶段构建(multi-stage build)的支持。

于是,我们就按照“多阶段构建”的语法将上面的Dockerfile.build.alpine和Dockerfile.target.alpine合并到一个Dockerfile中

//Dockerfile

FROM golang:alpine as builder

WORKDIR /go/src
COPY httpserver.go .

RUN go build -o httpd ./httpserver.go

From alpine:latest

WORKDIR /root/
COPY --from=builder /go/src/httpd .
RUN chmod +x /root/httpd

ENTRYPOINT ["/root/httpd"]

与之前Dockefile最大的不同在于在支持多阶段构建的Dockerfile中我们可以写多个“From baseimage”的语句了,每个From语句开启一个构建阶段,并且可以通过“as”语法为此阶段构建命名(比如这里的builder)。我们还可以通过COPY命令在两个阶段构建产物之间传递数据,比如这里传递的httpd应用,这个工作之前我们是使用“胶水”代码完成的。

 

跨越平台:从零开始

我们知道,构建镜像的过程一般都是从父镜像开始的,但父镜像依赖于指令集架构。所以有没有可能我们跨越平台架构来构建精简的镜像,那就需要用到scratch了。

为镜像进一步减重,减到尽可能的小,把所有不必要的东西都拆掉:仅仅保留能支撑我们应用运行的必要库,命令,其余的一律不纳入目标镜像。当然不仅仅是Size上的原因,小镜像还有额外的优势,比如:内存占用小,启动速度快,更加高效;因为它不包含额外不必要的工具,所以它更安全,不会因为其他不必要的工具、库的漏洞而被攻击,减少了“攻击面”。

scratch镜像很小,因为它基本上是空的,除了有点被docker添加的metadata(元数据:描述数据的数据)。可以用以下命令构建这个scratch镜像(前官方描述)(scratch镜像不可以直接从docker官方拉取下来):

# tar cv -f /dev/null | docker import - scratch

tar: Cowardly refusing to create an empty archive
Try `tar --help' or `tar --usage' for more information.
sha256:b9ce8716e13f8daf717b5c1ef7fe5e99685008c26074d0b0143e98c56a2e8875

# docker images | grep scratch

REPOSITORY           TAG                 IMAGE ID            CREATED           SIZE
scratch             latest              b9ce8716e13f        3 minutes ago       0B

官方对scratch的描述如下:an explicitly empty image, especially for building images “FROM scratch”

scratch构建镜像精简到可以只在scratch上面加一层二进制可执行文件。

那我们用scratch构建镜像需要注意什么呢?

在构建二进制可执行文件的时候,需要进行静态编译链接,因为scratch中没有我们需要的动态链接库

CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o empty –a -ldflags '-s' emptyImageProject/

CGO_ENABLED环境变量表示使用cgo编译器,而不是go编译器。-a参数表示要重建所有的依赖。否则,还是以动态链接依赖为结果。-ldflags '-s' 一个不错的额外标志,它可以缩减生成的可执行文件约50%的大小,GOARCH可以指定你要生成的二进制文件是基于哪个指令集架构平台的,这是go语言特有的交叉编译的特性。

我们可以查看go语言支持的指令集架构有哪些:

# go tool dist list

android/386
android/amd64
android/arm
android/arm64
darwin/386
darwin/amd64
darwin/arm
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
nacl/386
nacl/amd64p32
nacl/arm
netbsd/386
netbsd/amd64
netbsd/arm
openbsd/386
openbsd/amd64
openbsd/arm
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
windows/386
windows/amd64

最后编译好二进制文件后,静态编译链接后的二进制文件为empty。根据Dockerfile构建镜像,基于scratch构建镜像的Dockerfile如下:

FROM scratch
ADD empty /
CMD ["/empty"]

在此附上二进制源代码地址:https://github.com/wangzy0327/emptyImageProject

到此,从零构建镜像就介绍完毕,如有当之处,还请大家批评指正。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 `FROM scratch` 构建 CentOS 镜像需要一定的技术基础,因为 `FROM scratch` 基础镜像是一个空白镜像,没有任何文件系统和工具。这意味着你需要手动构建 CentOS 文件系统并安装必要的软件包。以下是基本步骤: 1. 下载 CentOS 的基础文件系统 可以从 CentOS 官网下载最新版本的基础文件系统。你可以选择 minimal 或 DVD 版本,但是需要注意的是,这个文件系统必须是 tar 格式的。 2. 解压缩 CentOS 基础文件系统 使用 tar 命令解压缩基础文件系统,例如: ``` tar -xvf centos-7-minimal.tar.xz -C /path/to/centos-rootfs ``` 3. 在 CentOS 文件系统中安装必要的软件包 使用 chroot 命令进入 CentOS 文件系统,并在其中安装必要的软件包,例如: ``` chroot /path/to/centos-rootfs /bin/bash yum install -y software-package-1 software-package-2 ``` 4. 退出 chroot 环境 使用 exit 命令退出 chroot 环境。 5. 创建 Dockerfile 在 CentOS 文件系统的根目录下创建 Dockerfile 文件,添加以下内容。 ``` FROM scratch LABEL maintainer="Your Name <your_email@domain.com>" # 添加 CentOS 文件系统 ADD centos-rootfs / # 设置环境变量 ENV LANG en_US.UTF-8 # 安装软件包 RUN yum install -y software-package-1 software-package-2 && yum clean all # 添加自定义文件 COPY custom-file /path/to/custom-file # 设置工作目录 WORKDIR /path/to/workdir # 运行命令 CMD ["command", "arg1", "arg2"] ``` 6. 构建镜像Dockerfile 文件所在目录下执行以下命令构建镜像。 ``` docker build -t my-centos-image . ``` 7. 运行容器 使用以下命令运行容器。 ``` docker run -it my-centos-image ``` 以上就是使用 `FROM scratch` 构建 CentOS 镜像的基本步骤。需要注意的是,这种方法需要手动构建文件系统和安装软件包,比较繁琐,但可以得到更加精简的镜像

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值