ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从手动到自动化:集成Hadess制品下载与部署的完整实践

从手动到自动化:集成Hadess制品下载与部署的完整实践 落地这个需求之前我先说说背景。团队里跑着一套基于Arbess的自动化作业与发布编排平台日常要对接的周边系统越来越多其中最频繁的人工操作就是“登录Hadess、挑版本、下载制品、再传到目标机器上解压部署”。一次两次还能忍等环境多了、版本迭代快了这套手动流程就成了发布链路上最大的瓶颈也是出错重灾区。所以我把“集成Hadess、下载制品、自动化部署”这个需求提上了日程做完之后整个发布链路顺畅了很多。这篇就把我这次的完整实施过程、关键设计思路和踩过的坑分享出来给准备做类似集成的朋友当个参考。1. 为什么一定要做这个集成手动下载制品的真实痛点先说清楚Hadess在链路里扮演的角色。Hadess是我们内部的制品管理服务负责统一存放构建阶段产出的各类交付物比如Spring Boot的jar包、前端打包后的dist目录压缩包、Docker镜像的离线tar文件、数据库变更脚本等等。每次构建完成后制品会自动归档到Hadess并生成版本号后续部署阶段再从Hadess拉取指定版本。这个模式本身没什么问题但“拉取”这个动作如果一直靠人去做问题就大了。我归纳了一下手动模式下最常见的几类痛点。第一人为选错版本。发布窗口通常都在晚上或者上线窗口人一紧张手一抖点了相邻的版本号部署上去之后功能对不上回滚又得重新下载整个窗口被拉长。这种事出过一次团队就再也不想靠肉眼去确认版本了。第二制品下载和服务器上传的割裂。本地下载完制品还要scp到目标机器再ssh上去解压、重启、检查进程一连串命令全靠人执行任何一个步骤漏了或者顺序错了都会造成发布失败。而且这种操作没法留痕事后追溯到底谁在哪台机器上部署了什么版本全靠聊天记录和记忆非常不靠谱。第三环境差异导致同样的命令在不同环境表现不一致。有的环境磁盘路径不同有的环境解压工具版本不同有的环境JVM参数配得不一样这些差异在手动模式下都是“隐雷”平时不炸偏偏在紧急发布的时候炸。把Arbess和Hadess集成起来之后上述问题全部变成代码和配置。Arbess负责任务编排和调度Hadess负责制品存储中间通过API完成认证、查询、下载、校验、传输、部署、验证这一整套闭环。版本号作为参数传入部署到哪个环境由目标主机分组决定每一步的执行结果都有日志和状态记录。人只需要触发任务并盯结果不再介入中间过程。这套集成做完我最大的感受是部署这件事从“靠人细心”变成了“靠流程兜底”。版本选错参数模板里写死任务跑完自动校验。漏传文件下载脚本统一解压并核对文件数与MD5。环境差异目标主机的部署脚本独立管理下载和部署解耦。下面从设计开始一步步拆。2. 集成前的设计功课权限、版本策略、网络边界三件事真正动手写脚本之前我花了不少时间在设计上。很多集成做出来以后不好用、不敢用问题不是出在代码上而是设计阶段考虑得太少。这次我重点想清楚了三个点。2.1 用独立服务账号而不是个人账号去访问Hadess这是第一优先级。初期方案里有人提议直接用运维同事的个人账号走API认证省事儿但被我拦住了。原因很简单用个人账号账号离职、改密、权限变更都会直接影响自动化任务而且一旦脚本里有凭据泄露没法定位是哪个业务的访问行为。最终做法是创建了一个专属服务账号只授权了读取制品和下载制品的权限没有写权限、没有删除权限。这保证即使这个账号被滥用最坏情况也只是多下载一些制品不会破坏Hadess里的原始数据。服务账号的Token我们存放在Arbess的密钥管理里不在脚本、配置文件和日志中出现明文。2.2 版本号用参数化模板避免“拉最新”和“拉全部”这是一个很容易被想简单的点。刚接手时有人问直接从Hadess拉取最新版本不就完了这会在发布时出大问题。最新版不一定对应你这次要发布的功能分支尤其在多特性并行开发的时候制品版本和代码分支的关系很微妙“最新”是个动态相对概念不能用它做部署依据。我采用的方案是版本号参数化。Arbess任务在触发时从外部传入目标版本比如通过手动触发时填写的输入框或者上游构建完成后自动携带的版本标识。任务内部不做任何“猜测最新版本”的逻辑只根据传入的版本号去Hadess查询、确认存在、下载、校验。这样每一次部署的版本都是清晰明确的回滚时也只需要把版本号改成上一个已验证的版本重新跑一遍任务即可。还有一个细节是版本保留策略。Hadess里的构建产物会很多全部拉取下来不仅慢也会把目标主机的磁盘空间打满。所以我们的任务里加了一个参数保留最近N个版本的制品其他历史版本在部署前自动清理。这个参数设置为3既保证回滚时有货可用也避免磁盘被撑爆。2.3 先理清网络边界和下载通道Hadess到底部署在哪个网络区域、Arbess任务执行节点能否直连、目标业务服务器能不能访问Hadess这三个问题直接决定了集成架构。我们公司网络分区比较多Arbess任务节点在自动化控制区Hadess在制品存储区目标业务机器在业务应用区。三个区域之间有防火墙规则也不是全部互通。我把这个作为设计前置条件来验证而不是等写完了代码再去问网络。实际结论是Arbess任务节点能够直连Hadess的API端口但目标业务机器出于安全策略不能直接访问Hadess。所以整体架构就定了Arbess统一负责从Hadess下载制品下载到任务节点本地或共享存储做一个暂存再由部署阶段把暂存的制品分发到各目标主机。这个设计还有个额外好处制品只经过Arbess任务节点这一个“咽喉”在节点上可以做病毒扫描、文件校验、格式审核这些安全动作然后再下发而不是让每台业务机器自己去Hadess拉。安全性更高运维上也更好监控。3. 核心落地从Hadess查询到制品下载的完整实现设计定完了进入实施环节。这部分是最关键的我按照“认证→查询→下载→校验→落位”这条链路逐步完成每一步都配了实际代码和踩坑说明。3.1 认证流程的细节以及Token刷新陷阱Hadess的API认证方式用的是Bearer Token机制。服务账号创建后通过一个特定的认证端点换取临时访问Token这个Token有时效性过期之后必须重新获取。很多初次接的人会忽略Token过期问题把Token硬编码在配置文件里结果一到过期时间任务就开始报401。我直接在Arbess的任务脚本里封装了获取和缓存Token的逻辑。首次调用时先检查内存/临时文件里是否有未过期的Token如果存在就直接拿来用如果不存在或者剩余有效期小于5分钟就去认证端点重新申请并更新本地缓存。这个过程对上层业务透明任务执行时不需要关心Token本身。实际的代码片段类似下面这样#!/usr/bin/env bash # 获取Hadess访问Token带缓存逻辑 TOKEN_FILE${HADESS_TOKEN_CACHE:-/tmp/hadess_token.cache} get_token() { if [ -f $TOKEN_FILE ]; then local expires_at expires_at$(cat $TOKEN_FILE | jq -r .expires_at // 0) local now now$(date %s) if [ $expires_at -gt $((now 300)) ]; then cat $TOKEN_FILE | jq -r .token return 0 fi fi local resp resp$(curl -sS -X POST $HADESS_AUTH_URL \ -H Content-Type: application/json \ -d {\username\:\$HADESS_SERVICE_USER\,\password\:\$HADESS_SERVICE_PASSWORD\}) local token token$(echo $resp | jq -r .access_token) local expires_in expires_in$(echo $resp | jq -r .expires_in // 3600) echo $resp | jq --arg t $token --argjson e $(( $(date %s) expires_in )) \ {token: $t, expires_at: $e} $TOKEN_FILE echo $token }这段代码里有两个实际注意点。第一缓存文件路径用了环境变量可覆盖的方式这样不同Arbess任务可以共用一个缓存避免每个任务都重新认证导致Hadess侧接口被频繁调用第二判断是否续期的阈值设在5分钟而不是等完全过期为的是避免网络抖动导致刚好在过期节点上请求失败。3.2 查询可用版本三步确认再动手下载之前先确认版本是否存在这一步不能省。我见过不少脚本直接拿版本号拼下载URL结果Hadess返回404日志里只有一段堆栈排查半天才发现是版本写错了。所以我在任务里加了前置的版本确认逻辑。查询接口一般支持按项目名、制品名、版本号组合筛选。我的做法是调一次“按条件查询制品版本列表”的接口拿返回结果检查版本号是否在列表里同时拿到该版本的存储路径、文件大小、MD5值等元数据为后续下载和校验做准备。# 查询制品是否存在并获取元数据 query_artifact() { local project_name$1 local artifact_name$2 local version$3 local token$4 curl -sS -X GET \ ${HADESS_API_URL}/api/v1/projects/${project_name}/artifacts/${artifact_name}/versions/${version} \ -H Authorization: Bearer ${token} \ -H Accept: application/json }这里我强烈建议把“查询”和“下载”做成两步查询结果里有一个显著字段标识制品状态。有些构建任务是异步归档的那次构建状态可能还是“处理中”如果你不做状态检查直接下载轻则下载失败重则下载到半个文件。所以查询之后必须确认状态值等于比如“已归档”才允许进入下一步别贪图少调一次接口。3.3 下载、MD5校验和文件落位下载这一步没什么高深技术就是把查询阶段拿到的下载地址拉下来但工程细节不少。第一个细节是使用重定向跟随和超时限制。Hadess的制品下载地址可能是临时签发的带有过期时间参数请求时服务器还可能返回302跳到对象存储。curl必须加-L参数处理重定向同时设置--connect-timeout和--max-time防止因为网络问题导致任务卡死挂起。曾经我在测试环境中没加超时限制某次Hadess依赖的对象存储响应异常curl在那儿傻等了几十分钟把整个发布窗口全拖垮了。第二个细节是下载后的MD5校验。构建侧在上传制品到Hadess时会计算一次MD5我们在下载完成后必须重新计算并比对。这一步不是走过场而是防止下载过程中文件损坏、被截断或者被篡改。实际场景里最常见的反倒是文件被截断比如磁盘满了、网络中断curl返回错误但文件已经写了一半如果不校验这个半成品会被直接拿去部署。# 下载制品并进行MD5校验 download_artifact() { local file_url$1 local target_path$2 local expected_md5$3 local token$4 curl -L -sS \ --connect-timeout 10 \ --max-time 1800 \ -o $target_path \ -H Authorization: Bearer ${token} \ $file_url local real_md5 real_md5$(md5sum $target_path | awk {print $1}) if [ $real_md5 ! $expected_md5 ]; then echo MD5校验失败: 期望 $expected_md5, 实际 $real_md5 rm -f $target_path exit 1 fi echo MD5校验通过: $real_md5 }如果校验失败我选择直接删除半成品文件并让任务失败退出而不是留着文件下次继续用。这样做的好处是避免被污染的缓存文件反复触发同样的问题也给排查问题留一个干净的起点。第三个细节是文件落位的目录结构。我统一使用这种格式${WORKSPACE}/downloaded/${project_name}/${artifact_name}/${version}/版本号放在目录层级里这样同一项目不同版本可以共存后续回滚时不需要先删除当前版本再下载旧版只需要让部署阶段切到对应版本的目录即可速度更快也更安全。4. 部署动作接入下载之后的关键闭环制品下载完成只是第一步真正体现自动化价值的是把“下载—分发—解压—启停—健康检查”串成一条完整的部署管线。这一节单独拎出来讲是因为这里最容易出现“下载自动化了部署还是半自动”的情况。4.1 把部署动作从下载脚本里完全拆出来很多初版集成会把部署命令直接写在下载脚本后面比如“下载完就unzip”“unzip完就重启服务”。我一开始也犯过这个错后来发现这种做法的耦合度太高。理由很简单不同环境的部署要求不同比如测试环境要保留日志目录生产环境要切换软链并校验双实例状态不同制品的部署方式也不同jar包可能要替换启动参数前端包则要处理静态资源缓存和nginx reload。如果部署逻辑和下载逻辑耦合在一起每加一个环境或者一种部署类型都要改整段脚本风险很大。所以最终架构拆成了三段第一段下载阶段。只负责和Hadess交互拿到原始制品并校验输出“制品目录版本号文件清单”。第二段分发阶段。把制品目录同步到目标服务器的临时目录比如/data/deploy/tmp/{version}/。第三段执行阶段。在目标服务器上运行与环境相配套的部署脚本完成备份、解压、停服、替换、启动、检查。Arbess任务里通过阶段划分来编排这三段每一段都可以独立重跑而且支持只重跑失败阶段不用从头再来。这个收益在实际使用中非常明显某次部署到执行阶段时有一台机器重启失败修完主机问题后我只重跑了执行阶段制品不用重新下载一两分钟就搞定了。4.2 分发阶段用并行控制别一把梭分发动作最简单也最容易踩坑。如果你有几十台目标主机脚本里直接循环scp大概率会遇到几台机器超时或者中断整个循环被卡死。我用的方案是控制在5台左右并发并且每台主机的分发结果独立记录失败的主机单独标记后续重试时只处理失败列表。# 用xargs -P做并发控制分发 cat ${target_hosts} | xargs -I {} -P 5 bash -c deploy_to_host $ _ {}分发到每台机器后第一步是在目标机器本地再做一次MD5校验。这里为什么要做第二次校验因为scp/rsync走网络传输也可能出问题尤其是大文件。在分发阶段校验一次可以在进入部署动作前就把问题挡掉避免解压到一半发现文件损坏。另外分发目录的权限要提前设置好。我遇到过目标机器上服务账户和部署账户不是同一个的情况制品文件分发过去后属主不对服务启动时读不了文件报了一堆权限错误。后来统一在分发后执行一次chown -R deploy:deploy并确保目标目录的写权限属于部署账户。4.3 部署执行脚本里的“先备份后替换”目标机器上的部署脚本是另一套工程这里只说和Hadess集成强相关的两个点备份策略和软链切换。备份策略替换前先把现网版本目录整个拷贝为一个带时间戳的备份目录比如app_20250617120000_before_deploy。备份只保留最近3份超出就删。有人觉得备份耗时想跳过我的建议是磁盘穷一时可以别在部署脚本里省备份。因为自动化部署跑得再快一旦新版本有严重问题回滚的时候你没有旧版本备份就得重新从Hadess下载旧版本绕一大圈。软链切换我强烈推荐把“当前版本”通过软链来暴露而不是直接把文件复制到固定的应用程序路径。也就是说每次部署时制品解压到带版本的目录然后执行两步ln -sfn /data/app/releases/${version} /data/app/current-sfn这个组合要注意-s创建软链-f强制覆盖旧链-n让系统把已有软链当作普通文件处理而不是跟随它。这个小小的参数组合能避免很多坑比如目标位置已经存在一个指向目录的软链不加-n的话我们创建的软链可能被放到那个目录里面去。软链到位后重启服务再去触发健康检查。这里有个小技巧健康检查不是只看进程在不在要看业务就绪状态。我沿用积微健康检查接口如果部署的是Web服务就周期性地访问/healthz或者/actuator/health端点只有返回预期JSON才认为部署成功。5. 自动化任务在Arbess里的编排实践集成逻辑写好了需要挂到Arbess上才能跑起来。Arbess里的任务概念大概可以理解成“一个可编排的作业流水线”支持定义多个阶段、多个步骤、参数传递和状态流转。我把集成做成一个模版任务这里讲讲关键的编排思路。5.1 任务参数的设计让使用者只关注版本号Arbess的任务可以定义输入参数。我给这个集成任务设计了以下几类参数目标任务环境比如测试环境、预发环境、生产环境任务根据这个参数自动选取对应的目标主机清单。目标制品版本号必填不提供默认值。制品项目名和制品名一般作为常量绑定在任务模版里一个项目一个任务模版避免每次触发都要填一堆东西。部署超时时间默认为30分钟超过则自动杀掉任务并发送告警。参数设计的原则是尽量让触发任务的人只填他真正关心的内容。如果你的任务需要填10个字段才能跑那使用者一定会填错而且会开始找理由绕过你的流程。我一开始的参数比较多后来逐步收敛成上面的几个跑起来明显顺畅多了。5.2 阶段划分与失败处理策略前面说过下载、分发、执行是三个阶段。在Arbess里我让每个阶段的输入输出都通过上下文变量传递阶段间不直接写临时文件到共享内存而是通过任务工作区和约定的目录结构交换数据。这样阶段可以独立重试、独立查看日志也方便定位问题到底出在哪一段。失败处理策略方面我建议这样设置下载阶段失败不重试其他阶段直接终止任务分发阶段失败仅对该阶段的重试或失败主机单独重试不要重新下载执行阶段失败先人工介入查看失败主机日志因为执行阶段往往是服务启动失败而不一定是部署脚本有问题盲目重试可能掩盖真实原因。在Arbess里可以用判断节点去实现这些策略本质上是一个状态机。我最终对外的表现就是任务跑完显示绿或者红红了以后点开日志能直接看到是下载失败还是哪台主机部署失败而不是一个笼统的“失败”需要到处找线索。5.3 日志规范化和制品信息回传自动化任务最重要的排障依据就是日志。我的脚本里统一了日志格式所有输出都带时间戳、阶段名、主机名、状态类似这样2025-06-17 18:30:22 [下载] 开始查询制品: proj-api:2.4.1 2025-06-17 18:30:25 [下载] 制品已归档大小: 342MBMD5: 8f3a... 2025-06-17 18:30:57 [下载] MD5校验通过文件落位: /workspace/downloaded/proj-api/2.4.1/ 2025-06-17 18:31:02 [分发] 目标主机: 10.20.1.11 分发完成耗时8s 2025-06-17 18:32:41 [执行] 10.20.1.11 服务启动成功健康检查通过这套格式被我们后续接入了日志平台任务结束后还能通过关键字去聚合统计比如“健康检查通过”的次数、平均部署耗时等。做久了你会发现日志没有结构化的自动化是没有灵魂的自动化出了问题你连分析的材料都没有。制品的元数据信息我还会回传到Arbess的任务扩展字段里。任务结束后在Arbess的任务界面可以直接看到本次部署了哪个项目的哪个版本、目标环境、目标主机数量和校验值不需要进到worker日志再翻。这对审计和事后追溯帮助很大。6. 上线初期踩过的坑认证缓存、并发分发和大文件下载这一段我专门讲在上线初期踩过的三个坑每个都真实影响到线上发布过希望你能绕过。6.1 认证缓存文件权限引发的诡异失败有一次任务在测试环境跑得好好的一跑到生产环境的Arbess任务节点就报401。排查了半天最终发现是Token缓存文件权限不对。生产节点上Arbess任务进程用户是arbess-runner但缓存文件是从一个旧任务里继承来的属主是root权限是600arbess-runner没法读取缓存每次只能重新认证。这本身不致命致命的是重新认证也失败因为缓存文件没法覆盖写入。这个问题反映了自动化任务里文件权限管理的隐蔽性不仅是脚本自身要可执行临时文件、缓存文件、下载目录、日志目录所有被任务读写的位置权限必须和任务运行用户匹配。我在Arbess的每个任务工作区初始化脚本里加了一句mkdir -p ${WORKSPACE} ${CACHE_DIR} ${LOG_DIR} chown -R ${RUN_USER}:${RUN_USER} ${WORKSPACE}从此这类问题再没出现过。6.2 并行分发时ssh连接数被打满第一版并行分发用的并发数设成了20想着速度快一点。结果跑到一半目标主机所在的跳板机ssh连接数直接被打满其他运维操作全部超时。这个问题是这样的你的部署任务只是它上面众多业务之一把并发开得太大会把共享的跳板机或管理通道资源抢光。后来我把并发控制参数做成可配置默认5跳板机繁忙时手动调成2反正不影响正确性慢几分钟而已。这里分享一个经验值单台目标机器分发大制品时带宽是主要瓶颈并发数再高也不会让单台机器的传输变快反而是给基础设施增加负担没必要追求过高的并发。6.3 下载大文件时的内存占用问题我们的最大制品是一个平台安装包接近3GB。最开始我用管道方式拿下载流去实时计算MD5考虑着“边下载边校验”更高效但实现上如果不注意缓冲区配置会占用大量内存。比如用curl加管道到md5sum在狭小的容器内存里跑一次就触发了OOM。后来改成先下载落盘、再读取文件计算MD5。虽然需要额外的磁盘空间和一次磁盘IO但内存占用非常稳定不会因为文件大小而波动。磁盘IO多出来的一次在百兆内网环境下几乎可以忽略。这里也提醒一下自动化任务运行环境的内存通常是有上限的处理大文件优先考虑磁盘换内存别把任务容器撑爆。7. 安全加固和配置分离项目上线前必做的几件事集成做到基本可用之后不要急着宣布完成。安全加固和配置分离是上线前必须处理的环节尤其是这种牵涉到制品下载和部署的核心链路。7.1 账号凭据绝不能出现在配置文件和脚本里脚本文件、Arbess任务的描述信息、日志输出任何地方都不允许出现明文用户名和密码。我统一把认证凭据放在Arbess的密钥变量里任务运行时通过环境变量注入。脚本里只引用变量名。这样即使脚本仓库被clone走也不会泄露任何敏感信息。# 正确用法从环境变量读取 HADESS_USER${HADESS_USER:?环境变量缺失} HADESS_PASSWORD${HADESS_PASSWORD:?环境变量缺失}加上:?语法的作用是如果环境变量没设置脚本直接报错退出避免用空值去请求Hadess产生一堆无意义认证失败日志。7.2 最小权限原则要贯彻到Hadess账号和Arbess任务两个层面Hadess服务账号刚才说过了只给读权限。Arbess任务层面也要做隔离不能所有项目共用一个任务模版和同一个目标主机列表。最好是每个业务线一个任务模版目标主机清单由业务线的维护者管理这样A项目的任务想误触发B项目的部署都做不到因为任务里根本没有B项目的主机信息。7.3 加一道“部署审批”而不是完全自动化这里要和“自动化”这个概念做一点平衡。我对自动化部署的定位是“执行自动化、审批不省略”。生产环境的部署动作在Arbess里配置了人工审批节点触发任务后先暂停由指定审批人在Arbess界面确认版本号无误后点击通过才继续执行下载和部署。这样既保证效率又给生产变更保留了必要的人为确认关卡。可能有人会觉得审批和自动化是矛盾的多了一道人工操作不够“自动”。但以我的经验生产环境还是应该保留审批这一环不是为了卡流程而是给版本确认、部署窗口确认一个正式的记录点。Arbess的支持也够审批动作本身有审计记录事后追溯时能清楚地看到“谁在什么时间批准了什么版本的部署”这是全自动流程很难做到的。8. 运行一段时间后回头看这套集成的边界与后续改进空间集成方案上线运行了大约一个季度发布效率提升明显但也在使用过程中暴露了一些设计边界。绕开这些边界继续扩展时有些地方我不建议做得太深。8.1 当前方案的适用边界这套方案最适用的是标准化的应用制品部署也就是说制品结构清晰、部署方式相对固定、目标环境差异可控的场景。比如Java服务加前端静态资源、Go二进制加配置、Python包等。如果你的部署模式特别复杂比如涉及数据库双写切换、跨机房多活、复杂依赖的服务编排这套简单三阶段模型就不够用了需要接入更专业的发布系统不要硬套。另外这套集成是“Arbess主动拉取”模型的产物。如果Hadess支持了Webhook回调那么下一步可以改为“构建完成自动触发Arbess任务”连手动触发那一下都可以省掉。这是很自然的演进路径。8.2 可以继续优化的三个方向第一部署结果的数据分析。前面提到日志已经结构化下一步可以在Arbess任务之上加一层统计面板按周/月维度统计各项目发布次数、成功率、平均耗时和失败分布。这些数据对研发效能改进很有价值。第二目标主机的动态扩缩容适配。当前目标主机清单是静态维护的一旦环境弹性扩缩容需要及时同步。这块可以做接口对接让Arbess从外部系统动态拉取当前主机列表而不是依赖手工维护的静态文件。第三制品的签名校验机制。目前只做了MD5一致性校验更严格的场景可以做数字签名验证保证制品确实是信任流水线构建出来的没有被中间过程篡改。MD5防的是传输损坏签名防的是信任边界被突破两者不是一个层级的安全保证。如果你们的合规要求高这条建议优先考虑。就我个人的使用体验来说把Hadess接入Arbess的最大价值并不在于“省了那几分钟下载时间”而在于部署过程变成了可重复、可追溯、可回滚的标准化流水线。过去发布靠人肉出了问题大家先回忆“上次是不是也这样”现在发布靠流程出了任何状况直接看任务节点日志五分钟内能定位到具体环节。如果你正在筹划类似的制品仓库和自动化平台集成希望这篇实战记录能帮你少走一点弯路。
返回列表