Docker 镜像构建之 Dockerfile

在 Docker 中构建镜像最常用的方式,就是使用 Dockerfile。Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。官方文档:https://docs.docker.com/engine/reference/builder/

目录
  • 一、Dockerfile 基本介绍
    • 1.1 什么是 Dockerfile
    • 1.2 Dockerfile 主体内容
    • 1.3 构建Dockerfile步骤
    • 1.4 理解构建上下文(Build Context)
    • 1.5 Build Cache
    • 1.6 Dockfile 文件的注意事项
    • 1.7 dockerfile的保留字指令
  • 二、Dockerfile 的保留字指令详解
    • 2.1 FROM
    • 2.2 MAINTAINER
    • 2.3 LABEL
    • 2.4 RUN
    • 2.5 EXPOSE
    • 2.6 WORKDIR
    • 2.7 ENV
    • 2.8 ADD
    • 2.9 COPY
    • 2.10 VOLUME
    • 2.11 CMD (这个指令需放在最后)
    • 2.12 ONBUILD
  • 三、构建镜像
    • 3.1 构建镜像
    • 3.2 .dockerignore 实践
  • 四、Dockerfile 实践
    • 4.1 使用 centos7 作为基础镜像部署 nginx 服务
    • 4.2 在容器中编译安装 nginx 服务
    • 4.3 构建以 Centos 为依赖镜像并安装 Django 的服务
    • 4.4 构建以 python 为依赖镜像并安装 Django 服务
    • 4.5 使用 NGINX 代理 Django
  • 五、Docker 镜像管理
    • 5.1 docker save
    • 5.2 docker load
    • 5.3 docker tag
    • 5.4 docker push
    • 5.5 docker pull
  • 六、镜像仓库
    • 6.1 Docker hub
    • 6.2 创建私有镜像仓库
  • 七、查看镜像构建历史
  • 八、多段构建 ( Multi-stage build)
  • 九、多进程的容器镜像
    • 9.1 选择适当的 init 进程
    • 9.2 Tini 开源项目
  • 十、Dockerfile 最佳实践

一、Dockerfile 基本介绍

1.1 什么是 Dockerfile

  • Dockerfile 是用来构建 Docker 镜像的构建文件, 是由一系列的命令和参数构成的脚本
  • 通过指令的方式构建镜像

1.2 Dockerfile 主体内容

Dockerfile 主体内容分为四部分:基础镜像信息、 维护者信息、 镜像操作指令和容器启动时执行指令。

1.3 构建Dockerfile步骤

  1. 编写 Dockerfile 文件
  2. docker build 构建镜像
  3. docker run 创建容器

1.4 理解构建上下文(Build Context)

  1. 当运行 docker build 命令时,当前工作目录被称为构建上下文。

  2. docker build 默认查找当前目录的 Dockerfile 作为构建输入,也可以通过 -f 指定 Dockerfile

docker build -f ./ Dockerfile
  1. docker build 运行时,首先会把构建上下文传输给 docker daemon,把没用的文件包含在构建上下文时,会导致传输时间长,构建需要的资源多,构建出的镜像大等问题。这种情况可以通过.dockerignore文件从编译上下文排除某些文件。
  2. 因此需要确保构建上下文清晰,比如创建一个专门的目录放置 Dockerfile,并在目录中运行 docker build。

1.5 Build Cache

构建容器镜像时,Docker 依次读取 Dockerfile 中的指令,并按顺序依次执行构建指令。
Docker 读取指令后,会先判断缓存中是否有可用的已存镜像,只有已存镜像不存在时才会重新构建。

  • 通常 Docker 简单判断 Dockerfile 中的指令与镜像。
  • 针对 ADDCOPY 指令,Docker 判断该镜像层每一个文件的内容并生成一个 checksum,与现存镜像比较时,Docker 比较的是二者的 checksum。
  • 其他指令,比如 RUN apt-get -y update,Docker 简单比较与现存镜像中的指令字串是否一致。
  • 当某一层 cache 失效以后,所有所有层级的 cache 均一并失效,后续指令都重新构建镜像。

