Kubernetesでコンテナに設定ファイルを追加したい場合にDockerイメージを作成せずに運用していくtips

こんにちは、スタディサプリ ENGLISH SREグループの大島です。

本記事では、設定ファイルをConfigMapとして作成して起動時にvolumeとしてmountすることで、わざわざDockerイメージをビルドしてpushする運用をしなくてもよくなるというtipsを紹介します。
スタディサプリ ENGLISHでは、EKS+Argo CD+Kustomizeでのgitopsを採用しており、その場合の設定の仕方について説明していきます。

設定ファイルをinjectしたいモチベーションの背景

今回、説明する題材について簡単に説明します。
スタディサプリ ENGLISHでは、監視システムとして、Datadogを利用しています。
DatadogのagentはDockerコンテナとして起動できるようになっており、設定ファイルを追加すると、追加のメトリクスを取得してくれるという仕組みがあり、その際に設定ファイルをコンテナに含めるためにDocker Imageをビルドしていました

例えば、MySQLのintegrationはコンテナ内にもともと存在するexampleファイルを変更することで、自動的にMySQLのmetricsを取得してくれるようになります。
設定ファイルはこのようなパスでexampleとしてyamlファイルが用意されています。

$ docker container run -t datadog/agent ls /etc/datadog-agent/conf.d/mysql.d/
conf.yaml.example

ファイルの中身はこのようになっています。よくある設定ファイルの形式だと思います。

init_config:
instances:
    ## @param server - string - required
    ## MySQL server to connect to.
    ## NOTE: Even if the server name is "localhost", the agent connects to MySQL using TCP/IP, unless you also
    ## provide a value for the sock key (below).
    #
  - server: localhost
    ## @param user - string - required
    ## Datadog Username created to connect to MySQL.
    #
    user: datadog
~略~

このファイルを変更して、イメージに含めるには以下のようにDockerfileを書いてビルドするしかないと思っていました。

FROM datadog/agent
COPY conf.yaml /etc/datadog-agent/conf.d/mysql.d/
# start.shは自分たちで作る
# 環境変数を利用してconfigを環境ごとに起動時に切り替える置換処理が入っている
CMD ["/start.sh"]

この方法でも特に問題はないのですが、我々の場合、こういった頻繁にデプロイしないようなメインのアプリケーションではない補助的なイメージのためのCI/CDを組んでこなかったというのがあります。
CI/CDが整っていないと、イメージの更新なども放置しがちになってしまうので、configMapをinjectする方法で管理するようにしてみたという背景です。
また、上記のDockerfileのコメントにあるように、自前の起動用のシェルスクリプトを用意しないといけないというのも手間でした。

これらの問題をKustomizeとConfigMapを使って解決することができます。

ConfigMapはファイルから生成できる

まず、ConfigMapはliteralだけでなく、ファイルからも作成することができます。ファイルの中身も特定のフォーマットである必要もなく、そのまま作成できます。

kubectlで言えば次のコマンドでこういうコマンドに相当します。

kubectl create cm  mysql-conf --from-file conf.yaml

ただ、Argo CDを導入しているので、kubectlで作成するのではなく、宣言的にyamlで記述しないといけません。

KustomizeでConfigMapを生成する

我々の場合、Argo CDとKustomizeを組み合わせているので、Kustomizeの機能で動的に生成するようにします。
Kustomizeには、ConfigMapを生成するConfigMapGeneratorという仕組みがあります。

以下のように書くことができます。

configMapGenerator:
- name: mysql-conf
  files:
  - dd-agent/conf.d/mysql.d/conf.yaml

ConfigMapをmountする

ConfigMapとして設定ファイルが作成されるので、このConfigMapをpodから読み込めるようにする必要があります。
これは、volumesvolumemMountsを利用して記述できます。

以下がDeploymentのyamlの設定です。
このように、DockerHubにおいてあるオフィシャルイメージをそのまま使って、必要なconfigだけを追加することができています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dd-agent
  labels:
    app: dd-agent
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dd-agent
  template:
    spec:
      containers:
        - name: dd-agent
          image: datadog/agent # officialイメージをそのまま使っている
          imagePullPolicy: Always
          volumeMounts:
            - name: mysql-conf
              mountPath: /etc/datadog-agent/conf.d/mysql.d # configMapで追加した設定ファイルをmountする
      volumes:
        - name: mysql-conf
          configMap:
            name: mysql-conf
            items:
              - key: conf.yaml
                path: conf.yaml
                mode: 0644 # 設定ファイルの権限

configMapGeneratorを使っているので、kustomize buildすると以下のようにhashがsuffixにきます
なぜsuffixがつくのかということについては、kustomizeの公式documentをご覧ください。

      volumes:
      - configMap:
          items:
          - key: conf.yaml
            mode: 420
            path: conf.yaml
          name: mysql-conf-12345abcde # hashがgeneratedされる
        name: mysql-conf

最終的な構成

以下のような構成でファイルを置くことで、gitopsで設定をinjectすることができます。
設定ファイル(conf.yaml)内には、環境ごとの設定なども入るので、overlaysで各環境ごとに用意してあげれば、環境ごとにイメージをビルドしないといけないということもないようにすることができます。

manifests
├── dd-agent
│   ├── deployment.yaml
│   └── kustomization.yaml
└── overlays
    └── production
        ├── dd-agent
        │   ├── conf.d
        │   │   └── mysql.d
        │   │       └── conf.yaml # 環境ごとの設定ファイルが入る
        │   └── deployment.yaml
        └── kustomization.yaml

[おまけ]はまったtips

1. volumeをmountしたときのファイルのownerはroot:rootになる

はまったという程でもないですが、mountする際のファイルの権限はroot:rootになるようです。
今回のdatadog/agentは設定ファイルのownerが異なっていても、readさえできればよいので問題にはなりませんでしたが、ownerが起動プロセスと同じである必要がある場合は、注意が必要です。

2. volumesで指定するmodeはkustomize buildすると10進数になるけど気にしない

公式referenceにも先頭に0をつけて、8進数で書くようにという指定があります。

Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.

      volumes:
        - name: mysql-conf
          configMap:
            name: mysql-conf
            items:
              - key: conf.yaml
                path: conf.yaml
                mode: 0644 # 設定ファイルの権限

が、kustomize buildすると、10進数に変換されて420とoutputされます。えっ、この数字どこからきたの・・と0644から0を消してみると、今度は、must be a number between 0 and 0777 (octal)というエラーになり、ハマってしまいましたが、結論としては、気にせずそのままでOKということです。

こちらのissueのコメントで、8進数を10進数に変換しても数値としては、同じでしょと書いてあり、確かに!!となりました。そしてファイルの権限ってそもそも2進数を8進数とした表現だったんだということに今更気づきました・・w

3. configMapGeneratorはnamespaceを揃えないとhashがつかない

こちらは、kustomizeの仕様かと思いますが、deploymentのvolumesとconfigMapGeneratorでnamespaceが揃っていないと、kustomize buildしてもconfigMapへのhashがつかないため、起動時にエラーになるという問題がありました。

これらは、明示的にnamespaceを指定してあげればhashがつくようになりました。

対応としては以下のどちらかのパターンで解決できます。

A. kustomization.yamlでnamespaceを指定する

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
+ namespace: kube-system

B. configMapGeneratorとDeploymentでnamespaceを揃えるようにする

 configMapGenerator:
 - name: mysql-conf
+  namespace: kube-system
   files:
   - dd-agent/conf.d/mysql.d/conf.yaml
関連職種の採用情報
詳しくはこちら