
文章目录从手动部署到自动化部署1. 为什么要引入 CI/CD2. 常见的 CI/CD 工具3. 不同工具的差异大吗4. 阿里云云效流水线实践5. 如果要迁移到其他工具前端部署流水线1. 创建空白流水线2. 配置流水线源3. 配置构建阶段4. 配置主机部署5. 保存并运行后端部署流水线1. 创建空白流水线2. 配置构建阶段3. 配置构建物上传4. 环境变量配置4.1 配置云效变量组4.2 修改服务器systemd文件5. 配置主机部署5.1 基础配置5.2 部署脚本重点5.3 踩坑过程5.4 兼容服务器手动部署脚本6. 保存并运行7. 关于环境变量如何配置的思考7.1 问题的提出7.2 技术上可行但不建议7.3 根本原因分析7.4 当前状态与最终形态从手动部署到自动化部署之前我写过一篇 《Java Snowy 框架生产环境安全部署全流程服务器篇》 的文章在里面讲述了在服务器上从零开始搭建和部署Java Snowy框架当时由于时间原因在文章 末尾 遗留了一个问题未来扩展考虑对于更专业的持续集成/持续部署CI/CD可以考虑Jenkins、GitLab CI、Ansible 等。今天就来把这部分补上。备注本文部分内容由AI辅助生成已全部经过本人仔细分析和验证请放心阅读1. 为什么要引入 CI/CD前文介绍的 手动部署脚本deploy-backend.sh、build-frontend.sh虽然能完成部署但存在明显局限依赖特定人员必须有人登录服务器执行脚本出了问题也只有这个人能排查缺乏标准化流程谁在什么时候部署了什么版本没有自动记录环境一致性难保证开发、测试、生产环境的构建过程可能因人为操作差异而不同无法扩展随着团队规模增长多人同时操作服务器容易产生冲突CI/CD持续集成/持续部署正是为了解决这些问题而生的。其核心思想是代码提交后自动触发构建和部署流程减少人工干预保证每次发布的过程可追溯、可复现。CI/CD 是什么CIContinuous Integration持续集成开发人员将代码频繁地合并到主分支每次合并都会自动触发构建和测试尽早发现集成问题。核心目标是让代码合入这件原本风险很高的事情变成低风险的日常操作。CDContinuous Delivery / Continuous Deployment持续交付/持续部署在持续集成的基础上将构建好的制品自动部署到目标环境。两者的区别在于持续交付需要人工点击发布按钮才会上线持续部署则是全自动化代码合并后直接上线。实际项目中通常采用持续交付模式——自动化到发布前的最后一步由人确认后再上线。用一个生活中的类比来理解CI/CD 就像一条流水线工厂——原材料代码放进去经过自动化的检测、组装构建和测试、包装制品归档最后出厂发货部署到服务器。整个过程不需要人工搬砖只需在关键节点确认即可。引入 CI/CD 后的优势对比维度手动部署CI/CD 自动化部署触发方式手动登录服务器执行代码提交自动触发或一键触发操作权限需要 SSH 登录服务器只需流水线平台操作权限构建环境依赖服务器本地环境云端容器环境隔离且可复现过程追溯无自动记录每次构建和部署有完整日志回滚能力需手动操作可基于历史制品快速回滚多人协作容易冲突流水线排队执行互不干扰2. 常见的 CI/CD 工具市面上主流的 CI/CD 工具主要有工具部署方式特点适用场景Jenkins自建服务器部署开源免费插件生态极其丰富社区活跃但需要自行维护服务器界面较老配置相对繁琐团队有运维能力需要高度自定义GitLab CI自建或 SaaS与 GitLab 代码仓库深度集成配置即代码.gitlab-ci.yml开箱即用已在使用 GitLab 管理代码的团队GitHub ActionsSaaS与 GitHub 深度集成Workflow 配置灵活市场上有大量现成 Action代码托管在 GitHub 的开源项目或商业项目阿里云云效SaaS阿里云生态深度集成可视化编排界面友好上手成本低与 Codeup 代码仓库无缝衔接阿里云用户尤其是代码已在 Codeup 的团队3. 不同工具的差异大吗核心逻辑几乎一样差异主要在界面和语法。所有 CI/CD 工具的流水线都遵循同一个模型触发代码提交、合并请求、手动触发等构建在容器中执行编译、打包命令制品保存构建产物JAR、dist 压缩包等部署将制品下发到目标服务器并执行部署脚本差异主要体现在配置方式Jenkins 用 Groovy / 界面配置GitLab CI 和 GitHub Actions 用 YAML 文件云效用可视化编排界面构建环境Jenkins 需自建 Agent其他工具提供云端构建容器部署方式Jenkins 通常用 SSH 或 Agent 连接服务器云效用主机组 部署脚本GitLab CI / GitHub Actions 多用 Runner 或 SSH4. 阿里云云效流水线实践由于我公司直接使用了阿里云云效管理代码仓库因此本文以云效为例搭建自动化部署流水线。流水线的核心逻辑分为两步构建阶段在云效提供的云端容器中安装依赖、编译生成静态文件并将其打包成一个压缩包制品。部署阶段将上一步生成的制品下载到目标服务器上解压并放到 Nginx 配置的网站目录下。准备事项代码仓库后端代码已托管至阿里云 Codeup且本地测试编译和启动正常。服务器目标服务器ECS已配置 systemd 服务snowy-web-app.service服务能正常启动。云效主机组已在云效中创建主机组并将目标服务器加入其中参考云效文档。5. 如果要迁移到其他工具本文以云效为例但如果将来需要迁移到 Jenkins 或其他工具部署脚本几乎可以原封不动地复用因为它们本质就是在服务器上执行的 shell 命令。主要需要调整的是需要调整的部分说明流水线配置语法从云效的可视化配置改为 Jenkinsfile /.gitlab-ci.yml/.github/workflows/*.yml构建环境声明JDK 版本、Node 版本、Maven 版本等需要在新工具中重新声明制品管理方式云效自动归档Jenkins 需要archiveArtifactsGitLab CI 需要artifacts关键字服务器连接方式云效通过主机组Jenkins 通过 SSH Agent 或节点GitLab CI 通过 Runner环境变量管理云效用变量组Jenkins 用 Credentials 环境变量GitLab CI 用 CI/CD Variables总结掌握了云效流水线的核心思路后迁移到其他工具的学习成本很低。因为流水线的灵魂——构建命令和部署脚本——是通用的变的只是壳。前端部署流水线1. 创建空白流水线登录云效控制台后在左侧导航栏找到并点击“流水线”。https://flow.aliyun.com/my?page1点击页面右上角的 “新建流水线” 按钮。在弹出页面中顶部选择 “可视化编排”。然后在左侧类型中选择 “空模板”右侧选中 “空模板·空模板”。点击 “创建” 按钮。2. 配置流水线源这是连接代码仓库的关键一步。在流水线编辑页面顶部找到 “流水线源” 区域点击 “添加流水线源”。在弹窗中 “代码源” 类型选择 “Codeup”。然后选择服务连接和代码仓库和分支。3. 配置构建阶段这里需要一个任务来执行具体的构建命令。在流水线编辑页面中点击 “新建阶段”。在新阶段的空白区域点击 “新建任务”。在任务列表中找到 “Node.js构建” 并选中。在右侧的配置面板中填写以下内容任务名称自行输入。构建集群保持默认的云效北京构建集群即可。Node版本选择选择与项目匹配的版本例如22.16。构建命令cdsnowy-admin-webnpminstallnpmrun build:test构建命令为用户自定义构建命令。支持 npmcnpm 和 yarn命令执行目录为代码库根路径。如何设置构建依赖下载访问凭证上传方式归档至云效公共存储空间制品名称Artifacts_${PIPELINE_ID}打包路径snowy-admin-web/dist完成后保存配置。4. 配置主机部署这个步骤会将制品发布到服务器。点击流水线中的 “新建阶段”命名为部署。在新阶段中点击 “新建任务”搜索并选中 “主机部署”。在右侧配置面板中填写以下关键信息任务名称保持默认。制品在下拉框中选择上一步创建的制品即Artifacts_${PIPELINE_ID}。主机组选择或新建一个主机组。这是你的目标服务器集群。如果是第一次添加需要按照向导创建并将你的ECS实例加入其中。下载路径填写/root/project/snowy/git/snowy_frontend_flow/dist.tgz。这是制品下载到服务器上的临时存放路径。执行用户填写root确保有写入权限。部署脚本直接复制并粘贴以下脚本它会自动解压、备份并更新相关文件。这个shell脚本是部署的关键#!/bin/bashset-e# 任何命令失败立即退出DOWNLOAD_PATH/root/project/snowy/git/snowy_frontend_flow/dist.tgzDEPLOY_DIR/root/project/snowy/frontend/pc-webecho检查制品文件:$DOWNLOAD_PATHif[!-f$DOWNLOAD_PATH];thenecho错误制品文件不存在exit1fils-lh$DOWNLOAD_PATHecho创建临时目录...TMP_DIR$(mktemp-d)echo临时目录:$TMP_DIRecho开始解压制品...tar-xzvf$DOWNLOAD_PATH-C$TMP_DIRecho解压完成目录内容ls-la$TMP_DIRecho清空目标目录:$DEPLOY_DIRrm-rf$DEPLOY_DIR/*mkdir-p$DEPLOY_DIRecho复制文件...if[-d$TMP_DIR/dist];thenecho检测到 dist 文件夹复制其内容cp-rv$TMP_DIR/dist/*$DEPLOY_DIR/elseecho无 dist 文件夹直接复制所有文件cp-rv$TMP_DIR/*$DEPLOY_DIR/fiecho设置权限...chmod-R755$DEPLOY_DIRecho清理临时文件和制品...rm-rf$TMP_DIRrm-f$DOWNLOAD_PATHecho 部署完成 5. 保存并运行点击页面右上角的 “保存并运行” 按钮在弹窗中确认要运行的分支。可以在流水线运行详情页实时查看每一步的执行日志以排查问题。后端部署流水线后端部署流水线的整体逻辑与前端类似但构建命令和部署脚本不同。1. 创建空白流水线参考前端步骤登录云效 → 流水线 → 新建流水线 → 可视化编排 → 空模板·空模板。创建后配置流水线源Codeup选择后端代码仓库及分支。2. 配置构建阶段添加一个“Java 构建”任务。步骤名称Java 构建JDK 版本JDK 21Maven 版本Maven 3.9.3或更高构建命令替换默认内容为以下命令# 多模块项目先安装所有模块mvn cleaninstall-DskipTestscdsnowy-web-app mvn clean package-DskipTests说明Snowy 是多模块项目直接打包snowy-web-app会失败必须先执行install。3. 配置构建物上传构建完成后需要将生成的 JAR 文件作为制品保存供后续部署使用。在同一个“构建”阶段中配置“构建物上传”部分。上传方式归档至云效公共存储空间制品名称Artifacts_${PIPELINE_ID}打包路径snowy-web-app/target/snowy-web-app-3.0.0.jar不勾选 “制品中包含打包路径的目录”注意版本号3.0.0请根据实际项目版本修改或使用通配符snowy-web-app/target/*.jar若只有一个 jar 文件。常见错误打包路径写错会导致制品内容不对例如打包了源码目录。务必确认路径正确。4. 环境变量配置Spring Boot 项目的application.properties中使用了${DB_HOST}、${REDIS_DATABASE}等占位符。这些值需要在运行时通过环境变量提供否则启动会报错。手动部署时这些环境变量可能来源于 Shell 的export或application-test.properties文件。而云效部署时服务器上的 systemd 服务进程没有继承这些变量会导致启动失败。云效上推荐使用变量组统一管理环境变量并在部署脚本中注入。4.1 配置云效变量组操作步骤在云效控制台左侧菜单进入 “全局设置” → “通用变量组”。点击 “新建变量组”命名为snowy-test-env-config-v2。查看项目的snowy-web-app/src/main/resources/application-test.properties内容添加以下变量变量名统一加SNOWY_ENV_前缀原因见下方说明变量名默认值私密模式建议SNOWY_ENV_SERVER_PORT8082否SNOWY_ENV_FRONT_URLhttps://rx-web-test.snowy.com否SNOWY_ENV_BACKEND_URLhttps://rx-api-test.snowy.com否SNOWY_ENV_DB_HOSTrm-2zxxxx.rds.aliyuncs.com否SNOWY_ENV_DB_PORT3306否SNOWY_ENV_DB_NAMEsnowy否SNOWY_ENV_DB_USERxxx是SNOWY_ENV_DB_PASSWORDxxxx是SNOWY_ENV_REDIS_HOSTr-2zexxxx.redis.rds.aliyuncs.com否SNOWY_ENV_REDIS_PORT6379否SNOWY_ENV_REDIS_DATABASE1否SNOWY_ENV_REDIS_PASSWORDxxxx是1为什么要加SNOWY_ENV_前缀部署脚本需要从云效注入的环境变量中筛选出业务变量写入/etc/snowy/env.conf。如果没有统一前缀就只能用正则逐个匹配变量名如SERVER_PORT|FRONT_URL|DB_|REDIS_以后每次新增变量都要同时修改变量组和部署脚本维护成本高。加了SNOWY_ENV_前缀后部署脚本只需grep ^SNOWY_ENV_即可筛选以后新增变量只需在变量组中添加SNOWY_ENV_XXX即可部署脚本永远不用改。脚本会自动用sed去掉前缀写入 env.conf 的仍然是SERVER_PORT8082等无前缀变量名和application.properties中的${SERVER_PORT}完全匹配。2安全提醒密码类变量务必开启“私密模式”防止泄露关键信息。3备注设置为“私密模式”的变量再次编辑的时候无法查看和编辑只能删除后重新添加。因此建议只对敏感数据设为“私密模式”。创建完成后回到后端部署流水线的编辑页面。点击 “变量和缓存” → “通用变量组” → “关联变量组”选择snowy-test-env-config-v2。以上在云效变量组中的配置的变量如DB_HOST、DB_PASSWORD等需要传递给服务器上的 Java 进程才能生效。Spring Boot 能读取系统环境变量因此可以通过 systemd 的EnvironmentFile来加载这些变量。4.2 修改服务器systemd文件接下来需要手动修改 systemd 服务文件只需处理这一次登录服务器编辑/etc/systemd/system/snowy-web-app.service在[Service]部分添加一行EnvironmentFile/etc/snowy/env.conf[Unit] DescriptionSnowy Web Application Afternetwork.target mysql.service redis.service [Service] Typesimple Userroot Grouproot WorkingDirectory/root/project/snowy/logs/backend EnvironmentJAVA_HOME/usr/lib/jvm/java-21-openjdk # 添加下面这行其它的和之前一样 # 可以加上-如果文件不存在systemd 不会报错只是忽略该配置 EnvironmentFile-/etc/snowy/env.conf # 环境切换测试环境用 --spring.profiles.activetest正式环境将下面改为 --spring.profiles.activeprod ExecStart/usr/bin/java -jar /root/project/snowy/backend/api/snowy-web-app.jar --spring.profiles.activetest Restarton-failure RestartSec10 [Install] WantedBymulti-user.target踩坑提醒systemd 不支持行内注释EnvironmentFile等指令后面不能写# 注释systemd 会把#及其后面的内容当作文件路径的一部分导致文件找不到。又因为使用了-前缀忽略加载错误systemd 会静默跳过不会报任何错误非常隐蔽。保存后执行systemctl daemon-reload这一步完成后以后就不需要再改 service 文件了。5. 配置主机部署5.1 基础配置新建“部署”阶段添加“主机部署”任务。制品选择上一步的Artifacts_${PIPELINE_ID}主机组选择目标服务器主机组下载路径/tmp/backend-artifact.tgz先下载压缩包后面再解压一开始按照DeepSeek给的方案写的下载路径/root/project/snowy/backend/api/snowy-web-app.jar覆盖原 jar 包这个不行是错的不要直接下载到.jar路径否则会损坏文件。执行用户root部署脚本这个shell脚本是部署的关键5.2 部署脚本重点这里需要在云效部署脚本中动态生成/etc/snowy/env.conf每次云效部署时在主机部署任务的部署脚本中将变量组中的实际值写入/etc/snowy/env.conf文件覆盖旧内容。这样 systemd 启动服务时就能读取到最新的环境变量。注意云效中这个shell脚本如果内容太长会报错 “由于系统限制执行脚本已经超过2000字符的最大长度建议通过执行shell脚本完成部署指令”#!/bin/bashset-e# 1. 定义路径 TMP_TGZ/tmp/backend-artifact.tgzJAR_PATH/root/project/snowy/backend/api/snowy-web-app.jarENV_FILE/etc/snowy/env.conf# 2. 解压制品并移动 JAR echo 制品文件:$TMP_TGZls-lh$TMP_TGZecho 解压制品...tar-xzf$TMP_TGZ-C/tmpecho 移动 JAR 文件到目标位置...mv-f/tmp/snowy-web-app-3.0.0.jar$JAR_PATHecho JAR 文件已放置:$JAR_PATHls-lh$JAR_PATH# 3. 生产环境变量配置文件关键步骤 echo 生产环境变量配置文件$ENV_FILEmkdir-p/etc/snowy# 确保目录存在# DeepSeek给出的很笨拙很low的方法。如果这么写相当于是以后新增的配置项不光要修改变量组的内容还要修改这个部署脚本的内容太麻烦了吧# cat $ENV_FILE EOF# SERVER_PORT${SERVER_PORT}# DB_HOST${DB_HOST}# DB_PORT${DB_PORT}# DB_NAME${DB_NAME}# DB_USER${DB_USER}# .....# EOF# 能不能有办法直接读取到变量组的全部内容呢后续要修改的话就只修改变量组。然后DeepSeek告诉我这个样子....# 但我还是觉得这个方式很笨拙很low难道就没有更友好更优雅的方式解决这个问题吗# 郑重说明如果后期没找到更合适的方案如果新增了其它配置比如ES的配置记得给下面添加对应的前缀# env | grep -E ^(SERVER_PORT|FRONT_URL|BACKEND_URL|DB_|REDIS_|RABBITMQ_|KAFKA_) $ENV_FILE# 最终思考后想到的方案统一前缀方案只筛选 SNOWY_ENV_ 开头的变量云效中的变量组已添加SNOWY_ENV_前缀# 在这里去掉前缀后写入 env.conf# 以后新增变量只需在云效变量组中添加 SNOWY_ENV_XXX 即可此处无需再修改。完美env|grep^SNOWY_ENV_|seds/^SNOWY_ENV_//$ENV_FILE# 可选查看文件内容调试用私密模式的变量会显示****cat$ENV_FILE# 4. 重启服务 echo 重启服务...systemctl restart snowy-web-appsleep5systemctl status snowy-web-app --no-pagerecho 后端部署完成关于打包部分的关键说明云效下载制品时无论打包的是什么文件都会将其打包成.tgz。所以必须先下载为.tgz解压后得到真正的 JAR再移动覆盖。注意tar -xzf $TMP_TGZ -C /tmp使用mv -f强制覆盖避免提示“overwrite”导致脚本卡死踩坑点。5.3 踩坑过程关于统一前缀方案的演进过程上面脚本第 3 步中的env | grep ^SNOWY_ENV_ | sed s/^SNOWY_ENV_//看起来很简单但实际是经过多次踩坑和思考才得出的方案记录一下踩坑过程方案一已被淘汰逐个变量写入最初 DeepSeek 给出的方法是手动列出每个变量cat$ENV_FILEEOF SERVER_PORT${SERVER_PORT}DB_HOST${DB_HOST}DB_PORT${DB_PORT}DB_USER${DB_USER}...... EOF问题每新增一个配置项不光要修改变量组的内容还要修改这个部署脚本的内容太麻烦了。方案二已被淘汰正则匹配多个前缀后来改用env | grep从环境变量中筛选env|grep-E^(SERVER_PORT|FRONT_URL|BACKEND_URL|DB_|REDIS_|RABBITMQ_|KAFKA_)$ENV_FILE问题虽然不用手动列变量了但正则中的前缀列表仍需维护。如果新增了其它配置比如 ES 的ES_前缀还得修改部署脚本添加对应的前缀。方案三当前方案统一 SNOWY_ENV_ 前缀 sed 去前缀env|grep^SNOWY_ENV_|seds/^SNOWY_ENV_//$ENV_FILE云效变量组中所有业务变量统一加SNOWY_ENV_前缀部署脚本只匹配SNOWY_ENV_再用sed去掉前缀。写入 env.conf 的是无前缀的SERVER_PORT8082和application.properties中的${SERVER_PORT}完全匹配。以后新增变量只需在云效变量组中添加SNOWY_ENV_XXX即可部署脚本永远不用改。5.4 兼容服务器手动部署脚本注意如果原来服务器上的手动部署的脚本依然要保留使用为了兼容云效流水线需要对原有的服务器上的发布脚本部分调整如下# echo 更新软链接...# 升级版本时如从 3.0.0 升级到 3.0.1记得同步修改脚本中的文件名# ln -sf $BACKEND_DIR/snowy-web-app/target/snowy-web-app-3.0.0.jar $JAR_LINK# 兼容云效流水线改为直接复制覆盖echo 复制JAR包cp-f$BACKEND_DIR/snowy-web-app/target/snowy-web-app-3.0.0.jar$JAR_LINK调整原因云效的“下载制品”会直接覆盖软链接文件破坏链接关系使其变成普通文件。如果服务器上保留了旧 JAR 和软链接云效覆盖后可能留下一堆孤立的文件逻辑混乱。cp -f复制虽然会多占用一份磁盘空间一个 JAR 约 100-200 MB但它是普通文件任何部署方式手动或云效都可以直接覆盖不会有“软链接 vs 普通文件”的冲突。记得先删除已有的软链接rm -rf snowy-web-app.jar如果以后不再使用服务器上的手动部署脚本那就忽略这一步。6. 保存并运行点击“保存并运行”选择分支观察构建和部署日志。查看详细日志查看流水线任务日志在流水线运行详情页点击每个任务如“Java 构建”、“主机部署”即可看到实时输出。查看服务器上的应用日志journalctl-usnowy-web-app-n100--no-pagertail-f/root/project/snowy/logs/backend/app-log/log_error.log查看主机部署脚本的临时日志云效会在服务器上生成/tmp/rdc_deploy_command_*.log可登录服务器查看。流水线执行成功后登录服务器检查# 确认 JAR 文件已更新ls-lh/root/project/snowy/backend/api/snowy-web-app.jar# 确认服务正常运行systemctl status snowy-web-app# 测试 APIcurlhttp://127.0.0.1:8082/api/auth/getCaptcha若一切正常则后端自动化部署流水线配置完成。备注经过测试在云效上部署比直接在服务器上部署要慢得多。原因云效的构建流程在云端容器中执行每次构建需要经历容器调度启动、依赖下载Maven/npm 包从远程仓库拉取而服务器本地通常已有缓存、制品打包上传到云效存储、再从云效存储下载到目标服务器等多轮网络传输此外云端构建资源是共享的高峰期可能还需要排队等待。而直接在服务器上部署时编译环境已就绪Maven 本地仓库有缓存构建产物也不需要经过网络中转直接本地操作即可。因此可以根据情况灵活选择在云效部署还是直接在服务器部署各有利弊7. 关于环境变量如何配置的思考在云效流水线的部署和各种踩坑过程之后终于部署成功了其中很长一段时间是卡在了snowy项目读取数据库环境变量部分。事后回顾整个配置链路产生了一个疑问值得记录和思考。7.1 问题的提出上面 4.1 中在云效配置了变量组4.2 中在 systemd 中配置了EnvironmentFile-/etc/snowy/env.conf5.2 中的部署脚本把变量组的内容写入 env.conf。云效部署成功后查看服务器上的/etc/snowy/env.conf和/root/project/snowy/git/snowy_backend/snowy-web-app/src/main/resources/application-test.properties的内容完全一样。那为什么还要在云效配置变量组呢直接把 systemd 的EnvironmentFile指向application-test.properties不就行了吗# 既然内容一样这样改岂不更简单 EnvironmentFile-/root/project/snowy/git/snowy_backend/snowy-web-app/src/main/resources/application-test.properties7.2 技术上可行但不建议systemd 的EnvironmentFile只认KEYVALUE格式而application-test.properties恰好也是这个格式语法兼容。但这样做存在三个问题1路径耦合到 git 仓库这个路径在 git 仓库目录下。如果某天仓库迁移、目录重命名、执行git clean或重新 clone文件就没了。服务重启会直接挂掉且由于使用了-前缀忽略加载错误systemd 不会报任何明显错误非常隐蔽。2失去云效流水线的集中管控能力用变量组的核心价值是配置与代码分离通过流水线统一管控。场景变量组方案指向 properties 文件改数据库密码只改云效变量组重新部署即可必须登录每台服务器改文件多台服务器变量组统一分发每台服务器各自维护审计追溯云效有操作记录无记录敏感信息安全云效支持私密模式明文存放在服务器文件中3混淆了开发配置和运维配置的职责边界application-test.properties是开发侧维护的定义各环境的基础参数/etc/snowy/env.conf是运维侧维护的由部署流程写入两者内容恰好相同只是当前阶段恰好如此。以后可能出现同一分支部署到不同服务器但每台服务器配置不同临时切换数据库地址做调试紧急修改密码这些场景下运维需要不修改代码文件就能调整配置。7.3 根本原因分析产生上述疑问的根本原因是application-test.properties本来就不应该存在。在手动部署阶段为了快速跑通流程采用了application-test.properties这种简单粗暴的方式——把数据库密码等敏感信息直接写在 properties 文件里Spring Boot 启动时直接读取。这导致开发人员能看到测试/生产环境的数据库密码存在安全隐患也模糊了开发和运维的职责边界。正确的架构应该是层面职责包含的内容application.properties代码仓库定义配置结构即需要什么变量server.port${SERVER_PORT}、spring.datasource.urljdbc:mysql://${DB_HOST}:...云效变量组CI/CD 平台提供配置值即这些变量等于什么APP_SERVER_PORT8082、APP_DB_PASSWORDxxx环境配置值本来就应该由运维人员在云效后台配置成变量组流水线部署时读取变量组中的信息注入到运行环境。开发人员严格来说不应该看到服务器上的敏感配置信息。7.4 当前状态与最终形态当前处于从手动部署向云效流水线部署的过渡阶段所以出现了两份一样的配置共存的情况。正如前面提到的“经过测试在云效上部署比直接在服务器上部署要慢得多因此可以根据情况灵活选择在云效部署还是直接在服务器部署”两种部署方式暂时并存。最终应该的干净形态删除服务器上的application-test.properties在代码仓库的.gitignore中确保application-test.properties被忽略已经是了代码仓库只保留application.properties含占位符application-dev.properties本地开发用不含敏感信息或只含本地信息云效变量组是唯一的环境配置来源部署脚本将变量组内容写入/etc/snowy/env.confsystemd 通过EnvironmentFile注入给 Spring Boot如果打算彻底切换到云效流水线部署application-test.properties就可以彻底退场了。