アーキテクチャ{Intel, ARM}の異なるNodeをKubernetesで一元管理する方法

#はじめに
　今回は、Kubernetesで異なるアーキテクチャのNodeを一元管理するための設定を試します。多くのユーザは、サーバやPCで多く利用されているIntel互換の命令セットのアーキテクチャ(以降amd64と略します)のCPUと、RaspberryPiなど小型PCで利用されているARMの命令セットのアーキテクチャ(以降armと略します)のCPUを持っている人が多いのではないでしょうか。
　これまで、クラスタ環境(サーバの集中管理も含む)を組むためには、アーキテクチャを統一するのがセオリーでした。しかし、時代はIoT時代。センサーなどの情報を取得する小型PCなどで構成されているEdgeコンピューティングと、集めたセンサーデータを分析する大型サーバ/クラウドコンピューティング(Coreコンピューティング)を併せて一つのEdge-Coreのサービスを構築しているケースが増えてきました。つまり、サービスの観点からみるとEdgeとCoreのコンピューティングを併せてサービスを実行するためのひとつのコンピューティング環境なのです。また、これらのコンピュータを管理する管理者視点で見ると、同じアーキテクチャの機器の台数が増えても、監視方法やソフトウェアのアップデートなどが同じやり方であれば負担はあまり変わりません。負担が激増するのは、異なるアーキテクャの種類が増えることです。アーキテクチャの種類毎に監視方法やアップデートなどのやり方が変わることは、管理者にとって悪夢でしかありません。そのため、管理者は、IoTの要望に応えるべく色々な機器をサポート/管理したいが、手が出せないと言うのが現実ではないでしょうか。
　そこで、今回は異なるアーキテクチャを混在させたKubernetes Cluserを構築し、Kubernesにて一元管理できる環境を目指します。具体的には、Mac(Intel Core i7: amd64アーキテクチャ)上のVirtualBox上に構築したKubernetesのMasterに、Raspberry Pi(ARMv8: armアーキテクチャ)をNodeとして追加します。amd64のみ(又はarmのみ)の単一なアーキテクチャ(ホモジーニアス)のハードウェアで構築されたKubernetes Clusterの環境ではなく、マルチなアーキテクチャ(ヘテロジーニアス)で構築されたKubernetes Clusterを作成します(下図参照)。これにより、アーキテクチャの異なるNodeをKubernetesで一元管理することが出来ます。

