Docker Desktop ショックがあって以来、なるべく少ない制約の下で docker を動かせる環境を色々調べています。そんな中で見つけた1つの方法が Docker in Docker(以下、DinD)です。

DinD はその名前の通りで、コンテナクラスタ(親)の中で動くコンテナ(子)として Docker サーバー&クライアントを動かす、というものです。多くの場合、「親コンテナ=Kubernetes」となることが多いので、正確には "Docker in Kubernetes" と表現すべきかもしれませんが、広い意味(?)での "Docker in Docker" ということだと思います。

単なる運用環境の観点だと「Kubernetes が使えるなら Docker は要らないのでは?」と思うかもしれませんが、特に開発段階だとコマンドとしての docker CLI を使いたいことがあったり、プロダクション環境とは別の小さな開発環境を Docker で作っておけると便利なことも多くあります。そういった意味で Kubernetes があってもそれとは別に Docker 環境が欲しくなることがあるのでした。


IBM Cloud からも IKS(IBM Kubernetes Services)ROKS(Redhat Openshift Kubernetes Services) が提供されていて、これらの環境でも DinD を使うことができます。特に今回は IKS の 30 日無料版を使って DinD 環境を作って使う手順を紹介します。なお、IKS 30 日無料版の制約事項や環境準備手順についてはこちらの過去記事を参照ください:
http://dotnsf.blog.jp/archives/1079287640.html


↑この記事最後の "$ kubectl get all" コマンドが成功するまでになれば準備完了です。なお Kubernetes クラスタに対して "$ kubectl get all" コマンドが実行できるようになっていれば IKS 以外の他の Kubernetes クラスタ環境でも以下同様にして DinD 環境を作ることができると思います。


【(IKS に) DinD の Pod を作る】
では早速 DinD 環境を作ります。DinD 環境といっても大それたものではなく、コンテナ的に言えば「DinD の Pod を1つ作る」ことになります。そしてその1つの Pod の中に「Docker デーモンのコンテナ」と「Docker クライアントのコンテナ」を1つずつ作ります(つまり1つの Pod の中で2つのコンテナを動かします)。

実際の作成に関しても、以下の内容のマニフェストファイルを用意するだけです(この内容を dind.yml という名前で保存してください):
apiVersion: v1
kind: Pod
metadata:
  name: dind
spec:
  containers:
    - name: docker
      image: docker:19.03
      command: ["docker", "run", "nginx:latest"]
      env:
        - name: DOCKER_HOST
          value: tcp://localhost:2375
    - name: dind-daemon
      image: docker:19.03-dind
      env:
        - name: DOCKER_TLS_CERTDIR
          value: ""
      resources:
        requests:
          cpu: 20m
          memory: 512Mi
      securityContext:
        privileged: true

そして、kubectl コマンドで以下を実行してマニフェストを適用します:
$ kubectl apply -f dind.yml

※または dind.yml を用意しなくても、このコマンドでも同じ結果になります:
$ kubectl apply -f https://raw.githubusercontent.com/dotnsf/dind_iks/main/dind.yml


初回のみイメージのダウンロードで少し時間がかかりますが、しばらく待つと dind という名前の Pod が1つ(コンテナは docker dind-daemon の2つ)起動します:
$ kubectl get pods

