ラズパイクラスタにKubernetesをインストール(成功編)

「ラズパイクラスタにKubernetesをインストール(失敗編)」でCentOSを選んだためDockerのインストールに失敗したので、今度はUbuntuで試してみる。

今回の作業内容

全体の作業概要

Dockerをインストールしようとすると依存関係のエラーが出て、その解決方法を調べるのに時間がとられた。

OSイメージをMicroSDに焼く

UbuntuのwikiからOSイメージをダウンロードする。

公式イメージはPi2用しか配布されていないのでPi3にインストールするためには一手間が必要になる。一応Unofficialとして個人が公開しているPi3用のイメージへのリンクがwikiに貼られている。

いろいろ面倒そうなので公式イメージを使うことにした。

ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img.xzMacにダウンロードしてxzコマンドで解凍する。

$ xz -d ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img.xz 
$ ls
ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img

MicroSDカードをUnMountしてからSDカードへ書き込む。1枚書き込むのにだいたい30分かかる。

$ diskutil unmountDisk /dev/disk2
Unmount of all volumes on disk2 was successful
$ sudo dd bs=1m if=./ubuntu-18.04.1-preinstalled-server-armhf+raspi2.img of=/dev/disk2
2252+0 records in
2252+0 records out
2361393152 bytes transferred in 1949.493602 secs (1211285 bytes/sec)

このOSイメージはPi2用なので、Pi3用で動かすためには

  • dbtファイルの置き換え
  • config.txtの書き換え

が必要。

また、Pi3 B+で動かすためにはさらにbootloader用にbootcode.bin, fixup.dat and start.elfを置き換える必要がある。

dbtファイルを含めて必要なファイルはGithubraspberrypi/firmwareリポジトリで公開されている。

wgetでダウンロードする。

$ wget https://github.com/raspberrypi/firmware/raw/master/boot/bcm2710-rpi-3-b-plus.dtb
$ wget https://github.com/raspberrypi/firmware/raw/master/boot/bootcode.bin
$ wget https://github.com/raspberrypi/firmware/raw/master/boot/fixup.dat
$ wget https://github.com/raspberrypi/firmware/raw/master/boot/start.elf

MicroSDカードが/Volume/system-bootにマウントされているとして、ダウンロードしたファイルをコピーする

$ cp bcm2710-rpi-3-b-plus.dtb /Volumes/system-boot/
$ cp start.elf /Volumes/system-boot/
$ cp bootcode.bin /Volumes/system-boot/
$ cp fixup.dat /Volumes/system-boot/

次に、/Vomue/system-boot/config.txtの中身を以下のように変更する。

# For more options and information see
# http://www.raspberrypi.org/documentation/configuration/config-txt.md
# Some settings may impact device functionality. See link above for details

# コメントアウト
#kernel=uboot.bin
#device_tree_address=0x02000000

# 追記
kernel=vmlinuz
initramfs initrd.img followkernel

終わったらSDカードを取り出す。

$ diskutil eject /dev/disk2
Disk /dev/disk2 ejected

OSをインストールする

MicroSDカードを刺した状態で電源をいれるとOSのインストールが始まる。

ログインプロンプトが表示されたら

アカウント名:ubuntu

パスワード: ubuntu

でログインしてすぐにパスワードの初期化を求められるので実施する。

  • ホスト名の設定: hostname XXX

続いて、ネットワークの設定をしてパッケージを更新する。

  • eth0にアドレスを割り当てる

eth0へのネットワークの割り当てはsudo dhclient eth0でできる。

続いて、パッケージを更新する。

  • sudo apt update
  • sudo apt upgrade -y
    • 途中、keyboad-configurationの設定を聞かれる.

上記を3台に実施する。

ホスト名とIPアドレス、それぞれの役割は以下の通り。

ホスト名 IPアドレス 役割
compute-0-0 192.168.10.109 KubernetesのMaster
compute-0-1 192.168.10.108 KubernetesのNode1
compute-0-2 192.168.10.107 KubernetesのNode2

このままだとネットワークの名前解決ができないので、各ホストの/etc/hostsに以下の内容を追記する。

