37 - Docker 与容器技术

容器技术是现代软件开发和运维的基石。它利用 Linux 内核的 namespaces、cgroups、overlayfs 和 seccomp 等原语,在不需要完整虚拟机的前提下实现进程级隔离。本章将从容器底层原理讲起,深入 Docker 和 Podman 的使用,覆盖镜像构建、网络、存储、安全配置,以及 Buildah、Skopeo、Distrobox、systemd-nspawn 等生态工具。


37.1 容器技术概述

37.1.1 容器 vs 虚拟机

graph TD
 subgraph vm["虚拟机"]
 VA["App A + Bins/Libs"] --- VB["App B + Bins/Libs"] --- VC["App C + Bins/Libs"]
 VA --> VGA["Guest OS"]
 VB --> VGB["Guest OS"]
 VC --> VGC["Guest OS"]
 VGA --> HV["Hypervisor"]
 VGB --> HV
 VGC --> HV
 HV --> VHO["Host OS"]
 VHO --> VHW["Hardware"]
 end
 subgraph ct["容器"]
 CA["App A + Bins/Libs"] --- CB["App B + Bins/Libs"] --- CC["App C + Bins/Libs"]
 CA --> CR["Container Runtime"]
 CB --> CR
 CC --> CR
 CR --> CK["Host OS Kernel"]
 CK --> CHW["Host Hardware"]
 end
 style vm fill:#ffecb3,stroke:#333
 style ct fill:#c8e6c9,stroke:#333
 style HV fill:#ffcdd2,stroke:#333
 style CR fill:#e1f5fe,stroke:#333
特性虚拟机容器
隔离级别硬件级进程级
启动时间秒~分钟毫秒~秒
镜像大小GB 级MB 级
资源开销
密度低(数十个/主机)高(数百~数千/主机)
内核独立内核共享宿主内核
安全隔离中等
适用场景强隔离、异构 OS微服务、CI/CD

37.2 容器底层原理

graph TD
 CONTAINER["容器进程"]
 CONTAINER --> NS["Namespaces<br/>PID / Network / Mount<br/>UTS / IPC / User"]
 CONTAINER --> CG["Cgroups<br/>CPU 限制 / 内存限制<br/>I/O 限制 / PID 数限制"]
 CONTAINER --> OFS["Overlay FS<br/>镜像层 / 容器层 / 合并视图"]
 CONTAINER --> SEC["安全机制<br/>seccomp / AppArmor / Caps drop"]
 style CONTAINER fill:#e1f5fe,stroke:#333,stroke-width:2px
 style NS fill:#c8e6c9,stroke:#333
 style CG fill:#fff9c4,stroke:#333
 style OFS fill:#f3e5f5,stroke:#333
 style SEC fill:#ffcdd2,stroke:#333
# 手动创建"容器"(理解原理)
 
# 1. 创建 rootfs
mkdir -p /tmp/container/rootfs
# 使用 pacstrap 创建最小根文件系统
sudo pacstrap -c /tmp/container/rootfs base
 
# 2. 使用 unshare 创建命名空间并 chroot
sudo unshare --pid --net --mount --uts --ipc --fork \
 chroot /tmp/container/rootfs /bin/bash
 
# 在"容器"内
mount -t proc proc /proc
mount -t sysfs sysfs /sys
hostname my-container
ps aux # 只能看到自己的进程
ip addr # 只有 lo 接口
exit
 
# 3. 使用 cgroups 限制资源
sudo mkdir /sys/fs/cgroup/my_container
echo 100000 | sudo tee /sys/fs/cgroup/my_container/cpu.max
echo "256M" | sudo tee /sys/fs/cgroup/my_container/memory.max
echo $$ | sudo tee /sys/fs/cgroup/my_container/cgroup.procs

37.3 Docker 在 Arch Linux 上

37.3.1 安装

# 安装 Docker
sudo pacman -S docker docker-compose docker-buildx
 
# 启动 Docker 服务
sudo systemctl enable --now docker.service
 
# 将当前用户加入 docker 组(无需 sudo 使用 docker)
sudo usermod -aG docker $USER
# 需要重新登录或使用 newgrp docker
 
# 验证安装
docker version
docker info
 
# 运行测试容器
docker run --rm hello-world

37.3.2 基本操作

# === 镜像操作 ===
docker pull alpine:latest # 拉取镜像
docker pull archlinux:latest # 拉取 Arch Linux 镜像
docker images # 列出本地镜像
docker image ls # 同上
docker image rm alpine:latest # 删除镜像
docker image prune # 清理悬空镜像
docker image prune -a # 清理所有未使用镜像
 
