docker—Dockerfile指令详解

时间:2022-07-22
本文章向大家介绍docker—Dockerfile指令详解,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

dockerfile用来定制镜像,我们知道镜像实际上是一层一层的,镜像的定制实际上就是定制每一层所添加的配置和文件。

dockerfile是一个文本文件,该文件里包含了一条一条的指令,每一条指令就代表一层镜像,例如下面的一些例子

1 2

FROM nginx RUN echo 'test' > /usr/share/nginx/html/index.html

这是一个非常简单的镜像构建,实际上所谓定制镜像,其实就是在已有镜像的基础上进行二次修改,所以,FROM指令,必须要有,且永远都要放在第一条的位置

RUN 执行命令

还是以上面的dockerfile为例,只有短短的两行,我们看到RUN指令,这个可以像shell脚本一样,一行行去执行命令,例如:

1 2 3 4 5 6 7 8 9

FROM debian:jessie RUN apt-get update RUN apt-get install -y gcc libc6-dev make RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" RUN mkdir -p /usr/src/redis RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 RUN make -C /usr/src/redis RUN make -C /usr/src/redis install

一共是创建了7层镜像,实际上做的这些操作就是在安装redis,而这些操作我们完全可以在一层上来完成,这样既可以节省构建部署的时间,而且镜像也不会如此臃肿,修改如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14

FROM debian:jessie RUN buildDeps='gcc libc6-dev make' && apt-get update && apt-get install -y $buildDeps && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" && mkdir -p /usr/src/redis && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 && make -C /usr/src/redis && make -C /usr/src/redis install && rm -rf /var/lib/apt/lists/* && rm redis.tar.gz && rm -r /usr/src/redis && apt-get purge -y --auto-remove $buildDeps

这样,我们便对RUN指令有了一个大概的理解。

COPY 复制文件

从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置

1 2

COPY package.json /usr/src/app/

源路径可以是多个,甚至可以是通配符,如:

1 2

COPY hom* /mydir/ COPY hom?.txt /mydir/

目标路径可以是容器内的绝对路径,也可以是相对与工作目录的相对路径,目标路径不需要事先创建,不存在会自行创建,另外使用COPY指令进行复制,源文件的各种元数据都会保留,比如读、写、执行权限、文件变更时间等。

ADD更高级的复制文件

ADD指令和COPY指令的格式和性质基本一样,但是在COPY基础上加了一些功能

  • 源路径可以是一个URL,docker引擎会试图下载这个链接的文件放到目标路径中去,下载后的文件权限自动设置为600,如果想要修改权限,需要使用RUN指令进行权限调整,如果下载的是压缩包,则需要解压缩,同样需要RUN指令进行解压
  • 源路径是一个tar压缩文件,格式为gzip、bzip2、xz时,ADD会自动解压文件到目标路径去
  • 在COPY和ADD指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY,只有需要自动解压缩的场合使用ADD

CMD容器启动命令

Docker不是虚拟机,容器就是进程,既然是进程,那么在容器启动的时候,需要指定所运行的程序及参数,CMD指令用于指定默认的容器主进程的启动命令

另外需要注意:docker不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用upstart/systemd去启动服务,容器内没有后台服务的概念,例如启动nginx为例:

1

CMD service nginx start

