Dockerfile

Dockerfile概念

  • Dockerfile 是一个文本文件
  • 包含一条条指令
  • 每一条指令 构建一层 ,基于基础镜像,最终构建出全新的镜像
  • 对于开发人员: 可以为开发团队提供一个完全一致的开发环境
  • 对于测试人员: 可以直接拿开发时候构建的镜像或者通过 Dockerfile文件构建一个新的镜像开始工作
  • 对于运维人员: 部署的时候 ,可以实现应用无缝移植

参考是教程

Dockerfile 关键字目录

关键字 作用 备注
FROM 指定父镜像 指定 dockerfile 基于那个 image 构建
MAINTAINER 作者信息 用来标明这个 dockerfile 作者
LABEL 标签 用来标明 dockerfile 的标签可以使用 Label 代码 Maintainer 最终都是在 docker image 基本信息中可以查看
Run 执行命令 执行一段命令默认是 /bin/sh 格式:RUN command 或者 RUN [“command”,”param1”,”param2”]
CMD 容器启动命令 启动容器时候的默认命令和 ENTRYPOINT 配合使用 格式 CMD command param1 param2 或者 CMD [“command”,”param1”,”param2”]
COPY 复制文件 build 的时候复制文件到 image 中
ADD 添加文件 build 的时候添加文件到 image 中 不仅仅局限于当前的 build 上下文 可以来源与远程服务
ENV 环境变量 指定 build 时候的环境变量可以在启动的容器的时候通过 - e 覆盖格式 ENV name=value
ARG 构建参数 构建参数只在构建的时候使用的参数如果有 ENV 那么 ENV 的相同名字的只始终覆盖 arg 的参数
volume 定义外部可以挂载的数据卷 指定 build 的 image 哪些目录可以启动的时候挂载到文件系统中启动容器的时候使用 - v 绑定 格式 VOLUME [“目录”]
EXPOSE 暴露端口 定义容器运行的时候监听的端口启动容器的时候使用 - p 来绑定暴露端口 格式: EXPOSE 8080 或者 EXPOSE 8080/udp
WORKDIR 工作目录 指定容器内部的工作目录 如果没有创建则自动创建 如果指定 / 使用的是绝对地址 如果不是 / 开头 那么是在上一条 workdir 的路径的相对路径
USER 指定执行用户 指定 build 或者启动的时候 用户在 RUN CMD ENTRYPOINT 执行的时候的用户
HEALTHCHECK 健康检查 指定检测当前容器健康监测的命令 基本上没用 因为很多时候应用本身有健康监测机制
ONBUILD 触发器 当存在 ONBUILD 关键字的镜像作为基础镜像的时候 当执行 FROM 完成之后会执行 ONBUID 的命令 但是不影响当前镜像用处也不怎么大
STOPSIGNAL 发送信号量到宿主机 该 STOPSIGNAL 指令设置将发送到容器的系统调用信号以退出
SHELL 指定执行脚本的 shell 指定 RUN CMD ENTRYPOINT 执行命令的时候使用的 shell
entrypoint 指定镜像默认的入口命令 entrypoint [“executable”,“param1”,“param2” ]

Dockerfile 案例

构建centos镜像

自定义 centos7镜像

  1. 默认登陆路劲为 /usr
  2. 可以使用 vim
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 定义父镜像,指令忽略大小写,但是建议大写
FROM centos:7

# 作者
maintainer lyr lyr-2000.github.io

# 安装 vim
run yum install -y vim

# 定义默认的工作目录
workdir /usr
# 定义容器启动执行的目录
cmd /bin/bash

然后构建镜像

1
docker build -f ./Dockerfile  -t lyr_centos:1 .
运行镜像试试
1
2
3
docker run -it --name=test0   lyr_centos:1
# 然后 这里 你就进入了 容器内部的shell 了,当前目录是 /usr
# 这里不用写 /bin/bash ,因为默认就是 /bin/bash 了

构建springboot 镜像

1
2
3
4
5
6
7
8
9
# 构建 springboot 镜像
# 假设打包后是  demo.jar
From java:8
maintainer lyr lyr-2000.github.io

# 将 jar包 加入容器
ADD demo.jar app.jar
# 定义容器执行命令, 并且指定 配置环境 为 prod和 test
cmd java -jar app.jar --spring.profiles.active=prod,test

通过dockerfile 构建镜像

1
docker build -f dockerfile ./demo.jar -t myJar:1
运行镜像测试

