- 运维
- 任务调度
- 后端
【免费下载链接】rundeck
Enable Self-Service Operations: Give specific users access to your existing tools, services, and scripts
Rundeck 是一个面向自助运维(Self-Service Operations)的开源任务调度与自动化平台,允许把既有脚本、服务和工具安全地授权给特定用户执行。在发布 RPM/Deb 安装包之前,项目需要一套可靠的手段来验证"从零安装"是否成功。本文围绕仓库中 packaging/test/docker/rpminstall/Readme.md 展开,讲解如何用 Docker 对本地构建的 Rundeck RPM 包进行安装测试,并深入剖析配套的 Dockerfile、入口脚本与冒烟测试逻辑。读完本文,你将掌握手动与自动化两条测试路径,理解容器内服务启动、健康等待、加密配置校验等关键机制,并能对照源码定位每一步的实际行为。
这套测试方案解决什么问题
Rundeck 的发行包(RPM / Deb / WAR)由独立的packaging打包工程构建,产出物最终会被用户直接安装在生产服务器上。如果安装脚本、初始化逻辑或默认配置在真实环境里出错,代价很高。因此项目在 packaging/test/docker/rpminstall 提供了一套Docker 化的 RPM 安装测试方案:
- 在容器内模拟一台干净的 RHEL 系主机(基于 Red Hat UBI8);
- 将本地刚构建出来的
rundeck-*.rpm复制进镜像并执行rpm -ivh; - 安装完成后自动启动
rundeckd服务,并等待日志中出现成功标志; - 执行一组冒烟测试,验证服务状态、重复启动保护与密钥加密配置。
README 开宗明义:"You can use this to test install of a locally build .rpms for rundeck."——它的定位就是本地构建产物的安装验证,而非单元测试或功能测试。
配套的 Debian 版本位于 packaging/test/docker/debinstall,两者结构完全对称(仅包管理器不同:rpm -ivh对dpkg -i),本文以 RPM 侧为主线讲解。
前置条件:先构建出 RPM 产物
测试的前提是本地已构建出 RPM 包。从打包工程的 CI 脚本 scripts/circleci/packaging-functions.sh 可以看到标准构建方式:
packaging_create_packages() { local RELEASE_NUM="1" mkdir -p "${PACKAGING_DIR}/packaging/artifacts/" copy_rundeck_war "${PACKAGING_DIR}/packaging/artifacts/" cd "${PACKAGING_DIR}/packaging" LIB_DIR="${PACKAGING_DIR}/lib" ./gradlew ${GRADLE_BASE_OPTS} -PlibsDir=${LIB_DIR} -PpackageRelease=$RELEASE_NUM clean packageArtifacts }即进入 packaging 目录,用 Gradle 执行packageArtifacts,RPM 产物会落入packaging/build/distributions/(该路径在 packaging/test/test-docker-install-common.sh 中被显式引用:PATTERN="${PACKAGING_DIR}/packaging/build/distributions/*.${PACKAGE_TYPE}")。测试脚本正是从这个目录收集.rpm文件。
方案一:按 README 手动构建镜像并安装(挂载源码卷)
packaging/test/docker/rpminstall/Readme.md 给出了最直接的手动流程,全文核心命令如下:
# 1) 从 rundeck 源码仓库根目录执行,构建镜像 docker build docker/rpminstall # 2) 复制镜像 id,例如 53c9cb76cbe1 # (构建输出末尾的 Successfully built <id> 即是) # 3) 以交互方式运行容器,并把当前源码目录挂载进容器 docker run -it -v $PWD:/home/rundeck/rundeck $IMG bash其中关键点:
-v $PWD:/home/rundeck/rundeck:README 明确说明要从源码仓库根目录使用该挂载,将源码卷挂到容器内/home/rundeck/rundeck。容器的工作目录WORKDIR与VOLUME都指向$HOME/rundeck(即/home/rundeck/rundeck),挂载后容器内即可访问源码与构建产物。- 交互式 bash:
docker run ... $IMG bash让你进入容器后手工执行rpm -ivh、service rundeckd start等操作,适合调试。
需要注意,README 中docker build docker/rpminstall指向的是根目录下 docker/rpminstall 这套早期版本镜像(基于 CentOS 6、Java 1.8,入口脚本 docker/rpminstall/entry.sh 直接到/home/rundeck/packaging/packaging/rpmdist/RPMS/noarch/下查找rundeck*.rpm)。而 packaging/test/docker/rpminstall 目录内是一套更新的工程化流程(基于 Red Hat UBI8、Java 17,通过脚本自动复制 RPM 并构建),两者目录名与 README 内容一致,但镜像基础与安装路径不同。日常建议优先使用下面介绍的自动化脚本。
方案二:自动化脚本构建与运行(推荐)
仓库提供了两个驱动脚本:
- packaging/test/test-docker-install-rpm.sh:RPM 入口,设置
DIR="${PACKAGING_DIR}/test/docker/rpminstall"、COMMON="redhatubi8"、PACKAGE_TYPE="rpm",然后加载公共逻辑并依次调用build与run; - packaging/test/test-docker-install-common.sh:公共实现,负责基础镜像构建、产物复制、镜像构建与容器运行。
第一步:build —— 构建基础工具镜像
build()的逻辑:
build(){ local TAG="rdpro-$COMMON" docker build --no-cache -t "$TAG-util" \ -f "${PACKAGING_DIR}/test/docker/installcommon/${COMMON}.Dockerfile" \ "${PACKAGING_DIR}/test/docker/installcommon" }即先构建名为rdpro-redhatubi8-util的基础镜像,其定义在 packaging/test/docker/installcommon/redhatubi8.Dockerfile:
FROM redhat/ubi8 RUN yum -y update # Grails 7: Java 17 required RUN yum -y install java-17-openjdk java-17-openjdk-devel initscripts openssh openssl COPY scripts/rd-util.sh /rd-util.sh COPY scripts/rpm-tests.sh /init-tests.sh值得注意:基础镜像预装了Java 17,注释明确写着 "Grails 7: Java 17 required"——这正对应 rundeckapp 基于 Grails 7 的运行时要求;同时把公共工具 packaging/test/docker/installcommon/scripts/rd-util.sh 与测试脚本rpm-tests.sh拷贝为/rd-util.sh与/init-tests.sh,供后续所有安装镜像复用。
第二步:run —— 复制 RPM 并构建最终测试镜像
run(){ local TAG="rdpro-$COMMON" DEBS=$(list_debs) # packaging/build/distributions/*.rpm for DEB in $DEBS; do cp -pv "$DEB" "$DIR/rundeck.rpm" # 复制为 rpminstall 目录下的 rundeck.rpm docker build --no-cache "$DIR" -t "$TAG":latest docker run -it -e "PACKAGE=/rundeck/rundeck.rpm" "$TAG":latest -test done }该循环对packaging/build/distributions/下的每个 RPM 执行三件事:
- 将 RPM 复制为 packaging/test/docker/rpminstall 目录下的
rundeck.rpm,作为构建上下文的一部分; docker build构建最终镜像,镜像名rdpro-redhatubi8:latest;- 运行容器,通过
-e "PACKAGE=/rundeck/rundeck.rpm"注入安装包路径环境变量,并以-test参数触发安装后的冒烟测试。
整个测试链路会被 CI 定期触发:见 scripts/circleci/packaging-functions.sh 的packaging_test_packages(),它依次调用test-docker-install-deb.sh与test-docker-install-rpm.sh。
最终镜像剖析:Dockerfile 与入口脚本
Dockerfile 关键指令
packaging/test/docker/rpminstall/Dockerfile:
FROM rdpro-redhatubi8-util:latest RUN useradd rundeck ENV USERNAME=rundeck \ USER=rundeck \ HOME=/home/rundeck \ LOGNAME=$USERNAME \ TERM=xterm-256color ENV JAVA_HOME=/etc/alternatives/java_sdk COPY --chown=rundeck:root rundeck.rpm /rundeck/rundeck.rpm COPY entry.sh /entry.sh RUN chmod +x /entry.sh VOLUME $HOME/rundeck WORKDIR $HOME/rundeck EXPOSE 4440 ENTRYPOINT ["/entry.sh"]要点:
- 预置 RPM:
COPY rundeck.rpm /rundeck/rundeck.rpm,安装包在构建阶段即被打进镜像(因此run()必须先复制产物); - 专用用户:
useradd rundeck,与安装脚本中创建同名服务用户保持一致; - 端口与入口:
EXPOSE 4440(Rundeck Web 默认端口),入口固定为/entry.sh,镜像启动即进入安装流程。
entry.sh:安装 → 授权 → 展示配置 → 启动
packaging/test/docker/rpminstall/entry.sh:
#!/bin/bash . /rd-util.sh set -e FLAV=${EDITION:-cluster} # 版本风味,默认 cluster DIR="${HOME}/${PACKAGING_DIR}/packaging/build/distributions" install_rundeck(){ echo "Install Rundeck from file: " $DIR/$PACKAGE rpm -ivh "$PACKAGE" # 实际安装的是环境变量 PACKAGE 指向的绝对路径 } main(){ install_rundeck cp_license echo_config entry_start "$@" } main "$@"执行顺序与作用:
rpm -ivh "$PACKAGE"——安装 RPM。容器运行时通过-e "PACKAGE=/rundeck/rundeck.rpm"指定;EDITION环境变量(默认cluster)用于选择版本风味;cp_license——若设置了LICENSE_FILE/LICENSE环境变量,则把许可证复制为/etc/rundeck/rundeckpro-license.key(详见 rd-util.sh);echo_config——打印/etc/rundeck/rundeck-config.properties内容,方便确认安装生成的默认配置;entry_start "$@"——启动服务并按参数执行后续动作。
rd-util.sh:服务启动与健康等待
rd-util.sh 的entry_start是整个流程的"交通枢纽":
entry_start(){ if [ "$1" != "-skipstart" ]; then service rundeckd start wait_for_start /var/log/rundeck/service.log else shift fi if [ "$1" = "-test" ] ; then shift . /init-tests.sh test_all fi exec "$@" }- 默认行为:启动
rundeckd并调用wait_for_start等待就绪; -skipstart:跳过启动(适用于已运行的服务或纯安装验证);-test:加载/init-tests.sh(即rpm-tests.sh)并执行test_all冒烟测试;- 末尾
exec "$@":把剩余参数作为命令执行——README 中docker run ... $IMG bash之所以能进入交互 shell,正是因为这行把bash交棒给了容器主进程。
wait_for_start的实现很有参考价值:它以30 次 × 10 秒 ≈ 5 分钟为上限轮询/var/log/rundeck/service.log,直到出现Grails application running这一成功标志;期间若service rundeckd status失败则立即报错退出。这与 packaging/lib/rpm/etc/rc.d/init.d/rundeckd 启动脚本把日志写入/var/log/rundeck/service.log的行为一一对应。
容器内冒烟测试:rpm-tests.sh
安装并启动之后,-test参数触发 packaging/test/docker/installcommon/scripts/rpm-tests.sh 中三组断言:
| 测试函数 | 断言内容 | 失败处理 |
|---|---|---|
test_status | service rundeckd status输出包含is running | 打印FAILED并exit 2 |
test_start_twice | 对已运行的rundeckd再次start输出Already started(防重复启动) | 同上 |
test_encryption_password | 配置中不存在明文占位default.encryption.password,且rundeck.storage.converter.N.config.password为 32 位十六进制 | 同上 |
第三项测试值得展开。打包模板 packaging/lib/common/etc/rundeck/rundeck-config.properties 中默认写着:
rundeck.storage.converter.1.type=aes-gcm-encryption rundeck.storage.converter.1.path=keys rundeck.storage.converter.1.config.password=default.encryption.password而 RPM 安装脚本 packaging/lib/rpm/scripts/postinst.sh 在安装后会把占位密码替换为随机值:
STORAGE_PASS=$(openssl rand -hex 16) sed -i -E 's/^rundeck\.storage\.converter\.([0-9]+)\.config\.password=default\.encryption\.password$/rundeck.storage.converter.\1.config.password='"$STORAGE_PASS"'/' "$RDECK_CONFIG"openssl rand -hex 16生成 32 位十六进制随机串,与测试里的^[0-9a-f]{32}$正则完全吻合。这条测试本质上在验证:安装过程确实完成了密钥存储加密口令的随机化,未把不安全的默认占位值留在生产配置里(对应 aes-gcm 加密插件 plugins/aes-gcm-encryption-plugin 的存储加密链路)。
安装脚本在背后做了什么
理解容器内安装测试的断言,还需要知道 RPM 安装脚本自身的动作。以 packaging/lib/rpm/scripts/postinst.sh 为例:
- 生成 SSH 密钥:若
~rundeck/.ssh/id_rsa不存在则生成(Rundeck 节点通信的基础设施); - 写入 server UUID:若
framework.properties中尚无合法的 UUID(8-4-4-4-12格式),追加随机生成的rundeck.server.uuid; - 随机化加密口令:如上文所示替换两处
default.encryption.password; - 支持目录重定位:通过
RPM_INSTALL_PREFIX0~4可自定义 home、etc、bin、log、init 目录,非默认路径时会批量改写profile、配置与 init 脚本; - 注册服务:当 init 目录为默认的
/etc/rc.d/init.d时执行/sbin/chkconfig --add rundeckd。
而 packaging/lib/rpm/scripts/preinst.sh 负责前置校验:解析java -version得到主版本号,要求 Java 17、21 或 25,否则exit 1;随后创建rundeck用户/组及 home 目录。这与基础镜像预装 Java 17 的选择相互印证,也解释了为什么这套新方案把旧版 CentOS 6 + Java 8 镜像替换为 UBI8 + Java 17。
常见问题与调试技巧
结合源码实现,排查时可重点关注以下几点:
- 找不到 RPM 报错:旧版入口脚本 docker/rpminstall/entry.sh 会检查
$HOME/packaging/packaging/rpmdist/RPMS/noarch/rundeck*.rpm,若文件不存在exit 2。请确认先执行了./gradlew packageArtifacts产出包,再运行测试脚本; - 启动超时:
wait_for_start最多等约 5 分钟。若超时,它会打印service.log全文帮助定位(如rundeckd status失败也会立即报错并输出日志); - 进入容器手工验证:
docker run -it ... $IMG bash可进入 shell 手动执行rpm -qa | grep rundeck、service rundeckd status、cat /etc/rundeck/rundeck-config.properties等检查; - 跳过启动/测试:追加
-skipstart仅做安装验证,追加-test执行三组冒烟断言,二者可组合控制容器行为; - 指定版本风味:通过
-e EDITION=cluster(默认)等环境变量控制FLAV。
总结
从 packaging/test/docker/rpminstall/Readme.md 的三步命令出发,Rundeck 项目把"本地 RPM 安装验证"沉淀为一套完整的 Docker 测试体系:Gradle 构建产物 →test-docker-install-rpm.sh自动复制并构建镜像 →entry.sh执行rpm -ivh→rd-util.sh启动并等待Grails application running→rpm-tests.sh验证服务状态、幂等启动与加密口令随机化。这条链路同时覆盖了安装、初始化脚本(preinst.sh / postinst.sh)与运行时健康三方面的回归验证,是理解 Rundeck 发行包质量保障机制的理想切入点,也完全可以作为你自己项目"发行包容器化测试"的参考模板。Debian 一侧仅需把rpm -ivh换成dpkg -i,流程与本文完全对称,可对照 packaging/test/docker/debinstall 继续阅读。
- 运维
- 任务调度
- 后端
【免费下载链接】rundeck
Enable Self-Service Operations: Give specific users access to your existing tools, services, and scripts
相关推荐
PHPStan 错误详解:preInc.nonNumeric——对非数值类型使用前置自增运算符
PHPStan 错误详解:preInc.nonNumeric——对非数值类型使用前置自增运算符 导读 preInc.nonNumeric 是 PHPStan(P
运维任务调度后端Chat2DB 新手入门指南:连接 40+ 数据库,用你自己的 AI 模型生成与优化 SQL
Chat2DB 新手入门指南:连接 40+ 数据库,用你自己的 AI 模型生成与优化 SQL Chat2DB 是一款免费的跨平台数据库客户端与 SQL 工作空间
运维任务调度后端Rich Console Protocol 完全指南:为自定义对象打造终端富文本渲染能力
Rich Console Protocol 完全指南:为自定义对象打造终端富文本渲染能力 Rich( rich https://link.gitcode.com
运维任务调度后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考