云原生核心技术 (9/12): K8s 实战:如何管理应用的配置 (ConfigMap/Secret) 与数据 (Volume)?

发布于:2025-06-15 ⋅ 阅读:(14) ⋅ 点赞:(0)

大家好,欢迎准时来到《云原生核心技术》系列的第九篇!

至此,我们已经取得了巨大的进步:我们不仅能用 Deployment 部署和管理应用,还能通过 Service 和 Ingress 将其安全、优雅地暴露给外部世界。我们部署的 Nginx 应用就像一个训练有素、不知疲倦的员工,能自我修复,也能平滑升级。

但我们忽略了一个重要的问题:现实世界的应用,并非都是像 Nginx 这样简单的“无状态”应用。它们往往有更复杂的需求:

  • 它们需要读取配置文件,比如数据库的连接地址、端口号等。
  • 它们需要保存敏感信息,比如 API 密钥、数据库密码。
  • 它们需要持久化地存储数据。想象一下,如果你的数据库 Pod 重启一次,所有用户数据就都消失了,那将是灾难性的!

今天,我们就来学习 K8s 提供的三大法宝,专门用来解决这些“有状态”的难题:ConfigMapSecretVolume


一、配置管理双雄:ConfigMap 与 Secret

在 K8s 中,我们强烈推荐将配置与应用镜像分离。你不应该把配置文件直接打包进 Docker 镜像里。为什么?因为配置(比如数据库地址)在开发、测试、生产环境中都是不同的。如果把配置写死在镜像里,你就需要为每个环境打一个新镜像,这完全违背了“一次构建,到处运行”的原则。

K8s 提供了两种资源对象来优雅地解决这个问题。

1. ConfigMap: 明文配置的管家

ConfigMap 顾名思义,就是“配置地图”,它专门用来存储非敏感的、明文的键值对配置。

比喻时间:把 ConfigMap 想象成一张贴在冰箱门上的备忘录便签。上面写着“Wi-Fi密码:MyHomeWiFi”、“牛奶在第二层”等公开信息。家里任何一个成员(Pod)都可以随时查看。

如何使用 ConfigMap?

你可以通过两种主要方式将 ConfigMap 中的数据“注入”到 Pod 中:

  1. 作为环境变量:将 key-value 直接变成容器内的环境变量。
  2. 作为文件挂载:将 key-value 变成文件,挂载到容器的指定目录中。这种方式对于传统的、需要读取配置文件的应用(如 Spring Boot 的 application.properties)非常友好。

动手:用 ConfigMap 定制 Nginx 欢迎页

让我们来创建一个 ConfigMap,用它来动态修改 Nginx 的默认页面内容。

第一步:创建 ConfigMap

创建一个名为 nginx-configmap.yaml 的文件:

# nginx-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-index-html
data:
  # key 是 "index.html",value 是整个 HTML 文件的内容
  index.html: |
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome from ConfigMap!</title>
    </head>
    <body>
    <h1>This page is dynamically configured by a Kubernetes ConfigMap!</h1>
    <p>We are learning about stateful applications.</p>
    </body>
    </html>

应用它:kubectl apply -f nginx-configmap.yaml

第二步:修改 Deployment 以使用 ConfigMap

现在,修改我们之前的 nginx-deployment.yaml 文件。我们需要添加一个 volumesvolumeMounts 部分,将这个 ConfigMap 挂载到 Nginx 存放 index.html 的目录 /usr/share/nginx/html

# nginx-deployment-with-configmap.yaml
apiVersion: apps/v1
kind: Deployment
# ... metadata, replicas, selector 不变 ...
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-server
  template:
    metadata:
      labels:
        app: nginx-server
    spec:
      containers:
      - name: nginx
        image: nginx:1.21
        ports:
        - containerPort: 80
        # 新增内容:定义卷挂载
        volumeMounts:
        - name: nginx-index-volume # 挂载点的名字,需要和下面的 volume 名字对应
          # 挂载到容器内的目标路径
          mountPath: /usr/share/nginx/html
      # 新增内容:定义 Pod 级别的卷
      volumes:
      - name: nginx-index-volume # 卷的名字
        # 卷的类型是 ConfigMap
        configMap:
          # 使用名为 nginx-index-html 的 ConfigMap
          name: nginx-index-html