-d 表示后台运行

1
docker run -id -p 9000:8080  myJar

构建go语音镜像

1
docker run -it --rm golang:1.15.7-alpine3.13 go version

参考文档

方式一【不推荐】
1
2
3
4
5
6
7
8
from golang:1.15.7-alpine3.13
workdir /go/src
# 将本目录 添加进容器里面
add . /go/src
# 编译打包
run cd /go/src && go build -o main
# 设置运行命令
CMD ["/go/src/main"]

这种方式不推荐,因为 打包出来的东西 有 300M, 这 比 jar 包都大

我们要使用分阶段来构建

方式二
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from golang:1.15.7-alpine3.13  AS buildStage

workdir /go/src
# 将本目录 添加进容器里面
add . /go/src
# 编译打包
run cd /go/src && go build -o main
# 编译
# ------   编译阶段完成 ------

# 打包阶段
from  alpine:latest
workdir /app
copy --from=buildStage /go/src/main /app/
entrypoint ./main
1
2
3
docker image build -t gotest:v02
# 运行
docker run -it --rm gotest:v02
1
2
3
4
5
6
7
[root@localhost test]# docker images
REPOSITORY    TAG                 IMAGE ID       CREATED          SIZE
gotest        v02                 5663a722e768   9 seconds ago    7.62MB

[root@localhost test]# docker run -it --rm gotest:v02
hello world
[root@localhost test]# 

导出镜像 【从 images中导出】

1
2
3
4
# 导出镜像
docker save  5663a722e768 > gotest.tar
# 导入镜像
docker load < gotest.tar 

docker 多段构建(Multi-stage build)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
FROM golang:1.16-alpine AS build
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project
#(只有这个二进制文件是产线需要的,其他都是waste)
FROM scratch
# 从上面 那个 build 的 别名的镜像中 copy 过来
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

VOLUME: 将指定目录定义为外挂存储卷,Dockerfile 中在该指令之后所有对同一目录的修改都无效 VOLUME ["/data"] 等价于 docker run –v /data,可通过 docker inspect 查看主机的 mount point, /var/lib/docker/volumes//_data • USER:切换运行镜像的用户和用户组,因安全性要求,越来越多的场景要求容器应用要以 non-root 身份运行 USER [:] • WORKDIR:等价于 cd,切换工作目录 WORKDIR /path/to/workdir • 其他非常用指令 • ARG • ONBUILD • STOPSIGNAL • HEALTHCHECK • SHELL

Dockerfile 最佳实践

• 不要安装无效软件包。 • 应简化镜像中同时运行的进程数,理想状况下,每个镜像应该只有一个进程。 • 当无法避免同一镜像运行多进程时,应选择合理的初始化进程(init process)。 • 最小化层级数 • 最新的 docker 只有 RUN, COPY,ADD 创建新层,其他指令创建临时层,不会增加镜像大小。 • 比如 EXPOSE 指令就不会生成新层。 • 多条 RUN 命令可通过连接符连接成一条指令集以减少层数。 • 通过多段构建减少镜像层数。 • 把多行参数按字母排序,可以减少可能出现的重复参数,并且提高可读性。 • 编写 dockerfile 的时候,应该把变更频率低的编译指令优先构建以便放在镜像底层以有效利用 build cache。 • 复制文件时,每个文件应独立复制,这确保某个文件变更时,只影响改文件对应的缓存。

多进程的容器镜像 • 选择适当的 init 进程 • 需要捕获 SIGTERM 信号并完成子进程的优雅终止 • 负责清理退出的子进程以避免僵尸进程 开源项目 https://github.com/krallin/tini

golang 项目打包案例

1
2
3
4
5
6
7
set GOARCH=amd64
set GOOS=linux
:: 打包 main文件
go build .\cmd\cmdb\main.go
:: 打包docker镜像

docker build -t test0:v1   -f  .\addon\deploy\Dockerfile  .
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM alpine:3.11.6
ARG APOLLO
ARG PROJECT
ARG PROJECT_ID
ARG BRANCH_NAME

ENV APOLLO=${APOLLO}
ENV PROJECT=${PROJECT}
ENV PROJECT_ID=${PROJECT_ID}
ENV BRANCH_NAME=${BRANCH_NAME}
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories \
        && apk add --update --no-cache ca-certificates tzdata bash curl busybox-extras dos2unix

