- How to use auto-updates and rollbacks in Podman
- podman-auto-update - Auto update containers according to their auto-update policy
- sd_notify() from Java
本文将通过一个简单的 Spring Boot 3 Web 项目演示 Podman 的 Auto-update 功能和 Rollback 机制。项目使用 eclipse-temurin:17-jre-noble
基底打包为容器镜像,并可通过如下命令启动:
docker run --rm -p 8080:8080 registry.ddrpa.cc/gunvor:latest
程序监听 HTTP 请求并返回诸如 1.0.0
之类的版本号,可确认程序正常运行并且是期望的版本。
curl localhost:8080/
> 1.0.0
Auto-update
参考 Quadlet 让 systemd 管理容器更容易 创建容器单元文件并启动服务。
# $HOME/.config/containers/systemd/user/gunvor.container
[Unit]
Description=Podman container-gunvor-showcase.service
Documentation=man:podman-systemd.unit
Wants=network-online.target
After=network-online.target
[Container]
Image=registry.ddrpa.cc/gunvor:latest
AutoUpdate=registry
PublishPort=8080:8080
[Install]
WantedBy=default.target
Podman 通过比较镜像的摘要与 registry(如果设置 AutoUpdate=registry
)或本地(AutoUpdate=local
)存储的摘要是否一致判断当前使用的镜像是否为新版本。因此在实际操作过程中,如果你需要将容器回滚到一个较旧的版本,完全可以将其 tag
修改为 latest
。
可以使用 podman auto-update --dry-run
检查镜像是否有更新。
$ podman auto-update --dry-run
UNIT CONTAINER IMAGE POLICY UPDATED
gunvor.service 4247e399b668 (systemd-gunvor) registry.ddrpa.cc/gunvor:latest registry false
如果有镜像更新,UPDATED
就会显示为 pending
,此时执行 podman auto-update
即可。
Podman 会注册名为 podman-auto-update
的服务单元和定时器单元。定时器默认设置每日零点启动更新服务,可以使用 systemctl --user enable podman-auto-update.timer
启用,下方的日志就展示了一次定时检查。
$ journalctl --user -xeu podman-auto-update.service
Sep 24 00:07:52 alma systemd[1213]: Starting Podman auto-update service...
░░ Subject: A start job for unit UNIT has begun execution
░░ Defined-By: systemd
░░ Support: https://access.redhat.com/support
░░
░░ A start job for unit UNIT has begun execution.
░░
░░ The job identifier is 524.
Sep 24 00:07:52 alma podman[32573]: 2024-09-24 00:07:52.966786397 +0800 CST m=+0.108055223 system auto-update
Sep 24 00:07:53 alma podman[32573]: UNIT CONTAINER IMAGE >
Sep 24 00:07:53 alma podman[32573]: gunvor.service a1481e70ace5 (systemd-gunvor) registry.ddrpa.cc/gunvor:l>
Sep 24 00:07:54 alma.red.stratus.zjxasz.com systemd[1213]: Finished Podman auto-update service.
░░ Subject: A start job for unit UNIT has finished successfully
░░ Defined-By: systemd
░░ Support: https://access.redhat.com/support
░░
░░ A start job for unit UNIT has finished successfully.
░░
░░ The job identifier is 524.
你可以修改定时器配置,改变自动更新的频率和时间。
Simple Rollback
尽管发版之前程序已经通过了测试人员的重重检查,有的时候仍会出现程序无法正常启动的问题。Podman 3.4 开始支持在容器启动失败时自动回滚到上一个可用的镜像。
在下文的例子中,当前的 latest
版本在程序启动时故意抛出一个 NPE 令程序异常退出,可以看到 podman auto-update
在执行结果中标注 UPDATED
为 rolled back
。此时容器自动回退到旧版本的镜像。
> podman auto-update
Trying to pull registry.ddrpa.cc/gunvor:latest...
Getting image source signatures
Copying blob bd40a8ba9b98 skipped: already exists
Copying blob 44b1b7b9b07e skipped: already exists
Copying blob 2bdf9306e3bf done |
Copying config 6a7488ac68 done |
Writing manifest to image destination
UNIT CONTAINER IMAGE POLICY UPDATED
gunvor.service 01f9ccc70a61 (systemd-gunvor) registry.ddrpa.cc/gunvor:latest registry rolled back
如何实现?
systemd 需要知道服务的进程 ID 来跟踪服务的状态,例如在主进程退出后将其标记为 stopped
,根据退出代码(exit code)判断是否 failed
。
在 Podman 中,主进程默认指向 conmon(Container monitor)。conmon 管理并监控着容器,当容器退出时,conmon 会使用相同的代码退出。
这就带来了一个问题,conmon 在启动容器后会发出 ready
信号,systemd 就认为服务启动成功了。就算你的程序写的有问题,退出也是那一刻之后的事情了,而回滚只会在 conmon(作为主进程)启动后立即异常退出的情况下发生。
sd_notify
一种解决方法是让容器负责发出 ready
信号。
通过 --sdnotify=container
参数或是在容器单元文件的 [container]
章节中配置 Notify=true
,容器就有充足的时间完成初始化,然后通知 systemd 服务就绪。如果容器在启动时出现异常退出,或因为各种各样的原因超时,则 systemd 会检测到服务启动失败,令 Podman 开始回滚。
发出 ready
信号是通过向环境变量 NOTIFY_SOCKET
指定的套接字发送消息来实现的。对 C/C++ 技术栈,可以使用 sd_notify
函数或写入 /run/systemd/notify
套接字文件来完成。虽然 Java 技术栈在 JDK16 实现了 JEP-380: Unix domain socket channels,相关功能还未得到支持。
使用一些基于 junixsocket - GitHub 或 JNI 的库
可以使用 SDNotify - GitHub,在程序启动完成后发出就绪信号。注意这里通过检查环境变量 NOTIFY_SOCKET
判断程序是否需要发送就绪信号。
import info.faljse.SDNotify.SDNotify;
@Component
public class ReadyNotifier {
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() throws IOException {
if (System.getenv("NOTIFY_SOCKET") != null) {
SDNotify.sendNotify();
}
}
}
或是参考 septatrix/SdNotify.java - GitHub。
Red Hat Universal Base Image 镜像 (UBI)
使用 UBI 镜像作为基底(例如eclipse-temurin:17-jdk-ubi9-minimal
),可以通过执行 shell 命令发送 ready
消息:
systemd-notify --ready
在 Java 代码中使用 Runtime.getRuntime().exec("systemd-notify --ready")
。
你不需要考虑这些代码的异常处理,等待 ready
信号超时后,systemd 会自动结束服务。
Notify=healthy
Podman 5.0.0+ 支持设置 Notify=healthy
,此时会使用 HealthCmd
的执行结果判断服务是否启动成功。
[Container]
Image=registry.dddrpa.cc/gunvor:latest
AutoUpdate=registry
Notify=healthy
HealthCmd=curl -f http://localhost:8080/ || exit 1
不过截至本文写作时,你只能在 Fedora 40 中使用 Podman 5.0。