资讯动态

基于Jenkins+Kubernetes+Argo CD的返利APP持续交付与灰度发布实践

发布时间:2026/9/29 5:16:29 来源:尧图企业网站定制
1. 项目背景与整体设计思路做返利APP的这两年我最大的体会就是业务迭代快、发版频率高、出问题必须能快速止血。返利类应用本身有个特点——用户对价格、返利比例这些数据极其敏感一个返利计算逻辑的小改动直接影响用户在商品详情页看到的价格和预期收益。一旦线上出问题轻则用户投诉重则单量断崖式下滑。所以传统的一个月发一次版、发版当天熬夜盯着的流程根本撑不住这个节奏。我们当时的状态是代码仓库在GitLab测试环境手动部署生产环境靠运维用一堆脚本打包、分发、登录服务器替换镜像。每次发版至少需要两个人配合一个负责构建一个盯着服务器日志整个过程耗时一两个小时如果中间出问题时间翻倍很正常。更要命的是测试环境和生产环境配置经常不一致很多Bug在测试环境没暴露一上生产就开始闹脾气。这个项目要做的事说白了就是解决三件事第一把从代码提交到生产发版的整个过程自动化能用脚本做的绝不让人手动做第二引入Kubernetes作为统一的运行环境让测试和生产环境的差异尽可能缩小第三用GitOps的方式管理部署状态让每一次变更都有迹可循所有人都能看清楚当前线上跑的到底是什么版本。这套体系上线之后的效果是实打实的一次常规发版从原来的1到2小时压缩到10分钟左右而且发版过程中几乎不需要人盯灰度发布从原来手动摘流量、改配置变成了一键触发、自动观测、异常自动回滚。这篇文章把我踩过的坑和最终落地的方案完整记录下来给同样在做电商、返利、优惠券这类强业务属性的团队一个参考。适合的人群主要是运维开发、后端开发、或者准备从传统发布方式往DevOps转型的团队。2. 工具链选型与核心架构2.1 为什么选Jenkins Pipeline而不是其他CI工具团队在CI/CD工具选型上并没有太多纠结因为大家最熟悉的还是Jenkins。虽然现在GitLab CI、GitHub Actions、Drone这些工具都很成熟但我们的实际情况是Jenkins在企业内网部署最省事插件生态丰富遇到奇怪问题时能找到大量社区案例另一个关键点是团队里已经有几个熟练的Jenkins用户培训成本几乎为零。我重点要说明的是Pipeline这块。很多人第一次接触Jenkins会觉得它就是一个界面上点来点去的构建工具配几个Job每次手动点构建按钮。但Jenkins Pipeline的核心价值在于把整个构建流程写成代码也就是Pipeline as Code。这个看似不起眼的转变实际解决了一个大问题——流程不再依赖某个人的记忆所有人都能在代码仓库里看到完整的CI流程改流程要走Merge Request评审流程变更本身也留下了审计记录。pipeline { agent any stages { stage(流水线配置) { steps { echo Pipeline 已启动 } } } }这个例子虽然简单但它是整个体系的地基。后续所有构建、测试、打包、推送镜像、更新部署清单的操作全部挂在这条Pipeline上。用声明式语法写Pipeline对团队里的新人更友好因为它强制把结构固定成stages/steps的格式不会像脚本式语法那样自由到没法约束。2.2 Kubernetes在返利APP场景里的角色选Kubernetes的原因很朴素返利APP的后端服务越来越多商品抓取、价格同步、订单回调、返利结算、用户中心光核心服务就有七八个再加上定时任务、消息消费者用传统的虚拟机部署方式管理这么多服务的环境已经快失控了。Kubernetes提供的容器编排能力本质上解决的是环境的标准化这个老难题。我一直在和团队强调一个观点Kubernetes不是一个运维工具而是一个平台思维。它把服务器资源抽象出来应用只关心自己需要什么镜像、多少个副本、需要什么配置剩下的事情交给Kubernetes调度。在我们的场景里每个微服务都跑在独立的Deployment中配置通过ConfigMap和Secret挂载流量通过Service暴露网关入口统一走Ingress。有人可能会问我们的服务量并不大有必要上Kubernetes吗我的答案是虽然服务数量不多但环境数量多。开发环境、测试环境、预发环境、生产环境再加上不同团队负责的业务线如果每个环境都用独立的虚拟机脚本去管理维护成本会指数级上升。Kubernetes的好处是你可以用Namespace隔离环境一份Deployment清单可以在不同环境里运行差异只是Namespace和配置不同。apiVersion: apps/v1 kind: Deployment metadata: name: rebate-api namespace: rebate-prod spec: replicas: 3 selector: matchLabels: app: rebate-api template: metadata: labels: app: rebate-api spec: containers: - name: rebate-api image: harbor.internal/rebate/api:v1.2.3 ports: - containerPort: 8080 envFrom: - configMapRef: name: rebate-api-config这是生产环境下返利结算服务的Deployment定义replicas是3镜像版本是v1.2.3。这份清单一旦提交到Git仓库GitOps工具就会自动把它同步到集群里替换掉旧的版本。2.3 GitOps的核心思想Git仓库是唯一事实来源GitOps这个理念我第一次接触时觉得它也没什么特别的不就是在Git里存部署清单然后让工具自动同步到Kubernetes集群吗但真正用起来之后我才发现它的价值不在于自动化而在于可审计、可回滚、可控权这三个特性。传统发布方式的流程是运维拿到构建产物登录服务器执行命令。这个过程中谁在什么时候改了什么配置没有记录出了问题也只能对着终端历史翻来翻去。而GitOps模式下任何需要变更的事情都要通过Pull Request来发起你要改镜像版本提交一个PR你要改配置项提交一个PR你要调整副本数提交一个PR。集群里的实际状态始终朝着Git仓库里描述的状态收敛。这里我要展开说明一下收敛这个词的含义。GitOps工具比如我们用的Argo CD会周期性轮询Git仓库对比仓库里的目标状态和集群里的实际状态。如果两者不一致工具会判定为OutOfSync然后根据配置自动或手动地把集群状态拉回到目标状态。这样一来线上出了故障排查问题的第一步不是登录服务器而是看Git仓库里最后一次变更记录。# 查看当前同步状态 argocd app get rebate-api # 输出结果中会显示 # HEALTH STATUS: Healthy # SYNC STATUS: OutOfSync看到OutOfSync就能立刻知道线上和期望状态不一致于是去查最后一次变更。这个思维方式转变让整个运维团队从灭火队变成了管仓库的人。2.4 环境规划与仓库设计在动手搭建之前我花了不少时间规划环境划分和仓库结构。之前的经验教训告诉我源码头松散会导致后面维护成本暴增所以这一步必须一开始就定好规范。我们的环境分为四套dev、test、staging、prod。前两套给开发和测试用staging用于预发布验证prod就是生产。每套环境对应的Kubernetes Namespace是rebate-dev、rebate-test、rebate-staging、rebate-prod。仓库设计上采用一个应用一个仓库、部署清单单独放的原则代码仓库gitlab.internal/rebate/api存放源码部署仓库gitlab.internal/rebate/deploy存放Kubernetes YAML清单为什么部署清单不放同一个仓库因为我发现返利项目里多个服务会共享一些全局配置比如网关规则、监控配置如果放在代码仓库里改一个服务的配置需要去翻对应服务的仓库太散。统一放在deploy仓库里按目录区分服务和环境结构一目了然。deploy/ ├── base/ # 公共基础配置 │ ├── kustomization.yaml │ └── namespace.yaml ├── apps/ │ ├── rebate-api/ │ │ ├── dev/ │ │ ├── staging/ │ │ └── prod/ │ └── rebate-task/ └── clusters/ └── prod/ └── kustomization.yaml这个结构配合Kustomize使用公共部分写在base里各个环境的差异通过overlay覆盖。这么做的好处是新增一个环境的复杂度被控制在很小的范围内而且Kustomize是Kubernetes原生的渲染方式不需要额外引入Helm模板引擎。3. Jenkins Pipeline 全流程实操3.1 Pipeline 阶段的划分与设计逻辑要让Pipeline靠谱阶段划分必须清晰。我最终的方案把整个流水线分成七个阶段每个阶段只做一件事上一阶段失败就直接中断避免脏数据流入下一步。阶段划分如下代码拉取 → Maven构建 → 单元测试 → 镜像构建与推送 → 更新部署清单 → GitOps同步 → 环境验证每个阶段的职责代码拉取从GitLab拉取指定分支或标签的源码Maven构建执行mvn clean package -DskipTestsfalse同时把测试报告归档单元测试这是质量关口。测试覆盖率低于阈值就失败我在Jenkins里用JUnit插件解析测试结果镜像构建与推送用Dockerfile构建应用镜像打上版本号推送到Harbor镜像仓库更新部署清单修改deploy仓库里的镜像版本号提交并推送这里用的是Git命令操作GitOps同步触发Argo CD同步让Kubernetes集群里的工作负载更新到新版本环境验证调用一个健康检查接口确认应用启动成功返回200才继续这里有一个很多人容易犯的错误镜像标签一定要唯一。我见过团队直接用latest作为镜像标签结果生产环境拉到的镜像不可控出现问题想回滚都找不到对应版本。我们的做法是镜像标签使用${BUILD_NUMBER}-${GIT_COMMIT_SHORT}比如rebate-api:184-a3f8c29既能对应构建记录又能对应代码提交。Jenkins Pipeline中对应构建与推送阶段的关键代码stage(构建Docker镜像) { steps { script { def imageName harbor.internal/rebate/api:${BUILD_NUMBER}-${GIT_COMMIT_SHORT} sh docker build -t ${imageName} . docker login -u ${HARBOR_USER} -p ${HARBOR_PASS} harbor.internal docker push ${imageName} } } }注意这里的凭据不能硬编码在Jenkinsfile里应该使用Jenkins的凭据管理功能Credentials Binding否则代码仓库里到处都是明文密码安全隐患巨大。3.2 与Kubernetes的联动Jenkins不直接操作集群这里要特别强调一个设计原则Jenkins不要直接去操作Kubernetes集群。很多人第一步想到的是Jenkins里配置kubectl命令构建完成后直接执行apply。这个做法短期可行但长期会带来两个问题第一Jenkins的权限边界不清晰。如果Jenkins直接能操作集群那么所有能触发Jenkins构建的人相当于都拿到了集群的运维权限风险太大。第二GitOps的可审计性被破坏了。如果Jenkins直接apply YAML那Git仓库里的部署清单就成了摆设线上实际状态和仓库状态脱节回滚和追踪都无从谈起。正确的做法是Jenkins只负责更新Git仓库里的部署清单并推送。后面的同步动作交给Argo CD去完成。Jenkins和Kubernetes之间没有直接的API通道中间隔着一层Git仓库作为缓冲。这就像两个人合作写文档一个人改完本地副本并推送另一个人负责把关后发布中间有明确的交接点。3.3 多分支Pipeline与Tag驱动的发布策略除了常规的main分支自动部署到测试环境我们还设计了生产发布的分支策略。具体规则如下main分支的更新自动触发Pipeline构建完成后通过Argo CD同步到test环境release-*分支的更新手动触发构建通过后同步到staging环境v*开头的Git标签手动触发经过审批后同步到prod环境为什么要用标签驱动生产发版因为标签是不可变的一旦打了标签就表示这个版本确定要发比分支更稳定。而且Git标签和镜像版本号可以一一对应比如代码标签v1.2.3构建出来的镜像就是rebate-api:196-v1.2.3看到版本号就知道对应的代码是什么状态。在Jenkinsfile里处理标签触发// 只在打标签时执行生产发布 if (env.TAG_NAME ~ /^v\d\.\d\.\d$/) { // 更新deploy仓库中prod目录下Deployment的镜像版本 }这样的策略带来的好处是开发人员不需要理解发布流程的细节只要知道要发正式版打一个v开头的标签剩下的自动化流程都会按规矩办事。3.4 环境验证阶段的细节整个Pipeline中我投入时间最多的其实是最后的环境验证阶段。返利APP后端服务的启动过程不像简单的Web服务那么快它需要依赖数据库、Redis、消息队列有时候还要加载本地的规则引擎配置启动时间可能在30秒到1分钟以上。如果Pipeline构建完镜像马上就去调健康检查接口大概率会失败。我们的做法是环境验证阶段编写一个脚本循环检测服务的就绪状态最多等待120秒每5秒检查一次。同时验证不仅仅是接口能通而是要真正验证业务核心链路。比如返利API服务验证脚本要模拟一个查询流程确认返利比例计算接口能返回正确结构的响应。# 等待pod就绪并调用健康检查 kubectl wait --forconditionready pod -l apprebate-api -n rebate-test --timeout120s # 对健康检查接口做HTTP探测 curl -sf http://rebate-api.rebate-test.svc.cluster.local:8080/actuator/health这个阶段看似简单但它把大量发版后才发现服务没起来的尴尬提前暴露在测试环境里省去了很多来回沟通的时间。4. Kubernetes 灰度发布方案实现4.1 返利场景下为什么需要灰度发布灰度发布Canary Release这个概念相信大家都不陌生但不同的业务场景对灰度发布的需求强度完全不同。返利APP的业务特点决定了它对灰度发布的需求非常强烈原因有两个。第一返利比例、结算规则这些核心配置如果有Bug不是功能不可用这种表面问题而是数据算错。功能不可用用户能感知到会投诉但返利比例算错了用户可能过一段时间才发现自己拿到手的返利比预期的少这直接影响信任感有些用户就直接流失了。第二很多返利服务的Bug是特定条件下才会触发的。比如某些商品类目、某些用户等级、或者绑定特定支付渠道时才会出问题。全量发布一旦踩中这些条件影响面被瞬间放大。灰度发布能让我们先让一小部分流量先走新版本观察一段时间确认没有异常再全量放量。4.2 灰度发布方案的选型Ingress-Nginx权重方案 vs Argo Rollouts实现灰度发布的技术方案有很多种从简单到复杂排列大致如下方案原理优点缺点Deployment多副本手动切流量新旧两个Deployment同时存在负载均衡指向新Deployment简单直接手动操作不够自动化Ingress-Nginx权重在Ingress中配置多个Service加权轮询无需额外工具权重的调整依赖手动修改Ingress配置Argo Rollouts控制器管理金丝雀发布流程按策略自动调整权重自动化程度高支持指标分析需要额外安装控制器学习成本稍高我们最终选择了Argo Rollouts原因是它和GitOps体系契合度最高。正常情况下Argo CD负责同步代码仓库里的Deployment状态而Argo Rollouts是一个类似Deployment的控制器但它专门用于渐进式交付。你可以把它理解成Deployment的升级版除了管理副本数还管理发布策略。安装Argo Rollouts控制器kubectl create namespace argo-rollouts kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/download/v1.6.1/install.yaml然后我们需要把应用的Deployment改为Rollout资源。这里要注意Rollout资源的API字段和Deployment不完全一样但核心部分保持一致比如replicas、selector、template这些字段都一样多出来的是strategy字段。4.3 金丝雀发布的Rollout配置实战以下是我们返利结算服务rebate-api的Rollout配置。这一步是整个灰度发布方案的落地核心apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: rebate-api namespace: rebate-prod spec: replicas: 10 revisionHistoryLimit: 5 selector: matchLabels: app: rebate-api template: metadata: labels: app: rebate-api spec: containers: - name: rebate-api image: harbor.internal/rebate/api:v1.4.2 ports: - containerPort: 8080 strategy: canary: steps: - setWeight: 10 - pause: {duration: 10m} - setWeight: 50 - pause: {duration: 20m} - setWeight: 90 - pause: {duration: 15m} - setWeight: 100这个策略的表达含义是先把10%的流量切到新版本观察10分钟如果没问题切50%观察20分钟再切90%观察15分钟最后完全切到新版本。流量权重的调整由Rollout控制器自动完成它会修改Service后面的Endpoint权重。需要注意的是金丝雀发布的流量路由并不是修改工作负载本身的流量而是通过Service网络层的权重分配实现的。这里要配套使用一个稳定版本Service和一个金丝雀版本ServiceIngress会根据Rollout定义的权重比例将流量分发到这两个Service上。apiVersion: v1 kind: Service metadata: name: rebate-api-stable namespace: rebate-prod spec: selector: app: rebate-api # 这个selector必须匹配stable pod rollouts-pod-template-hash: stable这里有个非常容易踩的坑Selector必须精确定位到稳定版本或金丝雀版本的Pod而不是笼统的apprebate-api。因为两个版本同时运行如果用同一个Selector流量就会在各Pod间随机打散灰度控制就失效了。Argo Rollouts会为每个revision生成一个包含pod-template-hash的标签我们需要正确使用这个标签来区分稳定和灰度版本的Pod。4.4 灰度发布过程中的可观测性保障灰度发布的核心不只是能切流量更关键的是在流量切换的过程中能及时发现异常。如果没有监控和日志灰度发布等于闭着眼睛开车。我在灰度发布期间最关注三类指标错误率、响应延迟、业务核心指标。错误率用Prometheus监控并在Grafana里做成了看板响应延迟重点关注P99分位数如果新版本的P99出现明显抖动说明可能有性能问题业务核心指标方面例如返利结算服务的接口成功率、每次返利计算的平均耗时、以及新老版本之间的返利计算差异率。这里我强烈建议关注新老版本计算差异对比这个思路。返利规则引擎有时候会改动算法逻辑如果新旧版本的ID计算结果不一致说明可能改了不该改的逻辑。实际操作是在金丝雀发布期间编写一个对比脚本抓取同样输入条件下新旧版本接口的返回值计算差异比例。一旦超过阈值立即终止灰度发布并自动回滚到稳定版。# 对比脚本示例 for i in {1..100}; do new$(curl -s http://new-rebate-api/rebate/calc?order_id$ORDER_ID | jq .amount) old$(curl -s http://old-rebate-api/rebate/calc?order_id$ORDER_ID | jq .amount) if [ $new ! $old ]; then echo 差异发现: order$ORDER_ID new$new old$old fi done这个自动化对比检测为我们发现了多次规则引擎的意外差异把很多问题拦截在灰度阶段。金丝雀发布如果完全依赖人盯指标容易漏掉局部、偶发的新问题但对比检测就像给系统加了一道数学卷子对答案的保险特别适合返利这种对计算正确性极其敏感的业务。4.5 自动回滚策略与人工介入机制自动回滚是灰度发布的关键保险机制。Argo Rollouts支持设置分析和自动回滚策略当检测到新版本的指标异常时控制器会自动将流量全部切回稳定版本并把Rollout画面标记为Degraded状态。strategy: canary: analysis: templates: - templateName: rebate-api-error-rate args: - name: namespace value: rebate-prodAnalysisTemplate的定义示例apiVersion: argoproj.io/v1alpha1 kind: AnalysisTemplate metadata: name: rebate-api-error-rate spec: metrics: - name: error-rate interval: 5m successCondition: result 0.05 failureLimit: 3 provider: prometheus: address: http://prometheus.monitoring.svc.cluster.local:9090 query: | sum(rate(http_requests_total{apprebate-api,route/api/rebate/calc,status~5..}[2m])) / sum(rate(http_requests_total{apprebate-api,route/api/rebate/calc}[2m]))要注意的是自动回滚需要配置合理的判定条件和阈值。阈值设太严会频繁中断发版太松又会让异常版本藏过灰度期。我的经验是先用历史正常状态下的指标分布来定阈值再逐步放宽或收紧。另外还是要保留人工介入的入口。自动检测只能识别预设的异常模式遇到没有预期到的情况比如新版本导致的业务逻辑错误接口返回的是错误的计算结果但HTTP状态码是200自动化检测就不一定有效。因此在Argo CD上配置了人工审核的角色权限灰度期间管理员可以手动暂停、继续或回滚。5. GitOps 持续交付的落地细节5.1 Argo CD 安装与应用接入GitOps实现以Argo CD为核心安装Argo CD的过程本身不复杂但要注意版本兼容性。我们用的Kubernetes版本是1.26选了Argo CD 2.8以上的版本兼容性比较稳妥。kubectl create namespace argocd kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml安装完成后可以通过端口转发访问Argo CD的UIkubectl port-forward svc/argocd-server -n argocd 8080:443。默认用户名是admin初始密码通过下面的命令获取kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath{.data.password} | base64 -d第一次登录后必须立刻修改密码。然后把deploy仓库作为GitOps源接入Argo CDargocd repo add gitlab.internal/rebate/deploy.git --username gitops-bot --password ****** --insecure-skip-server-verification argocd app create rebate-api \ --repo gitlab.internal/rebate/deploy.git \ --path apps/rebate-api/prod \ --dest-namespace rebate-prod \ --dest-server https://kubernetes.default.svc \ --sync-policy automated这里我建议创建专用的GitOps账号用只读权限访问部署仓库。不要在Argo CD里使用拥有最高权限的个人账号否则人员变动时的权限回收非常麻烦。5.2 自动同步策略的设置既要自动化又要可控Argo CD的自动同步策略不是非黑即白的选择它有几个可以细调的开关参数作用我的建议Automated Sync自动同步Git变更到集群测试环境开启生产环境建议开启但配合手动确认窗口Self Heal自动修复被手动改动的集群资源生产环境慎开会限制运维的应急手段Prune删除Git中已移除的资源全程打开避免垃圾资源残留生产环境我的最终实践是开启自动同步但配置syncOption并设置一个审批机制同步本身可以自动但同步前的进入生产环境动作需要经过审批。由于我们是Jenkins Pipeline触发同步那么实际上Jenkins任务本身带有审批关卡。# 实际通过Rollout调整时策略是 - 新版本已进入稳定运行状态超过24小时 - 主要负责人手动确认放量这样做的目的是避免Argo CD把所有代码仓库的Push都立刻推到生产环境而是有节奏地推进变更。5.3 部署仓库中的Kustomize管理与版本管理我们的deploy仓库用Kustomize来组合和复用配置。原因在于部分服务在不同环境下的配置差异不大但复制粘贴会导致后期修改一个公共配置时遗漏某个环境。Kustomize的bases和overlays机制能很好地解决这个问题。比如基础配置里定义了Deployment的通用字段、环境变量前缀、资源请求限制等各环境的overlay里只写差异化的部分# apps/rebate-api/prod/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../../base/rebate-api patchesStrategicMerge: - deployment-patch.yaml - configmap-patch.yaml而镜像版本一般不在Kustomize里直接改因为镜像版本号的逻辑由Jenkins Pipeline在推送时修改。我们约定使用占位符Pipeline通过脚本替换# deployment-patch.yaml模板 apiVersion: apps/v1 kind: Deployment metadata: name: rebate-api namespace: rebate-prod spec: template: spec: containers: - name: rebate-api image: harbor.internal/rebate/api:__IMAGE_TAG__在Pipeline的更新部署清单阶段执行sed -i s/__IMAGE_TAG__/${BUILD_NUMBER}-${GIT_COMMIT_SHORT}/替换完成后提交。cd deploy sed -i s/__IMAGE_TAG__/184-a3f8c29/ apps/rebate-api/prod/deployment-patch.yaml git add apps/rebate-api/prod git commit -m release: rebate-api v1.4.2 (184-a3f8c29) git push origin main注意这里用GIT_COMMIT_SHORT而不是BUILD_NUMBER作为版本标识更容易追踪到源码因为生产环境拿到镜像我们能立刻定位它对应的代码提交记录。5.4 回滚操作的最佳实践GitOps最大的好处之一就是回滚极其简单。传统发布回滚要重新构建老版本镜像、重新部署、验证至少要十几分钟而且过程中容易出错。GitOps模式下回滚就是把Git仓库里的配置恢复到之前的提交。比如V1.4.2版本在灰度阶段表现正常但全量发布后发现某个边缘场景的计算异常需要回滚到V1.4.1。操作方式git revert掉提交V1.4.2的变更记录推送到主分支Argo CD自动同步到集群中的旧镜像。如果是我们已经用Argo Rollouts做灰度发布那么直接执行kubectl argo rollouts undo rollout/rebate-api -n rebate-prod实际体验中从发现异常到集群100%恢复旧版本大概只有几分钟而且不需要任何人登录服务器。这个回滚速度在以前的脚本部署时代是难以想象的。不过我要提醒的是回滚动作本身也会产生新的变更记录同样会触发一轮灰度发布因为Rollout有完整的发布策略。如果你要的是立即切回旧版本需要把回滚命令的权重策略先设置为100%只走旧版本否则Argo会再次经历10%、50%等权重的渐进过程。这个细节很容易被忽略导致回滚操作后新版本流量还没有真正归零。6. 常见问题与排查技巧实录6.1 Pipeline构建成功但镜像没有更新到集群这个问题的典型表现是Jenkins的任务跑完显示成功Argo CD的应用状态是Healthy但线上用的还是旧版本镜像。排查下来最常见的原因是部署清单更新没有推送到正确的分支。我们的deploy仓库的分支策略是Jenkins更新main分支Argo CD监听的也是main分支。但有时候Pipeline里执行的git push origin main因为某种原因失败而Jenkins任务中忽略了这一步的错误某些情况下sh命令的返回码不为0但脚本继续执行。后来我在Jenkinsfile里加了严格检查sh set -e git push origin main 另一个坑是Argo CD自动同步的配置默认有15秒的间隔修改了Git仓库不是立刻同步。如果图表显示Not Synced可以手动触发同步argocd app sync rebate-api6.2 灰度发布期间Ingress路由没有按预期分配流量这个问题让我花了不少时间排查现象是设置了setWeight: 30但实际通过Ingress访问的时候几乎感觉不到有30%的流量走新版本。排查思路是Rollout控制器的权重调整依赖Service的spec.selector精准匹配。如果稳定版Service和灰度版Service的Selector写得太宽泛比如都匹配apprebate-api那么两边Pod都被路由到但权重可能不生效。Argo Rollouts在这方面的设计是它会为每个版本维护一个唯一的Pod标签rollouts-pod-template-hash你必须在Service的Selector里显式使用这个哈希来指向对应的版本。正确的Service配置apiVersion: v1 kind: Service metadata: name: rebate-api-stable spec: selector: app: rebate-api rollouts-pod-template-hash: 764d9d4b9d如果手抖把哈希写错了流量就全部指向一个不存在的Pod集合Service后端为空导致503错误。所以灰度策略上线前一定要先验证新旧版本Service后端的Pod数量是否符合预期kubectl get endpoints rebate-api-stable -n rebate-prod6.3 镜像仓库凭据问题导致kubelet拉取镜像失败生产环境Kubernetes集群通过Harbor拉取私有镜像需要提前在每个节点的Docker配置中加入Harbor的凭据或者在Kubernetes里创建ImagePullSecret。我们遇到的症状是新版本发布之后Pod状态始终是ImagePullBackOffkubectl describe pod看到拉的镜像需要认证。创建ImagePullSecret并在Deployment中引用的操作如下kubectl create secret docker-registry harbor-registry \ --docker-serverharbor.internal \ --docker-usernamerobot$rebate \ --docker-password****** \ --docker-emaildevrebate.local然后在Deployment的Pod模板中spec: imagePullSecrets: - name: harbor-registry这里要提醒一下Harbor的机器人账号比较推荐使用因为它可以为不同项目设置独立的拉取凭据比统一账号更安全也更容易在凭据泄漏时进行指标控制。6.4 Argo CD界面显示OutOfSync但集群正常这种情况分两种一种是确实有差异比如有人手动改了集群里的某个字段另一种是Argo CDUI显示不确定但argocd app diff看输出是空的。区分方式如下argocd app diff rebate-api --hard如果这个命令没有输出差异说明集群结构正常只是Argo CD的生效版本被刷新问题干扰了。之后执行argocd app refresh rebate-api即可。如果是确实有差异需要检查谁改了集群中的资源。这里往往能发现运维人员因为紧急情况直接改了Deployment的副本数量触发SelfHeal后改回原样这种操作本身解决不了问题。6.5 返利结算服务金丝雀期间的部分用户数据不一致这是返利场景特有的问题金丝雀期间一个订单被随机分配到新版本计算返回结果与旧版本不同。因为返利计算的实时性要求高一旦新版本有逻辑调整同一批订单在不同版本间会返回不同结果。这会让用户感觉返利怎么和昨天不一样引投诉。我们的应对方案是返利计算类的服务坚决不做流量百分比灰度而是做请求参数维度灰度。例如先让白名单用户内部测试人员、种子用户走新版本其他用户全部走旧版本。如果必须做新老逻辑切换我宁可让所有用户都从新版本开始也不让同一用户前后两次请求落到不同代码版本上否则体验差异就会让用户无法接受。在白名单灰度方案中Ingress或ServiceMesh根据Header头或Cookie进行流量切分。我们用Argo Rollouts在灰度步骤中加上一个特殊的setHeaderRoute或setMirrorRoute策略把指定用户名的请求指向金丝雀版本steps: - setWeight: 0 - setHeaderRoute: name: canary-canaryroute match: - header: name: user-id exact: u_10086这样做灰度发布期间新版本的可见范围完全可控而且不会导致任何用户的订单在前后请求之间出现不一致。对于返利业务来说这一点比权重分配的灵活性重要得多。7. 写在最后的经验与踩坑总结整套方案从设计、搭建到稳定运行中间花了不少时间。回头看最有价值的不一定是最炫酷的技术组件而是几个朴素的原则环境标准化、变更可追溯、发布有节奏、回滚有准备。返利APP这个场景对数据准确性的要求极高所以我在工程上花了很多精力在可观测可对比可精准灰度上这些东西平时不起眼一旦线上出问题就都是命根子。几个我认为值得特别交代的细节第一Pipeline里的每个阶段都要设置超时和失败重试策略。返利服务在高峰期构建会比较重Maven依赖拉取偶尔会超时如果阶段没有超时控制整个队列会被卡住。Jenkinsfile里应该在每个stage上都加timeout(time: 20, unit: MINUTES)。第二灰度发布不是只能靠Argo Rollouts自动完成。在很多场景下一个简单的新旧两个Deployment Ingress手动切流量也能解决问题关键是流量切换前后必须有人盯着指标变化。过度自动化有时候会让问题在无人察觉的时候发生我认为比较好的模式是自动化做80%的常规工作留下20%的人工确认空间给关键变更。第三CI/CD体系的维护者和使用者最好不是同一批人。我们团队里有专门的DevOps小组搭建和维护Pipeline、Argo CD、Kubernetes环境但应用开发者日常是直接通过Git标签和Merge Request参与发布流程的。这种分工让工具链的稳定性有了保障也让开发者的工作流被简化为提交代码、打标签、看结果。最后分享一个心法这套体系不要在一开始就追求全覆盖先从核心服务比如返利结算跑通形成规范后再推广到其他服务。一个跑得很顺的核心服务比十个稀烂、以半自动状态运转的服务更能让大家愿意配合使用。一旦习惯建立DevOps文化就会自然生长出来后续的复制和扩展都是水到渠成的事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