multipassでつくるmacOS向けのDockerd & K8s環境

## まえおき

2016年ごろからずっとローカルDockerd&K8sにはminikubeを使ってきました。

[minikubeでローカルKubernetesクラスタを5分でつくる方法 - Qiita](https://qiita.com/mumoshu/items/8f55ee830d8e5c172dd4)

が、minikubeが最近重い。Minikube自身が原因かどうか深追いはしてないものの、気軽に乗り換えられそうならやってみるか、程度の軽い気持ちで[multipass](https://github.com/CanonicalLtd/multipass)を試してみました。

// minikubeの重さについて定量的な検証はとくにしていないので、必要な形はこの手順で構築したVMとminikubeVMのCPU・メモリ利用量などを実際に比較してみることをおすすめします

## TL;DR;

[`multipass`](https://github.com/CanonicalLtd/multipass) というシングルバイナリのツールで、Ubuntuの入ったhyperkit VMを簡単に操作できる。

- UbuntuでDocker/ローカルK8sを利用する標準的な構成(docker & microk8s)をセットアップするだけの構成。

minikube同様な点

- ホストOS側のディレクトリマウントにも対応している。

minikubeよりよいと思われる点

- microk8sのアドオンにローカルdocker registryがあり、高速にdocker build -> push -> K8sへデプロイするサイクルがまわせる
- microk8sとk3dによるK8sクラスタを共存させることもできる。Namespacedでないリソース（CRDなど）を複数バージョン作って平行でE2Eテストを走らせるような使い方をする場合、microk8sだとバッティングしてしまうので、k3dで高速に使い捨てのクラスタをつくれるのは便利。

そのほかにやってみたこと

- k3dでmultipass VM上に軽量K8sを構築。microk8sのk8sクラスタと共存
- kanikoでmacOS上のファイルをクラスタに転送してコンテナイメージをビルド(dockerd依存なしで)してローカルレジストリに高速にpush

## 手順

### multipassのインストール

0.7.0のRC版を使います（後述の `primary` マシンが使えるようになり便利になったので

https://github.com/CanonicalLtd/multipass/releases/tag/v0.7.0-rc

### VM の作成

`multipass launch` の引数にVMに割り当てる最大メモリ、ストレージサイズ、CPUコア数とVM名（名前を変えればVMを複数つくることができます）を指定する。

名前を `primary` にすると、`multipass` の各コマンドでVM名を省略したときのデフォルトとして扱ってくれるため、よく使うVMまたは最初の一台とりあえず `primary` にしておくことをおすすめします。

```
mac$ multipass launch --mem 8G --disk 40G --cpus 2 --name primary
```

`multipass ls` コマンドでVM一覧を確認できます。StateがRunningとなっていればひとまず問題ありません。また、`IPv4` 欄に表示されるIPアドレスは macOS 側からVMで起動しているサービスにアクセスする場合によく使うことになるので、覚えておきましょう。

```
mac$ multipass ls
Name                    State             IPv4             Release
primary                 Running           192.168.64.8     Ubuntu 18.04 LTS
```

### K8sクラスタの構築

最小構成としてK8s自身とクラスタDNS、そしてローカル開発に便利なクラスタ内コンテナイメージレジストリを構築する。

K8s自身は、Ubuntuのパッケージマネージャである `snap` で `microk8s` というsnapをインストールして、IPフォワードの許可をするだけでOK。

```
multipass@primary:~$ sudo snap install microk8s --classic
multipass@primary:~$ sudo iptables -P FORWARD ACCEPT
```

次に、クラスタDNSとコンテナイメージレジストリを`microk8s.enable`コマンドでインストールする。

```
multipass@primary:~$ microk8s.enable registry
Enabling the private registry
Enabling default storage class
deployment.extensions/hostpath-provisioner created
storageclass.storage.k8s.io/microk8s-hostpath created
Storage will be available soon
Applying registry manifest
namespace/container-registry created
persistentvolumeclaim/registry-claim created
deployment.extensions/registry created
service/registry created
The registry is enabled
multipass@primary:~$ microk8s.enable dns
Enabling DNS
Applying manifest
service/kube-dns created
serviceaccount/kube-dns created
configmap/kube-dns created
deployment.extensions/kube-dns created
Restarting kubelet
DNS is enabled
```

`microk8s` をインストールすると、同梱された `containerd` が起動した状態になる。

```
multipass@primary:~$ ls /var/snap/microk8s/current/args/
cni-network/              containerd-env            containerd.toml           etcd                      kube-controller-manager   kube-scheduler            kubelet
containerd                containerd-template.toml  ctr                       kube-apiserver            kube-proxy                kubectl
multipass@primary:~$ cat /var/snap/microk8s/current/args/containerd
--config ${SNAP_DATA}/args/containerd.toml
--root ${SNAP_COMMON}/var/lib/containerd
--state ${SNAP_COMMON}/run/containerd
--address ${SNAP_COMMON}/run/containerd.sock
```

これだけだと `docker` コマンドの接続先となる、`containerd` のフロントエンドとなる `dockerd` が存在しないため、別途 docker のみインストールする。

```
multipass@primary:~$ sudo apt-get install docker.io
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following package was automatically installed and is no longer required:
  grub-pc-bin
Use 'sudo apt autoremove' to remove it.
Suggested packages:
  aufs-tools debootstrap docker-doc rinse zfs-fuse | zfsutils
The following NEW packages will be installed:
  docker.io
0 upgraded, 1 newly installed, 0 to remove and 48 not upgraded.
Need to get 0 B/46.4 MB of archives.
After this operation, 234 MB of additional disk space will be used.
Preconfiguring packages ...
Selecting previously unselected package docker.io.
(Reading database ... 60225 files and directories currently installed.)
Preparing to unpack .../docker.io_18.09.2-0ubuntu1~18.04.1_amd64.deb ...
Unpacking docker.io (18.09.2-0ubuntu1~18.04.1) ...
Setting up docker.io (18.09.2-0ubuntu1~18.04.1) ...
Processing triggers for ureadahead (0.100.0-21) ...
Processing triggers for systemd (237-3ubuntu10.21) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
```

`systemctl status` で `dockerd` が起動していることを確認しておく。 `Active: active (running)` になっていれば一旦は問題なし。

```
multipass@primary:~$ systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; disabled; vendor preset: enabled)
   Active: active (running) since Wed 2019-05-22 17:47:16 JST; 25min ago
     Docs: https://docs.docker.com
 Main PID: 7660 (dockerd)
    Tasks: 25
   CGroup: /system.slice/docker.service
           ├─7660 /usr/bin/dockerd -H fd://
           └─7687 docker-containerd --config /var/run/docker/containerd/containerd.toml --log-level info

May 22 17:47:15 primary dockerd[7660]: time="2019-05-22T17:47:15.888067580+09:00" level=info msg="Loading containers: done."
May 22 17:47:16 primary dockerd[7660]: time="2019-05-22T17:47:16.055035350+09:00" level=info msg="Docker daemon" commit=6247962 graphdriver(s)=overlay2 version=18.09.2
May 22 17:47:16 primary dockerd[7660]: time="2019-05-22T17:47:16.055151889+09:00" level=info msg="Daemon has completed initialization"
May 22 17:47:16 primary dockerd[7660]: time="2019-05-22T17:47:16.065357823+09:00" level=info msg="API listen on /var/run/docker.sock"
May 22 17:47:16 primary systemd[1]: Started Docker Application Container Engine.
May 22 17:49:11 primary dockerd[7660]: time="2019-05-22T17:49:11.615281919+09:00" level=info msg="Attempting next endpoint for push after error: Get https://localhost:32000/v2/: http: server gave HTTP response to HTTPS client"
May 22 17:49:54 primary dockerd[7660]: time="2019-05-22T17:49:54.981556940+09:00" level=info msg="Pull session cancelled"
May 22 17:49:55 primary dockerd[7660]: time="2019-05-22T17:49:55.748494759+09:00" level=info msg="shim docker-containerd-shim started" address="/containerd-shim/moby/c7265d99a9070c43c41bc7f6a298b669a4c87354a22599174dd0d37a82acd483/shim.
May 22 17:50:20 primary dockerd[7660]: time="2019-05-22T17:50:20.647918693+09:00" level=info msg="shim reaped" id=c7265d99a9070c43c41bc7f6a298b669a4c87354a22599174dd0d37a82acd483
May 22 17:50:20 primary dockerd[7660]: time="2019-05-22T17:50:20.657969019+09:00" level=info msg="ignoring event" module=libcontainerd namespace=moby topic=/tasks/delete type="*events.TaskDelete"
```

このままだと `/var/run/docker.sock` への権限が原因で `sudo` をつけないと `docker` コマンド経由の通信が失敗するため、`docker.help` コマンドの出力に従ってLinuxユーザ・グループをセットアップする。

```
multipass@primary:~$ docker.help
Docker snap: Docker Linux container runtime.

Due to the confinement issues on snappy, it requires some manual setup to make docker-snap works on your machine.
We'll take you through the steps needed to set up docker snap work for you on ubuntu core and ubuntu classic.

On Ubuntu classic, before installing the docker snap,
please run the following command to add the login user into docker group.
    sudo addgroup --system docker
    sudo adduser $USER docker
    newgrp docker

On Ubuntu Core 16, after installing the docker snap from store,
you need to connect the home interface as it's not auto-connected by default.
    sudo snap connect docker:home :home

Then have fun with docker in snappy.
```

```consnole
$ sudo addgroup --system docker
$ sudo adduser $USER docker
$ newgrp docker
```

実際に `docker` コマンドをいくつか実行してみて、動作確認しておく。

```console
multipass@primary:~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
multipass@primary:~$ docker version
Client:
 Version:           18.09.2
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        6247962
 Built:             Tue Feb 26 23:52:23 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.09.2
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       6247962
  Built:            Wed Feb 13 00:24:14 2019
  OS/Arch:          linux/amd64
  Experimental:     false
```

`docker build` もできる。

```
multipass@primary:~/work$ cat >Dockerfile
FROM nginx:alpine
^C
multipass@primary:~/work$ docker build . -t mynginx:local
Sending build context to Docker daemon  2.048kB
Step 1/1 : FROM nginx:alpine
alpine: Pulling from library/nginx
e7c96db7181b: Pull complete
264026bbe255: Pull complete
```

### 最高のクラスタ内コンテナイメージレジストリを用意する

少し手間をかけると、docker pushだけでなくkanikoにも対応できます。

#### microk8s デフォルトの状態

`microk8s.enable registry` でインストールした docker registry は VM の 32000番でアクセスできるようになっている。また、`apt-get` でインストールした `dockerd` はデフォルトでローカルレジストリを信頼するようになっている。したがって、以下のように `localhost:32000` をDockerレジストリとして `docker push` などが可能。

```
docker build . -t localhost:32000/mynginx:registry
docker push localhost:32000/mynginx
```

#### dockerd, K8sの両方からローカルレジストリにアクセス可能にする

レジストリ名にはlocalhostではなくIPアドレスを使うことがポイント。

イメージ名に含まれるレジストリのアドレスに「localhost:32000」を使うより、IPアドレス：ポートを使うようにする、ということ。

localhostの場合multipass VM上のUbuntuから起動しているdockerdがアクセスすることはもちろん可能だが、例えばkanikoでin-cluster buildをしてkanikoからpushをするような場合にアクセスできないから(kanikoにとってのlocalhostはkaniko自身のPodになるため)。

したがって、`multipass ls` で確認できるVMのIPアドレスが 192.168.64.8 だとしたら、`192.168.64.8:32000` をイメージレジストリのアドレスとして利用する。そうすれば、macOSやUbuntuから`docker push` する場合、kanikoからin-cluster build/pushする場合で同じレジストリのアドレスを利用することができ、混乱が少ない。

##### dockerdのinsecure registry設定

基本的には `docker` コマンドを使う場合は `docker tag 192.168.64.8:32000/MYIMAGE` のようにタグを打てばよいが、dockerd側の設定をしないと以下のエラーになる。

```
multipass@primary:~$ docker push 192.168.64.8:32000/alpine
The push refers to repository [192.168.64.8:32000/alpine]
Get https://192.168.64.8:32000/v2/: http: server gave HTTP response to HTTPS client
```

これは、microk8sで導入したレジストリがHTTPモードになっているため。dockerdは`localhost:32000`のようなレジストリが指定された場合はHTTPモードのレジストリとして通信をするが、`IPアドレス:32000` のようにIPアドレスを明示した場合はHTTPSをデフォルトとして利用するようで、このようなエラーになってしまう。

そこで、dockerd に該当アドレスにいるレジストリはHTTPを話すということを伝える必要がある。具体的には、`/etc/docker/daemon.json` を以下の通り作成する。

```
multipass$ sudo sh -c 'cat > /etc/docker/daemon.json'
{
  "insecure-registries" : ["192.168.64.8:32000"]
}
```

参考: https://docs.docker.com/registry/insecure/

この設定をdockerdに読み込ませるために、dockerdを再起動する。

```
multipass@primary:~$ sudo systemctl restart docker
```

先程エラーになったdocker pushコマンドを再度実行してみると、今度はうまくいく。

```
multipass@primary:~$ docker push 192.168.64.8:32000/alpine
The push refers to repository [192.168.64.8:32000/alpine]
f1b5933fe4b5: Pushed
3.9: digest: sha256:bf1684a6e3676389ec861c602e97f27b03f14178e5bc3f70dce198f9f160cce9 size: 528
```

#### kanikoのinsecureレジストリ設定

`kaniko` の場合は、`--insecure` というフラグをつけるとpush先のレジストリをHTTPサーバとみなす。

https://github.com/GoogleContainerTools/kaniko#--insecure

`kaniko` のREADMEによれば、`kaniko` を試すときは docker runやkubectlから実行するのが常のようだが、手元から試しにビルドする場合には手順が煩雑なので、ここではskaffoldを使うことにする。skaffoldにはkanikoビルダがあり、`kaniko`を直接使うよりは簡単にビルドができる。

skaffoldは`kaniko` の`--insecure`フラグを自動で付与したりはしてくれない（しようもない）ので、それは自分で `skaffold` の設定ファイルに記述する。

skaffoldの設定ファイルである`skaffold.yaml`のリファレンスのとおり、`build.artifacts[].kaniko`にkaniko関連の設定を記述する。

https://skaffold.dev/docs/references/yaml/

`flags` に前述の`--insecure`フラグを追加すればよい。

```
apiVersion: skaffold/v1beta11
kind: Config
build:
  artifacts:
  - image: 192.168.64.8:32000/mumoshu/skaffoldtest
    context: .
    kaniko:
      flags:
      # Required in order to avoid the following error when pusing to a local, insecure registry:
      #   error pushing image: failed to push to destination 192.168.64.8:32000/mumoshu/skaffoldtest:6383d2f-dirty: Get https://192.168.64.8:32000/v2/: http: server gave HTTP response to HTTPS client
      - --insecure
      buildContext:
        localDir: {}
  # If omitted, `local` builder is selected by default even though the `kaniko` settings is provided
  cluster:
    pullSecret: /Users/YOU/.docker/config.json
```

この内容で `skaffold build`  を実行すると、skaffoldがkanikoを使ってクラスタ内（のPodを使って）イメージビルドしてくれる。

```
$ skaffold build
Generating tags...
 - 192.168.64.8:32000/mumoshu/skaffoldtest -> 192.168.64.8:32000/mumoshu/skaffoldtest:6383d2f-dirty
Tags generated in 49.780056ms
Starting build...
Creating kaniko secret [kaniko-secret]...
Building [192.168.64.8:32000/mumoshu/skaffoldtest]...
Storing build context at /var/folders/_w/3lwtgvv51tl_fgxkdvcmtm340000gp/T/context-c2cb37afc85a7037bdbf94e9502fcbd7.tar.gz
WARN[0000] The additionalFlags field in kaniko is deprecated, please consult the current schema at skaffold.dev to update your skaffold.yaml.
INFO[0000] Downloading base image nginx:alpine
2019/05/24 06:22:47 No matching credentials were found, falling back on anonymous
INFO[0002] Taking snapshot of full filesystem...
INFO[0002] Skipping paths under /kaniko, as it is a whitelisted directory
INFO[0002] Skipping paths under /secret, as it is a whitelisted directory
INFO[0002] Skipping paths under /dev, as it is a whitelisted directory
INFO[0002] Skipping paths under /sys, as it is a whitelisted directory
INFO[0002] Skipping paths under /proc, as it is a whitelisted directory
INFO[0002] Skipping paths under /var/run, as it is a whitelisted directory
2019/05/24 06:22:50 existing blob: sha256:5595887beb811004cfb48cd5bb3eb9a9602fb1fd47d93117b92ef97e22859c00
2019/05/24 06:22:50 existing blob: sha256:264026bbe25598d28ce74f288ce0b4ef93dc4123bb793bf655780e0455453f4c
2019/05/24 06:22:50 existing blob: sha256:a71634c55d292856f6a48a5984ff4aa7e244f2e82083e1279aa5af8a7da5819f
2019/05/24 06:22:50 existing blob: sha256:e7c96db7181be991f19a9fb6975cdbbd73c65f4a2681348e63a141a2192a5f10
2019/05/24 06:22:50 pushed blob sha256:a0c491e91aadbd72d0375bc0d186ebede44c5e9e2f2377a035de37baf5b1d878
2019/05/24 06:22:50 192.168.64.8:32000/mumoshu/skaffoldtest:6383d2f-dirty: digest: sha256:ac66fceb3d272c21e54450bfa7c18e7235baea1b5924c2ac2d2e029eae8b78fb size: 912
Build complete in 14.884723143s
Starting test...
Test complete in 7.838µs
Complete in 14.9516144s
```

kanikoをPod内で実行する場合、ローカルマシンからkanikoのPodへビルドコンテキスト(DockerfileからADDやCOPYできるファイルがある場所。`docker build .`だとカレントディレクトリがそれ)を渡すのが面倒だが、skaffoldはそれも面倒を見てくれる。具体的には、skaffold.yamlに記述した場所にあるディレクトリをtarballにアーカイブして、kubectl execでkaniko podに転送する、ということを自動的にやってくれる。素のkanikoより便利ですね。

参考: https://github.com/GoogleContainerTools/skaffold/blob/fe31429012110e6fd70f97971288bd266ba95bed/pkg/skaffold/build/cluster/sources/localdir.go#L113-L121

kanikoがローカルレジストリにpushしたことを確認するため、dockerコマンドを使ってdockerdにpullしてみる。

まず、pullする前はイメージがdockerdには見えない状態。これは、kanikoがdocker/dockerdを経由しないで自前でイメージをビルドしてローカルレジストリにpushしたため。

```
$ docker images
```

以下のようにpullすれば、イメージが見えるようになるはず。

```
$ docker pull 192.168.64.8:32000/mumoshu/skaffoldtest:6383d2f-dirty
6383d2f-dirty: Pulling from mumoshu/skaffoldtest
e7c96db7181b: Already exists
264026bbe255: Already exists
a71634c55d29: Already exists
5595887beb81: Already exists
Digest: sha256:3132ea39b2e91e2d76f45adbdf0921f394a5793fd9ab4a4b7c895da46ecdd033
Status: Downloaded newer image for 192.168.64.8:32000/mumoshu/skaffoldtest:6383d2f-dirty
```

`docker images` コマンドで dockerd から見えているイメージを見てみると、無事にkanikoがpushしたイメージがある。

```
macos$ docker images | grep mumoshu
192.168.64.8:32000/mumoshu/skaffoldtest   6383d2f-dirty       f39da67d6567        39 seconds ago      16.1MB
```

### macOS側からdockerdにアクセスできるようにする

このままだと `/var/run/docker.sock` 経由によるdockerdとの通信しかできない。macOS側の `docker` コマンドからは tcp 経由の通信をしたいので、 `dockerd` の設定を変更する。

方法はいろいろあるが、今回は `dockerd` のコマンドラインオプションに `-H プロトコル://アドレス:ポート` の記述を追加することで対応する。ポート2376番で、どのネットワークインターフェースでも待ち受けてほしい場合は、 `-H tcp://0.0.0.0:2376` のように書けばよい。

参考: [Docker - How do I enable the remote API for dockerd](https://success.docker.com/article/how-do-i-enable-the-remote-api-for-dockerd)

```
multipass@primary:~$ sudo mkdir /etc/systemd/system/docker.service.d/
multipass@primary:~$ sudo sh -c 'cat > /etc/systemd/system/docker.service.d/startup_options.conf'
# /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376
^C
multipass@primary:~$ sudo systemctl daemon-reload
multipass@primary:~$ sudo systemctl restart docker.service
```

以下のように `dockerd` のコマンドライン引数に `-H tcp://0.0.0.0:2376` が追加されていればOK

```
multipass@primary:~$ systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; disabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/docker.service.d
           └─startup_options.conf
   Active: active (running) since Wed 2019-05-22 18:14:13 JST; 1s ago
     Docs: https://docs.docker.com
 Main PID: 17395 (dockerd)
    Tasks: 22
   CGroup: /system.slice/docker.service
           ├─17395 /usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376
           └─17432 docker-containerd --config /var/run/docker/containerd/containerd.toml --log-level info

May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.122806400+09:00" level=warning msg="Your kernel does not support cgroup rt period"
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.122820048+09:00" level=warning msg="Your kernel does not support cgroup rt runtime"
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.123708656+09:00" level=info msg="Loading containers: start."
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.217868247+09:00" level=info msg="Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address"
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.254483709+09:00" level=info msg="Loading containers: done."
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.305465422+09:00" level=info msg="Docker daemon" commit=6247962 graphdriver(s)=overlay2 version=18.09.2
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.305581627+09:00" level=info msg="Daemon has completed initialization"
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.311982639+09:00" level=info msg="API listen on [::]:2376"
May 22 18:14:13 primary dockerd[17395]: time="2019-05-22T18:14:13.312016834+09:00" level=info msg="API listen on /var/run/docker.sock"
May 22 18:14:13 primary systemd[1]: Started Docker Application Container Engine.
```

### macOS側

#### Dockerの接続先設定

まだ入ってなければ `docker` コマンドをインストールする。

```console
mac$ brew install docker
```

`multipass ls` コマンドで VMのIPアドレスを調べて、

```
$ multipass ls
Name                    State             IPv4             Release
primary                 Running           192.168.64.8     Ubuntu 18.04 LTS
```

`docker` コマンドであれば `-H` フラグでそのIPアドレスの2376(前述の設定のとおり)をDocker APIのエンドポイントとして使うようにします。

```console
mac$ docker -H 192.168.64.8:2376 ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

いちいち `-H` を設定するのが面倒な場合や、`docker` コマンド以外の方法（例えば各プログラミング言語向けの Docker APIクライアントやそれを内部的に使っているようなツール）で dockerd と通信したいような場合は、`DOCKER_HOST` 環境変数を設定してください。

```
mac$ export DOCKER_HOST=tcp://192.168.64.8:2376
mac$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```

#### VM上のK8s APIにアクセスする

`microk8s` をインストールすると作られる `microk8s.config` コマンドで、`kubeconfig` の内容を標準出力に出すことができます。

```
multipass@primary:~$ microk8s.config
```

設定内容に含まれるK8s APIエンドポイントURLのホスト名部分には VMのプライベートIPアドレスが書かれており、VMのIPアドレスはmacOS側から特に何もせずともアクセスすることができるため、これを単にmacOS側に保存して`kubectl` などに読ませればOKです。

```
$ multipass exec primary -- /snap/bin/microk8s.config > microk8s.kubeconfig
$ export KUBECONFIG=$(pwd)/microk8s.kubeconfig
```

実際に `kubectl` を実行してアクセスできるか確認してみます。

```
$ kubectl version
$ kubectl get no
NAME      STATUS   ROLES    AGE   VERSION
primary   Ready    <none>   9h    v1.14.1
```

#### ホームディレクトリのマウント

##### TL;DR;

```console
mac$ multipass mount $HOME primary:$HOME
```

##### 説明

macOSの場合 `$HOME` = `/Users/$USER` がホームディレクトリになっていると思います。`minikube` や `docker-machine` はデフォルトで macOS 側のホームディレクトリを VM 上の同じパスにマウントしてくれるため、ホームディレクトリ以下のディレクトリであれば `docker run -v $SRC:$DST` でマウントするソースディレクトリのパスに macOS 側のパスをそのまま利用できて便利です。

たとえば `docker run --rm -it -v $(pwd):$(pwd) myimage /bin/bash` のようにカレントディレクトリをマウントしたコンテナを起動するというようなことができるのは、macOS側の$(PWD)が示すパスがVM上にもあるから、ですよね。

`multipass mount` で明示的にホームディレクトリをマウントすれば、同じ状態を再現することができます。

```console
mac$ multipass mount $HOME primary:$HOME
```

これまで通り、macOS側のパスとVM上のパスの読み替えをすることなく、macOSにあるファイルをコンテナ内から参照することができます。

```
mac$ docker run --rm -it -v $HOME:$HOME alpine:3.9 sh
```

## 上級編

### k3d on multipass VM

multipass VM上にmicrok8sとは別の、k3dによるK8sクラスタを相乗りさせることもできる。

ただし、k3dの実装上の都合により、k3dが作成したK8sクラスタへのアクセスはVM側から行ったほうが良い。例えばkubectlを使うなら、macOS側からではなくUbuntu側からkubectlを実行したほうがよい。これは、macOS側からk3dのK8sにアクセスしようとすると、CA証明書の検証エラーとなってしまうため。

```
$ KUBECONFIG=~/.config/k3d/k3s-default/kubeconfig.yaml  k get no
Unable to connect to the server: x509: certificate signed by unknown authority
```

エラー内容から察するに、k3dはUbuntu側のローカルルートCAで署名されたCA証明書をK8s APIとの通信に利用するようになっており、Ubuntu側のローカルルートCAはmacOS側で信頼されていないから、ということではないかと思う。(macOS側でそのルートCAを信頼するようにすればよいが、面倒なのでやっていない。)

そのときに利用するKUBECONFIGはk3dコマンドが生成したものを使うことになるため、最初からmacOS側ではなくUbuntu側でk3dコマンドを実行してkubeconfigがmacOS側ではなくUbuntu側に用意された状態にすると混乱がない。

以上を踏まえて、Ubuntu側に `kubectl` と `k3d` をインストールし、Ubuntu上で `k3d` によるK8sクラスタの作成と、`kubectl` による接続確認を行う。

```
multipass@primary:~$ sudo snap install kubectl --classic
```

```
multipass@primary:~$ k3d create
2019/05/24 11:06:10 Created cluster network with ID baee312bc080400e2625b2097f886fef96b7c41a8afb1f9fb40c0826380495b2
2019/05/24 11:06:10 Creating cluster [k3s-default]
2019/05/24 11:06:10 Creating server using docker.io/rancher/k3s:v0.5.0...
2019/05/24 11:06:12 SUCCESS: created cluster [k3s-default]
2019/05/24 11:06:12 You can now use the cluster with:

export KUBECONFIG="$(k3d get-kubeconfig --name='k3s-default')"
kubectl cluster-info
```

KUBECONFIGを指定しない場合は microk8s で作られたほうの K8s に向いている。

```
multipass@primary:~$ kubectl cluster-info
Kubernetes master is running at http://localhost:8080
KubeDNS is running at http://localhost:8080/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
```

`k3d create` コマンドの出力で指示されたとおりKUBECONFIGを設定すると、k3d で作られたほうのK8sに向く。

```
multipass@primary:~$ export KUBECONFIG="$(k3d get-kubeconfig --name='k3s-default')"
multipass@primary:~$ kubectl cluster-info
Kubernetes master is running at https://localhost:6443
CoreDNS is running at https://localhost:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
```

## 注意点

再起動するたびに下記コマンドを実行すること。

```
multipass@primary:~$ sudo iptables -P FORWARD ACCEPT
```

実行しないと、クラスタ外のIPアドレス（例えばインターネット上のAlpine Package Registryやコンテナイメージレジストリ含めて）にあらゆるPod内からアクセスできない。kube-dnsもUpstreamである 8.8.8.8 などにアクセスできないので、kube-dnsが起動しているにもかかわらず、あらゆるPodからクラスタ外の名前解決ができない、という状態になってしまう。

## 動かないときは

とりあえず microk8s.inspect を実行して、なにかエラーや注意が出ていないか確認する。例えば前述のiptablesの件は、microk8s.inspectにも出てくる。(`WARNING` の部分)

```
multipass@primary:~$ microk8s.inspec
microk8s.inspec: command not found
multipass@primary:~$ microk8s.inspect
Inspecting services
  Service snap.microk8s.daemon-containerd is running
  Service snap.microk8s.daemon-apiserver is running
  Service snap.microk8s.daemon-proxy is running
  Service snap.microk8s.daemon-kubelet is running
  Service snap.microk8s.daemon-scheduler is running
  Service snap.microk8s.daemon-controller-manager is running
  Service snap.microk8s.daemon-etcd is running
  Copy service arguments to the final report tarball
Inspecting AppArmor configuration
Gathering system info
  Copy network configuration to the final report tarball
  Copy processes list to the final report tarball
  Copy snap list to the final report tarball
  Inspect kubernetes cluster




 WARNING:  IPtables FORWARD policy is DROP. Consider enabling traffic forwarding with: sudo iptables -P FORWARD ACCEPT
Building the report tarball
  Report tarball is at /var/snap/microk8s/522/inspection-report-20190524_145809.tar.gz
```

## 所感

- （個人的には）ブラックボックス感が少なくて、問題が起きたときに対処しやすそう
- minikubeでよく行っていた `docker build && kubectl apply` のようなワークフローも問題なく流せそうなので、移行したい