Skip to main content

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 数量——弹性伸缩。