# === 容器操作 ===
# 运行容器
docker run -it alpine sh # 交互式运行
docker run -d --name web nginx # 后台运行
docker run --rm alpine echo "hello" # 运行后自动删除
 
# 端口映射
docker run -d -p 8080:80 --name web nginx
# 宿主机 8080 -> 容器 80
 
# 环境变量
docker run -e MY_VAR=hello -e DB_HOST=localhost alpine env
 
# 资源限制
docker run --memory=512m --cpus=1.5 alpine stress-ng --vm 1
 
# 列出容器
docker ps # 运行中的容器
docker ps -a # 所有容器
 
# 容器管理
docker stop web # 停止
docker start web # 启动
docker restart web # 重启
docker rm web # 删除
docker rm -f web # 强制删除运行中的容器
 
# 进入运行中的容器
docker exec -it web bash
docker exec -it web sh
 
# 查看容器日志
docker logs web
docker logs -f web # 跟踪日志
docker logs --tail 100 web # 最后 100 行
 
# 查看容器详情
docker inspect web
docker inspect --format '{{.State.Pid}}' web
 
# 容器文件复制
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
docker cp ./index.html web:/usr/share/nginx/html/
 
# 容器资源使用
docker stats
docker top web
 
# 清理
docker container prune # 清理已停止的容器
docker system prune # 清理所有未使用资源
docker system prune -a --volumes # 深度清理
docker system df # 查看磁盘使用

37.3.3 Dockerfile 编写详解

# === 基础镜像 ===
FROM archlinux:latest
 
# === 元数据 ===
LABEL maintainer="user@example.com"
LABEL version="1.0"
LABEL description="My Arch Linux application"
 
# === 环境变量 ===
ENV APP_HOME=/app
ENV APP_PORT=8080
 
# === 设置工作目录 ===
WORKDIR $APP_HOME
 
# === 安装依赖(充分利用缓存层)===
RUN pacman -Syu --noconfirm && \
 pacman -S --noconfirm \
 python \
 python-pip \
 nginx && \
 pacman -Scc --noconfirm
 
# === 复制依赖文件(利用缓存)===
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
 
# === 复制应用代码 ===
COPY . .
 
# === 创建非 root 用户 ===
RUN useradd -r -s /usr/bin/nologin appuser
USER appuser
 
# === 暴露端口 ===
EXPOSE $APP_PORT
 
# === 健康检查 ===
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
 CMD curl -f http://localhost:$APP_PORT/health || exit 1
 
# === 启动命令 ===
ENTRYPOINT ["python"]
CMD ["app.py"]

常用指令说明:

指令说明
FROM基础镜像
RUN构建时执行命令
COPY复制文件到镜像
ADD复制文件(支持 URL 和自动解压)
WORKDIR设置工作目录
ENV设置环境变量
ARG构建参数(仅构建时可用)
EXPOSE声明端口(文档性质)
USER切换用户
ENTRYPOINT入口命令(不可被 docker run 参数覆盖)
CMD默认参数(可被 docker run 参数覆盖)
VOLUME声明卷挂载点
HEALTHCHECK健康检查
SHELL更改默认 shell
STOPSIGNAL停止信号
# 构建镜像
docker build -t my-app:latest .
docker build -t my-app:v1.0 -f Dockerfile.prod .
 
# 使用构建参数
docker build --build-arg VERSION=1.0 -t my-app .

37.3.4 多阶段构建

# === 阶段 1:构建 ===
FROM archlinux:latest AS builder
 
RUN pacman -Syu --noconfirm && \
 pacman -S --noconfirm gcc make
 
WORKDIR /build
COPY . .
RUN make
 
# === 阶段 2:运行 ===
FROM archlinux:latest
 
RUN pacman -Syu --noconfirm && \
 pacman -S --noconfirm glibc && \
 pacman -Scc --noconfirm
 
WORKDIR /app
COPY --from=builder /build/my_app .
 
RUN useradd -r -s /usr/bin/nologin appuser
USER appuser
 
ENTRYPOINT ["./my_app"]
# Go 应用多阶段构建(生成静态二进制)
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o server .
 
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
# Rust 应用多阶段构建
FROM rust:latest AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs && \
 cargo build --release && rm -rf src
COPY src ./src
RUN cargo build --release
 
FROM archlinux:latest
RUN pacman -Syu --noconfirm && pacman -Scc --noconfirm
COPY --from=builder /app/target/release/my_app /usr/local/bin/
ENTRYPOINT ["my_app"]

37.3.5 docker compose 使用

# docker-compose.yml (或 compose.yaml)
 
