生产环境 DevOps 实战 | 第 12 天:Tekton Triggers——用 Webhook/Binding/EventListener 触发流水线

第 12/60 天

引言

前几篇文章我们学会了如何编写 Task、编排 Pipeline,但每次运行都要手动创建 PipelineRun——这在生产环境显然不可接受。真正的 CI 应该是「代码一提交,流水线自动跑起来」。Tekton 官方为此提供了独立的组件 Tekton Triggers,它监听外部事件(最典型的就是 Git 仓库的 Webhook),把事件载荷中的关键信息(分支、commit SHA、仓库地址)提取出来,自动生成并执行对应的 PipelineRun。

本文将深入讲解 Tekton Triggers 的三大核心 CRD:EventListener(事件入口)、TriggerBinding(数据绑定)、TriggerTemplate(资源模板),以及用于安全校验的 Interceptor(拦截器)。通过一个完整的 GitHub push 事件 → 自动触发构建流水线的实战案例,让你掌握事件驱动 CI 的全链路搭建方法。

核心概念

事件流全景

一次 Webhook 触发的完整链路如下:

Git 仓库 push
    │  发送 HTTP POST(JSON 载荷 + 签名头)
    ▼
EventListener(常驻 Pod,监听 8080 端口)
    │  1. Interceptor 校验签名、过滤事件类型/分支
    ▼
TriggerBinding(从载荷提取参数)
    │  2. body.ref、body.head_commit.id、body.repository.clone_url ...
    ▼
TriggerTemplate(生成资源)
    │  3. 用 $(tt.params.xxx) 填充 PipelineRun 字段
    ▼
PipelineRun(真正执行 CI 流水线)

四个核心 CRD

组件 类型 职责 类比
EventListener Service + Pod 暴露 HTTP 端点,接收并分发事件 邮局的收件窗口
Interceptor 内嵌插件 校验签名、过滤事件、修改载荷 安检与分拣员
TriggerBinding 参数提取 $(body.xxx) 从 JSON 载荷取值 拆信封取信件
TriggerTemplate 资源模板 定义要创建的 Kubernetes 资源(PipelineRun) 填好的申请表

v1 版本的 Notetriggers.tekton.dev/v1 提供独立的 Trigger CRD(把 Binding + Template + Interceptors 组合成一个对象),EventListener 通过 name 引用它;也可以在 EventListener 的 spec.triggers 里内联定义。两种写法等价,本文以独立 Trigger 为例(结构更清晰)。

取值语法速查

表达式 含义
$(body.xxx) 从 Webhook JSON 载荷取值(点号路径)
$(header.xxx) 从 HTTP 请求头取值
$(tt.params.xxx) 引用 TriggerTemplate 中定义的参数
$(extensions.xxx) 引用 Interceptor 处理后的扩展字段

实战步骤

1. 安装 Tekton Triggers

确保已经安装了 Tekton Pipelines(第 8 天内容),然后安装 Triggers:

# 安装最新稳定版(所有 CRD、Controller、Webhook 一次性部署到 tekton-pipelines 命名空间)
kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml

# 生产环境建议锁定具体版本,避免升级漂移
# kubectl apply -f https://storage.googleapis.com/tekton-releases/triggers/previous/v0.28.0/release.yaml

# 验证安装
kubectl get pods -n tekton-pipelines | grep triggers
kubectl get crd | grep triggers

2. 创建凭据:GitHub Webhook Secret

在 GitHub Webhook 配置里填写的 Secret,用于 Interceptor 校验 X-Hub-Signature-256 签名:

# 生成随机 token 并写入 K8s Secret
kubectl create secret generic github-secret 
  -n tekton-pipelines 
  --from-literal=secretToken="$(openssl rand -hex 24)" 
  --dry-run=client -o yaml | kubectl apply -f -

3. 创建 TriggerTemplate(定义生成的 PipelineRun)

apiVersion: triggers.tekton.dev/v1
kind: TriggerTemplate
metadata:
  name: ci-pipeline-template
  namespace: tekton-pipelines