1.6 Dockfile 文件的注意事项

  • Docker 以从上到下的顺序运行 Dockerfile 的指令。为了指定基本映像,第一条指令必须是 FROM。
  • 每条保留字指令都必须是大写字母, 并且后面要跟随至少一个参数
  • 指令按照从上到下的顺序执行
  • 每条指令可用 # 添加注释
  • 每条指令都会创建一个新镜像层, 并对镜像进行提交

1.7 dockerfile的保留字指令

  • 主要保留指令:
    1. FROM
    2. RUN
    3. ADD
    4. COPY
    5. WORKDIR
    6. CMD

一般用以上保留字指令就可以完成容器想要的功能,所有字段如下。

指令 含义
FROM 指定基础镜像,必须为第一个命令
MAINTAINER 维护者信息
RUN 构建镜像docker build时执行的命令
ADD 将本地文件添加到容器中,tar 类型文件会自动解压(网络压缩资源不会被解压)
COPY 功能类似ADD,但是是不会自动解压文件,也不能访问网络资源
CMD 在docker run时会执行的命令,如果存在多个则仅最后一个生效。
LABEL 用于为镜像添加元数据
ENV 设置环境变量
EXPOSE 指定于外界交互的端口
VOLUME 用于指定持久化目录
WORKDIR 工作目录,类似于cd命令
ARG 用于指定传递给构建运行时的变量
ONBUILD 用于设置镜像触发器

二、Dockerfile 的保留字指令详解

2.1 FROM

  • 基础(依赖)镜像, 就是当前要创建的镜像是基于那个镜像
  • 基本语法如下:
FROM <image> 
FROM <image>:<tag> 
FROM <image>@<digest> 
示例: 
	FROM mysql:5.6 
# 注: tag 或 digest 是可选的,如果不使用这两个值时,会使用 latest 版本的基础镜像

如果不以任何镜像为基础,那么写法为:FROM scratch。官方说明:scratch 镜像是一个空镜像,可以用于构建 busybox 等超小镜像,可以说是真正的从零开始构建属于自己的镜像。

2.2 MAINTAINER

  • 指明镜像维护者及其联系方式(一般是邮箱地址)。官方说明已过时,推荐使用 LABEL。
格式:
	MAINTAINER <name>
示例:
    MAINTAINER Jasper Xu MAINTAINER sorex@163.com
    MAINTAINER Jasper Xu <sorex@163.com>

2.3 LABEL

  • 语法:LABEL <key>=<value> <key>=<value> <key>=<value> ...
  • 功能是为镜像指定标签或为镜像添加元数据。也可以使用 LABEL 来指定镜像作者。
#示例:元数据
LABEL version="1.0" description="这是一个 Web 服务器" by="IT 笔录" 

# 注:使用 "LABEL" 指定元数据时,一条 "LABEL" 指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条 "LABEL" 指令指定,以免生成过多的中间镜像LABEL 
示例二 
maintainer="http://blog.taoxiaoxin.club/"

2.4 RUN

  • 容器构建时需要运行的命令
# RUN 用于在镜像容器中执行命令,一个Dockerfile文件内可以有多个RUN其有以下两种命令执行方式: 
shell 执行 
	格式:RUN <command> 
exec 执行 
	格式:RUN ["executable", "param1", "param2"] 
	示例:
		 RUN ["executable", "param1", "param2"] 
		 RUN apk update RUN ["/etc/execfile", "arg1", "arg1"] 
注: RUN 指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建 时指定--no-cache 参数,如:docker build --no-cache

2.5 EXPOSE

  • 暴露容器运行时的监听端口给外部,可以指定端口是监听 TCP 还是 UDP,如果未指定协议,则默认为 TCP。
# 格式:
EXPOSE <port> [<port>...]
# 示例:
EXPOSE 80 443
EXPOSE 8080
EXPOSE 11211/tcp 11211/udp

# 注:"EXPOSE" 并不会让容器的端口访问到主机。如果想使得容器与宿主机的端口有映射关系,要使其可访问,需要在" docker run" 运行容器时通过"-p" 来发布这些端口,或通过"-P" 参数来发布" EXPOSE" 导出的所有端口,

