CUBE SUGAR CONTAINER

技術系のこと書きます。

リモートの Docker ホストでコンテナを SSH Port Forward 経由で動かす

今回は、Docker クライアントをリモートの Docker ホストに SSH Port Forward 経由で接続させてコンテナを操作する方法を試してみる。

まず、Docker クライアントの環境は次のとおり。 macOS に Docker for Mac をインストールしてある。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H2
$ docker version                  
Client: Docker Engine - Community
 Cloud integration  0.1.18
 Version:           19.03.13
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        4484c46d9d
 Built:             Wed Sep 16 16:58:31 2020
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.13
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       4484c46d9d
  Built:            Wed Sep 16 17:07:04 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.3.7
  GitCommit:        8fba4e9a7d01810a393d5d25a3621dc101981175
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

リモートの Docker ホストの環境は次のとおり。 Ubuntu 18.04 LTS に Docker をインストールしてある。

$ cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"
NAME="Ubuntu"
VERSION="18.04.4 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.4 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
$ sudo docker version
Client:
 Version:           19.03.6
 API version:       1.40
 Go version:        go1.12.17
 Git commit:        369ce74a3c
 Built:             Fri Feb 28 23:45:43 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          19.03.6
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       369ce74a3c
  Built:            Wed Feb 19 01:06:16 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.3-0ubuntu1~18.04.2
  GitCommit:        
 runc:
  Version:          spec: 1.0.1-dev
  GitCommit:        
 docker-init:
  Version:          0.18.0
  GitCommit:        

ようするに、上記の macOS を Docker クライアント、Ubuntu を Docker サーバとして動作させたい、ということ。

Docker デーモンをローカルホストの TCP:2376 で待ち受けるようにする

デフォルトでは、Docker デーモンは Unix ドメインソケット経由で制御されるように設定されている。 しかし、これだとリモートから扱う上で都合が悪い。 そこで、まずは Docker ホストのデーモンを TCP で待ち受けるように変更する。

まずは、リモートにある Docker ホストにログインする。 この段階では、シェルの操作ができさえすれば良いので、別にログインの方法は何でも構わない。

$ ssh <hostname>

はじめに、Docker の設定が入った systemd のコンフィグをバックアップしておく。

$ sudo cp /lib/systemd/system/docker.service{,.orig}

そして、次のように dockerd のオプションとしてローカルホストの 2376 ポートで待ち受けるようにオプションを追加する。

$ diff -u /lib/systemd/system/docker.service{.orig,}
--- /lib/systemd/system/docker.service.orig 2020-10-10 14:22:10.482817997 +0000
+++ /lib/systemd/system/docker.service  2020-10-10 14:22:56.297714001 +0000
@@ -11,7 +11,7 @@
 # the default is not to use systemd for cgroups because the delegate issues still
 # exists and systemd currently does not support the cgroup feature set required
 # for containers run by docker
-ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
+ExecStart=/usr/bin/dockerd -H fd:// -H tcp://127.0.0.1:2376 --containerd=/run/containerd/containerd.sock
 ExecReload=/bin/kill -s HUP $MAINPID
 TimeoutSec=0
 RestartSec=2

ちなみに、上記は APT でインストールしたパッケージが管理しているファイルなので、直接編集するのはあまりお行儀が良くない。

$ dpkg-query -L docker.io | grep systemd
/lib/systemd
/lib/systemd/system
/lib/systemd/system/docker.service
/lib/systemd/system/docker.socket

編集したらサービスを再起動する。

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

これで Docker デーモンがローカルホストの TCP:2376 ポートで待ち受けるようになる。

$ ss -tlnp | grep 2376
LISTEN0      128                             127.0.0.1:2376        0.0.0.0:*

これでリモートの Docker ホストの準備は整った。

クライアントからリモートの Docker デーモンを操作する

ここからは Docker クライアントの操作になる。

リモートの Docker ホストに、SSH Port Forward を有効にしてあらためてログインする。

$ ssh -L 2376:localhost:2376 <hostname>

これで、ローカルの TCP:2376 ポートにアクセスすると、リモートの TCP:2376 ポートにつながることになる。

$ lsof -i -P | grep -i listen | grep 2376
ssh       7028 amedama    5u  IPv6 0xda00d64e20c3a761      0t0  TCP localhost:2376 (LISTEN)
ssh       7028 amedama    6u  IPv4 0xda00d64e24baa8d1      0t0  TCP localhost:2376 (LISTEN)

あとは、シェル変数の DOCKER_HOSTtcp://127.0.0.1:2376 を指定して docker コマンドを使うだけ。

$ DOCKER_HOST=tcp://127.0.0.1:2376 docker version
Client: Docker Engine - Community
 Cloud integration  0.1.18
 Version:           19.03.13
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        4484c46d9d
 Built:             Wed Sep 16 16:58:31 2020
 OS/Arch:           darwin/amd64
 Experimental:      false

Server:
 Engine:
  Version:          19.03.6
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.17
  Git commit:       369ce74a3c
  Built:            Wed Feb 19 01:06:16 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.3-0ubuntu1~18.04.2
  GitCommit:        
 runc:
  Version:          spec: 1.0.1-dev
  GitCommit:        
 docker-init:
  Version:          0.18.0
  GitCommit:        

上記から、クライアントとサーバで Docker のバージョンが異なっていることが確認できる。

また、シェル変数を有効にしたときとしないときで uname -a の結果が変わっていることもわかる。 コンテナはホストのカーネルを共有するため、このようなことになる。

$ docker container run -it alpine:latest uname -a
Linux 829155145f04 4.19.76-linuxkit #1 SMP Tue May 26 11:42:35 UTC 2020 x86_64 Linux
$ DOCKER_HOST=tcp://127.0.0.1:2376 docker container run -it alpine:latest uname -a
Linux f4981d8001f3 4.15.0-111-generic #112-Ubuntu SMP Thu Jul 9 20:32:34 UTC 2020 x86_64 Linux

これで、ローカルの docker コマンドを使って、リモートにある Docker ホスト上のコンテナを操作できるようになった。

注意点

ただし、このやり方にはいくつか注意点もある。

たとえば、このやり方ではコンテナが動いているのはあくまでリモートのホストになる。 そのため、ホストのボリュームをコンテナでマウントしようとしたときに使われるディレクトリはリモートのものになる。

確認しておこう。 まずはリモートにある Docker ホストにディレクトリを作ってファイルを用意する。

$ mkdir -p /tmp/mnt
$ echo "Remote" > /tmp/mnt/loc.txt

そして、ローカルの Docker クライアントにも同じようにディレクトリを作って区別できるようにファイルを用意する。

$ mkdir -p /tmp/mnt
$ echo "Local" > /tmp/mnt/loc.txt

上記で作ったディレクトリをマウントしたコンテナを起動してみよう。

$ export DOCKER_HOST=tcp://127.0.0.1:2376
$ docker container run \
  -v /tmp/mnt:/mnt \
  -it ubuntu:latest \
  bash

そして、ファイルの中身を確認する。

# cat /mnt/loc.txt 
Remote

うん、リモートだね。

あとは、受け付けるアドレスをローカルホストに絞っているとはいえ、これだとホストにログインできるユーザは誰でも Docker が使える。 もし、それが好ましくない状況であれば、以下のようにクライアントからのアクセス制御をした方が良いと思われる。

docs.docker.com

いじょう。