生产环境 DevOps 实战 | 第 10 天:Tekton Pipeline 实战——多阶段流水线的编排与依赖

第 10/60 天

引言

在上一篇文章中,我们学习了如何编写 Tekton 的 Task 和 Step,构建了第一个构建任务。然而,真实的 CI 流水线远不止一个任务——代码检出、单元测试、代码检查、镜像构建、安全扫描、制品推送……每一个环节都需要独立的任务,并且它们之间存在明确的前后依赖关系。

Tekton Pipeline 正是为此而生。Pipeline 是 Tekton 的”编排层”,它通过声明式 YAML 来定义多个 Task 的执行顺序、并行关系、数据传递和条件分支。本文将深入讲解 Pipeline 的核心编排机制,并通过一个完整的 CI 流水线示例,展示如何将多个 Task 组合成可复用、可观测的多阶段流水线。

核心概念

Pipeline 的编排原语

概念 说明 类比
Pipeline 一系列 Task 的有序集合 工作流定义
PipelineRun Pipeline 的一次执行实例 运行记录
Task 流水线中的最小执行单元 单个工序
runAfter 声明 Task 之间的显式依赖关系 先后顺序
from 声明对上一个 Task 的 Results 依赖 数据传递
when 条件执行,满足条件才运行 Task 分支判断
finally 无论成功失败都会执行的收尾任务 清理/通知

流水线编排的三种模式

  1. 串行模式:Task A → Task B → Task C,使用 runAfter 声明依赖
  2. 并行模式:Task A 完成后,B 和 C 同时执行,互不依赖
  3. 扇入/扇出模式:多个上游 Task 完成后,汇聚到下游 Task

数据传递机制

Pipeline 中的 Task 通过以下方式交换数据:

  • Workspaces:共享卷挂载,适用于大文件传递(如源码目录)
  • Results:Task 内输出的结构化数据,供下游 Task 通过 $(tasks.<task-name>.results.<result-name>) 引用
  • Parameters:Pipeline 级别的参数,向下传递到每个 Task

实战步骤

1. 编写基础 Task:代码检出

首先创建一个通用的 Git Clone Task,用于从仓库拉取源码:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: git-clone
spec:
  params:
    - name: url
      description: Git 仓库地址
    - name: revision
      description: 分支或 commit SHA
      default: main
  workspaces:
    - name: source
      description: 源码输出目录
  results:
    - name: commit
      description: 检出的 commit SHA
    - name: commit-message
      description: 提交信息
  steps:
    - name: clone
      image: alpine/git
      script: |
        #!/bin/sh
        git clone "$(params.url)" /workspace/source/
        cd /workspace/source/
        git checkout "$(params.revision)"
        echo -n "$(git rev-parse HEAD)" | tee $(results.commit.path)
        echo -n "$(git log -1 --pretty=%s)" | tee $(results.commit-message.path)

2. 编写 Task:单元测试

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: unit-test
spec:
  params:
    - name: test-dir
      description: 测试目录
      default: ./...
  workspaces:
    - name: source
      description: 源码目录(与 git-clone 共享)
  results:
    - name: test-status
      description: 测试结果状态
  steps:
    - name: run-tests
      image: golang:1.22
      script: |
        #!/bin/sh
        cd /workspace/source
        go test $(params.test-dir) -v -race -coverprofile=coverage.out 2>&1 | tee /workspace/test-output.txt
        if [ ${PIPESTATUS[0]} -eq 0 ]; then
          echo -n "PASS" | tee $(results.test-status.path)
        else
          echo -n "FAIL" | tee $(results.test-status.path)
          exit 1
        fi

3. 编写 Task:Docker 镜像构建与推送

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: build-and-push
spec:
  params:
    - name: image-registry
      description: 镜像仓库地址,如 harbor.stellardata.top
    - name: image-name
      description: 镜像名称
    - name: image-tag
      description: 镜像标签
      default: latest
  workspaces:
    - name: source
      description: 源码目录
  results:
    - name: image-digest
      description: 镜像 digest
    - name: image-url
      description: 完整镜像地址
  steps:
    - name: build-and-push
      image: gcr.io/kaniko-project/executor:latest
      env:
        - name: DOCKER_CONFIG
          value: /kaniko/.docker
      args:
        - --context=$(workspaces.source.path)
        - --dockerfile=$(workspaces.source.path)/Dockerfile
        - --destination=$(params.image-registry)/$(params.image-name):$(params.image-tag)
        - --cache=true
        - --cache-ttl=24h
      script: |
        #!/busybox/sh
        /kaniko/executor 
          --context=$(workspaces.source.path) 
          --dockerfile=$(workspaces.source.path)/Dockerfile 
          --destination=$(params.image-registry)/$(params.image-name):$(params.image-tag) 
          --cache=true 
          --cache-ttl=24h 
          --digest-file=/tmp/digest
        DIGEST=$(cat /tmp/digest)
        echo -n "$DIGEST" | tee $(results.image-digest.path)
        echo -n "$(params.image-registry)/$(params.image-name):$(params.image-tag)" | tee $(results.image-url.path)