services:
 web:
 build: .
 ports:
 - "8080:80"
 environment:
 - DB_HOST=db
 - DB_PORT=5432
 volumes:
 - ./app:/app
 - static_data:/app/static
 depends_on:
 db:
 condition: service_healthy
 restart: unless-stopped
 deploy:
 resources:
 limits:
 cpus: '1.0'
 memory: 512M
 
 db:
 image: postgres:16-alpine
 environment:
 POSTGRES_USER: myuser
 POSTGRES_PASSWORD: mypass
 POSTGRES_DB: mydb
 volumes:
 - db_data:/var/lib/postgresql/data
 ports:
 - "5432:5432"
 healthcheck:
 test: ["CMD-SHELL", "pg_isready -U myuser"]
 interval: 10s
 timeout: 5s
 retries: 5
 
 redis:
 image: redis:7-alpine
 ports:
 - "6379:6379"
 volumes:
 - redis_data:/data
 
 nginx:
 image: nginx:alpine
 ports:
 - "80:80"
 - "443:443"
 volumes:
 - ./nginx.conf:/etc/nginx/nginx.conf:ro
 - ./certs:/etc/nginx/certs:ro
 depends_on:
 - web
 
volumes:
 db_data:
 redis_data:
 static_data:
 
networks:
 default:
 driver: bridge
 ipam:
 config:
 - subnet: 172.28.0.0/16
# docker compose 命令
docker compose up # 启动所有服务
docker compose up -d # 后台启动
docker compose up --build # 重新构建并启动
docker compose down # 停止并删除容器
docker compose down -v # 同时删除卷
docker compose ps # 查看服务状态
docker compose logs # 查看日志
docker compose logs -f web # 跟踪特定服务日志
docker compose exec web bash # 进入容器
docker compose restart web # 重启服务
docker compose pull # 拉取最新镜像
docker compose build # 构建镜像
docker compose config # 验证配置文件
docker compose top # 查看进程

37.3.6 网络模型

# === bridge(默认)===
# 容器通过虚拟网桥通信
docker network create my_bridge
docker run -d --network my_bridge --name web1 nginx
docker run -d --network my_bridge --name web2 nginx
# web1 和 web2 可以通过容器名互相访问
docker exec web1 ping web2
 
# === host ===
# 容器直接使用宿主机网络栈
docker run -d --network host --name web nginx
# 直接访问 localhost:80
 
# === none ===
# 无网络(完全隔离)
docker run -d --network none --name isolated alpine sleep 3600
 
# === macvlan ===
# 容器获得独立 MAC 地址,直接接入物理网络
docker network create -d macvlan \
 --subnet=192.168.1.0/24 \
 --gateway=192.168.1.1 \
 -o parent=eth0 my_macvlan
 
docker run -d --network my_macvlan \
 --ip 192.168.1.100 \
 --name web nginx
 
# === 网络管理 ===
docker network ls # 列出网络
docker network inspect bridge # 查看网络详情
docker network create my_net # 创建网络
docker network rm my_net # 删除网络
docker network connect my_net web # 将容器加入网络
docker network disconnect my_net web # 将容器从网络移除
docker network prune # 清理未使用网络

37.3.7 存储

# === Volumes(推荐)===
# Docker 管理的存储
docker volume create my_data
docker run -d -v my_data:/app/data --name app alpine
docker volume ls
docker volume inspect my_data
docker volume rm my_data
docker volume prune
 
# === Bind Mounts ===
# 直接挂载宿主机目录
docker run -d -v /host/path:/container/path --name app alpine
docker run -d -v $(pwd)/config:/app/config:ro --name app alpine # 只读
 
# === tmpfs ===
# 内存中的临时文件系统
docker run -d --tmpfs /tmp:size=100m --name app alpine
 
# === 使用 --mount(更明确的语法)===
docker run -d \
 --mount type=volume,source=my_data,target=/app/data \
 --name app alpine
 
docker run -d \
 --mount type=bind,source=/host/config,target=/app/config,readonly \
 --name app alpine
 
docker run -d \
 --mount type=tmpfs,target=/tmp,tmpfs-size=100m \
 --name app alpine

37.3.8 存储驱动

# 查看当前存储驱动
docker info | grep "Storage Driver"
 
# === overlay2(默认,推荐)===
# 基于 OverlayFS,适用于大多数文件系统(ext4、xfs)
# 配置:/etc/docker/daemon.json
{
 "storage-driver": "overlay2"
}
 
# === btrfs ===
# 如果 Docker 数据目录在 btrfs 分区上
{
 "storage-driver": "btrfs"
}
 
# 查看层信息
docker image inspect alpine --format '{{.RootFS.Layers}}'
 
# 查看容器读写层大小
docker ps -s

37.3.9 Docker rootless 模式

# 安装依赖
sudo pacman -S fuse-overlayfs slirp4netns
 
