强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

systemd 教程 / systemd 与 Docker/Podman

systemd 与 Docker/Podman

容器技术已成为现代应用部署的主流方式。如何将容器与 systemd 有效集成,是生产环境中必须面对的问题。本章将介绍 Docker 和 Podman 与 systemd 的集成方案。


1. Docker 容器的 systemd 管理

1.1 Docker 服务配置

Docker 守护进程本身就是 systemd 管理的服务:

# /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd \
    --storage-driver=overlay2 \
    --log-driver=journald \
    --default-ulimit nofile=65535:65535 \
    --live-restore
LimitNOFILE=65535
LimitNPROC=65535

1.2 为 Docker 容器创建 systemd 服务

# 方法 1:使用 docker create + systemd
docker create --name myapp \
    --restart=no \
    -p 8080:80 \
    -v /data/myapp:/usr/share/nginx/html \
    nginx:latest

# 创建服务文件
sudo tee /etc/systemd/system/myapp-container.service <<EOF
[Unit]
Description=My App Container
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker start myapp
ExecStop=/usr/bin/docker stop myapp
TimeoutStartSec=300
TimeoutStopSec=30

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable myapp-container.service

1.3 动态生成服务文件

#!/bin/bash
# generate-docker-service.sh

CONTAINER_NAME=$1
IMAGE=$2
PORTS=$3
VOLUMES=$4

cat > /etc/systemd/system/${CONTAINER_NAME}.service <<EOF
[Unit]
Description=${CONTAINER_NAME} Container
After=docker.service
Requires=docker.service

[Service]
Type=simple
ExecStartPre=-/usr/bin/docker stop ${CONTAINER_NAME}
ExecStartPre=-/usr/bin/docker rm ${CONTAINER_NAME}
ExecStart=/usr/bin/docker run --name ${CONTAINER_NAME} --rm \\
    -p ${PORTS} \\
    -v ${VOLUMES} \\
    ${IMAGE}
ExecStop=/usr/bin/docker stop ${CONTAINER_NAME}
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload

2. Podman 的 systemd 集成

2.1 Podman vs Docker

特性DockerPodman
守护进程需要 dockerd无守护进程
rootless部分支持原生支持
systemd 集成需要额外配置原生支持
Docker Compose原生支持podman-compose
OCI 兼容
安全性较低较高

2.2 使用 podman generate systemd

Podman 可以自动生成 systemd 服务文件:

# 创建容器(不启动)
podman create --name myapp -p 8080:80 nginx:latest

# 生成 systemd 服务文件
podman generate systemd --name myapp > /etc/systemd/system/myapp.service

# 查看生成的服务文件
cat /etc/systemd/system/myapp.service

# 启用并启动服务
sudo systemctl daemon-reload
sudo systemctl enable myapp.service
sudo systemctl start myapp.service

2.3 podman generate systemd 参数

# 生成服务文件到指定目录
podman generate systemd --name myapp --files --new \
    --restart-policy=always \
    --stop-timeout=30

# 参数说明
# --name: 指定容器名
# --files: 输出到文件而不是标准输出
# --new: 创建新容器而非使用现有容器
# --restart-policy: 重启策略
# --stop-timeout: 停止超时

2.4 Rootless Podman 用户服务

# 在用户空间创建服务
mkdir -p ~/.config/systemd/user/

# 生成用户级服务
podman generate systemd --name myapp \
    --files --new \
    --restart-policy=always

mv myapp.service ~/.config/systemd/user/

# 启用用户服务
systemctl --user daemon-reload
systemctl --user enable myapp.service
systemctl --user start myapp.service

# 允许用户服务在用户注销后继续运行
loginctl enable-linger $(whoami)

3. 容器健康检查(HealthCheck)

3.1 Docker 健康检查

# Dockerfile 中定义
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD curl -f http://localhost/ || exit 1

# docker run 中定义
docker run --health-cmd="curl -f http://localhost/" \
    --health-interval=30s \
    --health-timeout=10s \
    --health-retries=3 \
    nginx

3.2 Podman 健康检查

# 创建带健康检查的容器
podman create --name myapp \
    --health-cmd="curl -f http://localhost/" \
    --health-interval=30s \
    --health-timeout=10s \
    --health-retries=3 \
    nginx:latest

# 查看健康状态
podman healthcheck run myapp

3.3 systemd 健康检查集成

[Service]
ExecStart=/usr/bin/podman run --name myapp ...
ExecStartPost=/bin/sleep 5
ExecReload=/bin/kill -HUP $MAINPID

# 健康检查脚本
ExecStartPost=/bin/bash -c 'while ! curl -sf http://localhost:8080/health; do sleep 1; done'

# 或使用 systemd 的 watchdog
WatchdogSec=30

4. 容器日志集成(journald 日志驱动)

4.1 Docker 日志驱动配置

# /etc/docker/daemon.json
{
    "log-driver": "journald",
    "log-opts": {
        "tag": "docker/{{.Name}}",
        "labels": "service,environment"
    }
}

4.2 Podman 日志集成

Podman 默认使用 journald 日志驱动:

# 运行容器时指定日志驱动
podman run --log-driver=journald nginx

# 查看容器日志
journalctl CONTAINER_NAME=myapp

# 使用 tag 过滤
journalctl CONTAINER_TAG=myapp

4.3 日志转发配置

# 将 Docker 日志转发到 systemd-journal
# /etc/docker/daemon.json
{
    "log-driver": "journald",
    "log-opts": {
        "tag": "docker.{{.Name}}"
    }
}

5. 容器自动重启策略

