[TOC]
0x01 Dockerfile 镜像构建浅析与实践 描述:Dockerfile是一个文本格式的配置文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 用户可以使用Dockerfile快速创建自定义的镜像;通过它所支持的内部指令,以及使用它创建镜像的基本过程,Docker拥有”一点修改代替大量更新”的灵活之处;
文本化的镜像生成操作让其方便版本管理和自动化部署;
每条命令对应镜像的一层,细化操作后保证其可增量更新,复用镜像块减小镜像体积(后面您会体验到);
总结为一点就是将每一层修改、安装、构建、操作命令
都写入到一个脚本之中。
weiyigeek.top-体系
温馨提示: 一个镜像构建时不能超过 127 层,我们需要保证了稳定的变化的命令至于上层保证了每层打包出来的 Layer 能够尽可能的复用,而不会徒增镜像的大小,影响后续拉取镜像的速度;
1.基本结构 Dockerfile分为四个部分:
基础镜像信息:FROM \<image\> 或者 FROM \<image\>:\<tag\>
维护者信息: MAINTAINER (建议使用LABEL标签进行替代,先已丢弃
)
镜像标签信息: LABEL
镜像操作指令: RUN
容器启动时执行指令: CMD
例如:在/opt/目录中利用dockerfile创建一个基于ubuntu的nginx容器与vnc服务;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 FROM ubuntu MAINTAINER weiyigeek weiyigeek@qq.com LABEL version="1.0" LABEL maintainer="weiyigeek@163.com" WORKDIR /opt/demo ADD /opt/package /data/ ENV MYSQL_VERSION 5.6 RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main uiverse" >> /etc/apt/source.list RUN apt-het update && apt-get install -y nginx \ net-tools mysql-server="${MYSQL_VERSION} " RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf CMD /usr/sbin/nginx
weiyigeek.top-dockerfile
在编写完成Dockerfile之后可以通过docker build 命令来创建镜像
,该命令读取指定路径下(包括子目录)的dockerfile(实际上是构建上下文Context),并将该路径下的内容发送给Docker服务端由它创建镜像;
因此一般建议放置Dockerfile的目录为空另外可以通过dockerignore文件(每一行添加一条匹配模式
)会让Docker忽略路径下的目录和文件;
docker 镜像生成常用命令:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 docker build [选项] - t :指定标签信息 --build-arg <参数名>=<值> $docker build -t [TAG/version] /tmp/docker_builder$docker build https://github.com/twang2218/gitlab-ce-zh.git$docker build http://server/context.tar.gz $docker build - < Dockerfile$cat Dockerfile | docker build -$docker build - < context.tar.gz
2.指令浅析 Dockerfile指令参数(Instruction arguments)如下,可以参考Docker官方有关dockerfile指令介绍的详细文档。
https://docs.docker.com/engine/reference/builder
1) FROM - 基础镜像信息 描述:尽可能使用官方镜像或者信任的镜像作为你构建镜像的基础设施,推荐使用[Alpine](https://hub.docker.com/_/alpine/)
镜像,因为它被严格控制并保持最小尺寸(目前小于 6 MB),但它仍然是一个完整的发行版。1 2 3 4 5 6 FROM <image> 或者 FROM <image>:<tag> FROM alpine FROM golang:1.9-alpine as builder
2) LABEL - 标签信息 描述: 可以给镜像添加标签来帮助组织镜像、记录许可信息、辅助自动化构建等
注意: 如果你的字符串包含空格,那么它必须被引用或者空格必须被转义。如果您的字符串包含内部引号字符(”),则也可以将其转义。
1 2 3 4 5 6 7 8 9 10 LABEL key=<value> LABEL maintainer="WeiyiGeek" LABEL vendor="ACME Incorporated" LABEL version=1.1 LABEL com.example.version="0.0.1-beta" LABEL com.example.release-date="2015-02-12"
Tips(1): 在 1.10 之前,建议将所有标签合并为一条LABEL
指令,以防止创建额外的层,但是现在这个不再是必须的了,以上内容也可以写成下面这样:1 2 3 4 5 LABEL vendor=ACME\ Incorporated \ version=1.1 \ com.example.version="0.0.1-beta" \ com.example.release-date="2015-02-12"
3)MAINTAINER - 维护者信息 1 2 3 4 5 MAINTAINER key=<value> MAINTAINER WeiyiGeek master@weiyigeek.top
4)RUN - 镜像操作命令: 描述:为了保持 Dockerfile 文件的可读性,以及可维护性,建议将长的或复杂的RUN
指令用反斜杠\
分割成多行。
RUN 指令最常见的用法是安装包用的apt-get
,因为该指令会安装包,所以有几个问题需要注意。
不要使用 RUN apt-get upgrade 或 dist-upgrade
, 如果基础镜像中的某个包过时了,你应该联系它的维护者。
如果你确定某个特定的包比如 foo 需要升级,使用 apt-get install -y foo
就行,该指令会自动升级 foo 包。
最好将 RUN 多条语句汇集成为一条 apt-get update
和 apt-get install
以及 rm -rf /var/lib/apt/lists*
组合成一条 RUN 声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 RUN <COMMAND> 或者 RUN ["executable" ,"param1" ,"param2" ] 如:RUN ["/bin/bash" ,"-c" ,"echo Hello" ],当命令较长时可以使用\来换行; RUN apt-get update;\ apt-get install -y nginx ;\ rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ curl \ dpkg-sig \ libcap-dev \ libsqlite3-dev \ mercurial \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.* \ && rm -rf /var/lib/apt/lists/*
Tips(1):为何建议将RUN写在一行目录之中?
1.比如假设你有一个 Dockerfile 文件:将 apt-get update 放在一条单独的 RUN 声明中会导致缓存问题以及后续的 apt-get install 失败。1 2 3 FROM ubuntu:18.04 RUN apt-get update RUN apt-get install -y curl
2.构建镜像后所有的层都在 Docker 的缓存中。假设你后来又修改了其中的 apt-get install
添加了一个包。1 2 3 FROM ubuntu:18.04 RUN apt-get update RUN apt-get install -y curl nginx
3.Docker 发现修改后的 RUN apt-get update 指令和之前的完全一样。所以apt-get update
不会执行,而是使用之前的缓存镜像。因为 apt-get update 没有运行后面的 apt-get install
可能安装的是过时的 curl 和 nginx 版本。
解决办法:采用cache busting(缓存破坏)的方式进行:1 2 3 4 5 6 7 RUN apt-get update && apt-get install -y \ package-bar \ package-baz \ package-foo=1.3.*
Tips(2): 清理掉 apt 缓存 rm -rf /var/lib/apt/lists/*
可以减小镜像大小,因为 RUN 指令的开头为 apt-get udpate
包缓存总是会在 apt-get install
之前刷新。
注意:官方的 Debian 和 Ubuntu 镜像会自动运行 apt-get clean,所以不需要显式的调用 apt-get clean。
Tips(3): 挂载docker的设置的secret文件到指定目录文件中,
1 2 3 4 RUN --mount=type =secret,id=mysecret cat /run/secrets/mysecre RUN --mount=type =secret,id=mysecret,dst=/foobar cat /foobar
5)CMD- 容器启动时执行指令 描述:指令用于执行目标镜像中包含的软件和任何参数, 实际上为容器提供一个默认的执行命令。
在Dockerfile中CMD被用来为ENTRYPOINT指令提供参数,则CMD和ENTRYPOINT指令都应该使用exec格式
当基于镜像的容器运行时将会自动执行CMD指令, 并且如果在docker run命令中指定了参数,这些参数将会覆盖在CMD指令中设置的参数。
多数情况下CMD 都需要一个交互式的 shell (bash, Python, perl 等),例如 CMD [“perl”, “-de0”],或者 CMD [“PHP”, “-a”]。使用这种形式意味着,当你执行类似docker run -it python
时,你会进入一个准备好的 shell 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 CMD ["executable" ,"param1" ,"param2" ] CMD ["param1" ,"param2" ] CMD command param1 param2 CMD ["apache2" , "-DFOREGROUND" ] CMD echo $HOME
注意事项:
(1)如果用户启动容器指定了运行命令则会覆盖掉CMD指定命令,注意每个Dockerfile只能有一条CMD命令,如果指定了多条命令只有最后一条执行;
(2)CMD 在极少的情况下才会以 CMD [“param”, “param”] 的形式与ENTRYPOINT
协同使用,除非你和你的镜像使用者都对 ENTRYPOINT 的工作方式十分熟悉。
6)EXPOSE - 端口映射指令 描述:EXPOSE
指令用于指定容器将要监听的端口即默认向外部的暴露的服务端口。因此你应该为你的应用程序使用常见的端口。 对于外部访问,用户可以在执行 docker run 时使用一个标志来指示如何将指定的端口映射到所选择的端口。
1 2 3 4 5 6 7 8 9 10 11 12 EXPOSE <port> [<port>...] EXPOSE 22 80 8443 EXPOSE 80 27017 EXPOSE 30000-40000
7)ENV - 修改环境变量指令 描述:为了方便新程序运行,你可以使用ENV
来为容器中安装的程序更新 PATH 环境变量。例如使用ENV PATH /usr/local/nginx/bin:$PATH
来确保CMD ["nginx"]
能正确运行。类似于程序中的常量,该方法可以让你只需改变 ENV 指令来自动的改变容器中的软件版本。
1 2 3 4 5 6 7 8 9 10 11 12 ENV <key> <value> ENV PG_MAJOR 9.3 ENV PATH /usr/local /postgres-\$PG_MAJOR /bin:$PATH RUN curl -SL http://example.com/postgre-$PG_MAJOR .tar.xz && ENV PATH /usr/local /postgres-$PG_MAJOR /bin:$PATH ENV VERSION=1.0 DEBUG=on \ NAME="Happy Feet"
8)ARG - 构建参数 描述:构建参数和 ENV 的效果一样都是设置环境变量不同点就是容器构建完成则失效
;1 2 3 4 ARG <参数名> [=<默认值>] ${IMG_PATH}
9)ADD - 添加指定目录文件到镜像指令 描述:该命令将复制指定的源文件<src>
到镜像内中的目标文件<dest>
,其中<src>
可以是在Dockerfile所在的目录的一个相对路径(文件或者目录)/URL/tar文件(本地 tar 提取和远程 URL 支持
);
1 2 3 4 5 6 7 8 9 10 11 ADD <src> <dest> ADD rootfs.tar.xz ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz ADD --chown=55:mygroup files* /mydir/ ADD $PWD /opt/blog/
注意事项:
(1)为了让镜像尽量小,最好不要使用 ADD 指令从远程 URL 获取包,而是使用 curl 和 wget。1 2 3 4 5 6 7 8 9 10 11 ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things all RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.xz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all
10)COPY - 复制指定文件或者目录到容器中 描述:COPY只支持简单将本地文件拷贝到镜像中它比 ADD 更透明,所以ADD
和COPY
功能类似但一般优先使用 COPY ;
COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。
当目标路径不存在时候自动创建,当使用本地目录作为源目录时候推荐使用COPY:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 COPY <src> <dest> --from=多阶段构建的镜像名称 COPY package.json /usr/src/app/ COPY hom* /mydir/ COPY hom?.txt /mydir/ COPY --chown=55:mygroup files* /mydir/ COPY --from=0 /go/src/github.com/go/helloworld/app . COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
Tips:
对于其他不需要 ADD 的自动提取功能的文件或目录,你应该使用 COPY。
采用CPOY –from 从上一个构建阶段拷贝文件时,使用的路径是相对于上一阶段的根目录的,此时建议复制成果时候采用绝对路径;
11)ENTRYPOINT - 配置容器启动进入后的执行命令-应用运行前的准备工作 描述: 该指令是设置镜像的主命令,其作用是允许将镜像当成命令本身来运行(使用终端提供默认选项)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ENTRYPOINT ["executable" ,"param1" ,"param2" ] ENTRYPOINT command param1 param2 ENTRYPOINT ["s3cmd" ] CMD ["--help" ] FROM alpine ENTRYPOINT ["top" , "-b" ] CMD ["-c" ] $ docker run -it --rm --name alpine:test top -H PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top $ docker exec -it alpine:test ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -H root 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux FROM alpine ENTRYPOINT exec top -b
补充说明:
ENTRYPOINT 指令也可以结合一个辅助脚本使用,和前面命令行风格类似,即使启动工具需要不止一个步骤。
注意:该脚本使用了 Bash 的内置命令 exec,所以最后运行的进程就是容器的 PID 为 1 的进程。这样,进程就可以接收到任何发送给容器的 Unix 信号了。
1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash set -eif [ "$1 " = 'postgres' ]; then chown -R postgres "$PGDATA " if [ -z "$(ls -A "$PGDATA") " ]; then gosu postgres initdb fi exec gosu postgres "$@ " fi exec "$@ "
该辅助脚本被拷贝到容器,并在容器启动时通过 ENTRYPOINT 执行:1 2 COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh" ]
该脚本可以让用户用几种不同的方式和 Postgres 交互1 2 3 $ docker run postgres $ docker run postgres postgres --help $ docker run --rm -it postgres bash
2.通过ENTRYPOINT指令可以将容器设置作为可执行的文件
docker run <image>
命令行参数将会被追加到exec格式的ENTRYPOINT所有元素之后,并将会覆盖使用CMD指定的所有元素,此时是允许将参数传递到入口点;1 2 3 4 docker run -i -t --rm -p 80:80 nginx
下面的Dockerfile显示使用ENTRYPOINT在前台运行Apache:1 2 3 4 5 FROM debian:stable RUN apt-get update && apt-get install -y --force-yes apache2 EXPOSE 80 443 VOLUME ["/var/www" , "/var/log/apache2" , "/etc/apache2" ] ENTRYPOINT ["/usr/sbin/apache2ctl" , "-D" , "FOREGROUND" ]
注意:当指定多个ENTRYPOINT时候只有最后一个生效;
13)VOLUME- 创建本地主机或其他主机挂载点-定义匿名卷 描述:指令用于暴露任何数据库存储文件,配置文件,或容器创建的文件和目录。强烈建议使用 VOLUME来管理镜像中的可变部分和用户可以改变的部分。
VOLUME指令只是起到了声明了容器中的目录作为匿名卷,但是并没有将匿名卷绑定到宿主机指定目录的功能
;
镜像run了一个容器的时候,docker会在安装目录下的指定目录下面生成一个目录来绑定容器的匿名卷
(这个指定目录不同版本的docker会有所不同)1 2 3 4 5 6 VOLUME ["<路径1>" , "<路径2>" ...] VOLUME ["/data" ]
13)USER- 指定容器运行时名用户名或者UID 描述:如果某个服务不需要特权执行,建议使用 USER 指令切换到非 root 用户。
注意事项:
1.在镜像中用户和用户组每次被分配的 UID/GID 都是不确定的,下次重新构建镜像时被分配到的 UID/GID 可能会不一样。 如果要依赖确定的 UID/GID 你应该显示的指定一个 UID/GID。1 2 3 4 RUN groupadd -r postgres -g 1001 && useradd -r -g postgres postgres -u 1001 RUN useradd -r -u 1001 -U postgres
2.应该避免使用 sudo 命令因为它不可预期的 TTY 和信号转发行为可能造成的问题比它能解决的问题还多。如果你真的需要和 sudo 类似的功能(例如以 root 权限初始化某个守护进程,以非 root 权限执行它
)你可以使用 gosu 命令; 最后为了减少层数和复杂度,避免频繁地使用 USER 来回切换用户。
1 2 3 4 5 6 7 8 USER daemon RUN groupadd -r postgres && useradd -r -g postgres postgres USER postgres
14) WORKDIR - 配置工作目录 描述:为了清晰性和可靠性,你应该总是在WORKDIR
中使用绝对路径。另外你应该使用 WORKDIR 来替代类似于 RUN cd … && do-something 的指令,后者难以阅读、排错和维护。1 2 3 4 5 6 7 8 9 WORKDIR /path/to/workdir WORKDIR /a WORKDIR b WORKDIR C RUN pwd
15)ONBUILD - 为他人做嫁衣裳 描述:ONBUILD
是一个特殊的指令在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像去构建下一级镜像的时候才会被执行。它后面跟的是其它指令比如 RUN, COPY
等
Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。
为了方便我们理解先来看一个示例: 假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm 进行包管理,所有依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,需要先进行 npm install 才可以获得所有需要的依赖然后再通过 npm start 来启动应用。
Dockerfile1 2 3 4 5 6 7 FROM node:slimRUN mkdir /app WORKDIR /app COPY ./package.json /app RUN [ "npm" , "install" ] COPY . /app/ CMD [ "npm" , "start" ]
把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。
但是如果我们还有第二个 Node.js 项目也差不多呢? 好吧那就再把这个 Dockerfile 复制到第二个项目里那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题:如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile,再次构建,问题解决。
第一个项目没问题了,但是第二个项目呢? 虽然最初 Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。
那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢? 答案是显而易见这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,重新构建后就继承了基础镜像的更新?好吧,可以让我们看看这样的结果。 那么上面的这个 Dockerfile 就会变为:1 2 3 4 FROM my-node RUN mkdir /app WORKDIR /app CMD [ "npm" , "start" ]
现在把相关构建的指令拿出来放在子项目里面,此时假如基础镜像名称为my-node
各个项目内的自己Dockerfile变成如下:1 2 3 4 FROM node:slim COPY ./package.json /app RUN [ "npm" , "install" ] COPY . /app/
基础镜像变化后,各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新。
那么,问题解决了么? 没有准确说,只解决了一半。如果这个 Dockerfile 里面有些东西需要调整呢?比如 npm install 都需要加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,因为涉及到了当前项目的 ./package.json,难道又要一个个修改么?所以说这样制作基础镜像,只解决了原来的 Dockerfile 的前4条指令的变化问题,而后面三条指令的变化则完全没办法处理。
完整解决Dockerfile1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm" , "install" ] ONBUILD COPY . /app/ CMD [ "npm" , "start" ] Docker build -t weiyigeek/my-node-onbuild:latest . FROM my-node
16)STOPSIGNAL - 指定所创建镜像启动的容器接收退出的信号值: 描述: 该指令设置将发送到的系统调用信号容器退出,如果不定义信号名称默认是 SIGTERM。 此信号可以是格式中的 SIG <NAME>
, 例如 SIGKILL,或与 例如内核的系统调用表 9.
17)SHELL - 指定其他命令执行时默认使用shell的类型 描述: 该指令允许使用 shell 形式覆盖命令, Linux中默认的Shell是["/bin/sh", "-c"]
,而在Windows中 默认的Shell是["cmd", "/S", "/C"]
, 如果Linux中还存在备用的shell例如(zsh、csh、tcsh
),我们也可以采用此种方法指定。
1 2 3 4 5 6 7 8 9 10 SHELL ["/bin/sh" ,"-c" ] SHELL ["powershell" ,"-command" ] FROM microsoft/nanoserver SHELL ["powershell" ,"-command" ] RUN New-Item -ItemType Directory C:\Example ADD Execute-MyCmdlet.ps1 c:\example\ RUN c:\example\Execute-MyCmdlet -sample 'hello world'
18)HEALTHCHECK - 健康检查 描述:该命令设置检查容器健康状况的命令,它与 kubernetes 中的 Pod 探针类似
在没有 HEALTHCHECK
指令前Docker 引擎只可以通过容器内主进程是否退
出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出
,但是该容器已经无法提供服务了。
在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。
从 Docker 1.12 引入该指令HEALTHCHECK
指令是告诉 Docker 应该如何进行判断容器的状态是否正常,从而比较真实的反应容器实际状态。
当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy
,如果连续一定次数失败,则会变为 unhealthy。
1 2 3 4 5 6 7 8 9 10 HEALTHCHECK [选项] CMD <命令> --interval=<间隔>:两次健康检查的间隔,默认为 30 秒; --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间本次健康检查就被视为失败默认 30 秒; --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。 HEALTHCHECK NONE
基础示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s --retries=5 \ CMD curl -fs http://localhost/ || exit 1 docker build -t myweb:v1 . $ docker run -d --name web -p 80:80 myweb:v1 $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web $ docker inspect --format '{{json .State.Health}}' web | python -m json.tool { "FailingStreak" : 0, "Log" : [ { "End" : "2019-12-30T14:35:37.940957051Z" , "ExitCode" : 0, "Output" : "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>...." , "Start" : "2019-12-30T14:35:37.780192565Z" } ], "Status" : "healthy" }
3.补充知识 Q:ADD与COPY之间的比较? 答:优先使用COPY命令,而ADD除了COPY功能还有解压功能(在多数情况下显得多余) 注意:ADD 指令会令镜像构建缓存失效从而可能会令镜像构建变得比较缓慢。
Q:RUN and CMD and ENTRYPOINT之间的差别?
区别在于 RUN 是在镜像构建过程中执行的,而 CMD/ENTRYPOINT 是在镜像生成实例的时候执行的 RUN:执行命令并创建新的Image Layer CMD:设置容器启动后默认执行的命令和参数(如果定义多个CMD,只有最后一个执行) ENTRYPOINT:设置容器启动时运行的命令,让容器以应用程序或服务形式运行
补充重点:前面我们说过CMD和ENTRYPOINT指令都可以定义容器运行时所执行的命令,下面是它们之间协调的一些规则:
在Dockerfile至少需要设置一条CMD或者ENTRYPOINT指令;
当将容器作为可执行文件使用时,建议定义ENTRYPOINT指令;
CMD作为为ENTRYPOINT命令定义默认参数的一种方式;
当使用带有参数的命令运行容器时 CMD将会被覆盖。
下表是显示了不同的ENTRYPOINT / CMD
指令组合的命令执行情况: | | No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT [“exec_entry”, “p1_entry”] | | :—————————-: | :————————: | :—————————-: | :——————————————–: | | No CMD** | 报错,这种情况不运行出现 | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry | | CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd | | CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd | | CMD exec_cmd p1_cmd* | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
Q:命令指令的(shell 与 exec
)不同格式异同?; 描述:通过shell格式去运行命令会读取$name变量,而exec格式是仅仅的执行一个命令而不是shell指令;1 2 RUN/CMD/ENTRYPOINT yum install -y vim RUN/CMD/ENTRYPOINT ["apt-get" ,"install" ,"-y" ,"vim" ]
Q:ARG与ENV之间的区别? 答:ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的,但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。 而ENV会将变量通过镜像的entrypoint指令与容器中的应用传值;
Q:Docker前后台执行浅析问题? 描述:提到 CMD 就不得不提容器中应用在前台执行和后台执行的问题(初学者常出现的一个混淆
)
Docker不是虚拟机在容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务, 而容器内没有后台服务的概念
。 对于容器而言,启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出容器就失去了存在的意义从而退出,其它辅助进程不是它需要关心的东西。1 2 3 4 5 6 7 8 CMD service nginx start CMD [ "sh" , "-c" , "service nginx start" ] CMD ["nginx" , "-g" , "daemon off;" ]
那么什么是上下文路径呢? 首先我们要理解 docker build 的工作原理,由于Docker是C/S设计架构,Dockerclient通过这组 API 与 Docker 引擎交互,从而完成各种功能; 因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上一切都是使用的远程调用形式在服务端(Docker 引擎)完成。 当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。
而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端也就是 Docker 引擎中构建的;用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。1 2 3 COPY /opt/package.json /app/ COPY ./package.json /app/
下列指令可以支持环境变量展开: ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。
注意:对于Windows系统建议Dockerfile开头添加#escape=来指定转义信息
4.基础实战 描述: 确保app.py和dockerfile在同一个目录 1.准备好app.py的flask程序1 2 3 4 5 6 7 8 9 10 11 [root@localhost ~] from flask import Flask app=Flask(__name__) @app.route('/' ) def hello(): return "hello docker" if __name__=="__main__" : app.run(host='0.0.0.0' ,port=8080) [root@master home] app.py Dockerfile
weiyigeek.top-app.py测试
2.编写dockerfile1 2 3 4 5 6 7 8 FROM python:2.7 LABEL version="1.0" LABEL maintainer="weiyigeek" RUN pip install flask COPY app.py /app/ WORKDIR /app EXPOSE 8080 CMD ["python" ,"app.py" ]
使用.dockerignore文件(每一行添加一条匹配模式)来让Docker忽略匹配模式路径下的目录和文件;
1 2 3 4 5 */temp* */*/temp* tmp? ~*
3.构建镜像image找到当前目录的dockerfile开始构建1 2 docker build -t weiyigeek/flask-hello-docker . docker image ls
4.启动此flask-hello-docker容器,映射一个端口供外部访问(检查运行的容器)1 2 docker run -d -p 8080:8080 weiyigeek/flask-hello-docker docker container ls
5.提交修改并推送到我们的私有仓库中1 2 docker tag weiyigeek/flask-hello-docker 192.168.11.37:5000/peng-flaskweb docker push 192.168.11.37:5000/peng-flaskweb
5.Dockerfile 编写构建示例 示例0.自定义 Debian 镜像安装中文编码支持 1 2 3 4 5 6 7 8 9 10 11 FROM weiyigeek/tomcat:7.0 .85 -jre8MAINTAINER WeiyiGeek <master@WeiyiGeek.top>RUN wget -O /etc/apt/sources.list http://192.168.10.206:8080/sources.list \ && apt clean && apt update && apt install -y locales \ && sed -i 's/# zh_CN.GB18030/zh_CN.GB18030/g' /etc/locale.gen \ && locale-gen \ && locale -a \ && rm -rf /var/cache/apt/* ENV LANG=zh_CN.gb18030CMD ["catalina.sh" "run" ]
示例1.Docker - Webp Server Go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 FROM golang:alpine as builderARG IMG_PATH=/opt/picsARG EXHAUST_PATH=/opt/exhaustRUN apk update ;\ apk add alpine-sdk ;\ git clone https://github.com/webp-sh/webp_server_go /build ;\ cd /build ;\ sed -i "s|.\/pics|${IMG_PATH} |g" config.json ;\ sed -i "s|\"\"|\"${EXHAUST_PATH} \"|g" config.json ;\ sed -i 's/127.0.0.1/0.0.0.0/g' config.json WORKDIR /build RUN go build -o webp-server . FROM alpineCOPY --from=builder /build/webp-server /usr/bin/webp-server COPY --from=builder /build/config.json /etc/config.json WORKDIR /opt VOLUME /opt/exhaust CMD ["/usr/bin/webp-server" , "--config" , "/etc/config.json" ]
注意事项 :在 RUN 指令的每行结尾我使用的是 ;\ 来接下一行 shell 而不是 && 其中缘由相信读者也猜到一二了吧(提高容错性),两则本质区别是 ;
运行失败时会继续运行而 &&
运行成功则继续执行; 笔者也逛了一圈 docker hub 官方镜像中用;
较多一些,个人觉得是因为;
比&&
要美观一些
示例2.进行dockerfile编写并生成自定义hexo博客环境的镜像 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 FROM mhart/alpine-node:latest LABEL maintainer="weiyigeek <weiygeek@qq.com>" LABEL description="weiyigeek blog" WORKDIR /opt/web/ RUN apk update \ && apk add git \ && git clone https://gitee.com/WeiyiGeek/blog.git ENV LANG=en_US.UTF-8 \ LANGUAGE=en_US:en \ LC_ALL=en_US.UTF-8 WORKDIR /opt/web/blog/ RUN npm install -g hexo-cli --save \ && npm install --production --registry=https://registry.npmmirror.com --save EXPOSE 4000 30000-40000 ENTRYPOINT ["hexo" , "server" ]
weiyigeek.top-hexoblog-dockerfile
执行完成后将会在docker iamges 中显示我们设置仓库名称:1 2 3 4 5 6 $docker run -d -p 80:4000 --name blog weiyigeek/blogc6059fc19891792ce03304bcf943dccd708e5b8c7565fe86159d3407e23e7530 $docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c6059fc19891 weiyigeek/blog "hexo server" 7 seconds ago Up 5 seconds 0.0.0.0:80->4000/tcp blog
示例3.Dockerfile多阶段构建实践 构建docker容器的多种方式: 方式1:将所有的构建过程编包含在一个 Dockerfile 中,包括项目及其依赖库的编译、测试、打包等流程 产生的问题:
镜像层次多,镜像体积较大,部署时间变长
源代码存在泄露的风险
优点:管理方便简单
方式2:将项目及其依赖库编译测试打包好后,再将编译好的可执行文件拷贝到运行环境中 产生的问题:部署过程较复杂 优点:生成的镜像大小相比较于方式1较小
比如,参照下面的多阶段构建1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/bin/sh echo Building gcc/helloworld:builddocker build -t gcc/helloworld:build . -f Dockerfile.build docker create --name extract go/helloworld:builder docker cp extract:/gcc/demo/test ./test docker rm -f extract echo Building gcc/helloworld:runnerdocker build --no-cache -t gcc/helloworld:2 . -f Dockerfile.copy rm ./test
方式3:Docker v17.05 开始支持多阶段构建 (multistage builds),此种方法综合和方式1和2并且解决了他们响应的问题;
比如,我们要编译.c文件分为两步进行,编译c再运行
步骤01.test.c 文件内容如下1 2 3 4 5 6 #include <stdio.h> int main (void ) { printf ("Hello World! Dockerfile!\n" ); return 0 ; }
步骤02.编写Dockerfile,此处使用多阶段构建1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FROM alpine as builder MAINTAINER weiyigeek <weiyigeek@q.com> RUN apk update && apk --no-cache add gcc g++ WORKDIR /gcc/demo/ COPY test.c . RUN gcc test.c -o test FROM alpine-c as runer WORKDIR /gcc/demo/ COPY --from=0 /gcc/demo/test . CMD ["./test" ]
步骤03.进行镜像构建操作1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ls mutilDockerfile test.c $docker build -t weiyigeek/hello:1 -f mutilDockerfile . Sending build context to Docker daemon 3.072kB Step 1/10 : FROM alpine as builder ---> 055936d39205 Step 2/10 : MAINTAINER weiyigeek <weiyigeek@q.com> ---> Running in 59097e3a8120 Removing intermediate container 59097e3a8120 ---> 8e796c96a921 Step 3/10 : RUN apk update && apk --no-cache add gcc g++ ---> Running in 89fca19b761d
步骤04.最后运行构建的镜像1 2 $docker run --name hello weiyigeek/hello:1Hello World! Dockerfile!
0x02 Docker 镜像构建最佳实践浅析 描述: Docker拥有自己的操作系统,完全基于于 Docker 的Linux发行版CoreOS。 目前常用的Linux发行版主要包括Debian/Ubuntu系列和CentOS/Fedora系列。
前者以自带软件包版本较新而出名
后者则宣称运行更稳定一些
1.Dockerfile 指令最佳实践
FROM:尽可能使用当前官方仓库作为你构建镜像的基础
LABEL: 一个镜像可以包含多个标签但建议将多个标签放入到一个 LABEL 指令中
RUN:将长的或复杂的 RUN 指令用反斜杠 \ 分割成多行 (不要使用 RUN apt-get upgrade 或 dist-upgrade,因为许多基础镜像中的「必须」包不会在一个非特权容器中升级,而且建议使用指定版本的形式)
CMD:用于执行目标镜像中包含的软件可以包含参数
EXPOSE:在执行 docker run 时使用一个标志来指示如何将指定的端口映射到所选择的端口
ENV: 为了方便新程序运行,你可以使用 ENV 来为容器中安装的程序更新 PATH 环境变量
ADD 和 COPY(优先使用它),了让镜像尽量小最好不要使用 ADD 指令从远程 URL 获取包,而是使用 curl 和 wget
ENTRYPOINT: 最佳用处是设置镜像的主命令,允许将镜像当成命令本身来运行(用 CMD 提供默认选项)。
VOLUME: 强烈建议使用它来管理镜像中的可变部分和用户可以改变的部分。
USER:如果某个服务不需要特权执行,建议使用 USER 指令切换到非 root 用户(你应该避免使用 sudo),同时也避免频繁地使用USER来回切换用户。
使用类似 RUN groupadd -r postgres && useradd -r -g postgres postgres 的指令创建用户和用户组。
WORKDIR:为了清晰性和可靠性建议都使用结对路径;
2.Dockerfile 编写最佳实践 描述: 容器应该是短暂的,短暂意味着可以停止和销毁容器,并且创建一个新容器并部署好所需的设置和配置工作量应该是极小的;
下面列出了Dockerfile最佳实践的一些要素方法:
1.学习Docker的hub中官方仓库中镜像和对应的Dockerfile编写方法与习惯;
2.提高构建镜像的效率使用.dockerignore
文件来要忽略的文件和目录与指定上下文环境, .dockerignore 文件的排除模式语法和 Git 的 .gitignore 文件相似。(Dockerfile目录尽量为空,然后将构建镜像所需要的文件添加到该目录中
);
3.使用精简镜像(选择体积较小的基础镜像), 比如 alpine 或者 debian:buster-slim
;1 2 3 4 REPOSITORY TAG IMAGE ID CREATED SIZE debian buster-slim e1af56d072b8 4 days ago 69.2MB alpine latest cc0abc535e36 8 days ago 5.59MB
4.网络环境受限的情况下,需将默认的软件源更换为国内的软件源镜像站,目前国内稳定可靠的镜像站主要有,华为云、阿里云、腾讯云、163
等,其中华为云的镜像站速度最快,平均 10MB/s,峰值可达到 20MB/s,极大的能加快构建镜像的速度。1 2 3 4 5 6 7 8 9 echo "http://mirrors.huaweicloud.com/alpine/latest-stable/main/" > /etc/apk/repositories ;\echo "http://mirrors.huaweicloud.com/alpine/latest-stable/community/" >> /etc/apk/repositories ;\apk update ;\ sed -i 's/deb.debian.org/mirrors.huaweicloud.com/g' /etc/apt/sources.list ;\ sed -i 's|security.debian.org/debian-security|mirrors.huaweicloud.com/debian-security|g' /etc/apt/sources.list ;\
4.减少镜像层数则要尽量合并指令(调整合理的指令顺序),例如多个RUN指令可以利用反斜杠符号 \
合并为一条;
5.避免安装不必要的包(指定软件版本号
)来减少所构建镜像的大小(实际需要将各层安装的东西尽量最小),降低复杂性、减少依赖、节约构建时间 (别使用yum upgrade / apt-get upgrade / dist-upgrade 来更新依赖应用
);
1 2 3 RUN apt update && apt-get --no-install-recommends install -y \ package-bar \ package-foo=1.3.* && \
6.分阶段构建在 Docker 17.05 以上版本中可以采用此种方式来减少所构建镜像的大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package main import ( "github.com/gin-gonic/gin" "net/http" ) func main () { router := gin.Default() router.GET("/ping" , func(c *gin.Context) { c.String(http.StatusOK, "PONG" ) }) router.Run(":8080" ) } FROM golang AS build-env ADD . /go/src/app WORKDIR /go/src/app RUN go get -u -v github.com/kardianos/govendor RUN govendor sync RUN GOOS=linux GOARCH=386 go build -v -o /go/src/app/app-server FROM alpine RUN apk add -U tzdata RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime COPY --from=build-env /go/src/app/app-server /usr/local /bin/app-server EXPOSE 8080 CMD [ "app-server" ]
6.增加可读性将多行参数排序, 建议在反斜杠符号 \ 之前添加一个空格 ,并且只要有可能,就将多行参数按字母顺序排序
(比如要安装多个包时),帮助你避免重复包含同一个包,更新包列表时也更容易,也更容易阅读和审查;
1 2 3 4 5 6 7 8 9 10 11 LABEL Version="1.1" \ Author="WeiyiGeek" \ InnerVersion="0.1" RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion
7.时区设置由于绝大多数 docker 镜像都是默认采用的 UTC 的时区,与北京时间相差 8 个小时,这将会导致容器内的时钟与北京时间不一致,因而会对一些应用造成一些影响,以及影响容器内日志和监控的数据;1 2 3 4 5 6 7 8 9 10 11 apk add --no-cache tzdata ;\ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\ echo "Asia/Shanghai" > /etc/timezone ;\apk del tzdata ; cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\ echo "Asia/shanghai" > /etc/timezone ;\
8.尽量使用 URL 添加源码,对于需要在容器内进行编译的项目,最好通过git 或者 wegt 的方式将源码打入到镜像内,而非采用 ADD 或者 COPY ,因为源码编译完成之后源码就不需要可以删掉了,而通过 ADD 或者 COPY 添加进去的源码已经用在下一层镜像中了无法被删除掉
。这种方法缺点就是网络可能受限下载缓慢1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 RUN set -x \ && echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories \ && echo "http://mirrors.aliyun.com/alpine/latest-stable/community/" >> /etc/apk/repositories \ && apk update \ && apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev git \ && mkdir -p /usr/local /src \ && cd /usr/local /src \ && git clone https://github.com/happyfish100/libfastcommon.git --depth 1 \ && git clone https://github.com/happyfish100/fastdfs.git --depth 1 \ && git clone https://github.com/happyfish100/fastdfs-nginx-module.git --depth 1 \ && wget http://nginx.org/download/nginx-1.15.4.tar.gz \ && tar -xf nginx-1.15.4.tar.gz \ && cd /usr/local /src/libfastcommon \ && ./make.sh \ && ./make.sh install \ && cd /usr/local /src/fastdfs/ \ && ./make.sh \ && ./make.sh install \ && cd /usr/local /src/nginx-1.15.4/ \ && ./configure --add-module=/usr/local /src/fastdfs-nginx-module/src/ \ && make && make install \ && apk del .build-deps \ && apk add --no-cache pcre-dev bash \ && mkdir -p /home/dfs \ && mv /usr/local /src/fastdfs/docker/dockerfile_network/fastdfs.sh /home \ && mv /usr/local /src/fastdfs/docker/dockerfile_network/conf/* /etc/fdfs \ && chmod +x /home/fastdfs.sh \ && rm -rf /usr/local /src* REPOSITORY TAG IMAGE ID CREATED SIZE fastdfs alpine e855bd197dbe 10 seconds ago 29.3MB fastdfs debian e05ca1616604 20 minutes ago 103MB fastdfs centos c1488537c23c 30 minutes ago 483MB
10.使用虚拟编译环境,对于只在编译过程中使用到的依赖,我们可以将这些依赖安装在虚拟环境中,编译完成之后可以一并删除这些依赖1 2 3 4 apk add --no-cache --virtual .build-deps gcc libc-dev make perl-dev openssl-dev pcre-dev zlib-dev git
容器应该是短暂的通过 Dockerfile 构建的镜像所启动的容器应该尽可能短暂(生命周期短),意味着可以停止和销毁容器,并且创建一个新容器并部署好所需的设置和配置工作量应该是极小的。们可以查看下[12 Factor(12要素)应用程序方法]的进程部分,可以让我们理解这种无状态方式运行容器的动机。
建立上下文,在执行docker build命令时所在工作目录被称为构建上下文,默认情况下,Dockerfile 就位于该路径下,当然您也可以使用-f参数来指定不同的位置,此时无论 Dockerfile 在什么地方,当前目录中的所有文件内容都将作为构建上下文发送到 Docker 守护进程中去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 mkdir myproject && cd myproject echo "hello" > helloecho -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfiledocker build -t helloapp:v1 . mkdir -p dockerfiles context mv Dockerfile dockerfiles && mv hello context docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context Dockerfile: Sending build context to Docker daemon 187.8MB
11.精简生成镜像的大小及时删除临时文件和缓存文件,特别是在执行apt-get指令后 /var/cache/apt
和 /var/lib/apt/lists
下面会缓存一些安装包;
删除中间文件:比如下载的压缩包
删除临时文件:如果命令产生了临时文件,也要及时删除1 2 3 rm -rf /var/lib/apt/lists/* rm -rf /var/cache/apt
12.构建缓存查找是否已经存在可重用的镜像,如果有就使用现存的镜像不再重复创建 , 在开启缓存的情况下,内容不变的指令尽量放在前面;当然如果你不想在构建过程中使用缓存,你可以在 docker build 命令中使用--no-cache=true
选项;Docker中缓存遵循的基本规则如下:
1 2 3 4 5 - 从基础镜像开始(即FROM指令指定),下一条指令将和该基础镜像的所有子镜像进行匹配,检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样。IF 不是 则缓存失效; - 多数情况下简单地对比 Dockerfile 中的指令和子镜像,然而有些指令需要更多的检查和解释; - 对于 ADD 和 COPY 指令镜像中对应文件的内容也会被检查,每个文件都会计算出一个校验值;在缓存的查找过程中会将这些校验和和已存在镜像中的文件校验值进行对比,如果文件有任何改变,比如内容和元数据则缓存失效。 - 缓存匹配过程不会查看临时容器中的文件来决定缓存是否匹配,例如当执行完 `RUN apt-get -y update` 指令后,容器中一些文件被更新,但 Docker 不会检查这些文件。这种情况下只有指令字符串本身被用来匹配缓存。 - 一旦缓存失效所有后续的 Dockerfile 指令都将产生新的镜像,缓存不会被使用
13.不要在 Dockerfile 中单独修改文件的权限, 因为 docker 镜像是分层的,任何修改都会新增一个层,修改文件或者目录权限也是如此, 如果有一个命令单独修改大文件或者目录的权限,会把这些文件复制一份这样很容易导致镜像很大。
1 2 3 - 在添加到 Dockerfile 之前就把文件的权限和用户设置好; - 在容器启动脚本(entrypoint)做这些修改,或者拷贝文件和修改权限放在一起做(最终也只是增加一层)
14.保证容器的横向扩展和复用, 一个容器只运行一个进程将多个应用解耦到不同容器中(类似于微服务-K8s表现得淋漓精致
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 docker build -t test :v1.1 . Sending build context to Docker daemon 2.56kB Step 1/6 : FROM alpine latest: Pulling from library/alpine df20fa9351a1: Pull complete Digest: sha256:185518070891758909c9f839cf4ca393ee977ac378609f700f60a771a2dfe321 Status: Downloaded newer image for alpine:latest ---> a24bb4013296 Step 2/6 : LABEL Author="WeiyiGeek" Description="Test Dockerfile" ---> Running in 88827d239060 Removing intermediate container 88827d239060 ---> 11d6ad764d64 Step 3/6 : MAINTAINER WeiyiGeek master@WeiyiGeek.top ---> Running in 5c939c83df34 Removing intermediate container 5c939c83df34 ---> 3fcdbc7bd1d8 Step 4/6 : RUN echo "http://mirrors.huaweicloud.com/alpine/latest-stable/main/" > /etc/apk/repositories ; echo "http://mirrors.huaweicloud.com/alpine/latest-stable/community/" >> /etc/apk/repositories ; apk update ; apk add --no-cache tzdata ; cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ; echo "Asia/Shanghai" > /etc/timezone ; apk del tzdata ; ---> Running in 2879a08f5f04 fetch http://mirrors.huaweicloud.com/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz fetch http://mirrors.huaweicloud.com/alpine/latest-stable/community/x86_64/APKINDEX.tar.gz v3.12.0-74-gb8f92cf12a [http://mirrors.huaweicloud.com/alpine/latest-stable/main/] v3.12.0-77-ge186c138b6 [http://mirrors.huaweicloud.com/alpine/latest-stable/community/] OK: 12734 distinct packages available fetch http://mirrors.huaweicloud.com/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz fetch http://mirrors.huaweicloud.com/alpine/latest-stable/community/x86_64/APKINDEX.tar.gz (1/1) Installing tzdata (2020a-r0) Executing busybox-1.31.1-r16.trigger OK: 9 MiB in 15 packages (1/1) Purging tzdata (2020a-r0) Executing busybox-1.31.1-r16.trigger OK: 6 MiB in 14 packages Removing intermediate container 2879a08f5f04 ---> afed82abd763 Step 5/6 : ENTRYPOINT ["top" ,"-b" ] ---> Running in ce8bc0f98fca Removing intermediate container ce8bc0f98fca ---> c5fe50376063 Step 6/6 : CMD ["d2" ] ---> Running in d6bbe5535a87 Removing intermediate container d6bbe5535a87 ---> fe57118d688c Successfully built fe57118d688c Successfully tagged test :v1.1