NAME READY STATUS RESTARTS AGE dind 2/2 Running 2 101m $ kubectl describe pod dind Name: dind Namespace: default Priority: 0 Node: 10.144.222.147/10.144.222.147 Start Time: Sun, 20 Feb 2022 21:52:46 +0900 Labels: Annotations: cni.projectcalico.org/containerID: 0c110243107d25c9e27b82202871504047d9ac691ad294d8f89bb1a9b114ca5e cni.projectcalico.org/podIP: 172.30.216.78/32 cni.projectcalico.org/podIPs: 172.30.216.78/32 kubernetes.io/psp: ibm-privileged-psp Status: Running IP: 172.30.216.78 IPs: IP: 172.30.216.78 Containers: docker: Container ID: containerd://0dbd05bc4171f96caaa7601a10e1b2f853511b4ed7087ab3958c016024c65f1c Image: docker:19.03 Image ID: docker.io/library/docker@sha256:ea1f0761c92b600417ad14bc9b2b3a30abf8e96e94895fee6cbb5353316f30b0 Port: Host Port: Command: docker run nginx:latest State: Running Started: Sun, 20 Feb 2022 22:53:02 +0900 Last State: Terminated Reason: Completed Exit Code: 0 Started: Sun, 20 Feb 2022 21:53:08 +0900 Finished: Sun, 20 Feb 2022 22:53:01 +0900 Ready: True Restart Count: 2 Environment: DOCKER_HOST: tcp://localhost:2375 Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gz5fb (ro) dind-daemon: Container ID: containerd://5a6a15eefa9ebdf8a0dcc825f1c87fa4a5a37daab005a1d83e5df8f9a33ff7bb Image: docker:19.03-dind Image ID: docker.io/library/docker@sha256:c85365ad08c7f6e02ac962a8759c4a5b8512ea5c294d3bb9ed25fca52e9e22e5 Port: Host Port: State: Running Started: Sun, 20 Feb 2022 21:53:07 +0900 Ready: True Restart Count: 0 Requests: cpu: 20m memory: 512Mi Environment: DOCKER_TLS_CERTDIR: Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-gz5fb (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: kube-api-access-gz5fb: Type: Projected (a volume that contains injected data from multiple sources) TokenExpirationSeconds: 3607 ConfigMapName: kube-root-ca.crt ConfigMapOptional: DownwardAPI: true QoS Class: Burstable Node-Selectors: Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 600s node.kubernetes.io/unreachable:NoExecute op=Exists for 600s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Created 43m (x3 over 103m) kubelet Created container docker Normal Started 43m (x3 over 103m) kubelet Started container docker Normal Pulled 43m (x2 over 103m) kubelet Container image "docker:19.03" already present on machine d


DinD 環境はあっけなく完成しました。


【(IKS の) DinD 内の docker を操作する】
次に完成した DinD 環境を実際に CLI で使ってみます。そのためにまずは Docker クライアントが使える環境のシェルにアタッチする必要があります。Docker クライアントは docker という名前のコンテナで動いていることが分かっているので、以下のコマンドを実行します:
$ kubectl exec -it dind -c docker -- /bin/sh

/ #

プロンプトが "/ #" という記号に変わればアタッチ成功です。ここからは docker CLI コマンドが実行できます。試しに "docker version" コマンドを実行するとクライアント&サーバー双方の docker バージョン情報(下の例ではどちらも "19.03.15")を確認できます:
/ # docker version

Client: Docker Engine - Community
 Version:           19.03.15
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        99e3ed8
 Built:             Sat Jan 30 03:11:43 2021
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.15
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       99e3ed8
  Built:            Sat Jan 30 03:18:13 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.3.9
  GitCommit:        ea765aba0d05254012b0b9e595e995c09186427f
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683


実際にサーバーイメージをデプロイして動作確認してみましょう。というわけで、まずは nginx イメージを 8000 番ポートでデプロイしてみます:
/ # docker run -d --name ningx -p 8000:80 nginx

成功したら早速アクセスして動作確認を・・・と思ったのですが、この docker コンテナには HTTP クライアントが curl 含めてインストールされていないようでした。というわけで動作確認用のコマンドもインストールしておきます。とりあえず curl と w3m あたりでいいですかね。。:
/ # apk add --update curl

/ # apk add --update w3m

インストールが成功したら、まずは curl で http://localhost:8000/ にアクセスしてみます:
/ # curl http://localhost:8000/

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

うぉっ、と。少なくとも HTTP サーバーとして動いているらしいことは確認できたのですが、これだとちょっと見にくいですね。というわけで、先程一緒にインストールした w3m で確認してみます:
/ # w3m http://localhost:8000/

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

2022022101


かろうじて Nginx のトップ画面らしきものが確認できました。というわけで一応動いていると思います。


【まとめ】
というわけで DinD が IBM Cloud の 30 日無料版 Kubernetes クラスタ環境でも動かせることが確認できました。