5.1 Docker 重启策略

# Docker 重启策略
docker run --restart=always nginx      # 总是重启
docker run --restart=unless-stopped nginx  # 除非手动停止
docker run --restart=on-failure:3 nginx    # 失败时最多重启 3 次
docker run --restart=no nginx             # 不自动重启

5.2 systemd 重启策略

[Service]
Restart=always                    # 总是重启
Restart=on-failure               # 失败时重启
Restart=on-abnormal              # 异常退出时重启
Restart=on-abort                 # 收到未处理信号时重启
Restart=no                       # 不重启(默认)

RestartSec=10                    # 重启间隔 10 秒
StartLimitBurst=5               # 5 分钟内最多启动 5 次
StartLimitIntervalSec=300       # 5 分钟时间窗口

5.3 最佳实践

[Service]
# 生产环境推荐配置
Restart=on-failure
RestartSec=10
StartLimitBurst=5
StartLimitIntervalSec=300

# 如果 5 分钟内重启超过 5 次,则停止尝试
# 使用 systemctl reset-failed 可以重置计数器

6. cgroup v2 与容器

6.1 检查 cgroup 版本

# 检查 cgroup 版本
stat -fc %T /sys/fs/cgroup/
# cgroup2fs = cgroup v2
# tmpfs = cgroup v1

# 查看详细信息
cat /proc/filesystems | grep cgroup
mount | grep cgroup

6.2 Docker cgroup v2 配置

# /etc/docker/daemon.json
{
    "exec-opts": ["native.cgroupdriver=systemd"],
    "cgroup-parent": "docker.slice"
}

6.3 systemd 资源控制

# docker-myapp.slice
[Unit]
Description=My App Container Slice

[Slice]
CPUQuota=50%
MemoryMax=1G
IOWeight=500
TasksMax=100
# 使用 slice
docker run --cgroup-parent=docker-myapp.slice nginx

7. Rootless 容器与用户服务

7.1 设置 Rootless Docker

# 安装 rootless Docker
sudo dnf install fuse-overlayfs slirp4netns

# 配置用户
dockerd-rootless-setuptool.sh install

# 设置环境变量
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

7.2 Rootless Podman

# Podman 原生支持 rootless
podman run --name myapp -p 8080:80 nginx

# 用户服务
mkdir -p ~/.config/systemd/user/
podman generate systemd --name myapp --files
mv myapp.service ~/.config/systemd/user/
systemctl --user enable myapp.service

7.3 用户服务持久化

# 允许用户服务在注销后继续运行
sudo loginctl enable-linger $(whoami)

# 验证
loginctl show-user $(whoami) | grep Linger

8. 实际案例:Docker Compose + systemd

8.1 Docker Compose 配置

# /opt/myapp/docker-compose.yml
version: "3.8"
services:
  web:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - api
    restart: unless-stopped

  api:
    image: myapp/api:latest
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:15
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=secret
    restart: unless-stopped

volumes:
  db-data:

8.2 systemd 服务文件

# /etc/systemd/system/myapp-compose.service
[Unit]
Description=MyApp Docker Compose
After=docker.service
Requires=docker.service
After=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/myapp

ExecStartPre=/usr/bin/docker compose pull
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose restart

TimeoutStartSec=300
TimeoutStopSec=60

[Install]
WantedBy=multi-user.target
# 启用服务
sudo systemctl daemon-reload
sudo systemctl enable myapp-compose.service
sudo systemctl start myapp-compose.service

9. 容器化服务最佳实践

9.1 服务文件模板

# /etc/systemd/system/[email protected]
[Unit]
Description=%i Container
After=docker.service
Requires=docker.service

[Service]
Type=simple
ExecStartPre=-/usr/bin/docker stop %i
ExecStartPre=-/usr/bin/docker rm %i
ExecStart=/usr/bin/docker run --name %i --rm \
    --network=host \
    --log-driver=journald \
    --health-cmd="curl -f http://localhost/ || exit 1" \
    --health-interval=30s \
    %i
ExecStop=/usr/bin/docker stop %i

Restart=on-failure
RestartSec=10
StartLimitBurst=3
StartLimitIntervalSec=300

# 资源限制
CPUQuota=50%
MemoryMax=1G

# 安全加固
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

9.2 生产环境检查清单

检查项推荐配置说明
日志驱动journald集中日志管理
重启策略on-failure + 限制避免无限重启
健康检查内置 HealthCheck自动检测服务状态
资源限制CPU/Memory 限制防止资源耗尽
安全加固NoNewPrivileges禁止提权
网络模式自定义网络避免 host 模式
数据持久化卷挂载数据与容器分离

⚠️ 注意事项

  1. cgroup 版本兼容:Docker 和 Podman 对 cgroup v2 的支持程度不同,注意版本匹配
  2. rootless 限制:rootless 模式下某些功能受限(如端口 < 1024)
  3. 日志驱动切换:切换日志驱动后,已有容器不受影响,需重建容器
  4. 容器重启策略:与 systemd 的 Restart= 配合时,避免重复重启逻辑
  5. 网络模式选择:生产环境建议使用桥接或自定义网络,避免 host 模式

💡 提示

  • 使用 podman generate systemd 可以快速生成符合 systemd 规范的服务文件
  • loginctl enable-linger 是 rootless 用户服务持久化的关键
  • 使用 journalctl CONTAINER_NAME=myapp 可以方便地查看容器日志
  • Docker Compose 配合 systemd 是中小型项目的最佳实践
  • 定期执行 docker system prune 清理无用资源

扩展阅读