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
| 特性 | Docker | Podman |
|---|---|---|
| 守护进程 | 需要 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 模式 |
| 数据持久化 | 卷挂载 | 数据与容器分离 |
⚠️ 注意事项
- cgroup 版本兼容:Docker 和 Podman 对 cgroup v2 的支持程度不同,注意版本匹配
- rootless 限制:rootless 模式下某些功能受限(如端口 < 1024)
- 日志驱动切换:切换日志驱动后,已有容器不受影响,需重建容器
- 容器重启策略:与 systemd 的 Restart= 配合时,避免重复重启逻辑
- 网络模式选择:生产环境建议使用桥接或自定义网络,避免 host 模式
💡 提示
- 使用
podman generate systemd可以快速生成符合 systemd 规范的服务文件 loginctl enable-linger是 rootless 用户服务持久化的关键- 使用
journalctl CONTAINER_NAME=myapp可以方便地查看容器日志 - Docker Compose 配合 systemd 是中小型项目的最佳实践
- 定期执行
docker system prune清理无用资源