应用修改后的 Deployment:kubectl apply -f nginx-deployment-with-configmap.yaml

第三步:验证

等待 Pod 更新完成后,再次访问你的 Nginx 服务(通过 http://nginx.test 或 NodePort)。你会惊喜地发现,欢迎页面已经变成了我们用 ConfigMap 定义的内容!

2. Secret: 敏感信息的保险箱

Secret 和 ConfigMap 非常相似,但它是专门为存储敏感数据而设计的,比如密码、Token、SSH 密钥等。

比喻时间:如果 ConfigMap 是冰箱门上的便签,那 Secret 就是锁在卧室抽屉里的密码本。它也是给家人(Pod)用的,但你不会把它公开展示。

重要提示:默认情况下,K8s 只是将 Secret 的内容进行了 Base64 编码,它不是加密! Base64 是一种任何人都可以解码的编码方式。Secret 的“安全”主要体现在 K8s 的 RBAC(基于角色的访问控制)上,你可以精细地控制哪些用户或服务账号有权读取某个 Secret。

Secret 的创建和使用方式与 ConfigMap 几乎完全相同,只是 kind 变成了 Secret


二、数据持久化:Volume, PV 和 PVC

Pod 的生命周期是短暂的,它们随时可能被销毁和重建。Pod 内部文件系统的任何数据都会随着 Pod 的消亡而灰飞烟灭。这对于无状态应用没问题,但对于数据库、消息队列等有状态应用来说是致命的。

我们需要一种方法,让数据能够独立于 Pod 的生命周期而存在。这就是 Volume 的使命。

Volume 的核心思想:解耦

K8s 的存储设计非常精妙,它将“存储的使用”和“存储的提供”这两个环节进行了解耦。这是通过三个核心概念实现的:

  1. PersistentVolume (PV): 持久卷。

    • 角色:集群管理员(运维人员)。
    • 职责:负责提供存储。管理员会事先创建好各种类型的存储资源(比如一块 10GB 的快速 SSD,或是一块 100GB 的慢速 HDD),并将它们定义成 PV 对象,放入 K8s 的“存储资源池”中。
    • 比喻:PV 就像是房产开发商建好的、待出租的仓库。仓库有大有小,位置有好有坏。
  2. PersistentVolumeClaim (PVC): 持久卷声明。

    • 角色:应用开发者(用户)。
    • 职责:负责申请存储。开发者不需要关心存储具体是怎么来的(是NFS还是Ceph),他只需要提交一个“声明”,说明“我需要一个 5GB 大小、可以被多个 Pod 读写的存储”。
    • 比喻:PVC 就像是租户提交的一份租房申请:“我需要一个 100 平方米的仓库”。
  3. StorageClass: 存储类。

    • 角色:自动化机制。
    • 职责:动态地创建 PV。在没有 StorageClass 的情况下,管理员需要手动创建 PV。有了 StorageClass,当 K8s 收到一个 PVC 申请,并且没有现成的 PV 能满足时,StorageClass 会自动调用底层存储插件(比如云厂商的磁盘服务)来创建一个全新的 PV,并与该 PVC 绑定。这是目前最主流的方式。
    • 比喻:StorageClass 就像一个全自动的房产中介。你提交租房申请,它能立刻凭空给你变出一套符合要求的仓库来。

整个流程:开发者创建 PVC (租房申请) -> K8s(或 StorageClass)找到/创建一个匹配的 PV (仓库) -> K8s 将 PVC 和 PV 绑定 -> 开发者在 Pod 中引用 PVC (拿到仓库钥匙),将这个 Volume 挂载到容器的某个路径下。

这样一来,即使 Pod (租客) 挂掉了,数据(存放在仓库里的货物)依然安全地保管在 PV 中。新的 Pod (新租客) 启动后,只要引用同一个 PVC (使用同一把钥匙),就能继续访问之前的数据。

动手:让 Nginx 拥有持久化的日志

让我们来实践一下,创建一个 PVC,并将其挂载到 Nginx Pod 中,用来持久化存储访问日志。

第一步:创建 PVC (持久卷声明)

创建一个 nginx-pvc.yaml 文件:

# nginx-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-log-pvc
spec:
  # 访问模式: ReadWriteOnce 表示该卷只能被单个节点读写
  accessModes:
    - ReadWriteOnce
  # 资源请求
  resources:
    requests:
      # 申请 1GB 的存储空间
      storage: 1Gi
  # storageClassName: standard # 在 Minikube 中,通常有一个名为 "standard" 的默认 StorageClass

应用它:kubectl apply -f nginx-pvc.yaml

第二步:修改 Deployment 以使用 PVC

再次修改我们的 Deployment,这次我们添加对 PVC 的引用。

# nginx-deployment-with-pvc.yaml
apiVersion: apps/v1
kind: Deployment
# ...
spec:
  # ...
  template:
    # ...
    spec:
      containers:
      - name: nginx
        image: nginx:1.21
        ports:
        - containerPort: 80
        volumeMounts:
        # 我们把日志卷挂载到 Nginx 默认的日志目录
        - name: nginx-log-volume
          mountPath: /var/log/nginx
      volumes:
      # 定义卷,这次的类型是 persistentVolumeClaim
      - name: nginx-log-volume
        persistentVolumeClaim:
          # 引用我们刚刚创建的 PVC 的名字
          claimName: nginx-log-pvc

注意:为了演示,你可以把之前 ConfigMap 的挂载去掉,也可以保留,它们不冲突。

应用它:kubectl apply -f nginx-deployment-with-pvc.yaml

第三步:验证数据持久性

  1. 找到一个 Nginx Pod 的名字:kubectl get pods
  2. 进入该 Pod,在日志目录里创建一个测试文件:
    kubectl exec -it <your-nginx-pod-name> -- bash
    # 进入 Pod 后执行:
    echo "This is a persistent log file." > /var/log/nginx/my-test.log
    cat /var/log/nginx/my-test.log
    exit
    
  3. 现在,手动删除这个 Pod:kubectl delete pod <your-nginx-pod-name>
  4. 等待 Deployment 创建一个新的 Pod。
  5. 获取新 Pod 的名字,并进入新的 Pod:
    kubectl exec -it <new-nginx-pod-name> -- bash
    # 再次查看文件是否存在:
    cat /var/log/nginx/my-test.log
    

你会发现,my-test.log 文件依然存在!数据成功地在 Pod 重启后得以保留。


总结

今天我们解锁了在 K8s 中处理有状态应用的核心技能。通过这三大法宝,我们的应用变得更加完善和健壮:

  • ConfigMap:管理非敏感配置,实现配置与代码的分离。
  • Secret:安全地管理密码、密钥等敏感信息。
  • PV/PVC 模型:为我们的应用提供了独立于 Pod 生命周期的、可靠的持久化存储。

到目前为止,我们已经逐个学习了部署一个真实世界应用所需的几乎所有 K8s 核心组件。就像学完了乐高的所有基本积木块,是时候将它们拼装成一个复杂的、令人惊叹的模型了!

在下一篇,我们将迎来本系列的终极实战:从零开始,将一个包含 Spring Boot 后端、MySQL 数据库和 Redis 缓存的复杂微服务应用,完整地部署到我们的 Kubernetes 集群中!这将是你展示和巩固所学知识的最佳机会。准备好了吗?我们下一篇见!