[outdated] rak8sを利用してRaspberry PiにKubernetes環境を構築する


> この記事で利用しているrak8sは更新されていませんので、Raspberry Pi上にKubernetes環境を構築する場合には、[k3s](https://k3s.io/) を利用してください。

# はじめに

Raspberry Piを利用してKubernetesクラスターを構築してみようと調べたところ、[rak8s.io](https://rak8s.io/)に行きあたったので試してみました。

結果としては、Kubernetesのバージョンが古かったり、微妙な不具合があったので、ansible taskを少し修正するなどしています。

# 環境

このセクションの作業は、ノートPCなどのコンピュータ側で行ないます。

## ansible実行環境
* Ubuntu 18.04.2 LTS (amd64版) on VMware Workstation 15
* PPA - Ansible

```bash:/etc/apt/sources.list.d/ansible-ubuntu-ansible-bionic.list
deb http://ppa.launchpad.net/ansible/ansible/ubuntu bionic main
deb-src http://ppa.launchpad.net/ansible/ansible/ubuntu bionic main
```

## Kubernetes用
* Raspberry Pi 3 and 3+ (IPv4 address: 192.168.1.77/24, 192.168.1.78/24)
* Raspbian Lite (2018-11-13版)

[rak8s.ioのgithubリポジトリ](https://github.com/rak8s/rak8s)をforkした[リポジトリ(https://github.com/YasuhiroABE/rak8s)](https://github.com/YasuhiroABE/rak8s)を公開しています。

次の要領で利用できます。

```bash
$ git clone https://github.com/YasuhiroABE/rak8s.git
$ cd rak8s
$ git checkout -b v1.13.5 origin/v1.13.5
```

# ansible-playbookコマンドを実行するための準備

この作業はRaspberry Piの全台で実施してください。

## 静的IPアドレスの設定

手元の環境はdnsmasqを動かしているので、MACアドレスから固定IPを割り当てるようにしています。

DHCPサーバーに手を入れることができないのであれば、/etc/dhcpcd.confファイルを変更します。

```text:/etc/dhcpcd.confの編集例

# Example static IP configuration:
interface eth0
static ip_address=192.168.0.10/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1
```

```$ ip addr```コマンドでIPアドレスが意図したアドレスに変更されていない場合は、確認を兼ねて再起動がお勧めです。

## Locale等の変更

配布されているRaspbian Liteのイメージから起動すると、sshサーバーは無効化されています。
そのため、あらかじめsshサーバーを起動しておくか、初回はHDMIケーブルとキーボードを接続し、raspi-configコマンド等からsshを有効化しておきます。

```bash:raspi-configの起動
$ sudo raspi-config
```

起動した後は、5.Interfacing Optionsを選択し、2番目のSSHからYESを選択し、サーバーを起動させます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/78296/02e9a1ff-4ab5-51db-95d7-930141d1d924.png)

Raspbian Liteのデフォルトロケールはイギリス英語なので、en_US.UTF-8がデフォルトになるように、4. Localisation Optionsを選択します。
遷移した画面で、Localeを変更し、en_GB.UTF-8を外し、en_US.UTF-8がデフォルトになるように変更しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/78296/9e86646c-61f1-7bee-cdb8-21fdcf780555.png)

お勧め設定として、GPUメモリをデフォルトの64MBから16MBに変更することが https://rak8s.io/ に記載されています。
この設定をしないとansible-playbookを実行した時にエラーになるので、変更しておきます。
"7. Advanced Options"に進み、"A3. Memory Split"メニューから変更します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/78296/6a06a704-6fdf-f66a-5c59-ff5d300622a9.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/78296/ad13048b-a581-2bdf-9f6f-3d70fadcf872.png)

Timezoneについては、ansibleがUTCに変更するので、何も設定していません。

## authorized_keysファイルの配置
パスワードなしにsshで実行できるように、ansible実行環境のUbuntuにある公開鍵をコピーしています。

今回はed25519形式を利用したかったので、新たに鍵を生成しています。
Raspberry Pi側で何もしていないと~/.ssh/ディレクトリが存在していないので、作成し、パスワードを入力して公開鍵を適切な場所にコピーしています。

```bash
$ ssh-keygen -t ed25519
...
$ ssh pi@192.168.1.77 mkdir --mode=700 .ssh
$ scp ~/.ssh/id_ed25519.pub pi@192.168.1.77:.ssh/authorized_keys
$ ssh pi@192.168.1.78 mkdir --mode=700 .ssh
$ scp ~/.ssh/id_ed25519.pub pi@192.168.1.78:.ssh/authorized_keys
```

## kernelのアップデート

デフォルトの4.14.98-v7+から、**4.19.30-v7+**に変更しています。

```bash
$ sudo rpi-update
```

# ansibleによるkubernetes環境の構築

ここからの作業はノートPCなどのコンピュータ側で行ないます。
あらかじめrak8sのディレクトリに移動しておきます。

## inventoryファイルの変更
まず、inventoryファイルを変更します。

```text:./inventoryファイル
[prod]
rak8s002    ansible_host=192.168.1.79
rak8s003    ansible_host=192.168.1.80

[master]
rak8s002
```

## group_vars/all.yml ファイルの修正

バージョンの指定を最新のv1.13.4に合わせます。
docker-ceは18.09でも動作確認はしているはずですが、ここでは18.06を指定しています。

all.ymlファイルにはpodnet変数が設定されていますが、途中で変更するとcleanup.ymlを使用しても、正常に変更が反映されずに残るようなので気をつけてください。

```diff:group_vars/all.yml
diff --git a/group_vars/all.yml b/group_vars/all.yml
index a4eb864..54053ff 100644
--- a/group_vars/all.yml
+++ b/group_vars/all.yml
@@ -1,7 +1,7 @@
 token: udy29x.ugyyk3tumg27atmr
 podnet: 10.244.0.0/16
 
-kubernetes_package_version: "1.11.4-00"
+kubernetes_package_version: "1.13.4-00"
 # Available versions:
 # 1.10.3-00
 # 1.10.2-00
@@ -12,7 +12,7 @@ kubernetes_package_version: "1.11.4-00"
 # 1.9.6-00
 # 1.9.5-00
 
-kubernetes_version: "v1.11.4"
+kubernetes_version: "v1.13.4"
 # Available versions:
 # v1.10.3
 # v1.10.2
@@ -23,7 +23,7 @@ kubernetes_version: "v1.11.4"
 # v1.9.6
 # v1.9.5
 
-docker_ce_version: "18.04.0~ce~3-0~raspbian"
+docker_ce_version: "18.06.3~ce~3-0~raspbian"
 # Available versions:
 # 18.05.0~ce~3-0~raspbian
 # 18.04.0~ce~3-0~raspbian
```

## roles/master/tasks/main.ymlファイルの修正

v1.13.4を前提にtask/main.ymlファイルを修正しています。

```diff:roles/master/tasks/main.yml
diff --git a/roles/master/tasks/main.yml b/roles/master/tasks/main.yml
index 99124bc..e957007 100644
--- a/roles/master/tasks/main.yml
+++ b/roles/master/tasks/main.yml
@@ -5,7 +5,7 @@
   register: kubeadm_reset
 
 - name: "Initialize Master {{ kubernetes_version }}"
-  shell: kubeadm init --apiserver-advertise-address={{ ansible_default_ipv4.address }} --token={{ token }} --kubernetes-version={{ kubernetes_version }} --pod-network-cidr={{ podnet }}
+  shell: kubeadm init --apiserver-advertise-address={{ ansible_default_ipv4.address }} --token={{ token }} --kubernetes-version={{ kubernetes_version }} --pod-network-cidr={{ podnet }} --ignore-preflight-errors=SystemVerification
   register: kubeadm_init
   when: kubeadm_reset is succeeded
 
@@ -34,7 +34,7 @@
   register: kubeadm_join
 
 - name: Install Flannel (Networking)
-  shell: "curl -sSL https://rawgit.com/coreos/flannel/{{ flannel_version }}/Documentation/kube-flannel.yml | sed 's/amd64/arm/g' | kubectl create -f -" 
+  shell: "curl -sSL https://raw.githubusercontent.com/coreos/flannel/{{ flannel_version }}/Documentation/kube-flannel.yml | sed 's/amd64/arm/g' | kubectl create -f -" 
 
 - name: Poke kubelet
   systemd:
```

## ansible-playbookコマンドの実行の前に

これまで試したところ、直接 ansible-playbook コマンドを実行すると、python-aptのタスクで停止しました。
そのため事前にapt-getコマンドを実行しています。

rak8sディレクトリの中にいる状態（カレントディレクトリにansible.cfgファイルがる状態）で、次のコマンドを実行します。

```bash
$ ansible all -m command -b -a "apt-get update"
$ ansible all -m command -b -a "apt-get -y dist-upgrade"
```

いろいろ変更されていると思うので、ここで再起動しておきます。

```bash
$ ansible all -m command -b -a "shutdown -r now"
```

## ansible-playbookコマンドの実行

引き続き、ansible実行側の環境で作業を進めます。

```bash
$ ansible-playbook cluster.yml
```

初回の起動は次のようなメッセージがでて、いくつかTaskが実行されて失敗するので、ノードを再起動してから続けます。

```ansible
TASK [common : Reboot Message] *******************************************************************************
ok: [rak8s002] => {
    "msg": "A reboot is required but the reboot module is a little wonky. Hopefully someone fixes this soon."
}
...
TASK [kubeadm : Run Docker 18.06.3~ce~3-0~raspbian Install Script] *******************************************
fatal: [rak8s002]: FAILED! => ...
... OR ...
TASK [master : Initialize Master v1.13.5] ********************************************************************
fatal: [rak8s000]: FAILED! => ...
```

最初のapt dist-upgradeの後で再起動する・しない、といった状況によって停止する場所は違いました。
再起動はansibleコマンドを使っています。

```bash
$ ansible all -m command -b -a "shutdown -r now"
```

しばらく待ってノードの稼動を```$ ansible all -m ping```などで確認しつつ、再度ansible-playbookコマンドを実行します。

```bash
$ ansible-playbook cluster.yml
```

確認した環境では、これでエラーなく完了しています。


# 稼動確認

podはあっても起動しているか、再起動を繰り返していないか、きちんと確認しておく必要があります。
最初にrak8sを利用した時は、いろいろ動かないところがあって苦労しました。

ただ全体が起動するまでに8分前後の時間がかかっています。

作業を簡単にするために、以下のようなaliasを利用しています。

```bash:~/k8s.envrc
alias kc="sudo kubectl -n kube-system"
alias kcg="kc get"
alias kcga="kcg all"
alias kcgan="kcga --all-namespaces"
alias kcd="kc describe"
```

2回目の```ansible-playbook cluster.yml```が終わった後で、5分程度経過してからkubectlコマンドを実行した結果は次のようになりました。

```bash:稼動確認の様子
$ . ~/k8s.envrc
$ kcg pod   ## "sudo kubectl -n kube-system get pod" のエイリアス
NAME                               READY   STATUS    RESTARTS   AGE
coredns-86c58d9df4-7bkfz           1/1     Running   0          3m51s
coredns-86c58d9df4-jzggq           1/1     Running   0          3m51s
etcd-rak8s000                      1/1     Running   0          3m44s
kube-apiserver-rak8s000            1/1     Running   1          4m7s
kube-controller-manager-rak8s000   1/1     Running   0          3m44s
kube-flannel-ds-arm-5hss9          1/1     Running   1          3m15s
kube-flannel-ds-arm-7vxwg          1/1     Running   0          3m40s
kube-proxy-frfhx                   1/1     Running   0          3m51s
kube-proxy-v9fgh                   1/1     Running   0          3m15s
kube-scheduler-rak8s000            1/1     Running   0          3m44s
```

ここでRESTARTSのカウントが上がり続けないか、2〜3分間隔を開けて確認します。

問題がなければ、coreDNSを中心に、きちんとコンテナが稼動することを確認するために、metallbとnginxをデプロイしてみます。

## metaillbのデプロイメント

あらかじめaliasを設定するためのファイルを準備しておきます。

```bash:~/k8s.envrc.metallb
test -f ~/k8s.envrc && . ~/k8s.envrc
alias kc="sudo kubectl -n metallb-system"
```

実際の作業は次のようになっています。

```bash
$ . ~/k8s.envrc.metallb
$ kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml
## 適当なエディタで次のような内容のlayer2-config.yamlファイルを作成する
$ cat layer2-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.1.83-192.168.1.84
$ kc apply -f layer2-config.yaml
$ kcga
NAME                              READY   STATUS    RESTARTS   AGE
pod/controller-7cc9c87cfb-wgn82   1/1     Running   0          12m
pod/speaker-5zp6x                 1/1     Running   0          12m

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/speaker   1         1         1       1            1           <none>          12m

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/controller   1/1     1            1           12m

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/controller-7cc9c87cfb   1         1         1       12m
```

## nginxによるmetallbの稼動確認

metallbと同様にaliasを設定するためのファイルを準備します。

```bash:~/k8s.envrc.nginx
test -f ~/k8s.envrc && . ~/k8s.envrc
alias kc="sudo kubectl -n nginx"
```

実際の作業は次のようになっています。

```bash
$ . ~/k8s.envrc.nginx
$ kc create ns nginx
$ cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.15
        ports:
        - containerPort: 80
$ kc apply -f deployment.yaml
$ cat service.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: LoadBalancer
$ kc apply -f service.yaml
$ kcga
NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-5fc86c987f-xcl8c   1/1     Running   0          13m

NAME            TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
service/nginx   LoadBalancer   10.105.192.198   192.168.1.83   80:30043/TCP   13m

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   1/1     1            1           13m

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-5fc86c987f   1         1         1       13m
```

EXTERNAL-IPがきちんとConfigMAPで指定したレンジから割り振られていれば完了です。

# この他のTips

rak8sでは毎回```$ sudo kubectl ...```とsudoで指定していますが、/root/.kube/config には必要なファイルはansibleで配置されています。

piユーザーのままでkubectlをsudoなしに実行したければ、同様にファイルをコピーすれば可能です。

```bash
$ mkdir -m 700 ~/.kube/
$ sudo cp /root/.kube/config ~/.kube/config
$ sudo chown -R pi:pi  ~/.kube/.
```

以上