从零开始的云原生之旅(七):ConfigMap 和 Secret 配置管理

从零开始的云原生之旅(七):ConfigMap 和 Secret 配置管理

不要把配置写死在代码里!灵活配置才是王道!

📖 文章目录

  • 前言
  • [一、为什么需要 ConfigMap 和 Secret?](#一、为什么需要 ConfigMap 和 Secret?)
    • [1.1 我以前的配置方式](#1.1 我以前的配置方式)
    • [1.2 遇到的问题](#1.2 遇到的问题)
    • [1.3 K8s 的解决方案](#1.3 K8s 的解决方案)
  • 二、ConfigMap:管理配置数据
    • [2.1 什么是 ConfigMap?](#2.1 什么是 ConfigMap?)
    • [2.2 创建 ConfigMap](#2.2 创建 ConfigMap)
    • [2.3 使用 ConfigMap 的 4 种方式](#2.3 使用 ConfigMap 的 4 种方式)
  • 三、Secret:管理敏感数据
    • [3.1 什么是 Secret?](#3.1 什么是 Secret?)
    • [3.2 创建 Secret](#3.2 创建 Secret)
    • [3.3 使用 Secret](#3.3 使用 Secret)
    • [3.4 Secret 的加密存储](#3.4 Secret 的加密存储)
  • [四、实战案例:API 服务配置](#四、实战案例:API 服务配置)
    • [4.1 配置需求分析](#4.1 配置需求分析)
    • [4.2 创建 ConfigMap](#4.2 创建 ConfigMap)
    • [4.3 注入到 Deployment](#4.3 注入到 Deployment)
    • [4.4 应用代码读取配置](#4.4 应用代码读取配置)
  • [五、实战案例:CronJob 配置](#五、实战案例:CronJob 配置)
    • [5.1 定时任务需求](#5.1 定时任务需求)
    • [5.2 CronJob 配置详解](#5.2 CronJob 配置详解)
    • [5.3 调度表达式](#5.3 调度表达式)
    • [5.4 并发策略](#5.4 并发策略)
  • 六、配置的动态更新
    • [6.1 ConfigMap 更新](#6.1 ConfigMap 更新)
    • [6.2 应用如何感知更新?](#6.2 应用如何感知更新?)
    • [6.3 强制更新 Pod](#6.3 强制更新 Pod)
  • 七、最佳实践
    • [7.1 配置分层](#7.1 配置分层)
    • [7.2 命名规范](#7.2 命名规范)
    • [7.3 版本管理](#7.3 版本管理)
    • [7.4 安全建议](#7.4 安全建议)
  • 八、常见问题排查
    • [8.1 ConfigMap 不存在](#8.1 ConfigMap 不存在)
    • [8.2 配置未生效](#8.2 配置未生效)
    • [8.3 Secret 解码失败](#8.3 Secret 解码失败)
  • [九、ConfigMap vs Secret vs 环境变量](#九、ConfigMap vs Secret vs 环境变量)
  • 结语

前言

在前面的文章中,我学会了部署各种工作负载:

  • Deployment:无状态应用
  • StatefulSet:有状态应用(Redis)
  • DaemonSet:节点级服务(日志采集)

但我发现一个问题:配置都写死在代码里!

go 复制代码
// 写死的配置
const (
    RedisHost = "redis-service"
    RedisPort = 6379
    LogLevel  = "info"
)

这样有什么问题?

  • ❌ 开发、测试、生产环境配置不同,需要重新编译
  • ❌ 修改配置需要重新构建镜像
  • ❌ 敏感信息(密码、Token)暴露在代码中

这篇文章,我会学习 K8s 的配置管理方案:

  • ConfigMap:管理配置数据
  • Secret:管理敏感数据
  • CronJob:定时任务的配置
  • ✅ 配置的动态更新
  • ✅ 最佳实践

一、为什么需要 ConfigMap 和 Secret?

1.1 我以前的配置方式

方式 1:写死在代码里

go 复制代码
package config

const (
    RedisHost = "redis-service"
    RedisPort = 6379
    LogLevel  = "debug"  // 开发环境:debug,生产环境:info
    AppEnv    = "development"
)

问题:

  • 换环境要改代码、重新编译
  • 镜像和环境强绑定

方式 2:读取配置文件

go 复制代码
// 读取 config.yaml
cfg, _ := os.ReadFile("config.yaml")
yaml 复制代码
# config.yaml
redis:
  host: redis-service
  port: 6379
log_level: info

问题:

  • 配置文件怎么放到容器里?
  • 不同环境要维护不同的配置文件
  • 修改配置要重新构建镜像

方式 3:环境变量

go 复制代码
redisHost := os.Getenv("REDIS_HOST")
redisPort := os.Getenv("REDIS_PORT")
yaml 复制代码
# Deployment
env:
- name: REDIS_HOST
  value: "redis-service"
- name: REDIS_PORT
  value: "6379"

这个还不错,但:

  • 配置分散在多个 Deployment 中
  • 修改配置要编辑所有 YAML 文件
  • 敏感信息(密码)明文存储

1.2 遇到的问题

场景:部署 3 个微服务,都要连 Redis

yaml 复制代码
# service-a/deployment.yaml
env:
- name: REDIS_HOST
  value: "redis-service"  # 写死

# service-b/deployment.yaml
env:
- name: REDIS_HOST
  value: "redis-service"  # 又写了一遍

# service-c/deployment.yaml
env:
- name: REDIS_HOST
  value: "redis-service"  # 又又写了一遍

Redis 地址改了,要改 3 个文件!


场景:数据库密码

yaml 复制代码
env:
- name: DB_PASSWORD
  value: "mySecretPassword123"  # 明文!

问题:

  • 密码明文存储在 YAML 文件中
  • YAML 文件通常提交到 Git
  • 密码泄露!

1.3 K8s 的解决方案

ConfigMap:管理配置数据

yaml 复制代码
# 统一的配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: common-config
data:
  redis_host: "redis-service"
  redis_port: "6379"

所有服务引用同一个 ConfigMap:

yaml 复制代码
# service-a, service-b, service-c 都这样写
env:
- name: REDIS_HOST
  valueFrom:
    configMapKeyRef:
      name: common-config
      key: redis_host

好处:

  • ✅ 配置集中管理
  • ✅ 修改一次,所有服务生效
  • ✅ 配置和代码分离

Secret:管理敏感数据

yaml 复制代码
# 创建 Secret
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  password: bXlTZWNyZXRQYXNzd29yZDEyMw==  # Base64 编码

使用 Secret:

yaml 复制代码
env:
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: password  # 自动解码

好处:

  • ✅ Base64 编码(虽然不是加密)
  • ✅ RBAC 权限控制(谁能看 Secret)
  • ✅ 可以启用加密存储

二、ConfigMap:管理配置数据

2.1 什么是 ConfigMap?

ConfigMap = 键值对的集合

复制代码
┌───────────────────────────┐
│       ConfigMap           │
│                           │
│  key1: value1             │
│  key2: value2             │
│  config.json: { ... }     │
│  redis.conf: ...          │
│                           │
└───────────────────────────┘

用途:

  • 应用配置(日志级别、数据库地址)
  • 配置文件(nginx.conf, redis.conf
  • 命令行参数
  • 环境变量

2.2 创建 ConfigMap

方法 1:从字面量创建

bash 复制代码
kubectl create configmap my-config \
  --from-literal=log_level=info \
  --from-literal=redis_host=redis-service

查看:

bash 复制代码
kubectl get configmap my-config -o yaml
yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  log_level: info
  redis_host: redis-service

方法 2:从文件创建

bash 复制代码
# 从单个文件
kubectl create configmap redis-config --from-file=redis.conf

# 从目录(目录下所有文件)
kubectl create configmap app-config --from-file=./config/

结果:

yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
data:
  redis.conf: |
    bind 0.0.0.0
    port 6379
    maxmemory 128mb

方法 3:从 YAML 文件创建(推荐)

yaml 复制代码
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
  labels:
    app: api
    version: v0.2
data:
  # 简单的键值对
  log_level: "info"
  app_env: "production"
  
  # Redis 配置
  redis_host: "redis-service"
  redis_port: "6379"
  
  # 缓存配置
  cache_ttl: "3600"
  
  # 特性开关
  enable_cache: "true"
  enable_metrics: "true"
bash 复制代码
kubectl apply -f configmap.yaml

2.3 使用 ConfigMap 的 4 种方式

① 作为环境变量(单个键)
yaml 复制代码
spec:
  containers:
  - name: api
    image: my-api:v1.0
    env:
    - name: LOG_LEVEL  # 环境变量名
      valueFrom:
        configMapKeyRef:
          name: api-config  # ConfigMap 名称
          key: log_level    # ConfigMap 中的键

Pod 内看到的:

bash 复制代码
echo $LOG_LEVEL
# info

② 作为环境变量(所有键)
yaml 复制代码
spec:
  containers:
  - name: api
    image: my-api:v1.0
    envFrom:
    - configMapRef:
        name: api-config  # 所有键都注入为环境变量

Pod 内看到的:

bash 复制代码
echo $log_level
# info

echo $redis_host
# redis-service

echo $cache_ttl
# 3600

注意:键名会自动转换为环境变量格式(大写、下划线)


③ 作为文件挂载(单个文件)
yaml 复制代码
spec:
  containers:
  - name: redis
    image: redis:7-alpine
    volumeMounts:
    - name: config
      mountPath: /etc/redis/redis.conf
      subPath: redis.conf  # 只挂载一个文件
  
  volumes:
  - name: config
    configMap:
      name: redis-config

Pod 内看到的:

bash 复制代码
cat /etc/redis/redis.conf
# bind 0.0.0.0
# port 6379
# maxmemory 128mb

④ 作为目录挂载(所有键)
yaml 复制代码
spec:
  containers:
  - name: app
    image: my-app:v1.0
    volumeMounts:
    - name: config
      mountPath: /etc/config  # 挂载为目录
  
  volumes:
  - name: config
    configMap:
      name: api-config

Pod 内看到的:

bash 复制代码
ls /etc/config/
# cache_ttl
# enable_cache
# log_level
# redis_host
# redis_port

cat /etc/config/log_level
# info

每个键变成一个文件!


三、Secret:管理敏感数据

3.1 什么是 Secret?

Secret = 加密存储的键值对

复制代码
┌───────────────────────────┐
│         Secret            │
│                           │
│  username: YWRtaW4=       │  ← Base64 编码
│  password: cGFzc3dvcmQ=   │
│                           │
└───────────────────────────┘

与 ConfigMap 的区别:

特性 ConfigMap Secret
用途 配置数据 敏感数据
存储 明文 Base64 编码
大小限制 1MB 1MB
加密 不支持 可启用加密存储
权限控制 一般 更严格(RBAC)

3.2 创建 Secret

方法 1:从字面量创建

bash 复制代码
kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password=mySecretPassword123

查看:

bash 复制代码
kubectl get secret db-secret -o yaml
yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: YWRtaW4=  # Base64("admin")
  password: bXlTZWNyZXRQYXNzd29yZDEyMw==  # Base64("mySecretPassword123")

方法 2:从文件创建

bash 复制代码
# SSH 私钥
kubectl create secret generic ssh-key \
  --from-file=ssh-privatekey=~/.ssh/id_rsa

# TLS 证书
kubectl create secret tls tls-secret \
  --cert=path/to/tls.cert \
  --key=path/to/tls.key

方法 3:从 YAML 创建(手动 Base64 编码)

yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:
  username: YWRtaW4=  # echo -n "admin" | base64
  password: bXlTZWNyZXRQYXNzd29yZDEyMw==

或使用 stringData(自动编码):

yaml 复制代码
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
stringData:  # 不需要手动 Base64
  username: admin
  password: mySecretPassword123
bash 复制代码
kubectl apply -f secret.yaml

3.3 使用 Secret

使用方式和 ConfigMap 一样:

① 作为环境变量

yaml 复制代码
env:
- name: DB_USERNAME
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: username

- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: password

② 作为文件挂载

yaml 复制代码
volumeMounts:
- name: db-creds
  mountPath: /etc/db-creds
  readOnly: true  # 只读,更安全

volumes:
- name: db-creds
  secret:
    secretName: db-secret

Pod 内:

bash 复制代码
ls /etc/db-creds/
# username
# password

cat /etc/db-creds/password
# mySecretPassword123  ← 自动解码

3.4 Secret 的加密存储

默认情况:Secret 只是 Base64 编码,不是加密!

bash 复制代码
# 任何人都可以解码
echo "YWRtaW4=" | base64 -d
# admin

启用加密存储(推荐):

K8s 支持 Encryption at Rest(静态加密),配置后:

  • Secret 在 etcd 中加密存储
  • 只有 K8s API Server 能解密
  • 即使攻击者拿到 etcd 备份,也无法读取 Secret

配置方法(需要集群管理员权限):

yaml 复制代码
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      - identity: {}  # 回退到不加密

这是生产环境必须配置的!


四、实战案例:API 服务配置

4.1 配置需求分析

我的 API 服务需要:

  • Redis 地址和端口
  • 日志级别
  • 缓存 TTL
  • 特性开关

这些配置需要:

  • ✅ 不同环境不同值(开发/生产)
  • ✅ 修改后不重新构建镜像
  • ✅ 集中管理

4.2 创建 ConfigMap

yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
  labels:
    app: api
    version: v0.2
data:
  # 应用配置
  log_level: "info"
  app_env: "production"
  
  # Redis 配置
  redis_host: "redis-service"
  redis_port: "6379"
  
  # 缓存配置
  cache_ttl: "3600"  # 默认 1 小时
  
  # 性能配置
  max_connections: "100"
  
  # 特性开关
  enable_cache: "true"
  enable_metrics: "true"
bash 复制代码
kubectl apply -f k8s/v0.2/api/configmap.yaml

4.3 注入到 Deployment

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: api
        image: cloudnative-go-api:v0.2
        
        # 环境变量(从 ConfigMap 注入)
        env:
        - name: APP_ENV
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: app_env
        
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: log_level
        
        - name: REDIS_HOST
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: redis_host
        
        - name: REDIS_PORT
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: redis_port
        
        - name: CACHE_TTL
          valueFrom:
            configMapKeyRef:
              name: api-config
              key: cache_ttl

4.4 应用代码读取配置

go 复制代码
package config

import (
    "os"
    "strconv"
)

type Config struct {
    AppEnv         string
    LogLevel       string
    RedisHost      string
    RedisPort      int
    CacheTTL       int
    EnableCache    bool
    EnableMetrics  bool
}

func LoadConfig() *Config {
    return &Config{
        AppEnv:        getEnv("APP_ENV", "development"),
        LogLevel:      getEnv("LOG_LEVEL", "debug"),
        RedisHost:     getEnv("REDIS_HOST", "localhost"),
        RedisPort:     getEnvInt("REDIS_PORT", 6379),
        CacheTTL:      getEnvInt("CACHE_TTL", 3600),
        EnableCache:   getEnvBool("ENABLE_CACHE", true),
        EnableMetrics: getEnvBool("ENABLE_METRICS", true),
    }
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

func getEnvInt(key string, defaultValue int) int {
    if value := os.Getenv(key); value != "" {
        if intValue, err := strconv.Atoi(value); err == nil {
            return intValue
        }
    }
    return defaultValue
}

func getEnvBool(key string, defaultValue bool) bool {
    if value := os.Getenv(key); value != "" {
        return value == "true"
    }
    return defaultValue
}

使用:

go 复制代码
func main() {
    cfg := config.LoadConfig()
    
    fmt.Printf("环境: %s\n", cfg.AppEnv)
    fmt.Printf("日志级别: %s\n", cfg.LogLevel)
    fmt.Printf("Redis: %s:%d\n", cfg.RedisHost, cfg.RedisPort)
}

五、实战案例:CronJob 配置

5.1 定时任务需求

需求:每小时清理 Redis 的过期键

配置需求:

  • 调度时间(Cron 表达式)
  • Redis 地址
  • 任务超时时间
  • 失败重试次数
  • 历史记录保留数量

5.2 CronJob 配置详解

yaml 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup-job
  labels:
    app: cleanup-job
spec:
  # Cron 调度表达式
  schedule: "0 * * * *"  # 每小时的第0分钟
  
  # 历史保留限制
  successfulJobsHistoryLimit: 3  # 保留 3 个成功的
  failedJobsHistoryLimit: 1      # 保留 1 个失败的
  
  # 并发策略
  concurrencyPolicy: Forbid  # 禁止并发
  
  # 启动截止时间
  startingDeadlineSeconds: 100  # 错过100秒就跳过
  
  # Job 模板
  jobTemplate:
    spec:
      # 完成后 1 小时删除 Pod
      ttlSecondsAfterFinished: 3600
      
      # 失败重试次数
      backoffLimit: 3
      
      # 任务超时时间(5分钟)
      activeDeadlineSeconds: 300
      
      template:
        spec:
          restartPolicy: OnFailure
          
          containers:
          - name: cleanup
            image: cleanup-job:v0.2
            
            env:
            # Redis 地址
            - name: REDIS_HOST
              value: "redis-service:6379"
            
            # Job 信息
            - name: JOB_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            
            resources:
              requests:
                memory: "64Mi"
                cpu: "50m"
              limits:
                memory: "128Mi"
                cpu: "100m"

5.3 调度表达式

Cron 格式:

复制代码
 ┌───────────── 分钟 (0 - 59)
 │ ┌───────────── 小时 (0 - 23)
 │ │ ┌───────────── 日 (1 - 31)
 │ │ │ ┌───────────── 月 (1 - 12)
 │ │ │ │ ┌───────────── 星期 (0 - 6) (0 = 周日)
 │ │ │ │ │
 * * * * *

常用示例:

表达式 说明 场景
*/5 * * * * 每 5 分钟 频繁清理
0 * * * * 每小时 常规清理
0 2 * * * 每天凌晨 2 点 数据库备份
0 0 * * 0 每周日凌晨 周报生成
0 0 1 * * 每月 1 号凌晨 月度统计
0 9-17 * * 1-5 工作日 9-17 点每小时 工作时间任务

5.4 并发策略

concurrencyPolicy 控制并发行为:

策略 说明 适用场景
Allow 允许并发执行(默认) 独立任务(日志归档)
Forbid 禁止并发,跳过新任务 数据库备份(避免冲突)
Replace 取消旧任务,启动新任务 实时报表(只要最新)

示例:数据库备份

yaml 复制代码
spec:
  schedule: "0 2 * * *"  # 每天 2 点
  concurrencyPolicy: Forbid  # 如果上次备份还没完成,跳过

为什么?

  • 备份任务耗时长(可能超过 1 天)
  • 并发备份会导致数据库负载过高
  • 同时备份会冲突(写同一个文件)

六、配置的动态更新

6.1 ConfigMap 更新

bash 复制代码
# 方法 1:编辑 ConfigMap
kubectl edit configmap api-config

# 方法 2:替换 ConfigMap
kubectl apply -f configmap.yaml

# 方法 3:打补丁
kubectl patch configmap api-config \
  -p '{"data":{"log_level":"debug"}}'

6.2 应用如何感知更新?

① 环境变量方式:不会自动更新

yaml 复制代码
env:
- name: LOG_LEVEL
  valueFrom:
    configMapKeyRef:
      name: api-config
      key: log_level

更新 ConfigMap 后:

bash 复制代码
# 修改 ConfigMap
kubectl patch configmap api-config -p '{"data":{"log_level":"debug"}}'

# Pod 内查看
kubectl exec -it api-server-xxx -- env | grep LOG_LEVEL
# LOG_LEVEL=info  ← 还是旧值!

原因:环境变量在 Pod 启动时注入,不会动态更新!


② 文件挂载方式:会自动更新(有延迟)

yaml 复制代码
volumeMounts:
- name: config
  mountPath: /etc/config

volumes:
- name: config
  configMap:
    name: api-config

更新 ConfigMap 后:

bash 复制代码
# 修改 ConfigMap
kubectl patch configmap api-config -p '{"data":{"log_level":"debug"}}'

# 等待 1-2 分钟
sleep 120

# Pod 内查看
kubectl exec -it api-server-xxx -- cat /etc/config/log_level
# debug  ← 新值!

K8s 会自动同步,但有延迟(最多几分钟)


6.3 强制更新 Pod

如果希望立即生效:

方法 1:重启 Pod

bash 复制代码
kubectl rollout restart deployment api-server

方法 2:给 ConfigMap 添加版本号

yaml 复制代码
# configmap-v2.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config-v2  # 新名称
data:
  log_level: "debug"
yaml 复制代码
# deployment.yaml
env:
- name: LOG_LEVEL
  valueFrom:
    configMapKeyRef:
      name: api-config-v2  # 引用新 ConfigMap
      key: log_level
bash 复制代码
kubectl apply -f configmap-v2.yaml
kubectl apply -f deployment.yaml  # 触发滚动更新

方法 3:给 Deployment 添加 ConfigMap 的 Hash(自动触发更新)

bash 复制代码
# 计算 ConfigMap 的 Hash
CONFIG_HASH=$(kubectl get configmap api-config -o json | md5sum | cut -d' ' -f1)

# 添加到 Deployment 的 annotations
kubectl patch deployment api-server -p \
  "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"configmap-hash\":\"$CONFIG_HASH\"}}}}}"

每次 ConfigMap 改变,Hash 变化,触发滚动更新!


七、最佳实践

7.1 配置分层

不要把所有配置放在一个 ConfigMap:

yaml 复制代码
# ❌ 不推荐:所有配置混在一起
apiVersion: v1
kind: ConfigMap
metadata:
  name: all-config
data:
  log_level: info
  redis_host: redis-service
  db_password: password123  # 敏感信息!
  nginx_conf: |
    ...

✅ 推荐:分层管理

yaml 复制代码
# 通用配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: common-config
data:
  log_level: info
  app_env: production

---
# Redis 配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
data:
  redis_host: redis-service
  redis_port: "6379"

---
# 敏感数据(Secret)
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
stringData:
  password: password123

7.2 命名规范

推荐命名:

复制代码
<service>-<type>-<env>

示例:
  api-config-prod       # API 服务的生产配置
  api-config-dev        # API 服务的开发配置
  redis-config          # Redis 配置
  db-secret             # 数据库密钥

7.3 版本管理

方案 1:在名称中包含版本

yaml 复制代码
metadata:
  name: api-config-v2

好处:

  • 可以同时存在多个版本
  • 回滚简单(切换引用)

坏处:

  • 要修改 Deployment 引用

方案 2:在 ConfigMap 中记录版本

yaml 复制代码
metadata:
  name: api-config
  labels:
    version: v2
data:
  version: "v2"
  log_level: info

好处:

  • 不需要修改 Deployment
  • 可以在应用中读取版本号

7.4 安全建议

① Secret 不要提交到 Git

bash 复制代码
# .gitignore
*-secret.yaml
secret*.yaml

或使用加密工具(如 Sealed Secrets)


② 使用 RBAC 限制访问

yaml 复制代码
# 只允许特定 ServiceAccount 读取 Secret
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["db-secret"]
  verbs: ["get"]

③ 启用 Secret 加密存储

bash 复制代码
# 生产环境必须启用!
# 配置 API Server 的 --encryption-provider-config

④ 定期轮换密钥

bash 复制代码
# 每季度更新一次数据库密码
kubectl create secret generic db-secret \
  --from-literal=password=newPassword123 \
  --dry-run=client -o yaml | kubectl apply -f -

# 重启 Pod 使其生效
kubectl rollout restart deployment api-server

八、常见问题排查

8.1 ConfigMap 不存在

症状:

bash 复制代码
kubectl get pods
# NAME                  READY   STATUS                 RESTARTS   AGE
# api-server-xxx        0/1     CreateContainerError   0          10s

日志:

bash 复制代码
kubectl describe pod api-server-xxx
# Events:
#   Warning  Failed  Error: configmap "api-config" not found

解决:

bash 复制代码
# 检查 ConfigMap 是否存在
kubectl get configmap api-config

# 如果不存在,创建它
kubectl apply -f configmap.yaml

8.2 配置未生效

症状:修改了 ConfigMap,应用还是读取旧值

排查:

bash 复制代码
# 1. 确认 ConfigMap 已更新
kubectl get configmap api-config -o yaml

# 2. 检查使用方式
kubectl get deployment api-server -o yaml | grep -A10 "env:"

# 3. 如果是环境变量方式,需要重启 Pod
kubectl rollout restart deployment api-server

8.3 Secret 解码失败

症状:

bash 复制代码
kubectl logs api-server-xxx
# Error: invalid character in password

原因:Base64 编码错误

bash 复制代码
# 检查 Secret
kubectl get secret db-secret -o yaml
# data:
#   password: bXlTZWNyZXRQYXNzd29yZDEyMw==

# 手动解码测试
echo "bXlTZWNyZXRQYXNzd29yZDEyMw==" | base64 -d
# mySecretPassword123

如果解码失败,重新创建 Secret:

bash 复制代码
kubectl delete secret db-secret

kubectl create secret generic db-secret \
  --from-literal=password=mySecretPassword123

九、ConfigMap vs Secret vs 环境变量

方式 优点 缺点 适用场景
ConfigMap 集中管理、易于更新、支持文件 明文存储 配置数据(非敏感)
Secret 加密存储、权限控制 使用复杂 密码、Token、证书
环境变量 简单直接 分散管理、不能动态更新 简单配置、调试
文件 格式自由、支持复杂配置 需要挂载卷 配置文件(nginx.conf)

推荐组合:

yaml 复制代码
# 简单配置 → 环境变量(从 ConfigMap)
env:
- name: LOG_LEVEL
  valueFrom:
    configMapKeyRef:
      name: api-config
      key: log_level

# 复杂配置 → 文件挂载(从 ConfigMap)
volumeMounts:
- name: nginx-config
  mountPath: /etc/nginx/nginx.conf
  subPath: nginx.conf

volumes:
- name: nginx-config
  configMap:
    name: nginx-config

# 敏感数据 → Secret
env:
- name: DB_PASSWORD
  valueFrom:
    secretKeyRef:
      name: db-secret
      key: password

结语

这篇文章,我学会了:

ConfigMap:管理配置数据

  • 创建 ConfigMap 的 3 种方法
  • 使用 ConfigMap 的 4 种方式
  • 配置集中管理、易于更新

Secret:管理敏感数据

  • Base64 编码(不是加密)
  • 启用加密存储(生产必须)
  • RBAC 权限控制

CronJob:定时任务配置

  • Cron 表达式
  • 并发策略(Allow/Forbid/Replace)
  • 历史记录管理

动态更新配置

  • 环境变量不会自动更新(需重启)
  • 文件挂载会自动更新(有延迟)
  • 强制更新的 3 种方法

最佳实践

  • 配置分层(不要混在一起)
  • 命名规范(service-type-env)
  • 版本管理(v1, v2)
  • 安全建议(加密、RBAC、轮换)

最大的收获:

不要把配置写死在代码里!
ConfigMap 管理配置,Secret 管理密钥!
配置和代码分离,才能灵活部署!


v0.2 完结!

在 v0.2 中,我学会了:

  1. K8s 工作负载全景:Deployment、StatefulSet、DaemonSet、CronJob
  2. StatefulSet 部署 Redis:持久化存储、Headless Service
  3. DaemonSet 日志采集器:节点级服务、访问宿主机
  4. ConfigMap 和 Secret:配置管理、敏感数据

下一步(v0.3 预告):

v0.3 将学习 高级网络和存储

  • Ingress(统一入口)
  • NetworkPolicy(网络隔离)
  • StorageClass(动态存储)
  • 监控和日志(Prometheus + Grafana)

敬请期待!


如果这篇文章对你有帮助,欢迎点赞、收藏、分享!

有问题欢迎在评论区讨论! 👇

相关推荐
Robpubking5 小时前
Terraform 部署一个 AWS 的 EC2 附源码
云原生·aws·terraform
hwj运维之路5 小时前
《Kubernetes面试题汇总系列》
云原生·容器·kubernetes
Bellafu6666 小时前
zookeeper是什么
分布式·zookeeper·云原生
芝麻开门-新起点13 小时前
微服务高并发设计考虑要点
微服务·云原生·架构
The god of big data13 小时前
Metrics Server 完整配置安装手册
云原生·云计算·k8s
昙鱼15 小时前
Linux上使用Docker安装MinIO指南
云原生·eureka
2501_9387699915 小时前
云计算:IaaS和PaaS区别
云原生·云计算·paas
華勳全栈16 小时前
生产环境用Go语言完成微服务搭建和业务融入
微服务·云原生·架构
xx.ii1 天前
k8s的资源管理
云原生·容器·kubernetes