4. Kubernetes进阶:存储卷与持久化、Helm包管理、RBAC与安全、Pod安全策略

好,咱们继续往下走。前面几章我们把Kubernetes的基础概念和核心工作负载都过了一遍。说实话,到了生产环境,你会发现光会跑Pod是远远不够的。这一章我挑了几个真正让Kubernetes变得「可用」的关键点:存储怎么持久化、应用怎么打包部署、权限怎么管、安全怎么防。嗯,每一个都是我在项目里踩过坑的地方。

4.1 存储卷与持久化:让数据不随Pod消失

先问个问题:Pod挂了,数据还在吗?默认情况下,Pod里的容器一重启,所有临时数据就没了。这显然不行,对吧?

Kubernetes的存储卷(Volume)就是为了解决这个问题的。它本质上是一个目录,Pod里的容器都能访问。但要注意,Volume的生命周期和Pod绑定——Pod删了,Volume也就没了。那怎么办?我们需要持久化存储。

4.1.1 emptyDir:临时存储的实用场景

先说说最简单的emptyDir。它就是个空目录,Pod启动时创建,Pod删除时清空。听起来很鸡肋?其实不然。

我在项目中经常用它来做「边车容器」的数据交换。比如一个容器写日志,另一个容器用Filebeat收集日志,中间放个emptyDir共享。Pod挂了日志丢了?没关系,反正日志已经发出去了。

apiVersion: v1
kind: Pod
metadata:
  name: log-collector
spec:
  containers:
  - name: app
    image: my-app
    volumeMounts:
    - mountPath: /var/log
      name: log-volume
  - name: filebeat
    image: docker.elastic.co/beats/filebeat:7.10.0
    volumeMounts:
    - mountPath: /var/log
      name: log-volume
  volumes:
  - name: log-volume
    emptyDir: {}

4.1.2 hostPath:小心使用,但有时真香

hostPath直接把宿主机目录挂进Pod。我建议你谨慎使用——因为它破坏了Pod的隔离性。但有些场景确实绕不开,比如DaemonSet要读取宿主机日志,或者某些监控组件需要访问宿主机文件系统。

注意:hostPath会让Pod和宿主机强耦合。如果你用了hostPath,Pod调度到不同节点时数据就不一致了。我曾经因为这个原因排查了一整天,最后发现是hostPath惹的祸。

4.1.3 PersistentVolume与PersistentVolumeClaim:真正的持久化

这才是生产环境的主角。PV(PersistentVolume)是集群管理员准备的存储资源,PVC(PersistentVolumeClaim)是用户对存储的「需求声明」。两者通过匹配来绑定。

我个人习惯把PV和PVC分开管理。运维团队负责PV,开发团队只写PVC。这样职责清晰,互不干扰。

# PV定义
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-1
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 192.168.1.100
    path: "/data/k8s"

# PVC定义
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

这里有个关键概念:访问模式。ReadWriteOnce(单节点读写)、ReadOnlyMany(多节点只读)、ReadWriteMany(多节点读写)。选错了,Pod就起不来。我遇到过有人把数据库的PVC配成ReadWriteMany,结果多个Pod同时写同一个数据卷,数据全乱了。

4.1.4 StorageClass:动态供给的利器

手动创建PV太累了。StorageClass可以自动创建PV。你只需要写PVC,系统会根据StorageClass自动分配存储。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
  replication-type: none
我的建议:生产环境一定要用StorageClass。手动管理PV就像手动管理服务器——迟早会出问题。云厂商都提供了现成的StorageClass,比如AWS的gp2、GCP的pd-standard。

4.2 Helm包管理:Kubernetes的「应用商店」

你有没有遇到过这种情况:部署一个复杂应用,要写十几个YAML文件,还要手动调整参数?Helm就是来解决这个痛点的。

Helm把Kubernetes资源打包成一个Chart。一个Chart可以包含Deployment、Service、ConfigMap、PVC等等。你只需要改几个参数,就能部署出不同配置的应用。

4.2.1 Helm的核心概念

  • Chart:应用的打包格式,包含所有Kubernetes资源模板
  • Repository:Chart的存储仓库,类似Docker Hub
  • Release:Chart的一次部署实例,同一个Chart可以部署多次

我记得第一次用Helm部署Nginx时,就一行命令:helm install my-nginx bitnami/nginx。然后所有资源都自动创建好了。当时的感觉就是——这才是云原生该有的样子。

4.2.2 编写一个简单的Helm Chart

一个Chart的目录结构大概是这样的:

