Skip to content
60.Docker»40.dind»LV010-docker通信原理.md

LV010-docker通信原理

一、概述

在 Docker 的世界里,docker.sock 是一个绕不开的核心组件。它就像是 Docker 引擎的神经中枢,所有对 Docker 的操作指令,无论是来自命令行、图形界面还是 CI/CD 工具,几乎都离不开它。

text
Docker 通信流程概览
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Docker CLI    │───▶│   docker.sock    │───▶│ Docker Daemon  │
│                 │    │  (UNIX Socket)   │    │                 │
│ docker ps       │    │                  │    │ 容器管理         │
│ docker run      │    │ /var/run/        │    │ 镜像管理         │
│ docker build    │    │ docker.sock      │    │ 网络管理         │
└─────────────────┘    └──────────────────┘    └─────────────────┘
        │                        │                        │
        │                        │                        ▼
        │                        │              ┌─────────────────┐
        │                        │              │   Containerd    │
        │                        │              │                 │
        │                        │              │ 容器运行时        │
        │                        │              └─────────────────┘
        │                        │                        │
        │                        │                        ▼
        │                        │              ┌─────────────────┐
        │                        │              │      Runc       │
        │                        │              │                 │
        │                        │              │ 底层容器执行      │
        │                        │              └─────────────────┘
        │                        │
        ▼                        ▼
┌─────────────────┐    ┌──────────────────┐
│  第三方工具       │    │   编程语言SDK     │
│                 │    │                  │
│ Portainer       │    │ Python docker    │
│ Watchtower      │    │ Go client        │
│ Jenkins         │    │ Node.js dockerode│
└─────────────────┘    └──────────────────┘

二、Docker 通信原理简介

1. 技术架构

Docker 采用经典的 C/S (Client/Server) 架构。我们日常使用的 docker 命令实际上是客户端(Client),它通过一个接口与 Docker 守护进程(Daemon)通信,由守护进程来真正执行镜像 构建、容器启停等操作。而 docker.sock 就是这个通信接口中最常用的一种。

Docker Architecture diagram

Docker 通信架构流程图 如下:

text
                    Docker 完整通信架构
    ┌─────────────────────────────────────────────────────────────┐
    │                     User Space                              │
    │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
    │  │ Docker CLI  │  │ Docker SDK  │  │ Third-party Tools   │  │
    │  │             │  │ (Python/Go) │  │ (Portainer/Jenkins) │  │
    │  └─────────────┘  └─────────────┘  └─────────────────────┘  │
    └─────────────────────────────────────────────────────────────┘
                │              │                      │
                │              │                      │
                └──────────────┼──────────────────────┘
                               │ UNIX Socket 通信

    ┌─────────────────────────────────────────────────────────────┐
    │                   Kernel Space                              │
    │              ┌─────────────────────────┐                    │
    │              │  /var/run/docker.sock   │                    │
    │              │  (UNIX Domain Socket)   │                    │
    │              └─────────────────────────┘                    │
    └─────────────────────────────────────────────────────────────┘

                               │ 内核级别转发

    ┌─────────────────────────────────────────────────────────────┐
    │                   Docker Daemon                             │
    │  ┌─────────────────┐                                        │
    │  │ Docker API      │ ◄── 接收和处理 REST API 请求             │
    │  │ Server          │                                        │
    │  └─────────────────┘                                        │
    │           │                                                 │
    │           ▼                                                 │
    │  ┌─────────────────┐                                        │
    │  │ Containerd      │ ◄── 管理容器生命周期和镜像                 │
    │  │                 │                                        │
    │  └─────────────────┘                                        │
    │           │                                                 │
    │           ▼                                                 │
    │  ┌─────────────────┐                                        │
    │  │ Runc            │ ◄── 创建和运行容器的底层工具               │
    │  │                 │                                        │
    │  └─────────────────┘                                        │
    └─────────────────────────────────────────────────────────────┘


                    ┌─────────────────┐
                    │   Linux Kernel  │
                    │   (Namespaces,  │
                    │   Cgroups, etc) │
                    └─────────────────┘

2. 核心技术对比

通信方式 优点 缺点 典型场景
UNIX Socket ( docker.sock ) 性能高、开销小、默认配置、更安全(仅本地) 只能在宿主机本地访问 本地开发、容器内访问宿主机 Docker
TCP Socket 支持远程访问、跨主机管理 配置复杂、网络延迟、 安全风险高 (需 TLS 加密) 远程管理 Docker 集群、CI/CD 分布式构建
SSH 利用现有 SSH 认证,相对安全 性能开销比 TCP 略高 临时的、安全的远程单机管理

