CI 作业里 kubectl 连不上集群?用 Kubernetes Agent 打通部署链路的 7 个步骤

本文环境

先把环境交代清楚,版本不一致时下面的命令和字段位置可能略有差别。

组件 版本 / 说明
极狐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 转发到集群。

带来的三个直接变化:

  1. APIServer 不需要对 Runner 开放任何入站端口;
  2. CI 里不再有长期 kubeconfig,用的是 ci:<agent-id>:<CI_JOB_TOKEN> 形态的作业级凭证;
  3. 权限边界由集群侧 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 认证 ------ 那又是另一套配置,改天单独写。

相关推荐
用户837133200762 小时前
AI 写的发布流程 CI 全绿,为什么仍没通过需求验收?
ci/cd·github
天天喝旺仔3 小时前
CI/CD 实战:GitHub Actions 自动化构建、测试与发布流水线
ci/cd·自动化·github·devops·持续集成
小稀土1233 小时前
内网私有化部署 DevOps 软件落地指南:8 个坑、6 个步骤,一次讲清
devops
筑梦之路4 小时前
Kafka KRaft 模式 Kubernetes 部署手册(StatefulSet + apache/kafka 官方镜像)——筑梦之路
kafka·kubernetes·apache
浪子明X4 小时前
用 CMake Presets 固定跨平台构建:从本地开发到 CI 的依赖一致性实践
java·spring·ci/cd
cakeism8254 小时前
金融行业 DevOps 平台推荐:2026年主流方案对比与 Gitee 选型解析
金融·gitee·devops
A.说学逗唱的Coke4 小时前
【云原生专题】Kubernetes 备份完全实战:用 Velero 搞定集群备份、恢复与跨集群迁移(附完整命令与踩坑记录)
云原生·容器·kubernetes
Zhou1411365 小时前
Git_02_GitLab协作与CI_CD
git·ci/cd·gitlab
白帽攻防录5 小时前
SRC 挖洞:GitLab GraphQL 指令绕过深度复盘,CVE-2026-19478 未授权删项目怎么打穿代码托管平台
网络·网络安全·gitlab·graphql