kubeconfig、context 与 namespace
一位开发者从云控制台下载了新集群配置,把它追加到 KUBECONFIG,随后执行 kubectl get pods。结果没有报错,却看不到刚部署的服务。半小时后才发现:命令使用了第一个文件里的同名 context,namespace 又落回 default。更危险的版本是,两个环境都有同名对象,命令成功地作用到了错误集群。
kubeconfig 的价值是把连接参数组织起来,它的风险也来自同一点。一个文件不只是 API 地址清单,可能同时包含 CA、客户端证书、私钥、Bearer Token、代理地址以及 exec 认证程序。context 再把 cluster、user 和 namespace 绑成一个可切换名称。任何“导入配置”的动作,都可能改变下一条 kubectl 命令连接哪里、以谁的身份连接,以及在本机运行什么程序。
四类对象如何组成一次请求
kubeconfig 的顶层 clusters、users、contexts 都是具名映射,current-context 是默认选择器:
apiVersion: v1
kind: Config
clusters:
- name: dev-cn-your-project
cluster:
server: https://dev.example.com
certificate-authority: ./ca/dev-ca.crt
users:
- name: oidc-developer
user:
exec:
apiVersion: client.authentication.k8s.io/v1
command: your-cloud-login
args: ["kubernetes-token", "--cluster", "dev-cn-your-project"]
interactiveMode: IfAvailable
provideClusterInfo: false
contexts:
- name: dev-cn-your-project-developer
context:
cluster: dev-cn-your-project
user: oidc-developer
namespace: your-project-dev
current-context: dev-cn-your-project-developercluster.server 是 API Server URL;certificate-authority 决定客户端信任哪个 CA。user 保存认证材料或获取材料的方法。context.cluster 与 context.user 引用前两类对象,context.namespace 只是未显式传 -n 时的默认 namespace。namespace 不参与 TLS 或身份认证,也不是权限边界;真正是否允许操作,由 API Server 的 authorization 与 admission 决定。
文件路径有一个容易忽略的规则:kubeconfig 内的相对路径相对于该 kubeconfig 文件所在目录,命令行参数中的相对路径才相对于当前工作目录。移动文件但没有移动 certificate-authority 或 client-key 时,常见失败是 no such file or directory 或 unable to read client-cert。
先查看有效配置,而不是直接打开文件复制内容:
kubectl config current-context
kubectl config get-contexts
kubectl config view --minifycurrent-context 显示默认 context;get-contexts 展开 context、cluster、user 和 namespace;view --minify 只显示当前解析到的组合。默认输出会隐藏证书和 Token 内容。不要在共享终端、工单或 CI 中执行 kubectl config view --raw,因为 --raw 会显示原始敏感数据;--flatten 还可能把外部证书和密钥文件嵌入输出,适合受控迁移,不适合粘贴排障。
多文件不是拼接,而是带优先级的合并
没有 --kubeconfig 且未设置 KUBECONFIG 时,kubectl 读取 $HOME/.kube/config。显式传 --kubeconfig path 时只读取该文件,不参与合并。设置 KUBECONFIG 后,Linux/macOS 用冒号分隔文件,Windows 用分号分隔文件。官方 Organizing Cluster Access Using kubeconfig Files 给出的核心规则是:列表中第一个设置某个值或同名 map key 的文件获胜,后续文件不能补全这个同名对象的其他字段。
下面的实验只解析本地文件,不连接任何集群。它能稳定暴露“后来的配置覆盖前面的配置”这一错误直觉:
mkdir -p /tmp/kubeconfig-merge-demo
cat > /tmp/kubeconfig-merge-demo/first.yaml <<'YAML'
apiVersion: v1
kind: Config
clusters:
- name: shared
cluster:
server: https://first.example.com
users:
- name: developer
user:
token: FIRST_DEMO_TOKEN
contexts:
- name: shared
context:
cluster: shared
user: developer
namespace: first-ns
current-context: shared
YAML
cat > /tmp/kubeconfig-merge-demo/second.yaml <<'YAML'
apiVersion: v1
kind: Config
clusters:
- name: shared
cluster:
server: https://second.example.com
insecure-skip-tls-verify: true
- name: unique-second
cluster:
server: https://unique.example.com
users:
- name: developer
user:
token: SECOND_DEMO_TOKEN
contexts:
- name: shared
context:
cluster: shared
user: developer
namespace: second-ns
- name: unique-second
context:
cluster: unique-second
user: developer
namespace: unique-ns
current-context: unique-second
YAML
KUBECONFIG=/tmp/kubeconfig-merge-demo/first.yaml:/tmp/kubeconfig-merge-demo/second.yaml \
kubectl config view \
-o jsonpath='{.current-context}{"\n"}{.clusters[?(@.name=="shared")].cluster.server}{"\n"}{.contexts[?(@.name=="shared")].context.namespace}{"\n"}'预期依次输出 shared、https://first.example.com 和 first-ns。第二个文件新增的 unique-second 会进入结果,但它的 current-context、同名 shared cluster、同名 user 和同名 context 都不会覆盖第一个文件;insecure-skip-tls-verify 也不会被补进第一个文件的 shared cluster。
反向交换文件顺序,再观察结果:
KUBECONFIG=/tmp/kubeconfig-merge-demo/second.yaml:/tmp/kubeconfig-merge-demo/first.yaml \
kubectl config view \
-o jsonpath='{.current-context}{"\n"}{.clusters[?(@.name=="shared")].cluster.server}{"\n"}{.contexts[?(@.name=="shared")].context.namespace}{"\n"}'此时预期是 unique-second、https://second.example.com 和 second-ns。这证明环境变量中文件顺序本身就是配置。把结果写成永久单文件时,可以在确认不含不必要凭证后执行:
KUBECONFIG=/path/dev.yaml:/path/test.yaml \
kubectl config view --flatten > /secure/path/merged.yaml--flatten 可能把证书和密钥数据内嵌到新文件。输出文件应先放在仅 owner 可读写的目录,确认内容后再原子替换;绝不能直接重定向覆盖当前正在读取的 kubeconfig,否则解析失败或命令中断时可能留下空文件。Windows PowerShell 应使用 $env:KUBECONFIG = 'C:\secure\dev.yaml;C:\secure\test.yaml',并用 $env:KUBECONFIG = $null 恢复默认查找规则。
实验结束后删除只含演示值的目录:
rm -rf /tmp/kubeconfig-merge-demo团队配置应避免同名碰撞,而不是依赖顺序解决碰撞。cluster 名称包含环境、区域和项目,例如 prod-cn-your-project;user 名称体现身份来源与权限,例如 sso-readonly;context 再组合成 prod-cn-your-project-readonly。prod、default、admin 这类短名称在多组织、多区域机器上缺少足够信息。
切换前先把目标变成终端证据
交互式切换可以使用:
kubectl config use-context dev-cn-your-project-developer
kubectl config set-context --current --namespace=your-project-dev
kubectl config current-context
kubectl config view --minify \
-o jsonpath='{.clusters[0].cluster.server}{"\n"}{.contexts[0].context.namespace}{"\n"}{.users[0].name}{"\n"}'use-context 会修改 kubeconfig 的 current-context,set-context --current --namespace 会修改该 context 对象;它们不是只对当前 shell 生效。两个终端共享同一文件时,一个终端切换会影响另一个终端下一条命令。GUI、IDE 插件和后台脚本也可能同时读取或修改同一配置,因此高风险自动化不能依赖 current-context。
脚本应把 context 与 namespace 作为必填参数:
kubectl \
--kubeconfig /secure/path/team-dev.yaml \
--context dev-cn-your-project-developer \
--namespace your-project-dev \
get deployments三层固定各有意义:--kubeconfig 固定输入文件,--context 固定 cluster/user 组合,--namespace 固定 namespaced 目标。对于生产写操作,还可在脚本中维护允许列表,拒绝未知 Server URL 与空 namespace:
TARGET_CONTEXT="${KUBE_CONTEXT:?missing KUBE_CONTEXT}"
TARGET_NAMESPACE="${KUBE_NAMESPACE:?missing KUBE_NAMESPACE}"
case "$TARGET_CONTEXT" in
dev-cn-your-project-developer|test-cn-your-project-developer) ;;
*) echo "refuse context: $TARGET_CONTEXT" >&2; exit 2 ;;
esac
test "$TARGET_NAMESPACE" != "default" || {
echo "refuse namespace default" >&2
exit 2
}
kubectl --context "$TARGET_CONTEXT" -n "$TARGET_NAMESPACE" get deployment允许列表不是安全边界,攻击者仍可修改 kubeconfig 指向恶意 Server;它的作用是阻止操作员把开发脚本误指向生产。TLS CA 校验、RBAC、准入策略和独立网络入口才是下一层控制。不要用 insecure-skip-tls-verify: true 消除证书错误,它会取消 Server 身份校验,让凭证可能发送给中间人。应修复 CA 链、Server Name 或代理证书。
namespace 是组织单元,不是租户保险箱
context 中设置默认 namespace 能减少漏写 -n,但有三类对象不受它限制:Node、Namespace、ClusterRole 等 cluster-scoped 资源;命令显式指定的其他 namespace;拥有跨 namespace 权限的身份。先通过 discovery 确认资源是否 namespaced:
kubectl --context "$TARGET_CONTEXT" api-resources \
--namespaced=false
kubectl --context "$TARGET_CONTEXT" api-resources \
--namespaced=true共享开发集群应按项目或短期环境分 namespace,并给 namespace 配置 owner、生命周期标签、ResourceQuota 和 LimitRange。这样可以限制对象数和资源请求,避免一次错误循环创建大量 Job、Pod 或 PVC,拖高节点与云存储成本。namespace 删除会级联回收大多数 namespaced 对象,但外部 LoadBalancer、卷、云数据库或 operator 管理的资源是否同步释放,要由控制器和供应商语义确认。
把 default 作为长期联调区会混淆同名 Service、Secret 和 ConfigMap,也让成本无法归属。更稳妥的做法是让普通开发身份在 default 没有写权限,并由策略要求资源带 owner、project 和 expires-at 标签。标签本身不会自动回收资源,需要控制器或定时任务依据它执行并留下审计记录。
用授权自检证明“能做什么”
认证成功只说明 API Server 知道你是谁,kubectl auth can-i 才向授权层询问某个动作是否允许。它底层创建 SelfSubjectAccessReview,适用于 RBAC 之外的其他 authorization 模式,官方说明见 Authorization。
新 context 导入后,先做最小权限自检:
CTX=dev-cn-your-project-developer
NS=your-project-dev
kubectl --context "$CTX" -n "$NS" auth can-i get pods
kubectl --context "$CTX" -n "$NS" auth can-i list pods
kubectl --context "$CTX" -n "$NS" auth can-i create deployments.apps
kubectl --context "$CTX" -n "$NS" auth can-i delete deployments.apps
kubectl --context "$CTX" -n "$NS" auth can-i get secrets
kubectl --context "$CTX" auth can-i create clusterroles.rbac.authorization.k8s.io预期结果取决于职责,而不是统一全部为 yes。普通开发者常见基线是能读取 Pod、在开发 namespace 管理 Deployment,但不能读取 Secret 值、不能创建 ClusterRole。auth can-i --list -n "$NS" 可用于盘点,不过输出可能很大;在流水线中更适合逐项检查关键 verb/resource,并把意外的 yes 当成权限漂移。
can-i 返回 yes 仍不保证真实写入成功。authorization 之后还有 admission、配额、对象状态和外部 webhook;一次 create deployment 可能被策略以 403 拒绝,也可能因 ResourceQuota 返回 403。反过来,no 是明确的授权拒绝,不应通过切换 admin context 绕过。应提交包含 context、namespace、verb、resource 与业务理由的最小权限申请。
RBAC 没有显式 deny 规则,绑定会累加。一个身份同时绑定只读 Role 与高权限 ClusterRole 时,高权限不会被只读绑定抵消。尤其要谨慎授予 secrets 读取、pods/exec、pods/attach、工作负载创建、RBAC 绑定和 impersonate:它们可能间接取得 ServiceAccount 身份或敏感数据。自检应覆盖这些升级路径,而不是只检查 get pods。
exec credential 是一段会在本机执行的认证链
云厂商和 OIDC 常用 kubeconfig 的 users[].user.exec 调用外部程序,程序通过 stdout 返回 ExecCredential,其中可能包含短期 Token 或客户端证书。command、args 和 env 决定启动什么;apiVersion 决定输入输出结构;interactiveMode 决定是否需要 stdin;provideClusterInfo 决定是否通过 KUBERNETES_EXEC_INFO 向插件提供 Server 与 CA 等集群信息。字段定义见 kubeconfig v1 API 和 Client Authentication v1。
这意味着不可信 kubeconfig 与不可信 shell 脚本同样危险。只要触发一次需要认证的请求,恶意 exec.command 就可能以当前用户权限读取文件、环境变量和云凭证。导入前至少检查:
kubectl --kubeconfig /path/candidate.yaml config view \
-o jsonpath='{range .users[*]}{.name}{"\t"}{.user.exec.command}{"\t"}{.user.exec.args}{"\n"}{end}'还要直接审查原文件中的 env、相对路径、proxy-url、certificate-authority、client-key 和 extensions。仅用 config view 会对凭证脱敏,也可能解析合并结果,不能代替原文件代码审查。exec 的相对 command 会相对于 kubeconfig 文件所在目录解析;配置文件被移动或旁边的 bin 目录可被替换时,实际程序也会改变。command 应指向受控安装的固定程序,插件二进制要有来源、版本、摘要与升级 owner;不要让 kubeconfig 通过 sh -c、PowerShell 内联脚本或可写目录中的同名程序间接执行。
Kubernetes 1.35 起,kuberc 的 credential plugin policy 进入 beta,可以设置 AllowAll、DenyAll 或 Allowlist。未设置时为兼容旧行为,等同于 AllowAll。先用 kubectl version --client --output=yaml 确认客户端 minor;1.34 及更早版本执行下面的命令会返回 unknown command "kuberc",此时不能误以为策略已经启用。在支持该能力的 kubectl 上,可以先拒绝所有 exec plugin,再逐个允许经过审查的命令:
kubectl kuberc set --section credentialplugin --policy DenyAll
kubectl kuberc set --section credentialplugin \
--policy Allowlist \
--allowlist-entry command=your-cloud-loginKubernetes 1.36 已将 allowlist 条目中的旧字段 name 标记为弃用,使用 command。kuberc 仍是 beta 配置,升级 kubectl 时应复查 API version 与字段;官方当前行为见 Kubectl user preferences。允许列表降低误执行风险,但目前按命令名或路径匹配,不验证二进制摘要;PATH 污染、文件被替换和供应链风险仍需操作系统权限与软件分发控制解决。
凭证轮换必须同时证明新凭证生效、旧凭证失效
长期静态 Token 和客户端私钥一旦复制到聊天、工单或 CI,轮换成本会快速上升。优先使用组织身份系统签发的短期凭证,让 exec plugin 返回 expirationTimestamp;client-go 会缓存凭证到过期、进程退出或 Server 返回 401,再调用插件取证。静态证书仍需监控到期时间。先固定要修改的文件和 context,再在不打印私钥的情况下查看该身份的证书:
KCFG=/secure/path/team-dev.yaml
CTX=dev-cn-your-project-developer
NS=your-project-dev
kubectl --kubeconfig "$KCFG" --context "$CTX" \
config view --raw --minify \
-o jsonpath='{.users[0].user.client-certificate-data}' \
| base64 --decode \
| openssl x509 -noout -subject -issuer -startdate -enddate--raw 在这里会把证书数据送入管道;不要把完整输出写入日志,也不要把路径改成 client-key-data。如果 kubeconfig 使用 client-certificate 文件路径,应直接对证书文件执行 openssl x509 -in ...。Windows 可使用受控的 OpenSSL 环境,或由证书管理工具读取有效期。
轮换过程先准备独立 user 与 context,不覆盖当前可用入口:
kubectl --kubeconfig "$KCFG" config set-credentials oidc-developer-rotated \
--exec-api-version=client.authentication.k8s.io/v1 \
--exec-command=your-cloud-login \
--exec-arg=kubernetes-token \
--exec-interactive-mode=IfAvailable
kubectl --kubeconfig "$KCFG" config set-context dev-cn-your-project-rotated \
--cluster=dev-cn-your-project \
--user=oidc-developer-rotated \
--namespace=your-project-dev
kubectl --kubeconfig "$KCFG" --context dev-cn-your-project-rotated \
-n "$NS" auth can-i get pods
kubectl --kubeconfig "$KCFG" --context dev-cn-your-project-rotated \
-n "$NS" get pods --request-timeout=10s两条都是在线请求:第一条确认授权层对目标 verb/resource 的判断,第二条确认真实工作负载 API 读取成功。仅看到 yes 还不够,因为它不证明目标资源请求一定成功。新入口验证后,在身份提供方撤销旧凭证或旧会话,再用旧 context 做失效验证:
kubectl --kubeconfig "$KCFG" --context "$CTX" -n "$NS" \
get pods --request-timeout=10s预期是 401 Unauthorized、exec plugin 明确报告登录失效,或组织定义的拒绝;如果旧 context 仍成功,可能是客户端缓存尚未过期、撤销只影响刷新 Token、API Server 认证缓存未收敛,或旧 user 实际引用了另一份凭证。记录凭证 TTL,等待其理论过期点后再次验证。不要用命令行 --token 传真实 Token 做测试,命令历史和进程列表可能泄露它。
确认旧身份不再可用后,再移除本地对象:
kubectl --kubeconfig "$KCFG" config delete-context "$CTX"
remaining_contexts="$(kubectl --kubeconfig "$KCFG" config view \
-o jsonpath='{range .contexts[?(@.context.user=="oidc-developer")]}{.name}{"\n"}{end}')"
test -z "$remaining_contexts" || {
printf 'refuse to delete user; still referenced by:\n%s\n' \
"$remaining_contexts" >&2
exit 2
}
kubectl --kubeconfig "$KCFG" config delete-user oidc-developer
kubectl --kubeconfig "$KCFG" config get-contexts
kubectl --kubeconfig "$KCFG" --context dev-cn-your-project-rotated \
config view --minify删除 kubeconfig 条目不会撤销 Server 端证书、Token 或云身份;撤销必须在签发系统完成。反过来,只撤销 Server 端凭证也不会清理本机缓存、备份、CI Secret 和 GUI 导入副本。轮换清单应包含所有持有位置与 owner,并以“旧入口在每个持有位置都失败”作为完成证据。
故障证据要先按连接链分层
kubectl 失败时,错误文本已经提示了链路位置:
The connection to the server ... was refused:Server 地址可解析但端口未监听,或本地代理转发已停止。先看 cluster.server,再检查 VPN、代理与端口。i/o timeout、context deadline exceeded:网络、DNS、代理、API Server 排队或 admission 过慢。用 -v=6 观察请求 URL 与阶段,不要立即增加全局超时。
x509: certificate signed by unknown authority:CA 不匹配、企业 TLS 代理或证书链缺失。核对 certificate-authority 与 Server Name,不能改成跳过 TLS。exec: executable ... not found:认证插件未安装、PATH 不一致或 kubeconfig 的相对命令路径失效。检查 command -v/Get-Command 与受控安装版本。
Unauthorized:没有有效认证材料、凭证过期或插件输出无效。检查 exec 退出码与过期时间,避免打印 Token。Forbidden:认证已经成功,但授权或配额拒绝。错误中的 user、verb、resource 和 namespace 应与 auth can-i 一起分析。“资源不存在但同事能看到”:优先打印 context、Server 与 namespace,再检查 selector;不要先怀疑控制器丢数据。
一条适合工单、又不会泄露凭证的证据命令是:
kubectl config current-context
kubectl config view --minify \
-o jsonpath='server={.clusters[0].cluster.server}{"\n"}user={.users[0].name}{"\n"}namespace={.contexts[0].context.namespace}{"\n"}'
kubectl auth can-i get pods -n your-project-devServer URL 仍可能暴露内网拓扑,发到外部工单前需要脱敏。不要上传 kubeconfig 原文件、--raw 输出、完整 -v=9 日志或云登录缓存。
把 kubeconfig 纳入项目和团队治理
仓库只保存目标名称与权限需求,不保存真实 kubeconfig。项目接入文档可以约定:
kubernetesAccess:
allowedContexts:
- dev-cn-your-project-developer
- test-cn-your-project-developer
namespace: your-project-dev
requiredAccess:
- verb: get
resource: pods
- verb: patch
resource: deployments.apps
forbiddenAccess:
- verb: get
resource: secrets
- verb: create
resource: clusterroles.rbac.authorization.k8s.io这份仓库配置不是 Kubernetes API 对象,而是团队脚本和评审的契约。脚本据此运行 auth can-i,并拒绝未列出的 context。真实 cluster Server、证书、Token 和 exec 环境变量由受控身份平台交付。CI 使用独立、短期、namespace 限定的身份,不复用开发者 kubeconfig;生产只读和生产写入也应是不同身份与审批链。
本机文件权限同样重要。Linux/macOS 上可用 chmod 600 ~/.kube/config,并确保目录只有 owner 可进入;Windows 应检查 ACL,移除不必要的 Users 读取权限。磁盘加密可以降低设备丢失风险,但不能阻止当前用户进程、恶意插件或远程控制软件读取凭证。
配置数量会形成维护成本。几十个长期 context 会增加误选概率,过期 exec 插件会拖慢每次认证,大型内嵌 CA/证书会进入备份与同步系统。季度盘点时应删除离职项目、下线集群和过期身份,验证 Server 已不可达或身份已撤销;同时统计共享 admin 凭证数量、静态凭证年龄、default namespace 写权限和异常的 cluster-wide 权限。
清理演示与退出一个集群
本地合并实验只产生 /tmp/kubeconfig-merge-demo,前文已经给出删除命令。退出真实集群时,不要一上来删除当前 context;先确认存在备用入口,并记录引用关系:
KCFG=/secure/path/team-dev.yaml
kubectl --kubeconfig "$KCFG" config get-contexts
kubectl --kubeconfig "$KCFG" config get-clusters
kubectl --kubeconfig "$KCFG" config get-users
kubectl --kubeconfig "$KCFG" config delete-context dev-cn-your-project-rotated
remaining_cluster_refs="$(kubectl --kubeconfig "$KCFG" config view \
-o jsonpath='{range .contexts[?(@.context.cluster=="dev-cn-your-project")]}{.name}{"\n"}{end}')"
remaining_user_refs="$(kubectl --kubeconfig "$KCFG" config view \
-o jsonpath='{range .contexts[?(@.context.user=="oidc-developer-rotated")]}{.name}{"\n"}{end}')"
test -z "$remaining_cluster_refs" || {
printf 'refuse to delete cluster; still referenced by:\n%s\n' \
"$remaining_cluster_refs" >&2
exit 2
}
test -z "$remaining_user_refs" || {
printf 'refuse to delete user; still referenced by:\n%s\n' \
"$remaining_user_refs" >&2
exit 2
}
kubectl --kubeconfig "$KCFG" config delete-cluster dev-cn-your-project
kubectl --kubeconfig "$KCFG" config delete-user oidc-developer-rotated
kubectl --kubeconfig "$KCFG" config viewdelete-cluster 和 delete-user 只删除本地具名对象,其他 context 若仍引用它们会变成残缺配置。删除前应搜索所有 context 的 cluster/user 引用。完成后还要在身份提供方撤销授权、删除 GUI/IDE 副本、清理 CI Secret 与登录缓存,并验证旧入口返回拒绝。
一个可持续的状态应满足:日常命令能从终端直接看出 context 与 namespace;脚本不读取偶然的 current-context;同名对象不会依赖 KUBECONFIG 顺序竞争;exec plugin 只有受控程序可执行;开发身份的 RBAC 不具备 Secret、exec、RBAC 或集群级升级路径;每次轮换既验证新凭证成功,也验证旧凭证失败;下线集群不会继续留在个人电脑、CI 和可视化工具里。这样,多集群效率才不会以误连和凭证扩散为代价。
