Kubernetes现在成了很多团队跑应用的首选,但越用越发现,安全这东西不能光靠运气。很多人一开始觉得“集群跑起来就完事了”,结果某天发现某个服务被挖矿程序占满了CPU,或者某个命名空间里莫名其妙多了一个容器,才后悔当初没做加固。其实Kubernetes的安全加固并没有想象中那么玄乎,只要把几个关键点盯住,就能挡住绝大多数常见的攻击。这里咱们就聊聊,从集群本身的组件,再到你天天打交道的应用容器,到底该怎么配置才是安全的。
一、为什么Kubernetes安全这么重要
先打个比方。你把Kubernetes集群想象成一套精装修的房子,API Server就是那扇大门,etcd是你家的保险柜,而每个容器就像是一个租客。如果你只是随手把门带上,保险柜也不上锁,租客之间也不装隔板,那万一有人混进来,整个家就被翻了个底朝天。Kubernetes默认的一些配置,其实就是“随手带门”的状态——它能让你尽快跑起来,但安全防护远远不够。
很多攻击者其实并不高明,他们只是到处扫描那些没设密码的API Server,或者滥用那些权限过大的Service Account。一旦得手,轻则看到你的业务数据,重则直接控制整个集群,甚至通过容器逃逸拿到宿主机的权限。所以,别觉得安全加固是“大公司才需要的事”,你哪怕只是跑一个个人项目,也该把最基础的门锁装上。
二、集群组件层面的安全加固
这一层主要负责把“房子”本身的钢筋水泥弄结实。Kubernetes的组件不少,但真正需要你花心思的其实就那几个:API Server、etcd、kubelet,还有它们之间的网络通信。
2.1 先把API Server和etcd这个组合锁死
API Server是集群的入口,所有操作都要经过它。如果你的集群暴露在公网,又没有做任何访问限制,那基本等于把家门钥匙挂在门口。首先,一定要确保API Server开启了TLS,并且只接受经过认证的请求。一般情况下,云服务商托管集群会自动做好这件事,但如果是自己搭的集群,就得检查一下。
etcd是集群所有数据的存储地,里面的Secret直接保存着你的密码、密钥等敏感信息。生产环境里,etcd一定要加密,而且最好和API Server放在不同的网络区域。你可以用下面的命令检查一下etcd的存储里,Secret是不是被加密过的:
# 先找到etcd的pod,然后进入pod执行etcdctl
# 注意:不同集群etcd的证书路径可能不一样,这里以常见的kubeadm为例
kubectl exec -n kube-system etcd-master -- sh -c \
'etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/my-secret'
如果返回的内容直接是明文,那就说明你的Secret没有被加密存储,需要赶紧开启KMS加密插件或者静态加密。另外,etcd的访问权限要严格限制,只有API Server的证书才允许连接它,千万别把etcd端口暴露到外网。
2.2 给kubelet也上把锁
很多人的注意力都在API Server上,常常忽略了kubelet。其实每个节点上的kubelet也是一个入口,它默认监听10250和10255端口。如果你没有配置认证,任何人都可以调用kubelet的接口,在节点上执行命令。生产环境必须保证:
- kubelet只监听内网地址。
- 开启kubelet的HTTPS认证,并关闭匿名访问。
- 对于不用的端口,直接关掉。
可以用下面的命令快速检查集群里kubelet的匿名访问是否被禁用:
# 查看kubelet的配置选项
# 在某个节点上执行(或者用kubectl查看kubelet的configmap)
kubectl -n kube-system get cm kubelet-config -o yaml | grep anonymous
如果看到anonymous-auth: false,说明没问题;如果是true,那就要赶紧改配置了。另外,最好把kubelet的证书轮换功能打开,这样就不用担心证书过期了。
2.3 网络策略不是可选的
默认情况下,Kubernetes的网络是“全通”的——也就是一个Pod可以随意访问另一个Pod。这就像楼下大门没锁,每个房间之间也不锁门。攻击者只要打穿一个Pod,就能在内网里横向乱窜。要解决这个问题,就得借助NetworkPolicy,它就像给每个Pod之间装上了带锁的门。
下面是一个完整的示例,只允许前端Pod访问后端Pod的8080端口:
# 使用kubectl apply -f - 一次性创建一个NetworkPolicy
# 这里用bash的here-doc往标准输入里写内容,方便演示
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: myapp
spec:
# 这个策略作用在带有app=backend标签的Pod上
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress # 只控制入站流量
ingress:
- from:
# 允许来自带有app=frontend标签的Pod
- podSelector:
matchLabels:
app: frontend
ports:
# 只允许访问8080端口
- protocol: TCP
port: 8080
EOF
创建之后,没有匹配规则的Pod就连不上backend了。需要注意,NetworkPolicy需要网络插件支持,比如Calico、Cilium、WeaveNet等。如果你用的网络插件不支持,那就得换一个支持的了。
2.4 RBAC:别把管理员权限到处送
Kubernetes的权限控制主要靠RBAC。很多新手图省事,直接把一个cluster-admin的Service Account挂到应用上,这就像把整个房子的钥匙交给了每个租客。正确的做法是尽量用普通用户,并且只给最小权限。
举个例子,假设你有一个应用,它只需要读取某个命名空间里的ConfigMap,那你就应该创建一个Role和对应的Service Account,而不是直接给它绑定到cluster-admin。下面是一个完整的RBAC配置:
# 创建命名空间、ServiceAccount、Role和RoleBinding
# 全部放在一个YAML里,用here-doc一次性应用
kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: myapp
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: myapp
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: configmap-reader
namespace: myapp
rules:
# 只允许读取和列出ConfigMap
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: myapp-sa-configmap-reader
namespace: myapp
subjects:
- kind: ServiceAccount
name: myapp-sa
namespace: myapp
roleRef:
kind: Role
name: configmap-reader
apiGroup: rbac.authorization.k8s.io
EOF
这样配置之后,myapp-sa就只能读取myapp命名空间里的ConfigMap了。注意,RoleBinding只能生效在同一个命名空间里,如果跨命名空间,就需要用ClusterRole和ClusterRoleBinding,但使用范围一定要谨慎。
三、应用容器层面的安全加固
集群组件加固好了,接下来就该管管“租客”了。容器虽然比虚拟机轻,但背后共享同一个内核,所以一旦容器被攻破,攻击者有机会逃逸到宿主机。给应用容器做好安全配置,能大大降低这种风险。
3.1 镜像:别用“带病”的基础镜像
很多人的镜像都是从Docker Hub上随便拉一个ubuntu:latest开始的,里面装着一堆用不到的软件包。这些额外的包只会增加攻击面。更严重的是,如果你不小心把带有漏洞的镜像部署到生产环境,那等于邀请攻击者来玩。
建议尽量使用slim或alpine这类精简镜像,并且定期用工具扫描镜像漏洞。比如用trivy扫描本地镜像:
# 先安装trivy,具体安装方式可参考官方文档
# 这里假设你已经在机器上安装了trivy
# 扫描镜像中的高危漏洞
trivy image --severity HIGH,CRITICAL nginx:1.25
# 如果发现漏洞,可以选择升级版本,或者使用更小的发行版镜像
扫描结果会列出漏洞编号和修补建议。记得把扫描过程集成到CI流水线里,发现高危漏洞就阻止构建,这样才能从一开始就保证镜像干净。
3.2 securityContext:让容器“瘦身”再“上锁”
安全上下文是让容器运行得更加“拘束”的地方。你可以通过securityContext指定容器以非root用户运行、让根文件系统变成只读、丢弃掉不需要的Linux能力。下面是一个完整的示例,展示如何在Pod里配置一个安全的容器:
# 应用一个带有安全上下文的Pod
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: myapp
spec:
# 先创建一个普通用户,这里使用runAsUser来指定用户ID
# 通常建议在镜像里创建好用户,或者使用0以外的UID
securityContext:
# 给整个Pod定义运行用户和组
runAsUser: 1000
runAsGroup: 2000
# 开启强制使用非root用户
runAsNonRoot: true
# 让文件系统变成只读
readOnlyRootFilesystem: true
containers:
- name: my-app
image: busybox:1.36
command: ["/bin/sh", "-c", "sleep 3600"]
securityContext:
# 丢弃所有Linux能力,只保留NET_BIND_SERVICE(如果需要绑定80端口)
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
# 如果应用需要写入临时文件,可以挂载一个emptyDir
volumeMounts:
- name: tmp-volume
mountPath: /tmp
ports:
- containerPort: 8080
volumes:
- name: tmp-volume
emptyDir: {}
EOF
在这个例子里,runAsUser: 1000让容器以非root用户运行,readOnlyRootFilesystem: true让容器里的文件系统只读,这样攻击者想往容器里写点东西都难。capabilities.drop: ["ALL"]则把容器默认拥有的权限都拿掉,只加了一个Docker最常用的端口绑定能力。这样配置之后,即使容器被攻破,攻击者能够做的事情也非常有限。
3.3 资源限制:防止“一个服务吃光整个集群”
还有一种常见的攻击是“资源耗尽型”的。攻击者进入容器后,疯狂占CPU和内存,让其他Pod跑不动,甚至引发节点OOM。解决办法就是给每个Pod设置明确的资源请求和限制。下面是一个完整的示例:
# 为一个Deployment配置资源限制
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: limited-app
namespace: myapp
spec:
replicas: 2
selector:
matchLabels:
app: limited-app
template:
metadata:
labels:
app: limited-app
spec:
containers:
- name: app
image: nginx:1.25
# 这里规定最小和最大资源
resources:
requests:
cpu: 100m # 最少需要0.1个CPU
memory: 128Mi # 最少需要128Mi内存
limits:
cpu: 500m # 最大使用0.5个CPU
memory: 256Mi # 最大使用256Mi内存
EOF
除了给单个Pod设置限制,还可以在命名空间级别用ResourceQuota和LimitRange来统一控制。比如限制整个命名空间的总请求数,或者规定每个容器必须设置resources。这样就算有人忘记给Pod加限制,也有一道防线兜底。
四、漏洞防护:时刻保持“警觉”
安全配置做好了,不代表一劳永逸。漏洞每天都在被发现,你需要一套机制来持续发现和修复问题。
4.1 及时更新Kubernetes版本
Kubernetes官方会定期发布新版本,其中包含安全修复。如果你一直停留在非常老的版本,等于开着漏洞裸奔。建议至少关注每个次要版本的安全公告,并尽快升级。升级前先在测试环境验证一遍,毕竟版本升级也可能带来兼容性问题。
4.2 审计日志别浪费
Kubernetes的审计日志记录了谁在什么时间对什么资源做了什么操作。开启审计日志后,一旦发生异常,你可以回溯发生了什么。比如你可以在kube-apiserver的部署里打开审计,并配置一个策略文件。
4.3 使用策略引擎强制安全标准
从Kubernetes 1.25开始,Pod Security Policy被移除了,取而代之的是Pod Security Admission。你可以通过给命名空间设置标签,来强制要求其中的Pod必须满足安全标准。下面是一个例子:
# 给命名空间打上“必须运行在受限制模式下”的标签
kubectl label namespace myapp pod-security.kubernetes.io/enforce=restricted
kubectl label namespace myapp pod-security.kubernetes.io/audit=restricted
这样,如果myapp命名空间里想要部署一个以root运行的Pod,Kubernetes会直接拒绝(或者记录一条警告,取决于标签的值)。这是今天最推荐的一种策略方式,比手动检查每个Pod容易得多。
五、应用场景与技术优缺点
上面提到的这些加固措施,在不同场景下侧重点不太一样,咱们简单梳理一下。
如果你的业务是面向公网的无状态应用,比如Web服务,重点应该放在API Server的访问控制和网络策略上,防止被人直接扫到管理端口;同时把镜像扫描和Pod安全标准跑起来,避免因为一个漏洞被拖垮。
如果你的集群是多租户共享的,让多个团队或客户共用一套K8s,那RBAC和ResourceQuota就是绝对的核心。你需要精细地给每个租户分配权限和资源上限,并且开启严格的网络隔离,防止租户之间互相干扰。
如果跑的是有状态应用,比如数据库、消息队列,那etcd的加密和存储加密就更重要了,因为这些数据通常都有很高的商业价值。同时,容器的安全上下文也需要严格限制,毕竟数据库是攻击者最想碰的目标。
这些加固措施的优点很明显:能挡住大多数自动化攻击,减小爆炸半径,提高审计能力。缺点也很实在:配置多了,学习成本高了,而且某些严格策略可能会让应用跑不起来。比如只读根文件系统,如果应用需要写日志到根目录,就会报错;再比如强制以非root运行,有的老镜像就是默认root启动,你得改镜像。所以安全加固不是“一键开启”的事,需要不断的调优和验证。
六、注意事项
在做安全加固的时候,有几个坑特别容易踩,这里提醒一下。
第一,别为了保护安全把集群弄到“不可用”。比如你给所有容器都开了只读根文件系统,但应用本身需要写临时文件,那必然出问题。正确的做法是给有写需求的目录挂载emptyDir,或者只在关键组件上开启严格限制。
第二,NetworkPolicy不是万能的。它只能控制Pod之间的网络访问,不能控制Pod访问外部internet,也不能控制外部访问集群的负载均衡。如果攻击者直接绕过了Pod网络,NetworkPolicy就无能为力了。
第三,RBAC配置要定期清理。很多团队创建了一堆ClusterRoleBinding,时间长了都忘了谁在用。建议每隔一段时间检查一下,把不再使用的绑定删掉,避免权限残留。
第四,镜像扫描要真的“跑起来”。不要只在出事的时候扫描一次,要把它集成进CI流程,并且处理扫描结果。扫描出漏洞不修复,等于没扫。
第五,开启审计日志之前要先想好存储方案。审计日志很占磁盘空间,要配置好轮换和保留策略,不然过几天磁盘就满了。
七、文章总结
Kubernetes安全加固并不是一件“高大上”的事,它更像是一个习惯:把门锁好,把保险柜锁好,给房客立好规矩,并且定期检查。从集群组件开始,我们给API Server和etcd加了锁,给kubelet关上了后门,用NetworkPolicy隔开了Pod,用RBAC收紧了权限。再从应用容器入手,用干净的镜像,用安全上下文限制容器的行为,用资源限制防止资源耗尽。最后配合漏洞扫描和策略引擎,让整个集群处于一个不断被加固和监视的状态。
安全是一个持续的过程,不是某个周末做完就算完工的。希望你能从这篇文章里找到适合自己的加固方法,哪怕只动手改掉一个你可能一直忽略的配置,也是很值得的。你的集群和你的业务,会感谢你今天做的这些事。
评论
围绕“Kubernetes安全加固要点:从集群组件到应用容器的安全配置与漏洞防护”参与讨论