docker基础之DockerFile(六)
DockerFile介绍
dockerfile是用来构建docker镜像的文件! 命令参数脚本!
构建步骤:
1.编写一个dockerfile文件
2.docker build 构建成为一个镜像
3.docker run 运行镜像
4.docker push 发布镜像(DockerHub,阿里云镜像仓库)
=============
查看官方是怎么做的
也是一个Dockerfile脚本
官方镜像多为基础包,很多功能没有,需要我们再其基础上制作自己镜像
DockerFile构建过程
很多指令,
基础知识:
1.每个保留关键字(指令)都是大写
2.执行从上到下顺序执行
3.#表示注释
4.每个指令都会创建提交一个新的镜像层,并提交
=========
DockerFile指令
FROM #基础镜像,一切从这里构建 MAINTAINER #镜像是谁写的,姓名+邮箱,要废弃 RUN #镜像构建时要运行的命令 ADD #添加内容,tomcat镜像,添加tomcat包 WORKDIR #工作目录 VOLUME #挂载目录 EXPOSE #暴露端口 CMD #指定容器启动时运行的命令,最后一个生效,可被替代 ENTRYPOINT #指定容器启动是运行的命令,可以追加命令 ONBUILD #当构建一个被继承的dockerfile,这是会运行ONBUILD指令
命令详解:
Dockerfile格式
# Comment 注释
INSTRUCTION arguments
- INSTRUCTION:指令,不区分大小写,但是约定使用大写形式,以遍更容易的与参数区分开来
- arguments : 命令行参数
- Comment : 以京考开头的注释
FROM 基础镜像
FROM [--platform=<platform>] <image> [AS <name>] FROM [--platform=<platform>] <image>[:<tag>] [AS <name>] FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM 指令初始化一个新的构建阶段,并为后续指令设置基础镜像,该镜像可以是任何有效的镜像。举个例子:
FROM centos:7 ...
需要注意的是:
- 一般来说,Dockerfile都是以
FROM
指令开始;但ARG
是Dockerfile中唯一可能先于FROM
的指令(具体参考下面的ARG介绍)。 FROM
可以在一个Dockerfile中出现多次,以创建多个映像或使用一个构建阶段作为另一个构建阶段的依赖项。只需在每个新的FROM
指令之前记录提交的最后一个图像ID输出。每个FROM
指令清除前面指令创建的任何状态。[AS <name>]
参数可以添加在FROM
指令之后,来为新的构建阶段指定一个名称。该名称可以在后续的FROM
和COPY ——FROM =<name>
指令中使用,以引用在此阶段构建的映像。tag
或digest
值是可选的。如果省略其中任何一个,构造器默认采用latest
标记。如果不能找到tag
值,构造器将返回一个错误。--platform
标志可用于在FROM
引用多平台镜像的情况下指定镜像的平台。如:linux/amd64
,linux/arm64
, orwindows/amd64
- 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为
scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
MAINTAINER (废弃) 设置Author
MAINTAINER <name>
MAINTAINER
指令设置生成的镜像的Author字段,已经废弃使用。LABEL
指令是一个更灵活的版本,你、应该使用它,因为它可以设置所需要的任何元数据,并且可以很容易地通过docker inspect
查看。可以使用与MAINTAINER
字段相对应的标签:
LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"
RUN 执行命令
RUN
指令将在当前镜像上的新层中执行任何命令并提交结果。生成的提交镜像将用于Dockerfile中的下一步。其格式有两种:
- shell 格式:
RUN <命令>
,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的RUN
指令就是这种格式。 - exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
,这更像是函数调用中的格式。
需要注意的是,Dockerfile 中每一个指令都会建立一层,RUN
也不例外。每一个 RUN
的行为,就会新建立一层,在其上执行这些命令,执行结束后,commit
这一层的修改,构成新的镜像。
在shell形式中,可以使用(反斜杠)来将单个RUN指令继续到下一行。例如这两行:
RUN /bin/bash -c 'source $HOME/.bashrc; \ echo $HOME' #等价于 RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
COPY 复制文件
COPY [--chown=<user>:<group>] <源路径>... <目标路径> COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
和 RUN
指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY
指令将从构建上下文目录中 <源路径>
的文件/目录复制到新的一层的镜像内的 <目标路径>
位置。比如:
COPY package.json /usr/src/app/
<源路径>
可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match
规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
--chown=<user>:<group>
选项用来改变文件的所属用户及所属组。
COPY --chown=55:mygroup files* /mydir/ COPY --chown=bin files* /mydir/ COPY --chown=1 files* /mydir/ COPY --chown=10:11 files* /mydir/
注意:如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
ADD 更高级的复制文件
ADD
指令和 COPY
的格式和性质基本一致。但是在 COPY
基础上增加了一些功能。
比如 <源路径>
可以是一个 URL
,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径>
去。下载后的文件权限自动设置为 600
,如果这并不是想要的权限,那么还需要增加额外的一层 RUN
进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN
指令进行解压缩。所以不如直接使用 RUN
指令,然后使用 wget
或者 curl
工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
如果 <源路径>
为一个 tar
压缩文件的话,压缩格式为 gzip
, bzip2
以及 xz
的情况下,ADD
指令将会自动解压缩这个压缩文件到 <目标路径>
去。
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu
中:
FROM scratch ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz / ...
同样,在使用该指令的时候还可以加上 --chown=<user>:<group>
选项来改变文件的所属用户及所属组。
COPY
vs ADD
:
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY
,因为 COPY
的语义很明确,就是复制文件而已,而 ADD
则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD
的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是,ADD
指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在 COPY
和 ADD
指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY
指令,仅在需要自动解压缩的场合使用 ADD
。
CMD 容器启动命令
The CMD
指令的三种格式:
CMD ["executable","param1","param2"] (exec 格式, 首选推荐) CMD ["param1","param2"] (在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数,下面介绍ENTRYPOINT指令) CMD command param1 param2 (shell 格式)
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD
指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu
镜像默认的 CMD
是 /bin/bash
,如果我们直接 docker run -it ubuntu
的话,会直接进入 bash
。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release
。这就是用 cat /etc/os-release
命令替换了默认的 /bin/bash
命令了,输出了系统版本信息。
在指令格式上,一般推荐使用 exec
格式,如果使用 shell
格式的话,实际的命令会被包装为 sh -c
的参数的形式进行执行。比如:
CMD echo $HOME #在实际执行中,会将其变更为: CMD [ "sh", "-c", "echo $HOME" ]
注意1:容器中应用应在前台执行;原因:Docker 不是虚拟机,容器就是进程。如下面Nginx容器启动的命令。
无效的命令(容器执行后就立即退出,原因容器主进程结束):
CMD service nginx start
有效命令(前台方式运行,不结束主进程):
CMD ["nginx", "-g", "daemon off;"]
注意2:一个Dockerfile应该仅有一个CMD
指令,当存在多个,仅最后一个生效(以最后一个为准)。
ENTRYPOINT 入口点
ENTRYPOINT
的格式和 RUN
指令格式一样,分为 exec
格式和 shell
格式。
ENTRYPOINT
的目的和 CMD
一样,都是在指定容器启动程序及参数。ENTRYPOINT
在运行时也可以替代,不过比 CMD
要略显繁琐,需要通过 docker run
的参数 --entrypoint
来指定。
当指定了 ENTRYPOINT
后,CMD
的含义就发生了改变,不再是直接的运行其命令,而是将 CMD
的内容作为参数传给 ENTRYPOINT
指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
那么有了 CMD
后,为什么还要有 ENTRYPOINT
呢?这种 <ENTRYPOINT> "<CMD>"
到底是什么意思,有什么好处?
举个例子🌰:
假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD
来实现:
FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* CMD [ "curl", "-s", "http://myip.ipip.net" ]
使用 docker build -t myip .
来构建镜像。 然后启动容器:
$ docker run myip 当前 IP:219.142.100.68 来自于:中国 北京 北京 电信
从上面的 CMD
中可以看到实质的命令是 curl
,那么如果我们希望显示 HTTP 头信息,就需要加上 -i
参数,结果如下:
$ docker run myip -i docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
显然curl -s http://myip.ipip.net
被替换成了-i
,并不存在这样的命令于是报错了。我们需要执行下面的命令才能正确的执行:
$ docker run myip curl -s http://myip.ipip.net -i HTTP/1.1 200 OK ... 当前 IP:219.142.100.68 来自于:中国 北京 北京 电信
这里便用完整的 curl -s http://myip.ipip.net -i
替换了curl -s http://myip.ipip.net
从而实现了HTTP头信息的显示。但这显然不是很好的解决方案,而使用 ENTRYPOINT
就可以解决这个问题。现在我们重新用 ENTRYPOINT
来实现这个镜像:
FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
这次我们再来尝试直接使用 docker run myip -i
:
$ docker run myip 当前 IP:219.142.100.68 来自于:中国 北京 北京 电信 $ docker run myip -i HTTP/1.1 200 OK Date: Wed, 22 Sep 2021 09:18:21 GMT Content-Type: text/plain; charset=utf-8 Content-Length: 69 Connection: keep-alive X-Shadow-Status: 200 X-Via-JSL: 1d9bd9a,4f293cc,- Set-Cookie: __jsluid_h=9e709c5148e18188831d1230155ff4d4; max-age=31536000; path=/; HttpOnly X-Cache: bypass 当前 IP:219.142.100.68 来自于:中国 北京 北京 电信
由此可见,当存在 ENTRYPOINT
后,CMD
的内容将会作为参数传给 ENTRYPOINT
,而这里 -i
就是新的 CMD
,因此会作为参数传给 curl
,从而达到了我们预期的效果。
ENV 设置环境变量
ENV
指令用于设置环境变量,无论是后面的其它指令,如 RUN
,还是运行时的应用,都可以直接使用这里定义的环境变量。
格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
示例1:
如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。
ENV VERSION=1.0 DEBUG=on \ NAME="Happy Feet"
示例2:
定义环境变量,在后续的指令中使用这个环境变量。比如在官方 node
镜像 Dockerfile
中,就有类似这样的代码。这里先定义了环境变量 NODE_VERSION
,其后的 RUN
这层里,多次使用 $NODE_VERSION
来进行操作定制。
ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.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
可以看到,将来升级镜像构建版本的时候,只需要更新 7.2.0
即可,Dockerfile
构建维护变得更轻松了。
下列指令都支持环境变量: ADD
、COPY
、ENV
、EXPOSE
、FROM
、LABEL
、USER
、WORKDIR
、VOLUME
、STOPSIGNAL
、ONBUILD
、RUN
。
可以看到,环境变量可以使用的地方很多,很强大。通过环境变量,可以让一份 Dockerfile
制作更多的镜像,只需使用不同的环境变量即可。
ARG 构建参数
ARG <参数名>[=<默认值>]
构建参数和 ENV
的效果一样,都是设置环境变量。所不同的是,ARG
所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG
保存密码之类的信息,因为 docker history
还是可以看到所有值的。
Dockerfile
中的 ARG
指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build
中用 --build-arg <参数名>=<值>
来覆盖。
灵活的使用 ARG
指令,能够在不修改 Dockerfile 的情况下,构建出不同的镜像。
ARG 指令有生效范围,如果在 FROM
指令之前指定,那么只能用于 FROM
指令中。
ARG DOCKER_USERNAME=library FROM ${DOCKER_USERNAME}/alpine RUN set -x ; echo ${DOCKER_USERNAME}
使用上述 Dockerfile 会发现无法输出 ${DOCKER_USERNAME}
变量的值,要想正常输出,必须在 FROM
之后再次指定 ARG
# 只在 FROM 中生效 ARG DOCKER_USERNAME=library FROM ${DOCKER_USERNAME}/alpine # 要想在 FROM 之后使用,必须再次指定 ARG DOCKER_USERNAME=library RUN set -x ; echo ${DOCKER_USERNAME}
VOLUME 定义匿名卷
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中(后面进一步介绍 Docker 卷的概念 待完善)。
为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile
中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
VOLUME /data
这里的 /data
目录就会在容器运行时自动挂载为匿名卷,任何向 /data
中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。比如:
$ docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。(具名挂载)
EXPOSE 暴露端口
EXPOSE <端口1> [<端口2>...]
EXPOSE
指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE
的端口。
要将 EXPOSE
和在运行时使用 -p <宿主端口>:<容器端口>
区分开来。-p
,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE
仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
WORKDIR 指定工作目录
WORKDIR <工作目录路径>
使用 WORKDIR
指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR
会帮你建立目录。
示例1
Dockerfile
进行构建镜像运行后,找不到 /app/world.txt
文件的写法:
RUN cd /app RUN echo "hello" > world.txt
在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile
中,这两行 RUN
命令的执行环境根本不同,是两个完全不同的容器。
正确找到 /app/world.txt
文件的写法:
WORKDIR /app RUN echo "hello" > world.txt
示例2
WORKDIR
指令使用的相对路径,那么所切换的路径与之前的 WORKDIR
有关:
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
#RUN pwd
的工作目录为/a/b/c
。
USER 指定当前用户
USER <用户名>[:<用户组>]
USER
指令和 WORKDIR
相似,都是改变环境状态并影响以后的层。WORKDIR
是改变工作目录,USER
则是改变之后层的执行 RUN
, CMD
以及 ENTRYPOINT
这类命令的身份。
注意,USER
只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ]
如果以 root
执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su
或者 sudo
,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu
。
# 建立 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.12/gosu-amd64" \ && chmod +x /usr/local/bin/gosu \ && gosu nobody true # 设置 CMD,并以另外的用户执行 CMD [ "exec", "gosu", "redis", "redis-server" ]
HEALTHCHECK 健康检查
格式:
HEALTHCHECK [选项] CMD <命令>
:设置检查容器健康状况的命令HEALTHCHECK NONE
:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK
指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。
当在一个镜像指定了 HEALTHCHECK
指令后,用其启动容器,初始状态会为 starting
,在 HEALTHCHECK
指令检查成功后变为 healthy
,如果连续一定次数失败,则会变为 unhealthy
。
HEALTHCHECK
支持下列选项:
--interval=<间隔>
:两次健康检查的间隔,默认为 30 秒;--timeout=<时长>
:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;--retries=<次数>
:当连续失败指定次数后,则将容器状态视为unhealthy
,默认 3 次。
和 CMD
, ENTRYPOINT
一样,HEALTHCHECK
只可以出现一次,如果写了多个,只有最后一个生效。
在 HEALTHCHECK [选项] CMD
后面的命令,格式和 ENTRYPOINT
一样,分为 shell
格式,和 exec
格式。命令的返回值决定了该次健康检查的成功与否:0
:成功;1
:失败;2
:保留,不要使用这个值。
举个例子🌰:
假设有个镜像是个最简单的 Web 服务,通过增加健康检查来判断其 Web 服务是否在正常工作,可以用 curl
来帮助判断,其 Dockerfile
的 HEALTHCHECK
可以这么写:
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
这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1
作为健康检查命令。
使用 docker build
来构建这个镜像:
$ docker build -t myweb:v1 .
构建好了后,我们启动一个容器:
$ docker run -d --name web -p 80:80 myweb:v1
当运行该镜像后,可以通过 docker container ls
看到最初的状态为 (health: starting)
:
$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web
在等待几秒钟后,再次 docker container ls
,就会看到健康状态变化为了 (healthy)
:
$ 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
如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)
。
为了帮助排障,健康检查命令的输出(包括 stdout
以及 stderr
)都会被存储于健康状态里,可以用 docker inspect
来查看。
$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool { "FailingStreak": 0, "Log": [ { "End": "2016-11-25T14:35:37.940957051Z", "ExitCode": 0, "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n", "Start": "2016-11-25T14:35:37.780192565Z" } ], "Status": "healthy" }
ONBUILD 为他人作嫁衣
格式:ONBUILD <其它指令>
。
ONBUILD
是一个特殊的指令,它后面跟的是其它指令,比如 RUN
, COPY
等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile
中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD
是为了帮助别人定制自己而准备的。
举个例子🌰:
假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm
进行包管理,所有依赖、配置、启动信息等会放到 package.json
文件里。在拿到程序代码后,需要先进行 npm install
才可以获得所有需要的依赖。然后就可以通过 npm start
来启动应用。我们可以先做一个基础镜像:
FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ]
然后在多个子项目中:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile
构建镜像时,之前基础镜像的那三行 ONBUILD
就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install
,生成应用镜像。
LABEL 为镜像添加元数据
LABEL
指令用来给镜像以键值对的形式添加一些元数据(metadata)。
LABEL <key>=<value> <key>=<value> <key>=<value> ...
我们还可以用一些标签来申明镜像的作者、文档地址等:
LABEL org.opencontainers.image.authors="yeasy" LABEL org.opencontainers.image.documentation="https://yeasy.gitbooks.io"
具体可以参考 https://github.com/opencontainers/image-spec/blob/master/annotations.md
STOPSIGNAL 容器平滑退出
STOPSIGNAL signal
官方文档
The
STOPSIGNAL
instruction sets the system call signal that will be sent to the container to exit. This signal can be a valid unsigned number that matches a position in the kernel’s syscall table, for instance 9, or a signal name in the format SIGNAME, for instance SIGKILL.
STOPSIGNAL
指令设置将发送到容器的系统调用信号以退出。此信号可以是与内核的系统调用表中的位置匹配的有效无符号数,例如 9,或 SIGNAME
格式的信号名,例如 SIGKILL
。
默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过--stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s。
SHELL 指令
格式:SHELL ["executable", "parameters"]
SHELL
指令可以指定 RUN
ENTRYPOINT
CMD
指令的 shell,Linux 中默认为 ["/bin/sh", "-c"]
SHELL ["/bin/sh", "-c"] RUN lll ; ls SHELL ["/bin/sh", "-cex"] RUN lll ; ls
两个 RUN
运行同一命令,第二个 RUN
运行的命令会打印出每条命令并当遇到错误时退出。
当 ENTRYPOINT
CMD
以 shell 格式指定时,SHELL
指令所指定的 shell 也会成为这两个指令的 shell
SHELL ["/bin/sh", "-cex"] # /bin/sh -cex "nginx" ENTRYPOINT nginx
SHELL ["/bin/sh", "-cex"] # /bin/sh -cex "nginx" CMD nginx
参考
- 官方Dockerfile参考文档:Dockerfile reference
- Docker--从入门到实践:https://yeasy.gitbook.io/docker_practice/image/dockerfile
编写例子:
1.微服务springboot编译的jar文件发布成镜像
# 把编译的jar文件和Dockerfile放一块
FROM java:8 LABEL author="fps2tao" LABEL email="xxxx@qq.com" COPY *.jar /app.jar CMD ["--server.port=8080"] EXPOSE 8080 ENTYPPOINT ["javr","-jar","/app.jar"]
2.演示一个go语言的web
首先我们使用go语言做了个web服务,如下main.go
package main import ( "log" "net/http" "time" ) func main() { // 创建路由器 mux := http.NewServeMux() // 设置路由规则 mux.HandleFunc("/hello", sayHello) // 创建服务器 server := &http.Server{ Addr: ":8080", WriteTimeout: time.Second * 30, Handler: mux, } // 监听端口并提供服务 log.Println("starting httpserver at http:localhost:8080") log.Fatal(server.ListenAndServe()) } func sayHello(w http.ResponseWriter, r *http.Request) { time.Sleep(1 * time.Second) w.Write([]byte("hello , this is httpserver")) }
然后我们便可着手写Dockerfile文件了,示例如下
Dickerfile
# FROM 基于 golang:1.18-alpine FROM golang:1.18-alpine AS builder # ENV 设置环境变量 ENV GOPATH=/opt/repo ENV GO111MODULE=on ENV GOPROXY=https://goproxy.io,direct # RUN 设置 Asia/Beijing 时区 RUN apk --no-cache add tzdata && \ ln -sf /usr/share/zoneinfo/Asia/Beijing /etc/localtime && \ echo "Asia/Beijing" > /etc/timezone # COPY 源路径 目标路径 从镜像中 COPY COPY --from=builder /opt /opt # EXPOSE 设置端口映射 EXPOSE 8080/tcp # WORKDIR 设置工作目录 WORKDIR /opt # CMD 设置启动命令 CMD ["./main.go", "run"]
然后便可在主机的docker compose目录下去创建docker-compose.yml 文件并进行编写了,示例如下
# yaml 配置 version: '1.18' services: web: build: ./main.go ports: - "8080:8080"
然后便可通过docker-compose up 命令将项目启动并生成容器
和docker命令一样, 可加-d使其处于守护进程的方式运行 (关于docker-compose,可以看之后篇幅的介绍)
==========================
dockerfile是面向开发的,我们以后要发布项目,做镜像,需要编码dockerfile文件,这个文件十分简单。
docker镜像逐渐成为企业交付的标准,必须掌握,之前是jar,war,等.
dockerfile : 构建文件,定义一切的步骤,源代码,
dockerImages : 通过dockerfile构建生成的镜像,最终发布和运行产品,
docker容器: 容器就是镜像运行起来提供服务.
参考
https://www.cnblogs.com/nm666/p/15325164.html
https://www.cnblogs.com/nm666/p/15305561.html
https://blog.csdn.net/single_0910/article/details/113558099
https://blog.csdn.net/u010271601/article/details/109104417
https://juejin.cn/post/7155424094035378184