UNIX Domain Socket (UDS) 是一种在同一台操作系统上的两个进程之间进行数据交换的机制。与通过网络接口进行通信的 TCP/IP 套接字不同,UDS 使用文件系统作为其地址空间。

Tips:UDS vs. TCP Loopback

  • 性能: UDS 绕过了网络协议栈(TCP/IP),不需要进行 TCP 握手、校验和计算等操作,因此数据传输效率更高,延迟更低。

  • 资源: UDS 不占用网络端口,避免了端口冲突的问题。

  • 安全: UDS 的访问权限直接由文件系统的权限控制(user, group, other),比网络端口更容易管理。

docker.sock 正是利用了 UDS 的这些优点,为本地 Docker 操作提供了最高效、最安全的默认通信方式。

三、docker.sock的应用

1. 容器内访问宿主机 Docker

容器内访问宿主机 Docker 是 docker.sock 最经典的应用场景,常用于需要动态管理其他容器的"元容器"(Meta Container)。

容器访问宿主机 Docker 流程
┌─────────────────┐    ┌──────────────────┐     ┌─────────────────┐
│   容器内部       │    │   docker.sock    │     │   宿主机Docker   │
│                 │    │   (挂载卷)        │     │                 │
│ docker ps       │───▶│ /var/run/        │───▶│ 返回宿主机        │
│ docker run      │    │ docker.sock      │     │ 容器列表         │
│ docker build    │    │                  │     │                 │
└─────────────────┘    └──────────────────┘     └─────────────────┘
shell
# 运行一个新容器,并将宿主机的 docker.sock 挂载进去
docker run -it --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  ubuntu:latest

# 在容器内,需要安装 docker-cli 才能与守护进程通信
# apt-get update && apt-get install -y docker-ce-cli

# 安装后,在容器内执行 docker ps,看到的是宿主机上的所有容器
# docker ps

2. 使用 Docker SDK

各种编程语言的 SDK 使得以编程方式与 Docker 交互成为可能,例如Python。

python
import docker

# 默认连接到 /var/run/docker.sock
client = docker.from_env()

# 列出宿主机上的所有容器
print("Listing all containers on the host:")
for container in client.containers.list(all=True):
    print(f"  - ID: {container.short_id}, Name: {container.name}, Status: {container.status}")

# 运行一个临时容器并获取其输出
print("\\nRunning a temporary container...")
logs = client.containers.run("alpine", "echo 'Hello from SDK!'")
print(f"Container output: {logs.decode('utf-8').strip()}")

3. 直接与API交互

甚至可以使用 curl 这样的工具,通过 docker.sock 直接向 Docker API 发送 HTTP 请求:

shell
# 获取 Docker 版本信息 (等同于 docker version)
curl --unix-socket /var/run/docker.sock http://localhost/version

# 列出所有容器 (等同于 docker ps -a)
curl --unix-socket /var/run/docker.sock http://localhost/containers/json?all=true | jq .

四、安全风险

docker.sock 挂载到容器中,常被称为 "Docker out of Docker",这带来了极大的安全隐患。

text
docker.sock 安全风险链
┌─────────────────┐    ┌──────────────────┐     ┌─────────────────┐
│   恶意容器       │    │   docker.sock     │     │   宿主机系统     │
│                 │    │   (挂载访问)       │     │                │
│ 获得sock权限     │───▶│ 执行任意docker     │───▶│ 完全控制宿主机    │
│                 │    │ 命令              │     │                 │
└─────────────────┘    └──────────────────┘     └─────────────────┘
        │                        │                        │
        ▼                        ▼                        ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ 攻击手段         │    │ 中间步骤           │    │ 最终后果         │
│                 │    │                  │    │                 │
│ • 特权容器       │    │ • 挂载根目录       │    │ • 读写任意文件    │
│ • 网络劫持       │    │ • 绕过隔离         │    │ • 植入后门       │
│ • 资源滥用       │    │ • 提权操作         │    │ • 数据泄露       │
└─────────────────┘    └──────────────────┘    └─────────────────┘

1. 有多危险?为什么说拿到 docker.sock 就等于 root