192.168.10.109 compute-0-0
192.168.10.108 compute-0-1
192.168.10.107 compute-0-2

Dockerのインストール

インストールしたUbuntuはarmhf(32bit版)として動作している。docker-ceの推奨は64bitだがUbuntuであればarmhfをサポートしているので問題ない。

$ sudo apt-get update

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# armv7lなのでarmhfアーキ用を実行
$ sudo add-apt-repository \
   "deb [arch=armhf] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

$ sudo apt-get update

$ sudo apt-get install docker-ce

$ docker --version
Docker version 18.09.0, build 4d60db4

ubuntuユーザがdockerコマンドを実行するたびにsudoが必要である。

sudoがなくてもdockerコマンドを実行できるようにubuntuユーザをdockerグループに追加しておく。

$ sudo gpasswd -a ubuntu docker
Adding user ubuntu to group docker
$ sudo systemctl restart docker

なお、依存関係のエラーでdocker-ceのインストールに失敗することがあった。

その場合はapt-utilの依存関係を修復する。

$ sudo apt-get install docker-ce
Reading package lists... Done
Building dependency tree       
Reading state information... Done
You might want to run 'apt --fix-broken install' to correct these.
The following packages have unmet dependencies:
 apt-utils : Depends: apt (= 1.6.3) but 1.6.6 is to be installed
 docker-ce : Depends: docker-ce-cli but it is not going to be installed
             Depends: containerd.io but it is not going to be installed
             Recommends: aufs-tools but it is not going to be installed
             Recommends: cgroupfs-mount but it is not going to be installed or
                         cgroup-lite but it is not going to be installed
             Recommends: pigz but it is not going to be installed
             Recommends: libltdl7 but it is not going to be installed
E: Unmet dependencies. Try 'apt --fix-broken install' with no packages (or specify a solution).

$ sudo apt --fix-broken install
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Correcting dependencies... Done
The following additional packages will be installed:
  apt-utils
The following packages will be upgraded:
  apt-utils
1 upgraded, 0 newly installed, 0 to remove and 89 not upgraded.
16 not fully installed or removed.
Need to get 0 B/192 kB of archives.
After this operation, 0 B of additional disk space will be used.
Do you want to continue? [Y/n] Y
...

修復が完了したらもう一度docker-ceをインストールする。

$ sudo apt-get install docker-ce
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following additional packages will be installed:
  aufs-tools cgroupfs-mount containerd.io docker-ce-cli libltdl7 pigz
The following NEW packages will be installed:
  aufs-tools cgroupfs-mount containerd.io docker-ce docker-ce-cli libltdl7
  pigz
0 upgraded, 7 newly installed, 0 to remove and 89 not upgraded.
1 not fully installed or removed.
Need to get 29.7 MB of archives.
After this operation, 149 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
...

Kubernetesをインストールする

Kubernetesは1.8からSwapが有効になっているとkubeletが起動しなくなっている。 今回の環境はSwap領域は存在しないので気にしてくていいが、もしSwapが有効になっていれば無効化が必要。

# swap領域が0になっている
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           923M        138M         84M        4.4M        701M        764M
Swap:            0B          0B          0B
# 念のためSwapの有無を確認
$ swapon -s

続いて、公式ドキュメントに従って全ホストでkubeadm, kubelet, kubectlをインストールする。

# rootになる
sudo su -
apt-get update && apt-get install -y apt-transport-https curl
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 https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

インストールしたパッケージのバージョンを確認する。

# dpkg -l |grep kube
ii  kubeadm                          1.13.1-00                          armhf        Kubernetes Cluster Bootstrapping Tool
ii  kubectl                          1.13.1-00                          armhf        Kubernetes Command Line Tool
ii  kubelet                          1.13.1-00                          armhf        Kubernetes Node Agent
ii  kubernetes-cni                   0.6.0-00                           armhf        Kubernetes CNI

続いて、Kubernetesクラスタのネットワークを設計する。

Kubernetesはoerlay networkを構築する必要がある。そのためのツールとして有名なところではflannnel, weave, Calicoなどがある。

今回はARMプロセッサの上で動くUbuntu 18.04の32bit版で動作することが要件になる。

