公司动态

CI/CD从Jenkins到Tekton的迁移复盘:云原生Pipeline架构的渐进式迁移与双轨并行策略

📅 2026/7/26 18:40:38
CI/CD从Jenkins到Tekton的迁移复盘:云原生Pipeline架构的渐进式迁移与双轨并行策略
CI/CD从Jenkins到Tekton的迁移复盘云原生Pipeline架构的渐进式迁移与双轨并行策略一、项目背景与业务挑战我们团队管理着 85 微服务的 CI/CD Pipeline全部运行在 Jenkins 上。Jenkins 作为 CI/CD 领域的老兵稳定运行了 5 年但随着业务云原生化的深入三个结构性矛盾日益突出架构耦合问题Jenkins Master 是单点架构85 Pipeline 并发构建时 Master 节点 CPU 经常飙到 90%构建排队等待时间平均 8 分钟。Agent 通过 SSH 连接静态 EC2 节点无法利用 K8s 的弹性调度能力。配置碎片化85 服务的 Jenkinsfile 分散在各项目仓库中共享逻辑靠copy-paste一旦安全扫描步骤需要升级需要逐个修改 85 个 Jenkinsfile运维成本极高。K8s生态脱节Jenkins Pipeline 在 K8s 外部运行与 ArgoCD、Prometheus、K8s RBAC 等云原生工具链割裂无法实现 GitOps 闭环。Tekton 是 CNCF 的云原生 CI/CD 框架Pipeline 以 K8s Pod 形式运行天然具备弹性调度、RBAC 集成、GitOps 对齐等优势。经过 3 个月的评估和 POC我们决定启动 Jenkins → Tekton 的迁移项目。二、核心方案渐进式迁移与双轨并行策略2.1 Jenkins vs Tekton 架构对比迁移的第一步是理解两个架构的核心差异核心差异总结维度JenkinsTekton调度模式Master集中调度K8s原生Pod调度节点管理预分配静态Agent按需动态创建PodPipeline定义JenkinsfileGroovyTask/PipelineYAML共享逻辑Shared LibraryGroovyClusterTaskYAMLRBACJenkins自有权限体系K8s RBAC资源利用Agent常驻利用率低Pod按需创建销毁2.2 双轨并行渐进式迁移策略我们不能一刀切切换——85 服务全量迁移风险太大。我们设计了四阶段渐进式迁移策略from dataclasses import dataclass, field from datetime import datetime from enum import Enum from typing import Dict, List, Optional import logging logger logging.getLogger(__name__) class PipelineStatus(Enum): Pipeline迁移状态 ON_JENKINS on_jenkins # 仍在Jenkins运行 ON_TEKTON on_tekton # 已迁移到Tekton DUAL_RUNNING dual_running # 双轨并行运行中 MIGRATION_FAILED migration_failed # 迁移失败回退到Jenkins dataclass class PipelineMigrationRecord: Pipeline迁移记录 service_name: str # 服务名称 current_status: PipelineStatus # 当前状态 jenkins_pipeline: Optional[str] None # Jenkinsfile路径 tekton_pipeline: Optional[str] None # Tekton Pipeline YAML路径 dual_start_time: Optional[datetime] None # 双轨并行开始时间 switch_time: Optional[datetime] None # 切换到Tekton时间 rollback_time: Optional[datetime] None # 回退时间如果迁移失败 migration_notes: str # 迁移备注 class PipelineMigrationManager: Pipeline迁移状态管理器 # 双轨并行切换条件连续7天Tekton构建成功率≥98% DUAL_RUNNING_DAYS 7 SUCCESS_RATE_THRESHOLD 0.98 def __init__(self): self.records: Dict[str, PipelineMigrationRecord] {} def evaluate_switch_condition(self, service: str) - bool: 评估双轨并行服务是否满足切换条件 Args: service: 服务名称 Returns: 是否可以正式切换到Tekton try: record self.records.get(service) if not record or record.current_status ! PipelineStatus.DUAL_RUNNING: logger.warning(f服务 {service} 不在双轨并行状态) return False if not record.dual_start_time: return False # 检查双轨并行天数 days_running (datetime.now() - record.dual_start_time).days if days_running self.DUAL_RUNNING_DAYS: logger.info(f服务 {service} 双轨并行仅 {days_running} 天未满 {self.DUAL_RUNNING_DAYS} 天) return False # 检查Tekton构建成功率 tekton_success_rate self._get_tekton_success_rate(service) if tekton_success_rate self.SUCCESS_RATE_THRESHOLD: logger.warning( f服务 {service} Tekton成功率 {tekton_success_rate:.2%} f低于阈值 {self.SUCCESS_RATE_THRESHOLD:.2%} ) return False logger.info(f服务 {service} 满足切换条件并行 {days_running} 天成功率 {tekton_success_rate:.2%}) return True except Exception as e: logger.error(f切换条件评估异常: {e}, exc_infoTrue) return False def switch_to_tekton(self, service: str) - bool: 将服务正式切换到Tekton Args: service: 服务名称 Returns: 切换是否成功 try: record self.records.get(service) if not self.evaluate_switch_condition(service): logger.warning(f服务 {service} 未满足切换条件拒绝切换) return False # 更新状态 record.current_status PipelineStatus.ON_TEKTON record.switch_time datetime.now() record.migration_notes f | 正式切换到Tekton: {record.switch_time} logger.info(f服务 {service} 正式切换到Tekton) return True except Exception as e: logger.error(f切换异常: {e}, exc_infoTrue) return False def rollback_to_jenkins(self, service: str, reason: str) - bool: 回退到Jenkins Args: service: 服务名称 reason: 回退原因 Returns: 回退是否成功 try: record self.records.get(service) if not record: logger.warning(f服务 {service} 无迁移记录) return False record.current_status PipelineStatus.MIGRATION_FAILED record.rollback_time datetime.now() record.migration_notes f | 回退原因: {reason} logger.warning(f服务 {service} 回退到Jenkins原因: {reason}) return True except Exception as e: logger.error(f回退异常: {e}, exc_infoTrue) return False def _get_tekton_success_rate(self, service: str) - float: 查询Tekton构建成功率模拟调用监控系统 # 实际实现调用 Prometheus 查询 Tekton PipelineRun 成功率 return 0.99 # 模拟值2.3 Jenkinsfile 到 Tekton Pipeline 的转化工具85 个 Jenkinsfile 手动转化为 Tekton YAML 不现实。我们开发了自动化转化工具import yaml import logging from typing import Dict, List logger logging.getLogger(__name__) class JenkinsToTektonConverter: Jenkinsfile → Tekton Pipeline 转化器 # Jenkins步骤 → Tekton Task 映射表 STEP_MAPPING: Dict[str, dict] { git checkout: { task_name: git-clone, clusterTask: git-clone, # 使用ClusterTask共享任务 params: {url: $(params.repo-url), revision: $(params.revision)} }, mvn clean install: { task_name: maven-build, clusterTask: maven, params: {GOALS: clean install, MAVEN_PROJECT: $(workspaces.source.path)} }, docker build: { task_name: build-and-push-image, clusterTask: kaniko, # 使用kaniko替代docker build params: {IMAGE: $(params.image-url), DOCKERFILE: $(workspaces.source.path)/Dockerfile} }, security scan: { task_name: security-scan, clusterTask: trivy-scan, params: {IMAGE: $(params.image-url)} }, kubectl deploy: { task_name: deploy-to-k8s, clusterTask: kubectl-deploy, params: {manifest: $(workspaces.source.path)/k8s/, namespace: $(params.namespace)} }, } def convert(self, jenkinsfile_content: str, service_name: str) - str: 将Jenkinsfile转化为Tekton Pipeline YAML Args: jenkinsfile_content: Jenkinsfile全文 service_name: 服务名称 Returns: Tekton Pipeline YAML字符串 try: # 第一步解析Jenkinsfile中的步骤 steps self._parse_jenkins_steps(jenkinsfile_content) logger.info(f解析到 {len(steps)} 个构建步骤: {steps}) # 第二步映射到Tekton Tasks tekton_tasks [] for step in steps: mapping self.STEP_MAPPING.get(step) if mapping: tekton_tasks.append(mapping) else: # 未映射的步骤生成自定义Task tekton_tasks.append(self._generate_custom_task(step, service_name)) # 第三步组装Tekton Pipeline YAML pipeline_yaml self._assemble_pipeline_yaml(service_name, tekton_tasks) logger.info(fPipeline YAML生成完成: {service_name}) return pipeline_yaml except Exception as e: logger.error(fPipeline转化异常: {e}, exc_infoTrue) return def _parse_jenkins_steps(self, content: str) - List[str]: 解析Jenkinsfile中的构建步骤名称 steps [] for line in content.split(\n): line line.strip() # 提取sh/git/mvn/docker等步骤 if line.startswith(sh ) or line.startswith(sh(): cmd line.split()[1] if in line else for key in self.STEP_MAPPING: if key in cmd.lower(): steps.append(key) break elif checkout in line.lower(): steps.append(git checkout) elif stage in line.lower(): # 提取stage名称作为步骤参考 pass return steps def _assemble_pipeline_yaml(self, service_name: str, tasks: List[dict]) - str: 组装Tekton Pipeline YAML pipeline { apiVersion: tekton.dev/v1beta1, kind: Pipeline, metadata: {name: f{service_name}-pipeline}, spec: { params: [ {name: repo-url, type: string}, {name: revision, type: string, default: main}, {name: image-url, type: string}, {name: namespace, type: string, default: service_name}, ], workspaces: [{name: source}], tasks: [] } } for idx, task in enumerate(tasks): task_def { name: task[task_name], taskRef: {name: task.get(clusterTask, task[task_name])}, params: task.get(params, {}), workspaces: [{name: source, workspace: source}], } # 设置runAfter依赖前序任务完成 if idx 0: task_def[runAfter] [tasks[idx-1][task_name]] pipeline[spec][tasks].append(task_def) return yaml.dump(pipeline, default_flow_styleFalse)2.4 Tekton workspace 与 Secret 配置迁移过程中踩坑最多的地方是 workspace 和 Secret 的对接# Tekton PipelineRun 示例包含workspace和Secret配置 apiVersion: tekton.dev/v1beta1 kind: PipelineRun metadata: name: user-service-pipeline-run-001 spec: pipelineRef: name: user-service-pipeline params: - name: repo-url value: https://gitlab.internal.com/devops/user-service.git - name: revision value: main - name: image-url value: registry.internal.com/devops/user-service:$(params.revision) - name: namespace value: user-service # workspace配置使用PVC作为源码工作空间 workspaces: - name: source volumeClaimTemplate: spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: fast-ssd # 使用SSD存储类提升构建速度 # Secret配置镜像仓库凭证和Git凭证 # 注意Tekton使用K8s原生Secret而非Jenkins的Credentials Store taskSpecs: - taskName: build-and-push-image workspaces: - name: source workspace: source params: - name: IMAGE value: $(params.image-url) # 超时配置防止Pod无限等待 timeouts: pipeline: 30m # 整个Pipeline最大30分钟 tasks: 10m # 每个Task最大10分钟 # 服务账号绑定Secret访问权限 serviceAccountName: tekton-pipeline-sa三、实践落地迁移过程与效果数据3.1 四阶段迁移时间线阶段时间服务数状态POC验证第1-2月3个服务Tekton验证通过双轨并行第3-4月15个服务JenkinsTekton同步构建批量迁移第5月60个服务逐个切换到Tekton全量切换第6月85个服务Jenkins下线3.2 迁移过程中的踩坑记录坑1workspace 数据丢失Jenkins 的 workspace 是 Agent 节点上的持久目录多个 stage 自然共享。Tekton 的 workspace 是 Pod 级的Task Pod 销毁后 workspace 数据随之丢失。解决方案使用volumeClaimTemplate为 PipelineRun 创建 PVC整个 Pipeline 生命周期内 workspace 数据持久化。构建完成后 PVC 自动回收。坑2Secret 体系差异Jenkins 使用 Credentials Store 管理 Git/Registry 凭证Tekton 使用 K8s Secret。迁移初期大量构建因凭证缺失而失败。解决方案开发 Secret 同步工具从 Jenkins Credentials Store 自动导出为 K8s Secret绑定到 Tekton ServiceAccount。坑3超时与资源限制Jenkins Pipeline 没有硬超时限制长时间构建只是排队等待。Tekton Pod 默认超时较短60s导致大型构建频繁超时失败。解决方案配置 PipelineRun 的timeouts字段根据服务构建特征定制超时时间10m-30m。3.3 迁移效果对比数据指标Jenkins迁移前Tekton迁移后变化平均构建时长12分钟8.9分钟↓26%构建成功率94.2%96.6%↑2.4%构建排队等待8分钟0分钟↓100%Agent CPU利用率28%62%↑119%Jenkins Master负载CPU 90%N/A已下线Pipeline配置复用率15%78%↑63%GitOps闭环支持无ArgoCDTekton联动全量四、关键挑战与应对策略4.1 转化工具的覆盖率问题Jenkinsfile 是自由格式的 Groovy 脚本85 服务的 Jenkinsfile 各有方言。转化工具初期的步骤映射覆盖率仅 60%大量自定义脚本无法自动映射。应对策略两轮转化第一轮用工具转化标准步骤覆盖率 60%第二轮人工审查并补充自定义步骤逐步收敛将自定义脚本抽象为 ClusterTask减少后续服务的转化工作量4.2 双轨并行期间的成本翻倍双轨并行期间Jenkins Tekton 两套 CI/CD 同时运行构建资源消耗翻倍。应对策略缩短并行窗口从计划的 14 天缩短到 7 天基于成功率快速评估Tekton构建不触发部署双轨期间 Tekton Pipeline 只做构建验证不触发实际部署减少资源浪费4.3 团队技能转型运维团队对 Jenkins 有 5 年经验对 Tekton 完全陌生。迁移后需要快速掌握 Tekton 调试、YAML 编写、K8s RBAC 等新技能。应对策略Tekton 培训周迁移前安排 2 周集中培训覆盖 Pipeline 编写、调试、运维双轨期间跟跑学习双轨期间 Tekton 构建结果同步发送给服务负责人逐步熟悉新模式4.4 Jenkins 下线的心理阻力部分团队对 Jenkins 有长期依赖Jenkins 一直在为什么非要换 这种心理阻力在迁移后期最明显。应对策略数据驱动说服用构建时长、成功率、资源利用率等硬数据证明 Tekton 的改进保留回退窗口Jenkins 环境在正式下线前保留 30 天冷备期消除无法回退的焦虑五、总结从 Jenkins 到 Tekton 的迁移本质是从传统 CI/CD向云原生 Pipeline的架构演进。双轨并行渐进式迁移策略让我们在不中断业务的前提下完成了 85 服务的全量切换。三个关键经验渐进式而非一刀切85 服务不能同时迁移。四阶段渐进策略POC→双轨→批量→全量将风险降到最小每个阶段都有明确的评估标准和回退机制。转化工具是加速器而非替代品自动化转化工具能处理 60% 的标准步骤但剩余 40% 的自定义逻辑仍需人工审查。工具加速了迁移进度但不能完全替代人的判断。技术迁移也是组织转型团队技能转型、心理阻力、流程适配等非技术因素对迁移成败的影响不亚于技术本身。培训和数据驱动的说服是关键。下一步计划基于 Tekton Pipeline 构建更完整的 GitOps 闭环——Tekton 构建镜像 ArgoCD 自动部署 Prometheus 监控反馈实现从代码提交到生产部署的全自动化流程。