my-chart/
├── Chart.yaml          # Chart元数据
├── values.yaml         # 默认配置值
├── templates/          # 模板文件
│   ├── deployment.yaml
│   ├── service.yaml
│   └── _helpers.tpl    # 辅助函数
└── charts/             # 子Chart依赖

模板文件里用Go模板语法。比如在deployment.yaml里:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-chart.fullname" . }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"

然后在values.yaml里定义默认值:

replicaCount: 3
image:
  repository: nginx
  tag: latest

部署时可以用--set覆盖:helm install my-release ./my-chart --set replicaCount=5

关键点:Helm的模板能力很强,但别滥用。我见过有人把整个应用的逻辑都写在模板里,结果Chart变得比原始YAML还复杂。记住,Helm是简化部署的,不是增加复杂度的。

4.2.3 Helm的版本管理

Helm v3已经废弃了Tiller,直接和Kubernetes API交互。升级时用helm upgrade,回滚用helm rollback。每次部署都会生成一个Release版本号。

我曾经在生产环境用helm upgrade --install做滚动更新,发现新版本有问题,直接helm rollback my-release 1就回退到上一个版本。整个过程不到10秒,比手动改YAML快太多了。

4.3 RBAC与安全:谁可以做什么

Kubernetes的RBAC(基于角色的访问控制)说白了就是三件事:谁(User/ServiceAccount)、能干什么(Role/ClusterRole)、在哪些资源上(Resource)。

我见过很多团队一开始图省事,直接给所有人cluster-admin权限。结果某天有人误删了kube-system命名空间下的资源,整个集群挂了。嗯,从那以后我再也不敢偷懒了。

4.3.1 Role与ClusterRole的区别

类型 作用范围 使用场景
Role 单个命名空间 限制某个团队只能操作自己的命名空间
ClusterRole 整个集群 集群级别的资源(如Node、PV)或跨命名空间的操作

4.3.2 实战:创建一个只读用户

假设我们要给一个运维人员只读权限,只能看Pod和Service:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "services"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: ops-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

verbs有get、list、watch、create、update、patch、delete。我建议遵循最小权限原则——只给够用的权限,不多给一个。

4.3.3 ServiceAccount:Pod的身份

Pod要访问Kubernetes API怎么办?用ServiceAccount。每个Pod都可以绑定一个ServiceAccount,然后通过RBAC控制这个ServiceAccount的权限。

我习惯为每个微服务创建独立的ServiceAccount,而不是用默认的。这样即使某个Pod被攻破,攻击者也拿不到集群的管理权限。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-app-binding
subjects:
- kind: ServiceAccount
  name: my-app-sa
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io

4.4 Pod安全策略:给Pod上把锁

Pod安全策略(PSP)在Kubernetes 1.21之后被废弃了,取而代之的是Pod Security Admission(PSA)。但原理是一样的:限制Pod能做什么,不能做什么。

说白了,就是防止有人用特权容器、挂载宿主机目录、或者以root身份运行。这些都是安全漏洞的高发区。

4.4.1 Pod Security Standards的三个级别

级别 说明 典型限制
Privileged 无限制
Baseline 基本安全 禁止特权容器、禁止hostPID、禁止hostNetwork
Restricted 严格限制 禁止所有特权、必须非root、只读根文件系统

4.4.2 用Pod Security Admission实施策略

在命名空间上打标签即可:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: baseline
    pod-security.kubernetes.io/warn: baseline

这里三个标签分别表示:强制级别(违反则拒绝)、审计级别(记录但不拒绝)、警告级别(提示但不拒绝)。

我曾经踩过的坑:有一次我把一个命名空间设为restricted级别,结果所有Pod都起不来了——因为镜像里默认用root用户运行。后来我不得不逐个修改Dockerfile,加上USER指令。所以建议先设成audit或warn级别,观察一段时间再强制。

4.4.3 Pod安全上下文的配置

在Pod定义里,可以通过securityContext来声明安全需求:

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
  containers:
  - name: app
    image: my-app
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
      readOnlyRootFilesystem: true

这些配置看起来繁琐,但能有效防止容器逃逸攻击。我建议所有生产环境的Pod都加上这些安全上下文,哪怕只是多花几分钟配置。

我的习惯:在CI/CD流水线里加一个安全扫描步骤,自动检查Pod的securityContext是否合规。不符合的直接阻断部署。这样既保证了安全,又不用人工审查每个YAML文件。

好了,这一章的内容就到这里。存储、Helm、RBAC、安全策略——这四个点看似独立,其实都是生产环境绕不开的「硬骨头」。下一章我们会聊聊监控和日志,到时候你会发现,没有这些基础,监控数据都拿不到。嗯,慢慢来,每一步都踩实了。