Dockerコンテナを持ち歩こう(Portable Docker)

## 概要

USBメモリを使い、管理者権限一切不要でWindowsPCがあればどこでもDockerコンテナを動かせる環境を構築します。



## USBメモリについて

USB3.0対応の速度の速いものを選ばないと書き込み速度に引っ張られて動作が遅くなります。

CrystalDiskMarkのランダム書き込み(右下2つ)の値が1.0を超えていないと辛いと思います(※体感には個人差があります)

ちなみに自分はI-O DATAのU3-MAX2/32Kを使っています。

多少嵩張ってもいいのならポータブルSSDにすれば不良品でもない限り速度面で問題は出ないはず。

32GBまでのUSBメモリの場合、初期状態では概ねFAT32でフォーマットされていますが、FAT32は1ファイル最大4Gまでという制限があり、1ファイルで構成されている仮想ストレージがすぐに足りなくなり書き込みエラーが発生するのでNTFSやexFATでフォーマットしなおす必要があります。

また性質上書き込みを大量に行うので最悪いつ壊れてもいいように対応はしておきましょう。



## 仮想化ソフトのインストール

### Qemu

Dockerは(64bitの)Linux上でしか動きませんので、仮想化ソフトでLinuxを動作させる必要があります、仮想化ソフトにはQemuを使います。

Qemuはエミュレート速度はいまいちですが、管理者権限が必須な各種アクセラレーターを有効にしない状態で起動することができます。つまり、遅くてもいいなら管理者権限なしで起動することが可能です。

WSL2や他の仮想化ソフトは管理者権限が必須な上ポータブル運用はできませんし、ポータブル運用を想定したPortable-VirtualBoxは管理者権限が必須です。

