8. 安全
集群安全不是"设密码"那么简单
你搭好了一个 K8S 集群,API Server 跑在 https://api-server:6443。谁都能 kubectl apply 吗?
显然不是。但你很快就会发现,K8S 的安全模型跟你熟悉的数据库或网站完全不一样。没有"管理员账号密码"这个东西。所有的访问控制分三步走:
认证(Authentication)→ 授权(Authorization)→ 准入控制(Admission Control)
你是谁 → 你能做什么 → 你想做的事合不合规矩
认证:你凭什么说自己是你
三种常见方式:
X509 客户端证书。kubeconfig 里那片 base64 证书就是你的身份证明:
# ~/.kube/config 里的一截
users:
- name: jimmy
user:
client-certificate-data: LS0tLS1CRU... # 证书
client-key-data: LS0tLS1CRUdJTi... # 私钥
API Server 启动时指定 --client-ca-file 指向一个 CA 证书——能验证客户端证书的签名。谁能被验证,谁就进去了。kubeadm 默认就是这么配的。
思考题:打开你的
~/.kube/config,里面至少有clusters、users、contexts三个 section。context 到底是干嘛的?为什么需要"把 user 和 cluster 绑定成 context"而不是直接用 user+cluster 组合?如果你管着三个集群,怎么快速切换kubectl指向哪个?
思考题:有没有比
kubeconfig更简单的方式连集群?比如不能装 kubectl 的场景——只用一个curl能操作 K8S 吗?
ServiceAccount。Pod 不能随身带证书。Pod 里 curl https://kubernetes.default.svc 时,用的是 ServiceAccount Token——一个 JWT,存在 /var/run/secrets/kubernetes.io/serviceaccount/token 里。
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: default
每个 namespace 有个 default ServiceAccount,Pod 不指定的话就用这个。这个机制的接口关系很明确:
Pod 需要调用 API Server → ServiceAccount 提供 Token → API Server 验证 Token 里的签名和有效期
思考题:Pod 不是应该跑业务逻辑吗——处理 HTTP 请求、读写数据库——什么场景下 Pod 需要调 API Server?去
kubectl -n kube-system get pods看看那些系统 Pod——它们为什么几乎都在调 API Server?
OIDC。IdP 发 Token,API Server 验证 Token。这是云上常见的接入方式——用你公司的 SSO 登 K8S。
授权:进来之后,什么能做什么不能做
K8S 主要有 RBAC 和 Node Authorizer 两个授权模式。RBAC 是最重要的——它管"人能用哪些 API"。
四个对象:
Role → 定义了权限:"能 get、list Pod"
RoleBinding → 把 Role 绑给用户:"jimmy 能在 production namespace 里 get Pod"
ClusterRole → 同上,但作用于整个集群(所有 namespace)
ClusterRoleBinding → 把 ClusterRole 绑给用户,范围也是全集群
# 定义一个 Role:能读 Pod
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
# 把 jimmy 绑给这个 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: default
name: jimmy-pod-reader
subjects:
- kind: User
name: jimmy
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
搞懂 RBAC 不需要背 CRUD,只要理解它的模型:Who(Subject)绑了 What(Role),对哪些资源可以做哪些操作。
源码里的授权链在 k8s.io/apiserver/pkg/authorization/authorizer/:
// authorizer interface
type Authorizer interface {
Authorize(ctx context.Context, attrs Attributes) (authorized Decision, reason string, err error)
}
// 请求进来时带着 Attributes: user=jimmy, verb=get, resource=pods, namespace=default
// RBAC authorizer 解析 RoleBindings,找到 jimmy 绑的 Role,判断 verbs 里有没有 "get"
不是说"先认证再授权"就完了——API Server 会根据授权结果返回 403,或在 List 时自动过滤掉你没有权限看的对象。这就是你 kubectl get pods 在 kube-system namespace 看不到东西的原因(如果你只有 default namespace 的 Role)。
PodSecurity:Pod 本身能不能跑
就算你有权限创建 Pod,你想用 hostNetwork: true 把 Pod 直接绑到宿主机网络上、想挂 /proc 做特权操作——行不行?
K8S 1.25 后用 Pod Security Admission(PSA)替代了旧的 PSP:
# 给 namespace 打标签,限制这个 namespace 里的 Pod 安全等级
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: baseline # 不准用 hostNetwork
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted # 不合规的只警告不拒绝
三个等级:
| 等级 | 限制 |
|---|---|
| privileged | 什么都不限制 |
| baseline | 禁止 hostNetwork、hostPID、特权容器 |
| restricted | 最严:必须跑非 root、必须 drop 所有 capabilities、禁止 hostPath |
你用 kubectl apply 一个带 hostNetwork: true 的 Pod 在 enforce=baseline 的 namespace 里,会被拒绝。这不是 RBAC 在起作用,而是 Admission Controller——请求已经通过了认证和授权,但准入规则拦下来了。
准入控制:API Server 的最后一关
RBAC 管的是"这个用户能不能调这个 API"。准入控制器管的是"这个 请求体 合不合规"——跟谁发起的无关。
API Server 有两条准入链:
kubectl apply
→ 认证:你是 jimmy ✓
→ 授权:jimmy 能创建 Pod ✓
→ Mutating Admission(变异)→ 修改请求体
例:Istio 给 Pod 注入 sidecar 容器
例:cert-manager 在 Ingress 上改写 TLS 配置
→ Object Schema Validation → 校验字段
→ Validating Admission(验证)→ 拒绝/放行
例:PodSecurity Admission → hostNetwork 不合规 → 拒
例:ResourceQuota → 超出配额 → 拒
→ 写入 etcd
自带准入控制器——编译进 API Server 的,不用额外部署:
| 控制器 | 做什么 |
|---|---|
NamespaceLifecycle | 删除 namespace 时拒绝对其中的资源操作 |
ResourceQuota | 确保 namespace 不超过资源配额 |
LimitRanger | 默认 request/limit 补齐 Pod 没设的值 |
PodSecurity | 上面讲的 PSA——拒绝 hostNetwork 等不安全配置 |
NodeRestriction | 限制 kubelet 只能修改自己的 Node 对象和上面的 Pod |
动态准入控制器——以 Webhook 形式注册,跑在集群外:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: deny-latest-tag
webhooks:
- name: deny-latest-tag.example.com
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
clientConfig:
service:
name: webhook-service
namespace: webhooks
path: "/validate"
failurePolicy: Fail # webhook 挂了→ 拒绝所有请求(strict)
failurePolicy: Fail 是最安全的但也是最大风险的——Webhook 崩了整个集群创建 Pod 全失败。大部分场景用 failurePolicy: Ignore——Webhook 不服务了就跳过检查。
生产上最常见的动态准入——Istio sidecar 注入(Mutating——给 Pod 加 Envoy 容器)、cert-manager(Mutating——在 Ingress 上自动塞 TLS Secret 引用)、镜像签名验证(Validating——拒绝未签名镜像)。
RBAC vs 准入的本质区别:RBAC 说"jimmy 能创建 Pod";准入说"jimmy 创建的这个 Pod 里面有没有安全漏洞"。一个是"谁"能操作,一个是"什么操作"被允许。
ServiceAccount 要小心的事
一个容易被忽视的地方:ServiceAccount 有自己的 Token,Pod 可以拿这个 Token 调 API Server。所以你在 Pod 里 curl https://kubernetes.default.svc/api/v1/namespaces/default/pods 能拿到所有 Pod 列表——如果你的 ServiceAccount 绑了 Pod Lister 的 Role。
Pod 能做的事 = ServiceAccount 绑了什么 Role。你启动了 nginx,但你的 SA 绑了一个能删所有 Deployment 的 Role——恭喜,nginx 容器里跑任意代码的人可以删掉所有 Deployment。
底线:ServiceAccount 只绑最小权限。永远不要给默认 ServiceAccount 绑额外的 Role。
小结
curl https://api-server:6443
│
├─→ 1. 认证:你是哪个用户/SA?(证书、Token、OIDC)
│
├─→ 2. 授权:你能 get/patch/delete 这个资源吗?(RBAC)
│
└─→ 3. 准入:Pod 里能不能有 hostNetwork?(PSA、Webhook)
三层防线,缺了哪一关都不安全。RBAC 管粒度,PSA 管底线,准入 Webhook 管定制规则。
解答
1. ~/.kube/config 里的 context 是什么意思?
kubeconfig 有三个 section,context 是它们的绑定层:
clusters: # 集群列表:去哪儿
- name: prod-cluster
cluster:
server: https://prod-api.example.com:6443
certificate-authority-data: ...
- name: dev-cluster
cluster:
server: https://dev-api.example.com:6443
certificate-authority-data: ...
users: # 身份列表:你是谁
- name: jimmy-admin
user:
client-certificate-data: ... # jimmy 的管理员证书
- name: jimmy-readonly
user:
client-certificate-data: ... # jimmy 的只读证书
contexts: # 绑定:以谁的身份去哪个集群
- name: prod-admin
context:
cluster: prod-cluster
user: jimmy-admin
- name: prod-readonly
context:
cluster: prod-cluster
user: jimmy-readonly
- name: dev-admin
context:
cluster: dev-cluster
user: jimmy-admin
current-context: prod-admin # 当前生效
为什么需要 context? 因为 user 和 cluster 是多对多的——同一个用户(jimmy)可以以不同身份(admin/readonly)访问不同集群(prod/dev)。context 就是把这两者组合起来的"快捷方式"。没有 context 你每次都得手动指定 --cluster + --user。
切换集群:
kubectl config use-context dev-admin # 切到 dev 集群
kubectl config get-contexts # 看所有 context
kubectl config current-context # 看当前是哪个
# 偷懒:用 kubectx(kubectx + kubens 工具)
kubectx prod-admin
kubectx - # 切回上一个
2. 有什么最简单的连接集群的方式?
如果连 kubectl 都不想装,用 curl + ServiceAccount Token:
# 1. 从 Pod 里拿到 API Server 地址和 Token
APISERVER=https://kubernetes.default.svc
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# 2. 直接 curl
curl -s --cacert $CACERT \
-H "Authorization: Bearer $TOKEN" \
$APISERVER/api/v1/namespaces/default/pods
API Server 的接口就是 REST + JSON——任何能发 HTTPS 请求的工具都能操作 K8S。
如果不想每次拼 URL,用 kubectl proxy 做跳板:
kubectl proxy --port=8001 &
# 现在用 curl 不需要证书和 Token 了——proxy 已经替你认证过了
curl http://127.0.0.1:8001/api/v1/namespaces/default/pods
kubectl proxy 在本机开一个 HTTP 代理,把请求转发给 API Server 并自动带上你的认证信息。
kubectl 本身也可以通过 --server 临时指定集群——不写 kubeconfig:
kubectl --server=https://some-cluster:6443 \
--token="eyJhbG..." \
--certificate-authority=/tmp/ca.crt \
get pods
三种方式从复杂到简单:curl 裸调 → kubectl proxy → kubectl 直连。
3. 为什么会有 Pod 内访问 API Server 的情况?
不是所有 Pod 都像 nginx 那样"处理完 HTTP 请求就完事"。只要你的 Pod 是 K8S 生态的一部分——控制器、调度器、网络插件、存储插件——它就必须调 API Server。它需要:
- Watch 资源变化。Controller Manager、Scheduler、kube-proxy——它们不是在"响应 HTTP 请求",它们是在"监听集群状态变化"。
- 读写自己的 CRD。Operator 管理数据库(如 etcd Operator 看 etcd 集群状态)、cert-manager 管理证书(看 Certificate 资源的状态),它们都在持续 Reconcile。
- 更新状态。pod 里的应用想知道自己的 Pod 被分配了哪个 IP、所属的 Deployment 是什么版本——调 API Server 读自己的 Pod/Node/Service 对象。
- Leader Election。几个副本竞争 Lease 资源——就是通过 API Server 做的。
一句话:K8S 的"控制面"就是以 API Server 为中枢的——所有需要对集群状态做出反应的进程,都会在 Pod 里调 API Server。
下一章,看怎么根据负载自动增减 Pod 数量——弹性伸缩。