kubeadm + RasPi3

手元に使えるRasPi3があったのでクラスタを組んでみました。そのメモです。

 * RasPi 3B+
 * Kubernetes v1.18.2
 * kubeadmを使用
 * 7台 (LB, controller x 3, worker x 3)
 * Pod network add-on として Flannel

ちなみに練習としてこの前にKubernetes the Hard Wayとそのon Vagrant版をやっています。

前回: https://qiita.com/amedama/items/4bd9cf067c456bd59ead

# 環境のメモ

```
$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
```

```
$ cat /proc/cpuinfo
processor	: 0
model name	: ARMv7 Processor rev 4 (v7l)
BogoMIPS	: 38.40
Features	: half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xd03
CPU revision	: 4

processor	: 1
model name	: ARMv7 Processor rev 4 (v7l)
BogoMIPS	: 38.40
...

Hardware	: BCM2835
Revision	: a020d3
Serial		: 000000005b98c9fa
Model		: Raspberry Pi 3 Model B Plus Rev 1.3
```

ARMv7というのがあかんですが、本記事の範囲ではこのままでやってます。


# 今回確認したかった焦点

 * 実ハードウェアを動員した時に変わる視点はどこか
 * `kubeadm` は何を管理してくれて何を自前で管理する必要があるか。何が追加で問題になるか。何が楽になるか

ここではやったことを一つ一つ説明するのではなく他の資料を見ながら「違うな」と思った点などをかいつまんで書いておきます。

# 準備

Raspbian (Buster) が元々入っていたのでそのまま使います。後で気づきましたが 64bits モードで起動するのを忘れていてCPUが ARMv7 と認識されていました。

こうなると Pod network add-on として選択できるのはガチで Flannel だけなのですが、これはこれで現状（2020-05-06)ちょっと怪しい記述があってドキドキします。

https://github.com/kubernetes/website/commit/f73647531dcdade2327412253a5f839781d57897/

> Remove flannel from the list of CNIs in the kubeadm "create cluster"
> page. The reason for that is that Cluster Lifecycle have been
> getting a number of issues related to flannel (either in kubeadm or
> kops tickets) and we don't have good answers for the users as the
> project is not actively maintained.

気づかずにCalicoを使ってみたら、add-onをインストール (yamlをapply) した後にいつまで経ってもDockerイメージをとってきてくれず、調べてみたらarchに対応するイメージがない、という話でした。なんと凡庸なミス。

ARM 64bits ならCalicoとか、もっと人が集まってそうなものを使えそうな気がしますが、ここでは上記のドキドキものりこえてFlannelを使います。結論から言えば「運用時にどうなるかは知らんが今回のチュートリアルではまだ問題ない」という状況です。

また、CPU温度とか色々調べたかったのもあってZabbixをLB相当のマシンに入れました。これはRasPi 3Bの方の学びを得るためとか、ついでにコロナ禍でおうちネットワークが断続的に落ちるので外部にpingしておいてネットワークダウンを計測したいとか、色々と今回の記事と関係ない需要にも対応しています。

# 予習

kubeadm絡みの公式の資料は相当充実しているので一周読んでおきます

- Installing kubeadm
 - [https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)
(https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/troubleshooting-kubeadm/)
- Creating a single control-plane cluster with kubeadm
 - [https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/]
- Options for Highly Available topology
 - https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/
- Creating Highly Available clusters with kubeadm
 - https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/

今回の構成では Controll Plane 側のホストが3台あり、いわゆる高可用クラスタに属することになります。これは Kubernetes the Hard Way も同じです。

これ以外にRasPiベースの他の記事を読んでおきます。ちなみに大抵は何某か事情が古い部分があったり前提が異なっているため、そのままやるとハマることもあります。

 * https://qiita.com/go_vargo/items/29f6d832ea0a289b4778

# ネットワーク構成

- LB
    - lb-0 = 192.168.7.140
        - Zabbixマシンも兼用。
- Controll Plane (etcd同居を想定)
    - controller-0 ... 192.168.7.201
    - controller-1 ... 192.168.7.202
    - controller-2 ... 192.168.7.203