アクセラレーターなしではGnome等のデスクトップ環境を動かすのは困難ですが、CUI(コマンドライン)環境ならそれなりに動きます。(※体感には個人差があります)
Docker界隈では基本的にUIはWebブラウザが担うものがほとんどなのでさほど問題ではないです。
[既定のWebブラウザをプライベートモードで起動するバッチファイル](https://qiita.com/riafeed/items/51f098b4157441ceda35)や[ポータブル版のWebブラウザ](https://qiita.com/riafeed/items/ae0b46e601db5daf83fe)を一緒に入れておくと便利です。

32bitPC・OS上でも64bitPCをエミュレートできる点もポイント高いです。

ちなみにアクセラレータが無効なら仮想化環境で動いているWindowsからも起動できます、PCエミュレータやVPS上で動いているWindowsからでもDockerが使えます！


### ダウンロード・インストール

[公式サイト](https://www.qemu.org/)から[QemuのWindows版](https://www.qemu.org/download/#windows)([32bit版](https://qemu.weilnetz.de/w32/)または[64bit版](https://qemu.weilnetz.de/w64/))をダウンロードします。

Windows版のQemuは管理者権限が必要なインストーラー形式でしか配布をしていないようなので、インストーラーの解凍に対応した[Universal Extractor2](https://github.com/Bioruebe/UniExtract2)([直リンク](https://github.com/Bioruebe/UniExtract2/releases/download/v2.0.0-rc.2/UniExtractRC2.zip))でUSBメモリに解凍します。
実際は一緒に入っている[7-zip](https://sevenzip.osdn.jp/)を使って解凍しているので7-zipを使ってもいいのですが7-zipも最新版は管理者権限が必要なインストーラー形式でしか配布していないようです(msiからファイルを取り出す方法はあるけれど手動では手間かも)。

一応管理者権限を使ってインストールをし、インストールされたファイルをUSBメモリにコピーしても使えます。


## Linuxのインストール

### Alpine Linux

QemuをUSBメモリにインストール(コピー)したら、仮想PCにLinuxをインストールします。

インストールするLinuxデストリビューションは64bit対応でDockerをインストールできるものなら何でもいいのですが、個人的には[Alpine Linux](https://alpinelinux.org/)がオススメです。

Debian等のよく使われるLinuxデストリビューションをイチからインストールしようとすると、ただでさえ遅いQemu上では最小構成でも数時間は覚悟しないといけませんが、Alpine Linuxなら数分でインストールが終わります。

Alpine Linuxは誤解を恐れずざっくりいうと互換性を犠牲にして軽量化にこだわったデストリビューションで、サイズが小さいためDockerイメージのベースとしてよく採用されており、Docker界隈ではよく目にします。Dockerを触るなら知っておいて損はないデストリビューションだとは思います。

サイズ削減のため標準Cライブラリに[glibc](https://www.gnu.org/software/libc/)ではなく[musl](https://musl.libc.org/)を採用している関係で、他のLinuxデストリビューションで使わているソフトウェアが一部使えないなどいくらか互換性に難がありますが、Alpine Linux本体はDockerホストに徹し、ソフトウェアは基本的にDockerコンテナとして動かすという運用をすれば問題になりません。

非互換で有名なところは[go言語](https://www.google.com/search?q=go%E8%A8%80%E8%AA%9E+alpine)関係でしょうか？go言語で作った単一バイナリは[特定の設定を追加してビルドしたものじゃないと動かない場合があります](http://okzk.hatenablog.com/entry/2016/08/03/234738)。Docker界隈ではそこそこ大きな問題のようで調べればいろいろ出てきます。また自分の知る範囲ではWeb管理ツール[Cockpit](https://cockpit-project.org/)や[VSCodeのリモートプラグイン用のサーバー](https://code.visualstudio.com/docs/remote/linux)もAlpine Linuxでは動きませんが、これもホストはAlpine Linuxでも他のLinuxデストリビューションベースのDockerコンテナ上なら動きます(Cockpitをコンテナ上で動かしてもホストのLinuxは管理できないので大して意味ありませんが…)。

### isoダウンロード

[公式サイト](https://alpinelinux.org/)に[isoのダウンロードページ](https://alpinelinux.org/downloads/)があるのでx86_64版をダウンロードします。

Standard(124M)でもいいのですが、仮想PC用のさらにサイズの小さいVirtual(40M)をお勧めします。

ダウンロードするとalpine-virt-3.12.0-x86_64.isoといった長いファイル名になるので、alpine.isoとかに変えておくと後が楽です。


### インストール

Qemuは他の仮想化ソフトとは異なりすべてコマンドラインで設定・起動する仕組みになっています(GUIで動かすためのフロントエンドはいろいろありますが)

#### 仮想ストレージファイルの作成

```
> qemu-img create -f qcow2 linux.qcow2 20G
```

最後の20Gは仮想ストレージファイルの最大容量になるので環境に合わせて変更しましょう。
いきなり20G全容量を確保するのではなく、最大容量に達するまで必要に応じてサイズが大きくなっていく仕組みになっています。

#### isoファイルをブートディスクにして起動

Qemuのオプションはドキュメントを見てください、自分は以下のように設定しています。

```
> qemu-system-x86_64.exe -display sdl -rtc clock=vm,base=utc -smp 2 -boot d -m 2048 -net nic,model=virtio -hda linux.qcow2 -cdrom alpine.iso
```
問題がなければウインドウが出てLinuxのブートを開始するはずです。ブートには1分から数分かかります。

#### インストール

ブートが終了するとコンソールのログイン画面が出てきますが、rootと入力してエンターを押すとパスワードなしでログインできます。

```
localhost login: root
```

ログインしたら、

```
# setup-alpine
```

でインストールを開始します。コマンドライン上でいくつか質問されるので、答えていくとインストールを始めます。

具体的にどんな内容なのかは[ここ](https://qiita.com/sg_5002_1914/items/624a6ecd6c588cf493d8)らへんを参考にどうぞ。長いので省略します。

インストールが終わったら

```
# poweroff
```

でシャットダウンします。


### Linux起動・設定

#### 起動

```
> qemu-system-x86_64.exe -display sdl -rtc clock=vm,base=utc -smp 2 -m 2048 -net nic,model=virtio -net user,hostfwd=tcp::22-:22,hostfwd=tcp::2375-:2375,hostfwd=tcp::9000-:9000 -hda linux.qcow2
```

自分はこのようにして起動しています(一部を除く)。

「-net user,hostfwd=tcp::22-:22,hostfwd=tcp::2375-:2375,hostfwd=tcp::9000-:9000」の部分で指定したポートを通じてQemu内Linux上のサーバーに接続できるようになります。ポートフォワーディングというらしい。
(※同一PCのローカル接続の話であって外部PCからの接続はWindowsファイアウォールやルータ等の関係でそのままでは出来ませんし管理者権限が必要です)

例えばこの場合だとlocalhost:22でWindowsホストではなく、Qemu内のLinuxで22番ポートを待ち受けしているサーバーに接続できるようになります。

ここではSSH用の22番とDocker CLI用の2375番、Portainer用の9000番を割り当てています。上記のように,でつないでいけば複数のポートを開くことができますが、すでに別のサーバーで待っているポートを指定するとQemu自体エラーで起動しないので注意してください。

ちなみに、Dockerコンテナ上のサーバーの接続にも有効です。つまり、Qemuの起動時に9000番ポートの待ち受けを有効にしていれば、Linuxホストの9000番ポートを待ち受ける設定で起動したDockerコンテナにWindowsホストから9000番ポートで接続できるということです(ややこしい…)。

Linuxの起動には環境にもよりますが1分～2分ほどかかります。遅いのかこんなものなのかはよくわからないですが、ポータブル運用だと起動と停止を繰り返すことになるのでネックかも。

#### SSHの設定

インストール時同様にログイン画面が出たら、IDはroot、パスワードはインストール時に設定したパスワードでログインします。

Qemuのウインドウではコマンドのコピペが出来ず何かと不便なのでSSHを使えるようにします。

Alpine Linuxは最初からSSHサーバーが入っていますが(インストール時のSSHの質問でnoneと答えなかった場合)、SSHサーバーの仕様でそのままではrootユーザーでログイン出来ないようになっているので、ここでは手っ取り早く設定ファイルを追記してrootでログインできるようにします。

```
# echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
```

さらにパスワードなしで運用する場合は次も入力します

```
# echo "PermitEmptyPasswords yes" >> /etc/ssh/sshd_config
```

設定ファイルを変更したらQemuを一度シャットダウンして再起動するかまたはSSHサーバーを再起動します

```
# service sshd restart
```

#### 接続

最近のWindows10は標準でOpehSSHクライアントが入っていますが、そのまま使うと環境変数を無視してWindows本体のユーザーフォルダに.sshフォルダを作ってしまいポータブルではなくなってしまうので、以下のように設定ファイルを読み込まず、鍵のチェックをせず、KnownHostsファイルを残さないオプションをつけて起動します。

```
> ssh -F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@localhost
```

#### QemuへTelnet接続

上記SSHサーバーの設定のような無防備な状態で運用したくない場合は、「-display sdl」でウインドウを開く代わりにTelnetを使ってコンソールを表示することもできます。

```
> qemu-system-x86_64.exe -display none -serial telnet:localhost:4321,server,nowait -rtc clock=vm,base=utc -smp 2 -m 2048 -net nic,model=virtio -net user,hostfwd=tcp::22-:22,hostfwd=tcp::2375-:2375,hostfwd=tcp::9000-:9000 -hda linux.qcow2
```

これでQemuを起動した後4321ポートにTelnet接続すればコンソールが表示されます。

Windows10では標準でTelnetクライアントは起動しないので、[Tera Term](https://forest.watch.impress.co.jp/library/software/utf8teraterm/)か[Putty](https://portableapps.com/apps/internet/putty_portable)といったTelnet対応のターミナルソフト(どちらもポータブル版あり)が必要です。

どちらもコマンドのコピペができるので大変便利になります。

常にこれを有効にしているとポートさえわかれば接続できてSSHサーバーで上記設定をしているのと大差なくなるので、必要な時だけ有効にしましょう。

ちゃんとしたSSHサーバーの設定等が終わるまでのつなぎとしての利用が無難でしょう。

よくよく考えれば、TeraTermにはマクロ機能があり、Telnet接続さえできればマクロでLinuxを自動操作できるっぽいので、バッチファイルと組み合わせればこの記事の作業全部自動化できるのでは…→[できました](https://github.com/uniras/Portable-Docker-for-Windows)


### Dockerインストール

Alpine Linuxはapkという独自のパッケージマネージャーを持っていますが、初期状態ではdockerをインストールできないので、インストールできるようにvi等で/etc/apk/repositoriesを編集します。

初期状態では2行目のcommunityリポジトリが#でコメントアウトされていますが、#を消して有効にします。

```text:/etc/apk/repositories
#/media/cdrom/apks
http://dl-cdn.alpinelinux.org/alpine/v3.12/main
http://dl-cdn.alpinelinux.org/alpine/v3.12/community
#http://dl-cdn.alpinelinux.org/alpine/edge/main
#http://dl-cdn.alpinelinux.org/alpine/edge/community
#http://dl-cdn.alpinelinux.org/alpine/edge/testing
```

保存したら、apkでdockerとdocker-composeパッケージをインストールします

```
# apk update
# apk add docker docker-compose
```

インストールが終わったらdockerを起動します。

```
# service docker start
```

起動時に自動で開始するように設定しておきます。

```
# rc-update add docker boot
```

ちゃんと動作しているかを確認する場合は(多分)そのために用意されているhello-worldイメージをダウンロードしてコンテナとして動作させてみます。

```
# docker run --rm hello-world
```

上手く行けば以下のような表示が出るはずです。

```
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:49a1c8800c94df04e9658809b006fd8a686cab8028d33cfba2cc049724254202
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

```

### Windows版Docker CLIでコンテナ操作

Windows版のDocker CLIを使ってQemu内のDockerをWindowsホストから操作することもできます。

使ったことないので何とも言えないですが、結局仮想化ソフト上のLinux内にあるDockerを操作しているWindows版Docker(Docker TookboxとかDocker for Windowsとか)みたいな感じに多分なると思います。

Docker CLIは[自分でビルドするのが基本](https://github.com/docker/cli)のようですが、[ビルド済みバイナリ](https://download.docker.com/win/static/stable/x86_64/)が公開されているのでビルドが面倒な方はどうぞ。

Docker Composeは[公式のビルド済みバイナリ](https://github.com/docker/compose/releases/)があるのでそちらを使いましょう。後でdocker-compose.exeにリネームしておいてください。

さすがに何も設定しないまま接続はできないので、以下のような/etc/docker/daemon.jsonファイルを作ってWindowsホストから接続できるようにします。Qemuも2375番ポートに接続できるように起動オプションを変更する必要があります。

```/etc/docker/daemon.json
{"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]}
```

ファイルを作ったらDockerを再起動します

```
# service docker restart
```


Windowsでは先ほどダウンロードしたexeにパスを通し、localhost:2375に接続するように環境変数を設定します。

```
> set DOCKER_HOST=localhost:2375
```

試しにdocker psで動作するかどうか確認します。上手く行ってる場合はこんな感じになります。

```
> docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                                          NAMES
5b07a88001c9        portainer/portainer   "/portainer"             5 days ago          Up 9 minutes        0.0.0.0:9000->9000/tcp                         portainer

```

正直なところ、WindowsのコマンドラインからDockerを操作できるだけではあんまり意味がありません。SSHなりTelnet経由で直接Linuxから操作したほうがいいです。

強いてメリットを探すとすればWindowsで動かしているVisual Studio CodeからDocker拡張機能やRemote-Containers拡張機能を通じてDockerの操作やコンテナへのリモート開発が可能になることくらいでしょうか…なくてもコンテナ側でSSHサーバーを立てておいてSSHでリモート開発するなどやりようはあるのでそれも微妙なところなんですが。


### Portainerのインストール

Dockerコンテナをすべてコマンドラインで管理するのが辛い場合は、Webブラウザ上でDockerコンテナを管理できる[Portainer](https://www.portainer.io/)をお勧めします。

Dockerをインストール出来ていれば以下のコマンド1行でインストール完了です。

```
# docker run -d -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
```

インストール完了後、Webブラウザで http://localhost:9000 にアクセスすればPortainerのログイン画面が出てくるはずです。

初期設定は[ここらへん](https://mebee.info/2020/04/24/post-10314/#outline__4)を参考にどうぞ。


## ネットワーク

Qemuでは特殊な設定をしなければlocalhostでWindows側からQemuのLinux(Qemuで設定したポートのみ)にlocalhostでアクセスできるのはもちろん、QemuのLinux側からWindows側のサーバーにも固定IPでアクセスできます。

|方向 | アドレス|備考
|----|---|---|
|Windows → Qemu・Dockerコンテナ　|localhost(127.0.0.1) |Qemu起動時に設定したポート番号のみ|
|Qemu・Dockerコンテナ → Windows　 | 10.0.2.2||
|Qemu → Dockerコンテナ | localhost(127.0.0.1) ||
|Dockerコンテナ → Qemu(別のDockerコンテナ)|	172.17.0.1|Docker共通?|



## セキュリティ

rootでの運用はこの場合はUSBメモリをPCに接続しQemuが動作している間しか機能せず、かつ通常ローカル接続しかできないのであまり問題にはなりませんが、基本的にはやってはいけない運用であることは覚えておきましょう。またUSBメモリの紛失盗難には気を付けましょう。


## さいごに

これでDockerコンテナをUSBメモリで持ち歩く環境が完成しました。
自分が自由に管理できるPCがない人におすすめです。