跳转到内容
搜索 Rocky Linux 文档

    浏览当前文档目录

    Rocky 10:quadlet 与 systemd 的契约,以及离线怎么备镜像

    Rocky Linux 10.2 Server 组虚拟机,podman 5.8.2 / quadlet 5.8.2。

    实验网访问不到任何 registry,所以先解决「镜像从哪来」,再做 quadlet 全生命周期验证。截图:public/evidence/quadlet/rocky-quadlet.png。

    离线备镜像:import 一个最小 rootfs

    Section titled “离线备镜像:import 一个最小 rootfs”
    Terminal window
    D=/tmp/lab-img2; mkdir -p "$D/bin" "$D/lib64"
    cp /bin/bash "$D/bin/bash"
    for lib in $(ldd /bin/bash | awk '/=>/ {print $3}'); do cp -L "$lib" "$D/lib64/"; done
    cp -L /lib64/ld-linux-x86-64.so.2 "$D/lib64/"
    tar -C "$D" -cf /tmp/rootfs.tar .
    podman import --change 'CMD ["/bin/bash","-c","while true; do read -t 30 || true; done"]' \
    /tmp/rootfs.tar localhost/labshell:1

    read -t 30 是 bash 内建,不需要 coreutils——所以在「只有 bash + 动态库」的极小镜像里也能常驻。

    顺带一个真实的坑:从别的架构导出的镜像(arm64)在这台 x86_64 上会 Exec format error,日志写法是 image platform (linux/arm64/v8) does not match the expected platform (linux/amd64)。

    契约:生成单元里多出来的字段

    Section titled “契约:生成单元里多出来的字段”

    .container 文件只写了 [Container] 与 [Install],但生成出来的单元还有:

    $ systemctl cat webapp.service | grep -E "^(Wants|After|RequiresMountsFor|Delegate|KillMode|ExecStop)"
    Wants=network-online.target
    After=network-online.target
    RequiresMountsFor=%t/containers
    KillMode=mixed
    ExecStop=/usr/bin/podman rm -v -f -i lab-webapp
    Delegate=yes

    每一项都有原因:

    字段作用
    Wants/After=network-online.target拉镜像前网络就绪
    RequiresMountsFor=%t/containers容器存储挂载完成再启动
    KillMode=mixed先给主进程发信号,再清理整个 cgroup
    ExecStop=podman rm -v -f -i停服务即删容器(含匿名卷)
    Delegate=yes让容器自己管 cgroup(容器内的 systemd、CPU 限制)
    $ systemctl start webapp.service; sleep 3
    $ systemctl is-active webapp.service
    active
    $ podman ps --format '{{.Names}} | {{.Status}} | {{.Ports}}'
    lab-webapp | Up 3 seconds | 0.0.0.0:8088->80/tcp
    $ ss -tlnp | grep 8088
    LISTEN 0 4096 0.0.0.0:8088 0.0.0.0:* users:(("conmon",pid=10657,fd=5))

    conmon 是 podman 的容器守护进程,它持有发布端口并转发进容器网络命名空间。所以:

    • 用 ss 看到的是 conmon,不是你的应用进程;
    • 防火墙放行的也是这个宿主端口;
    • 应用没起来时端口照样在 LISTEN(连接会被拒或挂起),别拿「端口在听」当「服务正常」。

    Rocky Linux 10.2:quadlet 源、生成单元、运行与停止

    quadlet 是 systemd 生成器(/usr/lib/systemd/system-generators/podman-system-generator),只在 daemon-reload 或开机时跑:

    $ systemctl daemon-reload # 重新生成 webapp.service
    $ systemctl start webapp.service

    改了 .container 不 reload,systemctl cat 里还是旧内容——这是最常见的「配置不生效」。

    Terminal window
    systemctl cat webapp.service # 生成后的真实单元(唯一可信来源)
    journalctl -u webapp.service -n 50 # podman 的拉取/启动/退出原因都在这
    podman ps -a --format '{{.Names}} {{.Status}}' # 残留容器与崩溃循环

    systemctl cat 特别重要:手写的 .container 与最终 podman run 参数之间隔着一层生成器,猜不如看。

    • Exec format error:镜像架构与本机不符(我们用 arm64 镜像踩过);
    • 服务反复重启:Restart=on-failure 会把失败循环放大,先看 journalctl 里的进程退出码;
    • podman ps 为空但端口被占:可能是上一个 conmon 没退,podman ps -a 与 ss 一起看;
    • 不要用 WantedBy=multi-user.target 之外的花样:quadlet 默认期望 default.target。
    Terminal window
    sudo tee /etc/containers/systemd/broken.container >/dev/null <<'EOF'
    [Container]
    Image=localhost/missing:1
    [Install]
    WantedBy=default.target
    EOF
    sudo systemctl daemon-reload
    sudo systemctl start broken.service # 预期失败
    sudo journalctl -u broken.service -n 10 --no-pager | tail -5

    检查:

    1. 失败原因是镜像不存在(而不是语法错误);
    2. 把 Image= 改成 localhost/labshell:1 后 daemon-reload + start 成功;
    3. systemctl cat 里能看到对应的 podman run;
    4. 收尾 systemctl disable --now 并删除文件、再 reload。

    常见问题

    离线环境怎么给 quadlet 供镜像?

    在有网的机器上导出(skopeo/podman save),拷进来 podman load;或者像本文一样用 podman import 做最小镜像。

    容器里的服务怎么写日志?

    直接写 stdout/stderr,podman 会转发到 journald,用 journalctl -u <unit> 查看。

    能不能让容器在失败时自动重建?

    能,[Service] 的 Restart=on-failure 配合 RestartSec;注意失败循环会刷日志,先定位根因。