给 mirrord 开发者按 namespace 签发 kubeconfig
目录
我们的服务部署在 K8s 环境里,要调试一个服务,需要改代码、提 GitLab MR、跑 Jenkins 构建、ArgoCD 部署,整个流程非常慢。而且如果多个人要调试同一个服务的不同 feature,环境占用就会是个问题。为了提升调试效率,我们引入了 mirrord OSS——它能绕过整个 CI/CD 链路,直接在本地进程里接入集群服务。但 mirrord OSS 不带权限模型——直接发 cluster-admin 不现实。这篇我从 DevOps 角度走一遍:怎么给一个开发者签发 kubeconfig、按 namespace 给他绑 RoleBinding、验证授权真的生效。
团队层面「几十个人来来去去怎么管」是另一个更通用的 K8s 用户管理问题,不属于 mirrord 特有,我会另起一篇。
这篇文章对应的可运行 demo 放在 GitHub:meirongdev/mirrord-demo,对应 commit 是 6e44011。如果只是想看脚本细节,可以直接看 repo 里的 rbac/ 目录。
配置形态里有一处不直观的地方:除了按 namespace 绑的
RoleBinding,每个开发者还要额外绑一条只含 1 条规则的 cluster-scopedClusterRoleBinding。这条来自 K8s impersonation 鉴权的硬约束,单独追到的踩坑过程另起一篇 《VS Code 跑 mirrord 撞上 WebSocket 403》。本文直接采用修复后的形态。
一、这次 demo 想解决什么#
我自己在团队里推 mirrord 时,技术上没有大问题;卡住的反而是「给开发者发什么 kubeconfig」。mirrord OSS 不会替我们解决这件事,所以真实团队里通常还会多出几个治理问题:
- 开发者不应该拿
cluster-adminkubeconfig。 - 新发的 kubeconfig 默认最好没有任何业务 namespace 权限。
- 授权应该按 namespace 做,而不是一发就能扫整个集群。
- 撤销访问时,最好只删一条授权关系,不重新签发所有凭据。
- 管理员需要一套可重复验证的脚本,证明 allow/deny 都按预期工作。
所以这次 demo 的目标不是介绍 mirrord 的所有能力,而是验证一套更窄的接入模型:
- 管理员先定义两个 ClusterRole:
mirrord-developer(namespace 内的所有 mirrord API 能力)和mirrord-impersonator(只 1 条规则,cluster-scoped)。 - 管理员给开发者签发一个只代表身份的 kubeconfig。
- 开发者拿到 kubeconfig 后,默认不能访问任何 namespace。
- 管理员用
RoleBinding(namespace 绑mirrord-developer)+ 一条ClusterRoleBinding(绑mirrord-impersonator)把这个身份接进去。 - 开发者只能在被绑定的 namespace 里使用 mirrord;多出来的 cluster-scoped 能力只有 impersonate SA 这 1 条。
二、demo 结构#
repo 里的核心目录是 rbac/:
rbac/
├── README.md
├── validate-rbac.sh
├── admin/
│ ├── kind-config.yaml
│ ├── manifests/
│ │ ├── 01-mirrord-developer-clusterrole.yaml
│ │ ├── 01b-mirrord-impersonator-clusterrole.yaml
│ │ ├── 02-namespaces.yaml
│ │ ├── 03-test-workloads.yaml
│ │ ├── 04-rolebinding-template.yaml
│ │ ├── 04b-mirrord-impersonator-rolebinding.yaml
│ │ ├── 04-mysql-template.yaml
│ │ └── 05-app-template.yaml
│ └── scripts/
│ ├── bootstrap-cluster.sh
│ ├── issue-developer-kubeconfig.sh
│ ├── grant-namespace-access.sh
│ └── revoke-namespace-access.sh
└── developer/
├── mirrord.json
└── scripts/
├── whoami.sh
└── run-mirrord.sh
这个 demo 用 kind 起一个本地集群,里面有两个 namespace:
team-a-devteam-b-dev
每个 namespace 都有自己的 workload。开发者 alice 只会被授权到 team-a-dev,然后用脚本证明她能访问 team-a-dev,但访问不了 team-b-dev。
三、我用的权限模型#
这套模型里的关键对象:
| 对象 | 谁维护 | 作用 |
|---|---|---|
ClusterRole/mirrord-developer |
管理员 | 定义 mirrord 在一个 namespace 内需要的所有 API 能力 |
ClusterRole/mirrord-impersonator |
管理员 | 仅 1 条规则:cluster-scoped 的 serviceaccounts: impersonate |
| client cert + kubeconfig | 管理员签发,开发者持有 | 表示开发者身份,比如 alice |
RoleBinding(每用户每 namespace 一条) |
管理员 | 把开发者绑到 mirrord-developer,作用域限定到 namespace |
ClusterRoleBinding(每用户一条) |
管理员 | 把开发者绑到 mirrord-impersonator,仅放出 1 条 cluster-scoped verb |
这里有一个容易混淆的点:ClusterRole 本身是全集群对象,但如果通过 RoleBinding 引用它,权限会被限制在 RoleBinding 所在的 namespace 内。也就是说,ClusterRole 负责定义能力,RoleBinding 负责限定作用域。这一点在 Kubernetes 官方 RBAC 文档 的 RoleBinding and ClusterRoleBinding 一节里有明确说明。
绝大多数 mirrord 需要的权限走的就是这条路径:能力写在 mirrord-developer 里,作用域靠 RoleBinding 限到 namespace。但 K8s impersonation 鉴权里有一条 cluster-scoped 检查 RoleBinding 物理上接不住——只能通过 ClusterRoleBinding 授权(另一篇文章 详细解释了为什么)。为了把这条 cluster-scoped 例外的爆炸半径压到最小,demo 里额外做了一个只含 1 条规则的 ClusterRole/mirrord-impersonator:这样开发者多出来的 cluster-scoped 能力只有 impersonate SA 一条,跨 namespace 隔离没破。
四、管理员先准备 kind 集群和 workload#
详细步骤在 rbac/README.md 和 admin-runbook.md。核心命令大概是这样:
mvn -q package
docker build -t mirrord-demo:local .
kind load docker-image mirrord-demo:local --name mirrord-rbac-demo
bash rbac/admin/scripts/bootstrap-cluster.sh
bootstrap-cluster.sh 会创建 kind 集群、ClusterRole/mirrord-developer,以及 team-a-dev / team-b-dev 两个 namespace。
然后把 Spring Boot + MySQL 的 template apply 到两个 namespace,作为 mirrord 的调试目标:
sed 's/TEAM/team-a-dev/g' rbac/admin/manifests/04-mysql-template.yaml | kubectl apply -f -
sed 's/TEAM/team-a-dev/g' rbac/admin/manifests/05-app-template.yaml | kubectl apply -f -
sed 's/TEAM/team-b-dev/g' rbac/admin/manifests/04-mysql-template.yaml | kubectl apply -f -
sed 's/TEAM/team-b-dev/g' rbac/admin/manifests/05-app-template.yaml | kubectl apply -f -
到这里,每个 namespace 里都有一组 Spring Boot + MySQL,后面就能验证本地进程通过 mirrord 接入 cluster 内 MySQL 的路径。
五、ClusterRole:把 mirrord 需要的能力集中审查#
主要 YAML 在 01-mirrord-developer-clusterrole.yaml。下面是这次 demo 里实际用到的权限轮廓:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: mirrord-developer
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "secrets", "endpoints"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["argoproj.io"]
resources: ["rollouts"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "delete"]
- apiGroups: [""]
resources: ["pods/ephemeralcontainers"]
verbs: ["update", "patch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: [""]
resources: ["pods/portforward"]
verbs: ["create"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get", "impersonate"]
这里有几个点需要单独说明。
第一,jobs 和 pods 的 create/delete 是实权。mirrord OSS 会在目标 namespace 里创建 agent 相关资源,所以这个权限不能随便给到生产 namespace。
第二,pods/log、events、pods/portforward 不是装饰项。它们会影响 agent 启动失败时的排障体验,以及本地进程和 agent 之间的数据通道。
第三,secrets 只给了只读,是为了让应用通过 Secret / ConfigMap 注入配置时本地进程也能拿到。如果你不希望开发者直接读 namespace 里的 Secret,可以把这一条从 ClusterRole 里拆掉。
第四,pods/ephemeralcontainers 是可选能力。demo 里把它放进来,是为了覆盖 mirrord 配置切到 ephemeral container 模式时的权限需求;如果你的集群明确不允许这种模式,可以先不开放。
额外的 mirrord-impersonator#
上面 mirrord-developer 里那条 serviceaccounts: get, impersonate 只能匹配 namespace-scoped 的 SA impersonation 检查。但 K8s 实际 impersonation 鉴权还会跑另一条 cluster-scoped 的 users 虚拟资源检查——RoleBinding 物理上接不住——所以 demo 里再单独追加一个只含 1 条规则的 ClusterRole,文件是 01b-mirrord-impersonator-clusterrole.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: mirrord-impersonator
rules:
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get", "impersonate"]
为什么必须拆出来:mirrord client 发请求时会同时触发 users 和 serviceaccounts 两条 impersonation 鉴权,其中 users 那条只能在 cluster 作用域下匹配规则,RoleBinding 物理上接不住——所以必须有一条 cluster-scoped 授权。完整原委见 《VS Code 跑 mirrord 撞上 WebSocket 403》 §五–§七。
为什么不直接给 mirrord-developer 加一条 ClusterRoleBinding 就完事:那样最省事,一行解决,但等于把上面那十几条 namespace verb(list pods / create jobs / pods/portforward …)一起提升到 cluster 作用域,开发者立刻能在 kube-system、default、team-b-dev 全部 namespace 里 list pods、create jobs——整个「按 namespace 隔离」的治理目标当场就破了。拆出 mirrord-impersonator 是为了让这条不得不放出去的 cluster-scoped 授权只覆盖 1 个 verb,而不是十几个。
六、Namespace 需要配合 Pod Security#
这次 demo 的 namespace 配置不是 baseline,而是 privileged:
apiVersion: v1
kind: Namespace
metadata:
name: team-a-dev
labels:
app.kubernetes.io/part-of: mirrord-rbac-demo
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/enforce-version: latest
原因是 mirrord-agent 需要一些较高权限的 Linux capabilities,比如 NET_ADMIN 和 SYS_PTRACE。如果 namespace 开了较严格的 Pod Security Admission,agent pod 很可能会在准入阶段被拒绝。
这里 pod-security.kubernetes.io/enforce 这套 label 假设的是 Kubernetes 内置的 Pod Security Admission(K8s ≥ 1.22 引入、≥ 1.25 默认开启)。如果你的集群是用 Kyverno / OPA Gatekeeper 做策略,或者 OpenShift 的 SCC,这套 label 本身不起作用,要换成对应机制下的放行规则。
dev namespace 这样放是为了让 demo 能跑通;production namespace 不要为了 mirrord OSS 直接放 privileged,更细的策略(Kyverno / OPA / 镜像限制)可以另外讨论。
七、开发者 kubeconfig:用 Kubernetes CSR API 签发#
demo 用 Kubernetes CSR API 给开发者签客户端证书(官方教程 Issue a Certificate for a Kubernetes API Client Using A CertificateSigningRequest),脚本在 issue-developer-kubeconfig.sh,下面 6 步基本对应官方教程的流程。
管理员执行:
bash rbac/admin/scripts/issue-developer-kubeconfig.sh alice
脚本做了几件事:
- 生成
alice.key。 - 生成
CN=alice的 CSR。 - 创建
CertificateSigningRequest。 - 管理员 approve 这个 CSR。
- 从 CSR 的
status.certificate取回签好的客户端证书。 - 把 cluster server、CA、client cert、client key 组装成 standalone kubeconfig。
CSR 里关键的部分是:
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: mirrord-rbac-demo-alice
spec:
signerName: kubernetes.io/kube-apiserver-client
expirationSeconds: 31536000
usages:
- client auth
签发完成后,alice.kubeconfig 可以认证成 Kubernetes user alice。但此时她还没有任何业务 namespace 的授权:
KUBECONFIG=rbac/.credentials/alice.kubeconfig kubectl auth whoami
# alice
KUBECONFIG=rbac/.credentials/alice.kubeconfig kubectl auth can-i list pods -n team-a-dev
# no
这里要特别注意:standalone kubeconfig 里已经嵌入了 client key,所以它本身就是敏感凭据。不要提交到 Git,也不要用普通聊天工具分发。
八、授权和撤销:RoleBinding + 一条 ClusterRoleBinding#
签发 kubeconfig 只是给了身份。真正的授权发生在 binding 这一层:
bash rbac/admin/scripts/grant-namespace-access.sh alice team-a-dev
脚本会渲染并 apply 两份 YAML——一份 namespace 限定的 RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mirrord-developer-alice
namespace: team-a-dev
labels:
mirrord-rbac-demo/user: alice
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mirrord-developer
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: alice
和一份 per-user 的 ClusterRoleBinding,引用 §五 末尾那个只 1 条规则的 mirrord-impersonator:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: mirrord-impersonator-alice
roleRef:
kind: ClusterRole
name: mirrord-impersonator
subjects:
- kind: User
name: alice
这两条加起来的含义是:alice 在 team-a-dev 内拥有 mirrord-developer 的全部 verb;同时在全集群拥有 serviceaccounts: impersonate 这 1 个 verb。她不会因此获得 team-b-dev 里的 list pods / create jobs 这些能力。
撤销稍微复杂一点,因为 ClusterRoleBinding 是 per-user 而非 per-namespace:
bash rbac/admin/scripts/revoke-namespace-access.sh alice team-a-dev
脚本的逻辑:
- 删
team-a-dev里的RoleBinding/mirrord-developer-alice。 - 数一下
alice在所有 namespace 里剩多少条带mirrord-rbac-demo/user=alice标签的 RoleBinding。 - 只有当结果是 0 时,才删
ClusterRoleBinding/mirrord-impersonator-alice;非 0 就保留——否则就跨 namespace 误伤了(alice 还在team-b-dev用 mirrord 呢)。
撤销后,alice 的证书仍然能认证身份,但 RBAC 检查会重新回到 deny。这个"身份"和"授权"分离的形状,比直接共享 admin kubeconfig 好维护很多。
九、开发者怎么用#
开发者拿到 kubeconfig 后,先设置环境变量:
export KUBECONFIG=rbac/.credentials/alice.kubeconfig
然后可以跑 demo 里的身份和权限检查:
bash rbac/developer/scripts/whoami.sh
预期结果是:
team-a-dev | list pods | yes
team-a-dev | create jobs.batch | yes
team-a-dev | create pods | yes
team-b-dev | list pods | NO
team-b-dev | create jobs.batch | NO
team-b-dev | create pods | NO
真正运行 mirrord 时,demo 用 mirrord.json 固定 target:
{
"target": {
"namespace": "team-a-dev",
"path": "deployment/app"
},
"agent": {
"namespace": "team-a-dev",
"ttl": 30
},
"feature": {
"network": {
"incoming": "mirror"
}
}
}
开发者侧脚本会用这份配置启动:
bash rbac/developer/scripts/run-mirrord.sh
如果要换成本地 Java 进程:
bash rbac/developer/scripts/run-mirrord.sh -- java -jar target/myapp.jar
这里默认用的是 mirror,也就是复制流量到本地进程。要用 steal 前最好先确认环境不是生产 namespace,并且团队能接受目标 pod 的流量被本地进程接走。
在 VS Code 里启动#
团队里大多数人不在命令行跑 mirrord,而是从 VS Code / IntelliJ 直接 launch debug。装 mirrord 的 VS Code 扩展 之后有两个非 RBAC 但很容易卡人的细节。
1. 用 alice 的 KUBECONFIG 启动整个 VS Code 进程,不要写在 launch.json 里。
launch.json 里 env 字段只影响被调试进程(这里是 Java),而 mirrord VS Code 扩展是一个更早启动的独立进程,根本读不到那个 env。结果会是扩展用了你的默认 kubeconfig(admin),跟代码里 alice 完全不是一个身份,agent 跑去错误的 namespace。
正确姿势是用 alice 的 KUBECONFIG 启动整个 VS Code 进程:
KUBECONFIG=$(pwd)/rbac/.credentials/alice.kubeconfig code .
或者把这个 env 写到 shell profile / VS Code 启动包装里——launch.json 那层来不及。
2. 在 .mirrord/*.json 里显式写 agent.namespace。
不写的话,mirrord 会把 agent Job 创建到当前 kubectl context 的 default namespace——demo 里就是 default。alice 在 default 里啥权限都没有,第一个错就是:
Failed to create mirrord-agent: ApiError: jobs.batch is forbidden:
User "alice" cannot create resource "jobs" in API group "batch"
in the namespace "default"
target.namespace 和 agent.namespace 是两件事——前者是「我想接哪个 workload」,后者是「mirrord 把 agent Job 创建到哪里」。RBAC 是按后者鉴权的,在 scoped 环境里必须把后者写明确,否则报错信息会指错地方。上面的 mirrord.json 已经把这两个字段都写满了。
Steal 模式:用 curl 对比验证#
repo 里也准备了一份 steal 配置 .mirrord/steal.json,跟 mirrord.json 的唯一差别是 incoming 从字符串 "mirror" 换成对象,并加上 header filter:
"incoming": {
"mode": "steal",
"http_filter": {
"header_filter": "^x-mirrord-mode: steal$"
}
}
只有带 x-mirrord-mode: steal header 的请求会被本地进程接走,其他流量正常进 cluster pod。这样一次发两条 curl 就能直接看出来是哪一边在处理。
三个终端跑起来:
# 终端 1:port-forward,让本地能 curl 集群 svc(app 是 ClusterIP)
kubectl -n team-a-dev port-forward svc/app 8080:8080
# 终端 2:起带 mirrord 的本地 Spring Boot
# 注意 --server.port=8081,避开 port-forward 占的 8080
KUBECONFIG=rbac/.credentials/alice.kubeconfig \
mirrord exec -f .mirrord/steal.json \
-- java -jar target/mirrord-demo-*.jar --server.port=8081
# 终端 3:对比着发两条 curl
curl -s -H "x-mirrord-mode: steal" http://localhost:8080/api/messages/current # 被截到本地
curl -s http://localhost:8080/api/messages/current # 还在 cluster pod
POST 也是一样的套路,header 决定走向:
curl -s -X POST http://localhost:8080/api/messages/current \
-H "Content-Type: application/json" \
-H "x-mirrord-mode: steal" \
-d '{"message":"hello from local laptop"}'
写 header_filter 时几个容易踩的点:
- 它是正则匹配字面字符串
name: value,不是 HTTP header 语义匹配。配置里写的是全小写x-mirrord-mode,curl 也保持全小写最稳。 - 锚点
^...$要求 value 严格是steal:-H "x-mirrord-mode: steal "(尾空格)不命中。 - 冒号后预期一个空格:
-H "x-mirrord-mode:steal"(无空格)也不命中。
验证哪一边在处理最快的方法是看日志——带 header 的请求会在本地 Spring Boot console 里出现 controller 命中;不带 header 的请求只会出现在 kubectl -n team-a-dev logs deploy/app -f。两边日志各贴一条对比,就能确认 filter 工作正常。
十、用脚本验证 allow 和 deny#
validate-rbac.sh 把整条链路跑一遍——RBAC 配置容易写得看似合理,但漏一个 pods/log、events、namespace 内的 serviceaccounts impersonate,或者那条 cluster-scoped 的 serviceaccounts impersonate,真正跑 mirrord 时才会报 403。
- 管理员 bootstrap kind 集群和 RBAC scaffold。
- 管理员给
alice签发 kubeconfig。 - 验证没有 binding 时,
alice在两个 namespace 都 deny,cluster-scoped impersonate 也 deny。 - 给
alice授权team-a-dev(同时 apply RoleBinding 和 per-user ClusterRoleBinding)。 - 验证
team-a-devallow,team-b-devdeny;且 cluster-scopedimpersonate serviceaccountsallow。 - 如果本机装了 mirrord,再验证
mirrord ls -n team-a-dev可以发现 target,而team-b-dev会失败。 - 撤销
team-a-dev(同时撤掉 RoleBinding 和——由于这是 alice 最后一条 RoleBinding——cluster-scoped 那条),验证重新回到 deny。
cluster-scoped 那条断言对应这一对 helper:
assert_allowed_cluster() {
local kubeconfig="$1" verb="$2"
if ! KUBECONFIG="$kubeconfig" kubectl auth can-i $verb >/dev/null 2>&1; then
die "expected ${USER_NAME} to be allowed to '${verb}' cluster-wide, but was denied"
fi
}
assert_denied_cluster() { ... } # 对称的
没有这条断言,下次有人误删 mirrord-impersonator ClusterRole、或者 grant-namespace-access.sh 退化成只 apply RoleBinding,CI 不会拦住——只能等到「跑 mirrord 才发现」。
运行:
bash rbac/validate-rbac.sh
十一、什么时候考虑 mirrord Teams#
这篇只讨论 OSS + Kubernetes 原生 RBAC 的做法。对小团队,或者“一个服务通常只有一两个人调试”的场景,这套方式已经能解决不少接入治理问题。
但它也有边界:
- 多人同时对同一个 workload 做
steal,仍然需要额外协调。 - 审计主要依赖 Kubernetes audit log,而不是 mirrord 自带的团队审计界面。
- 权限策略、agent 行为限制、并发控制都要自己维护。
- 每个集群的 Pod Security、Admission、证书体系会有差异。
- 这套接入流程一次只处理一个开发者;团队规模化下「几十个人来来去去怎么发证、怎么吊销、怎么按组授权」是一个独立的 K8s 用户管理问题(和 mirrord 关系不大),我会单独整理一篇。
一个具体的触发点:多人想同时按 header steal#
demo 落地后,团队里很快有人来问:能不能几个人同时调同一个服务,每人 steal 自己 header 的请求(X-Dev: alice / X-Dev: bob)?OSS 的答案是 串行可以、并发不行——哪怕每个人用不同的 kubeconfig、不同的 header filter,也不行。
原因在 mirrord-agent 的工作机制:
- agent 进 target pod 用
nsenter改 iptables,把流量 DNAT 到 agent。 feature.network.incoming.http_filter的判断发生在 agent 进程内,前提是流量先被 iptables 劫过来。- 同一个 pod 上跑两个 agent,两份 iptables 规则互相覆盖。
所以 mirrord 干脆做了"dirty iptables"检测,第二个 agent 启动时直接拒:
mirrord preparation failed: ... Close("Detected dirty iptables. Either some
other mirrord agent is running or the previous agent failed to clean up
before exit. ...")
这个错误信息本身就在推 Teams——单 pod 多 steal 不是 OSS 能修的 bug,是架构边界。身份层(kubeconfig / SA / RBAC)也解决不了,因为冲突点在 target pod 的 iptables,跟 K8s API 没关系。
OSS 实际能支持的形态:
| 模式 | OSS | Teams |
|---|---|---|
| 串行 + 不同 header(A 退出 → B 起) | 支持 | 支持 |
| 并发 + 不同 target pod(每人一个 replica) | 勉强可行,不推荐当主路径 | 支持 |
| 并发 + 同 target pod + 不同 header | 不支持 | 支持 |
最后这一行就是 Teams 相对 OSS 的核心增量之一:operator 常驻集群,target pod 的 iptables 只被改一次(指向 operator),operator 做 L7 解包后按 header 分发到各个开发者的 client。OSS 里那个"两份 iptables 规则互相覆盖"的物理冲突,在 operator 模型下根本不会发生。
也就是说,「OSS RBAC 已经够用」这个判断的有效期跟团队的并发 steal 需求强相关。不同人调试不同服务,OSS + 本文的 RBAC 模型就够了;同一个服务多人想同时按 header 切片,这就是 Teams 的决策点。
顺便补一个 OSS 下的护栏:上一个人没干净退出(kill -9 / 网络断 / agent crash)会留下脏 iptables,下一个人就会撞到上面那个错。修复就是把 target pod 重启一次:
kubectl -n team-a-dev rollout restart deploy/app
kubectl -n team-a-dev rollout status deploy/app
可以包成 make mirrord-reset 放进项目,复发时一键修。
如果团队规模更大,或者需要更集中的权限管理、审计和并发控制,再评估 mirrord Teams 会更合适。至少在我这次 demo 里,OSS + CSR + RoleBinding 的组合已经足够支撑”不发 cluster-admin,也能让开发者按 namespace 使用 mirrord”这个判断。
总结#
这次整理后,我对 mirrord OSS 接入治理的理解变成了三句话:
- kubeconfig 只表达身份,不应该顺手带上全集群权限。
ClusterRole定义能力,RoleBinding决定这个能力在哪个 namespace 生效——而对 K8s impersonation 那条无法 namespace 限定的 cluster-scoped 检查,用一个只 1 条规则的小 ClusterRole + per-user ClusterRoleBinding 把例外的爆炸半径压到最小。- mirrord-agent 需要的权限和 Pod Security 例外都不算小,最好用 demo 和脚本反复验证。
如果你想自己跑一遍,可以从这个入口开始:
git clone https://github.com/meirongdev/mirrord-demo.git
cd mirrord-demo
git checkout feat/rbac-mirrord-governance
bash rbac/validate-rbac.sh
跑通之后,把 team-a-dev / team-b-dev 换成你们实际的 dev namespace、alice 换成一两个真实开发者,就能在团队 dev 环境里用起来。真要推到 production namespace 之前,再把权限和 Pod Security 收一遍。
迁移到 k3s、EKS 或 GKE 时,核心模型仍然是同一个:身份单独签发,授权按 namespace 绑定,撤销以 RoleBinding 为单位、只有用户最后一条 RoleBinding 被撤掉时才回收 ClusterRoleBinding。