# 确保 subuid/subgid 已配置
grep $USER /etc/subuid
grep $USER /etc/subgid
# 如果没有,添加:
echo "$USER:100000:65536" | sudo tee -a /etc/subuid
echo "$USER:100000:65536" | sudo tee -a /etc/subgid
 
# 安装 rootless 模式
dockerd-rootless-setuptool.sh install
 
# 设置环境变量
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
 
# 启动 rootless Docker
systemctl --user start docker
 
# 设置开机自启
systemctl --user enable docker
sudo loginctl enable-linger $USER
 
# 验证
docker info | grep -i rootless
# 输出应包含:rootless
 
# 注意事项:
# - 端口映射:rootless 模式下无法绑定 <1024 端口
# - 网络:使用 slirp4netns 或 pasta
# - 存储:使用 fuse-overlayfs

37.3.10 安全配置

# === seccomp profile ===
docker run --security-opt seccomp=my_profile.json alpine
 
# === 去除所有 capabilities,只添加必要的 ===
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx
 
# === 只读根文件系统 ===
docker run --read-only --tmpfs /tmp alpine
 
# === 禁止容器获得新权限 ===
docker run --security-opt no-new-privileges alpine
 
# === 限制资源 ===
docker run \
 --memory=256m \
 --memory-swap=512m \
 --cpus=0.5 \
 --pids-limit=100 \
 alpine
 
# === 以非 root 用户运行 ===
docker run --user 1000:1000 alpine
 
# === 禁用进程间通信 ===
docker run --ipc=none alpine
 
# === 安全加固示例(综合)===
docker run -d \
 --name secure-app \
 --read-only \
 --tmpfs /tmp:size=50m \
 --cap-drop=ALL \
 --cap-add=NET_BIND_SERVICE \
 --security-opt no-new-privileges \
 --security-opt seccomp=default.json \
 --memory=256m \
 --cpus=0.5 \
 --pids-limit=50 \
 --user 1000:1000 \
 --network my_bridge \
 my-app

37.3.11 性能优化

# /etc/docker/daemon.json
{
 "storage-driver": "overlay2",
 "log-driver": "json-file",
 "log-opts": {
 "max-size": "10m",
 "max-file": "3"
 },
 "default-ulimits": {
 "nofile": {
 "Name": "nofile",
 "Hard": 65536,
 "Soft": 65536
 }
 },
 "max-concurrent-downloads": 10,
 "max-concurrent-uploads": 5,
 "live-restore": true,
 "userland-proxy": false
}
# .dockerignore 示例(减少构建上下文)
.git
.gitignore
node_modules
*.md
Dockerfile
docker-compose.yml
.env
__pycache__
*.pyc
.vscode
.idea

37.3.12 BuildKit 使用

# 启用 BuildKit(Docker 23+ 默认启用)
export DOCKER_BUILDKIT=1
 
# 或在 /etc/docker/daemon.json 中配置
{
 "features": {
 "buildkit": true
 }
}
 
# BuildKit 特性
 
# === 缓存挂载(加速包管理器)===
# syntax=docker/dockerfile:1
FROM archlinux:latest
RUN --mount=type=cache,target=/var/cache/pacman \
 pacman -Syu --noconfirm && \
 pacman -S --noconfirm python
 
# === Secret 挂载(不会保存到镜像层)===
RUN --mount=type=secret,id=my_secret \
 cat /run/secrets/my_secret
 
# 构建时传递 secret
docker build --secret id=my_secret,src=./secret.txt .
 
# === SSH 挂载(用于 git clone 私有仓库)===
RUN --mount=type=ssh git clone git@github.com:user/repo.git
 
# 构建时转发 SSH agent
docker build --ssh default .
 
# === 多平台构建 ===
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t my-app:latest --push .
 
# 查看 builder
docker buildx ls
 
# 构建缓存管理
docker builder prune # 清理构建缓存
docker builder prune -a # 清理所有构建缓存

37.4 Podman(Docker 替代方案)

37.4.1 无守护进程架构

Docker 架构: Podman 架构:
┌──────┐ ┌──────────┐ ┌──────┐
│Client│──>│dockerd │ │podman│──> fork/exec ──> conmon ──> runc
│(CLI) │ │(daemon) │ │(CLI) │ │
└──────┘ │ │ │ └──────┘ │
 │ ▼ │ ▼
 │ containerd│ 容器进程
 │ │ │
 │ ▼ │
 │ runc │
 │ │ │
 │ ▼ │
 │容器进程 │
 └──────────┘
# 安装 Podman
sudo pacman -S podman podman-compose
 
# 配置(rootless)
sudo pacman -S fuse-overlayfs slirp4netns crun
 
# 检查 subuid/subgid
grep $USER /etc/subuid /etc/subgid
 
