KubernetesのPodでDockerコンテナを実行する

KubernetesのPodでDockerコンテナを実行する方法を紹介します。

Dockerコンテナ内でコンテナを起動する方法のことをDinD (Docker inside Docker)などと言われていますが、同じことをKubernetesのPodで実行するには少し工夫する必要があります。
アプローチとして2種類のやり方があり、双方にメリットとデメリットがあるため状況に合わせて適切なものを選択する必要があります。

アプローチ

1つめのやり方はPodが動いているホストマシンのDockerデーモンを共有する方法で、DooD (Docker outside of Docker)とよばれるものをPodに適応させたパターンです。
2つめのやり方はPod内でDockerデーモンを動かす、DinD (Docker inside Docker)と呼ばれるものを適応させたパターンです。

それぞれの方法のやり方とメリット・デメリットについて紹介します。

DooDパターン

DooDパターンをk8sに適応させるのは簡単で、ホストの /var/run を共有してDockerデーモンにアクセスできるようにするだけです。

k8sマニフェストファイルで表すとこのようになります。

apiVersion: v1
kind: Pod
metadata:
  name: dood
spec:
  containers:
    - name: docker
      image: docker:19.03
      command: ["docker", "run", "nginx:latest"]
      volumeMounts:
        - mountPath: /var/run
          name: docker-sock
  volumes:
    - name: docker-sock
      hostPath:
        path: /var/run

DooDパターンの場合、コンテナ内で作成されたコンテナはホストが管理するその他のコンテナと同列の扱いになることに注意する必要があります。
つまり、コンテナを作成したPodと同列の扱いのコンテナとなります。
しかしながらPod内で作成したコンテナはk8sの管理下にはありません。
また、もし他にもコンテナを作成するPodが存在した場合は、コンテナ名などのリソースが競合する可能性があるので注意しなければいけません。
偶然発生する可能性は低いですが、k8sのリソースと競合する可能性もあります。

コンテナのポートをマッピングする場合も注意が必要で、デーモンが動いているのはホストマシンになるためポートマッピングされるのもホストになります。
そのためコンテナを作成したPodのIPで作成したコンテナにはアクセスできません。ホストのIPでアクセスする必要があります。

また、k8sの管理から外れるため、Podに指定したリソース制限が適用されません。
さらに -d としてコンテナを起動した場合はPodの削除時に作成したコンテナが削除されません。

このように様々な落とし穴があるため、k8sのPodの場合はDooDパターンはオススメしません。次に解説するDinDパターンを使うべきです。

メリット

  • 特別な権限を与える必要がない
  • コンテナを動かしたいイメージにDockerコマンドをインストールするだけで動かすことができる

デメリット

  • Podの中ではなくホストマシンの中にコンテナが作成される
  • k8sの管理下からはずれたところにコンテナが作成される
  • ホストでリソースの競合が発生する可能性がある

DinDパターン

DinDパターンはDooDパターンと比べると少しだけ複雑です。
dockerコマンドを実行するコンテナのサイドカーとしてDockerデーモンを動かし、dockerコマンドがサイドカーに対してアクセスするように設定します。

以下がその設定をしたマニフェストファイルです。

apiVersion: v1
kind: Pod
metadata:
  name: dind
spec:
  containers:
    - name: docker
      image: docker:18.09
      command: ["docker", "run", "nginx:latest"]
      env:
        - name: DOCKER_HOST
          value: tcp://localhost:2375
    - name: dind-daemon
      image: docker:18.09-dind
      resources:
        requests:
          cpu: 20m
          memory: 512Mi
      securityContext:
        privileged: true

Dockerデーモンを動かすコンテナは公式のイメージがあるのでそれを使います。この公式イメージは2375ポートでDockerのREST APIを公開しています。
そこに対してDocker CLIがアクセスするように環境変数 DOCKER_HOST を設定します。

DinDパターンでは作成したコンテナはDockerデーモンが動くサイドカーコンテナの中に作成されるため、Podに設定したCPUやメモリなどのリソースの制限を受けた状態でコンテナを作成できます。
また、このPodを削除したときに作成したコンテナも同時に削除されます。
ポートマッピングを使用したときもDockerデーモンが動いているネットワーク、つまりはPodのネットワークに対して行われるため、ホストのリソースを汚すことなくPodのIPでアクセスできます。
同様にコンテナ名などのリソース競合もPod内でしかおこりません。

ただし一つだけ意識しなければならないことがあり、Dockerデーモンが動くコンテナは privileged 権限で実行しなければいけません。ここが障害にならない限りDinDパターンを選択することをオススメします。

メリット

  • Pod内にコンテナを作成できる
  • ホストに影響を与えることなくPod内で完結する
  • Podの設定(リソースやネットワーク)を引き継ぐことができる

デメリット

  • サイドカーコンテナが1つ増える
  • privilegedでコンテナを動かす必要がある

まとめ

privilegedで動かしても問題ない環境であればDinDパターンがオススメです。
DooDを使う場合はリソース競合など、ホスト側でコンテナが動いていることを忘れずに使う必要があります。