Dockerの便利なコマンドを使ってみよう

これは [フェンリル デザインとテクノロジー Advent Calendar 2022](https://adventar.org/calendars/8019) 4日目の記事です。

![shinkai_makkoukujira.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237636/67308efa-f0a7-6cef-1c8d-4618c622bc39.png)

Dockerを使う方の中には `build` や `up` コマンドくらいしか使ったことない方も大勢いるのではないでしょうか。Dockerのコマンド一覧のドキュメントを見てみると、見たことすら無いものもたくさんあると思います。

今回はたくさんある機能の中から、リモートのDockerを便利に使うためのコマンドを2つ紹介したいと思います。

## 作業環境

以降の作業は以下の環境で実行したものです。

* PC : MacBook Pro 13-inch 2020
* OS : macOS Ventura 13.0.1
* Docker Engine : `20.10.21`

## docker save / docker load

ビルドしたDockerイメージをリモートのサーバーに持っていきたいけど、レジストリを用意するほどでもない…。そんなときにはこのコマンドを使ってみましょう。

コマンドの使い方は以下のとおりです。

```
$ docker save [OPTIONS] IMAGE [IMAGE...]
```

|Name, shorthand|Default|Description|
|---|---|---|
|--output , -o||Write to a file, instead of STDOUT|

```
$ docker load [OPTIONS]
```

|Name, shorthand|Default|Description|
|---|---|---|
|--input , -i||Read from tar archive file, instead of STDIN|
|--quiet , -q||Suppress the load output|

> 引用元 : [docker save](https://docs.docker.com/engine/reference/commandline/save/) / [docker load](https://docs.docker.com/engine/reference/commandline/load/)

事前に Hello World と出力するだけのDockerイメージを作成しておきます。

```dockerfile:Dockerfile
FROM alpine:latest
CMD ["echo","Hello World"]
```

ビルドと実行をします。

```bash:bash
$ docker build -q -t helloworld .
sha256:b8e5160def5593aab3002009595fcdd031d249bbc240f26260090f88bfe36c2f
$ docker image ls helloworld
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
helloworld   latest    b8e5160def55   13 days ago   7.05MB
$ docker run --rm helloworld
Hello World
```

Hello World と出力されています。

ではこのイメージを `docker save` コマンドでアーカイブファイルとして出力してみましょう。

```bash:bash
$ docker save helloworld > helloworld.tar
$ ls -lah helloworld.tar 
-rw-r--r--  1 yoneda  staff   7.0M 12  4 00:00 helloworld.tar
```

`tar` ファイルとして保存されました！

本当に保存されたか確認するために、先程ビルドしたDockerイメージを削除してみましょう。

```bash:bash
$ docker rmi helloworld
Untagged: helloworld:latest
Deleted: sha256:b8e5160def5593aab3002009595fcdd031d249bbc240f26260090f88bfe36c2f
```

イメージの削除ができたので、出力されたアーカイブファイルから `docker load` コマンドでイメージを読み込ませてみましょう。

```bash:bash
$ docker load -q < helloworld.tar 
Loaded image: helloworld:latest
$ docker image ls helloworld
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
helloworld   latest    b8e5160def55   13 days ago   7.05MB
```

一覧に `helloworld` のイメージが復活していますね。では実行してみましょう。

```bash:bash
$ docker run --rm helloworld
Hello World
```

Hello World が出力されました！！

このように `docker save` / `docker load` コマンドを使うことで、レジストリを用意することなくDockerイメージをファイルとして自由に持ち運ぶことができます。`scp` コマンドを利用すれば別のサーバーに持っていくこともできますね。

注意点としては、Docker Hub等のレジストリを使う場合にはイメージを複数に分割して並列でアップロード・ダウンロードが実行されるのですが、`docker save` / `docker load` の場合は単一のファイルとして転送することになるため、大きなイメージになればなるほど転送時間が長くなってしまいます。gzipで圧縮をするなどの工夫をしましょう。

## docker context

リモートのサーバー上で動いているDockerを操作するために、まずはSSHで接続してから、ディレクトリを移動して、dockerコマンドを打ち込んで…。こんな操作を頻繁にしている人も多いのではないでしょうか。そんなときにはこのコマンドを使ってDockerをリモートで操作してみましょう。

まずはリモートで動作するDockerを準備します。ここでは以下のようなAWS EC2を利用します。

* Instance type : t3.medium
* AMI ID : `ami-072bfb8ae2c884cc4` (Amazon Linux 2 Kernel 5.10)
* Public IP address : `54.248.12.145`

注意点としては、外部からTCP 2375番ポートを経由してDockerを操作することになるため、何らかの手段で経路を確保する必要がある点です。EC2の場合はSystems ManagerのSession Managerを使って2375番ポートをローカルにポートフォワードさせる事を推奨します。

> 参考URL : [SSM Session Managerを使ってポートフォワードする | DevelopersIO](https://dev.classmethod.jp/articles/ssm-session-manage-port-forwarding/)

今回は作業を簡易的にするため、EC2インスタンスをパブリックネットワークに作成することでパブリックIPを割り当て、セキュリティグループで該当ポート経由の通信を許可するように設定します。

![スクリーンショット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237636/fc6dcfc4-1602-2dfe-297c-c3aa109d8104.png)

EC2インスタンスの起動が完了したらDockerのインストールと起動をします。

```bash:bash (remote)
$ sudo amazon-linux-extras install docker
Installing docker
...
（省略）
...
$ sudo systemctl start docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: https://docs.docker.com
$ sudo docker -v
Docker version 20.10.17, build 100c701
```

次にDaemon socket optionを使い、TCP 2375番で待ち受ける設定を追加します。`/var/lib/systemd/system/docker.service` の `ExecStart` で始まる行を確認します。

```text:/var/lib/systemd/system/docker.service (remote)
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock $OPTIONS $DOCKER_STORAGE_OPTIONS $DOCKER_ADD_RUNTIMES
```

ここに `-H tcp://0.0.0.0:2375` を追加しましょう。

> 公式ドキュメント : [Daemon CLI (dockerd) / Daemon socket option](https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option)

```text:/var/lib/systemd/system/docker.service (remote)
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 --containerd=/run/containerd/containerd.sock $OPTIONS $DOCKER_STORAGE_OPTIONS $DOCKER_ADD_RUNTIMES
```

設定を変更したら、Dockerを再起動します。

```bash:bash (remote)
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
```

これでリモート側の準備は完了です。ここからはローカルPC側の設定をしていきます。 `docker context create` コマンドでリモートのDockerを登録しましょう。

コマンドの使い方は以下のとおりです。

```bash
$ docker context create [OPTIONS] CONTEXT
```

|Name, shorthand|Default|Description|
|---|---|---|
|--default-stack-orchestrator| |(deprecated)Default orchestrator for stack operations to use with this context (swarm\|kubernetes\|all)|
|--description|	|Description of the context|
|--docker| |set the docker endpoint|
|--from| |create context from a named context|
|--kubernetes| |(deprecated)(Kubernetes) set the kubernetes endpoint|

> 引用元 : [docker context create](https://docs.docker.com/engine/reference/commandline/context_create/)

`--docker` オプションを利用して、先程設定したリモートで動いているDockerのエンドポイントを指定します。

```bash:bash (local)
$ docker context create --docker host=tcp://54.248.12.145:2375 remote
remote
Successfully created context "remote"
```

`remote` という名前で作成が完了しました。それでは `--context` オプションを付けてローカルからリモートのDockerを操作し、適当なイメージをPullしてみましょう。

```bash:bash (local)
$ docker --context remote pull docker/whalesay
Using default tag: latest
latest: Pulling from docker/whalesay
Image docker.io/docker/whalesay:latest uses outdated schema1 manifest format. Please upgrade to a schema2 image for better future compatibility. More information at https://docs.docker.com/registry/spec/deprecated-schema-v1/
e190868d63f8: Pull complete 
909cd34c6fd7: Pull complete 
0b9bfabab7c1: Pull complete 
a3ed95caeb02: Pull complete 
00bf65475aba: Pull complete 
c57b6bcc83e3: Pull complete 
8978f6879e2f: Pull complete 
8eed3712d2cf: Pull complete 
Digest: sha256:178598e51a26abbc958b8a2e48825c90bc22e641de3d31e18aaf55f3258ba93b
Status: Downloaded newer image for docker/whalesay:latest
docker.io/docker/whalesay:latest
```

Pullが完了しました。リモート側のターミナルに接続してイメージの確認をしてみましょう。

```bash:bash (remote)
$ sudo docker image ls
REPOSITORY        TAG       IMAGE ID       CREATED       SIZE
docker/whalesay   latest    6b362a9f73eb   7 years ago   247MB
```

先程ローカルから操作してPullしたイメージが保存されていることが確認できました。

## 応用してみる

今回紹介した `docker save` / `docker load` と `docker context` ですが、組み合わせて使うことができます。前半に `docker save` で出力した `helloworld.tar` を、`--context` オプションを付けてリモートのDockerに `docker load` で読み込ませてみましょう。

```bash:bash (local)
$ docker --context remote load < helloworld.tar 
ded7a220bb05: Loading layer [==================================================>]  7.338MB/7.338MB
Loaded image: helloworld:latest
$ docker --context remote image ls helloworld
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
helloworld   latest    b8e5160def55   13 days ago   7.05MB
$ docker --context remote run --rm helloworld
Hello World
```

ビルドしたイメージをリモートのDockerに転送して実行することができました。簡易的なデプロイが実現できましたね。

## まとめ

たくさんあるDockerの機能の中から、今回は `docker save / docker load` と `docker context` を紹介しました。あまり使っている例を見かけないコマンドたちではありますが、いざ使ってみると「こんなことにも使えるのでは…？」となったのではないでしょうか。

これ以外にもDockerには様々なコマンドが用意されているので、どんどん試してDockerマスターを目指しましょう！！！