# 初始化
podman system migrate
 
# 验证
podman info

37.4.2 podman vs docker 命令对比

# Podman 命令与 Docker 基本兼容
# 可以设置别名
alias docker=podman
 
# 对比表
# Docker Podman
# ─────────────────────────────────────────────────
# docker run podman run
# docker build podman build
# docker pull podman pull
# docker push podman push
# docker images podman images
# docker ps podman ps
# docker exec podman exec
# docker logs podman logs
# docker stop podman stop
# docker rm podman rm
# docker rmi podman rmi
# docker network podman network
# docker volume podman volume
# docker compose podman compose
# docker inspect podman inspect
 
# Podman 独有功能
podman pod # Pod 管理
podman generate systemd # 生成 systemd 单元文件
podman generate kube # 生成 Kubernetes YAML
podman play kube # 从 Kubernetes YAML 运行
podman machine # 管理虚拟机(macOS/Windows)
podman system connection # 远程连接管理

37.4.3 rootless 容器

# Podman 默认就是 rootless 的
podman run --rm alpine id
# uid=0(root) gid=0(root) # 容器内是 root,但映射到宿主机的非特权用户
 
# 查看用户映射
podman unshare cat /proc/self/uid_map
 
# rootless 网络
# 默认使用 slirp4netns 或 pasta
podman run -p 8080:80 nginx # 无需 root 就能端口映射(>1024)
 
# rootless 存储
# 使用 fuse-overlayfs
cat ~/.config/containers/storage.conf
# [storage]
# driver = "overlay"
# [storage.options.overlay]
# mount_program = "/usr/bin/fuse-overlayfs"

37.4.4 Pod 概念

# Pod = 一组共享网络命名空间的容器(类似 Kubernetes Pod)
 
# 创建 Pod
podman pod create --name my-pod -p 8080:80
 
# 在 Pod 中运行容器
podman run -d --pod my-pod --name web nginx
podman run -d --pod my-pod --name app my-backend
# web 和 app 共享网络,可以通过 localhost 互相访问
 
# Pod 管理
podman pod ls # 列出 Pod
podman pod inspect my-pod # 查看 Pod 详情
podman pod stop my-pod # 停止 Pod
podman pod start my-pod # 启动 Pod
podman pod rm my-pod # 删除 Pod
podman pod prune # 清理已停止的 Pod
 
# 从 Pod 生成 Kubernetes YAML
podman generate kube my-pod > my-pod.yaml
 
# 从 Kubernetes YAML 运行
podman play kube my-pod.yaml
podman play kube --down my-pod.yaml # 停止并删除

37.4.5 与 systemd 集成

# 为容器生成 systemd 服务文件
podman generate systemd --new --name my-web > ~/.config/systemd/user/container-my-web.service
 
# 启用服务
systemctl --user daemon-reload
systemctl --user enable --now container-my-web.service
 
# 查看状态
systemctl --user status container-my-web.service
 
# 让用户服务在登出后继续运行
sudo loginctl enable-linger $USER

37.4.6 Quadlet(声明式容器服务)

# Quadlet 是 Podman 4.4+ 内置的声明式容器管理
# 配置文件放在 ~/.config/containers/systemd/ 或 /etc/containers/systemd/
 
# ~/.config/containers/systemd/my-web.container
[Unit]
Description=My Web Server
 
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80
Volume=./html:/usr/share/nginx/html:ro
Environment=TZ=Asia/Shanghai
 
[Service]
Restart=always
 
[Install]
WantedBy=default.target
# ~/.config/containers/systemd/my-app.container
[Unit]
Description=My Application
After=my-db.service
 
[Container]
Image=my-app:latest
PublishPort=3000:3000
Environment=DB_HOST=localhost
Environment=DB_PORT=5432
Network=my-network.network
Secret=db-password,type=env,target=DB_PASSWORD
 
[Service]
Restart=always
 
[Install]
WantedBy=default.target
# 网络定义
# ~/.config/containers/systemd/my-network.network
[Network]
Subnet=172.20.0.0/16
Gateway=172.20.0.1
# 重新加载并启动
systemctl --user daemon-reload
systemctl --user start my-web.service
systemctl --user status my-web.service

37.5 Buildah(OCI 镜像构建)

# 安装
sudo pacman -S buildah
 
# Buildah 允许不使用 Dockerfile 构建镜像
 
# === 从头构建镜像 ===
# 创建工作容器
container=$(buildah from scratch)
 
# 挂载文件系统
mountpoint=$(buildah mount $container)
 
# 安装软件(使用宿主机的 pacman)
sudo pacstrap -c $mountpoint base python
 
