镜像是一个打包文件,其中包含了应用程序及其运行所依赖的环境,例如文件系统、环境变量、配置参数等等
联合文件系统
容器镜像内部并不是一个平坦的结构,而是由许多的镜像层组成,每层都是只读不可修改修改的一组文件,相同的层可以在镜像之间共享,然后多个层像搭积木一样堆叠起来,使用一种叫"Union FS联合文件系统"的技术将它们合并在一起,形成了容器最终看到的文件系统
使用docker inspect
可查看镜像的分层信息
$ sudo docker inspect nginx:alpine
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:ec34fcc1d526fba48f7f88e4ec765fccc17d4692570db85cf32d9d6b020330f2",
"sha256:5c497738d8721551c4835d7860e90259e10a1396c988377654fdfa775bf6f3a5",
"sha256:a2a3844a950f5f1872001e95f272cf298f60e5afdea9ac5456be96283038f972",
"sha256:6dec34c67396c2da53c440189accfc7359b6d6f29f1fe028c056f75f2c3b51a4",
"sha256:5b75fcec28b0cfd2ea2143d9de81409e98f4ed2c396877b45522919e0821bea0",
"sha256:7d4368f5306ea780e22b1bc0d0960ba2a5b84b7488d9e2eaacdfcabffd10ca35"
]
},
...
Dockerfile
Dockerfile是一个纯文本,里面记录了一系列构建指令,比如选择基础镜像、拷贝文件、运行脚本等等,每个指令都会生成一个Layer,而Docker顺序执行这个文件里的所有步骤,最后创建出一个新的镜像
创建一个最简单的Dockerfile:
FROM busybox
CMD echo "hello world"
该文件里仅有两条指令,第一条指令是FROM,所有的Dockerfile都要从它开始,表示选择构建使用的基础镜像
第二条指令是CMD,指定了docker run启动容器时默认运行的命令,这里使用echo命令,输出字符串"hello world"
使用docker build
命令创建出镜像:
$ sudo docker build -f Dockerfile.busybox .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
---> 62aedd01bd85
Step 2/2 : CMD echo "hello world"
---> Running in 6047b8d8741c
Removing intermediate container 6047b8d8741c
---> 35c82ebb67d7
Successfully built 35c82ebb67d7
命令的格式: 用-f参数制定Dockerfile文件名,后面必须跟一个文件路径,叫做"构建上下文",这里使用了点号,表示当前路径
$ sudo docker run 35c
hello world
新的镜像暂时没有名字,使用"IMAGE ID"可以指定它,如上通过指定路径前缀运行容器
Docker会逐行地读取并执行Dockerfile里的指令,依次创建镜像层,再生成完整的镜像
简单编写方法
构建镜像的第一条指令必须是FROM
,所以基础镜像的选择非常关键,关注镜像的安全性和大小的话,一般选择Alpine,如果关注的是应用的运行稳定性,那么可能会选择Ubuntu、Debian、CentOS
FROM alpine:3.15 # 选择Alpine镜像
FROM ubuntu:bionic # 选择Ubuntu镜像
COPY
命令可以将文件放到容器中,但源文件路径必须是"构建上下文"路径下
COPY ./a.txt /tmp/a.txt # 将构建上下文中的a.txt拷贝到镜像的/tmp目录里
Dockerfile中最重要的指令是RUN,它可以执行任意shell命令,比如更新系统、安装应用、下载文件、创建目录、编译程序等等,实现任意的镜像构建步骤,其中一条指令只能是一行,所以有的RUN指令会在每行的末尾使用续行符,命令之间也会用&&来连接,如下所示:
RUN apt-get update \
&& apt-get install -y \
build-essential \
curl \
make \
unzip \
&& cd /tmp \
&& curl -fSL xxx.tar.gz -o xxx.tar.gz\
&& tar xzf xxx.tar.gz \
&& cd xxx \
&& ./config \
&& make \
&& make clean
也可以将这些Shell命令集中到一个脚本文件里,用COPY命令拷贝进去再用RUN执行:
COPY setup.sh /tmp/ # 拷贝脚本到/tmp目录
RUN cd /tmp && chmod +x setup.sh \ # 添加执行权限
&& ./setup.sh && rm setup.sh # 运行脚本然后再删除
RUN指令实际上就是Shell编程,使用ARG和ENV可以创建变量,区别在于ARG创建的变量只在镜像构建过程中可见,容器运行时不可见,而ENV创建的变量不仅能够在构建镜像的过程中使用,在容器运行时也能够以环境变量的形式被应用程序使用
ARG IMAGE_BASE="node"
ARG IMAGE_TAG="alpine"
ENV PATH=$PATH:/tmp
ENV DEBUG=OFF
还有一个重要的指令是EXPOSE,它用来声明容器对外服务的端口号
EXPOSE 443
EXPOSE 53/udp
每个指令都会生成一个镜像层,所以Dockerfile里最好不要滥用指令,尽量精简合并,否则太多的层会导致镜像臃肿不堪