# 使用 Docker 镜像

# 获取镜像

docker [image] pull NAME[:TAG]
其中,NAME 是镜像仓库的名称(用来区分镜像),TAG 是镜像的标签(一般用来表示版本信息)。通常情况下,描述一个镜像需要包括 “名称” + “标签” 信息。

$ docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
......
Digest: sha256:e27e9d7f7f2Bd67aa9e2d7540bdc2b33254b452ee8e60f388875e5b7d9b2b696 
Status: Downloaded newer image for ubuntu:18.04

如果不显示指定 TAG,则默认会选择 latest 标签(最近的),这会下载仓库中最新的版本镜像。

从稳定性考虑,不要在生产环境中忽略镜像的标签信息或使用默认的 latest 标记的镜像

下载过程中,能看到镜像文件一般由若干层(layer)组成,当不同的镜像包括相同的层时,本地仅存储层的一份内容,减小了存储空间。

严格来讲,镜像的仓库名称中还应该添加仓库地址(registry,注册服务器)作为前缀,只是默认使用的是官方 Docker Hub 服务,该前缀可以忽略,例如,docker pull xxx:1.1,相当于 docker pull registry.hub.docker.com/xxx:v1.0。

如果从非官方的仓库下载,则需要在仓库名称前指定完整的仓库地址。比如网易蜂巢的镜像源来下载镜像,可以使用如下命令

$ docker pull hub.c.163.com/public/xxx:v1.0

pull 子命令

  • -a, --all-tags=true|false:是否获取仓库中的所有镜像,默认为否
  • –disable-content-trust:取消镜像的内容校验,默认为真

有时需要使用镜像代理来加速 Docker 镜像获取过程,可以在 Docker 服务启动配置中添加 --registry-mirror=proxy_url 来指定镜像代理服务地址

# 查看镜像信息

# 使用 images 命令列出镜像

使用 docker images 或 docker image ls 命令可以列出本地主机上已有镜像的基本信息。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
python              latest              9b5e75b69a4f        19 months ago       877 MB
docker.io/python    3.7                 9b5e75b69a4f        19 months ago       877 MB

信息中有几个字段,分别表示:

  • repository:来自于那个仓库,比如 python 表示 python 系列的基础镜像
  • tag:镜像的标签信息,比如 3.7、latest 表示不同的版本信息。标签只是标记,并不能标识镜像内容
  • image id:镜像的 ID(唯一标识镜像),如果两个镜像的 ID 相同,说明它们实际上指向了同一个镜像,只是具有不同标签名而已
  • created:创建时间,说明镜像最后的更新时间
  • size:镜像大小,优秀的镜像往往体积都比较小

# 使用 tag 命令添加镜像标签

为了方便后续工作中使用特定镜像,可以使用 docker tag 命令来为本地镜像任意添加标签。比如添加一个新的 mypython:latest 镜像标签