# 配置
buildah config --cmd "/usr/bin/python3" $container
buildah config --port 8080 $container
buildah config --author "user@example.com" $container
 
# 提交为镜像
buildah commit $container my-python:latest
 
# 清理
buildah unmount $container
buildah rm $container
 
# === 使用 Dockerfile 构建 ===
buildah build -t my-app:latest .
 
# === 脚本化构建 ===
#!/bin/bash
ctr=$(buildah from alpine:latest)
buildah run $ctr -- apk add --no-cache python3
buildah copy $ctr app.py /app/
buildah config --workingdir /app $ctr
buildah config --entrypoint '["python3", "app.py"]' $ctr
buildah commit $ctr my-app:latest
buildah rm $ctr

37.6 Skopeo(镜像管理)

# 安装
sudo pacman -S skopeo
 
# === 查看远程镜像信息(不下载)===
skopeo inspect docker://docker.io/library/alpine:latest
 
# 只看标签
skopeo inspect docker://docker.io/library/alpine:latest | jq '.RepoTags'
 
# === 复制镜像(不需要本地 daemon)===
# 从 Docker Hub 复制到私有 registry
skopeo copy docker://docker.io/library/nginx:latest \
 docker://my-registry.local:5000/nginx:latest
 
# 复制到本地目录
skopeo copy docker://alpine:latest dir:/tmp/alpine
 
# 复制到 OCI 布局
skopeo copy docker://alpine:latest oci:/tmp/alpine-oci:latest
 
# 复制到 docker-archive(tar 文件)
skopeo copy docker://alpine:latest docker-archive:/tmp/alpine.tar
 
# === 删除远程镜像 ===
skopeo delete docker://my-registry.local:5000/old-image:v1
 
# === 同步镜像 ===
skopeo sync --src docker --dest docker \
 docker.io/library/nginx my-registry.local:5000/
 
# === 列出远程仓库的所有标签 ===
skopeo list-tags docker://docker.io/library/alpine

37.7 容器运行时

# === runc ===
# Docker/Podman 默认的 OCI 容器运行时
sudo pacman -S runc
runc --version
 
# === crun ===
# 用 C 编写的轻量级运行时(Podman 默认)
sudo pacman -S crun
crun --version
 
# 对比:
# runc:Go 编写,Docker 默认,内存占用较高
# crun:C 编写,更轻量,启动更快,Podman 默认
 
# === youki ===
# Rust 编写的 OCI 运行时
# 从 AUR 安装
# yay -S youki
 
# 配置 Docker 使用不同运行时
# /etc/docker/daemon.json
{
 "runtimes": {
 "crun": {
 "path": "/usr/bin/crun"
 }
 },
 "default-runtime": "crun"
}
 
# Podman 配置运行时
# /etc/containers/containers.conf
# [engine]
# runtime = "crun"

37.8 容器镜像格式(OCI Image Spec)

# OCI 镜像由以下部分组成:
# 1. Image Manifest - 描述镜像内容
# 2. Image Index - 多平台镜像索引
# 3. Image Layers - 文件系统层(tar+gzip)
# 4. Image Configuration - 运行时配置
 
# 查看镜像层
docker image inspect alpine --format '{{json .RootFS}}' | jq
 
# 查看镜像历史(每一层的构建命令)
docker history alpine
 
# 导出镜像为 OCI 格式
skopeo copy docker://alpine:latest oci:/tmp/alpine-oci:latest
ls /tmp/alpine-oci/
# blobs/ index.json oci-layout
 
cat /tmp/alpine-oci/index.json | jq
 
# 导出容器文件系统
docker export my_container > container.tar

37.9 容器注册表

# === 搭建私有 registry ===
docker run -d \
 --name registry \
 -p 5000:5000 \
 -v registry_data:/var/lib/registry \
 --restart=unless-stopped \
 registry:2
 
# 推送镜像到私有 registry
docker tag my-app:latest localhost:5000/my-app:latest
docker push localhost:5000/my-app:latest
 
# 从私有 registry 拉取
docker pull localhost:5000/my-app:latest
 
# 查看 registry 中的镜像
curl http://localhost:5000/v2/_catalog
curl http://localhost:5000/v2/my-app/tags/list
 
# === 带 TLS 和认证的 registry ===
mkdir -p /opt/registry/{certs,auth,data}
 
# 生成证书
openssl req -newkey rsa:4096 -nodes -sha256 \
 -keyout /opt/registry/certs/domain.key \
 -x509 -days 365 \
 -out /opt/registry/certs/domain.crt \
 -subj '/CN=registry.local'
 
# 创建认证文件
docker run --rm --entrypoint htpasswd registry:2 \
 -Bbn admin my_password > /opt/registry/auth/htpasswd
 
