概述
这是一个Kubernetes系列文章,主要将我所学过,所使用过的经验、使用方法等记录在此。将会持续更新。
继续上一篇讲述,上一篇主要讲述了docker运行容器和其他的基础使用方法,具体可以在《docker基础使用》这片文章中找到使用方法。
本篇主要讲述的是如何打包及编译Docker的镜像,如何自己制作属于自己的镜像,以及如何减小打包体积等内容。
介绍
Docker Hub官方提供的包固然简单,但是这些包都只是提供了最基础的配置和启动方式,如果我们要进行参数配置或者添加其他守护进程,就会非常麻烦,这时,Dockerfile出现了,它可以让我们自定义Docker镜像。
Dockerfile是一个文本文件,其中包含了一条条的指令,每一条指令就是一层镜像。
这里我们还是以之前的nginx镜像为例,这次使用Dockerfile来定制一个自己的Docker镜像。先建立一个文件,文件名为:Dockerfile。
[root@localhost opt]# touch Dockerfile [root@localhost opt]# cat Dockerfile FROM centos RUN yum install -y nginx CMD ["/usr/sbin/nginx", "-g","daemon off;"]
复制
上面的Dockerfile比较简单,只涉及到了三条命令:
FROM:
指定基础镜像,意思就是说打包到nginx是基于哪个镜像的,这里指定的是centos镜像。
基础镜像可以是系统,如:centos、debian、alpine等系统。
如果觉得体积过大,也可以使用其他非系统镜像。如:nginx、mongodb、redis、mysql等。
除此之外,Docker还提供了一个特殊的基础镜像,名为:scratch。这个镜像是一个虚拟镜像,也是一个空白镜像,不存放任何东西。这种镜像在编译Go等静态编译程序来说非常有用,可以使整个镜像的体积更加小巧。
FROM scratch ...
复制
RUN:
在编译时运行的命令,这里为需要一个nginx镜像,所以基于centos镜像直接yum安装,如果是基于debian,那么就应该是apt-get安装了。
前面的时候说过,Docker的每一个指令都会创建一层镜像,RUN指令也不例外。每多一层,就会多占一层的体积,所以,在写RUN指令时,需要把命令写到一起,如:
RUN yum install -y nginx &&
mkdir -p /test
这样,将需要执行的命令以&&符号结合起来,可以非常显著的减少整体体积。
CMD:
在容器运行的时候,要执行什么命令.
Docker在启动的时候,容器内的进程必须是在前台启动的,否则会导致Docker容器启动失败,这里为关闭了nginx的daemon,在前台启动。
开始构建
现在,有了上面的Dockerfile之后,让我们开始镜像的构建,在 Dockerfile 文件所在目录执行:
[root@localhost opt]# docker build -t nginx:v1 .
Sending build context to Docker daemon 376MB
Step 1/3 : FROM centos
---> 0d120b6ccaa8
Step 2/3 : RUN yum install -y nginx
---> Running in 91689e23418d
CentOS-8 - AppStream 423 kB/s | 5.8 MB 00:13
CentOS-8 - Base 392 kB/s | 2.2 MB 00:05
CentOS-8 - Extras 11 kB/s | 7.3 kB 00:00
中间略...
Complete!
Removing intermediate container 91689e23418d
---> 4ed7f8415be4
Step 3/3 : CMD [“nginx”]
---> Running in 6d6be524b879
Removing intermediate container 6d6be524b879
---> 5c0e7169801a
Successfully built 5c0e7169801a
Successfully tagged nginx:v1
复制
从上面的输出结果中,可以清晰的看出每一步在做什么。这里使用了 docker build 命令 进行镜像构建。格式为:
docker build <参数> <路径> 参数一般为-t,后面跟打包的名称和版本 路径如果和Dockerfile同一及目录,那么就以一个点来表示(.),这里的点点意思是当前目录,也就是Dockerfile所在目录 如果要自己指定其他的文件名,可以使用-f参数来指定
复制
以上、是一个非常简单的镜像创建,如果创建比较复杂的,也是按照这种格式来编写,示例:
[root@localhost opt]# cat Dockerfile FROM centos:latest COPY libjemalloc.so.2 /usr/lib/libjemalloc.so COPY libnuma.so.1 /usr/lib64/libnuma.so.1 COPY mysql_docker-5.7.26-1.el7.x86_64.rpm /root/ COPY my.cnf /etc/my.cnf COPY run.sh /root/run.sh COPY create.sql /root/create.sql COPY bc /usr/bin/ WORKDIR /root RUN yum install -y libaio ENV PATH $PATH:/app/mysql/bin EXPOSE 3307 ENTRYPOINT ["run.sh"] CMD ["mysqld"]
复制
以上是我自己打的rpm包,复制到容器里,然后直接rpm -ivh安装,这样可以显著减小打包的体积。
那么,问题来了,我们如何减小体积,或者说我们应该遵守什么样的规则编写Dockerfile使其最优化?
首先,来自官方文档的最佳实践:Dockerfile最佳实践
Dockerfile最佳实践
编写Dockerfile的准则:
- 容器应该尽可能的短暂
通过Dockerfile构建的镜像所启动的容器的生命周期应该尽可能的短暂。创建、停止、销毁一个容器的配置或者准备工作,应该尽可能的少。 - 构建上下文目录
通常将当前工作目录称为上下文,也就是发出docker build的所在目录。在构建时,总是会把上下文目录的所有文件都发送到docker的守护进程中去。所以,在构建时,尽可能选择空的目录,并且当前目录只有必须文件。否则,生成的镜像文件将会很大。 - 通过stdin来构建
一种不需要编写Dockerfile的办法是在命令行使用管道,或者EOF来重定向,如:
echo -e 'FROM busybox\nRUN echo "hello world"' | docker build - docker build -<<EOF FROM busybox RUN echo "hello world" EOF
复制
并且,这样的构建方式并不需要想docker守护进程发送上下文,命令格式:
docker build [OPTIONS] -
复制
- 使用.dockerignore
使用.dockerignore文件来排除某些不需要的文件或目录,用以提高构建速度,和Dockerfile放到一个目录下。 - 使用多阶段构建
由于镜像是在生成过程的最后阶段产生的,因此可以利用生成缓存来最小化镜像层。也就是将多个FROM合并到一个Dockerfile文件中,类似这样:
FROM golang:1.7.3 WORKDIR /go/src/github.com/alexellis/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/alexellis/href-counter/app . CMD ["./app"]
复制
- 最小化构建层数
主要手段就是合并命令,将多个指令合并为一个指令,如:
RUN apt-get update RUN apt-get install -y bzr RUN apt-get install -y cvs RUN apt-get install -y git
复制
这样的指令可以合并为一个:
RUN apt-get update && apt-get install -y \ bzr \ cvs \ git
复制
其他指令,诸如COPY、ADD等都可以进行合并,以减小构建的层数。
7. 避免安装额外的包
在安装的时候,应该尽量避免安装额外的包,只需要安装服务运行所需即可,这将大大减短构建的时间和构建的大小。
Docker指令
FROM:
尽可能选择小的基础镜像,如Alpine镜像,一个非常精简的Linux系统,大小只有5M左右
LABLE:
在编写Dockerfile时,可以在当中增加一些标签,增加阅读性,组织镜像,辅助构建等,示例:
# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""
在1.10版本之前,这样写是不可以的,因为每个LABLE标签都会创建一个层,之后的版本不会。
RUN:
在构建时要运行一些shell命令,这时就需要使用RUN指令来构建这一层了,我们可以把要执行的shell命令都用&&符和\符号连接起来,以减少层的目的,示例:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git
CMD:
用于执行镜像中的命令,格式为:CMD ["执行的命令", "参数1", "参数2"...],该命令需要一个交互式的shell,以避免容器启动失败。
EXPOSE:
用于指定容器监听的端口,这个端口为镜像的运行端口。在外部,需要使用-p参数来暴露到宿主机端口上。
ENV:
容器内环境变量,设定一些环境变量,可以直接在其他指令调用。
ADD 和 COPY:
两者的功能相似,但是ADD比COPY多了本地tar提取等功能,更适合于tar包等拷贝。一般使用COPY更好一些。
ENTRYPOINT:
可以使用CMD指令中的选项来作为传入参数,如:CMD ["help"] ENTRYPOINT ["run.sh"],那么run.sh脚本的第一个传入参数就为help,可以在run.sh中使用这个参数。
同时,这个指令作为整个镜像启动的命令。
VOLUME:
顾名思义,就是用来暴露存储文件,配置文件,或容器创建的文件和目录。
USER:
切换到普通用户执行。
WORKDIR:
当前的工作目录,可以指定一个目录,以这个目录为基准进行操作,而不必每次都要输入绝对路径。
复制
镜像的常规操作
镜像的迁移
Docker提供了导出和导入镜像的命令,可以随时随地迁移整个Docker镜像。
这两个命令分别是:导出(docker save),导入(docker load)。
可以把镜像导出为一个tar文件,然后在另一台机器上导入。这是在没有Docker Registry 时的做法,现在已经不推荐。最佳应该使用Docker Registry或者Docker Hub来做迁移工作。
使用docker save可以将镜像保存为归档文件,比如想要将下面的nginx:v1镜像导出:
[root@localhost opt]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx v1 8415435e2a6e 2 hours ago 289MB [root@localhost opt]# docker save nginx:v1 -o nginx.tar [root@localhost opt]# ls containerd Dockerfile innodb_ruby nginx.tar rh 导出为一个名字叫nginx.tar的文件
复制
现在,我们把nginx.tar文件传送到另一台机器上,然后导入
[root@localhost opt]# docker load -i nginx.tar Loaded image: nginx:v1 [root@localhost opt]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx v1 8415435e2a6e 2 hours ago 289MB
复制
关于Docker Registry的讲述,将在下一篇讲解。