我们的服务部署在 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-scoped ClusterRoleBinding。这条来自 K8s impersonation 鉴权的硬约束,单独追到的踩坑过程另起一篇 《VS Code 跑 mirrord 撞上 WebSocket 403》。本文直接采用修复后的形态。


一、这次 demo 想解决什么#

我自己在团队里推 mirrord 时,技术上没有大问题;卡住的反而是「给开发者发什么 kubeconfig」。mirrord OSS 不会替我们解决这件事,所以真实团队里通常还会多出几个治理问题:

  1. 开发者不应该拿 cluster-admin kubeconfig。
  2. 新发的 kubeconfig 默认最好没有任何业务 namespace 权限。
  3. 授权应该按 namespace 做,而不是一发就能扫整个集群。
  4. 撤销访问时,最好只删一条授权关系,不重新签发所有凭据。
  5. 管理员需要一套可重复验证的脚本,证明 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-dev
  • team-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.mdadmin-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"]

这里有几个点需要单独说明。

第一,jobspodscreate/delete 是实权。mirrord OSS 会在目标 namespace 里创建 agent 相关资源,所以这个权限不能随便给到生产 namespace。

第二,pods/logeventspods/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 发请求时会同时触发 usersserviceaccounts 两条 impersonation 鉴权,其中 users 那条只能在 cluster 作用域下匹配规则RoleBinding 物理上接不住——所以必须有一条 cluster-scoped 授权。完整原委见 《VS Code 跑 mirrord 撞上 WebSocket 403》 §五–§七。

为什么不直接给 mirrord-developer 加一条 ClusterRoleBinding 就完事:那样最省事,一行解决,但等于把上面那十几条 namespace verb(list pods / create jobs / pods/portforward …)一起提升到 cluster 作用域,开发者立刻能在 kube-systemdefaultteam-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_ADMINSYS_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

脚本做了几件事:

  1. 生成 alice.key
  2. 生成 CN=alice 的 CSR。
  3. 创建 CertificateSigningRequest
  4. 管理员 approve 这个 CSR。
  5. 从 CSR 的 status.certificate 取回签好的客户端证书。
  6. 把 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

这两条加起来的含义是:aliceteam-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

脚本的逻辑:

  1. team-a-dev 里的 RoleBinding/mirrord-developer-alice
  2. 数一下 alice 在所有 namespace 里剩多少条mirrord-rbac-demo/user=alice 标签的 RoleBinding。
  3. 只有当结果是 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.jsonenv 字段只影响被调试进程(这里是 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.namespaceagent.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/logevents、namespace 内的 serviceaccounts impersonate,或者那条 cluster-scoped 的 serviceaccounts impersonate,真正跑 mirrord 时才会报 403。

  1. 管理员 bootstrap kind 集群和 RBAC scaffold。
  2. 管理员给 alice 签发 kubeconfig。
  3. 验证没有 binding 时,alice 在两个 namespace 都 deny,cluster-scoped impersonate 也 deny。
  4. alice 授权 team-a-dev(同时 apply RoleBinding 和 per-user ClusterRoleBinding)。
  5. 验证 team-a-dev allow,team-b-dev deny;且 cluster-scoped impersonate serviceaccounts allow。
  6. 如果本机装了 mirrord,再验证 mirrord ls -n team-a-dev 可以发现 target,而 team-b-dev 会失败。
  7. 撤销 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 接入治理的理解变成了三句话:

  1. kubeconfig 只表达身份,不应该顺手带上全集群权限。
  2. ClusterRole 定义能力,RoleBinding 决定这个能力在哪个 namespace 生效——而对 K8s impersonation 那条无法 namespace 限定的 cluster-scoped 检查,用一个只 1 条规则的小 ClusterRole + per-user ClusterRoleBinding 把例外的爆炸半径压到最小。
  3. 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。