Prow-Images & TessOps Deep Dive - Building and Configuring Kubernetes Clusters

在 eBay Tess 平台的 CI/CD 体系中,prow-imagestessops 是两个核心仓库,前者提供各类 CI 容器工具镜像,后者定义整个 K8s 集群的配置规格。理解它们的工作原理和协同方式,是深入参与 Tess 集群开发与维护的关键。本文将从整体架构出发,逐一拆解各核心组件,并重点介绍如何利用 tessops 作为集群配置源来搭建 K8s 集群。

整体架构概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────┐
│ Developer Workflow │
│ │
│ commit to tessops ──► Prow CI triggers ──► validates │
│ │ │
│ ┌───────────────▼──────────────┐ │
│ │ prow-images │ │
│ │ ┌────────┐ ┌────────────┐ │ │
│ │ │ kind │ │ e2e │ │ │
│ │ │ │ │ │ │ │
│ │ │ create │ │ apply tess-│ │ │
│ │ │ k8s │ │ ops specs │ │ │
│ │ │cluster │ │ & run tests│ │ │
│ │ └────────┘ └────────────┘ │ │
│ │ ┌────────┐ ┌────────────┐ │ │
│ │ │ bazel │ │ kaniko │ │ │
│ │ │ build │ │ img build │ │ │
│ │ └────────┘ └────────────┘ │ │
│ └───────────────────────────────┘ │
│ │
│ tessops (cluster config) ──► deployed to real clusters │
└─────────────────────────────────────────────────────────────┘

两个仓库分工明确:

  • prow-images:提供 CI/CD 流水线中使用的各类容器镜像(构建工具、测试框架、集群管理等)
  • tessops:定义 Tess 平台上 K8s 集群的完整配置规格(Salt 状态 + Releaser 模板)

prow-images 仓库解析

仓库结构

prow-images 包含约 46 个独立的 Docker 镜像目录,每个目录对应一个功能明确的工具镜像。镜像发布到内部 registry hub.tess.io/prowimages/

1
2
3
4
5
6
7
8
9
10
11
12
prow-images/
├── Makefile # 统一构建入口,32 个 image/push target
├── go.mod # Go module 依赖
├── kind/ # Kubernetes-in-Docker 集群管理
├── e2e/ # 端到端测试框架
├── bazel/ # Bazel 构建系统
├── golang/ # 多版本 Go 编译环境
├── kaniko/ # 容器镜像构建
├── git/ # Git 操作(含加密支持)
├── auto-approval/ # PR 自动审批
├── helm-bot/ # Helm chart 管理
└── ...

Makefile 构建模式统一:

1
2
3
4
5
6
image-kind:
docker build -t hub.tess.io/prowimages/kind:$(shell cat kind/VERSION) \
-f kind/Dockerfile .

push-kind:
docker push hub.tess.io/prowimages/kind:$(shell cat kind/VERSION)

版本由每个目录下的 VERSION 文件管理,当前 kind 镜像版本为 v0.0.13


核心镜像详解

1. kind 镜像 — K8s 集群创建核心

功能:在 Prow Job 容器内创建完整的 Kubernetes 集群,用于 E2E 测试。