一个容器被挂载了 -v /var/run/docker.sock:/var/run/docker.sock。容器内的进程可以通过 docker.sock 与宿主机的 Docker Daemon 通信。这意味着,它拥有了在宿主机上执行任何 docker 命令的权力。攻击者可以轻易地:

  • 启动一个特权容器: docker run --privileged
  • 挂载宿主机根目录: docker run -v /:/host_root
  • 修改宿主机文件: 在挂载了根目录的容器内,可以修改 /host_root 下的任何文件,例如写入 SSH 公钥、添加 cron job 等,从而实现 "容器逃逸",完全控制宿主机。

2. 如何安全地授权用户使用 Docker?

限制用户权限,例如普通用户执行 docker ps 时提示 permission denied

永远不要为了方便而直接 chmod 777 /var/run/docker.sock。正确的做法是使用 docker 用户组。

text
Docker 用户权限管理流程
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   普通用户       │    │   docker 用户组   │    │   docker.sock   │
│                 │    │                  │    │                 │
│ 无法访问Docker   │───▶│ 加入docker组      │───▶│ 获得访问权限     │
│                 │    │                  │    │                 │
└─────────────────┘    └──────────────────┘    └─────────────────┘
        │                        │                        │
        ▼                        ▼                        ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ usermod -aG     │    │ 权限验证          │    │ 正常使用Docker    │
│ docker $USER    │    │ 660 root:docker  │    │ 命令             │
└─────────────────┘    └──────────────────┘    └─────────────────┘

可以使用下面的命令:

shell
# 1. 检查 docker 组是否存在,不存在则创建
sudo groupadd --force docker

# 2. 将当前用户添加到 docker 组
sudo usermod -aG docker $USER

# 3. 验证 docker.sock 的权限是否为 root:docker 和 660
sudo chown root:docker /var/run/docker.sock
sudo chmod 660 /var/run/docker.sock

# 4. 重新登录或使用 newgrp docker 命令使组成员身份生效
newgrp docker
docker ps # 应该可以成功执行

3. 有没有比挂载 docker.sock 更安全的替代方案?

需要在 CI/CD 中构建镜像,但不想承担 docker.sock 的风险,可以这样:

text
Docker 安全替代方案对比
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ Docker-in-Docker│    │   Rootless Mode  │    │     Podman      │
│                 │    │                  │    │                 │
│ • 完全隔离       │    │ • 非root运行      │    │ • 无daemon       │
│ • 资源开销大      │    │ • 功能受限        │    │ • 兼容性好       │
└─────────────────┘    └──────────────────┘    └─────────────────┘
        │                        │                        │
        ▼                        ▼                        ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│ socket-proxy    │    │   远程API访问     │    │   本地替代        │
│                 │    │                  │    │                 │
│ • 权限控制       │    │ • 网络隔离         │    │ • 直接替换       │
│ • 代理过滤       │    │ • TLS加密         │    │ • 更安全         │
└─────────────────┘    └──────────────────┘    └─────────────────┘

(1)Docker-in-Docker (DinD): 在一个特权容器(--privileged)内运行一个全新的、独立的 Docker Daemon。它与宿主机 Docker 完全隔离,但性能开销大,且特权容器本身也是一个安全风险点。

shell
docker run --privileged -d --name dind docker:dind
docker run --rm --link dind:docker docker:cli docker ps

(2)Rootless Mode: 在非 root 用户下运行 Docker 守护进程。这是最安全的方案之一,但功能上有一些限制。

shell
# 安装 rootless Docker
curl -fsSL https://get.docker.com/rootless | sh
export PATH=/home/$USER/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

(3)Podman: 一个无守护进程的容器引擎,其 CLI 与 Docker 兼容。每个用户都在自己的命名空间中管理容器,天然隔离。

shell
# Podman 无需 daemon,更安全
podman run --rm -it alpine sh
podman build -t myapp .

(4)第三方代理服务: 部署一个安全的中间代理,它暴露有限的、经过授权的 API 端点,而不是整个 Docker API。例如 docker-socket-proxy

yaml
version: '3'
services:
  socket-proxy:
    image: tecnativa/docker-socket-proxy
    environment:
      CONTAINERS: 1
      IMAGES: 1
      AUTH: 1
      NETWORKS: 0
      VOLUMES: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - "2375:2375"

docker-socket-proxy 是一个轻量级的安全代理,它位于客户端和 docker.sock 之间,通过白名单机制精确控制允许执行的 API 请求。

参考资料:

Docker 通信核心:docker.sock 完全指南-CSDN博客

莫道桑榆晚 为霞尚满天.