[root@sarly ~]# docker tag python mypython:latest
[root@sarly ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker.io/python    3.7                 9b5e75b69a4f        19 months ago       877 MB
mypython            latest              9b5e75b69a4f        19 months ago       877 MB
python              latest              9b5e75b69a4f        19 months ago       877 MB

tag 命令添加的标签实际上起到了类似链接的作用

# 使用 inspect 命令查看详细信息

使用 docker [image] inspect 命令可以获取该镜像的详细信息,包括制作者、适应架构、各层的数字摘要等:

[root@sarly ~]# docker inspect python
[
    {
        "Id": "3d751dd524bb176ed5e49dd9404ef052ca1c7bba9e531679cb83929e83ed9470",
        "Created": "2023-01-04T02:16:54.50690745Z",
        "Path": "/bin/sh",
        "Args": [
            "-c",
            "while true; do echo hello world; sleep 1; done"
        ],
        ......
        "Image": "sha256:9b5e75b69a4fd4c3fc674a62857b855396f3e9d71b5bb5a2a3fbab70b4409132",
        ......
    }
    ......
]

返回的是 JSON 格式,如果只想要其中一项内容时,可以使用 - f 来指定

[root@sarly ~]# docker inspect -f .Id python
3d751dd524bb176ed5e49dd9404ef052ca1c7bba9e531679cb83929e83ed9470
[root@sarly ~]# docker inspect -f .State.Status python
running

# 使用 history 命令查看镜像历史

镜像文件由多个层组成,如何知道各个层的具体内容?可以使用 history 子命令,该命令将列出各层的创建信息

[root@sarly ~]# docker history python
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
9b5e75b69a4f        19 months ago       /bin/sh -c #(nop)  CMD ["python3"]              0 B                 
<missing>           19 months ago       /bin/sh -c set -ex;   wget -O get-pip.py "...   8.31 MB             
<missing>           19 months ago       /bin/sh -c #(nop)  ENV PYTHON_GET_PIP_SHA2...   0 B                 
<missing>           19 months ago       /bin/sh -c #(nop)  ENV PYTHON_GET_PIP_URL=...   0 B                 
<missing>           19 months ago       /bin/sh -c #(nop)  ENV PYTHON_PIP_VERSION=...   0 B                 
<missing>           20 months ago       /bin/sh -c cd /usr/local/bin  && ln -s idl...   32 B                
<missing>           20 months ago       /bin/sh -c set -ex   && wget -O python.tar...   46.9 MB             
<missing>           20 months ago       /bin/sh -c #(nop)  ENV PYTHON_VERSION=3.7.10    0 B                 
<missing>           20 months ago       /bin/sh -c #(nop)  ENV GPG_KEY=0D96DF4D411...   0 B                 
<missing>           20 months ago       /bin/sh -c apt-get update && apt-get insta...   18 MB               
<missing>           20 months ago       /bin/sh -c #(nop)  ENV LANG=C.UTF-8             0 B                 
<missing>           20 months ago       /bin/sh -c #(nop)  ENV PATH=/usr/local/bin...   0 B                 
<missing>           20 months ago       /bin/sh -c set -ex;  apt-get update;  apt-...   510 MB              
<missing>           20 months ago       /bin/sh -c apt-get update && apt-get insta...   146 MB              
<missing>           20 months ago       /bin/sh -c set -ex;  if ! command -v gpg >...   17.5 MB             
<missing>           20 months ago       /bin/sh -c set -eux;  apt-get update;  apt...   16.5 MB             
<missing>           20 months ago       /bin/sh -c #(nop)  CMD ["bash"]                 0 B                 
<missing>           20 months ago       /bin/sh -c #(nop) ADD file:1a1eae7a82c66d6...   114 MB

# 搜寻镜像

使用 docker search 可以搜索 Docker Hub 官方仓库中的镜像,语法为 docker search [option] keyword。支持一下命令

  • -f,–filter filter:过滤输出内容
  • –format string:格式化输出内容
  • –limit int:限制输出结果个数,默认为 25 个
  • –no-trunc:不截断输出结果

比如,搜索官方提供的带 nginx 关键字的镜像

[root@sarly ~]# docker search --filter=is-official=true nginx
INDEX       NAME              DESCRIPTION                STARS     OFFICIAL   AUTOMATED
docker.io   docker.io/nginx   Official build of Nginx.   17901     [OK]

搜索所有收藏数超过 4 的关键词包括 tensorflow 的镜像

[root@sarly ~]# docker search --filter=stars=4 tensorflow
INDEX       NAME                                                DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
docker.io   docker.io/tensorflow/tensorflow                     Official Docker images for the machine lea...   2090                 
docker.io   docker.io/jupyter/tensorflow-notebook               Scientific Jupyter Notebook Python Stack w...   325                  
docker.io   docker.io/tensorflow/serving                        Official images for TensorFlow Serving (ht...   130                  
docker.io   docker.io/bitnami/tensorflow-serving                Bitnami Docker Image for TensorFlow Serving     30                   [OK]
docker.io   docker.io/armswdev/tensorflow-arm-neoverse          TensorFlow builds for AArch64 CPUs              12                   
docker.io   docker.io/armswdev/tensorflow-arm-neoverse-n1       Please use "armswdev/tensorflow-arm-neoverse"   12                   
docker.io   docker.io/intel/intel-optimized-tensorflow          Containers with TensorFlow* optimized with...   9                    
docker.io   docker.io/intel/intel-optimized-tensorflow-avx512   Containers with TensorFlow* optimized with...   5

# 删除和清理镜像

# 使用标签删除镜像

使用 docker rmi 或 docker image rm 命令可以删除镜像,命令格式为 docker rmi IMAGE [IMAGE…],其中 IMAGE 可以为标签或 ID

  • -f,-force:强制删除镜像,即使有容器依赖它
  • -no-prune:不要清理未带标签的父镜像

例如,要删除掉 mypython:latest 镜像

[root@sarly ~]# docker rmi mypython:latest
Untagged: mypython:latest
Untagged: docker.io/python@sha256:2418d2580b0696cfe3e924ada34478dc30e5dbdf2cfd06158a3553e4608aae53

# 使用镜像 ID 来删除镜像

使用 docker rmi 命令,并且后面跟上镜像的 ID(也可以是能进行区分的部分 ID 串前缀)时,会先尝试删除所有指向该镜像的标签,然后删除该镜像文件本身

注意,当有该镜像创建的容器存在时,镜像文件默认是无法被删除的,例如,先使用 python 镜像创建一个简单的容器来输出一段话

[root@sarly ~]# docker run python:3.7 echo 'hello! I am here!'
hello! I am here!
[root@sarly ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
e3a2216f2b9b        python:3.7              "echo 'hello! I am..."   3 seconds ago       Exited (0) 3 seconds ago                       ecstatic_sinoussi

可以看到后台存在一个退出状态的容器,当删除该镜像时,Docker 会提示容器正在运行,无法删除

[root@sarly ~]# docker rmi python:3.7
Error response from daemon: conflict: unable to remove repository reference "python:3.7" (must force) - container e3a2216f2b9b is using its referenced image 9b5e75b69a4f

如果想强制删除镜像,可以 - f 参数

$ docker rmi -f xxx:1.0

通常不推荐使用 - f 参数来强制删除一个存在容器依赖的镜像。正确的做法应该是先删除依赖该镜像的所有容器,再来删除镜像。

$ docker rm xxxxxx # 删除容器
$ docker rmi xxxxxx # 删除镜像

# 清理镜像

使用 Docker 一段时间后,系统可能会遗留一些临时的镜像文件,以及一些没有使用的镜像,可以通过 docker image prune 命令来清理。支持选项包括:

  • -a,-all:删除所有无用镜像,不光是临时镜像
  • -filter:只清理符合给定过滤器的镜像
  • -f,-force:强制删除镜像,而不进行提示确认
[root@sarly ~]# docker image prune -f
Total reclaimed space: 0 B

# 创建镜像

创建镜像的方法主要有三种:给予已有镜像的容器创建、基于本地模板导入、基于 Dockerfile 创建

这里主要介绍 Docker 的 commit、import 和 build 子命令

# 基于已有容器创建

命令格式为 docker [container] commit [OPTIONS] CONTAINER [REPOSITORY [:TAG]],主要选择包括:

  • -a,–author="":作者信息
  • -c,–change=[]:提交的时候执行 Dockerfile 指令,包括 CMD|ENTRYPOINT|ENV|EXPOSE|LABEL|ONBUILD|USER|VOLUME|WORKDIR 等
  • -m,–message="":提交信息
  • -p,–pause=true:提交时暂停容器运行

首先启动一个镜像,并在其中进行修改。比如在容器内创建一个 test 文件,之后退出

[root@sarly ~]# docker run -it python:latest /bin/bash
root@70cff5836e25:/# touch test
root@70cff5836e25:/# exit

此时该容器与原 python:3.7 镜像相比,已经发生变化了,可以使用 docker [container] commit 命令来提交为一个新的镜像。提交时可以使用 ID 或名称来指定容器:

[root@sarly ~]# docker commit -m "Added a new file" -a "Docker Newbee" 70cff test:0.1
sha256:d9bf66bc145a484711c0f0440becf302afb6d3cfd4886ad7a3471f336ac58b4e
[root@sarly ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                0.1                 d9bf66bc145a        2 seconds ago       877 MB
docker.io/python    3.7                 9b5e75b69a4f        19 months ago       877 MB
python              latest              9b5e75b69a4f        19 months ago       877 MB

查看本地镜像列表,可以发现新创建的镜像已经存在了。

# 基于本地模板导入

也可以直接从一个操作系统模板文件导入一个镜像,主要使用 docker [container] import 命令。格式为 docker [image] import [OPTIONS] file|URL|-[REPOSITORY [:TAG]]

要直接导入一个镜像,可以使用 OpenVZ 提供的模板来创建,或者其它已导出的镜像模板来创建。

OPENVZ 模板下载地址为:http://openvz.org/Download/templates/pr,ecr~ated、https://wiki.openvz.org/Download/template/precreated

$ cat ubuntu-18.04-x86_64-minimal.rar.gz I docker imper七- ubuntu:18.04

# 基于 Dockerfile

基于 Dockerfile 创建是最常见的方式。Dockerfile 是一个文本文件,利用给定的指令描述基于某个父镜像创建新镜像的过程。

下面使用 Dockerfile,基于 debian:stretch-slim 镜像安装 python3 环境,构成一个新的 python:3 镜像

FROM debian:stretch-slim
LABEL version="1.0" maintainer="docker user <docker_user@github>"
RUN apt-get update & \
    apt-get install -y python3 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

创建镜像的过程可以使用 docker [image] build 命令,编译成功后本地将多出一个 python:3 镜像

$ docker [image] build -七 PY hon:3
Successfully built 4bl0f46eacc8 
Successfully agged PY hon:3
$ docker images | grep python
python 3 4bl0f46eacc8 Abou minu ago 95.1MB

# 存出和载入镜像

# 存储镜像

如果要导出镜像到本地文件,可以使用 docker [image] save 命令。该命令支持 - o、-output string 参数,导出镜像到指定文件中

$ docker images 
REPOSITORY TAG IMAGE ID CREATED VIRTUAL ZE
ubuntu 18.04 0458a4468cbc 2 weeks ago 188·MB 
... 
$ docker save -o ubuntu 18.04.tar ubuntu:18.04

之后,用户可以通过复制该文件将该镜像分享给他人

# 载入镜像

可以使用 docker [image] load 将导出的 tar 文件再导入到本地镜像库。支持 - i、-input string 选项,从指定文件中读入镜像内容

$ docker load -i ubuntu_18.04.tar
或者:
$ docker load < ubuntu_18.04.tar

# 操作 Docker 容器

容器是镜像的一个运行实例。所不同的是,镜像是静态的只读文件,而容器带有运行时需要的可写文件层,同时,容器中的应用程序处于运行状态

# 创建容器

以下主要介绍 Docker 容器的 create、start、run、wait、logs 子命令

# 新建容器

可以使用 docker [container] create 命令创建一个容器

[root@sarly data]# docker create -it ubuntu:latest
b9d0ef8dd73f277cdc5afb3f1a281120dd3fc3e5ea0f7a67a3ce7c14f6f4fe59
[root@sarly data]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
b9d0ef8dd73f        ubuntu:latest       "bash"              4 seconds ago       Created                                 hardcore_morse

使用 docker [container] create 命令新建的容器处于停止状态,可以使用 docker [container] start 命令来启动它

容器是整个 Docker 技术栈的核心,create 命令和 run 命令支持的选项都十分复杂

create 命令与容器运行模式相关的选项

create 命令与容器环境和配置相关的选项

create 命令与容器资源限制和安全保护相关的选项

其它选项还包括:

  • -l,–label=[]:以键值对方式指定容器的标签信息
  • –label-file=[]:从文件中读标签信息

# 启动容器

使用 docker [container] start 命令来启动一个已创建的容器

[root@sarly data]# docker start b9
b9
# 通过 docker ps 查看到一个运行中的容器
[root@sarly data]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
b9d0ef8dd73f        ubuntu:latest       "bash"              39 minutes ago      Up 7 seconds                            hardcore_morse

# 新建并启动容器

除了创建容器后通过 start 启动,也可以直接新建并启动容器,使用 docker [container] run 等价于先执行 docker create 然后再执行 docker start

例如,下面命令输出一个 Hello World 之后,容器自动终止

[root@sarly data]# docker run ubuntu /bin/echo "Hello World"
Hello World

当利用 docker [container] run 来创建并启动容器后,docker 在后台运行的标准操作包括:

  1. 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  2. 利用镜像创建一个容器,并启动该容器
  3. 分配一个文件系统给容器,并在只读的镜像层外面挂载一层可读写层
  4. 从宿主机配置的网桥接口中桥接一个虚拟接口到容器中
  5. 从网桥的地址池配置一个 IP 地址给容器
  6. 执行用户指定的应用程序
  7. 执行完毕后容器被自动终止

可以通过 man 命令来查看详细命令使用,如 man docker-run

可以使用 docker container wait CONTAINER [CONTAINER…] 子命令来等待容器退出,并打印退出返回结果

某些时候,执行 docker [container] run 时,因为命令无法正常执行容器会出错直接退出,此时可以查看退出的错误代码。

默认情况下,常见错误代码包括:

  • 125:Docker daemon 执行出错,例如指定了不支持的 Docker 命令参数
  • 126:所指定命令无法执行,例如权限出错
  • 127:容器内命令无法找到

命令执行后出错,会默认返回命令的退出错误码,比如,我们随便随便指定错误的子命令

[root@sarly data]# docker run -it ubuntu -psp
/usr/bin/docker-current: Error response from daemon: oci runtime error: container_linux.go:235: starting container process caused "exec: \"-psp\": executable file not found in $PATH".

这时候,我们通过 inspect 名字查看退出状态码

[root@sarly data]# docker inspect -f {\{.State.ExitCode}} e321
127

# 守护态执行

更多时候,需要让 docker 容器在后台以守护态(Daemonized)形态运行。此时,可以通过添加 - d 参数来实现

[root@sarly data]# docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
52ac37f27a10ec2a0cbeee8ccb0b4367ef3c5a2a098a0b54c89091d538aed963
[root@sarly data]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
52ac37f27a10        ubuntu              "/bin/sh -c 'while..."   3 seconds ago       Up 2 seconds                            peaceful_albattani

容器启动后会返回一个唯一 ID,可以通过 docker ps 或 docker container ls 来查看容器信息

# 查看容器输出

要获取容器的输出信息,可以通过 docker [container] logs 命令

该命令支持的选项包括:

  • -details:打印详细信息
  • -f,-follow:持续保持输出
  • -since string:输出从某个时间开始的日志
  • -tail string:输出最近的若干日志
  • -t,-timestamps:显示时间戳信息
  • -until string:输出某个时间之前的日志

查看容器的输出

[root@sarly data]# docker logs 52ac
hello world
......

# 停止容器

以下主要介绍 docker 容器的 pause/unpause、stop、prune 子命令

# 暂停容器

可以使用 docker [container] pause CONTAINER [CONTAINER…] 命令来暂停一个运行中的容器

例如,启动一个容器,并将其暂停

[root@sarly data]# docker run --name test --rm -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
189eea7f42f31007d4cbdd1f65cd263a5cda820e21915e3f2dda61e40dcc4b2f
[root@sarly data]# docker pause test
test
[root@sarly data]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                   PORTS               NAMES
189eea7f42f3        ubuntu              "/bin/sh -c 'while..."   18 seconds ago      Up 17 seconds (Paused)                       test

处于 paused 状态的容器,可以使用 docker [container] unpause CONTAINER [CONTAINER…] 命令来恢复到运行状态 \

[root@sarly data]# docker unpause test
test
[root@sarly data]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS               NAMES
189eea7f42f3        ubuntu              "/bin/sh -c 'while..."   About a minute ago   Up About a minute                       test

# 终止容器

使用 docker [container] stop 来终止一个运行中的容器,该命令的格式为 docker [container] stop [-t|–time [=10]] [CONTAINER…]

该命令会首先向容器发送 SIGTERM 信号,等待一段超时时间后(默认为 10 秒),再发送 SIGKILL 信号来终止容器

[root@sarly data]# docker stop 189
189

此时,执行 docker container prune 命令,会自动清楚掉所有处于停止状态的容器,此外还可以通过 docker [container] kill 直接发送 SIGKILL 信号来强行终止容器

# 进入容器

在使用 - d 参数时,容器启动后会进入后台,用户无法看到容器中的信息,也无法进行操作

这个时候如果需要进入容器进行操作,推荐使用官方的 attach 或 exec 命令

# attach 命令

attach 是 docker 自带的,命令格式为:docker [container] attach [–detach-key [=[]]] [–no-stdin] [–sig-proxy [=true]] CONTAINER

这个命令支持三个主要选项:

  • –detach-keys [=[]]:指定退出 attach 模式的快捷键序列,默认是 CTRL-P、CTRL-Q
  • –no-stdin=true|false:是否关闭标准输入,默认是保持打开
  • –sig-proxy=true|false:是否代理收到的系统信号给应用进程,默认为 true
[root@sarly data]# docker run -itd ubuntu
9e060eaec3b7bb2f4626d58f432a4c3cc0dc4149789986514ea3c3ee331fec5c
[root@sarly data]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
9e060eaec3b7        ubuntu              "bash"              1 second ago        Up 1 second                             optimistic_mestorf
[root@sarly data]# docker attach 9e0
root@9e060eaec3b7:/#

然而,使用 attach 命令有时候并不方便。当多个窗口同时 attach 到同一个容器时,所有窗口都会同步显示,当某个窗口因命令阻塞时,其它窗口也无法执行操作了。

# exec 命令

从 docker 的 1.3.0 版本起,docker 提供了一个更加方便的工具:exec 命令,可以在运行中容器内直接执行任意命令

该命令的基本格式为:
docker [container] exec [-d|–detach] [–detach-keys[=[]]] [-i|–interactive] [–privileged] [-t|–tty] [-u|–user[=USER]] CONTAINER COMMAND [ARG…]

比较重要的参数有:

  • -d,–detach:在容器中后台执行命令
  • –detach-keys="":指定将容器切回后台的按键
  • -e,–env=[]:指定环境变量列表
  • -i,–interactive=true|flase:打开标准输入接受用户输入命令,默认为 false
  • –privileged=true|false:是否给执行命令以最高权限,默认值为 false
  • -t,–tty=true|false:分配伪终端,默认值为 false
  • -u,–user="":执行命令的用户名或 ID
[root@sarly data]# docker exec -it 9e0 /bin/bash
root@9e060eaec3b7:/# w
 09:12:17 up 5 days, 16:05,  0 users,  load average: 0.00, 0.01, 0.05
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root@9e060eaec3b7:/# ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 09:12 ?        00:00:00 bash
root          7      0  0 09:12 ?        00:00:00 /bin/bash
root         14      7  0 09:12 ?        00:00:00 ps -ef

通过指定 - it 参数来保持标准输入打开,并且分配一个伪终端。通过 exec 命令对容器执行操作是最为推荐的方式

# 删除容器

可以使用 docker [container] rm 命令来删除处于终止或退出状态的容器,命令格式为:docker [container] rm [-f|–force] [-l|–link] [-v|–volumes] CONTAINER [CONTAINER…]

主要支持的选项包括:

  • -f,–force=false:是否强行终止并删除一个运行中的容器
  • -l,–link=false:删除容器的连接,但保留容器
  • -v,–volumes=false:删除容器挂载的数据卷

查看处于终止状态的容器,并删除

[root@sarly data]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                        PORTS               NAMES
9e060eaec3b7        ubuntu              "bash"              24 minutes ago      Exited (137) 11 seconds ago                       optimistic_mestorf
[root@sarly data]# docker rm 9e
9e

默认情况下,docker rm 命令只能删除已经处于终止或退出状态的容器,并不能删除还处于运行状态的容器。

如果要直接删除一个运行中的容器,可以添加 - f 参数,docker 首先会发送 SIGKILL 信号给容器,终止其中的应用,之后强行删除

[root@sarly data]# docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
8cd7bfdc0af8e80b744127f59bf7192082c4b7276c3e0059fd6f3397bbe5ed74
[root@sarly data]# docker rm 8cd
Error response from daemon: You cannot remove a running container 8cd7bfdc0af8e80b744127f59bf7192082c4b7276c3e0059fd6f3397bbe5ed74. Stop the container before attempting removal or use -f
[root@sarly data]# docker rm -f 8cd
8cd

# 导入和导出容器

有时候,需要将容器从一个系统迁移到另一个系统,此时可以使用 docker 的导入和导出功能,这也是 docker 自身提供的一个重要特性。

# 导出容器

导出容器是指,导出一个已经创建的容器到一个文件,不管此时这个容器是否处于运行状态。可以使用 docker [container] export 命令,该命令格式为:docker [container] export [-o|–output [=""]] CONTAINER

其中,可以通过 - o 选项来指定导出的 tar 文件名,也可以直接通过重定向来实现。

首先查看所有容器

[root@sarly data]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
04f755f9a3ff        ubuntu              "/bin/sh -c 'while..."   3 seconds ago       Up 3 seconds                            pensive_mirzakhani

导出 04f755f9a3ff 容器到文件 test.tar

[root@sarly data]# docker export -o test.tar 04f
[root@sarly data]# docker export 04f755f9a3ff > test.tar

之后,可将导出的 tar 文件传输到其它机器上,然后再通过导入命令导入系统中,实现容器的迁移。

# 导入容器

导出的文件又可以使用 docker [container] import 命令导入变成镜像,该命令格式为:docker import [-c|–change=[]] [-m|–message [=MESSAGE]] file|URL|-[REPOSITORY [:TAG]]

用户可以通过 - c,–change=[] 选项在导入的同时执行对容器进行修改的 Dockerfile 指令(参考后续章节)

将 test.rar 文件导入到系统中

[root@sarly data]# docker import test.tar test/ubuntu:v1.0
sha256:419b8b12d87c363dfd6a090143139599715fe77adaabe0063e966b8454fb36ea
[root@sarly data]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test/ubuntu         v1.0                419b8b12d87c        3 seconds ago       77.8 MB
docker.io/ubuntu    latest              6b7dfa7e8fdb        4 weeks ago         77.8 MB
docker.io/python    3.7                 9b5e75b69a4f        19 months ago       877 MB

前面的章节使用过 docker load 命令来导入一个镜像文件,与 docker import 命令十分类似。

实际上,既可以使用 docker load 来导入镜像文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于:容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积更大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

# 查看容器

主要介绍 docker 容器的 inspect、top、stats 子命令。

# 查看容器详情

查看容器详细可以使用 docker container inspect [OPTIONS] CONTAINER [CONTAINER…] 子命令。

例如,查看某容器的具体信息,会以 json 格式返回包括容器 ID、创建时间、路径、状态、镜像、配置等各项信息:

[root@sarly data]# docker container inspect 04f
[
    {
        "Id": "04f755f9a3ff6b407a1deab424f5633639165e8fdb94fc21e3f0f5d0483f4963",
        "Created": "2023-01-05T09:31:18.297279031Z",
        "Path": "/bin/sh",
        "Args": [
            "-c",
            "while true; do echo hello world; sleep 1; done"
        ],
        "State": {
            "Status": "running",
            ......
        }
        ......
    }
]

# 查看容器内进程

查看容器内进程可以使用 docker [container] top [OPTIONS] CONTAINER [CONTAINER…] 子命令。

这里子命令类似 Linux 中的 top,会打印出容器内的进程信息,包括 PID、用户、时间、命令等。例如查看某容器内的进程信息:

[root@sarly data]# docker top 04f
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                53837               53821               0                   09:45               ?                   00:00:00            /bin/sh -c while true; do echo hello world; sleep 1; done
root                60548               53837               0                   11:29               ?                   00:00:00            sleep 1

# 查看统计信息

查看统计信息可以使用 docker [container] stats [OPTIONS] [CONTAINER…] 子命令,会显示 CPU、内存、存储、网络等使用情况的统计信息。

支持选项包括:

  • -a,-all:输出所有容器统计信息,默认仅在运行中
  • -format string:格式化输出信息
  • -no-stream:不持续输出,默认会自动更新持续实时结果
  • -no-trunc:不截断输出信息

例如,查看当前运行中容器的系统资源使用统计:

[root@sarly data]# docker stats 04f --no-stream
CONTAINER           CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O           PIDS
04f                 0.35%               204 KiB / 7.62 GiB   0.00%               656 B / 656 B       0 B / 0 B           2

# 其它容器命令

本节主要介绍 docker 容器的 cp、diff、port、update 子命令。

# 复制文件

container cp 命令支持在容器和主机之间复制文件。命令格式为 docker [container] cp [OPTIONS] CONTAINER:SRC_PATH DESC_PATH|-。

支持的选项包括:

  • -a,-archive:打包模式,复制文件会带有原始的 uid/gid 信息
  • -L,-follow-link:跟随软连接。当原路径为软连接时,默认只复制链接信息,使用该选项会复制链接的目标内容

例如,将本地的路径 data 复制到 test 容器的 /tmp 路径下:

[root@sarly data]# docker cp . test:/tmp
# 容器内查看
root@04f755f9a3ff:/tmp# ls
test1.txt  test2.txt  test3.txt

# 查看变更

container diff 查看容器内文件系统的变更,命令格式为:docker [container] diff CONTAINER。

例如,查看 test 容器内数据修改:

[root@sarly data]# docker container diff test
C /root
A /root/.bash_history
C /run
D /run/secrets
C /tmp
A /tmp/test1.txt
A /tmp/test2.txt
A /tmp/test3.txt

docker diff 会列出 3 中容器内文件状态变化:

  • A:Add
  • D:Delete
  • C:Change

# 查看端口映射

container port 命令可以查看容器的端口映射情况。命令格式为 docker container port CONTAINER [PRIVATE_PORT [/PROTO]]。

例如,查看 test 容器的端口映射情况:

[root@sarly data]# docker container port test
7878/tcp -> 0.0.0.0:32768

# 更新配置

container update 命令可以更新容器的一些运行时配置,主要是一些资源限制份额。

命令格式为:docker [container] update [OPTIONS] CONTAINER [CONTAINER…]

支持的选项包括:

  • -blkio-weight uint16:更新块 IO 限制,10~1000,默认值为 0,代表着无限制
  • -cpu-preiod int:限制 CPU 调度器 CFS(Completely Fair Scheduler)使用时间,单位为微秒,最小 1000
  • -cpu-quota int:限制 CPu 调度器 CFS 配额,单位为微秒,最小 1000
  • -cpu-rt-period int:限制 CPU 调度器的实时周期,单位为微秒
  • -cpu-rt-runtime int:限制 CPU 调度器的实时运行时,单位为微秒
  • -c,-cpu-shares int:限制 CPU 使用份额
  • -cpus decimal:限制 CPU 个数
  • -cpuset-cpus string:允许使用的 CPU 核,如 0-3,0,1
  • -cpuset-mems string:允许使用的内存块,如 0-3,0,1
  • -kernel-memory bytes:限制使用的内核内存
  • -m,-memory bytes:限制使用的内存
  • -memory-reservation bytes:内存软限制
  • -memory-swap bytes:内存加上缓存区的限制,-1 表示为对缓冲区无限制
  • -restart string:容器退出后的重启策略

例如,限制总配额为 1 秒,容器 test 所占用时间为 10%

[root@sarly data]# docker update --cpu-quota 1000000 test
test
[root@sarly data]# docker update --cpu-period 100000 test
test

在生产环境中,为了提高容器的高可用性和安全性,一方面要合理使用资源限制参数来管理容器的资源消耗,另一方面要指定合适的容器重启策略,来自动重启退出容器。此外,还可以使用 HAProxy 等辅助工具来处理负载均衡,自动切换故障的应用容器

# Docker 数据管理

在生产环境中,往往需要对数据进行持久化,或者需要在多个容器之间进行数据共享,这必然涉及容器的数据管理操作。

容器中的管理数据主要有两种方式:

  • 数据卷(Data Volumes):容器内数据直接映射到本地主机环境
  • 数据卷容器(Data Volumes Containers):使用特定容器维护数据卷

以下将介绍如何在容器内创建数据卷,并且把本地的目录或文件挂载到容器内的数据卷中;如何使用数据卷在容器和主机、容器和容器之间共享数据,并实时数据的备份和恢复。

# 数据卷

数据卷(Data Volumes)是一个可供容器使用的特殊目录,它将主机操作系统目录直接映射进容器,类似 Linux 中的 mount 行为。

数据库可以提供很多有用的特性:

  • 数据卷可以在容器之间共享和重用,容器间传递数据将变得高效与方便
  • 对数据卷内数据的修改会立马生效,无论是容器内操作还是本地操作
  • 对数据卷的更新不会影响镜像,解耦开应用和数据
  • 卷会一直存在,知道没有容器使用,可以安全地卸载它

# 创建数据卷

docker 提供了 volume 子命令来管理数据卷,如下命令可以快速在本地创建一个数据卷:

[root@sarly data]# docker volume create -d local test
test

此时,查看 /var/lib/docker/volumes 路径下,会发现所创建的数据卷位置

[root@sarly data]# ls -l /var/lib/docker/volumes
total 44
......
drwxr-xr-x. 3 root root    19 Jan  6 17:06 test

除了 create,docker volume 还支持 inspect(查看详细信息)、ls(列出已有数据卷)、prune(清理无用数据卷)、rm(删除数据卷)等

# 绑定数据卷

除了使用 volume 子命令来管理数据卷外,还可以在创建容器时将本地的任意路径挂载到容器内作为数据卷,这种形式创建的数据卷称为绑定数据卷

在用 docker [container] run 命令时,可以使用 - mount 选择来使用数据卷。-mount 选项支持 3 种类型的数据卷,包括:

  • volume:普通数据卷,映射到主机 /var/lib/docker/volumes 路径下
  • bind:绑定数据卷,映射到主机指定路径下
  • tmpfs:临时数据卷,只存在内存中

下面使用 training/webapp 镜像创建一个 Web 容器,并创建一个数据卷挂载到容器的 /opt/webapp 目录:

docker run -d -P --name web --mount type=bind,source=/webapp,destination=/opt/webapp raining/webapp python app.py

上述命令等同于使用旧的 - v 标记可以在容器内创建一个数据卷:

[root@sarly data]# docker run -d -p 7878 --name web -v /webapp:/opt/webapp training/webapp python app.py # 但这样是运行不了的,因为挂载的目录没有 app.py

这个功能在进行应用测试时十分方便,比如用户可以放置一些程序或数据到本地目录中实时进行更新,然后在容器内运行和使用。

需注意,本地目录的路径必须是绝对路径,容器内路径可以是相对路径。如果路径不存在,Docker 会自动创建

Docker 挂载数据卷的默认权限是读写(rw),用户也可以通过 ro 指定为只读

[root@sarly webapp]# docker run -d --name web -v /root/webapp:/opt/webapp:ro training/webapp python app.py

加上 :ro 之后,容器内对所挂载数据卷内的数据就无法修改了。我们可以进入容器修改试一试,当我们保存文件修改时,它提示到

它说可以添加!以覆盖,那当我们加上!之后呢?

此时,它又提示说,你无法打开文件进行写入。

如果直接挂载一个文件到容器,使用文件编辑工具,如 vi 或者 sed --in-place 的时候,可能会造成文件 inode 的改变。从 Docker1.1.0 起,这会导致报错误信息。所以推荐的方式是直接挂载文件所在的目录到容器内。

# 数据卷容器

如果用户需要在多个容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器也是一个容器,但是它的目的是专门提供数据卷给容器挂载

[root@sarly ~]# docker run -it -v /dbdata --name dbdata ubuntu
root@981ad36801cd:/# ls
bin  boot  dbdata  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

然后在其它容器中使用–volumes-from 来挂载 dbdata 容器中的数据卷,例如创建 db1 容器,并从 dbdata 容器挂载数据卷

[root@sarly ~]# docker run -it --volumes-from dbdata --name db1 ubuntu
root@75c9e36f8bbd:/# ls
bin  boot  dbdata  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

此时,db1 挂载同一个数据卷到相同的 /dbdata 目录,任何一方在该目录下的写入,其他容器都能看到,比如在 dbdata 容器中创建一个 test 文件

root@981ad36801cd:/# cd dbdata/
root@981ad36801cd:/dbdata# touch test
root@981ad36801cd:/dbdata# ls
test

在 db1 容器中查看它

root@75c9e36f8bbd:/# ls dbdata/
test

可以多次使用–volumes-from 参数来从多个容器挂载多个数据卷,还可以从其它已经挂载了数据卷的容器来挂载数据卷:

[root@sarly ~]# docker run -d --name web --volumes-from db1 training/webapp python app.py
[root@sarly ~]# docker exec -it web bash
root@ab7515213761:/dbdata# ls /dbdata/
name  test

使用–volumes-from 参数所挂载数据卷的容器自身并不需要保持在运行状态

如果删除了挂载的容器(包括 dbdata 和 db1),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显示使用 docker rm -v 命令来指定同时删除关联的容器。

# 利用数据卷容器来迁移数据

可以利用数据卷容器对其中的数据卷进行备份、恢复、以实现数据的迁移。

# 备份

使用下面命令来备份 dbdata 数据卷容器内的数据卷

[root@sarly ~]# docker run --volumes-from dbdata -v $(pwd):/backup --name worker ubuntu tar cvf /backup/backup.tar /dbdata

这个命令稍微有点复杂,具体分析如下:

  1. 首先利用 ubuntu 镜像创建一个容器 worker
  2. 使用–volumes-from dbdata 参数来让 woker 容器挂载 dbdata 容器的数据卷
  3. 使用 - v $(pwd):/backup 参数来挂载当前目录到 worker 容器的 /backup 目录
  4. worker 容器启动后,使用 tar cvf /backup/backup.tar/dbdata 命令将 /dbdata 下内容备份为容器内的 /backup/backup.tar,即宿主机当前目录下的 backup.tar

tar 中的一些参数说明

  • tar cvf xxx.tar/target:打包一个 tar
  • tar xvf xxx.tar:解开一个 tar
  • tar cvzf xxx.tar.gz/target:打包压缩一个 tar
  • tar zxvf xxx.tar.gz:解压一个 tar

选项说明

  • z:压缩
  • c:打包
  • x:解压
  • v:过程
  • f:指定文件名
# 恢复

如果要恢复数据到一个容器,可以按照下面操作。

首先创建一个带有数据卷的容器 dbdata2:

[root@sarly ~]# docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

然后创建另一个新的容器,挂载 dbdata2 的容器,并使用 untar 解压备份文件到所挂载的容器卷中:

[root@sarly ~]# docker run --volumes-from dbdata2 -v $(pwd):/backup ubuntu tar xvf /backup/backup.tar
dbdata/
dbdata/test
dbdata/name

通过数据卷和数据卷容器对容器内的数据进行共享、备份、恢复等操作,即使容器在运行中出现故障,用户也不必担心数据发送丢失,只需要快速地重新创建容器即可。
在生产环境中,推荐在使用数据卷或数据卷容器之外,定期将主机的本地数据进行备份,或者使用支持容错的存储系统,包括 RAID 分布式文件系统,如 Ceph、GPFS、HDFS 等。
另外,有时候不希望将数据保存在宿主机或容器中,还可以使用 tmpfs 类型的数据卷,其中数据只存在于内存中,容器退出后自动删除。

# 端口映射与容器互联

一般工作中,经常会碰到需要多个服务容器共同协作的情况,这往往需要多个容器之间能够互相访问到对方的服务。

Docker 除了通过网络访问外,还提供了两个很方便的功能来满足服务访问的基本需求:一个是允许映射容器内应用的服务端口到宿主机;另一个是互联机制实现多个容器间通过容器名来快速访问。

# 端口映射实现容器访问

# 从外部访问容器应用

在启动容器时,如果不指定对应参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的。

当容器中运行一些网络应用,要让外部访问这些应用时,可以通过 - P 或 - p 参数来指定端口映射。当使用 - P(大写),Docker 会随机映射一个端口到内部容器开放的网络端口:

[root@sarly ~]# docker run -d --name web -P training/webapp python app.py
a406b41c7dbc882c6e042779056e9268ef6ef28793bf366c96c88bdced83bb5b
[root@sarly ~]# docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                     NAMES
a406b41c7dbc        training/webapp     "python app.py"     2 seconds ago       Up 1 second         0.0.0.0:32773->5000/tcp   web

此时,可以使用 docker ps 看到,本机的 32773 被映射到了容器的 5000 端口。访问宿主机的 32773 端口即可访问容器内 web 应用。

可以通过 docker logs 查看应用信息

[root@sarly ~]# docker logs web
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
192.168.1.41 - - [09/Jan/2023 06:39:52] "GET / HTTP/1.1" 200 -
192.168.1.41 - - [09/Jan/2023 06:39:52] "GET /favicon.ico HTTP/1.1" 404 -

-p(小写)则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 IP:HostPort:ContainerPort | IP::ContainerPort | HostPort:ContainerPort

# 映射所有接口地址

使用 HostPort:ContainerPort 格式本地的 5000 端口映射到容器的 5000 端口,可以执行如下命令:

[root@sarly ~]# docker run -d --name web -p 5000:5000 training/webapp python app.py

此时默认会绑定本地所有接口上的所有地址。多次使用 - p 标记可以绑定多个端口。例如:

[root@sarly ~]# docker run -d --name web -p 5000:5000 -p 3000:80 training/webapp python app.py

# 映射到指定地址的指定端口

可以使用 IP:HostPort:ContainerPort 格式指定映射使用一个特定地址,比如 localhost

[root@sarly ~]# docker run -d --name web -p 127.0.0.1:5000:5000 training/webapp python app.py

# 映射到指定地址的任意端口

使用 IP::ContainerPort 绑定 localhost 的任意端口到容器的 5000 端口,本机会自动分配一个端口

[root@sarly ~]# docker run -d --name web -p 127.0.0.1::5000 training/webapp python app.py

还可以使用 udp 标记来指定 udp 端口

[root@sarly ~]# docker run -d --name web -p 127.0.0.1::5000/udp training/webapp python app.py

# 查看映射端口配置

使用 docker port 来查看当前映射的端口配置,也可以查看到绑定的地址

[root@sarly ~]# docker port web
5000/udp -> 127.0.0.1:32768

容器有自己内部网络和 IP 地址,使用 docker [container] inspect containerID 可以获取容器的具体信息

# 互联机制实现便捷互访

容器的互联(linking)是一种让多个容器中的应用进行快速交互的方式。它会在源和接收容器之间创建连接关系,接收容器可以通过容器名快速访问到源容器,而不用指定具体的 IP 地址。

# 容器互联

使用–link 参数可以让容器之间安全地进行交互。

下面先创建一个新的数据库容器

[root@sarly ~]# docker run -d --name db training/postgres

然后创建一个 web 容器,并将它连接到 db 容器

[root@sarly ~]# docker run -d -P --name web --link db:db training/webapp python app.py

此时,db 容器和 web 容器建立互联关系,–link 参数的格式为:–link name:alias,其中 name 是要链接的容器的名字,alias 的别名

使用 docker inspect 来查看容器的连接

[root@sarly ~]# docker inspect -f "\{\{.HostConfig.Links}}" web
[/db:/web/db]

这表示 web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。

Docker 相当于在两个互联的容器之间创建了一个虚拟通道,而且不用映射它们的端口到宿主机上,在启动 db 容器时并没有使用 - p 和 - P 标记,从而避免了暴露数据库服务端口到外部网络上。

Docker 通过两种方式为容器公开连接信息:

  • 更新环境变量
  • 更新 /etc/hosts 文件

使用 env 命令来查看 web 容器的环境变量

[root@sarly ~]# docker run --rm --name web2 --link db:db training/webapp env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=4e427564841a
DB_PORT=tcp://172.17.0.3:5432
DB_PORT_5432_TCP=tcp://172.17.0.3:5432
DB_PORT_5432_TCP_ADDR=172.17.0.3
DB_PORT_5432_TCP_PORT=5432
DB_PORT_5432_TCP_PROTO=tcp
DB_NAME=/web2/db
DB_ENV_PG_VERSION=9.3
HOME=/root

其中,DB_开头的环境变量是供 web 容器连接 db 容器使用,前缀采用大写的连接别名。

除了环境变量,Docker 还添加 host 信息到父容器的 /etc/hosts 的文件。下面是容器 web 的 hosts 文件

[root@sarly ~]# docker run -it --rm --link db:db training/webapp /bin/bash
root@47d307f7fa1b:/opt/webapp# cat /etc/hosts 
......
172.17.0.3      db 9689095436f4
172.17.0.4      47d307f7fa1b

这里有 2 个 hosts 信息,第一个 db 容器的 IP 和主机名,第二个是 web 容器,web 容器采用自己的 id 作为默认主机名。

可以在 web 容器中使用 ping 来测试跟 db 容器的连通:

root@47d307f7fa1b:/opt/webapp# ping db
PING db (172.17.0.3) 56(84) bytes of data.
64 bytes from db (172.17.0.3): icmp_seq=1 ttl=64 time=0.066 ms
64 bytes from db (172.17.0.3): icmp_seq=2 ttl=64 time=0.101 ms
64 bytes from db (172.17.0.3): icmp_seq=3 ttl=64 time=0.067 ms

用 ping 测试 db 容器,它会解析成 172.17.0.5。

用户可以链接多个子容器到父容器,比如可以链接多个 web 到同一个 db 容器上。

# 使用 Dockerfile 创建镜像

Dockerfile 是一个文本格式的配置文件,用户可以使用 Dockerfile 来快速创建自定义的镜像。

# 基本结构

Dockerfile 由一行行命令语句组成,支持以 #开头的行注释

一般而已,Dockerfile 主题内容分为四部分:基础镜像组成、维护者信息、镜像操作指令和容器启动时执行指令。

下面是一个简单的实例:

# escape=\ (backslash)
# This dockerfile uses the ubuntu:xeniel image 
# VERSION 2 - EDITION 1 
# Author: docker_user 
# Command format: Ins ruc ion [arguments / command] ..
# Base image use, this must be se as the first line
FROM ubuntu:xeniel
# Maintainer: docker_user <docker_user at email.com> (@docker_user)
LABEL maintainer docker_user<docker_user@email.com>
# Commands to update the image
RUN echo "dev http://archive.ubuntu.com/ubuntu/ xeniel main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUNB echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# Commands when creating a new container
CMD /usr/sbin/nginx

首行可以通过注释来指定解析器命令,后续通过注释说明镜像的相关信息。主体部分首先使用 FROM 指令指明所基于的镜像名称,接下来一般是使用 LABEL 指令说明维护者信息。后面则是镜像操作指令,例如 RUN 指令将对镜像执行跟随的命令。每运行一条 RUN 指令,镜像则添加新的一层,并提交。最后是 CMD 指令,来指定运行容器时的操作命令。

# 指令说明

Dockerfile 中指令的一般格式为 INSTRUCTION arguments,包括 “配置指令”(配置镜像信息)和 “操作指令”(具体执行操作)

Dockerfile 中的指令及说明

配置指令

  • ARG:定义创建镜像过程中使用的变量
  • FROM:指定所创建镜像的基础镜像
  • LABEL:为生成的镜像添加元数据标签信息
  • EXPOSE:声明镜像内服务监听的端口
  • ENV:指定环境变量
  • ENTRYPOINT:指定镜像的默认入口命令
  • VOLUME:创建一个数据卷挂载点
  • USER:指定运行容器时的用户名或 UID
  • WORKDIR:配置工作目录
  • ONBUILD:创建子镜像时指定自动执行的操作指令
  • STOPSIGNAL:指定退出的信号值
  • HEALTHCHECK:配置所启动容器如何进行健康检查
  • SHELL:指定默认 shell 类型

操作指令

  • RUN:运行指定命令
  • CMD:启动容器时指定默认指定的命令
  • ADD:添加内容到镜像
  • COPY:复制内容到镜像

# 配置指令

ARG
定义创建镜像过程中使用的变量,格式为 ARG [=]

在执行 docker build 时,可以通过 - build-arg [=] 来为变量赋值。当镜像编译成功后,ARG 指定的变量将不再存在(ENV 指定的变量将在镜像中保留)。

Docker 内置了一些镜像创建变量,用户可以直接使用而无需声明,包括(不区分大小写)HTTP_PROXY、HTTPS_PROXY、FTP_PROXT、NO_PROXY

FROM
指定所创建镜像的基础镜像,格式为 FROM [AS ] 或 FROM : [AS ] 或 FROM @ [AS ]

任何 Dockerfile 中第一条指令必须为 FROM 指令。并且,如果在同一个 Dockerfile 中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)

ARG VERSION=1.0
FROM python:3.9

LABEL
LABEL 指令可以为生成的镜像添加元数据标签信息,这些信息可以用来辅助过滤出特定镜像,格式为 LABEL = = =

LABEL version="1.0.1"
LABEL author="yeasy@github" date="2023-01-09"
LABEL description="This text illustrates" \
    that label-values can span multiple lines.

EXPOSE
声明镜像内服务监听的端口,格式为 EXPOSE [/…]

EXPOSE 22 80 8443

需要注意,该指令只是起到声明作用,并不会自动完成端口映射。如果要映射端口出来,在启动容器时,可以使用 - P/-p 参数来指定映射端口

ENV
指定环境变量,在镜像生成过程中会被后续 RUN 指令使用,在镜像启动的容器中也会存在。格式为 ENV 或 ENV =

ENV ADD_VERSION=1.0.0
ENV APP_HOME=/usr/local/app
ENV PATH $PATH:/usr/local/bin

指令指定的环境变量在运行时可以被覆盖掉,如 docker run --env = built_image

当一条 ENV 指令中同时为多个环境变量赋值并且值也是从环境变量读取时,会为变量都赋值后再更新。如下指令,最终结果为 key1=value1 key2=value2

ENV key1=value2
ENV key1=value1 key2=${key1}

ENTRYPOINT
指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数。

支持两种格式:

  • ENTRYPOINT [“executable”, “param1”, “param2”]:exec 调用指定
  • ENTRYPOINT command param1 param2:shell 中执行

此时,CMD 指令指定值将作为根命令的参数。

每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。在运行时,可以被–entrypoint 参数覆盖掉,如 docker run --entrypoint

ENTRYPOINT 与 CMD 的区别可以参考:https://www.cnblogs.com/poloyy/p/15470409.html

VOLUME
创建一个数据卷挂载点,格式为 VOLUME ["/data"]

运行容器时可以从本地主机或其它容器挂载数据卷,一般用来存放数据库和需要保持的数据等。

USER
指定运行容器时的用户名和 UID,后续的 RUN 等指令也会使用指定的用户身份。格式为 USER daemon。

当服务需要管理员权限时,可以通过该命令指定运行用户,并且可以在 Dockerfile 中创建所需要的用户。例如:

RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres

要临时获取管理员权限可以使用 gosu 命令

WORKDIR
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。格式为 WORKDIR /path/to/workdir。

可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指令的路径。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

则最终路径为 /a/b/c

为了避免出错,推荐 WORKDIR 指令中只使用绝对路径

ONBUILD
指定当基于所生成镜像创建子镜像时,自动执行的操作指令。格式为 ONBUILD [instruction]

例如,使用如下的 Dockerfile 创建父镜像 ParentImage,指定 ONBUILD 指令:

# Dockerfile for ParentImage
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

使用 docker build 命令创建子镜像 ChildImage 时(FROM ParentImage),会首先执行 ParentImage 中配置的 ONBUILD 指令:

# Dockerfile for ChildImage
FROM ParentImage

等价于在 ChildImage 的 Dockerfile 中添加了如下指令:

# Automatically run the following when building ChildImage
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src

由于 ONBUILD 指令是隐式执行的,推荐在使用它的镜像标签中进行标注,例如 ruby:2.1-onbuild

ONBUILD 指令在创建专门用于自动编译、检查等操作的基础镜像时,十分有用

STOPSIGNAL
指定所创建镜像启动的容器接收退出的信号值:STOPSIGNAL signal

HEALTYCHECK
配置所启动容器如何进行健康检查(如何判断健康是否),自 Docker1.12 开始支持。

格式有两种:

  • HEALTHCHECK [OPTION] CMD command:根据所执行命令返回值是否为 0 来判断
  • HEALTHCHECK NONE:禁止基础镜像中的健康检查

OPTION 支持参数:

  • -interval=DURATION(default:30s):过过久检查一次
  • -timeout=DURATION(default:30s):每次检查等待结果的超时时间
  • -retries=N(default:3):如果失败了,重试几次才最终确定失败

SHELL
指定其它命令使用 shell 时的默认 shell 类型:SHELL [“executable”, “parameters”],默认值为 ["/bin/sh", “-c”]

对于 Windows 系统,Shell 路径中使用了 “” 作为分隔符,建议在 Dockerfile 开头添加 #escape=’ 来指定转义符

# 操作指令

RUN
运行指定命令

格式为:RUN 或 RUN [“executable”, “param1”, “param2”]。注意,后者指令会被解析为 JSON 数组,因此必须用双引号。前者默认将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行,不会启动 shell 环境。

指定使用其它终端类型可以通过第二种方式实现,如:RUN ["/bin/bash", “-c”, “echo hello”]。

每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像层。当命令较长时可以使用 \ 来换行

RUN apt-get update \
    && apt-get install -y libsnappy-dev zlig1g-dev libbz2-dev \
    && rm -rf /var/cache/apt \
    && rm -rf /var/lib/apt/lists/*

CMD
CMD 指令用来指定启动容器时默认执行的命令。

支持三种格式:

  • CMD [“executable”, “param1”, “param2”]:相当于执行 executable param1 param2,推荐方式
  • CMD command param2 param2:在默认 shell 中执行,提供给需要交互的应用
  • CMD [“param1”, “param2”]:提供给 ENTRYPOINT 的默认参数

每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。

如果用户启动容器时手动指定了运行的命令(作为 run 命令的参数),则会覆盖掉 CMD 指定的命令。

ADD
添加内容到镜像。格式为 ADD

该命令将复制指定的路径下内容到容器中的路径下。

其中可以是 Dockerfile 所在目录的一个相对路径(文件或目录);也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)可以是镜像内绝对路径,或者相对于工作目录(WORKDIR)的相对路径。

路径支持正则格式,例如

ADD *.c /code/

COPY
复制内容到镜像。格式为 COPY

复制本机的(为 Dockerfile 所在目录的相对路径,文件或目录)下内容到镜像中的。目标路径不存在时,会自动创建。路径同样支持正则格式

COPY 与 ADD 指令功能类似,当使用本地目录作为源目录时,推荐使用 COPY

# 创建镜像

编写完 Dockerfile 之后,可以通过 docker [image] build 命令来创建镜像。基本格式为 docker build [OPTIONS] PATH | URL | -

该命令将读取指定路径下(包括子目录)的 Dockerfile,并将该路径下所有数据作为上下文(Context)发送给 Docker 服务端。Docker 服务器在校验 Dockerfile 格式通过后,逐条执行其中定义的指令,碰到 ADD、COPY、RUN 指令会生成一层新的镜像层。最终如果创建镜像成功,会返回最终镜像的 ID。

如果上下文过大,会导致发送大量数据给服务端,延缓创建过程。因此除非是生成镜像所必须的文件,不然不要放到上下文路径下。如果非上下文路径下的 Dockerfile,可以通过 - f 选项来指定其路径。

要指定生成镜像的标签信息,可以通过 - t 来指定,该选项可以重复使用多次为镜像添加多个名称。

例如,上下文路径为 /tmp/docker_builder/,并且希望生成镜像标签为 builder/first_image:1.0.0,可以这样

$ docker build -t builder/first_image:1.0.0 /tmp/docker_builder/

# 命令选项

docker [image] build 命令支持一系列的选项,可以调整创建镜像过程的行为。

创建镜像的命令选项及说明

  • -add-host list:添加自定义的主机名到 IP 的映射
  • -build-arg list:添加创建时的变量
  • -cache-from strings:使用指定镜像作为缓存源
  • -cgroup-parent string:继承上层 cgroup
  • -compress:使用 gzip 来压缩创建上下文数据
  • -cpu-period int:分配的 CFS 调度器时长
  • -cpu-quota int:CFS 调度器总份额
  • -c,-cpu-share int:CPU 权重
  • -cpuset-cpus string:多 CPU 允许使用的 CPU
  • -cpuset-mems string:多 CPU 允许使用的内存
  • -disable-content-trust:不进行镜像校验,默认为真
  • -f,-file string:Dockerfile 名称
  • -force-rm:总是删除中间过程的容器
  • -iidfile string:将镜像 ID 写入到文件
  • -isolation string:容器的隔离机制
  • -label list:配置镜像的元数据
  • -m,-memory bytes:限制使用的内存量
  • -memory-swap bytes:限制内存和缓存的总量
  • -network string:指定 RUN 命令时的网络模式
  • -no-cache:创建镜像时不使用缓存
  • -platform string:指定平台类型
  • -pull:总是尝试获取镜像的最新版本
  • -q,-quiet:不打印创建过程中的日志信息
  • -rm:创建成功后自动删除中间过程容器,默认为 True
  • -security-opt strings:指定安全相关的选项
  • -shm-size bytes:/dev/shm 的大小
  • -squash:将新创建的多层挤压放入到一层中
  • -stream:持续获取创建的上下文
  • -t,-tag list:指定镜像标签列表
  • -target string:指定创建的目标阶段
  • -ulimit ulimit:指定 ulimit 的配置

# 选择父镜像

大部分情况下,生成新的镜像都需要通过 FROM 指令来指定父镜像。父镜像是生成镜像的基础,会直接影响到所生成镜像的大小和功能。

用户可以选择两种镜像作为父镜像,一种是所谓的基础镜像(baseimage),另外一种是普通的镜像(往往由第三方创建,基于基础镜像)

基础镜像比较特殊,其 Dockerfile 中往往不存在 FROM 指令,或者基于 scratch 镜像(FROM scratch),这意味着其在整个镜像树种处于根的位置。

下面的 Dockerfile 定义了一个简单的基础镜像,将用户提前编译好的二进制可执行文件 binary 复制到镜像中,运行容器时执行 binary 命令

FROM scratch
ADD binary /
CMD ["/binary"]

# 使用.dockerignore 文件

可以通过.dockerignore 文件(每一行添加一条匹配模式)来让 Docker 忽略匹配路径或文件,在创建镜像时不将无关数据发送到服务端。

# .dockerignore 文件中可以定义忽略模式
*/temp*
*/*/temp*
tmp?
~*
Dockerfile
!README.md
  • dockerignore 文件中模式语法支持 Golang 风格的路径正则格式
    • 表示任意多个字符
  • ? 代表单个字符
  • ! 表示不匹配(即不忽略指定的路径或文件)

# 建议

  • 精简镜像用途:尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像
  • 选用合适的基础镜像:容器的核心是应用,选择过大的父镜像(如 Ubuntu 系统镜像)会造成最终生成应用镜像的臃肿
  • 提供注释和维护者信息:Dockerfile 也是一种代码,需要考虑方便后续的扩展和他人使用
  • 正确使用版本号:使用明确的版本号信息,如 1.0,2.0,而非依赖默认的 latest。通过版本号可以避免环境不一致导致的问题
  • 减少镜像层数:如果希望所生成的镜像的层数尽量少,则要尽量合并 RUN、ADD 和 COPY 指令。通常情况下,多个 RUN 指令可以合并为一条 RUN 指令
  • 惬当使用多步骤创建(17.05 + 版本支持):通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,用户也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个 Dockerfile
  • 使用.dockerignore 文件:使用它可以标记在执行 docker build 时忽略的路径和文件,避免发送不必要的数据内容,从而加快整个镜像创建过程
  • 及时删除临时文件和缓存文件:特别是在执行 apt-get 指令后,/var/cache/apt 下会缓存一些安装包
  • 提高生成速度:合理使用 cache,减少内容目录下的文件,或使用.dockerignore 文件指定等
  • 调整合理的指令顺序:在开启 cache 的情况下,内容不变的指令尽量放在前面,这样可以尽量复用
  • 减少外部源的干扰:如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错

# 为镜像添加 SSH 服务

很多时候,系统管理者都习惯通过 SSH 服务来远程登录管理服务器,但 Docker 很多镜像是不带 SSH 服务的,那么用户如何才能管理容器?

下面会介绍如何创建一个带有 SSH 服务的镜像,并详细介绍两种创建容器的方法:基于 docker commit 命令创建和基于 Dockerfile 创建

# 基于 commit 命令创建

Docker 提供了 docker commit 命令,支持用户提交自己对制定容器的修改,并生成新的镜像。命令格式为 docker commit CONTAINER [REPOSITORY [:TAG]]

下面介绍如何用 docker commit 为 web 镜像添加 SSH 服务

  1. 首先获取 training/webapp 镜像,并创建一个容器
[root@sarly ~]# docker pull training/webapp
[root@sarly ~]# docker run -it training/webapp bash
root@68db55da9092:/#
  1. 配置软件源,并使用 apt-get update 命令来更新软件源信息
root@007fa5bc6bc5:/opt/webapp# apt-get install openssh-server

如果默认的官方源速度慢或者有问题,可以替换国内源,参考:https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/

  1. 更新软件包缓存后可以安装 SSH 服务了,选择主流的 openssh-server 作为服务端
root@007fa5bc6bc5:apt-get install openssh-server

如果需要正常启动 SSH 服务,则目录 /var/run/sshd 必须存在。下面手动创建它,并启动 SSH 服务

root@007fa5bc6bc5:/opt/webapp# mkdir -p /var/run/sshd
root@007fa5bc6bc5:/opt/webapp# /usr/sbin/sshd -D &
[2] 509

此时查看容器的 22 端口(SSH 服务默认监听的端口),可见端口已经处于监听状态

root@007fa5bc6bc5:/opt/webapp# netstat -tunlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      505/sshd        
tcp6       0      0 :::22                   :::*                    LISTEN      505/sshd        
[2]-  Exit 255                /usr/sbin/sshd -D

修改 SSH 服务的安全登录配置,取消 pam 登录限制

root@007fa5bc6bc5:/opt/webapp# sed -ri 's/session    required     pam_loginuid.so/# session    required     pam_loginuid.so/g' /etc/pam.d/sshd

在 root 用户目录下创建.ssh 目录,并复制需要登录的公钥信息(一般为本机主机用户目录的.ssh/id_rsa.pub 文件,可由 ssh-keygen -t rsa 命令生成)到 authorized_keys 文件中

root@007fa5bc6bc5: mkdir root/.ssh
root@007fa5bc6bc5: vi /root/.ssh/authorized_keys

创建自动启动 SSH 服务的可执行文件 run.sh,并添加可执行权限

root@007fa5bc6bc5: vi /run.sh
root@007fa5bc6bc5: chmod +x run.sh

run.sh 脚本

#! /usr/bash
/usr/sbin/sshd -D

最后,退出容器

  1. 将所退出的容器用 docker commit 命令保存为一个新的 sshd:web 镜像
[root@sarly ~]# docker commit 007 sshd:web

使用 docker images 查看本地生成的新镜像 sshd:web

[root@sarly ~]# docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
sshd                          web                 82c4561220ac        12 minutes ago      389 MB
......
  1. 启动容器,并添加端口映射 10022 -> 22。其中 10022 是宿主机端口,22 是容器的 SSH 服务监听端口
[root@sarly ~]# docker run -d --name sshweb -p 10022:22 sshd:web /run.sh
0b24f2650b8e2d4ab73605a2a1cd591cd062d989bb1fe003d5a4ff11d02bed45

启动成功后,可以看到容器运行的详细信息

[root@sarly ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                             NAMES
0b24f2650b8e        sshd:web            "/run.sh"           2 seconds ago       Up 1 second         5000/tcp, 0.0.0.0:10022->22/tcp   sshweb

在宿主机或其它主机上,可以通过 SSH 访问 10022 端口来登录容器

[root@sarly ~]# ssh 192.168.1.141 -p 10022

# 编写 Dockerfile

如何使用 Dockerfile 来创建一个 SSH 服务的镜像

  1. 首先创建一个 sshd_web 工作目录
[root@sarly docker]# ls
sshd_web

在其中,创建 Dockerfile 和 run.sh

[root@sarly docker]# cd sshd_web/
[root@sarly sshd_web]# touch Dockerfile run.sh
[root@sarly sshd_web]# ls
Dockerfile  run.sh
  1. 编写 run.sh 和 authorized_keys 文件,脚本文件 run.sh 内容与上面的一样
#! /bin/bash
/usr/sbin/sshd -D

在宿主机上生成 SSH 密钥对,并创建 authorized_keys 文件

[root@sarly sshd_web]# ssh-keygen -t rsa
[root@sarly sshd_web]# cat ~/.ssh/id_rsa.pub > authorized_keys
  1. 编写 Dockerfile
# 设置继承镜像
FROM training/webapp
# 提供一些作者的信息
MAINTAINER docker_user (user@docker.com)
RUN apt-get update
# 安装ssh服务
RUN apt-get install -y openssh-server
RUN mkdir -p /var/run/sshd
RUN mkdir -p /root/.ssh
# 取消pam限制
RUN sed -ri 's/session    required    pam_loginuid.so/#session    required    pam_loginuid.so/g' /etc/pam.d/sshd
# 复制配置文件到相应位置,并赋予脚本可执行权限
ADD authorized_keys /root/.ssh/authorized_keys
ADD run.sh /run.sh
RUN chmod 755 /run.sh
# 开放端口
EXPOSE 22
# 设置自启动命令
CMD ["/run.sh"]
  1. 创建镜像,在 sshd_web 目录侠,使用 docker build 命令来创建镜像
[root@sarly sshd_web]# docker build -t sshd:dockerfile .
  1. 测试镜像,运行容器,使用刚刚创建的镜像来运行一个容器
    直接启动镜像,映射容器的 22 端口到本地的 10122 端口
[root@sarly sshd_web]# docker run -d --name sshweb -p 10022:22 sshd:dockerfile
4c7f45e04c413e5e1475af7a28b68635892619c6f38ede49d05ab047ddea7a71

然后在新主机通过 ssh 连接容器

[root@sarly .ssh]# ssh 192.168.1.141 -p 10022
Welcome to Ubuntu 14.04.2 LTS (GNU/Linux 3.10.0-1127.19.1.el7.x86_64 x86_64)
 * Documentation:  https://help.ubuntu.com/
Last login: Wed Jan 11 09:36:23 2023 from 192.168.1.141
root@4c7f45e04c41:~#

如果之前连接不上 / 失败,这会再连接可能会有问题

[root@sarly sshd_web]# sudo ssh 192.168.1.141 -p 10022
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:3tTWjwwPDU0fHDKm+ei82IXriLChWQX/REryZWUffD0.
Please contact your system administrator.
Add correct host key in /root/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /root/.ssh/known_hosts:6
ECDSA host key for [192.168.1.141]:10022 has changed and you have requested strict checking.
Host key verification failed.

密钥出错,你可以通过提示,打开 /root/.ssh/known_hosts,找到提示的行,删除,然后重新连接即可

# 总结

对于是否需要为 Docker 容器启用 SSH 服务一直存在争议。

  • 一方的观点:Docker 的理念就是一个容器只运行一个服务。因此,如果每个容器都运行一个额外的 SSH 服务,就违背了这个理念。而且认为根本没有从远程主机进入容器进行维护的必要。
  • 另一方的观点:虽然使用 docker exec 命令可以从本地进入容器,但是如果要从其它远程主机进入依然没有更好的解决方案。

这两种说法各有理,其实就是讨论不同的容器场景:作为应用容器,还是作为系统容器。应用容器行为围绕应用生命周期,较为简单,不需要人工的额外干预;而系统容器则需要支持管理员的登录操作,这时候,对 SSH 服务的支持就变得十分必要了。

因此,在 Docker 推出更加高效、安全的方式对系统容器进行远程操作之前,容器的 SSH 服务还是比较重要的,而且它对资源的需求不高,同时安全性可以保障

Edited on Views times

Give me a cup of [coffee]~( ̄▽ ̄)~*

小芳芳 WeChat Pay

WeChat Pay

小芳芳 Alipay

Alipay