如何在Kubernetes ConfigMap中引用Secret中的数据库密码?
你遇到的核心问题是:Kubernetes不会自动替换ConfigMap内容里的环境变量引用。你把整个APP_CONFIG作为环境变量注入容器时,里面的$DB_PASSWORD只是静态字符串,并不会被Pod中定义的DB_PASSWORD环境变量值替换,所以应用连接MySQL时用的是字符串"$DB_PASSWORD"而非真实密码,自然连接失败。
下面提供两种可行的解决思路,你可以根据应用的实际情况选择:
方案1:拆分配置为独立环境变量(最简单,适合支持读取单个环境变量的应用)
如果你的应用可以直接读取单个数据库配置的环境变量,不需要统一的APP_CONFIG配置字符串,那可以把数据库相关配置拆分成独立的环境变量,分别从ConfigMap和Secret中获取:
修改ConfigMap
把数据库的非敏感配置拆成单独的键值对:
apiVersion: v1 kind: ConfigMap metadata: name: config data: APP_PORT: "8080" DB_HOST: "mysql" DB_MYSQL_PORT: "3306" DB_USER: "root"
修改Deployment
直接注入各个环境变量:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: app labels: app: backend spec: replicas: 1 template: metadata: labels: app: backend spec: containers: - name: app image: simple-app-image ports: - name: "8080" containerPort: 8080 env: - name: APP_PORT valueFrom: configMapKeyRef: name: config key: APP_PORT - name: DB_HOST valueFrom: configMapKeyRef: name: config key: DB_HOST - name: DB_MYSQL_PORT valueFrom: configMapKeyRef: name: config key: DB_MYSQL_PORT - name: DB_USER valueFrom: configMapKeyRef: name: config key: DB_USER - name: DB_PASSWORD valueFrom: secretKeyRef: name: "mysql-secret" key: "mysql-root-password"
这样应用直接读取这些环境变量就能拿到正确的数据库配置了。
方案2:用InitContainer生成动态配置文件(适合必须使用统一配置文件的应用)
如果你的应用必须读取一个完整的YAML/JSON配置文件,那可以借助initContainer和envsubst工具,先把ConfigMap中的模板文件替换成包含真实密码的配置文件,再让主容器使用这个文件:
修改ConfigMap
把原来的APP_CONFIG改成模板文件,用${DB_PASSWORD}作为占位符:
apiVersion: v1 kind: ConfigMap metadata: name: config data: app-config.template: | port: 8080 databases: default: connector: mysql host: "mysql" port: "3306" user: "root" password: "${DB_PASSWORD}"
修改Deployment
添加initContainer处理模板,同时挂载共享卷传递生成的配置文件:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: app labels: app: backend spec: replicas: 1 template: metadata: labels: app: backend spec: # 定义共享卷,用于传递生成的配置文件 volumes: - name: config-volume emptyDir: {} - name: config-template-volume configMap: name: config items: - key: app-config.template path: app-config.template # InitContainer:负责替换模板中的变量,生成真实配置文件 initContainers: - name: config-init image: busybox:1.35 # busybox包含envsubst工具 command: ['sh', '-c', 'envsubst < /config-template/app-config.template > /config/app-config.yaml'] env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: "mysql-secret" key: "mysql-root-password" volumeMounts: - name: config-volume mountPath: /config # 生成的配置文件存在这里 - name: config-template-volume mountPath: /config-template # 挂载ConfigMap中的模板文件 # 主容器:读取生成的配置文件 containers: - name: app image: simple-app-image ports: - name: "8080" containerPort: 8080 volumeMounts: - name: config-volume mountPath: /app/config # 这里改成你的应用读取配置文件的路径 # 如果应用需要通过环境变量指定配置文件路径,添加这个 env: - name: APP_CONFIG_PATH value: /app/config/app-config.yaml
原理说明
initContainer会在主容器启动前运行,它通过envsubst命令把模板中的${DB_PASSWORD}替换成从Secret中获取的真实密码,生成完整的配置文件存储在共享卷中。主容器启动后直接读取这个卷里的配置文件,就能拿到正确的数据库密码了。
内容的提问来源于stack exchange,提问作者yara mohamed