# 启动安全 registry
docker run -d \
 --name registry \
 -p 443:5000 \
 -v /opt/registry/data:/var/lib/registry \
 -v /opt/registry/certs:/certs \
 -v /opt/registry/auth:/auth \
 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
 -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
 -e REGISTRY_AUTH=htpasswd \
 -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
 -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
 --restart=unless-stopped \
 registry:2
 
# 登录
docker login registry.local

37.10 Distrobox

# Distrobox 允许在容器中运行其他 Linux 发行版,与宿主机深度集成
 
# 安装
sudo pacman -S distrobox
 
# 创建 Ubuntu 容器
distrobox create --name ubuntu --image ubuntu:24.04
 
# 创建 Fedora 容器
distrobox create --name fedora --image fedora:40
 
# 进入容器
distrobox enter ubuntu
 
# 在容器中:
# - 可以访问宿主机的 $HOME
# - 可以使用宿主机的 X11/Wayland
# - 可以使用宿主机的音频
# - 可以运行 GUI 应用
 
# 在容器中安装软件
sudo apt install -y firefox # Ubuntu 容器中
sudo dnf install -y gimp # Fedora 容器中
 
# 将容器中的程序导出到宿主机菜单
distrobox-export --app firefox
distrobox-export --app gimp
 
# 导出命令行工具
distrobox-export --bin /usr/bin/apt --export-path ~/.local/bin
 
# 管理
distrobox list # 列出所有容器
distrobox stop ubuntu # 停止容器
distrobox rm ubuntu # 删除容器
 
# 升级所有容器
distrobox upgrade --all

37.11 systemd-nspawn 与 Docker 对比

# systemd-nspawn 是 systemd 内置的轻量级容器工具
 
# === 创建容器 ===
# 方法 1:使用 pacstrap
sudo mkdir -p /var/lib/machines/my-container
sudo pacstrap -c /var/lib/machines/my-container base
 
# 方法 2:使用 machinectl 从镜像仓库拉取
sudo machinectl pull-tar https://example.com/image.tar.xz my-container
 
# === 启动容器 ===
# 交互式启动
sudo systemd-nspawn -D /var/lib/machines/my-container
 
# 以 boot 模式启动(运行 init 系统)
sudo systemd-nspawn -bD /var/lib/machines/my-container
 
# 使用 machinectl
sudo machinectl start my-container
sudo machinectl login my-container
 
# === 管理 ===
machinectl list # 列出所有机器
machinectl status my-container # 查看状态
machinectl shell my-container # 进入 shell
machinectl poweroff my-container # 关机
machinectl remove my-container # 删除
 
# === systemd-nspawn vs Docker ===
# systemd-nspawn:
# - systemd 原生,无额外守护进程
# - 适合运行完整 OS 容器
# - 与 systemd 服务管理深度集成
# - 无镜像分层(使用目录或 btrfs 子卷)
# - 主要用于开发/测试
 
# Docker:
# - 完整的容器生态系统
# - 分层镜像,高效存储
# - 庞大的镜像仓库
# - 适合微服务部署
# - 跨平台支持

37.12 QEMU/KVM 虚拟机 vs 容器

# === 何时选择虚拟机 ===
# - 需要运行不同内核(Windows、BSD 等)
# - 需要强安全隔离(多租户)
# - 需要完整的硬件模拟
# - 运行不受信任的代码
 
# === 何时选择容器 ===
# - 微服务架构
# - CI/CD 管道
# - 开发环境标准化
# - 需要高密度部署
# - 对启动速度有要求
 
# === 在 Arch 上安装 QEMU/KVM ===
sudo pacman -S qemu-full libvirt virt-manager
sudo systemctl enable --now libvirtd.service
sudo usermod -aG libvirt $USER
 
# === Kata Containers(VM 级隔离的容器)===
# 结合虚拟机和容器的优势:每个容器运行在轻量级 VM 中
# 从 AUR 安装
# yay -S kata-containers
 
# 配置 Docker 使用 Kata
# /etc/docker/daemon.json
{
 "runtimes": {
 "kata": {
 "path": "/usr/bin/kata-runtime"
 }
 }
}
 
docker run --runtime=kata -it alpine sh

37.13 容器网络进阶

# === CNI(Container Network Interface)===
# CNI 是容器网络的标准接口
sudo pacman -S cni-plugins
 
# CNI 插件目录
ls /usr/lib/cni/
# bridge dhcp host-local loopback macvlan portmap tuning vlan
 
# CNI 配置目录
ls /etc/cni/net.d/
 