ENV TZ Asia/Shanghai
RUN cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone
VOLUME /data
# 创建模板目录
# RUN mkdir -p /app/static
RUN mkdir -p /app/pkg/static/template/
COPY ./pkg/static/template/ /app/pkg/static/template/


COPY main /app/

EXPOSE 8000 8001 8002
WORKDIR /app
CMD ["/app/main"]

构建命令总结

构建命令如下

1
 docker build . -t ImageName:ImageTag -f Dockerfile

如何理解构建镜像的过程?

Dockerfile是一堆指令,在docker build的时候,按照该指令进行操作,最终生成我们期望的镜像

  • FROM 指定基础镜像,必须为第一个命令
1
2
3
4
5
6
7
格式:
	FROM <image>
	FROM <image>:<tag>
示例:
	FROM mysql:5.7
注意:
	tag是可选的,如果不使用tag时,会使用latest版本的基础镜像
  • MAINTAINER 镜像维护者的信息
1
2
3
4
5
6
格式:
	MAINTAINER <name>
示例:
	MAINTAINER Yongxin Li
    MAINTAINER inspur_lyx@hotmail.com
    MAINTAINER Yongxin Li <inspur_lyx@hotmail.com>
  • COPY|ADD 添加本地文件到镜像中

    1
    2
    3
    4
    5
    6
    
    格式:
    	COPY <src>... <dest>
    示例:
        ADD hom* /mydir/          # 添加所有以"hom"开头的文件
        ADD test relativeDir/     # 添加 "test" 到 `WORKDIR`/relativeDir/
        ADD test /absoluteDir/    # 添加 "test" 到 /absoluteDir/
    
  • WORKDIR 工作目录

    1
    2
    3
    4
    5
    6
    
    格式:
    	WORKDIR /path/to/workdir
    示例:
        WORKDIR /a  (这时工作目录为/a)
    注意:
    	通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行
    
  • RUN 构建镜像过程中执行命令

    1
    2
    3
    4
    5
    6
    7
    8
    
    格式:
    	RUN <command>
    示例:
        RUN yum install nginx
        RUN pip install django
        RUN mkdir test && rm -rf /var/lib/unusedfiles
    注意:
    	RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
    
  • CMD 构建容器后调用,也就是在容器启动时才进行调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    格式:
        CMD ["executable","param1","param2"] (执行可执行文件,优先)
        CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
        CMD command param1 param2 (执行shell内部命令)
    示例:
        CMD ["/usr/bin/wc","--help"]
        CMD ping www.baidu.com
    注意:
    	CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
    
  • ENTRYPOINT 设置容器初始化命令,使其可执行化

    1
    2
    3
    4
    5
    6
    7
    
    格式:
        ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
        ENTRYPOINT command param1 param2 (shell内部命令)
    示例:
        ENTRYPOINT ["/usr/bin/wc","--help"]
    注意:
    	ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令
    
  • ENV

    1
    2
    3
    4
    5
    6
    
    格式:
        ENV <key> <value>
        ENV <key>=<value>
    示例:
        ENV myName John
        ENV myCat=fluffy
    
  • EXPOSE

    1
    2
    3
    4
    5
    6
    7
    8
    
    格式:
        EXPOSE <port> [<port>...]
    示例:
        EXPOSE 80 443
        EXPOSE 8080
        EXPOSE 11211/tcp 11211/udp
    注意:
        EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口
    

Dockerfile示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# This my first django Dockerfile
# Version 1.0

# Base images 基础镜像
FROM centos:centos7.5.1804

#MAINTAINER 维护者信息
LABEL maintainer="inspur_lyx@hotmail.com"

#ENV 设置环境变量
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8

#RUN 执行以下命令
RUN curl -so /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo
RUN yum install -y  python36 python3-devel gcc pcre-devel zlib-devel make net-tools

#工作目录
WORKDIR /opt/myblog

#拷贝文件至工作目录
COPY . .

#安装nginx
RUN tar -zxf nginx-1.13.7.tar.gz -C /opt  && cd /opt/nginx-1.13.7 && ./configure --prefix=/usr/local/nginx \
&& make && make install && ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx

RUN cp myblog.conf /usr/local/nginx/conf/myblog.conf

#安装依赖的插件
RUN pip3 install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt

RUN chmod +x run.sh && rm -rf ~/.cache/pip

#EXPOSE 映射端口
EXPOSE 8002

#容器启动时执行命令
CMD ["./run.sh"]
1
$ docker build . -t myblog:v1 -f Dockerfile