这种启动方式会发现容器执行后就立即退出了,这是因为对于容器而言,启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义。使用service nginx start命令启动其实是使用upstart来以后台守护进程形式启动nginx服务,会被CMD理解为:CMD [“sh”,"-c”,“service nginx start”],这样的话实际上主进程是sh,那么当service nginx start执行完后,sh会结束,容器自然也会退出。

正确的CMD姿势:

1

CMD ["nginx","-g","daemon off;"]

ENTRYPOINT入口点

ENTRYPOINT和CMD一样,在指定容器启动程序及参数

那CMD和ENTRYPOINT的区别在哪呢

1、让镜像变成像命令一样使用

1 2 3 4 5 6 7 8 9 10 11

1、写文本文件 [root@xs_test01 myip]# cat Dockerfile FROM centos RUN yum -y update $$ yum -y install curl CMD [ "curl","-s","http://ip.cn"] 2、构建镜像 docker build -t myip 3、像命令一样使用镜像 [root@xs_test01 myip]# docker run myip 当前 IP:218.240.128.48 来自:北京市 京宽网络

CMD可以完成这个功能,但是如果我们想要查看HTTP头信息,那么CMD就无法胜任了,除非我们重构

2、使用ENTRYPOINT完成CMD无法完成的

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

FROM centos RUN yum -y update $$ yum -y install curl ENTRYPOINT [ "curl","-s","http://ip.cn"] 如果想使用同样的名称,可以删除之前的容器和镜像,删除顺序不能便,删除容器用docker rm 删除镜像用docker rmi 测试: [root@xs_test01 myip]# docker run myip -i HTTP/1.1 200 OK Date: Mon, 05 Mar 2018 09:51:11 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: __cfduid=d5836d5616d7166018b5107776a60302f1520243471; expires=Tue, 05-Mar-19 09:51:11 GMT; path=/; domain=.ip.cn; HttpOnly Server: cloudflare CF-RAY: 3f6baec393149330-SJC 当前 IP:218.240.128.48 来自:北京市 京宽网络

使用ENTRYPOINT后,-i会作为新的参数传给curl

3、来完成应用运行前的准备工作

启动容器就是启动主进程,有些时候启动主进程前需要做一些准备工作。例如MySQL类的数据库,可能需要一些数据库配置、初始化的工作、这些工作是在MySQL服务启动之前完成,还有就是我们希望避免使用root用户去启动服务,从而提高安全性等等

1 2 3 4 5 6 7 8 9 10

1、写配置文件 FROM alpine:3.4 RUN addgroup -S redis && adduser -S -G redis redis ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD ["redis-server"] 2、制成镜像 docker build -t entrypoint . 3、启动容器 docker run -itd --rm --name redis_01 redis redis-serve

ENV设置环境变量

1 2

ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"

1 2 3 4 5 6 7 8 9 10

ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.ta r.xz" && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc && grep " node-v$NODE_VERSION-linux-x64.tar.xz$" SHASUMS256.txt | sha256sum -c - && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components= 1 && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt && ln -s /usr/local/bin/node /usr/local/bin/nodejs

在这里先定义了环境变量NODE_VERSION,其后的RUN这层,多次使用$NODE_VERSION来进行操作定制,可以看到,将来升级镜像构建版本的时候,只需要更新7.2.0即可。我们也可以通过不同的环境变量来让一个dockerfile制作更多的镜像。

ARG构建参数

构建参数和ENV的效果是一样的,都是设置环境变量。不同的是构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的,但是不要因此就是用ARG保存密码之类的信息,因为docker history还是可以看到所有值的。

VOLUME定义匿名卷

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存与卷中,为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在dockerfile中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据

VOLUME /data

这里的/data目录就会在运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。运行时我们也可以通过指定参数来覆盖这个挂载设置,例如:

docker run -d -v mydata:/data nginx

意思是使用mydata这个命名卷挂载到了/data这个位置,替代了dockerfile中定义的匿名卷的挂载位置。

EXPOSE声明端口

EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务,好处是:

  1. 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射
  2. 在运行时使用随机端口映射时,也就是docker run -P时,会自动随机映射EXPOSE的端口

WORKDIR指定工作目录

使用WORKDIR指令可以指定工作目录,以及各层的当前目录就被改为指定的目录,如果该目录不存在,WORDDIR会自动创建

USER指定当前用户

USER指令和WORKDIR相似,都是改变环境状态并影响以后的层,WORKDIR是改变工作目录,USER则是改变之后层的执行RUN、CMD、及ENTRYPOINT这类命令的身份

1 2 3

RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ]

如果想在执行期间改变身份,不要使用su或sudo,建议使用gosu

1 2 3 4 5 6 7 8 9

# 建立 redis 用户,并使用 gosu 换另一个用户执行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下载 gosu RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/ gosu-amd64" && chmod +x /usr/local/bin/gosu && gosu nobody true # 设置 CMD,并以另外的用户执行 CMD [ "exec", "gosu", "redis", "redis-server" ]

HEALTCHCHECK健康检查

顾名思义,设置检查容器健康状况的命令

1 2 3 4

FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s CMD curl -fs http://localhost/ || exit 1

构建镜像

我们的Dockerfile写完之后,我们需要去执行它,也就是我们要开始构建镜像了,其格式为:

1

docker build [选项] &lt;上下文路径/URL/->

这里我们要说下什么是上下文路径?

有时我们的指令中会包含一些ADD和COPY指令,它们需要将本地文件复制进镜像,而docker的架构实际上是C/S架构,我们做的docker build操作实际上是在服务端,所以我们如果要想让服务端获得本地文件,我们就必须指定构建镜像的上下文路径,docker build执行后,会将路径下的所有内容打包,然后上传给docker引擎,这样docker引擎收到上下文包后,展开就会获得构建镜像所需的以切文件,举一个简单的例子:

1

COPY ./test.sh /data/

我们这里并不是要复制dockerfile文件所在的当前目录下的test.sh到当前目录下的子目录data中,而是复制上下文目录下的test.sh,因此COPY指定的源文件均是相对路径