9、dockerfile
dockerfile
本章要点:dockek-Dockerfile基础用法
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是Dockerfile
Dockerfile 是什么?
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建
- 从哪开始构建(FROM)
- 维护信息声明(LABEL)
- 要做哪些修改(RUN / COPY / ADD / ENV)
- 最终如何启动(CMD / ENTRYPOINT / EXPOSE)
Dockerfile目录说明
一般运行在顶层目录,下面包含子目录以及一个Dockerfile文件, 这个文件名一定是大写, 如/data/docker 下包含 (Dockerfile)类型文件, (nginx, tomcat)类型目录, 过滤文件 .dockeringore 写在这里头的都不会被打包
]# pwd
/data/build/nginx
nginx]# ls -laDockerfile <-- 打包文件.dockeringore <-- 过滤哪些文件或目录otherdirs <-- 由COPY/ADD/FILE 之类的参数引用,files相同 otherfiles
Dockerfile参数
FROM
指定基础镜像,必须为第一个命令
格式:FROM <image>FROM <image>:<tag>FROM <image>@<digest>示例: FROM mysql:5.6
注: tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像
注: 如果指定的镜像没有 就会从dockerhub上下载
]# cat Dockerfile
# then first dockerfile # 第一行可以是注释行
FROM busybox:latest # 注意FROM等关键字都是大写]# docker build -t test:v0.0.1 . # 构建镜像, 名称为 test:v0.0.1 .是表示当前的Dockerfile
Sending build context to Docker daemon 2.048kB
Step 1/1 : FROM busybox:latest---> 020584afccce
Successfully built 020584afccce
Successfully tagged test:v0.0.1
LABEL
MAINTAINER已替换化 LABEL 用于为镜像添加元数据
格式:LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:LABEL version="1.0" description="test" by="TEST"
注:使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。
]# cat Dockerfile
FROM busybox:latest
LABEL maintainer='xiong' desction="test container"]# docker build -t test:v0.0.2 .
....
]# docker image inspect -f {{.ContainerConfig.Labels}} test:v0.0.2"Labels": {"desction": "test container","maintainer": "xiong"}
COPY
功能类似ADD,但是是不会自动解压文件,也不能访问网络资源, 使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git进行管理的时候,
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]比如:COPY package.json /usr/src/app/
也可以是多个,甚至是通配符COPY home* /usr/src/app/COPY hom?.txt /usr/src/app/
文件复制准则
- 必须是build上下文中的路径,不能是其父目录中的文件
- 如果是目录, 则其内部文件或子目录会被递归复制,但目录自身不会被复制
- 如果指定了多个,或在中使用了通配符,则必须是一个目录,且必须以/结尾
- 如果事先不存在,它将会被自动创建,这包括其父目录路径
]# cat Dockerfile
From busybox:latest
LABEL desction='test_v0.0.3'COPY index.html /data/html/ # 将index.html复制到 /data/html/下
COPY in?.php /data/html/ # 也可以使用正则表达式 ?表示一个字符
COPY 1.txt 2.txt /data/html/ # 如果是多个源文件则 目标目录必须是以 / 结尾COPY test/a/* /opt/a # 假设a目录为空,或a目录下的目录为空,会提示目录不能为空的异常
ADD
ADD 指令和 COPY 的格式和性质基本一致,将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
格式:ADD <src>... <dest>ADD ["<src>",... "<dest>"] 用于支持包含空格的路径
示例:ADD hom?.txt /mydir/ # ? 替代一个单字符,例如:"home.txt"ADD xx.tar.gz /data/src/ # 将会自动将xx.tar.gz解压到 /data/src/目录下ADD http://xx.com/xx.tar.gz /data/src # 下载xx.tar.gz到/data/src目录下不会解压
]# cat Dockerfile
From busybox:latestLABEL desction='test_v0.0.4'# 查看区别, 如果是从服务器下载,那么就相当于是wget
ADD http://nginx.org/download/nginx-1.17.6.tar.gz /data/src/
ADD nginx-1.17.6.tar.gz /data/src/ # 直接复制文件就是tar解压# 将a.txt 设置为644 b.txt设置为777
COPY a.txt /data/ # 容器权限也会保持一致
ADD b.txt /data/ # 容器权限也会保持一致
ADD adir /data/ # adir为空]# docker run -it --rm 4dd # 随便运行一个
root@3bdeb5eb1065:/data# ls -l
total 8
-rw-r--r-- 1 root root 6 Oct 20 08:50 a.txt # 权限一样
-rwxrwxrwx 1 root root 9 Oct 20 08:50 b.txt
drwxr-xr-x 2 root root 33 Oct 20 08:48 src
drwxr-xr-x 3 root root 26 Oct 20 08:48 src2root@3bdeb5eb1065:/data# ls src # ADD 如果不是本地则就直接下载
nginx-1.17.6.tar.gzroot@3bdeb5eb1065:/data# ls src2/nginx-1.17.6/ # 如果是本地它会解压
CHANGES CHANGES.ru LICENSE README auto conf configure contrib html man src
WORKDIR
指定工作目录 ,以后各层的当前目录就被改为指定的目录,如该目录不存在, WORKDIR 会帮你建立目录
格式:WORKDIR /path/to/workdir
示例:WORKDIR /a (这时工作目录为/a)WORKDIR b (这时工作目录为/a/b)WORKDIR c (这时工作目录为/a/b/c)
]# cat Dockerfile
FROM busybox:latest
LABEL desction='test:v0.0.7'
WORKDIR /data/src/ # 第一次设置工作目录
COPY file1.txt ./ # 将文件复制到工作目录中
WORKDIR ./file/ # 第二次设置工作目录并在反面跟随file目录
ADD nginx-1.17.6.tar.gz ./ # 将其解压至 /data/src/file/目录中]# docker run -it --rm test:v0.0.7 # build之后 运行容器查看
root@ef2a7b81b606:/data/src/file # ls # 进入之后工作目录就是 /data/src/file
nginx-1.17.6 # nginx文件也会正常被解压
/data/src/file # cd ..
/data/src # ls -l # 而第一次设置的目录也复制正常
total 4
drwxr-xr-x 1 root root 25 Nov 20 06:55 file
-rw-r--r-- 1 root root 5 Nov 20 06:55 file1.txt# 只需要注意: 如果第一次设置,第二次用相对路径./file那就是 A+B路径, 如果都是绝对路径那就是 C路径
# 如 A: /data/src B: ./file = /data/src/file C: /file = /file 而不是A+B+C
VOLUME
格式: # 用于指定持久化目录VOLUME ["/path/to/dir"]
示例:VOLUME ["/data"]VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"
一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
- 卷可以容器间共享和重用
- 容器并不一定要和其它容器共享卷
- 修改卷后会立即生效
- 对卷的修改不会对镜像产生影响
- 卷会一直存在,直到没有任何容器在使用它
]# cat Dockerfile
FROM busybox:latestLABEL desctions='test:v0.0.8'
VOLUME /data/docker/volume/
# 将这个目录持久化, 这样持久方式是 docker-manage管理, 主机持久化目录是随机的
# build 然后 ]# docker run -it --rm --name file1 test:v0.0.8
]# docker image inspect test:v0.0.8"Mounts": [{"Type": "volume","Name": "xxxxxx","Source": "/var/lib/docker/volumes/xxxxxx/_data","Destination": "/data/docker/volume","Driver": "local",....}# 容器共享卷组还是得使用 --volumes-from
]# docker run -it --rm --volumes-from file1 --name file2 test:v0.0.8# 此时在创建第三个
~]# docker run -it --rm --name file3 test:v0.0.8 # 挂载点不变, 但与前两个不在同Local目录中
ENV
指定环境变量,在镜像生成过程中会被后续RUN指令使用, 后续运行容器时该环境变量也会生效。
格式:ENV <key> <value> ENV <key>=<value> ...
示例:ENV myName John ENV myDog Rex The DogENV myCat=fluffy cat2=cat2#<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
#可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行
]# cat Dockerfile
FROM ubuntu
LABEL version="v1.0.2" auth="xiong" time="20251020"WORKDIR /data/src
ADD nginx-1.17.6.tar.gz ./ENV NGINX_HOME="/data/src/nginx-1.17.6"]# docker build -t testu1:v1.0.7 .]# docker inspect 445
"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_HOME=/data/src/nginx-1.17.6" <-- 通过inspect查看详细标签
],
RUN
构建镜像时执行的命令
RUN用于在镜像容器中执行命令,其有以下两种命令执行方式:
shell执行
格式:RUN <command>
exec执行
格式:RUN ["executable", "param1", "param2"]
示例:RUN yum -y install nginx \yum clean allRUN apk updateRUN ["/etc/execfile", "arg1", "arg1"]# 如果想运行通配符之类的命令则需要使用 CMD ['/bin/bash','-c','xxx']
注:RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
run与cmd的区别: run是运行在build之时,而cmd是在运行容器的时候执行
]# cat Dockerfile
FROM ubuntu:latestLABEL desction="test_v0.0.9" command="run"RUN apt-get update \ # RUN在build的时候就会直接执行。&& apt-get -y install nginx \ # 安装nginx&& apt-get clean # 注意在每一层安装好文件一定要清除相关的文件
CMD
CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令
格式: # 如果运行了多个CMD 最终执行的命令只会是最后一个有效CMD ["executable","param1","param2"] (执行可执行文件,优先)CMD command param1 param2 (执行shell内部命令)# 前两种语法格式的意义同RUNCMD ["param1","param2"] (设置了 ENTRYPOINT ,则直接调用ENTRYPOINT添加参数)
示例:CMD echo "This is a test." | wc -CMD ["/usr/bin/wc","--help"]
-
env + cmd
]# cat Dockerfile FROM test:v0.0.9 # 这个镜像是在运行run之后的.... LABEL desction='test:v0.1.0' command="cmd" ENV NGINX_ENV="/usr/sbin/nginx" CMD ${NGINX_ENV} -g "daemon off;" # 让nginx运行在前台]# docker image inspect test:v0.1.0 "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_ENV=/usr/sbin/nginx" ], "Cmd": [ "/bin/sh", "-c", "${NGINX_ENV} -g daemon off;" ],cat Dockerfile # CMD使用方式二, 推荐使用这种,看起来更加简洁CMD ['/usr/sbin/nginx','-g','daemon off;'] # 使用列表方式# 查看容器cmd启动文件, 注意每次运行的时候 cmd都会自动加上 /bin/sh -c "Cmd": [ "/bin/sh","-c","['/usr/sbin/nginx','-g','daemon off;']"],# 注意点: 如果在运行容器的时候后面在跟随命令,将会覆盖镜像定义的cmd ]# docker run -it --rm --name ng1 test:v0.1.1 ls /bin/
-
run + cmd
]# cat Dockerfile FROM test:v0.0.9LABEL desction='test:v0.1.2' command="cmd" ENV NGINX_ENV="/usr/sbin/nginx" RUN mkdir /data/html -p && \ # 构建镜像时运行echo '<h1> welcome use docker </h1>' > /data/html/index.html# 注意如果这么写列表是取不到变量的,如果想要用变量就得加上前缀 /bin/sh -c # "Cmd": ["${NGINX_ENV}","-g", "daemon off;" ], 无法取到shell,如果有变量推荐使用第一种 CMD ${NGINX_ENV} -g "daemon off;" # 带变量还是使用这种,让程序自动解析
ENTRYPOINT
配置容器,使其可执行化。配合CMD可省去"application",只使用参数。
ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令
格式:ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)ENTRYPOINT command param1 param2 (shell内部命令)
示例:FROM ubuntuENTRYPOINT ["top", "-b"]CMD ["-c"]# 当同时定义CMD跟ENTRYPOINT时,CMD会被当成参数传给ENTRYPOINT
EXPOSE
EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口,
格式:EXPOSE <port> [<port>...]
示例:EXPOSE 80 443EXPOSE 11211/tcp 11211/udp
USER
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户
格式:USER userUSER user:groupUSER uidUSER uid:gidUSER user:gidUSER uid:group示例:USER www
HEALTHCHECK
HEALTHCHECK 支持下列选项:
--interval=<间隔> :两次健康检查的间隔,默认为 30 秒;
--timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
--retries=<次数> :当连续失败指定次数后,则将容器状态视为 unhealthy ,默认 3次。
空目录下创建一个Dockerfile
FROM centosCOPY nginx.repo /etc/yum.repos.d/
RUN groupadd -g 1000 nginx \&& useradd -g 1000 -u 1001 nginx \&& yum -y install nginx # 每10秒检查一次,超时时长5秒就退出
HEALTHCHECK --interval=10s --timeout=5s \CMD curl -fs http://127.0.0.1 || exit 1
EXPOSE 80CMD ["nginx", "-g", "daemon off;"]构建并运行并查看状态,一开始是healthy状态,然后理性成starting说明就启动了,失效了就会改变成 unhealthy状态
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e7982e3ec394 nginx:v3 "nginx -g 'daemon of…" 2 seconds ago Up 2 seconds (health: starting) 0.0.0.0:80->80/tcp Tnginx最后可以使用 docker inspect 查看容器的状态 使用python json模块打印
docker inspect -f '{{json .State.Health}}' Tnginx | python -m json.tool"Status": "healthy"
ARG
用于指定传递给构建运行时的变量
格式:ARG <name>[=<default value>]
示例:ARG siteARG build_user=www
ONBUILD
ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行,Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的
.dockerignore
# 忽略匹配模式路径下的文件
# file_dir*/temp**/*/temp*tmp?~*
总结镜像生成
-
精简镜像用途
-
选用合适的基础镜像, 书上推荐使用(debian), 这里我一般使用centos
# 从下载上来看, debian的确是比centos要小近一半 ubuntu latest 7e0aa2d69a15 12 days ago 72.7MB debian latest 0d587dfbc4f4 3 weeks ago 114MB centos latest 300e315adb2f 4 months ago 209MB
-
提供足够清晰的命令注释和维护者信息
-
正确使用版本号:
-
减少镜像层数: 如多个RUN可以合并为一个
-
及时删除临时和缓存文件: 如apt-get, /var/cache/apt下会缓存一些安装包
-
提高生成速度: 可利用 .dockerignore 文件指定
-
调整合理的指令顺序,内容不变的尽量放在前面,这样可尽量利用
-
减少外部源的干预,如果确实要从外部引入数据,需要指定持久的地址,并带有版本信息
实例
nginx
-
准备基础nginx包
# 注意,下载的包是源文件,需要先本机build一下 ]# wget http://nginx.org/download/nginx-1.28.0.tar.gz ]# tar xf nginx-1.28.0.tar.gz ]# cd nginx-1.28.0 ]# ./configure --prefix=/data/nginx ]# make -j 32 && make -j 32 install # 有多少核CPU写多少数字 ]# cd /data/ && tar zcf nginx-1.28.0.tar.gz nginx 这样基础就准备好了
-
nginx.conf
user root; events {worker_connections 102400; } http {include mime.types;default_type application/octet-stream;server {listen 9999;location / {root /data/web/;index index.html;}} } # 临时用,如果想一次构建多次用,记得用include xx/conf.d
-
Dockerfile
nginx]# cat Dockerfile FROM ubuntuLABEL version="1.28.0" auth="test"ADD nginx-1.28.0.tar.gz /data/ COPY nginx.conf /data/nginx/conf/RUN mkdir /data/web && \echo '<div> hello world </div>' >> /data/web/index.htmlENV NGINX_HOME="/data/nginx" EXPOSE 9999CMD ["/data/nginx/sbin/nginx","-g","daemon off;"]
-
运行并访问
nginx]# docker build -t nginx:1.28.0 . nginx]# docker run -d -P --name n1 nginx:1.28.0 nginx]# curl http://127.0.0.1:10243 <-- 用 docker ps看一下映射的端口并访问 <div> hello world </div>
ssh
没啥意义,就是测着玩
-
准备文件
docker-ssh]# ssh-keygen -t rsa docker-ssh]# cat /root/.ssh/id_rsa.pub > authorized_keys
-
Dockerfile
docker-ssh]# cat Dockerfile FROM ubuntuRUN apt-get update \&& apt-get install -y openssh-server \&& mkdir -p /var/run/sshd \ && mkdir -p /root/.ssh \ && sed -i "s@session required pam_loginuid.so@#session required pam_loginuid.so@gi" /etc/pam.d/sshd \&& chmod 755 /run.sh \&& rm -rf /var/cache/apt/*ADD authorized_keys /root/.ssh/authorized_keysEXPOSE 22CMD ["/usr/sbin/sshd","-D"]
-
运行
# 运行并连接,个人是不太推荐使用这种,一个容器就跑一个服务(docker理念), 用docker exec也能满足 docker-ssh]# docker run -itd -p 11122:22 --name s1 sshd:test docker-ssh]# ssh 192.168.9.234 -p 11122 root@43405da61e28:~#