# 使用 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 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建一个容器,并启动该容器
- 分配一个文件系统给容器,并在只读的镜像层外面挂载一层可读写层
- 从宿主机配置的网桥接口中桥接一个虚拟接口到容器中
- 从网桥的地址池配置一个 IP 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被自动终止
可以通过 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 |
这个命令稍微有点复杂,具体分析如下:
- 首先利用 ubuntu 镜像创建一个容器 worker
- 使用–volumes-from dbdata 参数来让 woker 容器挂载 dbdata 容器的数据卷
- 使用 - v $(pwd):/backup 参数来挂载当前目录到 worker 容器的 /backup 目录
- 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
任何 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 ADD_VERSION=1.0.0 | |
ENV APP_HOME=/usr/local/app | |
ENV PATH $PATH:/usr/local/bin |
指令指定的环境变量在运行时可以被覆盖掉,如 docker run --env
当一条 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 ["/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
该命令将复制指定的
其中
路径支持正则格式,例如
ADD *.c /code/ |
COPY
复制内容到镜像。格式为 COPY
复制本机的
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 服务
- 首先获取 training/webapp 镜像,并创建一个容器
[root@sarly ~]# docker pull training/webapp | |
[root@sarly ~]# docker run -it training/webapp bash | |
root@68db55da9092:/# |
- 配置软件源,并使用 apt-get update 命令来更新软件源信息
root@007fa5bc6bc5:/opt/webapp# apt-get install openssh-server |
如果默认的官方源速度慢或者有问题,可以替换国内源,参考:https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/
- 更新软件包缓存后可以安装 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 |
最后,退出容器
- 将所退出的容器用 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 | |
...... |
- 启动容器,并添加端口映射 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 服务的镜像
- 首先创建一个 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 |
- 编写 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 |
- 编写 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"] |
- 创建镜像,在 sshd_web 目录侠,使用 docker build 命令来创建镜像
[root@sarly sshd_web]# docker build -t sshd:dockerfile . |
- 测试镜像,运行容器,使用刚刚创建的镜像来运行一个容器
直接启动镜像,映射容器的 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 服务还是比较重要的,而且它对资源的需求不高,同时安全性可以保障