- 容器运行时
- 云原生
- CLI
【免费下载链接】podman
Podman: A tool for managing OCI containers and pods.
导读
本文深入剖析 Podman 仓库中test/compose/env_and_volume这一 docker-compose v2 集成测试,它用两个运行 Flask 的容器完整验证了 Compose 的环境变量注入与跨容器共享卷两大核心能力:一个容器把环境变量PODMAN_MSG的值写入共享卷中的文件,另一个容器读取该文件并通过 HTTP 对外返回。读完本文,你将掌握该测试的编排文件结构、双容器数据流设计、源码实现细节,以及如何借助test-compose测试框架在本地复现并验证这一场景。
一、测试背景:Podman 对 docker-compose v2 的兼容性验证
在 Podman 仓库中,test/compose 目录专门用于验证docker-compose v2在 Podman 下的行为。该目录说明明确写道:
Tests for docker-compose v2 under podman. docker-compose v1 is no longer supported upstream so we no longer test with it.
这意味着每个子目录都代表一个独立的 Compose 场景测试,env_and_volume正是其中专门覆盖**环境变量(environment)与共享卷(volume)**交互的用例。从仓库结构看,test/compose 下并列存在simple_port_map、mount_and_label、ipam_set_ip、two_networks等多个场景,而env_and_volume侧重的是容器间通过共享卷传递数据的典型模式——这一模式在真实微服务架构(如生产者写入、消费者读取的中间件场景)中非常常见。
二、测试设计总览:两个 Flask 容器如何协作
env_and_volume/README.md 用一段话清晰描述了测试的设计意图:
This test creates two containers both of which are running flask. The first container has an environment variable called PODMAN_MSG. That container pipes the contents of PODMAN_MSG to a file on a shared volume between the containers. The second container then reads the file and returns the PODMAN_MSG value via flask (http).
整个测试的数据流可以概括为三条链路:
- 环境变量注入:
writer容器启动时通过 Compose 的environment获得PODMAN_MSG=podman_rulez; - 共享卷写入:
writer把该变量的值写入挂载在/data的共享卷中的message文件; - 跨容器读取与 HTTP 暴露:
reader容器挂载同一卷,读取message文件内容,并通过 Flask 在 HTTP 端口对外返回。
两个容器各司其职:writer负责"生产",reader负责"消费",共享卷则是二者之间的数据管道。这验证了 Compose 两层关键能力——environment配置项能否正确把变量传入容器进程,以及同名命名卷(named volume)能否被多个服务同时挂载并实现数据互通。
三、docker-compose.yml 逐项拆解
测试的核心编排文件是 docker-compose.yml,完整内容如下:
version: '3' services: writer: environment: - PODMAN_MSG=podman_rulez build: write ports: - '5000:5000' volumes: - data:/data reader: build: read ports: - '5001:5000' volumes: - data:/data volumes: data:各配置项的作用与设计意图:
| 配置项 | 值 | 说明 |
|---|---|---|
version: '3' | Compose 文件版本 | 声明使用 Compose v3 规范,这也是 docker-compose v2 默认支持的格式 |
services.writer.environment | PODMAN_MSG=podman_rulez | 定义注入到writer容器的环境变量,即测试要验证的核心数据 |
services.writer.build | write | 指定从write/子目录构建镜像 |
services.writer.ports | '5000:5000' | 宿主机端口 5000 映射到容器内 Flask 默认端口 5000 |
services.writer.volumes | data:/data | 将命名卷data挂载到容器内/data目录 |
services.reader.build | read | 指定从read/子目录构建镜像 |
services.reader.ports | '5001:5000' | 宿主机端口 5001 映射到容器内 5000,避免与writer冲突 |
services.reader.volumes | data:/data | 与writer共享同一个命名卷data |
volumes.data | 空定义 | 声明名为data的命名卷,供两个服务共同引用 |
值得注意的关键点在于:两个服务挂载的是同一个命名卷data,而不是各自独立的匿名卷。这正是共享卷生效的前提——Compose 会在创建服务时将同名卷解析为同一存储位置,使/data/message文件对两个容器都可见。
四、容器镜像与 Flask 应用源码剖析
4.1 写入端 writer
写入端的镜像构建文件 write/Dockerfile 如下:
FROM quay.io/libpod/podman_python WORKDIR /app COPY . /app ENTRYPOINT ["python3"] CMD ["app.py"]它基于 Podman 项目维护的quay.io/libpod/podman_python镜像(内含 Python 与 Flask 环境),把write/目录内容复制到/app,以python3 app.py作为入口。
应用代码 write/app.py:
from flask import Flask import os app = Flask(__name__) @app.route('/') def hello(): f = open("/data/message", "w") f.write(os.getenv("PODMAN_MSG")) f.close() return "done" if __name__ == '__main__': app.run(host='0.0.0.0')writer的关键行为集中在根路由处理器中:
- 通过
os.getenv("PODMAN_MSG")读取容器环境变量——这正是 Composeenvironment注入结果的直接验证点; - 以写模式打开共享卷上的
/data/message,把变量值写入文件; - 向访问者返回字符串
done作为"写入成功"的信号。
这里app.run(host='0.0.0.0')绑定所有网卡,配合 Compose 的端口映射,使宿主机上的curl http://localhost:5000可以直接访问。
4.2 读取端 reader
读取端镜像构建文件 read/Dockerfile 与写入端完全一致,同样基于podman_python镜像。
应用代码 read/app.py:
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): f = open("/data/message", "r") return f.read() if __name__ == '__main__': app.run(host='0.0.0.0')reader的逻辑更加简洁:直接以读模式打开同一路径/data/message,把文件内容作为 HTTP 响应体返回。整个过程不依赖任何网络通信或外部服务,完全依靠共享卷完成数据传输,从而把"环境变量 + 共享卷"这条链路从writer到reader完整闭环。
五、验证方法:tests.sh 与 HTTP 断言
env_and_volume/tests.sh 定义了该测试的最终断言:
# -*- bash -*- test_port 5000 = "done" test_port 5001 = "podman_rulez"这与 README 中列出的两条验证命令一一对应:
* curl http://localhost:5000 and verify message * curl http://localhost:5001 and verify messagetest_port 5000 = "done":请求writer暴露的 5000 端口,期望返回done,确认写入端已成功把PODMAN_MSG写入共享卷文件;test_port 5001 = "podman_rulez":请求reader暴露的 5001 端口,期望返回podman_rulez(即环境变量注入的原始值),确认读取端从共享卷读到了writer写入的内容,且内容与环境变量值完全一致。
5.1 test_port 的实现原理
test_port函数定义在测试框架脚本 test-compose 中,其核心实现为:
function test_port() { local port="$1" # e.g. 5000 local op="$2" # '=' or '~' local expect="$3" # what to expect from curl output local actual actual=$(curl --retry 3 --retry-all-errors -s -S http://127.0.0.1:$port/) ... case "$op" in '=') is "$actual" "$expect" "$testname : port $port" ;; '~') like "$actual" "$expect" "$testname : port $port" ;; *) die "Invalid operator '$op'" ;; esac }test_port使用curl --retry 3 --retry-all-errors对127.0.0.1:<port>发起请求,内置 3 次重试以容忍容器启动的延迟。操作符方面,=表示完全相等,~则借助expr做子串匹配(支持通配)。从 test/compose/README.md 的约定可以知道:
tests.sh will probably contain commands of the form
test_port 12345 = 'hello there'Where 12345 is the port to curl to; '=' checks equality, '~' uses
exprto check substrings.
这一设计让每个测试子目录的tests.sh都能用极简的一行命令完成端口级行为断言。
六、测试框架:test-compose 如何驱动整个场景
6.1 整体执行流程
env_and_volume并不是一个孤立脚本,它由仓库根目录级别的test/compose/test-compose统一驱动。根据 test/compose/README.md,每个测试子目录的执行流程如下:
- 在空的工作目录下建立全新的 Podman 根(root)与运行根(runroot),保证测试环境隔离;
- 启动一个以该根为后端的 Podman 服务(
podman system service),并通过 Unix socket 暴露 Docker 兼容 API; - 切换到测试子目录,执行
docker-compose up -d; source该目录下的tests.sh执行断言;- 执行
docker-compose down清理资源。
此外,测试目录中还可以放置setup.sh和teardown.sh,分别在up之前和down之后被执行,用于准备或清理额外资源。
6.2 服务端启动与隔离细节
从 test-compose 的start_service函数可以看到 Podman 服务是如何以隔离方式启动的:
$PODMAN_BIN \ --log-level debug \ --storage-driver=vfs \ --root $WORKDIR/root \ --runroot $WORKDIR/runroot \ --cgroup-manager=systemd \ --network-config-dir $WORKDIR/networks \ --cdi-spec-dir $WORKDIR/cdi \ system service \ --time 0 unix://$DOCKER_SOCK \ &> $WORKDIR/server.log &要点:
- 每个测试都使用
mktemp生成的独立WORKDIR,其下的root、runroot、networks、cdi目录全部指向临时路径,实现存储与网络配置的完全隔离; - 存储驱动固定为
vfs,避免对 overlayfs 等宿主机内核特性的依赖; - 服务通过
unix://$DOCKER_SOCK暴露 Docker 风格 socket,供 docker-compose 客户端连接。
6.3 root 与 rootless 双模式支持
框架对 root 和 rootless 两种运行方式做了差异化处理:
- root 模式:导出
DOCKER_HOST="unix://$DOCKER_SOCK",docker-compose 直接使用该环境变量连接 Podman 服务; - rootless 模式:socket 路径放在用户可访问的
$WORKDIR/docker.sock,并通过podman system connection add compose-sock "unix://$DOCKER_SOCK"注册连接,同时设置PODMAN_CONNECTIONS_CONF指向临时配置文件,避免污染用户的真实连接配置。此时podman_compose函数会以$PODMAN_BIN --connection compose-sock compose "$@"的方式调用。
6.4 运行与调试命令
按 test/compose/README.md 的用法说明,可以在仓库根目录下运行全部 Compose 测试或仅运行指定子目录:
# 运行全部 compose 测试(需要 root) sudo test/compose/test-compose # 只运行与 env_and_volume 匹配的子目录 sudo test/compose/test-compose env_and_volume脚本通过参数通配匹配子目录:${TEST_ROOTDIR}/*${i}*/docker-compose.yml,因此传env_and_volume即可只跑本用例。若希望容器在docker-compose down之前暂停以便排查,可以设置COMPOSE_WAIT环境变量:
env COMPOSE_WAIT=1 sudo --preserve-env=COMPOSE_WAIT test/compose/test-compose暂停期间,可以在另一个终端借助测试输出中的临时目录X=/var/tmp/test-compose.tmp.XXXXXX直接对测试容器执行诊断:
podman --root $X/root --runroot $X/runroot ps -a podman --root $X/root --runroot $X/runroot logs -l6.5 测试结果输出
框架以近似 TAP 13 的格式输出测试结果(便于 CI 的 logformatter 识别),每个子目录会依次报告up、tests、down三个阶段的通过情况。若test_port的 curl 请求失败,还会自动输出server.log与测试日志帮助定位问题。
七、场景解读:该测试验证了哪些 Podman Compose 能力
结合源码与编排文件,可以归纳出env_and_volume实际覆盖的验证维度:
- 环境变量注入(environment):Compose 中的
environment列表项被正确转换为容器进程可见的环境变量,writer通过os.getenv("PODMAN_MSG")能读到podman_rulez; - 命名卷共享(named volume):两个服务声明挂载同一个
data卷后,writer在/data/message的写入能被reader在同一路径读取,证明卷在不同容器间共享且文件系统可见性一致; - 端口映射与多服务共存(ports):两个容器都在容器内监听 5000 端口,通过宿主机 5000/5001 的不同映射实现并行可访问,验证了 Compose 端口映射的隔离性;
- 端到端 HTTP 行为:通过
curl断言确认两条 HTTP 链路返回内容符合预期,证明 Podman 兼容层能够完整支撑 docker-compose 的工作流。
从工程实践角度看,这个测试也演示了一种可复用的设计模式:用"写入方 + 共享卷 + 读取方"三个要素构造最小可验证的数据管道。writer暴露"生产"接口(返回done表示落盘成功),reader暴露"消费"接口(返回真实数据),测试断言同时覆盖两端,任何一端的异常都会导致对应端口校验失败,从而精确定位问题出在写入链路还是读取链路。
八、总结
test/compose/env_and_volume虽然是一个体量很小的测试目录,却以极低的复杂度完整覆盖了 docker-compose 生态中最常用的两项能力——环境变量注入与命名卷共享,并且给出了清晰的验证路径。通过阅读 docker-compose.yml、write/app.py、read/app.py 与 tests.sh,可以把它当作一份"Compose 双容器共享数据"的参考实现;而 test-compose 脚本则示范了如何在隔离的 Podman 环境里驱动 docker-compose 并做端口级断言。对于希望在自己的 Podman + Compose 环境中验证环境变量与卷共享行为的开发者,这套目录本身就是一份可复制、可运行的模板。
- 容器运行时
- 云原生
- CLI
【免费下载链接】podman
Podman: A tool for managing OCI containers and pods.
相关推荐
Zonos-v0.1 Docker Compose配置:多服务协同与环境变量管理
Zonos v0.1 Docker Compose配置:多服务协同与环境变量管理 你是否在部署Zonos v0.1时遇到过服务启动失败、GPU资源无法调用或环境
语音音频基础模型AI 应用Path of Building PoE2:流放之路2角色构建的终极解决方案
Path of Building PoE2:流放之路2角色构建的终极解决方案 你是否曾经花费数小时研究《流放之路2》的天赋树,却在游戏中发现角色表现远不如预期?
桌面应用游戏开发Docker Compose与Spring Boot环境变量解析问题深度解析
Docker Compose与Spring Boot环境变量解析问题深度解析 问题背景 在使用Docker Compose部署Spring Boot应用时,开发
云原生容器编排DevOpsCLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考