2.6 WORKDIR

  • 指定创建容器后, 终端默认处在的工作目录, 也就是落脚点,为 RUN、CMD、ENTRYPOINT 以及 COPY 和 AND 设置工作目录。
# 格式:
WORKDIR /path/to/workdir
# 示例:
WORKDIR /a (这时工作目录为/a)
WORKDIR b (这时工作目录为/a/b)
WORKDIR c (这时工作目录为/a/b/c)

# 注:通过 "WORKDIR" 设置工作目录后,"Dockerfile" 中其后的命令 RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。在使用 "docker run" 运行容器时,可以通过"-w" 参数覆盖构建时所设置的工作目录

2.7 ENV

  • 用来在构建镜像过程中设置环境变量
# 格式:
ENV <key> <value> 
#<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
ENV <key>=<value> ... 
#可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行
# 示例:
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat=fluffy

2.8 ADD

  • 将宿主机目录下的文件拷贝到镜像里面 (会自动解压 tar 压缩包),src 可以是一个本地文件或者是一个本地压缩文件,压缩文件会自动解压。还可以是一个 url,如果把 src 写成一个 url,那么 ADD 就类似于 wget 命令,然后自动下载和解压。
# 格式:
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]  # 用于支持包含空格的路径
# 示例:
ADD hom* /mydir/            # 添加所有以"hom"开头的文件
ADD hom?.txt /mydir/        # ? 替代一个单字符,例如:"home.txt"
ADD test relativeDir/       # 添加 "test" 到 `WORKDIR`/relativeDir/
ADD test /absoluteDir/      # 添加 "test" 到 /absoluteDir/

2.9 COPY

  • 类似 ADD, 拷贝本地文件到镜像中 (不会自动解压)
指令:COPY
功能描述:复制文件到镜像中。
语法:COPY < src>… < dest>|[“< src>”,… “< dest>”]
提示:指令逻辑和 ADD 十分相似,同样 Docker Daemon 会从编译目录寻找文件或目录,dest 为镜像中的绝对路径或者相对于 WORKDIR 的路径。

2.10 VOLUME

  • 用于目录挂载
# 格式:
VOLUME ["/path/to/dir"]
# 示例:
VOLUME ["/data"]
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]

# 注:一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
1. 卷可以容器间共享和重用
2. 容器并不一定要和其它容器共享卷
3. 修改卷后会立即生效
4. 对卷的修改不会对镜像产生影响
5. 卷会一直存在,直到没有任何容器在使用它

2.11 CMD (这个指令需放在最后)

  • 指定容器启动时要运行的命令
# 格式:
CMD ["executable","param1","param2"] (执行可执行文件,优先)
CMD ["param1","param2"] (设置了 ENTRYPOINT,则直接调用 ENTRYPOINT 添加参数)
CMD command param1 param2 (执行 shell 内部命令)
# 示例:
CMD echo "This is a test." | wc -w
CMD ["/usr/bin/wc","--help"]

# 注: "CMD" 不同于 "RUN","CMD" 用于指定在容器启动时所要执行的命令,而 "RUN" 用于指定镜像构建时所要执行的命令

2.12 ONBUILD

  • 用于设置镜像触发器
# 格式:
ONBUILD [INSTRUCTION]
# 示例:
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

# 注:当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被触发

三、构建镜像

3.1 构建镜像

Dockerfile 文件编写好以后,真正构建镜像时需要通过 docker build 命令。

docker build 命令用于使用 Dockerfile 创建镜像。

# 使用当前目录的 Dockerfile 创建镜像
docker build -t mycentos:7 .
# 通过 -f Dockerfile 文件的位置创建镜像
docker build -f /usr/local/dockerfile/Dockerfile -t mycentos:7 .
  • -f:指定要使用的 Dockerfile 路径;
  • --tag, -t:镜像的名字及标签,可以在一次构建中为一个镜像设置多个标签。

3.2 .dockerignore 实践

docker build 运行时,首先会把构建上下文传输给 docker daemon,把没用的文件包含在构建上下文时,会导致传输时间长,构建需要的资源多,构建出的镜像大等问题。这种情况可以通过.dockerignore文件从编译上下文排除某些文件。