- Application Plane (Worker)
    - worker-0 ... 192.168.7.204
    - worker-1 ... 192.168.7.205
    - worker-2 ... 192.168.7.206

配線が面倒なのですべて宅内のWi-Fiで済ませています。これで「高可用クラスタ」とか言われても

※ 外に出ないネットワークとしてハブで有線化する構成も考えたんですが、運悪く8ポートのハブが壊れしまい、余っているのが5ポートで足りないというね……


# かいつまんでポイント

## cgroup
今回のBuster版のRaspbianではcgroup周りの設定をいじる必要はありませんでした

```
$ cat /proc/cgroups
#subsys_name	hierarchy	num_cgroups	enabled
cpuset	9	1	1
cpu	3	1	1
cpuacct	3	1	1
blkio	4	1	1
memory	7	76	1
devices	6	33	1
freezer	5	1	1
net_cls	2	1	1
pids	8	39	1
```

## swap

swapは無効化します。最近のKubeletでは起動しない、という話らしいので。

```
$ swapon --show
NAME      TYPE SIZE USED PRIO
/var/swap file 100M   0B   -2
```

Rapsbianでは以前からSwapファイルの管理には `dphys-swapfile` というものを使っているようです。Buster版ではこれがsystemd配下にありますので、disableします。

```
$ sudo systemctl disable dphys-swapfile
Synchronizing state of dphys-swapfile.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install disable dphys-swapfile
Removed /etc/systemd/system/multi-user.target.wants/dphys-swapfile.service.
```

## MACアドレスとproduct_uuidが全てのノードでユニークであることの検証

kubeadmを利用する際の確認事項なのですが、RasPiでは `product_uuid` は得られません。いきなりなんだってー

> /sys/class/dmi/id/product_uuid: The main board product UUID, as set by the board manufacturer and encoded in the BIOS DMI information. It may be used to identify a mainboard and only the mainboard. It changes when the user replaces the main board. Also, often enough BIOS manufacturers write bogus serials into it. In addition, it is x86-specific. Access for unprivileged