![スクリーンショット 2018-05-08 15.55.41.png](https://qiita-image-store.s3.amazonaws.com/0/229880/6431f0bb-013a-4f61-eb33-6b07ca8061e4.png)

#設定
今回の設定で用いる環境は次の環境です。

- Mac(amd64)のVirtualBox上に構築されたKubernetes Cluster
  - [kubeadm](https://kubernetes.io/docs/setup/independent/install-kubeadm/) にてセットアップ
  - Master, Node x 2 のKubernetes Cluster
  - Pod NetworkにFlannelを使用
- Mac上にインストールされた[kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
  - 上記のKubernetes Clusterを管理
- Raspberry Pi3 ModuleB+(arm)
  - [Raspberry PI と kubeadm で自宅 Kubernetes クラスタを構築する
](https://qiita.com/hatotaka/items/48a88ecb190e1f5e03c3)を参考に"kubeadmのインストール"までを実施

次節より、Mac上のVirtualBoxのamd64アーキテクチャで構築されたKubernetes Clusterに、armアーキテクチャ(Raspberry Pi)のノードを追加します。

##Raspberry Pi(arm)のNodeを追加
初めにRaspberry PiのNodeを追加します。
kubeadmを使いMac上にKubernetes masterをセットアップした際に、出力されたNode追加のコマンドをRaspberry Pi上で実行します。

```
$ sudo kubeadm join 192.168.0.23:6443 --token xxxxx --discovery-token-ca-cert-hash sha256:xxxx
<snip>
[discovery] Successfully established connection with API Server "192.168.0.23:6443"

This node has joined the cluster:
* Certificate signing request was sent to master 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.
```

Nodeが追加されたかどうかを`kubectl`コマンドにて確認します。

```
$ kubectl get nodes
NAME        STATUS     ROLES     AGE       VERSION
k8s         Ready      master    7d        v1.10.2
k8s-node1   Ready      <none>    7d        v1.10.2
k8s-node2   Ready      <none>    7d        v1.10.2
k8s-node3   NotReady   <none>    17s       v1.10.2
```

masterのk8s,k8s-node1,k8s-node2がMac上(amd64)で動いているNodeになります。
k8s-node3がRaspberry Pi(arm)で動いているNodeになります。
単に`kubeadm join`コマンドでNodeを追加しただけではSTATUSはNotReadyのままとなります。

これは、kube-systemネームスペースのkube-proxyとkube-flannnel-dsがarmアーキテクチャ上のNodeで実行されていないためです。
`kubectl get ds`コマンドで確認すると、両DaemonSetが3つづつ起動しており、amd64アーキテクチャのNode数(Master含む)しか起動していないのがわかります。

```
$ kubectl get ds -n kube-system
NAME              DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR                   AGE
kube-flannel-ds   3         3         3         3            3           beta.kubernetes.io/arch=amd64   7d
kube-proxy        3         3         3         3            3           beta.kubernetes.io/arch=amd64   7d
```

##kube-proxy(arm)を追加
armに対応したkube-proxyをセットアップします。
使用するkube-proxyのYAMLファイルを次に記します。

```kube-proxy-arm.yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  creationTimestamp: null
  generation: 1
  labels:
    k8s-app: kube-proxy-arm
  name: kube-proxy-arm
  selfLink: /apis/extensions/v1beta1/namespaces/kube-system/daemonsets/kube-proxy-arm
  namespace: kube-system
spec:
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kube-proxy-arm
  template:
    metadata:
      creationTimestamp: null
      labels:
        k8s-app: kube-proxy-arm
    spec:
      nodeSelector:
        beta.kubernetes.io/arch: arm
      containers:
      - command:
        - /usr/local/bin/kube-proxy
        - --kubeconfig=/var/lib/kube-proxy/kubeconfig.conf
        image: gcr.io/google_containers/kube-proxy-arm:v1.10.2
        imagePullPolicy: IfNotPresent
        name: kube-proxy-arm
        resources: {}
        securityContext:
          privileged: true
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/lib/kube-proxy
          name: kube-proxy
        - mountPath: /run/xtables.lock
          name: xtables-lock
        - mountPath: /lib/modules
          name: lib-modules
          readOnly: true
      dnsPolicy: ClusterFirst
      hostNetwork: true
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: kube-proxy
      serviceAccountName: kube-proxy
      terminationGracePeriodSeconds: 30
      tolerations:
      - effect: NoSchedule
        key: node-role.kubernetes.io/master
      - effect: NoSchedule
        key: node.cloudprovider.kubernetes.io/uninitialized
        value: "true"
      volumes:
      - configMap:
          defaultMode: 420
          name: kube-proxy
        name: kube-proxy
      - hostPath:
          path: /run/xtables.lock
          type: FileOrCreate
        name: xtables-lock
      - hostPath:
          path: /lib/modules
          type: ""
        name: lib-modules
  templateGeneration: 4
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 1
    type: RollingUpdate
status:
  currentNumberScheduled: 0
  desiredNumberScheduled: 0
  numberMisscheduled: 0
  numberReady: 0
```

　`kubectl get ds kube-proxy -n kube-system -o yaml`コマンドで出力されるYAMLを改良し上記のYAMLファイルを作成します。
`metadata.name`,`metadata.labels`などは、任意の名前を設定します。
設定のキーポイントは、`spec.nodeSelector`です。
Nodeがarmアーキテクチャの場合に、このDaemonSetが立ち上がるように`beta.kubernetes.io/arch: arm`と指定します。
併せて、armアーキテクチャのコンテナイメージを指定します。
`spec.containers.image`に`gcr.io/google_containers/kube-proxy-arm:v1.10.2`を指定します。これにより、armアーキテクチャのNodeでkibe-proxy-armのコンテナイメージが実行されるようになります。
　作成したkube-proxy-arm.yamlを`kubectl`コマンドでデプロイします。

```
$ kubectl create -f kube-proxy-arm.yaml 
daemonset.extensions "kube-proxy-arm" created

$ kubectl get ds -n kube-system 
NAME              DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR                   AGE
kube-flannel-ds   3         3         3         3            3           beta.kubernetes.io/arch=amd64   7d
kube-proxy        3         3         3         3            3           beta.kubernetes.io/arch=amd64   7d
kube-proxy-arm    1         1         1         1            1           beta.kubernetes.io/arch=arm     16s
```

kube-proxy-armがarmアーキテクチャ上で動作しているのがわかります。これで、amd64のNodeで3つ、armのNodeで1つのkube-proxyが実行します。

##kube-flannel-ds(arm)を追加
続いて、armに対応したkube-flannel-dsをセットアップします。
使用するkube-flannel-dsのYAMLファイルを次に記します。

```kube-flannel-ds-arm.yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: kube-flannel-ds-arm
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      hostNetwork: true
      nodeSelector:
        beta.kubernetes.io/arch: arm
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.9.1-arm
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conf
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.9.1-arm
        command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
        securityContext:
          privileged: true
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg
```
　ここでのキーポイントも`spec.template.spec.nodeSelector`に`beta.kubernetes.io/arch: arm`を指定する点です。
併せてコンテナイメージも`quay.io/coreos/flannel:v0.9.1-arm`を指定します。
　作成したkube-flannel-ds-arm.yamlを`kubectl`コマンドでデプロイします。

```
$ kubectl create -f kube-flannel-ds-arm.yaml 
daemonset.extensions "kube-flannel-ds-arm" created

$ kubectl get ds -n kube-system
NAME                  DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR                   AGE
kube-flannel-ds       3         3         3         3            3           beta.kubernetes.io/arch=amd64   7d
kube-flannel-ds-arm   1         1         1         1            1           beta.kubernetes.io/arch=arm     13s
kube-proxy            3         3         3         3            3           beta.kubernetes.io/arch=amd64   7d
kube-proxy-arm        1         1         1         1            1           beta.kubernetes.io/arch=arm     11m
```

kube-flannel-dsがarmアーキテクチャ上で動作しているのがわかります。これで、amd64のNodeで3つ、armのNodeで1つのkube-flannel-dsが実行します。

##Nodeの確認
kube-proxy,kube-flannel-dsがarmアーキテクチャのNode(k8s-node3)で実行された後、`kubectl`コマンドでNodeのSTATUSを確認します。
k8s-node3のSTATUSがReadyに変更され、無事に動作しているのがわかります。

```
$ kubectl get nodes
NAME        STATUS    ROLES     AGE       VERSION
k8s         Ready     master    7d        v1.10.2
k8s-node1   Ready     <none>    7d        v1.10.2
k8s-node2   Ready     <none>    7d        v1.10.2
k8s-node3   Ready     <none>    55m       v1.10.2
```
以上で構築完了です。

#感想
　今回、amd64とarmアーキテクチャのヘテロジーニアスなハードウェアのKubernetes Cluster環境を構築しました。これにより、管理者は`kubectl`コマンドにて一元管理することが可能となります。Kubernetesの利用者は、アプリケーションをamd64, armアーキテクャの何で動作させるかを選択する必要があります。選択には、`nodeSelector`に`beta.kubernetes.io/arch: arm`を指定します。これにより、amd64, armの環境を容易に指定できます。
　また、今回とは異なるヘテロジーニアスなハードウェアの管理方法として、amd64,arm毎に別々にKubernetes Clusterを構築する方法もあるかと思います。その場合、管理者は複数のKubernetes Clusterを管理することになり、複数のネットワークやAPIのアクセスポイント、証明書などの管理が必要となります。そのため、amd64,arm毎に運用するサービスが異なる場合には、ネットワーク分割なども併せて考慮すると有効かと思います。
　一方で、amd64,arm環境が連携し動作するようなサービスを運用する場合には、今回のKubernetes Clusterの環境が有効かもしれません。例えば、文頭で述べたように、IoT環境で利用するセンサーなどをRaspberry Piといった小型PCをEdge(armアーキテクャ)で動作させ、センサーから得たデータをセンター側のコンピュータ(amd64アーキテクチャ)で分析を行うといったサービスです。
　運用するサービスにより、Kubernetes Clusterをどう構築・運用するのか悩ましいところですが、少なくともKubernetesは、どちらの選択肢を取ることも可能であり、提供するサービスと運用形態によりマッチした環境を選べます。今後カンファレンスなどで面白いKubernetes Clusterの環境が発表されることを期待します。

#参考情報
- [Raspberry PI と kubeadm で自宅 Kubernetes クラスタを構築する](https://qiita.com/hatotaka/items/48a88ecb190e1f5e03c3)