举个例子,下面我们试着到一个包含文件很多的目录运行下面的命令,会感受到差异。

首先创建一个名为 my_project 的目录,并在其中添加一些无用的文件和子目录:

mkdir my_project
cd my_project

# 创建无用文件
touch file1.txt
touch file2.txt
touch file3.txt

# 创建无用子目录,并在其中添加文件
mkdir dir1
mkdir dir2
touch dir1/file4.txt
touch dir2/file5.txt

接下来,在 my_project 目录中创建一个 Dockerfile,内容如下:

FROM alpine:3.14.2

WORKDIR /app

COPY . .

CMD ["ls", "-l"]

接着,在当前目录下的 my_project 目录通过 -f 参数可以告诉 Docker 使用指定路径下的 Dockerfile 文件进行构建

docker build -f ./Dockerfile .

可以看到,构建当前镜像是非常快的,当前目录文件文件内容很小,因此构建过程比较快。

但是当我们直接切换到根目录,接下来我们来试试看。

docker build ~/workspace/my_project/ 

image-20240320222503890

你可以直观的感受到这个过程是很慢的,这是因为根目录作为构建上下文传输给 Docker daemon,包括所有的无用文件和子目录。如果目录中的文件和子目录很多,这个过程会变得非常耗时,尤其是在网络速度较慢的情况下。所以一般打包docker镜像请切换到dockerfile当前目录下

当然,有时候我们为了让打包的容器镜像更加轻量一点,我们可以创建一个 .dockerignore 文件,告诉 Docker 在构建时忽略某些文件或目录。

my_project 目录中创建一个名为 .dockerignore 的文件,并添加以下内容:

file*.txt
dir*/

这样,我们告诉 Docker 在构建时忽略所有以 file 开头的 .txt 文件和所有的 dir 开头的子目录。然后重新运行构建命令:

docker build -t my_image .

这次构建过程将忽略无用文件和子目录,从而加快构建时间并减小生成的镜像大小。

四、Dockerfile 实践

4.1 使用 centos7 作为基础镜像部署 nginx 服务

  • 先创建一个 nginx.repo 文件
cat > nginx.repo <<EOF
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
  • 编写 Dockerfile 文件
cat > Dockerfile <<EOF
# 指定基础镜像(依赖镜像)
FROM centos:7

# 执行一个命令
RUN yum install -y yum-utils

# 将本地文件添加到容器中
ADD nginx.repo /etc/yum.repos.d/nginx.repo

# 更新YUM缓存
RUN yum makecache

# 安装nginx
RUN yum install -y nginx

# 制定容器启动默认执行的命令
CMD nginx -g 'daemon off;'
EOF
  • 构建镜像
docker build -t install/nginx:v1 .
  • 查看刚刚构建的镜像, 然后实例容器
[root@shawn ~]# docker images
[root@shawn ~]# docker run -dit install/nginx:v1 sh
  • 查看刚刚实例出的容器, 并进入到容器中
[root@shawn ~]# docker exec -it 94f8e35f3357  bash
  • 检测 nginx 是否部署成功
[root@shawn ~]# crul 127.0.0.1  # 出现 html 代码说明部署成功

4.2 在容器中编译安装 nginx 服务

  • 编辑 Dockerfile 文件
[root@shawn ~]# vim Dockerfile
'''文件内容
# 指定基础镜像(依赖镜像)
FROM centos:7

# 执行命令
RUN yum install yum-utils wget zlib zlib-devel pcre pcre-devel make gcc gcc-c++
RUN cd /opt && wget http://nginx.org/download/nginx-1.18.0.tar.gz && tar -xvf nginx.1.18.0/ && cd nginx-1.18.0/ && ./configure && make && make install

# 指定进入容器的默认工作目录
WORKDIR /usr/local/nginx/sbin

# 指定容器启动默认执行的命令
CMD ./nginx -g 'daemon off;'
'''
  • 构建镜像
[root@shawn ~]# docker build -t yuan/install/nginx:v2 .
  • 查看是否构建成功,并实例出容器
