2019-05-07 Dockerfile 详解

本文仅为个人学习记录. 教程来源Docker--从入门到实践

FROM 指定基础镜像

FROM nginx

FROM 有一个比较特殊的镜像(空镜像)

FROM scratch

一般 golang 程序会直接使用FROM scratch.


RUN 执行命令

run指令有两种格式

  • shell格式
RUN echo 'hello docker'
  • exec格式
RUN apt-get update
RUN apt-get install -y gcc

注意:Dockerfile每一个指令都会构建一层,不得超过127层

所以很多时候我们会使用 && 来减少构建的层数
例如上例可改写为

RUN apt-get update && apt-get install -y gcc

Dockerfile nginx 实例

FROM nginx
RUN echo '

Hello,Docker!!

' > /usr/share/nginx/html/index.html

构建命令

docker build -t nginx:v3 .

运行命令

docker run --name webserver -d -p 80:80 nginx:v3

COPY 复制文件

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

COPY [--chown=:]<源路径> ... <目标路径>
COPY [--chown=:] ["<源路径1>",... "<目标路径>"]

<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
例子:

COPY package.json /usr/src/app/

这里源路径可以使用通配符,其通配符规则要满足 go 的 filepath.Match规则

例如:

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

ADD 更高级的复制文件 (虽然很高级但不建议使用)

ADD [--chown=:]<源路径> ... <目标路径>
ADD [--chown=:] ["<源路径1>",... "<目标路径>"]

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

比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用

注意 ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。


CMD 容器启动命令

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

CMD指令的格式和RUN相似,也是两种格式:

#shell格式
CMD <命令>
#exec格式
CMD ["可执行文件","参数1","参数2"...]

在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。

如果使用 shell 格式的话, 实际的命令会被包装为 sh -c 的参数的形式进行执行.

CMD echo $HOME

在实际执行中, 会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

这就是为什么我们可以使用环境变量的原因, 因为这些环境变量会被shell 进行解析处理, 提到CMD 就不得不提容器中应用在前台执行和后台执行的问题. 这是初学者常出现的一个混淆.

Docker不是虚拟机, 容器中的应用都应该以前台执行,而不是像虚拟机/物理机那样,用upstart/systemd去启动后台服务, 容器内没有后台服务的概念

一些初学者将CMD写为

CMD service nginx start

然后发现容器执行后就立即退出了,甚至在容器内去使用systemctl 命令,结果发现根本执行不了.这就是因为没搞明白前台,后台的概念, 没有区分容器和虚拟机的差异,依旧用传统虚拟机的角度去理解容器

对于容器而言, 其启动程序就是容器应用进程, 容器就是为了主进程而存在的, 主进程退出 , 容器就失去了存在的意义,从而退出, 其他辅助进程不是他需要关心的东西

而使用 service nginx start命令 则是希望upstart来以"后台守护进程"的形式启动nginx服务, 但刚才说了 CMD service nginx start 会被理解为 CMD [ "sh", "-c", "echo $HOME" ],因此主进程实际上是sh.那么当service nginx start命令结束后 , sh 也就结束了,sh作为主进程退出了, 自然就会让容器也跟着退出

正确的做法是 直接执行nginx可执行文件, 并且要求以前台的形式运行.
比如:

CMD ["nginx", "-g", "deamon off"]

个人理解:
推荐使用 exec格式的用意是为了避免类似sh -c的命令干扰


ENTRYPOINT (入口点)

ENTRYPOINT 的格式和RUN指令格式一样, 分为 execshell
ENTRYPOINT 的目的和CMD一样都是在指定容器启动程序及参数. ENTRYPOINT 在运行是也可以替代,不过比CMD略显繁琐, 需要通过docker run的参数--entrypoint来指定

当指定了ENTRYPOINT 后, CMD的含义就发生了改变, 不再是直接的运行其命令, 而是将CMD的内容当作参数传给ENTRYPOINT指令.可以理解为:

 ""

场景一: 让镜像变成像命令一样使用

假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD 来实现:

FROM ubuntu:18.04
RUN apt-get update \
    &&apt-get install -y curl \
    &&rm -rf /var/lib/apt/lists/*
CMD ["curl", "-s" ,"https://ip.cn"]

假如我们使用 docker build -t myip . 来构建镜像的话, 如果我们需要查询当前公网IP, 只需要执行

docker run myip

这里遇到一个问题, 如果我们想用curl -i的话就会出现问题
所以这里就引申出了ENTRYPOINT的用法

FROM ubuntu:18.04
RUN apt-get update \
    &&apt-get install -y curl \
    &&rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl","-s","https://ip.cn"]

ENV 设置环境变量

格式有两种

ENV  
ENV = = ...

这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。

.

你可能感兴趣的:(2019-05-07 Dockerfile 详解)