Carpe Diem

備忘録

ConfigMapでYAMLファイルを管理する

概要

コンテナのポータビリティのため、設定値を環境変数で管理することは一般的ですが、

  • 細かいパラメータを一括で管理したい
  • 改行を含むが、base64エンコードせずそのままの方が視認性が高く運用しやすいデータがある
    • PEM、PGPの公開鍵など

といった際にYAMLで管理すると便利です。

KubernetesではConfigMapがあるためそれを使ってYAMLファイルをマウントします。

環境

対応方法

以下のような設定値をYAMLで管理するとします。

---
params: 
  endpoint: example.com
  timeout: 10s
  retry: 3
key: |-
  -----BEGIN PUBLIC KEY-----
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7v6LsXjyw...
  -----END PUBLIC KEY-----

コード

christina04.hatenablog.com

と同じようなコードを用意します。

type ServerParams struct {
        Endpoint string        `yaml:"endpoint"`
        Timeout  time.Duration `yaml:"timeout" default:"10s"`
        Retry    int           `yaml:"retry" default:"3"`
}

type Config struct {
        Params ServerParams `yaml:"params"`
        Key    string       `yaml:"key"`
}

type EnvVars struct {
        ConfigPath string `split_words:"true" default:"./config.yaml"`
}

func main() {
        var s EnvVars
        err := envconfig.Process("", &s)
        if err != nil {
                log.Fatal(err.Error())
        }

        b, err := os.ReadFile(s.ConfigPath)
        if err != nil {
                log.Fatal(err)
        }
        cfg := Config{}
        if err := yaml.Unmarshal(b, &cfg); err != nil {
                log.Fatal(err)
        }

        fmt.Printf("%#v\n", cfg)
}

きちんと読み込めることをローカルで確認しておきます。

$ go run main.go
Config{Params:main.ServerParams{Endpoint:"https://example.com", Timeout:0, Retry:0}, Key:"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7v6LsXjyw...\n-----END PUBLIC KEY-----"}

Kubernetes

次にKubernetesの設定です。大きく2通りあります。

YAMLファイルを直接扱う場合は後者のconfigMapGeneratorが向いています。

kustomize.yaml

configMapGeneratorの場合、次のようにkustomize.yamlが参照できる場所にYAMLファイル(今回は例としてmyconfig.yaml)を置き、

$ tree
.
├── config
│   └── myconfig.yaml
├── deployment.yaml
├── kustomization.yaml
└── service.yaml

次のようにconfigMapGeneratorフィールドを書けばOKです。ConfigMapを定義するよりも直感的でシンプルですね。
もちろんルートディレクトリでもOKです。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: myapp-config
  files:
  - config/myconfig.yaml

ただconfigMapGeneratorと通常のConfigMapとでは挙動の違いがあります。

通常、ボリュームで使用されているConfigMapが更新されている場合、中のキーも同じく更新されます。これはkubeletが定期的な同期ごとにマウントされているConfigMapが更新されているかチェックしてくれるためです。
しかしながらconfigMapGeneratorはfileが更新されるとPod自体が再作成されることで、ConfigMapの中身を更新します。

subPathも交えながらまとめるとこの様になります。

ケース Podの再作成 自動更新
ConfigMapでsubPathなし されない される
ConfigMapでsubPathあり されない されない
configMapGenerator される 再作成で自動更新

ref: Podを構成してConfigMapを使用する | Kubernetes
ref: kustomize/examples/configGeneration.md at master · kubernetes-sigs/kustomize · GitHub

Deployment

Deploymentは通常のConfigMapと同じようにマウントして扱います。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
spec:
  selector:
    matchLabels:
      name: myapp
  template:
    metadata:
      labels:
        name: myapp
    spec:
      containers:
        - name: myapp
          image: xxxx
          env:
            - name: CONFIG_PATH
              value: /data/myconfig.yaml
          volumeMounts:
            - mountPath: /data
              name: config-volume  # spec.volumes[].nameと合わせる
      volumes:
        - name: config-volume
          configMap:
            name: myapp-config  # configMapGenerator[].nameと合わせる

以上でConfigMapでYAMLファイルをマウントし、アプリケーションで使えるようになります。

その他

subPathを使うとどうなるか

ConfigMapでググるとよく出てくるsubPathですが、具体的なユースケースは何か説明します。

例えば、以下のように複数のファイルを持つConfigMapがあるとします。

apiVersion: v1
kind: ConfigMap
metadata:
  name: example-configmap
data:
  database.conf: |
    # Database configuration
    host: db.example.com
    port: 5432
  app.conf: |
    # Application configuration
    logging: info

このConfigMapから、特定のファイルをPodの異なるパスにマウントするには、次のようなマニフェストにします。

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  containers:
  - name: example-container
    image: nginx
    volumeMounts:
    - name: config-volume
      mountPath: /etc/app.conf
      subPath: app.conf
      readOnly: true
    - name: config-volume
      mountPath: /etc/db/database.conf
      subPath: database.conf
      readOnly: true
  volumes:
  - name: config-volume
    configMap:
      name: example-configmap

このようにsubPathを使用すると、ConfigMap内の特定のファイルだけをPodの特定のパスにマウントすることができます。

YAML||-の違い

どちらもリテラルブロック(改行などもベタ書きで書きたい)スタイルですが、

  • |
    • 最後の改行も含む
  • |-
    • 最後の改行を消す

という違いがあります。

リテラルブロックのインデント

プログラミング言語だとヒアドキュメントを表現したい時は

const hoge = `xxxx
yyyy
zzzz
`

のようにインデントせずに書くことが多いですが、YAMLの場合は

---
hoge: |
    xxxx
    yyyy
    zzzz

のようにインデントしないといけないので忘れないようにしましょう。

まとめ

ConfigMapを使ってYAMLファイルを管理することで設定の複雑性や運用負荷を下げる方法を説明しました。

参考