RaspberryPi
kubernetes

RaspberryPiにkubernetesをセットアップ

Qiita内でも何件かRaspberryPiにkubernetesをセットアップする記事が上がっているので、簡単に行くものだと思っていたのですが、非常に苦労したので、備忘を含めて記録しておこうと思います。

前提

今回はRaspberry Pi 3B+に2018/06/27版のraspbian-strech-liteを使用しました。RaspberryPiのセットアップ手順は省略します。
kubernetesはkubeadm initを使って初期設定を行う手順になります。
文章内で明示しない限りはpiユーザを使って作業を行っています。

セットアップ

手順としては

  • dockerのインストール
  • kubeadmを使えるようにする
  • kubernetes環境の初期化

となります。

dockerのインストール

dockerのインストールは公式の手順に則って行います。
詳細はこのあたりを参考に、以下の手順でインストールします。
今回インストール時はdockerのバージョンが18.06.1-ceでした。

$ sudo apt-get install software-properties-common
$ curl -fsSL https://download.docker.com/linux/raspbian/gpg | sudo apt-key add -
$ echo "deb [arch=armhf] https://download.docker.com/linux/raspbian stretch stable" |  sudo tee /etc/apt/sources.list.d/docker.list
$ sudo apt-get update
$ sudo apt-get install docker-ce

kubeadmを使えるようにする

kubeadm,kubectl,kubeletをインストールします。
公式な手順はこのあたりを参考にします。
執筆時は丁度v1.12.0-rc1やらrc2やらが出始めたところでうまく動かない部分もあったのでv1.11.3に固定します。

$ curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install kubelet=1.11.3-00 kubeadm=1.11.3-00 kubectl=1.11.3-00

そのままコマンドを実行しようとするとエラーになるため、cgroupの設定とswapの設定をします。
cgroupの設定は、/boot/cmdline.txtファイルにcgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1を追加します。
swapの設定は以下のコマンドを実行します。

$ sudo dphys-swapfile swapoff
$ sudo dphys-swapfile uninstall
$ sudo update-rc.d dphys-swapfile remove

カーネルの起動オプション変更があるため再起動しておきましょう。

kubernetes masterノードの初期化にはまる

ここで公式手順通りに進めるとkubeadm initでmasterの初期化を行います。
flannelを使うため、--pod-network-cidr=10.244.0.0/16のオプションのみ使用します。
やってみました・・・

$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
[init] using Kubernetes version: v1.11.3
[preflight] running pre-flight checks
I0918 03:00:29.587034     789 kernel_validator.go:81] Validating kernel version
I0918 03:00:29.587889     789 kernel_validator.go:96] Validating kernel config
        [WARNING SystemVerification]: docker version is greater than the most recently validated version. Docker version: 18.06.1-ce. Max validated version: 17.03
[preflight/images] Pulling images required for setting up a Kubernetes cluster
[preflight/images] This might take a minute or two, depending on the speed of your internet connection
[preflight/images] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[preflight] Activating the kubelet service
[certificates] Generated ca certificate and key.
[certificates] Generated apiserver certificate and key.
[certificates] apiserver serving cert is signed for DNS names [vpod1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.0.xx]
[certificates] Generated apiserver-kubelet-client certificate and key.
[certificates] Generated sa key and public key.
[certificates] Generated front-proxy-ca certificate and key.
[certificates] Generated front-proxy-client certificate and key.
[certificates] Generated etcd/ca certificate and key.
[certificates] Generated etcd/server certificate and key.
[certificates] etcd/server serving cert is signed for DNS names [vpod1 localhost] and IPs [127.0.0.1 ::1]
[certificates] Generated etcd/peer certificate and key.
[certificates] etcd/peer serving cert is signed for DNS names [vpod1 localhost] and IPs [192.168.0.xx 127.0.0.1 ::1]
[certificates] Generated etcd/healthcheck-client certificate and key.
[certificates] Generated apiserver-etcd-client certificate and key.
[certificates] valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"
[controlplane] wrote Static Pod manifest for component kube-apiserver to "/etc/kubernetes/manifests/kube-apiserver.yaml"
[controlplane] wrote Static Pod manifest for component kube-controller-manager to "/etc/kubernetes/manifests/kube-controller-manager.yaml"
[controlplane] wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"
[etcd] Wrote Static Pod manifest for a local etcd instance to "/etc/kubernetes/manifests/etcd.yaml"
[init] waiting for the kubelet to boot up the control plane as Static Pods from directory "/etc/kubernetes/manifests"
[init] this might take a minute or longer if the control plane images have to be pulled

                Unfortunately, an error has occurred:
                        timed out waiting for the condition

                This error is likely caused by:
                        - The kubelet is not running
                        - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)
                        - No internet connection is available so the kubelet cannot pull or find the following control plane images:
                                - k8s.gcr.io/kube-apiserver-arm:v1.11.3
                                - k8s.gcr.io/kube-controller-manager-arm:v1.11.3
                                - k8s.gcr.io/kube-scheduler-arm:v1.11.3
                                - k8s.gcr.io/etcd-arm:3.2.18
                                - You can check or miligate this in beforehand with "kubeadm config images pull" to make sure the images
                                  are downloaded locally and cached.

                If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
                        - 'systemctl status kubelet'
                        - 'journalctl -xeu kubelet'

                Additionally, a control plane component may have crashed or exited when started by the container runtime.
                To troubleshoot, list all containers using your preferred container runtimes CLI, e.g. docker.
                Here is one example how you may list all Kubernetes containers running in docker:
                        - 'docker ps -a | grep kube | grep -v pause'
                        Once you have found the failing container, you can inspect its logs with:
                        - 'docker logs CONTAINERID'
