Kubernetes要从容器化开始,而容器又须要从Dockerfile开始,本文将介绍如何写出一个优雅的Dockerfile文件。python
文章主要内容包括:linux
Docker容器git
Dockerfilegithub
使用多阶构建golang
感谢公司提供大量机器资源及时间让咱们能够实践,感谢在此专题上不断实践的部分项目及人员的支持。 docker
咱们都知道容器就是一个标准的软件单元,它有如下特色:ubuntu
随处运行:容器能够将代码与配置文件和相关依赖库进行打包,从而确保在任何环境下的运行都是一致的。服务器
高资源利用率:容器提供进程级的隔离,所以能够更加精细地设置CPU和内存的使用率,进而更好地利用服务器的计算资源。运维
快速扩展:每一个容器均可做为单独的进程予以运行,而且能够共享底层操做系统的系统资源,这样一来能够加快容器的启动和中止效率。测试
目前市面上的主流容器引擎有Docker、Rocket/rkt、OpenVZ/Odin等等,而独霸一方的容器引擎就是使用最多的Docker容器引擎。
Docker容器是与系统其余部分隔离开的一系列进程,运行这些进程所需的全部文件都由另外一个镜像提供,从开发到测试再到生产的整个过程当中,Linux 容器都具备可移植性和一致性。相对于依赖重复传统测试环境的开发渠道,容器的运行速度要快得多,而且支持在多种主流云平台(PaaS)和本地系统上部署。Docker容器很好地解决了“开发环境能正常跑,一上线就各类崩”的尴尬。
Docker容器的特色:
轻量:容器是进程级的资源隔离,而虚拟机是操做系统级的资源隔离,因此Docker容器相对于虚拟机来讲能够节省更多的资源开销,由于Docker容器再也不须要GuestOS这一层操做系统了。
快速:容器的启动和建立无需启动GuestOS,能够实现秒级甚至毫秒级的启动。
可移植性:Docker容器技术是将应用及所依赖的库和运行时的环境技术改造包成容器镜像,能够在不一样的平台运行。
自动化:容器生态中的容器编排工做(如:Kubernetes)可帮助咱们实现容器的自动化管理。
Dockerfile是用来描述文件的构成的文本文档,其中包含了用户能够在使用行调用以组合Image的全部命令,用户还可使用Docker build实现连续执行多个命令指今行的自动构建。
经过编写Dockerfile生磁镜像,能够为开发、测试团队提供基本一致的环境,从而提高开发、测试团队的效率,不用再为环境不统一而发愁,同时运维也能更加方便地管理咱们的镜像。
Dockerfile的语法很是简单,经常使用的只有11个:
编写优雅的Dockerfile主要须要注意如下几点:
Dockerfile文件不宜过长,层级越多最终制做出来的镜像也就越大。
构建出来的镜像不要包含不须要的内容,如日志、安装临时文件等。
尽可能使用运行时的基础镜像,不须要将构建时的过程也放到运行时的Dockerfile里。
只要记住以上三点就能写出不错的Dockerfile。
为了方便你们了解,咱们用两个Dockerfile实例进行简单的对比:
FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y apt-utils libjpeg-dev \ python-pip RUN pip install --upgrade pip RUN easy_install -U setuptools RUN apt-get clean
FROM ubuntu:16.04 RUN apt-get update && apt-get install -y apt-utils \ libjpeg-dev python-pip \ && pip install --upgrade pip \ && easy_install -U setuptools \ && apt-get clean
咱们看第一个Dockerfile,乍一看条理清晰,结构合理,彷佛还不错。再看第二个Dockerfile,紧凑,不易阅读,为何要这么写?
第一个Dockerfile的好处是:当正在执行的过程某一层出错,对其进行修正后再次Build,前面已经执行完成的层不会再次执行。这样能大大减小下次Build的时间,而它的问题就是会因层级变多了而使镜像占用的空间也变大。
第二个Dockerfile把全部的组件所有在一层解决,这样作能必定程度上减小镜像的占用空间,但在制做基础镜像的时候若其中某个组编译出错,修正后再次Build就至关于重头再来了,前面编译好的组件在一个层里,得所有都从新编译一遍,比较消耗时间。
从下表能够看出两个Dockerfile所编译出来的镜像大小:
$ docker images | grep ubuntu REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu 16.04 9361ce633ff1 1 days ago 422MB ubuntu 16.04-1 3f5b979df1a9 1 days ago 412MB
呃…. 好像并无特别的效果,但若Dockerfile很是长的话能够考虑减小层次,由于Dockerfile最高只能有127层。
Docker在升级到Docker 17.05以后就能支持多阶构建了,为了使镜像更加小巧,咱们采用多阶构建的方式来打包镜像。在多阶构建出现以前咱们一般使用一个Dockerfile或多个Dockerfile来构建镜像。
在多阶构建出来以前使用单个文件进行构建,单文件就是将全部的构建过程(包括项目的依赖、编译、测试、打包过程)所有包含在一个Dockerfile中之下:
FROM golang:1.11.4-alpine3.8 AS build-env ENV GO111MODULE=off ENV GO15VENDOREXPERIMENT=1 ENV BUILDPATH=github.com/lattecake/hello RUN mkdir -p /go/src/${BUILDPATH} COPY ./ /go/src/${BUILDPATH} RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v CMD [/go/bin/hello]
这种的作法会带来一些问题:
Dockerfile文件会特别长,当须要的东西愈来愈多的时候可维护性指数级将会降低;
镜像层次过多,镜像的体积会逐步增大,部署也会变得愈来愈慢;
代码存在泄漏风险。
以Golang为例,它运行时不依赖任何环境,只须要有一个编译环境,那这个编译环境在实际运行时是没有任务做用的,编译完成后,那些源码和编译器已经没有任务用处了也就不必留在镜像里。
上表能够看到,单文件构建最终占用了312MB的空间。
在多阶构建出来以前有没有好的解决方案呢?有,好比采用多文件构建或在构建服务器上安装编译器,不过在构建服务器上安装编译器这种方法咱们就不推荐了,由于在构建服务器上安装编译器会致使构建服务器变得很是臃肿,须要适配各个语言多个版本、依赖,容易出错,维护成本高。因此咱们只介绍多文件构建的方式。
多文件构建,其实就是使用多个Dockerfile,而后经过脚本将它们进行组合。假设有三个文件分别是:Dockerfile.run、Dockerfile.build、build.sh。
Dockerfile.run就是运行时程序所必须须要的一些组件的Dockerfile,它包含了最精简的库;
Dockerfile.build只是用来构建,构建完就没用了;
build.sh的功能就是将Dockerfile.run和Dockerfile.build进行组成,把Dockerfile.build构建好的东西拿出来,而后再执行Dockerfile.run,算是一个调度的角色。
Dockerfile.build
FROM golang:1.11.4-alpine3.8 AS build-env ENV GO111MODULE=off ENV GO15VENDOREXPERIMENT=1 ENV BUILDPATH=github.com/lattecake/hello RUN mkdir -p /go/src/${BUILDPATH} COPY ./ /go/src/${BUILDPATH} RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v
Dockerfile.run
FROM alpine:latest RUN apk –no-cache add ca-certificates WORKDIR /root ADD hello . CMD ["./hello"]
Build.sh
#!/bin/sh docker build -t –rm hello:build . -f Dockerfile.build docker create –name extract hello:build docker cp extract:/go/bin/hello ./hello docker rm -f extract docker build –no-cache -t –rm hello:run . -f Dockerfile.run rm -rf ./hello
执行build.sh完成项目的构建。
从上表能够看到,多文件构建大大减少了镜像的占用空间,但它有三个文件须要管理,维护成本也更高一些。
最后咱们来看看万众期待的多阶构建。
完成多阶段构建咱们只须要在Dockerfile中屡次使用FORM声明,每次FROM指令可使用不一样的基础镜像,而且每次FROM指令都会开始新的构建,咱们能够选择将一个阶段的构建结果复制到另外一个阶段,在最终的镜像中只会留下最后一次构建的结果,这样就能够很容易地解决前面提到的问题,而且只须要编写一个Dockerfile文件。这里值得注意的是:须要确保Docker的版本在17.05及以上。下面咱们来讲说具体操做。
在Dockerfile里可使用as来为某一阶段取一个别名”build-env”:
FROM golang:1.11.2-alpine3.8 AS build-env
而后从上一阶段的镜像中复制文件,也能够复制任意镜像中的文件:
COPY –from=build-env /go/bin/hello /usr/bin/hello
看一个简单的例子:
FROM golang:1.11.4-alpine3.8 AS build-env ENV GO111MODULE=off ENV GO15VENDOREXPERIMENT=1 ENV GITPATH=github.com/lattecake/hello RUN mkdir -p /go/src/${GITPATH} COPY ./ /go/src/${GITPATH} RUN cd /go/src/${GITPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -v FROM alpine:latest ENV apk –no-cache add ca-certificates COPY --from=build-env /go/bin/hello /root/hello WORKDIR /root CMD ["/root/hello"]
执行docker build -t –rm hello3 .后再执行docker images ,而后咱们来看镜像的大小:
多阶构建给咱们带来不少便利,最大的优点是在保证运行镜像足够小的状况下还减轻了Dockerfile的维护负担,所以咱们极力推荐使用多阶构建来将你的代码打包成Docker 镜像。
内容来源:宜信技术学院