[root@shawn ~]# docker images
[root@shawn ~]# docker run -dit --name yuan_nginx yuan/install/nginx:v2 sh
  • 查看容器是否启动成功, 并测试 nginx
[root@shawn ~]# docker exec yuan_nginx crul 127.0.0.1  # 出现 html 代码说明部署成功

4.3 构建以 Centos 为依赖镜像并安装 Django 的服务

  • 首先构建一个Dockerfile文件
[root@shawn ~]#vim Dockerfile
# 指定基础镜像
FROM centos:7
# 运行命令
RUN yum makecache && yum update -y && yum install -y python3 && pip3 install django
# 拷贝本地文件到容器
COPY shawn /root/
# 指定进入到容器的工作目录
WORKDIR /root/
# 指定向外暴露的端口
EXPOSE 8080
# 运行命令
CMD cd ./shawn && python3 manage.py runserver 0.0.0.0:8080
  • 文件 shawn 的构建
在宿主机上安装 Django
django-admin startproject shawn  #创建一个 "Shawn" 项目
cd ./shawn  #进入目录
django-admin startapp application  #开始项目
cd ./shawn
vim setting.cong  #修改配置文件"*"代理
cd .. #退出
  • 构建镜像
[root@shawn ~]#docker build -t test333:v1 .
  • 查看并使用镜像实例化出容器
[root@shawn ~]#docker images
[root@shawn ~]#docker run -dit --name test001 -p 9999:8080 test333:v1 sh
  • 查看刚开启的容器,并进入容器启动 Django 服务
[root@shawn ~]#docker exec -it test001 bash
[root@80f1315c030c ~]# python3 manage.py runserver 0.0.0.0:8080
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 04, 2020 - 06:50:19
Django version 3.1.4, using settings 'lingxiu.settings'
Starting development server at http://0.0.0.0:8080/
Quit the server with CONTROL-C.
  • 使用浏览器验证一下

image-20201204180450328

4.4 构建以 python 为依赖镜像并安装 Django 服务

  • 编辑 Dockerfile 文件
[root@shawn ~]# vim Dockerfile
'''文件内容
# 指定依赖镜像
FROM python:3.6

# 设置作者
MAINTAINER Shawn

# 执行命令
RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip3 install django==2.2.2

# 拷贝文件
COPY app /root/

# 设置工作目录
WORKDIR /root/

# 执行命令
CMD cd ./app && python3 manage.py runserver 0.0.0.0:7777
  • 文件 app 的构建
在宿主机上安装 Django
django-admin startproject app  #创建一个 "app" 项目
cd ./app  #进入目录
django-admin startapp application  #开始项目
cd ./app
vim setting.cong  #修改配置文件"*"代理
cd .. #退出
  • 构建镜像
[root@shawn ~]#docker build -t jjjj .
  • 查看并使用镜像实例化出容器
[root@shawn ~]#docker images
[root@shawn ~]#docker run -dit --name jjjjtest -p 4444:7777 jjjj:latest sh
  • 查看刚开启的容器,并进入容器启动 Django 服务
[root@shawn ~]#docker exec -it jjjtest bash
root@b85f93fcc114:~# python3 manage.py runserver 0.0.0.0:7777
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
December 04, 2020 - 10:17:51
Django version 2.2.2, using settings 'app.settings'
Starting development server at http://0.0.0.0:7777/
Quit the server with CONTROL-C.
  • 使用浏览器检验一下

4.5 使用 NGINX 代理 Django

  • 先构建一个 Django 服务, 步骤与上一个例子相同
  • 改变了一下向外暴露的端口
????编写 "Dockerfile" 文件
[root@shawn DjangoDocker]#vim Dockerfile 
'''文件内容
# 指定依赖镜像
FROM pyhton:3.6

# 安装 Django
RUN /usr/local/bin/python -m pip install --upgrade pip
RUN pip3 install django==2.2.2

# COPY 文件
COPY app /root/

# 指定工作目录
WORKDIR /root/

