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

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

至此,我们已经取得了巨大的进步:我们不仅能用 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 的文件:

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

yaml 复制代码
# 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 文件:

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 的引用。

yaml 复制代码
# 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,在日志目录里创建一个测试文件:

    bash 复制代码
    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:

    bash 复制代码
    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 集群中!这将是你展示和巩固所学知识的最佳机会。准备好了吗?我们下一篇见!

相关推荐
陌上阳光10 小时前
docker搭建ray集群
docker·容器·ray
这就是佬们吗11 小时前
初识 docker [上]
java·开发语言·笔记·docker·容器
FJW02081411 小时前
负载均衡集群HAproxy
linux·服务器·云原生·负载均衡
BigBigHang11 小时前
【docker】DM8达梦数据库的docker-compose以及一些启动踩坑
数据库·docker·容器
云道轩11 小时前
使用Docker在Rocky Linux 9.5上在线部署LangFlow
linux·人工智能·docker·容器·langflow
伟大的大威11 小时前
Docker 部署 Supabase并连接
运维·docker·容器
杰克逊的日记12 小时前
k8s的csi对接GPFS
云原生·容器·kubernetes·存储·gpfs
岚天start12 小时前
云服务器以域名形式访问机房Kubernetes集群服务之解决方案
nginx·docker·kubernetes·kubesphere·解决方案·云服务器·机房
容器魔方16 小时前
「中科类脑」正式加入 Karmada 用户组!携手社区共建多集群生态
云原生·容器·云计算
终端行者16 小时前
k8s之ingress定义https访问方式
容器·https·kubernetes