[http://0pointer.de/blog/projects/ids.html](http://0pointer.de/blog/projects/ids.html)

この点は今回は無視します。幸いこの問題についてはwarningも何も出ませんでした。

MACアドレスがユニークかどうか、といった点はクリアしています。この「検証」の背景は、おそらくVMをコピーするとこのあたりの値が完全一致してしまうケースが出てしまうので調べておこう、ということでしょう。素直なハードウェアで被るような話ではないように思えます。

## iptablesがnftablesバックエンドを使用しないようにする

> もしあなたのシステムのiptablesツールがnftablesバックエンドを使用している場合、これらの問題を避けるためにiptablesツールをレガシーモードに切り替える必要があります。これは、少なくともDebian 10(Buster)、Ubuntu 19.04、Fedora 29、およびこれらのディストリビューションの新しいリリースでのデフォルトです。

これには引っかかります、ので対応します。

```
$ sudo apt-get install -y iptables arptables ebtables

$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
$ sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
$ sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
$ sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
```

## Dockerインストール

kubeadmの資料をそのままやるとこれにハマります
https://github.com/docker/for-linux/issues/709

以下の方法を取りました。

```
$ sudo su
# curl https://get.docker.com | sh
...
# sudo usermod -aG docker pi
```

kubeadmが警告を出すので `native.cgroupdriver=systemd` の指定もしておきます。

```
cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
```

## Kubernetes周りのツールインストール

```
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
```

地味なのですが `apt-mark` あたりは学びです。単純な `apt-get upgrade` でKubernetesのツールが更新されて破壊される、といったことを防ぐわけですが、手でインストールしてないとしばしば忘れて、後で「ｱｯｰ」ってなるやつです。お遊びだと忘れがちですねぇ……

## LBの設定

aptでHAProxyを入れます。 `net.ipv4.ip_nonlocal_bind=1` も追加しておきます

```
$ sudo su
# grep -q -F 'net.ipv4.ip_nonlocal_bind=1' /etc/sysctl.conf || echo 'net.ipv4.ip_nonlocal_bind=1' >> /etc/sysctl.conf
(戻る)
$ sudo apt-get install haproxy
```

HAProxyの設定を変更します。素のインストールだとHTTPのロードバランサ的な設定が入っているので `mode tcp` にしたりk8s用の設定を足したりします。



```
$ cat /etc/haproxy/haproxy.cfg
global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
	# stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
	stats socket /run/haproxy/admin.sock mode 660 level admin
	stats timeout 30s
	user haproxy
	group haproxy
	daemon
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private
	ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
	ssl-default-bind-options no-sslv3
defaults
	log	global
	# mode	http
	mode	tcp
	# option	httplog
	option	tcplog
	option	dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http
frontend k8s
	bind 192.168.7.140:6443
	default_backend k8s_backend
backend k8s_backend
	balance roundrobin
	mode tcp
	server controller-0 192.168.7.201:6443 check inter 1000
	server controller-1 192.168.7.202:6443 check inter 1000
	server controller-2 192.168.7.203:6443 check inter 1000
```

controllerが用意されてなくても反応はします。

```
$ nc -v 192.168.7.140 6443
Connection to 192.168.7.140 port 6443 [tcp/sun-sr-https] succeeded!
```

## 1台目のcontrollerのセットアップ

kubeadmを練習に使う際のちょっとしたつまづきポイントとして「先に決めておかないと後の祭り」がそれなりにあるってことですかね。ARMv7も、まぁ、それなんですけど。

FlannelをPodネットワークのadd-onに使うと決めた場合、 `kubeadm init` の段階で `--pod-network-cidr=10.244.0.0/16` を指定しておく必要があります。いやま、後から手で設定を書き換えて回るのは出来ると思いますが面倒ですしミスも増えるので、最初にやっておく。

LBのエンドポイント、つまりクラスタの外から `kubectl` 経由などでKube API Serverにアクセスする際のクチも指定します。あと、今回は「高可用」構成とするため、 `--upload-certs`も指定します

> When `--upload-certs` is used with `kubeadm init`, the certificates of the primary control plane are encrypted and uploaded in the `kubeadm-certs` Secret.

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/

```
$ sudo kubeadm init --control-plane-endpoint "192.168.7.140:6443" --upload-certs --pod-network-cidr=10.244.0.0/16

...

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join 192.168.7.140:6443 --token 3c2iy2.jkx1zkmj54ak4pez \
    --discovery-token-ca-cert-hash sha256:964e73...56d6c65 \
    --control-plane --certificate-key 1221d937fc...c9cc2dca049

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.7.140:6443 --token 3c2i...pez \
    --discovery-token-ca-cert-hash sha256:964e7...033fffdca56d6c65
```

Kubernetes the Hard Way で苦行だった証明書周りもダブついた作業も全部ケアされてるー。すごーい（と、素直に感動しました）

ちなみにこの1台目のmasterには、インストール後、次のような感じで証明書や設定が保存されます。

```
$ tree /etc/kubernetes
/etc/kubernetes
├── admin.conf
├── controller-manager.conf
├── kubelet.conf
├── manifests
│   ├── etcd.yaml
│   ├── kube-apiserver.yaml
│   ├── kube-controller-manager.yaml
│   └── kube-scheduler.yaml
├── pki
│   ├── apiserver.crt
│   ├── apiserver-etcd-client.crt
│   ├── apiserver-etcd-client.key
│   ├── apiserver.key
│   ├── apiserver-kubelet-client.crt
│   ├── apiserver-kubelet-client.key
│   ├── ca.crt
│   ├── ca.key
│   ├── etcd
│   │   ├── ca.crt
│   │   ├── ca.key
│   │   ├── healthcheck-client.crt
│   │   ├── healthcheck-client.key
│   │   ├── peer.crt
│   │   ├── peer.key
│   │   ├── server.crt
│   │   └── server.key
│   ├── front-proxy-ca.crt
│   ├── front-proxy-ca.key
│   ├── front-proxy-client.crt
│   ├── front-proxy-client.key
│   ├── sa.key
│   └── sa.pub
└── scheduler.conf

3 directories, 30 files
```

これ、感動ポイントです。 [こちら](https://github.com/dmiyakawa/kube-the-hard-way-on-vagrant)やったときのディレクトリをlsするとこんなですから

```
（これは Kubernetes the Hard Way on Vagrant の最終状態。ぱっぱらぱなし）
$ ls -1
activate.zsh
admin.csr
admin-csr.json
admin-key.pem
admin.kubeconfig
admin.kubeconfig.bak
admin.pem
bin
ca-config.json
ca.csr
ca-csr.json
ca-key.pem
ca.pem
coredns.yaml
encryption-config.yaml
kube-controller-manager.csr
kube-controller-manager-csr.json
kube-controller-manager-key.pem
kube-controller-manager.kubeconfig
kube-controller-manager.pem
kube-proxy.csr
kube-proxy-csr.json
kube-proxy-key.pem
kube-proxy.kubeconfig
kube-proxy.pem
kubernetes.csr
kubernetes-csr.json
kubernetes-key.pem
kubernetes.pem
kube-scheduler.csr
kube-scheduler-csr.json
kube-scheduler-key.pem
kube-scheduler.kubeconfig
kube-scheduler.pem
README.md
service-account.csr
service-account-csr.json
service-account-key.pem
service-account.pem
setup.sh
Vagrantfile
worker-0.csr
worker-0-csr.json
worker-0-key.pem
worker-0.kubeconfig
worker-0.pem
worker-1.csr
worker-1-csr.json
worker-1-key.pem
worker-1.kubeconfig
worker-1.pem
worker-2.csr
worker-2-csr.json
worker-2-key.pem
worker-2.kubeconfig
worker-2.pem
```


`docker ps` の結果も見ておきます。

```
$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS              PORTS               NAMES
59cda3239ab4        0919cc09383b           "/usr/local/bin/kube…"   10 seconds ago       Up 9 seconds                            k8s_kube-proxy_kube-proxy-wnprf_kube-system_9d231a62-7b1c-47a0-b243-5abd4b62c580_0
aff39cd3448e        k8s.gcr.io/pause:3.2   "/pause"                 11 seconds ago       Up 10 seconds                           k8s_POD_kube-proxy-wnprf_kube-system_9d231a62-7b1c-47a0-b243-5abd4b62c580_0
9ca88ca88e8e        8de7dc8a661a           "kube-controller-man…"   About a minute ago   Up About a minute                       k8s_kube-controller-manager_kube-controller-manager-hrus-001_kube-system_807045fe48b23a157f7fe1ef20001ba0_0
6053f549b283        19a741f6ffe7           "etcd --advertise-cl…"   About a minute ago   Up About a minute                       k8s_etcd_etcd-hrus-001_kube-system_93e2c1d4e818f05306ffdc2d9bb5e740_0
2801e67594aa        916fa374fc34           "kube-apiserver --ad…"   About a minute ago   Up About a minute                       k8s_kube-apiserver_kube-apiserver-hrus-001_kube-system_39a390fdc92ee1594bd13b2888fd71ac_0
074bdfe01c3a        8d8286f173d9           "kube-scheduler --au…"   About a minute ago   Up About a minute                       k8s_kube-scheduler_kube-scheduler-hrus-001_kube-system_155707e0c19147c8dc5e997f089c0ad1_0
f4bfbf691696        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago   Up About a minute                       k8s_POD_etcd-hrus-001_kube-system_93e2c1d4e818f05306ffdc2d9bb5e740_0
a2b49cc1bbdb        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago   Up About a minute                       k8s_POD_kube-scheduler-hrus-001_kube-system_155707e0c19147c8dc5e997f089c0ad1_0
d294f70f0161        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago   Up About a minute                       k8s_POD_kube-controller-manager-hrus-001_kube-system_807045fe48b23a157f7fe1ef20001ba0_0
dd27bec6d6c7        k8s.gcr.io/pause:3.2   "/pause"                 About a minute ago   Up About a minute                       k8s_POD_kube-apiserver-hrus-001_kube-system_39a390fdc92ee1594bd13b2888fd71ac_0
```

これらは `kubelet` が管理するもののようです。systemdの管理下にあるので、controllerの再起動とかしたいならsystemctl経由でやる、ということのようです。

※ なぜ書いたかというと実際トラブってそこまでやる必要があったためです。


## PodネットワークAdd-onのインストール

今回はFlannel v0.12.0 の設定を利用します。

```
$ echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.12.0/Documentation/kube-flannel.yml
```

※ あーうん、 `net.bridge.bridge-nf-call-iptables=1` がすでに設定されていないかを確認するひつようあるね

上記の実行直後だとそのためのPodが準備されていないので

```
$ kubectl get pods --all-namespaces
NAMESPACE     NAME                               READY   STATUS     RESTARTS   AGE
kube-system   coredns-66bff467f8-d4fh5           0/1     Pending    0          2m57s
kube-system   coredns-66bff467f8-lqqfz           0/1     Pending    0          2m57s
kube-system   etcd-hrus-001                      1/1     Running    0          3m5s
kube-system   kube-apiserver-hrus-001            1/1     Running    0          3m5s
kube-system   kube-controller-manager-hrus-001   1/1     Running    0          3m5s
kube-system   kube-flannel-ds-arm-w4r96          0/1     Init:0/1   0          29s
kube-system   kube-proxy-wnprf                   1/1     Running    0          2m56s
kube-system   kube-scheduler-hrus-001            1/1     Running    0          3m5s
```

少し待ちますと、Podが全部Runningな状態になります。

```
$ kubectl get pods --all-namespaces
NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE
kube-system   coredns-66bff467f8-d4fh5           1/1     Running   0          4m34s
kube-system   coredns-66bff467f8-lqqfz           1/1     Running   0          4m34s
kube-system   etcd-hrus-001                      1/1     Running   0          4m42s
kube-system   kube-apiserver-hrus-001            1/1     Running   0          4m42s
kube-system   kube-controller-manager-hrus-001   1/1     Running   0          4m42s
kube-system   kube-flannel-ds-arm-w4r96          1/1     Running   0          2m6s
kube-system   kube-proxy-wnprf                   1/1     Running   0          4m33s
kube-system   kube-scheduler-hrus-001            1/1     Running   0          4m42s

$ kubectl get nodes
NAME       STATUS   ROLES    AGE    VERSION
hrus-001   Ready    master   5m5s   v1.18.2
```

最終的にSTATUSがReadyになっていれば大丈夫そうです。

## controllerとworkerの追加

1台目のcontrollerで `kubeadm init` したときに出てきたコマンドをrootで実行すれば良いです。楽ですね。

## 軽く動作確認

Kubernetes the Hard Wayのスモークテストを一部実行します（暗号化部分の確認はちょっとハードルが高いので `kubeadm` を信頼してパスｗ）

```
$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl get pods -l app=nginx
NAME                    READY   STATUS    RESTARTS   AGE
nginx-f89759699-j5wpp   1/1     Running   0          2m14s
$ POD_NAME=$(kubectl get pods -l app=nginx -o jsonpath="{.items[0].metadata.name}")
$ echo $POD_NAME
nginx-f89759699-j5wpp
$ kubectl port-forward $POD_NAME 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080

(ホスト内の他のターミナルで）
$ curl --head http://127.0.0.1:8080
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Wed, 06 May 2020 10:17:54 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes

$ kubectl logs $POD_NAME
127.0.0.1 - - [06/May/2020:10:17:54 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.64.0" "-"

$ kubectl exec -ti $POD_NAME -- nginx -v
nginx version: nginx/1.17.10
```

チュートリアルおわり。


## （参考） kubeadm reset による各環境のリセット

kubeadmでは`kubeadm init`なり`kubeadm join`なりをした後に環境を「ベストエフォートで」元に戻す`kubeadm reset`コマンドまで用意されています。すごいね

とはいえ全部やってくれるわけではなく、一部のファイルは残ったりしますので、一応いくらか手を動かす必要はあります。

```
$ sudo kubeadm reset
[reset] Reading configuration from the cluster...
[reset] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[reset] WARNING: Changes made to this host by 'kubeadm init' or 'kubeadm join' will be reverted.
[reset] Are you sure you want to proceed? [y/N]: y
[preflight] Running pre-flight checks
[reset] Removing info for node "hrus-002" from the ConfigMap "kubeadm-config" in the "kube-system" Namespace
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Deleting contents of config directories: [/etc/kubernetes/manifests /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]
[reset] Deleting contents of stateful directories: [/var/lib/etcd /var/lib/kubelet /var/lib/dockershim /var/run/kubernetes /var/lib/cni]

The reset process does not clean CNI configuration. To do so, you must remove /etc/cni/net.d

The reset process does not reset or clean up iptables rules or IPVS tables.
If you wish to reset iptables, you must do so manually by using the "iptables" command.

If your cluster was setup to utilize IPVS, run ipvsadm --clear (or similar)
to reset your system's IPVS tables.

The reset process does not clean your kubeconfig files and you must remove them manually.
Please, check the contents of the $HOME/.kube/config file.

$ rm $HOME/.kube/config
$ sudo rm /etc/cni/net.d
$ sudo su
# iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
```

## 学び

GCPでのKubernetes the Hard Way, on Vagrant, kubeadm + RasPi3B と3段階で構築を遊んでみました。なんとなく雑感を書いておきます

ホスト構築の面倒臭さは、当たり前ですが GCP << Vagrant << RasPi3B となります。RasPi3Bについては思い立ってから必要な物理的な部品を準備するのも含めてVagrantのさらに倍以上かかっている感じ。調べたり試行錯誤を含めて、GCP版が2〜3時間、on Vagrant版が割り込み等ありつつ1日、RasPi3Bがほぼ2日でした。これらに今後の「運用」の観点はないですが、ハードのトラブル考えるとこれ以上にRasPi3B（もしくはオンプレ構成)は大変でしょうねぇ。

※ ちなみにある種の「心理的安全性」だと Vagrant > RasPi3B >>> GCP です。LBはGCP任せとして、ほとんど空のインスタンスを6台ぶちかますのはホビーとしてはドキドキします。ちなみに200円くらいで済んだようで、「ちゃんと管理さえできれば」単なる杞憂なんですけど。

とはいうものの今回コロナ禍におけるGWに3構成全部試せるくらいの時間しかかかっていない、と考えると、全体としては十分「短い」時間でチュートリアルは終わっています。これは「設計思想とかを把握していればKubernetes自体は単発のインストールに対してとてもシンプル」ということの裏返しかと思います。

今回は「高可用な構成にしてみたかった」というだけでControl Plane 3台ありますが、LBが明らかに脆弱で、クラスタ的には明らかに価値がないです。実践でこうする意味はほとんどないと思います。`kubeadm` 的には実行するコマンドとケアするポイントが少し変わるので、練習としてはありだとは思いますが……。

当然、Control Plane 1台から3台へ行く時に当然激しい物理構成上のジャンプがあると思います。落ちてもいいよね、っていうサービスならControl Planeは信頼の置けるハードウェア上に1台としておく方がトラブルは少ないでしょうねぇ

遊ぶにしてももう少しやりようはありそうですが、一旦はハードウェア・構成面は以上です。後はソフト面です。

kubadmは「これは自動化できるよなぁ」とthe Hard Wayで思っていた部分を自動化してくれるのがよく分かります。数台規模の小規模構成であれば `kubeadm` をつきつめても別に問題ない気がします。

フルスクラッチの場合、自分で規約を決めないと作業過程や中間ファイルが整理されずに乱雑に散らばる、ということになりがちですが、 `kubeadm` を介すると概ねそのあたりはケアしてくれるので、他者にとっても見通しの良いインストール構成になるのかなと思います。 `kubectl`でクラスタの構成を把握しつつIP割当の台帳とか見つつ後はローカルの設定ファイルをKubernetes及び `kubeadm` の流儀で見ていけば概ね状況把握できる印象（セキュリティ的な観点はまた別にあると思いますが、Nodeのホスト類に普通の人が出入りすべきでないのはおそらく普通なので、大丈夫な気もする）

ただ、背後で何が起こるのか、については、the Hard Wayのように手でやるのと比べると格段に複雑になります。特に自動でホストやインターフェースの一意性を判定する、みたいな処理が入るときにMACアドレスの一意性だの `product_uuid` の一意性だの、手で心をこめてやるのとは違う制約が導入される感じ。当たり前ですけど

この点は「抽象化レイヤを入れるほどその抽象化レイヤにハマる」ということでもあるのかなと思われました。管理を容易にしようとミドルウェアを入れると、そのミドルウェアが判断に必要なポイント（あるいは故障点）を増やしてハマりポイントが増える。the Hard Wayだと判断ポイントもなにも、基本的には「配線する」レベル感で手で設定するだけなので、PKIとかそういう部分が理解できていればそこまで難しくない。

※ 例外がCNIかなぁ、という印象。CNIは今回のkubeadmでもハマりましたが、the Hard Wayでもそこまで丁寧に説明されておらず手を動かすだけになっていた印象。Pod単位でIP振られるという時点でかなり高度な暗黙の前提（設計）をKubernetesが設定レイヤに要求しているので、ここだけ高度に「物理配線」的でない印象を受けています。私が勉強したら、この問題は改善するかもしれません。

今回、個人的に特に困ったのは以下の点です。

 * `product_uuid` がないことをはじめ、それぞれの制約や注意事項の濃淡、つまり「`kubeadm`が動作するのか否か」の判断に使える情報が十分でない。
 * 暗黙にAMD64アーキテクチャで構築する想定でドキュメントが書かれていて、他のアーキテクチャ、特に今回は事故的にARMv7 (32bits)でどうなるか、というのはcaveatも含めて基本なにも書かれていない
 * Podネットワークadd-onの話が途中から登場するのだけど、結構前の段階で意思決定しないとハマる類の選択である。まぁ今回は全部ARMv7のせいかもしれないけど

上記に `kubedam reset` の項目があるのは実際にやったからです。とはいえ、`kubeadm`はここでもよく出来ていたのでとても良い感じでした。分別がよくついていて、ツールでやるべきこととやるべきでないことをはっきり区別しつつ、出力にそれを書いてくれるのは圧倒的にユーザフレンドリです。

クラスタを構築したとはいえ、これが正しく動作しているのか、自分の意図したとおりに動いているのか、長期間稼働させる際にどこが問題になるのか、といった側面はこれだけではあんまり学べないなぁ、という印象。

ちなみに v1.18.2 の現時点で、例えばReplicaSetで5台nginxを走らせて、その上でそのPodが乗っているWorker Nodeを物理的に電プチしたとします。すると、

```
$ k get pods
NAME                   READY   STATUS        RESTARTS   AGE
replicaset-exp-6lb48   1/1     Terminating   0          114m
replicaset-exp-gw4w4   1/1     Running       0          106m
replicaset-exp-hvtsz   1/1     Terminating   0          114m
replicaset-exp-qtvqq   1/1     Running       0          114m
replicaset-exp-w26rf   1/1     Running       0          106m
replicaset-exp-xfsrz   1/1     Running       0          106m
replicaset-exp-zksq6   1/1     Running       0          114m
```

いつまでたっても `Terminating` が消えないんですねぇ。消えないんだ、これ。まぁ、消えないか（他の資料だと `Unknown` だったりするんですが、現状のレベルだと違いがわからず）

そもそも電プチ後、Nodeが `NotReady` になるまで素朴な期待よりはるかに時間がかかるんですよね。これはデフォルト設定のタイムアウトが結構長いからだそうです。

https://fatalfailure.wordpress.com/2016/06/10/improving-kubernetes-reliability-quicker-detection-of-a-node-down/

現状ではクラスタ運用云々だけでなくそもそもGKE等でKubernetesを使い倒したと言えるレベル感でもなくて、ここから先をこの段階で突きつめるのはどうなんかな、と思う状態。一旦ここでやめておきます。

VM系と比較した場合の良い点として（当たり前ですが）ハードの制約に引きづられるのを体感出来るのは、まぁ、良いポイントですかね。RasPi3Bなんだから、64bitsで起動できるよね……なんでこんなままなの。