Kubernetes 这个技术栈,我前前后后折腾了五六年,从最开始用 kubeadm 搭测试集群,到后来在生产环境里维护跨可用区的多集群,最有感触的一点是:真正拉开运维和开发差距的,不是背了多少 YAML 参数,而是对配置管理、权限控制、网络安全、自定义扩展这四块核心能力的理解深度。这四块内容既是 K8s 面试中的高频考点,也是生产环境里最容易踩坑的地方。
这篇文章我按自己的知识体系做一个系统整理,不是官方文档的复读,而是把我实际项目中验证过的方案、踩过的坑、以及排查问题的思路都放进来。无论你是准备 K8s 面试的候选人,还是正在维护集群的运维工程师、平台开发,这篇文章都能帮你把这四块内容串起来,形成一套可用的知识框架。
1. 为什么这四个知识点是 K8s 生产化的分水岭
1.1 K8s 真正的门槛不在 API,而在生产化能力
很多人上手 K8s 的第一步是搭集群、跑 Nginx、部署一个无状态应用,这些事用 minikube 或者 kind 半小时就能完成。但一旦进入真实业务场景,事情就完全变样了:应用的配置怎么管理,不会因为镜像重新构建就丢失?不同团队、不同角色的人访问集群,怎么保证只给该给的权限?集群内部的服务间通信如何做到最小信任?内置的 Workload 满足不了需求时,怎么扩展平台能力?
你没看错,这四个问题对应的正是配置管理、权限控制、网络安全和自定义扩展。一个集群能不能从“能跑”变成“能安全生产”,看的不是 Pod 数量,而是这四块底座扎不扎实。
1.2 四块能力在生产环境里的真实价值
配置管理解决的是“应用和行为分离”的问题。没有 ConfigMap 和 Secret 之前,配置只能写死在镜像里,改一个数据库地址就得重新构建镜像,这在发布流程里完全是灾难。有了配置管理,一套镜像可以同时跑在开发、测试、生产多个环境,这是 CI/CD 能顺畅运转的前提。
权限控制解决的是“谁能对集群做什么”的问题。我见过不少测试环境集群,所有人都是 cluster-admin,当时觉得方便,等业务越来越重要时才发现完全失控,连谁删了哪个 namespace 都查不出来。RBAC 不是用来限制效率的,而是用来划定责任边界的。
网络安全解决的是“东西向流量是否可信”的问题。传统网络安全的重点是边界防护,但在容器环境里,Pod IP 漂移频繁,服务数量可能成百上千,只靠防火墙根本防不住内部横向扩散。NetworkPolicy 这类机制才是容器网络安全的基石。
自定义扩展解决的是“平台能力和业务需求之间的缺口”问题。当你在 K8s 上跑了数据库、消息队列、大数据作业,你会发现内置的 Deployment、StatefulSet 远远不够表达业务意图。CRD 加 Operator 的模式,本质上就是把运维经验固化成代码,这是 K8s 生态最强大的生命力所在。
这四个方向,正好构成了一条从“能用”到“好用”的进阶路径,所以我强烈建议学习 K8s 时不要只盯着 Deployment,而是把这四条主线一起吃透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 配置管理实战:ConfigMap 与 Secret 用对才算入门
2.1 ConfigMap:把应用配置从镜像中解放出来
ConfigMap 这个概念很好理解,它就是用来保存非敏感配置数据的 API 对象,比如配置文件内容、环境变量、命令行参数。
我第一次用 ConfigMap 时,最直接的感觉就是“终于不用每次改配置都重新打包镜像了”。它的使用方式主要有三种:以环境变量的方式注入 Pod;以 Volume 的方式挂载成文件;作为命令行参数传给容器启动命令。
用环境变量很简单,下面这段是最常见的写法:
yaml复制apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DB_HOST: "mysql.prod.svc.cluster.local"
LOG_LEVEL: "info"
然后在 Deployment 里这样引用:
yaml复制envFrom:
- configMapRef:
name: app-config
这种方式适合配置项数量少、结构简单的场景。但如果应用本身就是通过读配置文件来启动的,我更推荐 Volume 挂载的方式:
yaml复制volumeMounts:
- name: config
mountPath: /etc/app
volumes:
- name: config
configMap:
name: app-config
把整个配置目录挂进去之后,应用直接读 /etc/app/application.yml 就行,代码完全不用关心配置从哪里来。
这里有一个关键细节:ConfigMap 的本质是键值对存储,Volume 挂载时,data 里的每个 key 都会变成一个文件,value 就是文件内容。如果你想挂载某个子路径,可以用 items 字段指定。比如只挂载 application.yml 这一个 key:
yaml复制volumes:
- name: config
configMap:
name: app-config
items:
- key: application.yml
path: app.yml
2.2 Secret:敏感信息不能只靠 Base64 心里踏实
Secret 和 ConfigMap 的使用方式几乎一样,唯一的本质区别是它专门用于保存敏感数据,比如数据库密码、API Token、TLS 证书。Secret 的 value 默认要求 Base64 编码,但这不代表它是加密的,Base64 只是编码格式,任何人都能解码。我之前见过有同事以为 Secret 加密了,直接把这个 YAML 提交到 Git 仓库,结果数据库账号密码全部泄露,这个教训很值得警惕。
Secret 的常见类型有三种:Opaque 是最通用的,用来存任意键值对;kubernetes.io/tls 用来存 TLS 证书和私钥;kubernetes.io/dockerconfigjson 用来存仓库拉取凭证。
创建方式也有讲究,直接手写 Base64 容易出错,更推荐用命令生成:
bash复制kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password='P@ssw0rd!'
或者加密整个文件:
bash复制kubectl create secret generic tls-secret \
--from-file=tls.crt=server.crt \
--from-file=tls.key=server.key
在 Pod 里引用 Secret 时,我习惯同时设置 optional: false,这样如果 Secret 不存在,Pod 会启动失败,而不是带病运行。要知道,配置缺失导致的故障,往往比配置错误更难排查,因为应用本身启动成功了,但功能不正常。
关于 Secret 的进一步加固,生产环境务必开启 etcd 加密,也就是在 kube-apiserver 的启动参数里配置 --encryption-provider-config,否则 Secret 落到 etcd 里仍然是明文,这属于安全基操,别等到审计时才发现。
2.3 配置热更新、subPath 与不可变配置的取舍
配置管理的进阶问题是“改完 ConfigMap 后,Pod 什么时候生效”。这里有个经典结论:环境变量方式注入的配置,修改 ConfigMap 后不会自动生效,必须重启 Pod 或者滚动更新;Volume 方式挂载的配置,默认会自动更新,Kubelet 会定期同步 ConfigMap 到 Volume 中,应用需要监听文件变化来自动 reload。
所以生产环境里的做法一般是:对支持热加载的应用,用 Volume 挂载配置;对不支持的,用环境变量注入,配置变更就触发展滚动更新。注意不要把 ConfigMap 的所有配置都塞进环境变量里,多了之后根本没法管理。
关于 subPath,这个坑我一定要单独拿出来说。如果用 volumeMounts 的 subPath 挂载了 ConfigMap 的某一个 key,那么这个挂载方式不会自动更新,因为 Kubelet 没法通过 subPath 去追踪整个 ConfigMap 的变化。
注意:不想每次配置变更都重启 Pod,就别在 subPath 场景下指望热更新。我在生产环境里踩过这个坑,改了 ConfigMap 后等了半小时配置都没变,查了半天才发现是因为 subPath。
还有一个容易被忽视的点是 immutable 字段。对于完全固定不变的配置,建议设置为不可变,比如 immutable: true。这样 kube-apiserver 会拒绝对该 ConfigMap 的更新请求,从源头上避免了误操作导致整个集群配置漂移的风险,同时还能提升性能,因为 Kubelet 不需要频繁监听这些不可能变化的资源。
配置管理这块我建议掌握到这个程度:能分清 ConfigMap 和 Secret 的适用场景,能说明白热更新的边界,知道 immutable 和 etcd 加密的存在。面试和实战都够用了。
3. 权限控制:RBAC 模型与最小权限落地
3.1 RBAC 的四个核心对象,一次彻底理清
K8s 的权限控制默认采用 RBAC(基于角色的访问控制)模型,核心就四个对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding。很多人搞不清它们的区别,我说一个最简单的记忆方法:Role 和 ClusterRole 是“权限模板”,Binding 相当于“模板和人之间的绑定关系”。
- Role:作用于某个 namespace 内的权限集合。
- ClusterRole:作用于整个集群的权限集合,包括所有 namespace 和集群级资源。
- RoleBinding:把 Role 绑定到某个 namespace 内的用户、ServiceAccount 上。
- ClusterRoleBinding:把 ClusterRole 绑定到全局用户或 ServiceAccount 上。
实际使用中有一种很常见但容易混淆的场景:用 ClusterRole 定义好一组权限,然后在某个 namespace 里通过 RoleBinding 引用这个 ClusterRole。这种情况下,这个 ClusterRole 虽然定义在集群级,但通过 RoleBinding 绑定后,它只对指定 namespace 生效,不会越权到其他 namespace。这就是官方的“聚合授权”设计,灵活且安全。
举个最简单例子,给一个跨 namespace 都只能读 Pod 的角色:
yaml复制apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
注意 apiGroups 这个字段,很多人在这里犯迷糊。核心组资源(Pod、Service、ConfigMap 等)的 apiGroup 是空字符串 "",而 Deployment、StatefulSet 这类属于 apps 组,NetworkPolicy 属于 networking.k8s.io 组。写规则时漏掉 apiGroups 或者写错,权限就不生效,排查时会很迷惑。
3.2 ServiceAccount:Pod 的身份与权限绑定
RBAC 中“人”这个概念在集群内很少直接使用,更多是通过 ServiceAccount 来代表 Pod 的身份。每个 namespace 创建时会自动生成一个名为 default 的 ServiceAccount,如果你的 Pod 没有显式指定 ServiceAccount,它就会以 default 的身份运行。
default ServiceAccount 的权限通常非常小,这是好事,但问题来了:很多应用需要通过 K8s API 获取自己的 Pod 信息、监听事件、操作 CRD,这些需求 default 根本满足不了。正确做法是创建专用 ServiceAccount,然后绑定最小权限。
比如我要给一个日志采集 Agent 创建只读权限:
yaml复制apiVersion: v1
kind: ServiceAccount
metadata:
name: log-agent
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: log-agent-reader
subjects:
- kind: ServiceAccount
name: log-agent
namespace: logging
roleRef:
kind: ClusterRole
name: pod-reader
apiGroup: rbac.authorization.k8s.io
然后在 Deployment 的 Pod Template 里指定:
yaml复制spec:
serviceAccountName: log-agent
这个流程在面试里经常被问到,核心考点有两个:一是 ServiceAccount 是 Pod 的身份凭证,二是 RBAC 绑定决定了这个身份能做什么。
这里还要记住一个容易忽略的细节:Pod 内通过 K8s API 访问时,默认会读取 /var/run/secrets/kubernetes.io/serviceaccount/token 文件来获取令牌,这也是 ServiceAccount Admission Controller 自动注入的。如果你用 Kubernetes 的官方客户端库,一般不需要手动处理这个文件,但如果你自己写 HTTP 请求调用 K8s API,就需要关心这个 token 和 CA 证书。
3.3 最小权限实战模板与权限浪费的排查
权限控制的核心原则是最小权限,但在真实集群里,我看到最多的两类现象:一类是图省事,直接把 namespace admin 的权限给所有开发;另一类是权限越给越多,离职之后 token 也不回收。
我这里提供一个比较实用的“namespace 内只读 + 只能操作 Deployment/Service 等应用资源”的角色示例:
yaml复制apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-developer
namespace: dev
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
运维人员不要盲目给开发同事 secret 的读写权限,如果业务上需要读取线上密钥,也应该考虑通过 Vault 等外部密钥管理系统,而不是直接把集群内 Secret 的权限放给所有人。因为 Secret 的 get 权限意味着可以看到该 namespace 下所有 Secret 的明文内容,这在安全审计中是高风险项。
排查权限问题有一个非常实用的命令:
bash复制kubectl auth can-i list pods --as=system:serviceaccount:dev:log-agent -n dev
这个命令能直接告诉你某个身份是否具备某个操作权限,省得反复读 YAML 判断绑定关系。另外,如果集群开启了审计日志(API Server 的 audit log),查谁干了什么基本一目了然,这也是建议生产集群必须开审计的原因。
4. 网络安全:给集群加一道默认拒绝的多层防线
4.1 NetworkPolicy:微隔离才是容器网络安全的灵魂
K8s 网络有一个默认模型:所有 Pod 可以互相通信,所有 Namespace 之间也可以互相通信。这个“默认全通”的逻辑在初期很省事,但安全上完全是灾难,一旦有一个 Pod 被攻破,攻击者可以横向渗透到整个集群。
NetworkPolicy 就是用来收紧这个模型的。它通过 label selector 选择一组 Pod,然后定义允许哪些来源(Ingress)和目标(Egress)访问它们。K8s 网络策略的核心思想是白名单,没有显式放行的流量,默认就是拒绝,前提是你定义了策略,并且你的 CNI 支持 NetworkPolicy(比如 Calico、Cilium、Antrea)。
下面是一个拦截所有入站流量、只允许来自相同 namespace 且带 app=web 标签的 Pod 访问的示例:
yaml复制apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-only-web-to-api
namespace: prod
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
创建这个策略之后,prod 这个命名空间里 app=api 的 Pod,只接受来自同命名空间内 app=web 的 Pod 的请求,其他所有来源一律拒绝。这种“微隔离”能力在传统网络里很难实现,但在 K8s 里面就是一句 YAML。
注意 NetworkPolicy 匹配是“同一规则内是 AND 关系,规则之间是 OR 关系”。上面 yaml 只有一个 ingress 规则,如果你写多个 ingress 数组项,只要匹配任意一个就会放行,不要搞混。
4.2 Ingress 与 TLS:入口流量安全收口
集群内部的安全做完了,外部流量的入口同样不能忽略。Ingress 资源本质上是一组路由规则,真正的流量转发是 Ingress Controller 完成的,比如 Nginx Ingress Controller、Traefik 等。
Ingress 配置 TLS 时,需要创建 kubernetes.io/tls 类型的 Secret:
yaml复制apiVersion: v1
kind: Secret
metadata:
name: example-tls
type: kubernetes.io/tls
data:
tls.crt: <base64 证书内容>
tls.key: <base64 私钥内容>
然后在 Ingress 里引用:
yaml复制apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example
spec:
tls:
- hosts:
- api.example.com
secretName: example-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 8080
在实际生产环境里,我还建议在 Ingress Controller 层面对外隐藏后端服务的真实信息,使用注解限制请求体大小、开启 Gzip 压缩、设置合理的超时时间等。这些虽然是 Nginx Ingress Controller 的注解,但非常常用,可以单独细挖。
4.3 Pod 安全上下文:容器内的最后一道防线
网络层防住了,容器内部还要防一层。Pod 安全上下文(securityContext)控制的是容器内部进程的用户、文件系统权限、内核能力等属性。最小化容器权限是每个生产集群都应该做的事情。
下面这个配置是我给大多数业务容器推荐的基础模板:
yaml复制securityContext:
runAsNonRoot: true
runAsUser: 10001
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
解释一下这几个字段的含义:runAsNonRoot: true 禁止容器以 root 身份运行,配合 runAsUser: 10001 指定一个非 root 用户 ID;readOnlyRootFilesystem: true 让根文件系统只读,如果应用需要写临时文件,可以把 tmpfs 挂载到 /tmp 目录;drop: ALL 丢弃所有 Linux Capabilities,这会让容器失去大部分内核特权;seccompProfile: RuntimeDefault 使用运行时的默认 seccomp 配置限制系统调用。
在注解层面还有一个机制叫 Pod Security Admission(PSA),它替代了早期的 PodSecurityPolicy,允许你给 namespace 设置安全级别:privileged、baseline、restricted。restricted 级别正好强制前面说到的那些字段,建议生产命名空间至少设成 baseline,高安全要求的设成 restricted。
我之前接手过一个集群,排查一个莫名其妙“Permission denied”的问题,最后发现就是 readOnlyRootFilesystem 导致了应用写文件失败。这类问题不算罕见,排查时先看容器日志和事件,大概率能定位到是安全上下文配置与业务冲突,而不是代码问题。
4.4 供应链与运行时的安全补充
集群网络和容器权限之外,镜像本身的安全性也很重要。现在比较成熟的方案包括:
- 镜像仓库上面接入镜像扫描,比如 Trivy、Clair,定期扫描 CVE;
- 基础镜像尽量使用 distroless 或者最小化镜像,减少容器内可被利用的组件数量;
- 镜像标签不要使用 latest,必须使用不可变 tag 或 digest;
- 有条件的团队可以在 CI 里加镜像签名校验,部署时只允许运行已签名的镜像。
这层安全不是 K8s 本身的机制,但它直接决定你部署到 K8s 里的“零件”是否可靠。就好比一栋大楼的防火墙做得再好,如果门禁卡被复制了,照样不安全。所以我把这部分也归到网络安全的内功里一起掌握。
5. 自定义扩展:从 CRD 到 Operator 的进阶玩法
5.1 什么时候该用 CRD,什么时候该停下
K8s 内置的 Deployment、Service、ConfigMap 能覆盖大部分无状态应用,但遇到“有状态中间件”“自定义调度需求”“某个非常垂直的业务抽象”时,内置资源就很难表达了。CRD(CustomResourceDefinition)就是让你把自定义资源注册进 K8s API 的一种方式,定义好 CRD 之后,你可以像操作 Deployment 一样去操作自己定义的对象。
但我要强调一点:不要为了用 CRD 而用 CRD。如果只是需要保存一份配置,用 ConfigMap 就够了;如果业务逻辑不复杂,用 Helm 打包模板可能比写 Operator 更合适。CRD 加 Operator 是重量级方案,它的价值在于把“人工运维流程”代码化、自动化,适合反复执行且很容易出错的场景,比如数据库备份恢复、集群扩缩容、应用发布策略控制等。
一个典型的 CRD 使用场景:把“一个 Redis 实例”抽象成 Kind = Redis,CRD 定义 Redis 有哪些字段(副本数、版本、持久化策略、备份策略等),Operator 监听 Redis 对象的变化,去自动创建 StatefulSet、Service、PVC 等底层资源。业务侧只需要提交一个简单的 Redis YAML,剩下的交给 Operator。
5.2 从一个最小 CRD 开始
先看一个最简单的 CRD 定义,定义一个叫 CronTab 的自定义资源:
yaml复制apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: crontabs.example.com
spec:
group: example.com
names:
kind: CronTab
plural: crontabs
singular: crontab
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
创建完 CRD 之后,你就可以直接提交一个 CronTab 对象:
yaml复制apiVersion: example.com/v1
kind: CronTab
metadata:
name: my-cron
spec:
cronSpec: "*/5 * * * *"
image: cron-image:v1.0
replicas: 1
执行 kubectl get crontabs 能看到它。但请注意,CRD 本身只是“数据模型”,它不会做任何事。真正让 CronTab “跑起来”的,是一个控制器或 Operator。
这里顺带说一个我常被问到的点:CRD 定义文档中 schema 部分很重要,它是 K8s 对自定义资源做结构校验的依据。如果不写 schema,任何字段都可以填,等于放弃了 API 的自我描述能力。生产级 CRD 建议打开 preserveUnknownFields: false,并配合 subresources 定义 status 字段,方便控制器汇报资源状态。
5.3 Operator 模式的核心逻辑:控制循环
Operator 是“CRD + 控制器”的合体,控制器不停地执行一个控制循环:观察期望状态、对比当前状态、采取行动向期望状态收敛。
我用一个生活化的类比解释:一个恒温器就是最简单的 Operator。你设定的温度是期望状态,室温是当前状态,温度传感器不断测量偏差,空调根据偏差决定加热还是制冷。Operator 干的就是这件事,只不过目标从“温度”换成了“应用实例数量、集群节点状态、数据库备份时间”。
在 K8s 里实现控制器,最常见的是用 Kubebuilder 或者 Operator SDK,底层依赖 controller-runtime 库。核心代码长这样(示例为伪代码):
go复制func (r *CronTabReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var cronTab examplev1.CronTab
if err := r.Get(ctx, req.NamespacedName, &cronTab); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 创建或者更新底层的 Deployment 对象
desiredDeploy := buildDeployment(cronTab)
if err := r.Create(ctx, desiredDeploy); err != nil && !errors.IsAlreadyExists(err) {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
控制循环有几个容易踩的坑:
- 控制器的
Reconcile函数必须保证“幂等”,重复执行和首次执行的结果一致,否则会反复触发资源变更。 - 当资源被删除时,通常需要处理 finalizer,否则控制器创建的底层资源可能会成为孤儿。
- Reconcile 失败时返回
ctrl.Result{RequeueAfter: 5 * time.Second},避免频繁重试打爆 API Server,这个重试间隔在真实环境中非常关键。
Operator 写起来不难,难的是把业务运维经验拆成状态机,想清楚每一步的期望状态、中间状态、失败状态。这个能力比写代码本身更值钱。
5.4 Admission Webhook:在请求落库前把它拦下来
自定义扩展的另一个重要手段是 Admission Webhook,分为 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook。前者可以在资源创建或更新时修改请求内容,比如自动给 Pod 注入 Sidecar、自动补全默认字段;后者可以对请求做校验,不满足条件的直接拒绝,比如强制资源必须带某个 Label。
典型应用场景是 Istio 注入 Sidecar,它本质上靠的就是一个 MutatingAdmissionWebhook,在 Pod 创建前把 Envoy Sidecar 容器、初始化容器注入到 Pod 的 spec 里。
如果你需要写自己的 Webhook,要注意几个关键点:
- Webhook 必须配置 TLS 证书,K8s 会通过 HTTPS 调用你的服务。
- 配置
failurePolicy为Fail或Ignore时要想清楚:Webhook 故障时是“拒绝所有请求”还是“放行所有请求”,这直接影响集群可用性,核心业务建议显式选择。 - 当你有多个 Webhook 时,执行顺序没有严格保证,建议不要在多个 Mutating Webhook 之间做过强的依赖假设,否则容易出现注入顺序引起的诡异问题。
涉及 Webhook 的事故都比较严重,我之前遇到过因为某个 ValidatingWebhook 代码 bug,导致整个 namespace 内所有 Deployment 都创建失败,最后只能紧急删除 webhook 配置恢复集群。这类组件在测试环境必须充分验证,灰度节奏要拉长。
6. 常见问题与排查技巧实录
配置管理、权限控制、网络安全、自定义扩展,这四个方向问题排查的思路完全不样,我按实战中遇到的频率整理成了速查表,供大家直接参考。
6.1 配置类问题
| 现象 | 大概率原因 | 处理方法 |
|---|---|---|
| 环境变量配置改了不生效 | env 方式注入不支持热更新 | 滚动更新 Pod 或触发新的 ReplicaSet |
| Volume 挂载配置更新了,文件还是旧的 | 应用没有监听文件变化,或者使用了 subPath | 确认挂载方式和应用 reload 机制 |
| ConfigMap 引用的键不存在 | 读不到配置导致容器启动失败 | 检查 Pod 事件,确认 ConfigMap 名称与键一致 |
| Secret 内容出现乱码 | Base64 编解码错误 | 用 kubectl get secret name -o jsonpath='{.data.password}' | base64 -d 验证 |
6.2 权限类问题
| 现象 | 大概率原因 | 处理方法 |
|---|---|---|
| 调用 K8s API 报 403 Forbidden | RoleBinding 或 ClusterRoleBinding 缺失 | 用 kubectl auth can-i --list 查看当前身份权限 |
| Pod 内访问 API 有时成功有时失败 | ServiceAccount 没固定,或 binding 选择的 namespace 不对 | 显式指定 serviceAccountName,并检查 binding 的 subjects 中 namespace |
| 删除资源时被 finalizer 卡住 | 自定义控制器没处理 finalizer | 检查相关 CRD/Operator 日志,手动确认是否需要移除 finalizer |
6.3 网络安全类问题
| 现象 | 大概率原因 | 处理方法 |
|---|---|---|
| 应用访问同 namespace 的 Pod 正常,跨 namespace 突然不通 | NetworkPolicy 默认拒绝跨 namespace | 增加允许来源的 namespaceSelector 或 ipBlock 规则 |
| 配置了 NetworkPolicy 后 DNS 解析失败 | Egress 没有放行 kube-dns | 添加 Egress 规则放行 TCP/UDP 53 到 kube-system |
| 容器以 root 启动被安全策略拒绝 | Pod Security Admission 级别过高 | 调整命名空间级别或修改 securityContext 满足策略 |
| 服务间访问有随机超时 | 可能是 seccomp 或 capabilites 限制导致系统调用失败 | 查看容器日志和事件,结合 dmesg 定位系统调用 |
6.4 CRD 与 Operator 调试经验
调试 Operator 给的建议比较直接:多利用 K8s 事件,所有 Reconcile 错误都可以通过 kubectl events 查看;把 controller 的日志级别调到 debug,观察它做了什么决策。如果是自定义控制器不触发 Reconcile,先确认你是否给 CRD 打开了 status.subresources 并且控制器正确设置了 OwnerReference;如果 Reconcile 报了 “the server could not find the requested resource”,大概率是 CRD 的 group/version 和客户端里的不一致。
另外,生产环境升级 CRD 时,一定要留意版本转换兼容性,K8s 支持的 conversion 配置非常有限,如果 CRD 的 schema 有破坏性变更,可能导致已有自定义资源无法读取,这在生产集群上属于严重事故,升级前建议先备份 CRD 对象。
写在最后
配置管理、权限控制、网络安全、自定义扩展这四块内容,我建议每个人都动手做一遍,而不是只看文档。亲手创建一个 ConfigMap 并踩一次热更新的坑,亲手配一个 RBAC 角色并被 403 教育一次,亲手写一个 NetworkPolicy 把服务隔离开再观察流量变化,亲手定义一个 CRD 并写一个最简单的控制器,这些东西一旦上手,比背任何知识点都记得牢。
我个人在实际项目里的习惯是:每接触一个 K8s 新特性,先在 kind 集群里验证一遍,再投入生产。K8s 本身是个非常庞大的系统,但只要把这几条主线捋清楚,遇到问题就不会慌,因为你已经知道该从哪个方向去定位了。