4. 编写 Pipeline:多阶段 CI 流水线

现在将以上三个 Task 编排成一个完整的 Pipeline:

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: ci-pipeline
spec:
  params:
    - name: repo-url
      description: 源码仓库地址
      type: string
    - name: branch
      description: 目标分支
      type: string
      default: main
    - name: image-registry
      description: Harbor 镜像仓库地址
      type: string
    - name: image-name
      description: 镜像名称
      type: string
  workspaces:
    - name: shared-source
      description: 所有 Task 共享的源码目录
  tasks:
    - name: checkout
      taskRef:
        name: git-clone
      params:
        - name: url
          value: $(params.repo-url)
        - name: revision
          value: $(params.branch)
      workspaces:
        - name: source
          workspace: shared-source

    - name: run-tests
      taskRef:
        name: unit-test
      runAfter:
        - checkout
      params:
        - name: test-dir
          value: ./...
      workspaces:
        - name: source
          workspace: shared-source

    - name: build-image
      taskRef:
        name: build-and-push
      runAfter:
        - run-tests
      params:
        - name: image-registry
          value: $(params.image-registry)
        - name: image-name
          value: $(params.image-name)
        - name: image-tag
          value: $(tasks.checkout.results.commit)
      workspaces:
        - name: source
          workspace: shared-source

  finally:
    - name: notify-status
      taskRef:
        name: send-notification
      params:
        - name: commit
          value: $(tasks.checkout.results.commit)
        - name: status
          value: "success"

5. PipelineRun:触发执行

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  name: ci-pipeline-run-001
  generateName: ci-pipeline-run-
spec:
  pipelineRef:
    name: ci-pipeline
  params:
    - name: repo-url
      value: https://github.com/example/my-app.git
    - name: branch
      value: main
    - name: image-registry
      value: harbor.stellardata.top
    - name: image-name
      value: my-app
  workspaces:
    - name: shared-source
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 5Gi

6. 条件分支:when 表达式实现选择性执行

有些场景下,我们希望根据条件来决定是否执行某个 Task。例如,仅当代码推送到 main 分支时才构建镜像:

tasks:
  - name: build-image
    taskRef:
      name: build-and-push
    when:
      - input: $(params.branch)
        operator: in
        values: ["main", "release-*"]
    runAfter:
      - run-tests
    params:
      - name: image-registry
        value: $(params.image-registry)
      - name: image-name
        value: $(params.image-name)
      - name: image-tag
        value: $(tasks.checkout.results.commit)
    workspaces:
      - name: source
        workspace: shared-source

7. 查看流水线执行状态

使用 tkn CLI 查看流水线执行情况:

# 列出所有 PipelineRun
tkn pipelinerun list

# 查看特定 PipelineRun 的详细信息
tkn pipelinerun describe ci-pipeline-run-001

# 实时查看日志
tkn pipelinerun logs ci-pipeline-run-001 -f

# 查看每个 Task 的详细状态
tkn pipelinerun describe ci-pipeline-run-001 -o yaml | grep -A 5 "taskRuns"

常见问题

Q1: Pipeline 中 Task 的执行顺序如何保证?

A: 使用 runAfter 字段声明依赖关系。Tekton 调度器会解析 Task 之间的依赖 DAG,确保上游 Task 完成后才执行下游 Task。如果没有依赖关系,Task 会并行执行。

Q2: Workspaces 共享时,多个 Task 同时写入会冲突吗?

A: 不会,因为 Tekton 的 runAfter 和 DAG 调度保证了串行访问。但要注意,不同的 Task 之间应使用不同的子目录来避免潜在的文件名冲突。

Q3: 如何从 Pipeline 外部传递参数到 Task?

A: 在 Pipeline 的 params 中定义参数,然后在 Task 的 params 中通过 $(params.<param-name>) 引用。PipelineRun 实例化时提供具体值。

Q4: Pipeline 执行失败后如何自动清理或通知?

A: 使用 finally 字段定义收尾 Task,无论 Pipeline 成功或失败,finally 中的 Task 都会执行。可以用于发送通知、清理资源、记录日志等。

Q5: from 和 runAfter 有什么区别?

A: runAfter 只控制执行顺序,不传递数据。from 既控制顺序也传递数据(通过 Results 引用),通常用于需要上一个 Task 输出结果的场景。

总结

  1. Pipeline 是 Tekton 的编排核心,通过声明式 YAML 定义多阶段 CI 流水线,Task 之间通过 runAfterfrom 建立依赖关系
  2. Workspaces 和 Results 构成数据传递双通道:Workspaces 用于大文件共享,Results 用于轻量级结构化数据传递
  3. 并行执行与条件分支:Tekton 自动并行化无依赖的 Task,when 表达式实现条件执行,finally 确保收尾操作
  4. 生产实践建议:将通用 Task(git-clone、build、test)定义为 ClusterTask 实现复用,为每个项目编写 Pipeline 进行编排
  5. 下一步:下一篇文章将介绍 Tekton Triggers,用 Webhook 自动触发流水线,实现真正的”代码提交即构建”
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容