一、简介

  在构建容器化应用时,相当重要的步骤莫过于镜像制作,本文将介绍镜像制作方法以及镜像制作的建议。通常镜像的制作有两种方式:
  • 使用现有的容器使用docker commit 生成镜像
  • 使用Dockerfile进行镜像构建
    采用docker commit 生成的镜像实际上是容器内的文件系统进行修改在进行提交,而运行的容器实际上是在镜像的文件系统顶层添加了一层读写层,所都的修改都是基于这一层,当生成镜像时会将这一层数据保存,所以每次使用commit提交镜像时候都会比原来多一层,这样会使得镜像越来越大并且不易维护。同时,对于镜像使用者来说完全不透明,使用者不清楚该镜像怎么样构建的,是否安全等,这种方式及其不推荐。
    而使用Dockerfile构建镜像,对于使用者来说完全透明,构建镜像的每一个步骤都在Dockerfile文件中描述的清清楚楚,同时当需要对镜像修改时候,只需修改Dockerfile文件中的指令,维护镜像只需要维护一个Dockerfile,这也是镜像构建的最佳方式。当然,要使用Dockerfile就必须明白Dockerfile的语法和各个指令,以下将作详细介绍。
 

二、Dockerfile介绍

 
Dockerfile实际上就是一个文本文件,只不过这里的文件内容被Docker Deamon识别从而进行镜像构建。
使用Dockerfile步骤:
  1.编写Dockerfile文件,用于描述镜像生成的步骤
  2.使用docker build -t name:tag 命令构建镜像
 

语法规则

1.#号代表注解。
2.Dockerfile每一行都是以某个指令(约定大写字母)开始,后面可加参数构成完整指令,用于描述镜像构建步骤。
3.指令从上倒下依次执行
4.Dockerfile的第一个指令一定是FROM指令,用于指定基础镜像
5.Dockerfile还可以使用.dockerignore文件来忽略在制作镜像时候需要忽略的文件或者目录,列如使用COPY指令时候忽略某些文件或者目录。
6.所有指令参数为数组时,最好使用双引号
 

环境变量引用

1.若要在Dockerfile中引环境变量则使用$variable_name或${variable_name}
2.当变量为空或者变量值未设置可以使用${variable_name:-value}来指定变量的默认值
 

docker build命令

docker build 命令用于基于Dockerfile构建镜像,使用语法:
docker build [OPTIONS] PATH | URL | -
其中PATH代表含有Dockfile的目录,当然也可以是URL中含有Dockerfile
常用选项:
  • -t, --tag list  指定生成镜像标签,格式为name:tag
  • -f, --file string  单独指定Dockerfile文件位置
  • --build-arg list  设置构建时的变量
  • --no-cache  构建镜像时候不使用缓存
 

快速开始