# 自定义 CNI 配置
cat > /etc/cni/net.d/10-my-bridge.conflist << 'EOF'
{
 "cniVersion": "1.0.0",
 "name": "my-bridge",
 "plugins": [
 {
 "type": "bridge",
 "bridge": "cni0",
 "isGateway": true,
 "ipMasq": true,
 "ipam": {
 "type": "host-local",
 "subnet": "10.88.0.0/16",
 "routes": [
 {"dst": "0.0.0.0/0"}
 ]
 }
 },
 {
 "type": "portmap",
 "capabilities": {"portMappings": true}
 }
 ]
}
EOF
 
# === slirp4netns ===
# 用户态网络栈,用于 rootless 容器
sudo pacman -S slirp4netns
 
# 特点:
# - 不需要 root 权限
# - 性能略低于 bridge 网络
# - 支持端口映射
 
# === pasta(Podman 5+ 默认)===
# passt 的容器变体,替代 slirp4netns
sudo pacman -S passt
 
# 特点:
# - 比 slirp4netns 性能更好
# - 支持 IPv6
# - 支持 UDP
# - Podman 5+ 默认使用
 
# 配置 Podman 使用 pasta
# /etc/containers/containers.conf
# [network]
# default_rootless_network_cmd = "pasta"
 
# === 容器间网络排查 ===
# 查看容器网络命名空间
sudo ls -la /proc/$(docker inspect --format '{{.State.Pid}}' my-container)/ns/net
 
# 进入容器网络命名空间排查
sudo nsenter -t $(docker inspect --format '{{.State.Pid}}' my-container) -n bash
ip addr
ip route
ss -tlnp
iptables -L -n
 
# 使用 tcpdump 抓容器流量
# 方法 1:在容器中安装 tcpdump
docker exec my-container tcpdump -i eth0 -nn
 
# 方法 2:在宿主机上抓 veth 对
# 找到容器的 veth 接口
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' my-container)
sudo nsenter -t $CONTAINER_PID -n ip link show eth0
# 根据 ifindex 在宿主机找到对应的 veth
ip link | grep "ifindex"
 
# 方法 3:使用 nsenter 进入网络命名空间抓包
sudo nsenter -t $CONTAINER_PID -n tcpdump -i eth0 -nn

37.14 容器存储进阶

# === fuse-overlayfs ===
# 在 rootless 容器中用于替代内核 overlayfs
sudo pacman -S fuse-overlayfs
 
# 为什么需要:
# 内核 overlayfs 在 user namespace 中需要特殊支持(Linux 5.11+)
# 旧内核需要 fuse-overlayfs 作为替代
 
# 检查是否支持内核 overlayfs in user ns
cat /proc/sys/kernel/unprivileged_userns_clone
unshare -rm mount -t overlay overlay \
 -o lowerdir=/tmp,upperdir=/tmp/upper,workdir=/tmp/work /tmp/merged 2>&1
 
# Podman rootless 存储配置
# ~/.config/containers/storage.conf
[storage]
driver = "overlay"
 
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
 
# === 容器存储层详解 ===
#
# 镜像层(只读):
# Layer 1: 基础系统(FROM alpine)
# Layer 2: 安装软件(RUN apk add ...)
# Layer 3: 复制代码(COPY . .)
#
# 容器层(读写):
# Layer 4: 运行时写入的数据
#
# 合并视图(Union Mount):
# 所有层合并为统一的文件系统视图
 
# 查看 overlay 挂载
mount | grep overlay
 
# 查看容器的 overlay 层
docker inspect my-container --format '{{json .GraphDriver}}' | jq
 
# === 存储最佳实践 ===
# 1. 持久数据使用 named volumes
docker volume create app_data
docker run -v app_data:/data my-app
 
# 2. 配置文件使用 bind mount(只读)
docker run -v ./config:/app/config:ro my-app
 
# 3. 临时数据使用 tmpfs
docker run --tmpfs /tmp:size=100m my-app
 
# 4. 日志不要写入容器层
# 使用 Docker 日志驱动或挂载卷
 
# 5. 减少镜像层数(合并 RUN 命令)
# 不好:
# RUN apt update
# RUN apt install -y curl
# RUN rm -rf /var/lib/apt/lists/*
 
# 好:
# RUN apt update && apt install -y curl && rm -rf /var/lib/apt/lists/*
 
# 6. 使用 .dockerignore 排除不必要文件
# 7. 多阶段构建减小最终镜像大小

小结:容器技术的核心是 Linux 内核原语(namespaces、cgroups、overlay fs、seccomp)的组合应用。Docker 是最流行的容器平台,而 Podman 作为无守护进程的替代方案正在快速发展。在 Arch Linux 上推荐安装 docker docker-compose docker-buildxpodman podman-compose buildah skopeo 作为容器工具链。掌握镜像构建优化、网络配置、存储管理和安全加固,是生产环境容器化部署的关键能力。