spec:
  params:
    - name: git-repo-url      # 仓库地址
    - name: git-revision      # commit SHA
    - name: git-branch        # 分支全名,如 refs/heads/main
  resourcetemplates:
    - apiVersion: tekton.dev/v1
      kind: PipelineRun
      metadata:
        generateName: ci-run-        # 自动生成唯一名称
        labels:
          app: demo-app
          tekton.dev/trigger: github-push
      spec:
        serviceAccountName: build-bot
        pipelineRef:
          name: ci-pipeline           # 第 10 天创建的构建流水线
        params:
          - name: repo-url
            value: $(tt.params.git-repo-url)
          - name: revision
            value: $(tt.params.git-revision)
          - name: branch
            value: $(tt.params.git-branch)
        workspaces:
          - name: source
            volumeClaimTemplate:      # 自动创建 PVC,用完即清
              spec:
                accessModes: ["ReadWriteOnce"]
                resources:
                  requests:
                    storage: 2Gi

4. 创建 TriggerBinding(从事件载荷提取参数)

apiVersion: triggers.tekton.dev/v1
kind: TriggerBinding
metadata:
  name: github-push-binding
  namespace: tekton-pipelines
spec:
  params:
    - name: git-revision
      value: $(body.head_commit.id)          # 最新一次 commit 的 SHA
    - name: git-repo-url
      value: $(body.repository.clone_url)    # 如 https://github.com/example/demo-app.git
    - name: git-branch
      value: $(body.ref)                     # 如 refs/heads/main

5. 创建 Trigger 与 EventListener(入口 + 安全校验 + 分发)

apiVersion: triggers.tekton.dev/v1
kind: Trigger
metadata:
  name: github-push-trigger
  namespace: tekton-pipelines
spec:
  interceptors:
    - ref:
        name: "github"                       # 官方 GitHub 拦截器:校验签名
      params:
        - name: "secretRef"
          value:
            secretName: github-secret        # 与步骤 2 创建的 Secret 对应
            secretKey: secretToken
        - name: "eventTypes"
          value: ["push", "pull_request"]    # 只接受这两种事件
  bindings:
    - ref: github-push-binding
  template:
    ref: ci-pipeline-template
---
apiVersion: triggers.tekton.dev/v1
kind: EventListener
metadata:
  name: ci-event-listener
  namespace: tekton-pipelines
spec:
  serviceAccountName: tekton-triggers-sa     # 需要具备创建 PipelineRun 的 RBAC 权限
  triggers:
    - name: github-push-trigger               # 引用上面的 Trigger

EventListener 会自动创建一个名为 el-ci-event-listener 的 Service(默认 ClusterIP,端口 8080),以及接收事件的 Pod。

6. 暴露 EventListener 到公网

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: el-ci-event-listener
  namespace: tekton-pipelines
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
  ingressClassName: nginx
  rules:
    - host: events.stellardata.top
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: el-ci-event-listener
                port:
                  number: 8080

7. 本地联调:模拟 GitHub Webhook

先把事件入口转发到本地,用伪造的 GitHub push 载荷测试:

# 端口转发(另开一个终端)
kubectl port-forward -n tekton-pipelines service/el-ci-event-listener 8080:8080
{
  "ref": "refs/heads/main",
  "head_commit": {
    "id": "9f8e7d6c5b4a3c2d1e0f1234567890abcdef1234"
  },
  "repository": {
    "clone_url": "https://github.com/example/demo-app.git"
  }
}
# 用与 Webhook Secret 相同的 token 计算 HMAC-SHA256 签名
SECRET="your-github-webhook-secret"
SIG=$(echo -n "$(cat /tmp/gh-payload.json)" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print "sha256="$2}')

# 发送模拟请求
curl -v -X POST http://localhost:8080 
  -H "Content-Type: application/json" 
  -H "X-GitHub-Event: push" 
  -H "X-Hub-Signature-256: $SIG" 
  --data @/tmp/gh-payload.json

# 验证流水线被自动创建并执行
kubectl get pipelineruns -n tekton-pipelines -l tekton.dev/trigger=github-push

签名错误(或缺失)时,EventListener 会返回 403 并拒绝触发;签名正确时返回 202 Accepted,并在几秒内看到新的 PipelineRun。

8. 配置 GitHub 仓库 Webhook

到 GitHub 仓库 Settings → Webhooks → Add webhook 填写:

字段
Payload URL https://events.stellardata.top/
Content type application/json
Secret 与步骤 2 生成的 token 一致
触发事件 勾选 Just the push event(或按需勾选 Pull requests)

保存后 GitHub 会立即发送一条 ping 事件,可在 Webhook 页面看到绿色勾(202 响应),说明链路已打通。