构建一个简单的nginx镜像:
1.创建一个目录用于存放DockerFile
mkdir /opt/demo -p
cd /opt/demo/
2.编辑Dockerfile文件,如果文件名称不是Dockerfile需要用-f指定名称。
FROM centos:latest  #指定基础镜像为centos
LABEL Author=“wd” #指明作者
RUN yum install -y yum epel-release && yum install -y nginx && echo "${HOSTNAME}-nginx server" > /usr/share/nginx/html/index.html #运行命令安装Nginx
CMD [ "/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf”] #启动容器运行的命令
3.构建镜像
[root@app51 demo]# docker build -t nginx:v1 ./
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos:latest
---> 1e1148e4cc2c
Step 2/4 : LABEL Author="wd"
---> Using cache
---> 8eb3ffcb8ba3
Step 3/4 : RUN yum install -y yum epel-release && yum install -y nginx && echo "${HOSTNAME}-nginx server" > /usr/share/nginx/html/index.html
---> Using cache
---> ac91999a716e
Step 4/4 : CMD [ "/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]
---> Running in 323afd4ac89d
Removing intermediate container 323afd4ac89d
---> 6403c553fd04
Successfully built 6403c553fd04
Successfully tagged nginx:v1
4.利用制作的镜像启动容器,并查看是否运行成功.
[root@app51 demo]# docker run -d --name nginx-demo-c1 -p 8088:80 nginx:v1
08812b7def62c9ad7879dfa4182bc28a20f524e2dbc5eb6e4fe63d2b67be3cc9
[root@app51 demo]# curl http://127.0.0.1:8088
60e5de135132-nginx server #访问成功
[root@app51 demo]#
以上的Dockerfile中的每一行是一个指令,用于描述镜像生成的步骤,以下将介绍这些指令用法。
 

三、指令详解

FROM

  FROM指令是最重要且必须为Dockerfile中的第一个非注视指令,用于为构建的镜像指定基础镜像。后续指令运行环境基于该基础镜像,构建镜像时候默认会先从主机上寻找镜像,若不存在时则从Docker HUB上拉取镜像。
语法 :
FROM <repository>
FROM <repository>[:<tag>]
FROM <repository>@<digest>

解释:
repository:镜像仓库
tag:镜像标签,省略就是latest
digest:镜像哈希码
示例: 
FROM centos:latest

LABEL

  LABEL用于为镜像提供元数据信息,其数据格式为key=value。
语法 :
LABEL <key>=<value> <key>=<value> <key>=<value> ...

示例:

LABEL "com.example.vendor"="ACME Incorporated”
LABEL maintainer="SvenDowideit@home.org.au"

MAINTAINER (deprecated)

  用于提供镜像提供者的信息,可以在Docker任何位置。该语法可能废弃,推荐使用LABEL
语法:
MAINTAINER <message>

解释:
message:可以是任意文本信息
示例: 
MAINTAINER "wd <xxx@163.com>"

COPY

  用于主机中的文件或者复制到镜像中

语法:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

解释:
src:源文件或者目录,支持通配符。如果src是目录,src目录自己不会被复制,复制的是目录中的文件
dest:容器中文件系统目录,如果目录不存在自动创建创建。
user:复制到容器中的文件所属用户
group:复制到容器中的文件所属用户组
注意事项:
  1. 如果复制的src或dest中存在空格字符需使用第二种加双引号方式
  2. src必须是 build的上下文目录(Dockerfile同级目录或子目录),不能是父目录或者绝对路径
  3. 如果指定来多个src或者src中使用了通配符,则dest必须是一个目录,且必须以/结尾
示例: 
COPY hom* /mydir/        #拷贝以hom开头的的所有文件
COPY hom?.txt /mydir/ #?代表占位符,可以拷贝

ADD

  ADD指令类似于COPY,但是ADD比COPY更强大,支持TAR文件和URL路径

语法:

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

解释
src:源文件或者目录,支持通配符。如果src是目录,src目录自己不会被复制,复制的是目录中的文件
dest:容器中文件系统目录,如果目录不存在自动创建创建。
user:复制到容器中的文件所属用户
group:复制到容器中的文件所属用户组
注意事项:
  1. 当src是URL时,如果dest不以/结尾,则src指定的文件将被下载并且被创建为dest,如果dest以/结尾,则src指定下载的文件会保存在dest目录下。
  2. 当src是一个本地目录的一个tar压缩格式文件,其在容器中会被展开为目录,类型与tar -x命令,通过URL下载的tar文件则不会被解压。
  3. 如果指定来多个src或者src中使用了通配符,则dest必须是一个目录,且必须以/结尾,多个文件一同被复制在dest目录下
示例:
ADD hom* /mydir/
ADD hom?.txt /mydir/

WORKDIR

  用于为Dockerfile中的各个指定设置工作目录,可以使用多次,当使用相对路径时目录是基于前一个WORKDIR指令。
语法 :
WORKDIR dirpath

示例:

WORKDIR /usr/local

ENV

  用于为镜像定义所需的环境变量,并可被Dockfile中位于其以后的指令所调用,如ADD、COPY、RUN等调用格式为$variable_name或者${variable_name},此外在启动容器时候这些变量也是存在的。

语法:

ENV <key> <value>
ENV <key>=<value> ...

注意:
  1. 第一种格式中key之后的所有值会被作为value,因此一次只能设置一个变量
  2. 第二种格式可一次性设置多个变量,每个变量为一个key=value的键值对,如果value种包含空格,可以用反斜线(\)转义,也可以通过对value加引号进行标识,此外反斜线也可用于续行,多个变量时候建议使用。

示例:

ENV myName="John Doe” \
myDog=Rex \
myCat=fluffy
ENV myCat fluffy

RUN

  用于在build过程中运行的程序,可以是任何指令,可以指定多个RUN

语法:

RUN <command>  #shell 格式默认linux采用/bin/sh -c,windows采用cmd /S /C
RUN ["executable", "param1", "param2”] #可执行程序格式

示例:

RUN yum install -y nginx
RUN ["/bin/bash", "-c", "echo hello"]

EXPOSE

  用于为容器暴露端口到外部,用于实现通讯,类似于docker run的-p选项

语法:

EXPOSE <port> [<port>/<protocol>...]

解释:
port:端口
protocol:协议,可以是udp或tcp,默认tcp
示例: 
EXPOSE 8080
EXPOSE 8080/udp 8088/tcp

VOLUME

  用于在image中创建一个挂载目录,以挂载宿主机上的目录

语法:

VOLUME <path>
VOLUME ["path"]

解释:
path:代表容器中的目录,与docker run 不同,Dockerfile中不能指定宿主机目录,默认使用docker管理的挂载点
示例: 
VOLUME ["/var/log/“]
VOLUME /myvol

CMD

  用于为在镜像启动为容时候提供的默认命令,该指定可以有多个,但是只有最后一个生效。
语法 :
CMD command param1 param2   #shell格式,含有shell环境
CMD ["executable","param1","param2”] #可执行程序格式
CMD ["param1","param2”] #第三种用于为ENTRYPOINT提供默认参数

注意:

  1. 在第一种格式中command 通常是一个shell命令,且默认以/bin/sh -c来运行它,这意味着此进程在容器的的PID不为1,不能接受unix信号,因此使用docker stop <container>命令停止容器时,此进程接受不到SIGTERM信号。
  2. 第二种格式是可执行程序运行方式,不会以"/bin/sh -c”来发起,无shell环境,所有shell变量不能引用,但是可以用"/bin/bash -c”作为启动命令达到第一种格式效果
  3. 第三种格式需要结合ENTRYPOINT使用,作用是为其提供默认参数

ENTRYPOINT

  类似于CMD功能,用于为启动容器指定默认启动命令,与CMD不同的是ENTRYPOINT命令不会随着docker run 后使用的命令覆盖而会把命令作为参数,除非docker run 参数中指定了—entrypoint
语法 :
ENTRYPOINT <command>
ENTRYPOINT ["<executable>", "<param1>", "<param2>"]

注意事项:
  • 与CMD类似,第一种方式默认会以/bin/sh -c 启动,而第二种则不会,也就意味着没有shell环境
  • 通常ENTRPOINT用于使用ENTRPOINT脚本启动
  • 当CMD与ENTRYPOINT同时存在时,CMD的参数为ENTRYPOINT提供

示例:

[“nginx”,"-g","daemon off"]

USER

  用于指定构建镜像时RUN、CMD、ENTRYPOINT等指令使用的用户或UID,默认情况容器运行身份为ROOT
语法 :
USER <user>[:<group>]
USER <UID>[:<GID>]

注意事项:
  1. 指定的USER或者GROUP必须在容器中存在,否则指令会运行失败
示例: 
USER nginx

STOPSIGNAL

  该指令用于设置容器停止时向容器内进程发送的信号,列如 9 、SIGKILL、SIGTERM。

语法:

STOPSIGNAL signal

示例:

STOPSIGNAL SIGKILL

注意事项:
  1. 向容器发送信号只能被PID=1的进程所接收,当PID=1进程不是应用进程时候,应用进程收不到终止信号。

HEALTHCHECK

  该指令在1.12版本中添加,用于对容器中的应用进行健康检查,不做检查使用NONE。当对容器做了健康检查时候,检查值为0表示成功,非0表示不健康。 
语法:
HEALTHCHECK [OPTIONS] CMD command

其中OPTIONS有如下选项:

  • --interval=DURATION 检查间隔(默认: 30s)
  • --timeout=DURATION 超时时间(默认t: 30s)
  • --start-period=DURATION 等待检查的时间,默认0s代表一启动就检查 (默认: 0s)
  • --retries=N (default: 3)  重试次数

示例:

HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1

SHELL

  将可执行程序运行为shell环境,默认以/bin/sh -c运行

语法:

SHELL ["executable", "parameters"]

示例:

SHELL ["echo", “hello"] #等价于 RUN echo hello

ARG

  该指令用于在build过程中提供参数,而在命令行使用--build-arg <varname>=<value>来传递参数值,这样可以使用参数进行构建镜像。 
语法:
ARG <name>[=<default value>]

示例:
Dockerfile
FROM nginx
ARG CONF="/tmp/nginx.conf"
LABEL Author=wd
RUN touch "${CONF}"

构建镜像:

[root@app51 ~]# docker build  --build-arg CONF='/etc/test.conf' -t nginx:v15.2 ./
Sending build context to Docker daemon 225.6MB
Step 1/4 : FROM nginx
---> f09fe80eb0e7
Step 2/4 : ARG CONF="/tmp/nginx.conf"
---> Using cache
---> ac081589c644
Step 3/4 : LABEL Author=wd
---> Using cache
---> 53b9b0ba4460
Step 4/4 : RUN touch "${CONF}"
---> Running in 50debe96f876
Removing intermediate container 50debe96f876
---> d8680a2433bc
Successfully built d8680a2433bc
Successfully tagged nginx:v15.2

运行容器查看:

[root@app51 ~]# docker run --rm nginx:v15.2 ls /etc/test.conf -l
-rw-r--r-- 1 root root 0 Feb 27 11:18 /etc/test.conf

ONBULD

  用于在Dockerfile中定义一个触发器,当制作出来的镜像被别人用于基础镜像时候自动触发。

语法:

ONBUILD [INSTRUCTION]

解释:
INSTRUCTION:指令可以是RUN 、COPY等
注意事项:
  1. ONBUILD不会触发FROM指令。
  2. 在镜像标签中应明确指出onbuild关键字,以标记使用其基础镜像会触发其他指令

示例:

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

四、使用multi-stage

  在构建镜像过程中,我们可能只需要某些镜像的产物,比如在运行一个go程序需要先go程序包编译后才运行,如果在一个镜像里面完成,先要经过安装编译环境,程序编译完再安装运行环境,最后运行程序,这样的镜像体积往往比较大,不利于我们使用。而真正我们需要的镜像是只有程序包和运行环境,编译环境的构建在运行容器时候是不需要的,所以Docker提供了一种解决方案就是multi-stage(多阶段构建)。
    Docker允许多个镜像的构建可以使用同一个Dockerfile,每个镜像构建过程可以称之为一个stage,简单理解就是一个FROM指令到下一个FROM指令,而每个stage可使用上一个stage过程的产物或环境(其实还支持其他镜像的),这样一来,最终所得镜像体积相对较小。不仅如此多阶段构建同样可以很方便地将多个彼此依赖的项目通过一个Dockerfile就可轻松构建出期望的容器镜像,而不用担心镜像太大、项目环境依赖等问题。
    通过上述介绍,我们可以在第一个stage将go程序编译得到编译后程序包,然后在第二个stage中直接拷贝编译好的go程序包到运行环境中,最后的镜像中就只有程序包和运行环境。以下作为示例: 
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]

在以上Dockerfile中存在两个FROM指令,也就是两个stage,第一个stage用于构建产物,而在第二个stage中使用COPY --from=0 意思将第一个stage中的/go/src/github.com/alexellis/href-counter/app拷贝到.目录,第二个stage仅仅相当于执行copy就有了构建产物,不用在安装编译环境,镜像会很缩小。 

命名stage

  默认情况下,stage未命名,可以通过整数来引用它们,第一个stage表示0,第二个表1以此类推。 但是,当有多个stage时候,这样会显得麻烦,Docker提供AS 语法可以为stage命名:

FROM golang:1.7.3 as builder

然后在另一个stage中使用:

COPY --from=builder /go/src/github.com/alexellis/href-counter/app .

使用本地stage

  除了可以使用Dockerfile中的stage外,构建镜像时候还可以直接使用本地已存在的环境和产物,例如:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

构建镜像建议

  1. 基础镜像尽量选择比体积较小的镜像,如每个官方发行的alpine镜像。虽然这版本镜像比较小,但是与之带来的是利用该类镜像运行的容器中排错的命令很少;
  2. 使用RUN指令时候,尽量把多个RUN指令合并为一个,通常做法是使用&&符号;
  3. 通过multi-stage方法减少一些不必要使用的环境来减小镜像;
  4. 安装完成软件同时删除一些不需要的文件或目录;

ref:

Docker镜像构建的更多相关文章

  1. Docker镜像构建(五)

    Docker 镜像介绍 Docker镜像构建分为两种,一种是手动构建,另一种是Dockerfile(自动构建) 手动构建docker镜像 案例:我们基于centos镜像进行构建,制作自己的nginx镜 ...

  2. 4、Docker 镜像构建

    Docker 镜像构建 构建分为两种 手动构建 自动构建dockerfile 手动构建 首先启动一个Centos 容器,然后在容器中安装一个nginx [root@node ~]# docker ru ...

  3. Docker镜像构建原理解析(不装docker也能构建镜像)

    在devops流程里面 构建镜像是一个非常重要的过程,一般构建镜像是写dockerfile文件然后通过docker client来构建的image. docker client 会先检查本地有没有im ...

  4. Docker镜像构建的两种方式

    关于Docker里面的几个主要概念 这里用个不太恰当的比方来说明. 大家肯定安装过ghost系统,镜像就像是ghost文件,容器就像是ghost系统.你可以拿别人的ghost文件安装系统(使用镜像运行 ...

  5. Docker-通过docker-maven-plugin插件实现docker镜像构建并自动发布到远程docker服务器

    我们知道,docker能实现应用打包隔离,实现快速部署和迁移.如果我们开发应用使用了spring cloud + spring boot架构,那么,通过docker-maven-plugin实现快速构 ...

  6. Docker镜像构建的两种方式(六)--技术流ken

    镜像构建介绍 在什么情况下我们需要自己构建镜像那? (1)当我们找不到现有的镜像,比如自己开发的应用程序 (2)需要在镜像中加入特定的功能 docker构建镜像有两种方式:docker commit命 ...

  7. (转)Docker镜像构建上下文(Context)

    镜像构建上下文(Context) Docker在构建镜像时,如果注意,会看到 docker build 命令最后有一个 ... 表示当前目录,而 Dockerfile 就在当前目录,因此不少初学者以为 ...

  8. sqler sql 转rest api 的docker 镜像构建(续)使用源码编译

    sqler 在社区的响应还是很不错的,已经添加了好多数据库的连接,就在早上项目的包管理还没有写明确, 下午就已经有go mod 构建的支持了,同时也调整下docker 镜像的构建,直接使用git cl ...

  9. Docker镜像构建上下文(Context)

    镜像构建上下文(Context) Dicker在构建镜像时,如果注意,会看到 docker build 命令最后有一个 ... 表示当前目录,而 Dockerfile 就在当前目录,因此不少初学者以为 ...

随机推荐

  1. 2.Odoo产品分析 (一) – 一切为零

    查看Odoo产品分析系列--目录 1. 默认数据库 声明在先  本系列文档(Odoo产品分析)整理来自本人对该ERP的理解,并结合文档Working-with-Odoo-10-Second-Editi ...

  2. finally知识讲解

    finally语句一定会执行吗,很多人认为一定会,其实未必,只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才会执行.假如在try语句之前执行了return操作 ...

  3. Android项目实战(四十一):游戏和视频类型应用 状态栏沉浸式效果

    需求:  手机app ,当打游戏或者全屏看视频的时候会发现这时候手机顶部的状态栏是不显示的,当我们从手机顶端向下进行滑动或手机底端向上滑动的时候,状态栏会显示出来,如果短暂的几秒时间没有操作的话,状态 ...

  4. ajax简单登录(踩过的坑)

    登陆页面: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEnc ...

  5. 第1章 HTTP协议基本介绍了解

    一.常见接口协议: HTTP     超文本传输协议 HTTPS   安全超文本传输协议 FTP       文件传输协议 TCP       网络控制协议 IP          互联网协议 UDP ...

  6. thread/threading——Python多线程入门笔记

    1 什么是线程? (1)线程不同于程序. 线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制: 多线程类似于同时执行多个不同程序. (2)线程不同于进程. 每个独立的进程有一个程 ...

  7. mssql 怎么配置指定的表 不允许删除数据?

    http://www.maomao365.com/?p=5089 <span style="color:red;font-weight:bold;">前言: 前几天收到 ...

  8. git 使用命令删除远程分支和本地分支

    删除远程分支命令: git push origin   :<远程分支名称> git push origin --delete <远程分支名称> 删除本地分支: git bran ...

  9. PHP中生产不重复随机数的方法

    PHP内置函数不重复随机数        需求:要生成一个数组,这个数组里面有10个元素,都是整形,并且是1-60之间不重复的随机数.  代码: 代码示例: 1 2 3 4 5 6 7 8 9 10 ...

  10. java实现支付宝支付及退款(一)

    本篇博客主要做支付宝支付的准备工作(注册沙箱.natapp内网穿透等操作).具体代码实现操作请看下篇博客 一.登录沙箱 1.登录蚂蚁金服开发平台: https://open.alipay.com/pl ...