# 运行命令
CMD cd ./app && python3 manage.py runserver 0.0.0.0:8080
'''
[root@shawn DjangoDocker]#ls
app  Dockerfile  # 这两个文件, "app" 在上一个例子中有构建

????构建镜像,并查看
[root@shawn DjangoDocker]#docker build -t python_django:v6 .
[root@shawn DjangoDocker]#docker images

????实例出容器,并查看
[root@shawn DjangoDocker]#docker run -dit --name p_d_test1 -p 8888:8080 python_django:v6 sh
6906ff9e3ec0f9d583eb27890d82c79deff4358a43e5f1ec768a702547d020bf
[root@shawn DjangoDocker]#docker ps

????进到容器里面,开启服务,再测试
[root@shawn DjangoDocker]#docker exec -it p_d_test1 bash
root@6906ff9e3ec0:~# python3 manage.py runserver 0.0.0.0:8080
[root@shawn DjangoDocker]#curl 127.0.0.1:8888
  • 然后来编写 nginx 服务以及代理配置
????编写 "nginx.repo" 文件
[root@shawn NginxDocker]#vim nginx.repo
'''文件内容(官网可复制)
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
'''

????编写 "default.conf" 文件(代理"Django"配置)
[root@shawn NginxDocker]#vim default.conf
'''文件内容
server {
        listen 80;
        server_name www.py16zxl.com;

        location / {
        # 这里填的是 Django 服务的访问地址与端口(映射端口)
        proxy_pass http://192.168.13.234:8888/;
        index index.html index.htm index.jsp;
        }
}
'''

????编写 "Dockerfile" 文件
[root@shawn NginxDocker]#vim Dockerfile
'''文件内容
# 指定依赖进行
FROM centos:7

# 指定作者
MAINTAINER shawn

# 安装依赖
RUN yum install -y yum-utils gcc gcc-c++ pcre pcre-devel zlib zlib-devel make wget

## 源码安装 nginx1.18.0
# RUN wget http://nginx.org/download/nginx-1.18.0.tar.gz && tar -xvf nginx-1.18.0.tar.gz && cd nginx.1.18.0 && ./configure --prefix="/usr/local/nginx-1.18.0" && make && make install

# 拷贝 NGINX 配置文件
COPY nginx.repo /etc/yum.repos.d/

# 更新 yum 软件包索引
RUN yum makecache fast

# yum 安装 nginx
RUN yum install -y nginx

# 指定向外暴露的端口
EXPOSE 8000

# 拷贝 nginx 默认配置文件
COPY default.conf /etc/nginx/conf.d/