9. 进阶:CEL Interceptor 过滤分支

只允许 main 分支触发,并把 commit SHA 截短为 7 位传给下游:

apiVersion: triggers.tekton.dev/v1
kind: Trigger
metadata:
  name: github-main-trigger
  namespace: tekton-pipelines
spec:
  interceptors:
    - ref:
        name: "github"
      params:
        - name: "secretRef"
          value: { secretName: github-secret, secretKey: secretToken }
    - ref:
        name: "cel"
      params:
        - name: "filter"
          value: "body.ref == 'refs/heads/main'"
        - name: "overlays"
          value:
            - key: truncated-sha
              expression: "body.head_commit.id.truncate(7)"
  bindings:
    - ref: github-push-binding
  template:
    ref: ci-pipeline-template

CEL 过滤不满足时请求返回 200(不报错但也不创建资源),避免因「拒绝」在 GitHub 后台刷红叉。

常见问题

Q1: EventListener 的 Service 类型怎么选?

ClusterIP 适合集群内部调用或搭配 Ingress/API 网关;NodePort 适合无 Ingress 的裸金属环境做简单暴露;LoadBalancer 适合云厂商直接分配公网 IP。生产环境推荐 Ingress + ClusterIP,由网关统一管理 TLS 与域名。

Q2: GitHub Webhook 一直显示签名校验失败(403)?

最常见的原因有三个:一是 GitHub Webhook 里的 Secret 与 github-secret Secret 中 secretToken 的值不一致;二是 secretRefname/secretKey 写错;三是签名计算方式不对——流程是先拼接 JSON 请求体(原始 body 字符串)与 Secret 做 HMAC-SHA256,前缀 sha256=,任何转义或换行差异都会导致签名不匹配。可用步骤 7 的 curl 脚本与 openssl dgst 在本地先验证。

Q3: 如何让不同分支触发不同的流水线?

用多个 Trigger 配合 CEL filter:例如 body.ref == 'refs/heads/main' 绑定生产流水线模板,body.ref.startsWith('refs/heads/feature') 绑定开发流水线模板。也可以在同一个 TriggerTemplate 里通过 $(body.ref) 动态决定参数,再让流水线内部用 when 表达式(Tekton v1 的条件)做分支判断。

Q4: 一个 EventListener 可以挂多个 Trigger 吗?

可以。EventListener 的 spec.triggers 支持多个条目,每个条目可以引用不同的 Trigger(或内联定义),EventListener 会根据 URL 路径、Interceptor 过滤结果把事件路由到匹配的 Trigger。生产环境常用模式是:一个仓库一个 EventListener,内部挂「push 触发构建」「PR 触发测试」等多个 Trigger。

Q5: EventListener 挂了会影响生产吗?如何高可用?

EventListener 本质是无状态 Pod,Controller 会保证一个副本持续运行;同时它支持 spec.replicas 字段扩多副本(自动生成对应 Deployment)。更稳妥的做法是:在 Ingress/网关层面启用重试,并在推送端保留事件日志;对要求严格不丢事件的场景,可开启 Tekton Triggers 的 delivery 功能(把失败事件投递到 Dead Letter 队列),或在 CI 侧做「轮询补偿」(如 Argo CD 的 Image Updater 定期检查新镜像,第 31 天会讲)。

总结

  1. 事件驱动是生产 CI 的标配:Tekton Triggers 把「Webhook 事件 → PipelineRun」这条链路标准化,开发者只需 push 代码即可触发完整构建流程
  2. 三件套各司其职:EventListener 负责接收 HTTP 事件,TriggerBinding 负责从 JSON 载荷提取参数,TriggerTemplate 负责生成 PipelineRun——理解这个数据流就掌握了 Triggers 的核心
  3. 安全校验不可跳过:生产环境必须使用 Interceptor(GitHub/GitLab/CEL)校验签名并过滤事件类型,否则任何人都能伪造请求触发你的流水线
  4. CEL 拦截器是精细化控制的关键:分支过滤、字段截断、载荷改写都可以用 CEL 表达式完成,一个表达式就能实现「只对 main 分支触发」
  5. 与后续篇目衔接:Triggers 触发构建后,镜像将推送至 Harbor(第 29 天完整流水线),再由 Argo CD 拉取部署(第 30 天)——事件驱动只是全链路的起点
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

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

    暂无评论内容