Calicoの要件AMD64プロセッサであること。Ubuntuの32bitで動作するか不安なので見送る。

WeaveはARMをサポートしたバイナリをリリースしているものの、32bitに対応しているか明記されていなかったこととWeaveをラズパイ3B+で動かしたときにカーネルに起因するバグがあるissueを見つけたので見送る。

そこで、今回はflannelを採用することにした。

先ほど書いたようにホストは192.168.10.0/24で設定されている。

flannelが環境構築のために公開しているマニフェストは初期値が10.2544.0.0/16になっている。Pod IPのCIDRは10.244.0.0/16を割り当てることにした。違うCIDRを割り当てたい場合は、後述するflannelの環境構築時にgithub上のマニフェストを指定するのではなく、ローカルにダウンロードしたマニフェストを編集して利用すればいい。

KubernetesのMasterを起動するため、compute-0-0でkubeadm initを実行する。

$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
[preflight] The system verification failed. Printing the output from the verification:
KERNEL_VERSION: 4.15.0-1017-raspi2
CONFIG_NAMESPACES: enabled
CONFIG_NET_NS: enabled
CONFIG_PID_NS: enabled
CONFIG_IPC_NS: enabled
CONFIG_UTS_NS: enabled
CONFIG_CGROUPS: enabled
CONFIG_CGROUP_CPUACCT: enabled
CONFIG_CGROUP_DEVICE: enabled
CONFIG_CGROUP_FREEZER: enabled
CONFIG_CGROUP_SCHED: enabled
CONFIG_CPUSETS: enabled
CONFIG_MEMCG: enabled
CONFIG_INET: enabled
CONFIG_EXT4_FS: enabled
CONFIG_PROC_FS: enabled
CONFIG_NETFILTER_XT_TARGET_REDIRECT: enabled (as module)
CONFIG_NETFILTER_XT_MATCH_COMMENT: enabled (as module)
CONFIG_OVERLAY_FS: enabled (as module)
CONFIG_AUFS_FS: enabled (as module)
CONFIG_BLK_DEV_DM: enabled
DOCKER_VERSION: 18.09.0
OS: Linux
CGROUPS_CPU: enabled
CGROUPS_CPUACCT: enabled
CGROUPS_CPUSET: enabled
CGROUPS_DEVICES: enabled
CGROUPS_FREEZER: enabled
CGROUPS_MEMORY: missing
    [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 18.09.0. Latest validated version: 18.06
error execution phase preflight: [preflight] Some fatal errors occurred:
    [ERROR SystemVerification]: missing cgroups: memory
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`

cgroupのmemoryが無効化されているらしい。/proc/cgroupsを確認するとたしかにmemoryのenabledが0(無効)になっている。

$ cat /proc/cgroups 
#subsys_name    hierarchy   num_cgroups enabled
cpuset  10  2   1
cpu 6   60  1
cpuacct 6   60  1
blkio   7   60  1
memory  0   68  0
devices 4   60  1
freezer 2   2   1
net_cls 3   2   1
perf_event  5   2   1
net_prio    3   2   1
pids    8   66  1
rdma    9   1   1

boot時の設定を変更してcgroupfsのmemoryを有効にする。

/boot/firmware/cmdline.txtcgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memoryを追記して再起動する。追記が終わったあとのファイルは以下の通り。

$ cat /boot/firmware/cmdline.txt 
net.ifnames=0 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory

# reboot

再起動したあとはeth0に割り当てられたIPが消えているのでsudo dhclient eth0を実行する。

memoryサブシステムが有効になっているのがわかる。

$ cat /proc/cgroups 
#subsys_name    hierarchy   num_cgroups enabled
cpuset  2   1   1
cpu 5   34  1
cpuacct 5   34  1
blkio   4   34  1
memory  7   68  1
devices 11  34  1
freezer 9   1   1
net_cls 3   1   1
perf_event  8   1   1
net_prio    3   1   1
pids    6   39  1
rdma    10  1   1

再度Kubernetesをインストールしなおす.

$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[init] Using Kubernetes version: v1.13.1
[preflight] Running pre-flight checks
    [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 18.09.0. Latest validated version: 18.06
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Activating the kubelet service
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [ubuntu kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.10.109]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [ubuntu localhost] and IPs [192.168.10.109 127.0.0.1 ::1]
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [ubuntu localhost] and IPs [192.168.10.109 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[kubelet-check] Initial timeout of 40s passed.
[apiclient] All control plane components are healthy after 85.513804 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.13" in namespace kube-system with the configuration for the kubelets in the cluster
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "ubuntu" as an annotation
[mark-control-plane] Marking the node ubuntu as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node ubuntu as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: ntvqxo.ghxfq7gvi8rc79aw
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master 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 machines by running the following on each node
as root:

  kubeadm join 192.168.10.109:6443 --token ntvqxo.ghxfq7gvi8rc79aw --discovery-token-ca-cert-hash sha256:709766a97f016e2a0fd5113b12ce5afa1cebc740ecfa6596f8bbe9f30cb5897f

kubectlに認証設定を読み込ませるために指定されたコマンドを実行する。

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

動作していることが確認できる。

$ kubectl get node
NAME          STATUS   ROLES    AGE   VERSION
compute-0-0   NotReady   master   23m   v1.13.1

STATUSがNodeReadyなのはOveray Networkが設定されていないから。flannelをインストールする。

$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.extensions/kube-flannel-ds-amd64 created
daemonset.extensions/kube-flannel-ds-arm64 created
daemonset.extensions/kube-flannel-ds-arm created
daemonset.extensions/kube-flannel-ds-ppc64le created
daemonset.extensions/kube-flannel-ds-s390x created

# STATUSがReadyになった
$ kubectl get node
NAME          STATUS   ROLES    AGE   VERSION
compute-0-0   Ready    master   34m   v1.13.1

Nodeになるcompute-0-1とcompute-0-2で以下を実行する。

$ sudo kubeadm join 192.168.10.109:6443 --token ntvqxo.ghxfq7gvi8rc79aw --discovery-token-ca-cert-hash sha256:709766a97f016e2a0fd5113b12ce5afa1cebc740ecfa6596f8bbe9f30cb5897f 
[preflight] Running pre-flight checks
    [WARNING SystemVerification]: this Docker version is not on the list of validated versions: 18.09.0. Latest validated version: 18.06
[discovery] Trying to connect to API Server "192.168.10.109:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://192.168.10.109:6443"
[discovery] Requesting info from "https://192.168.10.109:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "192.168.10.109:6443"
[discovery] Successfully established connection with API Server "192.168.10.109:6443"
[join] Reading configuration from the cluster...
[join] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet] Downloading configuration for the kubelet from the "kubelet-config-1.13" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Activating the kubelet service
[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "compute-0-1" as an annotation

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.

compute-0-0からkubectl get nodeを実行するとNodeがクラスタとして認識されていることを確認できる。

$ kubectl get node
NAME          STATUS   ROLES    AGE   VERSION
compute-0-2   Ready    <none>   73s   v1.13.1
compute-0-1   Ready    <none>   75s   v1.13.1
compute-0-0        Ready    master   39m   v1.13.1

以上でKubernetesクラスタの構築ができました。

CentOSのコンテナを作ってみる。

$ kubectl run tmp-centos --rm -it --image centos -- bash
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
If you don't see a command prompt, try pressing enter.
[root@tmp-centos-cc49649b6-kpfvg /]# 

Deploymentが動くことも確認できた。

まとめ

Kubernetesクラスタのインストールは何度もやったことがあったので、初のラズパイといってもまぁすぐに終わるだろうと思っていたら、実家のネットワーク設定によるものやSDカードの不具合によるもの、OSの選定、OSインストール後の謎の個体差などなどでかなり時間がかかってしまった。

他にも実はここに書ききれない細かなトライ&エラーを繰り返している。Ubuntu 18系を使うのは初めてだったので、原因がOSのバージョン、カーネルのバージョン、ラズパイのどれに起因するものなのかの切り分けが大変だった。

トラブルの原因を調べて解決することを繰り返すことで知識がつくので、これはこれで新年からいい経験ができたと思う。

参考サイト