Dockerfile 结构(以 1.32.Dockerfile 为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Stage 1: 编译 Go 入口程序 + 安装 git-crypt
FROM hub.tess.io/prowimages/golang:1.25-latest
WORKDIR /go/src/tess.io/contrib/prow-images
ADD . /go/src/tess.io/contrib/prow-images
RUN CGO_ENABLED=0 go install tess.io/contrib/prow-images/kind/entrypoint
RUN git clone https://github.com/AGWA/git-crypt.git && \
cd git-crypt && make && make install

# Stage 2: eBay 证书层
FROM hub.tess.io/tess/alpine:hardened as ebay-cert

# Stage 3: 基于 krte(Kubernetes Runtime Test Environment)的主镜像
FROM gcr.io/k8s-staging-test-infra/krte:latest-1.32
ARG KIND_VERSION="v0.27.0"
ARG KUBECTL_VERSION="v1.32.12"

COPY --from=ebay-cert /etc/ssl/certs/* /etc/ssl/certs/
RUN apt update && apt install -yy net-tools
COPY --from=0 /usr/bin/git-crypt /usr/bin/git-crypt
COPY --from=0 /go/bin/entrypoint /entrypoint

# 安装 kind、kubectl、helm
RUN curl -L ".../kind-linux-amd64" -o /usr/bin/kind && chmod +x /usr/bin/kind
RUN curl -L ".../kubectl" -o /usr/bin/kubectl && chmod +x /usr/bin/kubectl
RUN curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

ADD kind/config.toml /etc/containerd/config.toml
ENTRYPOINT ["/entrypoint"]

支持的 K8s 版本:1.231.281.321.34,每个版本对应一个独立 Dockerfile。

entrypoint(kind/entrypoint/main.go)的核心逻辑

1
2
3
4
5
6
7
1. 解析参数(--gpg-key, --repo-root, --script, --timeout 等)
2. 从 /credentials/githubtoken 读取 GitHub token
3. 若设置了 --wait-for,等待指定 GitHub Check 成功
4. 通过 git-crypt 解锁并 checkout 目标仓库
5. 若是 kubernetes-patches 仓库,还会 fetch 上游 K8s 源码并 apply patch
6. chdir 进入工作目录
7. 执行 bash -c <script>(实际的集群操作脚本)

kind node 镜像构建(build-node-image.sh)

kind 集群的每个节点是一个 Docker 容器,节点镜像需要包含特定版本的 K8s 组件。build-node-image.sh 完成以下工作:

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 根据 CI 环境确定 tag
tag="${BASE_REF}-$(date +%Y%m%d)" # 周期性 job
tag="pr-${PULL_NUMBER}" # PR job

# 2. 用 kind 官方工具从源码构建节点镜像
kind build node-image --image=hub.tess.io/kindest/node:${tag} --type=source .

# 3. 在节点镜像上叠加 eBay 定制层(证书 + containerd + 网络工具)
docker build -t hub.tess.io/kindest/node:${tag} -f /tmp/Dockerfile .

# 4. 推送到内部 registry
docker push hub.tess.io/kindest/node:${tag}

2. e2e 镜像 — 端到端测试编排

功能:编排 E2E 测试,管理集群申请/释放生命周期,执行测试脚本。

entrypoint(e2e/entrypoint/main.go)核心流程

1
2
3
4
// 1. 处理 addons-values 参数,设置环境变量 ADDONS_VALUES
// 2. 提取 PR HEAD SHA 并设置短格式环境变量
// 3. 执行 E2E 脚本(默认 /e2e.sh)
// 4. defer: 释放集群资源(调用 releaseCluster())

关键设计:即使测试失败,defer releaseCluster() 也确保集群被释放,避免资源泄露。


3. bazel 镜像 — 构建系统

基于官方 gcr.io/bazel-public/bazel:5.4.0,叠加:

  • eBay 内部 CA 证书(ebay.crt, certadmin.crt, ca.ebay.goskope.com.crt)
  • git-crypt(加密仓库支持)
  • crane(容器 registry 操作工具)

4. kaniko 镜像 — 无特权镜像构建

在 Prow Job(非特权容器)内构建 Docker 镜像,避免 Docker-in-Docker 的安全问题。


5. git 镜像 — 加密 Git 操作

专门处理 git-crypt 加密仓库的 clone/checkout,带 GitHub token 认证。


6. 自动化工具类镜像

镜像 功能
auto-approval 满足条件时自动 approve PR
auto-comment 在 PR 上自动发送评论
auto-security-pr 自动创建安全修复 PR
auto-validation 自动触发验证流程
autotag 自动打版本 tag
helm-bot 自动更新 Helm chart 依赖
prow-config-validator 验证 Prow 配置合法性

tessops 仓库解析

tessops 是 Tess 平台所有 K8s 集群的”配置真相来源”,采用 Salt(传统基础设施配置) + Releaser(云原生应用部署) 双轨架构。

目录结构

1
2
3
4
5
6
7
8
9
10
tessops/
├── hrt/ # Host Runtime - 主机级配置(与 K8s 无关)
├── kubernetes/ # K8s 组件配置(etcd/apiserver/kubelet...)
├── addon/ # 废弃的 kube-addon-manager 管理的插件
├── modules/ # 自定义 Salt 模块
├── reactor/ # Salt 事件驱动自动化
└── releases/ # 现代化 Releaser 部署规格(主要使用这里)
├── templates/ # 88 个组件的 Go 模板
├── values/ # 分层配置参数
└── kubectl.yaml # Releaser CRD 配置

Salt 配置层(hrt + kubernetes)

Salt 负责节点级别的基础配置,在集群启动时由 Salt minion 执行:

hrt(Host Runtime Templating)

1
2
3
4
5
6
hrt/
├── pillar/<profile>/ # 环境参数(ci, dev, production...)
└── salt/common/
├── kernel-modules/ # 加载内核模块
├── sssd/ # SSSD 认证配置
└── sysctl/ # 内核参数调优

kubernetes salt 环境

1
2
3
4
5
6
7
8
9
10
11
12
kubernetes/
├── pillar/
│ ├── ci/ # CI 集群 pillar
│ ├── production/ # 生产集群 pillar
│ ├── testing/
│ └── ... # 共 14 个集群 profile
└── salt/common/
├── etcd/ # etcd 安装和配置
├── apiserver/ # kube-apiserver 静态 manifest
├── kubelet/ # kubelet 配置和服务
├── certs/ # 证书生成和管理
└── ...

Releases 配置层(核心)

releases 是取代 addon 的现代化部署系统,使用 Releaser 控制器将 Go 模板渲染为 YAML 并 kubectl apply 到集群。

模板系统

releases/templates/ 下有 88 个组件目录,每个包含 K8s 资源的 Go 模板:

1
2
3
4
5
6
7
8
templates/
├── 00-base/ # 集群基础资源(namespace, clusterrole, CRD)
├── 01-ebay-controller-manager/ # eBay 定制控制器
├── 02-managed-cd/ # ArgoCD 管理的 CD 流程
├── cilium-*/ # Cilium CNI 相关
├── ai-adoption-controller/
├── applicationinstance-status-controller/
└── ...(共 88 个组件)

模板语法示例:

1
2
3
4
5
6
7
8
9
10
11
# 根据集群类型条件化配置
replicas: {{if eq .cluster_type "ci"}}1{{else}}3{{end}}

# 使用默认值
image: {{.tetragon_image | default "hub.tess.io/tess/tetragon:latest"}}

# 引用 pillar 中的敏感配置
username: {{.pillar_kube2udns_username}}

# 原样输出(避免二次解析)
config: '{{`{{.internalTemplate}}`}}'

值(Values)层次体系

这是 tessops 最精妙的设计:通过分层合并实现”通用默认 + 环境覆盖 + 集群精细化”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
releases/values/
├── global/ # 所有集群的默认值(110+ 文件)
├── regions/<region>/ # 地域级覆盖
├── zones/<zone>/ # 可用区级覆盖
├── envs/<env>/ # 环境级覆盖(dev/staging/prod)
├── clustertypes/
│ ├── ci/ # CI 集群类型覆盖(50+ 配置项)
│ ├── production/
│ ├── testing/
│ └── ...
└── clusters/
├── 11/ # 特定集群精细化配置
├── 103/
└── ...(180+ 个具体集群)

优先级(从高到低)

1
cluster > clustertypes > envs > zones > regions > global

CI 集群类型覆盖示例(values/clustertypes/ci/):

1
2
3
4
5
6
7
8
# 最小化资源占用
image_resource_requests_cpu: "0"
image_resource_requests_memory: "0"
force_best_effort: "true"

# CI 特定行为
account_hierarchy_admission: "enabled"
cilium_env: "dev"

kubectl.yaml — Releaser 控制器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: fleet.crd.tess.io/v1alpha1
kind: KubectlConfiguration
spec:
paths:
- releases/templates/ # Go 模板目录
valueTemplates:
- configMapRef: pillars # 从 ConfigMap 读取 pillar 值
- secretRef: pillars # 从 Secret 读取敏感 pillar 值
prune:
skip:
- apiGroups: [""]
resources: ["ConfigMap"]
namespaces: ["kube-system"]
# ... 大量不受 tessops 管理的资源
ytt: releases/ytt.yaml # YTT overlay 进一步过滤

如何用 tessops 构建 K8s 集群

理解架构后,来看实际的集群构建流程。E2E 测试中的集群创建是最典型的使用场景。

Step 1:启动 kind 容器(Prow Job 配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Prow Job 配置示例
spec:
containers:
- image: hub.tess.io/prowimages/kind:v0.0.13-1.32
args:
- --gpg-key=/credentials/git-crypt.key
- --repo-root=/workspace
- --script=/path/to/create-cluster.sh
- --timeout=2h
volumeMounts:
- name: github-token
mountPath: /credentials/githubtoken
subPath: token
- name: git-crypt-key
mountPath: /credentials/git-crypt.key
subPath: key

Step 2:entrypoint 执行集群创建

kind 镜像的 entrypoint 完成:

  1. 使用 git-crypt 解锁并 checkout tessops 仓库
  2. 调用用户提供的 --script

Step 3:kind 集群初始化脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# create-cluster.sh

# 1. 创建 kind 集群(使用 tessops 提供的 containerd 配置)
kind create cluster --config kind-config.yaml \
--image hub.tess.io/kindest/node:v1.32-20260101

# kind-config.yaml 示例:
# kind: Cluster
# apiVersion: kind.x-k8s.io/v1alpha4
# nodes:
# - role: control-plane
# - role: worker
# - role: worker
# containerdConfigPatches:
# - |-
# [plugins."io.containerd.grpc.v1.cri".registry.mirrors."hub.tess.io"]
# endpoint = ["https://hub.tess.io"]

# 2. 等待集群就绪
kubectl wait --for=condition=Ready nodes --all --timeout=5m

# 3. 应用 tessops 配置(使用 Releaser 或直接 kubectl apply)
kubectl apply -f /workspace/tessops/releases/templates/00-base/

Step 4:通过 Releaser 应用 tessops 规格

在真实集群中,Releaser 控制器负责持续应用 tessops 配置:

1
2
3
4
5
6
7
8
9
10
# 部署在集群中的 Release 对象
apiVersion: fleet.crd.tess.io/v1alpha1
kind: Release
metadata:
name: tessops-addons
namespace: kube-system
spec:
branch: master # tessops 主分支 → 生产集群
revision: <commitID> # 指定精确 commit
repository: https://git.vip.ebay.com/tess/tessops

Releaser 控制器执行流程:

1
2
3
4
5
6
7
1. 拉取 tessops 仓库指定 revision
2. 读取 kubectl.yaml 中的配置
3. 根据当前集群的 region/zone/env/clustertype/cluster 查找并合并 values
4. 从 ConfigMap/Secret "pillars" 补充敏感配置
5. 渲染 88 个组件的 Go 模板
6. kubectl apply 所有生成的 YAML
7. prune 删除不再管理的资源(根据 skip 规则)

Step 5:E2E 测试中的 addons 值注入

e2e 镜像支持通过 --addons-values 参数覆盖 tessops 的默认值:

1
2
3
4
5
6
7
# Prow E2E Job 中的参数
/e2e-entrypoint \
--addons-values="tetragon_enabled=true" \
--addons-values="cilium_env=dev" \
--addons-values="some_feature=enabled" \
--script=/e2e.sh \
--timeout=150m

entrypoint 将这些参数转换为 ADDONS_VALUES 环境变量,供 E2E 脚本读取并传递给 Releaser。


集群配置分层实例

以 CI 集群 #11 为例,展示配置合并过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 最终生效的值 = 以下各层合并(高优先级覆盖低优先级)

# global/tetragon.yaml
tetragon_enabled: "false"
tetragon_image: "hub.tess.io/tess/tetragon:v0.10.0"

# clustertypes/ci/resources.yaml
image_resource_requests_cpu: "0"
force_best_effort: "true"
cilium_env: "dev"

# envs/staging/networking.yaml
cilium_version: "1.14.5"

# clusters/11/overrides.yaml ← 最高优先级
tetragon_enabled: "true" # 仅集群 11 开启 tetragon

常见 Prow 镜像组合

场景 使用的镜像
构建 Go 代码 golang:1.24-latest
构建容器镜像 kaniko:v1.23.2
K8s E2E 测试 kind:v0.0.13-1.32 + e2e:latest
运行 Bazel 构建/测试 bazel:v5.4.0
Git 加密仓库操作 git:latest
Helm chart 更新 helm-bot:latest
安全 PR 自动化 auto-security-pr:latest
验证 Prow 配置 prow-config-validator:latest

总结

prow-imagestessops 共同构成了 Tess 平台 CI/CD 的基础设施骨架:

  • prow-images 提供工具层:每个 Docker 镜像职责单一,通过 VERSION 文件精确版本管理,通过 eBay 证书注入适配内网环境
  • tessops 提供配置层:Salt 管理主机级状态,Releaser 管理 K8s 应用级状态,通过 6 级值层次体系实现”一份配置,多集群复用”
  • 两者协同:Prow Job 使用 kind 镜像创建测试 K8s 集群,使用 e2e 镜像将 tessops 配置应用到测试集群并执行验证,通过后 tessops master 分支变更自动同步到生产集群

这套架构的优雅之处在于:代码即配置(Configuration as Code) 落地得非常彻底——从节点的 sysctl 参数到集群的 CNI 配置,从 CI 脚本到生产部署,全部以 Git 仓库作为唯一真相来源,通过 PR 审核机制保障变更质量。