Docker 疑难问题
docker
安装
开发测试环境
1 | curl -fsSL https://get.docker.com | bash -s docker |
links:
更新
查看系统版本,根据官方 文档 更新
1 | cat /etc/os-release |
QA: Error response from daemon: Unknown runtime specified docker-runc
当从不兼容的版本升级 docker 并且升级后无法启动 docker 容器时会出现这种情况,执行一下命令,然后重启 docker。
1 | grep -rl 'docker-runc' /var/lib/docker/containers/ | xargs sed -i 's/docker-runc/runc/g' |
dockerd
1 | ps -ef | grep dockerd |
/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
是 Docker daemon 的启动命令,其中:
-
/usr/bin/dockerd 是 Docker daemon 的路径。
-
-H fd:// 是 Docker daemon 的监听地址,fd:// 是一个特殊的 Unix socket,用于 systemd socket activation。
-
–containerd=/run/containerd/containerd.sock 是指定 Docker daemon 使用的 containerd 的 socket 文件路径。
Docker daemon 使用 containerd 作为其默认的容器运行时,主要是因为 containerd 是一个轻量级的、高性能的开源容器运行时,它可以管理完整的容器生命周期,包括镜像管理、容器执行、任务管理、网络管理等。
除了 containerd,Docker 还支持其他的容器运行时,例如 runc、gVisor、Kata Containers 等。可以通过修改 Docker daemon 的配置文件来更改默认的容器运行时。
cgroup driver
Docker 的 cgroup driver 主要有两种类型:systemd 和 cgroupfs。
-
systemd: 当 Docker 使用 systemd 作为其 cgroup driver 时,它将会使用 systemd 来创建和管理 cgroup。这意味着 Docker 将会与系统的其他部分(也使用 systemd 的部分)共享同一套 cgroup,从而实现更好的资源管理。
-
cgroupfs: 当 Docker 使用 cgroupfs 作为其 cgroup driver 时,它将会直接与 cgroup 文件系统交互来创建和管理 cgroup。这意味着 Docker 将会有自己独立的一套 cgroup,与系统的其他部分(使用 systemd 的部分)分开。
总的来说,systemd 和 cgroupfs 的主要区别在于它们与系统的其他部分的交互方式不同。systemd 更倾向于与系统的其他部分共享资源,而 cgroupfs 则更倾向于独立管理资源。
要将 Docker 的 cgroup driver 从 cgroupfs 迁移到 systemd,需要在 Docker 的配置文件中进行更改。以下是步骤:
-
打开 Docker 的配置文件,通常位于 /etc/docker/daemon.json。如果文件不存在,需要创建它。
1 | nano /etc/docker/daemon.json |
-
在配置文件中添加或修改 "exec-opts",将其设置为 "native.cgroupdriver=systemd"。
1 | { |
-
保存并关闭文件,然后重启 Docker 服务。
1 | systemctl restart docker |
迁移到 systemd 的好处主要有:
-
更好的资源管理:systemd 可以与系统的其他部分共享 cgroup,从而实现更好的资源管理。
-
更好的兼容性:一些 Kubernetes 工具(如 kubeadm)要求 Docker 使用 systemd 作为其 cgroup driver,以确保与 Kubernetes 的兼容性。
-
更好的稳定性:systemd 作为 cgroup driver 的稳定性通常比 cgroupfs 更好。
links:
command
docker daemon service
1 | service docker start/stop/restart/status |
volume
1 | docker volume ls |
docker QA
以下为 Docker 中各种疑难问题及解答
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/images/json: dial unix /var/run/docker.sock: connect: permission denied
docker daemon 配置
docker 运行配置主要通过文件 daemon.json 配置。所在路径在 /etc/docker/daemon.json
。
1 | { |
links:
docker context
docker context 用于本地方便地访问远程主机的 docker 服务。在不需要手动通过 ssh 连接远程服务器时比较有帮助。
添加 context
1 | docker context create <context-name> --docker "host=ssh://format" |
这里 format 可使用一下格式:
-
username@host-ip[:port]
-
name,指在~/.ssh/config 中提供的 ssh 连接配置名称。
示例:
1 | $ cat ~/.ssh/config |
常用命令
下面列出了常用的 docker context 命令和示例。
1 | $ docker --context my-remote-docker-machine images -q |
1 | $ docker context use my-remote-docker-machine |
1 | $ docker context use default # back to default |
使用 docker 上下文可能有助于避免手动将 SSH 连接到远程服务器。但是,当涉及到在本地使用远程 docker 构建映像时,需要考虑将上载 / 下载多少 docker 上下文。
reference
docker run permission
–cap-add
通常用于开发用的镜像需要设置 cli 参数给予相应的容器权限。
-
–cap-add=NET_RAW: 允许容器进程使用原始套接字,直接访问网络层,一般进行网络嗅探等网络底层操作的容器进程需要。
-
–cap-add=SYS_PTRACE: 允许容器进程使用 ptrace 系统调用。 ptrace 允许一个进程监视和控制另一个进程的执行,包括读取和修改其内存和寄存器状态。这个选项通常用于需要进行调试或其他进程控制操作的容器。
-
–cap-add=NET_ADMIN: 允许容器进程使用网络管理功能。该选项需要对网络进行配置或管理的容器,例如配置网络接口、设置路由或进行网络诊断等操作。
–security-opt
-
–security-opt seccomp=unconfined: 允许设置容器的 seccomp 配置。seccomp 是一种 Linux 内核安全模块,它可以限制进程可以执行的系统调用。该选项禁用 seccomp 配置,从而允许容器进程执行所有系统调用,通常用于需要执行一些不受限制的系统调用的容器,但是这也会降低容器的安全性。
–privileged
-
它允许容器进程拥有主机上的所有特权。这个选项通常用于需要访问主机上的特权设备或执行一些需要特权的操作的容器。使用这个选项会增加容器的安全风险,因为容器进程可以访问主机
–gpus
允许访问主机 gpu 资源
-
[num]: gpu 数量
-
all: 所有 gpu
[!TIP]
如果需要支持 Nvidia Cuda,则需要额外安装支持的 Docker Runtime
1
2 apt update && apt install -y nvidia-docker2 \
&& nvidia-ctk runtime configure
验证 gpu 是否可用
1 | # https://catalog.ngc.nvidia.com/orgs/nvidia/teams/k8s/containers/cuda-sample |
QA
-
failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error running hook #0: error running hook: exit status 1, stdout: , stderr: nvidia-container-cli: requirement error: unsatisfied condition: cuda>=11.7: unknown.
镜像版本要求 cuda 需要大于 11.7:
-
升级驱动满足镜像要求
-
采用符合要求的镜像
容器中执行设置错误退出
1 | # set -e 配置遇到错误即退出,并设置退出错误码255 |
dockerignore
.dockerignore 主要用于在 docker 构建镜像时忽略不需要打包进去的过滤方式,类似于 .gitignore 文件。该文件位于构建上下文的根目录。
匹配规则如下:
pattern | description |
---|---|
/temp | 匹配根路径下一级目录下所有以 temp 开头的文件或目录 |
//temp* | 匹配根路径下两级目录下所有以 temp 开头的文件或目录 |
temp? | 匹配根路径下以 temp 开头,任意一个字符结尾的文件或目录 |
**/*.go | 匹配所有路径下以 .go 结尾的文件或目录,即递归搜索所有路径 |
*.md !README.md |
匹配根路径下所有以 .md 结尾的文件或目录,但 README.md 除外 |
[!CAUTION]
如果存在冲突的行或重叠包含关系,那么以后面的匹配规则为准。
reference
dockerfile
links:
RUN
使用 EOF 避免使用冗余的换行符运行,dockerfile:1.4 声明版本,或者直接指定版本 1
1 | # syntax=docker/dockerfile:1 |
links:
ADD & COPY
在编写 Dockerfile 时经常需要拷贝文件或文件夹的操作,这时就需要用到 ADD 和 COPY 指令。
-
拷贝单个文件到指定目录
1 | #拷贝当前目录下的test.jar到/usr/bin目录下 |
-
拷贝特定的多个文件到指定目录
ADD 指令支持通配符,常用的示例如下:
1 | #拷贝当前目录下的bin文件夹的所有sh文件到/usr/bin目录下 |
-
拷贝文件夹到指定的目录
ADD 宿主机文件夹的全路径 docker 容器下的文件夹路径 + 新文件夹名
1 | #拷贝当前目录下的config文件夹到/usr/bin目录下 |
SHELL
用于覆盖默认的 Shell
1 | # syntax=docker/dockerfile:1 |
links:
dockerfile 优化
选择基础镜像
优化指令顺序
把 WORKDIR/ENV 等命令放在前面,COPY/ADD 等命令放在后面 [^2]。
合并构建指令
清理中间缓存
用户数据缓存:
-
~/.cache 等用户缓存。
-
安装包临时缓存。
开发镜像和运行时镜像分离
runtime 镜像仅包含基本运行环境,开发镜像包含所有的开发依赖。开发镜像尽可能以 runtime 镜像为基础构建。
多阶段构建
使用 buildkit
1 | # 构建多平台镜像 |
使用构建缓存 RUN --mount
[!CAUTION]
buildkit 会使用 docker 构建缓存,也就是 Build Cache,需要定期清理。查看缓存:docker system df
清理方式:echo yes | docker builder prune -a
二进制工具安装
从现有镜像中安装二进制文件
1 | FROM golang:1.17 |
reference
docker inspect
docker inspect 输出可以通过 --format
, 使用 {{.}}
检索某个字段结果
1 | # 输出原始数据, 这里`.`表示所有结果 |
log
Docker 默认将日志存储到一个日志文件中。要检查日志文件路径,请运行命令
1 | docker inspect --format='{{.LogPath}}' containername |
要查看实时日志,可以在以下命令中运行
1 | tail -f `docker inspect --format='{{.LogPath}}' containername` |
注意:
/var/lib/docker/containers/f844a7b45ca5a9589ffaa1a5bd8dea0f4e79f0e2ff639c1d010d96afb4b53334/f844a7b45ca5a9589ffaa1a5bd8dea0f4e79f0e2ff639c1d010d96afb4b53334-json.log
仅当 docker 生成日志(如果没有日志)时,才会创建此日志文件,否则该文件将不存在。如果我们运行命令 docker logs containername
,它类似于某个时候,但什么也不返回。在这种情况下,该文件将不可用。
-
重定向
1 | docker logs -f <yourContainer> &> your.log & |
说明:
-
-f(即–follow):写入所有现有日志,并继续(跟随)记录接下来出现的所有内容。
-
&> 重定向标准输出和标准错误。
-
可能想在后台运行该方法,因此 &。
-
可以通过以下方式将输出和 stderr 分开:(> output.log 2> error.log 而不是使用 &>)。
docker port
porting 映射
默认情况下,宿主机是无法访问容器内部网络的,但是可以使用端口映射来解决这个问题。主要通过 docker run 跟 -P(大写) 或 -p(小写)参数来实现。docker run -P 会把容器中监听的端口随机绑定到宿主机的可用端口上:
1 | docker run -p <format> image |
<format>
指定格式如下
-
ip:hostport:containerport #指定 ip、指定宿主机 port、指定容器 port
-
ip::containerport #指定 ip、未指定宿主机 port(随机)、指定容器 port
-
hostport:containerport #未指定 ip、指定宿主机 port、指定容器 port
容器互联
容器互联通过 --link 配置链接实现单独隔离容器的互联。
以下以 nginx + php-fpm 示例:
1 | # 这里分别有一个 php-fpm 和 nginx 镜像。 |
参考
dumb-init
这里介绍了一种容器初始化系统 dumb-init
。
主要关注于解决两个问题。
-
作为
PID
为 1 容器进程的信号处理。 -
孤儿僵尸进程回收。
为什么使用 dumb-init
使用时需注意参考
install
通过系统命令安装。
1 | yum/apt/apk install -y dumb-init |
通过 pip 安装管理器。
1 | pip install dumb-init |
通过 github 安装
1 | RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 |
参考
docker template
本文主要搜集了标准的 docker 镜像的标准模板。
python
python 官方标准模板 包括 python3 的基础镜像。
版本分布
版本分布 bullseye/buster/alpine:
-
slim 最小安装,不包含 gdb 调试脚本加载。
-
非 slim,包含 gdb 调试脚本(建议使用)。
-
alpine 非 glibc 的最小镜像模板。
参考
mysql
这里简单介绍 docker 启动配置 mysql 服务方式。
1 | # 安装时映射到容器中的3306即可 |
docker-compose
docker-compose 是一个多容器编排工具,常用于开发配置.
GPU support
1 | version: '3' |
healthcheck
CMD 和 CMD-SHELL 是 Docker Compose 文件中用于定义容器健康检查的两种不同的命令类型。
1 | version: "3.8" |
-
CMD 命令类型用于执行指定的命令,它将命令及其参数作为一个数组传递。在给定的示例中,CMD 命令执行的是
curl -f http://minio:9000/minio/health/live
命令,用于检查 MinIO 服务的健康状态。 -
CMD-SHELL 命令类型用于执行指定的命令字符串。在给定的示例中,CMD-SHELL 命令执行的是
curl -f http://tritonserver:8000/v2/health/ready
命令,同样用于检查 Triton Server 服务的健康状态。
因此,CMD 和 CMD-SHELL 的区别在于命令的传递方式,CMD 使用数组形式传递命令及其参数,而 CMD-SHELL 使用字符串形式传递命令。
restart
restart 策略用于控制容器在退出后的行为。
-
on-failure 是指在容器退出时,当其退出状态码不为 0(即失败)时,自动重启该容器。具体来说,它会重启那些在容器退出时返回非 0 状态码的容器。如果容器在退出时返回状态码 0,则不会自动重启该容器。
-
no:容器退出时不重启容器;
-
always:容器退出时总是重启容器;
-
unless-stopped:容器退出时重启容器,除非容器被手动停止。
volume
1 | version: '3' |
config
docker-compose config 命令用于验证并查看最终的 docker-compose 文件。当有多个 docker-compose 文件时,可以使用 -f 参数来指定多个文件。这些文件将按照它们在命令行中出现的顺序进行合并。如果在多个文件中定义了相同的服务,那么后面的定义将覆盖前面的定义。
合并规则如下:
-
对于字典类型的字段(例如 environment,labels),它们将被深度合并。如果同一个键在多个文件中定义,那么后面的值将覆盖前面的值。
-
对于列表类型的字段(例如 ports,volumes),它们将被扩展。也就是说,所有文件中的值都将被包含在最终的列表中。
-
对于其他类型的字段,例如 image,command,如果在多个文件中定义,那么后面的值将覆盖前面的值。
例如,如果有两个 docker-compose 文件,docker-compose.yml 和 docker-compose.override.yml,可以使用以下命令来查看最终的配置:
1 | docker-compose -f docker-compose.yml -f docker-compose.override.yml config |
docker api
Docker Engine 有一个 RESTful API。可以在这里找到完整的 API 文档 https://docs.Docker.com/Engine/API/v1.40 / 这意味着由于多克引擎有一个 RESTful HTTP/S API,我们可以连接到一个远程多克引擎,甚至可以使用 curl 或 wget 命令远程运行命令,就像我们可以对任何其他基于 HTTP/S 的 API 所做的那样。
python client
python 通过包 docker 提供 api 客户端操作工具,pip install docker
1 | import docker |
links:
docker swarm
Docker Swarm 是 Docker 的原生集群和编排工具,用于创建和管理 Docker 节点集群和在集群中部署服务。
links:
swarm 管理
Step 1, 初始化 Swarm 集群:首先,需要在一个节点上初始化 Swarm 集群。这个节点将成为集群的管理节点。使用以下命令初始化 Swarm:
1 | # 其中<MANAGER-IP>是管理节点的IP地址。 |
1 | docker swarm init --advertise-addr 10.16.31.3 |
Step 2, 添加工作节点:初始化 Swarm 后,会得到一个命令和 token,其他节点可以使用这个命令和 token 加入 Swarm 集群。命令如下:
1 | docker swarm join --token <TOKEN> <MANAGER-IP>:2377 |
Step 3, 从 Swarm 集群中退出
1 | # 在工作节点上执行:如果想让一个工作节点从Swarm集群中退出,可以在该节点上运行以下命令 |
[!CAUTION]
请注意,如果 Swarm 集群只有一个管理节点,不应该让它退出,因为这将导致失去管理 Swarm 集群的能力。
links:
节点管理
Docker Swarm 中的节点管理主要涉及到添加节点、查看节点状态、更新节点和删除节点等操作。
添加节点:在初始化 Swarm 集群后,会得到一个命令和 token,其他节点可以使用这个命令和 token 加入 Swarm 集群。命令如下:
1 | docker swarm join --token <TOKEN> <MANAGER-IP>:2377 |
节点状态:可以使用以下命令查看 Swarm 集群中所有节点的状态:
1 | docker node ls |
更新节点:可以使用以下命令更新 Swarm 集群中的节点:
1 | docker node update <NODE-ID> |
删除节点:可以使用以下命令从 Swarm 集群中删除节点:
1 | # 首相设置节点不再被调度 |
label 管理:
1 | # 给节点添加label |
links:
服务管理
docker stack 和 docker service 都是 Docker Swarm 模式下的命令,它们用于管理和操作服务,但是它们的使用场景和功能有所不同。
-
docker stack:这个命令主要用于管理和操作整个应用栈。一个应用栈可以包含多个服务,这些服务可以通过同一个 docker-compose.yml 文件定义。可以使用 docker stack deploy 命令来部署整个应用栈,或者使用 docker stack rm 来移除整个应用栈。
-
docker service:这个命令主要用于管理和操作单个服务。可以使用 docker service create 命令来创建一个新的服务,或者使用 docker service update 来更新一个现有的服务。此外,还可以使用 docker service scale 来调整服务的副本数量。
总的来说,docker stack 命令提供了一个更高级别的抽象,它允许一次性管理和操作整个应用栈,而 docker service 命令则提供了更细粒度的控制,它允许管理和操作单个服务。
1 | # 部署服务 |
GPU
将 docker runtime 默认位置为 nvidia.
links:
docker-compose
[!CAUTION]
docker-compose 中 depends_on,restart,deploy.resources.reservations 等资源不可用,需要单独配置.
UI
Docker Swarm 的管理和部署可以通过各种 UI 工具进行,以下是一些常见的选项:
-
Portainer:Portainer 是一个轻量级的管理 UI,可以用来管理 Docker、Swarm、Kubernetes 等。它提供了一个直观的界面来管理 Docker Swarm 集群,包括服务、容器、网络和卷。
-
SwarmPit:SwarmPit 提供了一个简单而强大的界面来管理 Docker Swarm 集群。它支持服务的自动扩展、容器日志、服务发现等功能。
-
Rancher:Rancher 是一个开源的容器管理平台,它支持 Docker Swarm、Kubernetes 等。Rancher 提供了一个全面的界面来管理和部署服务。
以上工具都可以帮助更方便地管理和部署 Docker Swarm 集群。可以根据需求选择合适的工具。
swarmpit
SwarmPit:Lightweight mobile-friendly Docker Swarm management UI
1 | docker run -it --rm \ |
portainer
1 | docker volume create portainer_data |
links:
swarm mesh
swarm 默认使用的是 IPVS 提供的四层模型请求负载均衡消费模式。即和默认非 swarm 模式下单独 docker 使用同一一个网络模型.
当有其它需求实时,推荐使用 traefik 等开源方案.
links:
docker proxy
在容器环境中配置代理环境变量,配置示例
-
HTTP_PROXY
-
http_proxy
-
HTTPS_PROXY
-
https_proxy
-
NO_PROXY
-
no_proxy
-
ALL_PROXY
-
all_proxy
links: