Raspberry pi 3B+ 3台でkubernetesクラスタを構築したメモ (2019年8月版)

# Raspberry pi 3B+ 3台でkubernetesクラスタを構築したメモ (2019年8月版)

## 用意したパーツ

- Raspberry Pi 3B+ × 3
- 16GB MicroSDメモリーカード × 3
- Ethernetケーブル × 3
- USB充電ケーブル × 4
- スイッチングハブ(LAN-SW05PSWE) × 1 ※USBケーブルで給電できるタイプ
- RAVPower USB充電器 (60W 6ポート) × 1
- 4層のラズパイスタックケース × 1
- 外付けUSBケースに入れた128GB SSD
- モニタ
- HDMIケーブル
- USBキーボード

![image.png](https://qiita-image-store.s3.amazonaws.com/0/146181/ecbf3702-dd0e-b023-8aa6-2090cb90b0d4.png)

## はじめに

勉強用に複数台のラズパイを使ってk8sのクラスタを作りたく、過去にもチャレンジしていたのですが、最近ふとHypriot OSが1.11.0となってDockerのバージョンも上がっているのを見つけて、どうせならとKubernetes自体も最新バージョンで再構築してみました。

## OSイメージの準備

当方Windows PCで作業していますので、ラズパイOSイメージの書き込みは[Win32DiskImager](https://forest.watch.impress.co.jp/docs/review/1067836.html)とUSB接続のSDカードリーダ・ライタを使用しています。
[hypriot OSの公式サイト](https://blog.hypriot.com/)からダウンロードしたイメージをSDカードに書き込んだ後、再度PCに接続します。

ルートディレクトにある`user-data`を編集し、ホスト名など変更が必要な箇所を修正します。
マスターとする1台に対してはWiFiの有効化も一緒に実施してしまうと楽なので、[こちら](https://github.com/hypriot/flash/tree/master/sample)を参考に以下のように編集します。

``` yaml
#cloud-config
# vim: syntax=yaml
#

# Set your hostname here, the manage_etc_hosts will update the hosts file entries as well
hostname: rpi-k8s-1
manage_etc_hosts: true

# You could modify this for your own user information
users:
  - name: pirate
    gecos: "Hypriot Pirate"
    sudo: ALL=(ALL) NOPASSWD:ALL
    shell: /bin/bash
    groups: users,docker,video,input
    plain_text_passwd: hypriot
    lock_passwd: false
    ssh_pwauth: true
    chpasswd: { expire: false }

# # Set the locale of the system
# locale: "en_US.UTF-8"

# # Set the timezone
# # Value of 'timezone' must exist in /usr/share/zoneinfo
# timezone: "America/Los_Angeles"

# # Update apt packages on first boot
# package_update: true
# package_upgrade: true
# package_reboot_if_required: true
package_upgrade: false

# # Install any additional apt packages you need here
# packages:
#  - ntp

# WiFi connect to HotSpot
# - use `wpa_passphrase SSID PASSWORD` to encrypt the psk
write_files:
  - content: |
      allow-hotplug wlan0
      iface wlan0 inet dhcp
      wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
      iface default inet dhcp
    path: /etc/network/interfaces.d/wlan0
  - content: |
      country=JP
      ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
      update_config=1
      network={
      ssid="YOUR_SSID"
      psk="YOUR_PSK"
      key_mgmt=WPA-PSK
      }
    path: /etc/wpa_supplicant/wpa_supplicant.conf

# These commands will be ran once on first boot only
runcmd:
  # Pickup the hostname changes
  - 'systemctl restart avahi-daemon'

  # Activate WiFi interface
  - 'ifup wlan0'

```

残りのノード用はホスト名の部分だけ`rpi-k8s-2`,`rpi-k8s-3`・・・といった変更をしてSDカードに書き込みます。

## マスターの起動

マスターとするラズパイにWiFi有効化の設定を実施したSDカードを挿入し、OSを起動します。まだリモートからログインできないので、ラズパイに直接モニター、キーボードを接続して作業してください。

## ネットワーク回りの設定

マスターとするラズパイとノードとなる他のラズパイは以下のようなネットワークで構成することとします。

![raspberrypi-k8scluster.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146181/c129e4d6-80ba-4f69-edbd-1c0025e7449d.png)


### SSHの有効化

`sudo raspi-config`でSSHをenableするか、`sudo touch /boot/ssh`などを実施し、リブートします。

### WiFi設定

最初のOSイメージを準備したときに`user-data`で設定済みの場合、`/etc/wpa_supplicant/wpa_supplicant.conf`には無線LANルータ接続に必要なSSIDとPSKがされているはずです。

```
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=JP

network={
        scan_ssid=1
        ssid="YOUR_SSID"
        psk="YOUR_PSK"
        key_mgmt=WPA-PSK
}
```

SSHログイン用にIPアドレスを固定にします。
`/etc/network/interfaces.d/wlan0`を編集し、ご自身のネットワークに合わせて適切な固定IPアドレスを設定します。

```
allow-hotplug wlan0
#iface wlan0 inet dhcp
iface wlan0 inet static
    address 192.168.1.100
    netmask 255.255.255.0
    gateway 192.168.1.1
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet dhcp
```

設定が終わったらリブートし、192.168.1.100からSSHで入れるかを確認します。ログインできましたら、この先の作業はリモートから行っても良いかと思います。

### k8sクラスタで使用するローカルネットワークの設定

マスターがk8sノードに対してIPアドレスを自動で割り当てられるよう、DHCPサーバ(isc-dhcp-server)をインストールします。事前に`apt-get update`と`apt-get upgrade`を実行しておいてください。

#### isc-dhcp-serverのインストール

インストールは以下のコマンドで行います。インストール直後はエラーになって立ち上がらない状態になりますが、以降の設定で解消されるようにしていきます。

```shell
sudo apt-get install isc-dhcp-server
```

#### isc-dhcp-serverの設定

`/etc/default/isc-dhcp-server`を編集し、INTERFACESV4の欄に有線LAN(eth0)を追加します。

```
# Defaults for isc-dhcp-server (sourced by /etc/init.d/isc-dhcp-server)

# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
#DHCPDv4_CONF=/etc/dhcp/dhcpd.conf
#DHCPDv6_CONF=/etc/dhcp/dhcpd6.conf

# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
#DHCPDv4_PID=/var/run/dhcpd.pid
#DHCPDv6_PID=/var/run/dhcpd6.pid

# Additional options to start dhcpd with.
#       Don't use options -cf or -pf here; use DHCPD_CONF/ DHCPD_PID instead
#OPTIONS=""

# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
#       Separate multiple interfaces with spaces, e.g. "eth0 eth1".
INTERFACESv4="eth0"						★v4のインターフェースとしてeth0を追加
INTERFACESv6=""
```

#### 有線LANポートのIPアドレスを固定

`/etc/network/interfaces.d/eth0`を編集し、10.0.0.1のIPアドレスで固定されるよう変更します。

```
allow-hotplug eth0
iface eth0 inet static
    address 10.0.0.1
    netmask 255.255.255.0
    broadcast 10.0.0.255
    gateway 10.0.0.1
```

#### サブネット設定

`/etc/dhcp/dhcpd.conf`を以下のように編集し、DHCPサーバを設定します。

```
# dhcpd.conf
#
# Sample configuration file for ISC dhcpd
#

# option definitions common to all supported networks...
option domain-name "cluster.home";			★任意のドメイン名を指定
#option domain-name-servers ns1.example.org, ns2.example.org;
option domain-name-servers 192.168.1.1;		★ネットワークに合わせてDNSサーバを指定
subnet 10.0.0.0 netmask 255.255.255.0 {		★10.0.0.x系でサブネット設定
    range 10.0.0.1 10.0.0.10;
    option subnet-mask 255.255.255.0;
    option broadcast-address 10.0.0.255;
    option routers 10.0.0.1;
}
default-lease-time 600;
max-lease-time 7200;

# The ddns-updates-style parameter controls whether or not the server will
# attempt to do a DNS update when a lease is confirmed. We default to the
# behavior of the version 2 packages ('none', since DHCP v2 didn't
# have support for DDNS.)
ddns-update-style none;

# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
authoritative;								★コメントをはずす

・・・以下略
```

この時点でリブートするとDHCPサーバのエラーは解消されて問題なく立ち上がると思います。

#### WiFi経由でインターネット接続するための設定

ローカルネットワーク側に接続した機器(あとで増設するk8sノードなど)がインターネットと通信できるよう、以下の設定をします。

1. WiFi側とローカルネットワーク側の優先順位を変更  
   `/etc/dhcpcd.conf`に以下の内容を追加し、WiFi側を優先にします。  
    
    ```  
    # set priority
    interface eth0
    metric 1
    interface wlan0
    metric 0
    ```
    
2. IPフォワーディングの有効化  
   `/etc/sysctl.conf`にある以下の行がコメントアウトされているので有効化します。  
    
    ```  
    # Uncomment the next line to enable packet forwarding for IPv4  
    #net.ipv4.ip_forward=1				★コメントをはずす  
    ```  
3. iptables-persistentのインストール  
    再起動してもルールが残るように、iptables-persistentをインストールします。  
    
    ```shell  
    sudo apt-get -y install iptables-persistent  
    ```  
    インストール後、現在のルールを保存しますか？というメッセージが出てきたら、Yesを選択して、ルールファイルを作っておきます。
4. ルールの追加  
    以下のコマンドを実行し、eth0からwlan0(その逆も)へフォワードされるよう、iptablesのルールを追加します。  
    
    ```shell  
    sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE  
    sudo iptables -A FORWARD -i wlan0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT  
    sudo iptables -A FORWARD -i eth0 -o wlan0 -j ACCEPT  
    ```  
    変更した設定は以下のコマンドで保存しておきます。  
    
    ```shell  
    sudo iptables-save | sudo tee -a /etc/iptables/rules.v4 > /dev/null  
    ```  

## Kubernetesのインストール

マスターへのk8sのインストールとノードの追加に関しては、[ラズパイでKubernetesを動かして見たら、驚いた！](https://qiita.com/MahoTakara/items/2b39e06f077927bafa2c)の記事を参考に一通り実施しました。

`apt-cache madison kubeadm`を実行し、利用できるバージョンを調べます。本記事の執筆時点では1.15.2が最新(kubernetes-cniは0.7.5)でしたので、それをインストールします。

```shell
sudo su -
apt-get update && apt-get install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install kubelet=1.15.2-00 kubeadm=1.15.2-00 kubectl=1.15.2-00 kubernetes-cni=0.7.5-00
```

参考にさせていただいたサイトにはKUBELET_DNS_ARGSの設定を変更する説明があったのですが、該当のファイルに設定項目がなかったため、とりあえずスキップしています。

kubeletを再起動します。

``` shell
systemctl daemon-reload && systemctl restart kubelet
```

参考サイトにならって、マスターノードの初期化を行います。

``` shell
kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.1.100 --service-cidr=10.244.0.0/16
```

結構時間がかかりますが、しばらくすると初期化成功のメッセージが出ます。メッセージ内の指示通り、スパーユーザから一般ユーザに戻って以下の3行を実行します。このときにノード追加用のコマンドラインも出力されるため、メッセージは保存しておくことをオススメします。

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

ポッド・ネットワークの作成についても参考サイトの通り実行するのですが、flannelのバージョンは0.11.0が最新でしたのでそちらを使います。中身の置換はオライリー本に従って以下のコマンドで書き換えます。

```shell
curl https://raw.githubusercontent.com/coreos/flannel/v0.11.0/Documentation/kube-flannel.yml | sed "s/amd64/arm/g" | sed "s/vxlan/host-gw/g" > kube-flannel-arm.yml
```

ymlファイルができたら、以下のコマンドで適用します。

```shell
kubectl apply -f kube-flannel-arm.yml
```

マスターがReady状態になったときのコマンド結果が若干違っていたので、自分が実行した際の出力結果を参考に載せておきます。

```
$ kubectl get po -n kube-system
NAME                                READY   STATUS    RESTARTS   AGE
coredns-5c98db65d4-vb8jk            1/1     Running   0          92m
coredns-5c98db65d4-xpx9r            1/1     Running   0          92m
etcd-rpi-k8s-1                      1/1     Running   0          92m
kube-apiserver-rpi-k8s-1            1/1     Running   0          92m
kube-controller-manager-rpi-k8s-1   1/1     Running   0          92m
kube-flannel-ds-arm-x4lqb           1/1     Running   0          3m45s
kube-proxy-k2x9x                    1/1     Running   0          92m
kube-scheduler-rpi-k8s-1            1/1     Running   0          92m
$ kubectl get node
NAME        STATUS   ROLES    AGE   VERSION
rpi-k8s-1   Ready    master   93m   v1.15.2
```

以上でマスタの設定は完了です。

### flannelのpodがErrorとなって解消されないとき

マスターをリセットしたときに同じ手順でポッド・ネットワークを構築しても、flannelのpodがError→CrashLoopBackOffのループに入ってしまうことがありました。
`kubeadm reset`→`kubeadm init`で解消されなかったので、kubenetesを一旦アンインストールして再インストールしたところ、事象は解消されました。

```shell
apt-get remove --purge kubelet=1.15.2-00 kubeadm=1.15.2-00 kubectl=1.15.2-00 kubernetes-cni=0.7.5-00
```

リセットだけだとifconfigで表示される中にflannelの設定が残っていたのが気になりましたが、原因を調べるのが大変そうだったので、根本解決ではありませんが再インストールで回避しました。

※2019/08/18追記
flannelのインターフェイスが残っていることの言及や対処方法については、参考サイトにあります[Raspberry PiでおうちKubernetes構築【論理編】](https://qiita.com/go_vargo/items/29f6d832ea0a289b4778)に書かれていますので参考にしてください。

## ノードの追加

### ノードの起動と初期設定

ノード用に`user-data`を編集したOSイメージ(SDカード)を挿入して起動すれば、10.0.0.x系に組み込まれて起動されるはずです。初期状態ではSSHログインなどができないため、モニターとキーボードを繋いで作業しますが、ついでにk8sノードとしての設定も行ってしまいます。

- SSHの有効化
  `sudo touch /boot/ssh`

- k8sパッケージのインストール
  `apt-get install kubelet=1.15.2-00 kubeadm=1.15.2-00 kubectl=1.15.2-00 kubernetes-cni=0.7.5-00`

- kubeletの再起動
  `systemctl daemon-reload && systemctl restart kubelet`

マスターで`kubeadm init・・・`を実行したときにメッセージに出力された`kubeadm join・・・`を各ノードで実行していきます。

マスターをリセットする場合は子ノードもリセットが必要なので忘れないようにしてください。

### 確認

ノード側でのコマンド実行が完了したら、マスター上で稼働状態を確認します。

```
$ kubectl get po -n kube-system
NAME                                READY   STATUS    RESTARTS   AGE
coredns-5c98db65d4-vb8jk            1/1     Running   0          8h
coredns-5c98db65d4-xpx9r            1/1     Running   0          8h
etcd-rpi-k8s-1                      1/1     Running   0          8h
kube-apiserver-rpi-k8s-1            1/1     Running   0          8h
kube-controller-manager-rpi-k8s-1   1/1     Running   0          8h
kube-flannel-ds-arm-4b9dp           1/1     Running   2          6m52s
kube-flannel-ds-arm-mnmzr           1/1     Running   0          3m3s
kube-flannel-ds-arm-x4lqb           1/1     Running   0          6h59m
kube-proxy-k2x9x                    1/1     Running   0          8h
kube-proxy-p8mzs                    1/1     Running   0          6m52s
kube-proxy-rdr7z                    1/1     Running   0          3m3s
kube-scheduler-rpi-k8s-1            1/1     Running   0          8h
$ kubectl get node
NAME        STATUS   ROLES    AGE     VERSION
rpi-k8s-1   Ready    master   8h      v1.15.2
rpi-k8s-2   Ready    <none>   6m43s   v1.15.2
rpi-k8s-3   Ready    <none>   2m54s   v1.15.2
```

以上でノードの追加も完了です。

## 最後に

ラズパイ上にKubernetesを構築するにあたっては、何度かバージョンの組み合わせなどでうまく動作しない状況に陥ったのですが、今回はうまくいったので「おうちKubernetes」を構築しようとお考えの方の参考になれば幸いです。

さて、こいつを使って何をしようか？というところを考えねば・・・。今後試したことで何か面白そうなことができましたらまた記事にしたいと思います。

## 参考リンク

本記事を書くにあたって参考にさせていただいたサイトや書籍を挙げさせていただきます。

- [ラズパイでKubernetesを動かして見たら、驚いた！](<https://qiita.com/MahoTakara/items/2b39e06f077927bafa2c>)
- [Raspberry PiでおうちKubernetes構築【論理編】](https://qiita.com/go_vargo/items/29f6d832ea0a289b4778)
- オライリー「入門 Kubernetes」([Amazon](https://www.amazon.co.jp/dp/4873118409/ref=cm_sw_em_r_mt_dp_U_QQ3vDbXTT83X1))
- HypriotOS本家サイト https://blog.hypriot.com/
- [Raspberry PI で NAT ルータを作る](https://momijiame.tumblr.com/post/89661497311/raspberry-pi-%E3%81%A7-nat-%E3%83%AB%E3%83%BC%E3%82%BF%E3%82%92%E4%BD%9C%E3%82%8B)