とはいえ、もともとは Docker Desktop の代替になるような Docker 環境を探していたことに立ち返ると、この(クライアント側のような) CLI だけの Docker 環境はどうしても使い道が限られてしまうように感じます。ある程度、Docker を理解している人向けに、CLI だけで完結する使いみちであればなんとか、といったところでしょうか。


本当はここで作った Pod を外部に EXPOSE できるといいんですが、自分で試行錯誤している限りではまだうまく行ってません。もし方法をご存じの方がいらっしゃったら是非教えてください。


(2022/02/21 追記ここから)
試行錯誤の中で外部公開する方法がわかりました。

上述の "$ kubectl apply -f ..." コマンドを実行する箇所の内容を以下のように変更してください:
$ kubectl apply -f https://raw.githubusercontent.com/dotnsf/dind_iks/main/dind_expose.yml

なお、ここで指定している dind_expose.yml の内容は以下のようなものです。Pod を Deployment に書き換えた上で Service オブジェクトを追加して、8000 番ポートでの待受けを 30800 番ポートから転送するように(この後の作業で 8000 番ポートで待ち受けるアプリケーションをデプロイする想定で)あらかじめ公開しています:
apiVersion: v1
kind: Service
metadata:
  name: dind
spec:
  selector:
    app: dind
  ports:
  - port: 8000
    name: port8000
    protocol: TCP
    targetPort: 8000
    nodePort: 30800
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dind
spec:
  selector:
    matchLabels:
      app: dind
  replicas: 1
  template:
    metadata:
      labels:
        app: dind
    spec:
      containers:
        - name: docker
          image: docker:19.03
          command: ["docker", "run", "nginx:latest"]
          env:
            - name: DOCKER_HOST
              value: tcp://localhost:2375
        - name: dind-daemon
          image: docker:19.03-dind
          env:
            - name: DOCKER_TLS_CERTDIR
              value: ""
          resources:
            requests:
              cpu: 20m
              memory: 512Mi
          securityContext:
            privileged: true

このコマンドの後、"$ kubectl get all" を実行すると以下のような結果になります:
$ kubectl get all
NAME READY STATUS RESTARTS AGE pod/dind-7d8546bc8-fxbhw 2/2 Running 1 19s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/dind NodePort 172.21.8.233 8000:30800/TCP 21s service/kubernetes ClusterIP 172.21.0.1 443/TCP 3d10h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/dind 1/1 1 1 20s NAME DESIRED CURRENT READY AGE replicaset.apps/dind-7d8546bc8 1 1 1 21s

以前の方法では Pod 名は "dind" 固定だったのですが、ここでは "dind-" に続けてランダムな文字列が付与されています(上例では "dind-7d8546bc8-fxbhw" となっています)。この Pod 名を指定して以下を実行して docker コンテナのシェルに接続します:
$ kubectl exec -it dind-7d8546bc8-fxbhw -c docker -- /bin/sh

/ #

この後は以前の方法と同様にして docker コンテナ内で NGINX を 8000 番ポートで起動します:
/ # docker run -d --name ningx -p 8000:80 nginx

これで以前と同様に k8s のワーカーノード内で(NodePort サービスを使って) NGINX が 8000 番ポートで起動します。ただ今回は Service オブジェクトで 8000 番リクエストを 30800 番ポートで外部公開しているので、 http://(ワーカーノードのパブリックIPアドレス):30800/ にアクセスすればクラスタ外部からでもこの NGINX に接続できるようになっています。


ワーカーノードのパブリック IP アドレスは IBM Cloud の IKS 環境であれば、IBM Cloud ダッシュボード画面から確認することができます(この例では "169.51.206.71" となっています):
2022022102


というわけで、改めてウェブブラウザで http://169.51.206.71:30800/ にアクセスしてみると、、、期待通りの画面が表示されました! IBM Cloud の30日無料版 Kubernetes クラスタ環境で構築した DinD のコンテナを外部に公開することができることが確認できました:
2022022100


というわけで、30 日間無料の IBM Cloud Kubernetes 環境を使って、Docker および Kubernetes クラスタの実行環境を構築することができました。

(2022/02/21 追記ここまで)