couldn't initialize a Kubernetes cluster

失敗しました。

apiserverの再起動問題

言われた通り、systemctl status kubeletを確認してみるのですが、Activeで問題ありません。
docker ps -a | grep kube | grep -v pauseを確認してみると、kube-apiserverが数分で落ちて再起動がかかる状況が繰り返されているようです。
とりあえずこの状況を何とかしなければならなそうなのですが、最初原因がわかりませんでした。そこでkubeadm init -v 10で様子を探ってみました。するとどうやらヘルスチェックをしようとしてタイムアウトしているようでした。
検索してみた結果、どうやらヒットしそうなバグ?をここで見つけました。
結果、上のログにある[controlplane] wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"が出力された直後からapiserverが立ち上がるまでの間に別ターミナルから以下のコマンドで回避しました。

$ sudo sed -i 's/initialDelaySeconds: 15/initialDelaySeconds: 300/g' /etc/kubernetes/manifests/kube-apiserver.yaml

これでapiserverは落ちなくなったので、セットアップ完了・・・かと思いきや、また同じ止まり方をしました。

kubeadm initのヘルスチェックが早すぎる問題

再度kubeadm init -v 10で様子を見てみたのですが、ヘルスチェックでタイムアウトしている状況は変わりません。
ヘルスチェックに使用しているコマンドcurl -k -v -XGET -H "Accept: application/json, */*" -H "User-Agent: kubeadm/v0.0.0 (linux/arm) kubernetes/$Format" 'https://192.168.0.xx:6443/healthz?timeout=32s'を叩いてみたところ、応答が安定するまで10分近くかかりました。どうやらkubeadm initがそこまで待ってくれないようです。正直どうしようもありません。

最終手段としてkubeadmコマンドを改造することにしました。
とりあえずソースツリーを取ってきます。

$ git clone https://github.com/kubernetes/kubernetes.git
$ cd kubernetes
$ git checkout remotes/origin/release-1.11

kubeadmコマンドのソースがcmd/kubeadm配下にあります。
今回はその中のapp/cmd/init.goの400行目に無理やり10分間sleepさせるという荒業にでました。もっと良い方法があるはずなのでよい子はマネしないでね。

--- init.go.bak 2018-09-22 12:10:02.029874009 +0900
+++ init.go     2018-09-22 09:47:08.546241353 +0900
@@ -396,6 +396,10 @@ func (i *Init) Run(out io.Writer) error
        fmt.Printf("[init] waiting for the kubelet to boot up the control plane as Static Pods from directory %q \n", kubeadmconstants.GetStaticPodDirectory())
        fmt.Println("[init] this might take a minute or longer if the control plane images have to be pulled")

+       //
+       fmt.Println("[init] sleep...")
+       time.Sleep(600*time.Second)
+
        if err := waitForKubeletAndFunc(waiter, waiter.WaitForAPI); err != nil {
                ctx := map[string]string{
                        "Error":                  fmt.Sprintf("%v", err),

ファイルを修正したらkubeadmコマンドを作り直します。

$ hack/run-in-gopath.sh bash --norc --noprofile
$ cd cmd/kubeadm/
$ go build kubeadm.go

これで無駄に10分sleepするkubeadmが出来上がりました。
これを使用して再度sudo ./kubeadm init --pod-network-cidr=10.244.0.0/16 -v 10を行います。途中kube-apiserver.yamlの編集も忘れずに。
ここまでやってようやく初期化が完了しました。

kubectlを使えるようにする

kubeadm initがうまくいけば、追加でやる事が表示されるはずですのでそのまま実行します。

$ 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
hoge      NotReady   master    35m       v1.11.3

のようになれば問題ないようです。

flanel add-on の追加

公式のここにも記載がありますが、今回はarm版でのセットアップですので、以下のようにします。

 $ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/c5d10c8/Documentation/kube-flannel.yml

待つこと数分、ようやっと動き出します。

$ kubectl get po -n kube-system
NAME                            READY     STATUS    RESTARTS   AGE
coredns-78fcdf6894-n8hq5        1/1       Running   0          46m
coredns-78fcdf6894-srq7x        1/1       Running   0          46m
etcd-cpod5                      1/1       Running   1          50m
kube-apiserver-hoge             1/1       Running   0          50m
kube-controller-manager-hoge    1/1       Running   2          50m
kube-flannel-ds-arm-pdzmd       1/1       Running   0          11m
kube-proxy-lmv6v                1/1       Running   0          46m
kube-scheduler-hoge             1/1       Running   1          50m
$ kubectl get node
NAME      STATUS    ROLES     AGE       VERSION
hoge      Ready     master    52m       v1.11.3

今後

とりあえずマスターノードをRaspberry Pi上で立ち上げるところまでは完了しました。
ノードの追加やアプリケーションのデプロイ等、きちんと動くのか検証していきます。