# 容器起来运行的命令
CMD /usr/local/nginx-1.18.0/sbin/nginx -g 'daemon off;' 
'''
# 当前需要的文件
[root@shawn NginxDocker]#ls
default.conf  Dockerfile  nginx.repo

# 开始构建镜像,并查看
[root@shawn NginxDocker]#docker build -t nginx_d:v7 .
[root@shawn NginxDocker]#docker images

# 实例化出容器,并查看
[root@shawn NginxDocker]#docker run -dit --name nginx_d -p 80:80 nginx_d:v7 sh
[root@shawn NginxDocker]#docker ps

# 进入容器,开启 "nginx" 服务,并验证
[root@shawn NginxDocker]#docker exec -it nginx_d bash
[root@51f54c1d5abb /]#nginx
[root@shawn NginxDocker]#curl 127.0.0.1:80
# 发现通过访问 nginx 也可以进入 Django 页面

五、Docker 镜像管理

5.1 docker save

docker save 命令用于将一个或多个镜像保存到归档文件中。这个命令常用于将镜像迁移到其他地方,或者在没有 Docker registry 的环境中分享镜像。

5.2 docker load

docker load 命令用于从一个归档文件中加载镜像。

举例:

docker load -i ubuntu.tar

这个例子将从当前目录下的 ubuntu.tar 文件中加载镜像。

5.3 docker tag

docker tag 命令用于为本地镜像创建一个新的标签。这个命令常用于准备将镜像推送到 Docker registry 之前,为镜像设置一个合适的版本号或者命名空间。

举个例子:

docker tag ubuntu:latest myusername/ubuntu:latest

这个例子将 ubuntu:latest 镜像打上一个新的标签 myusername/ubuntu:latest。

5.4 docker push

docker push 命令用于将本地的镜像上传到 Docker registry。
例子:

docker push myusername/ubuntu:latest

这个例子将 myusername/ubuntu:latest 镜像推送到 Docker Hub 中的 myusername 命名空间下。

5.5 docker pull

docker pull 命令用于从 Docker registry 拉取或者更新指定的镜像。
例子:

docker pull ubuntu:latest

这个例子将从 Docker Hub 拉取最新的 Ubuntu 镜像到本地。
使用这些命令时,请确保您已经登录到 Docker registry(使用 docker login 命令),并且有足够的权限进行 pushpull操作。

六、镜像仓库

6.1 Docker hub

  • https://hub.docker.com/

6.2 创建私有镜像仓库

sudo docker run -d -p 5000:5000 registry

七、查看镜像构建历史

docker history 镜像名称:标签|ID
docker history mycentos:7

八、多段构建 ( Multi-stage build)

  • 有效减少镜像层级的方式
# 使用 golang:1.16-alpine 作为基础镜像
FROM golang:1.16-alpine AS build

# 安装git,用于获取依赖
RUN apk add --no-cache git

# 安装dep工具,用于依赖管理
RUN go get github.com/golang/dep/cmd/dep

# 将项目依赖的Gopkg.lock和Gopkg.toml复制到容器中
COPY Gopkg.lock Gopkg.toml /go/src/project/

# 设置工作目录
WORKDIR /go/src/project/

# 使用dep安装项目依赖,仅使用-vendor-only模式
RUN dep ensure -vendor-only

# 复制整个项目到容器中
COPY . /go/src/project/

# 构建项目,输出可执行文件到/bin/project
RUN go build -o /bin/project
# 使用scratch作为基础镜像,减小镜像体积
FROM scratch

# 将可执行文件从第一个阶段的构建中复制到scratch镜像中
COPY --from=build /bin/project /bin/project

# 定义容器启动时的入口点
ENTRYPOINT ["/bin/project"]

九、多进程的容器镜像

9.1 选择适当的 init 进程

  • 需要捕获 SIGTERM 信号并完成子进程的优雅终止
  • 负责清理退出的子进程以避免僵尸进程

9.2 Tini 开源项目

Tini 是一个用于容器的微小但有效的 init。它的作用是生成一个单一的子进程(通常用于容器),并等待它退出,同时清理僵尸进程并进行信号转发。使用 Tini 有几个好处:防止软件意外创建僵尸进程,保证默认信号处理程序正常工作,并且可以完全透明地使用。在 Docker 中使用 Tini 时,如果版本是1.13或更高,则无需额外安装,只需通过 docker run 命令加上 --init 参数即可。另外,还可以使用预构建的 Docker 镜像或按照指南在其他平台上安装 Tini

开源项目地址:https://github.com/krallin/tini

十、Dockerfile 最佳实践

  • 不要安装安装无效软件包,适当使用 .dockerignore 文件忽略不需要包含在镜像中的文件和目录。

  • 应简化镜像中同时运行的进程数。理想状况下,每个镜像应该只有一个进程。当无法避免同一镜像运行多进程时,应选择合理的初始化进程 (init process)。

  • 最小化层级数。

    1. 最新的 Docker 只有 RUNCOPYADD 创建新层,其他指令创建临时层,不会增加镜像大小。比如 EXPOSE 指令就不会生成新层。
    2. 多条 RUN 命令可通过连接符连接成一条指令集以减少层数。
    3. 通过多段构建减少镜像层数。
  • 把多行参数按字母排序,可以减少可能出现的重复参数,并且提高可读性。

  • 编写 Dockerfile 的时候,应该把变更频率低的编译指令优先构建以便放在镜像底层以有效利用构建缓存。

  • 复制文件时,每个文件应独立复制,这确保某个文件变更时,只影响该文件对应的缓存。

终极目标:易管理、少漏洞、镜像小、层级少、利用缓存

上一篇:英伟达 V100、A100/800、H100/800 GPU 对比


下一篇:《LeetCode热题100》笔记&题解&思路&技巧&优化_Part_4-???????????? 相知