本文环境
先把环境交代清楚,版本不一致时下面的命令和字段位置可能略有差别。
| 组件 | 版本 / 说明 |
|---|---|
| 极狐GitLab | 17.x 私有化部署(专业版及以上,推送规则之外的 KAS 能力为基础版起) |
| Kubernetes | 1.30(云厂商托管集群),APIServer 不对公网开放 |
| GitLab Agent Helm Chart | gitlab/gitlab-agent(charts.gitlab.io) |
| kubectl | v1.34 |
| Helm | 3.14+ |
| Runner | Docker 执行器,与集群不在同一 VPC |
| 操作系统 | 本地终端 macOS 14 / 集群节点 Linux |
一、问题复现
团队最初的部署方式是「把 kubeconfig 存成 CI/CD 变量,流水线里解出来用」。跑起来之后,流水线稳定失败,报的是下面这两种错之一。
第一种,Runner 根本连不上 APIServer:
text
$ kubectl apply -f deployment.yaml
Unable to connect to the server: dial tcp 10.0.3.12:6443: i/o timeout
第二种,侥幸连上了,但权限不对:
text
$ kubectl get pods
Error from server (Forbidden): pods is forbidden:
User "system:serviceaccount:default:default" cannot list resource "pods"
in API group "" in the namespace "default"
第三种更隐蔽,本地能跑、CI 里报凭证失效:
text
error: You must be logged in to the server
(the server has asked for the client to provide credentials)
复现用的最小脚本
bash
# 本地:把 kubeconfig 编成 base64,塞进 CI/CD 变量 KUBE_CONFIG_B64
cat ~/.kube/config | base64 -w0
yaml
# .gitlab-ci.yml(改造前的写法,问题就出在这里)
deploy:
stage: deploy
image: bitnami/kubectl:1.30
script:
- echo "$KUBE_CONFIG_B64" | base64 -d > kubeconfig
- export KUBECONFIG=$CI_PROJECT_DIR/kubeconfig
- kubectl get pods
- kubectl apply -f k8s/
二、原因定位
三条根因,逐条对应上面三类报错。
第一条是网络。 Runner 在 CI 网络里,集群 APIServer 只在内网可达。要让它通,要么给 APIServer 开公网(安全风险),要么把 Runner 塞进集群内网(运维复杂度)。两条路都不理想。
第二条是凭证形态。 kubeconfig 里的 ServiceAccount token 是长期凭证,一旦变量泄漏或某个开发者把它复制到了别处,有效期就是永久。而且它跟「人」无关,出事后无法追溯到具体账号。
第三条是权限粒度。 为了让 CI 能部署,很多人直接给 cluster-admin。于是任何一条流水线作业都拥有整个集群的最高权限,包括删 namespace。
极狐GitLab Kubernetes Agent(下文简称 Agent)换了个模型:由集群内的 agentk 主动外连极狐GitLab 的 KAS 服务,CI 作业时 kubeconfig 由平台自动注入,kubectl 请求通过 KAS 的 k8s-proxy 转发到集群。
带来的三个直接变化:
- APIServer 不需要对 Runner 开放任何入站端口;
- CI 里不再有长期 kubeconfig,用的是
ci:<agent-id>:<CI_JOB_TOKEN>形态的作业级凭证; - 权限边界由集群侧 RBAC 决定,可以做到「这个 Agent 只能动这两个 namespace」。
三、7 个步骤打通部署链路
步骤 1:确认 KAS 地址
这一步必须最先做,地址错了后面全部白搭。
- JihuLab.com(SaaS):
wss://kas.gitlab.com - 私有化部署:默认在
wss://<你的域名>/-/kubernetes-agent/,具体以管理员配置的 agent 服务器地址为准
bash
# 私有化部署上确认 KAS 是否可达(换成你的域名)
curl -sS -o /dev/null -w "%{http_code}\n" https://gitlab.example.com/-/kubernetes-agent/
踩坑 1:不少私有化部署实例没启用 KAS。找管理员确认「极狐GitLab Kubernetes Agent」组件已启用,否则 agent 注册成功但永远连不上,状态一直灰着。
步骤 2:创建 agent 配置文件
在存放 agent 配置的项目里,默认分支上建文件:
bash
mkdir -p .gitlab/agents/prod-agent
touch .gitlab/agents/prod-agent/config.yaml
路径规范是 .gitlab/agents/<agent-name>/config.yaml。
yaml
# .gitlab/agents/prod-agent/config.yaml
# 先留空也行,后面授权时再补
ci_access:
projects:
- id: acme/backend/api-service
踩坑 2 :agent 名称遵循 RFC 1123 的 DNS 标签标准 ------ 最多 63 个字符,只能包含小写字母数字和
-,且必须以字母数字开头和结尾。写成prod_agent或Prod-Agent都会注册失败。
步骤 3:注册 agent 并保存令牌
路径:项目 → 运维 → Kubernetes 集群 → 连接集群(agent)→ 新 agent 名称填 prod-agent → 创建并注册。
页面会给出 agent 访问令牌和推荐安装命令,两样都复制下来。
text
# 页面给出的令牌形如(示例,勿直接使用)
glagent-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
踩坑 3 :令牌只在注册时明文展示一次。恶意攻击者拿到这个令牌,可以访问 agent 配置项目的源代码、实例上任何公开项目的源代码,在特定条件下甚至能拿到 Kubernetes 清单文件。务必按密钥等级保管,别贴进 issue 或聊天记录。
步骤 4:用 Helm 安装 agentk(生产别用默认权限)
默认 Helm 命令会给服务账户绑 cluster-admin,官方文档明确写了「不应在生产系统上使用此配置」。先准备一个最小权限角色:
yaml
# rbac.yaml ------ 只允许在指定 namespace 内部署常见工作负载
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitlab-agent-deploy
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "services", "deployments", "replicasets", "jobs", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gitlab-agent-deploy
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gitlab-agent-deploy
subjects:
- kind: ServiceAccount
name: gitlab-agent
namespace: gitlab-agent
bash
kubectl apply -f rbac.yaml
然后安装,跳过 chart 自带的 cluster-admin 绑定:
bash
helm repo add gitlab https://charts.gitlab.io
helm repo update
helm upgrade --install prod-agent gitlab/gitlab-agent \
--namespace gitlab-agent \
--create-namespace \
--set config.token=<你的 agent 令牌> \
--set config.kasAddress=wss://gitlab.example.com/-/kubernetes-agent/ \
--set rbac.create=false \
--set serviceAccount.create=false \
--set serviceAccount.name=gitlab-agent
踩坑 4 :
--set rbac.create=false之后你必须自己准备 ClusterRoleBinding,否则 agentk 起来但没有任何权限,CI 里会报Forbidden。如果不确定,先用--set rbac.useExistingRole=gitlab-agent-deploy过渡到自定义角色。
验证 agent 是否连上:
bash
kubectl -n gitlab-agent get pods
kubectl -n gitlab-agent logs deploy/prod-agent --tail=50
回到极狐GitLab 的「运维 → Kubernetes 集群」页面,agent 状态应变为已连接。
步骤 5:授权业务项目访问 agent
回到步骤 2 的 config.yaml,把要用这个 agent 部署的项目授权进去。
yaml
# .gitlab/agents/prod-agent/config.yaml
ci_access:
projects:
- id: acme/backend/api-service
- id: acme/backend/worker-service
groups:
- id: acme/frontend
授权粒度有三种:
| 写法 | 生效范围 | 限制 |
|---|---|---|
ci_access.projects |
指定项目 | 最多 500 个项目 |
ci_access.groups |
群组及其全部子群组 | 最多 500 个群组 |
ci_access.instance: {} |
实例上所有项目 | 需管理员开启实例级授权 |
踩坑 5 :授权配置可能需要一到两分钟才传播生效 。改完立刻跑流水线会拿到空的 context,表现为
error: no context exists with the name: ...。等两分钟再试,别急着改配置。
步骤 6:在 .gitlab-ci.yml 里选 context
context 的格式是 <agent 配置项目路径>:<agent 名称> ------ 注意用的是agent 所在的配置项目路径,不是你的业务项目路径。
yaml
deploy:
stage: deploy
image: debian:13-slim
variables:
KUBECTL_VERSION: v1.34
DEBIAN_FRONTEND: noninteractive
script:
- apt-get update
- apt-get install -y --no-install-recommends apt-transport-https ca-certificates curl gnupg
- kubectl config get-contexts
- kubectl config use-context acme/infra/clusters:prod-agent
- kubectl -n production apply -f k8s/
- kubectl -n production rollout status deploy/api-service --timeout=180s
如果用的是自带 kubectl 的镜像,可以省掉安装步骤:
yaml
deploy:
image:
name: bitnami/kubectl:1.30
entrypoint: [""]
script:
- kubectl config get-contexts
- kubectl config use-context acme/infra/clusters:prod-agent
- kubectl -n production apply -f k8s/
不确定 context 叫什么,就在作业里先跑一条:
bash
kubectl config get-contexts
踩坑 6 :
$KUBECONFIG环境变量里会包含所有已授权 agent 的 context。多集群场景下不选 context 直接用 kubectl,会打到默认那个上(可能是 staging)。多集群环境必须在每个作业里显式use-context。
步骤 7:多集群与环境变量
staging 和 production 各装一个 agent 时,用环境作用域的 KUBE_CONTEXT 变量区分:
yaml
deploy:
variables:
KUBE_CONTEXT: acme/infra/clusters:staging-agent
environment:
name: staging
bash
# 在 CI 变量里为 production 环境再定义一个同名变量
# Key: KUBE_CONTEXT
# Value: acme/infra/clusters:prod-agent
# Environment scope: production
自签名证书场景下,需要把 CA 传给 agent:
bash
helm upgrade --install prod-agent gitlab/gitlab-agent \
--namespace gitlab-agent \
--set-file config.kasCaCert=my-custom-ca.pem
四、验证结果
改造前后在同一条流水线上的对比:
| 指标 | 改造前(kubeconfig 变量) | 改造后(Agent) |
|---|---|---|
| APIServer 暴露面 | 需对 Runner 网络开放 | 零入站开放 |
| CI 中的凭证类型 | 长期 ServiceAccount token | 作业级 ci:<id>:<job-token> |
| 凭证轮换 | 手工,易遗漏 | 随作业生命周期自动失效 |
| 权限范围 | 常见为 cluster-admin | 可由 RBAC 精确到 namespace + 资源 |
| 部署成功率(两周统计) | 约 76%(多为超时/Forbidden) | 约 99% |
| 密钥泄漏风险 | 变量一旦泄漏长期有效 | 无法离线复用 |
踩坑 7 :别以为装了 Agent 就万事大吉。Agent 打通的是访问链路,不是权限治理。如果 agentk 的服务账户仍然是 cluster-admin,那么任何被授权的项目的任何 CI 作业都拥有集群最高权限。链路修好之后,下一步是收 RBAC。
五、排错速查
| 现象 | 可能原因 | 处理 |
|---|---|---|
no context exists with the name |
context 拼错,或授权未传播 | 核对 <配置项目路径>:<agent名>,等 1--2 分钟 |
Unauthorized / 401 |
agent 令牌失效或 agentk 未连上 | 查 agentk 日志,必要时重新注册 |
Forbidden |
RBAC 未绑定或权限不足 | 检查 ClusterRoleBinding 与 verbs |
| agent 一直未连接 | KAS 地址错、出网被拦、证书不受信 | 核对 config.kasAddress,必要时设 config.kasCaCert |
| 部署打到错误集群 | 未显式 use-context |
每个作业显式指定 context |
小结
整个链路的本质是把「从外部连进来」改成「从集群内连出去」。网络暴露面归零,长期凭证消失,权限边界交给 RBAC------这三件事是这次改造真正的收益,部署成功率只是副产品。
需要本文用到的 RBAC 清单、config.yaml 模板和 .gitlab-ci.yml 完整样例?脚本和资料我整理成了一份压缩包,在评论区留言「KAS 部署包」或在主页置顶文章里自取,就不在这里贴外链了(容易被判引流)。
如果你的集群是被动式场景(集群无法主动外连),可以看下「极狐GitLab 连接至 agent」这一段,旗舰版私有化部署支持,用的是 JWT 认证 ------ 那又是另一套配置,改天单独写。