AWS EKSクラスタにARMインスタンスをワーカーノードとして追加する

# (追記)

この記事はEKSでのARMインスタンスサポートがパブリックプレビューだったころに書かれたもので、すでにARMインスタンスサポートはGAとなっています。

eksctlなどによるサポートも行われており、基本的に特別なことはなく、通常のAMD64クラスタへの追加もeksctlを使ってできます。

なので今後この記事を参考にする必要はありません。

# はじめに

Amazon EKS(Elastic Kubernetes Service)はAWSが提供するマネージドKubernetesサービスです。

ワーカーノードとしてEC2インスタンスを割り当てるアンマネージドNodeGroupと、AWS側でそれを管理するマネージドNodeGroup、サーバレスコンピューティングエンジンのFargateをワーカノードとして利用するNodeGroupが選択できます。

また、Amazon EC2ではARM64プロセッサを搭載したA1インスタンスが利用できます。

そこでAmazon EKSのアンマネージドNodeGroupにA1インスタンスを追加すればARMプロセッサをEKSクラスタから利用できるのでは？と考えました。

本記事は実際にそれを行うための環境が整ったため、実験してみた記録です。
投稿時点ではTry & Errorで試してみただけの段階のため、詳細は一部割愛しています。今後、追加検証を行って手順の詳細を追加するかもしれません。

# 前提情報

「はじめに」にだけを読むと簡単にできそうですが、実際にはそこまで簡単ではありません。

実際、AWSはEKSでのArmサポート(パブリックプレビュー)を2019年4月に[公表](https://aws.amazon.com/jp/about-aws/whats-new/2019/04/-amazon-eks-supports-ec2-a1-instances-as-a-public-preview-/)しており、それに関する[ユーザーガイド](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/arm-support.html)を提供していますが、その中に考慮事項として以下が挙げられています:

- ワーカーノードは任意の A1 インスタンスタイプにできますが、すべてのワーカーノードは A1 インスタンスタイプである必要があります。
- ワーカーノードは、Kubernetes バージョン 1.13 または 1.14 でデプロイする必要があります。
    - 今回の実験では1.14を使用しますが、新しくサポートされた1.15にも対応していると思われます。
- A1 インスタンスワーカーノードを使用するには、新しい Amazon EKS クラスターをセットアップする必要があります。既存のワーカーノードがあるクラスターにワーカーノードを追加することはできません。

しかし、ユーザーガイドを読んだ限りでは1つめと3つめは技術的困難さがあるというよりも単に「当該ユーザーガイドの手順には含まれていない」というだけで、実際には可能と思われるので実験を行います。

# tl; dr

要するにCNIとkube-proxyを実行するDaemonSetをAMD64 Node用とARM64 Node用とで用意してやる必要があります。AMD64 NodeとARM64 Nodeの用意するインスタンス数によってはCoreDNSのDeploymentもARM64 Node用を用意してもいいかもしれません。
あと疎通のためSecurityGroupの設定が追加で必要になるかもしれません。

# EKSクラスタの作成

eksctlコマンドで。今回はCoreDNSを修正しないのでAMD64 NodeGroupとして1つはインスタンスを追加しておく必要があります。

# ARM64 Node用kube-proxy/aws-node DaemonSetの作成

## 既存のkube-proxy/aws-node をAMD64 Nodeのみに制限

nodeAffinityを追加して、AMD64 NodeにのみPodを配置するよう変更します。

なお、NodeのCPUアーキテクチャの判別にはNodeのLabelを用いますが、実験時は `kubernetes.io/arch` と `beta.kubernetes.io/arch` の2種類のラベルが混在していました。 Nodeにはどちらも設定されているのでどちらを使っても問題ありませんが、そろえておくほうがよいでしょう。

## ARM64 Node用kube-proxy/aws-node を作成

[ユーザガイド](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/arm-support.html#enable-arm-support)に従って作成するが、マニフェストを直接適用せずにダウンロードし、変更して適用します。また、CoreDNSの変更は行いません。

DaemonSetにnodeAffinityを追加してPodがARM64 Nodeにのみ配置されるよう変更します。

# A1インスタンスでアンマネージドNodeGroupの起動

[ユーザガイド](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/arm-support.html#launch-arm-worker-nodes)に従ってNodeGroupを起動します。

# A1インスタンスNodeGroupをクラスタに結合

[ユーザガイド](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/arm-support.html#join-arm-cluster)にならって `aws-auth` ConfigMapを編集します
。
すでにクラスタ内にConfigMapが設定されているはずなのでそれを取得して編集します。

次のように変更します:

```diff
 apiVersion: v1
 data:
   mapRoles: |
     - groups:
       - system:bootstrappers
       - system:nodes
       rolearn: arn:aws:iam::012345678901:role/eksctl-yourclustername-nodegroup-NodeInstanceRole-ABCDEFGHIJKL
       username: system:node:{{EC2PrivateDNSName}}
+    - groups:
+      - system:bootstrappers
+      - system:nodes
+      rolearn: arn:aws:iam::012345678901:role/eksctl-yourclustername-nodegroup-NodeInstanceRole-MNOPQRSTUVWX
+      username: system:node:{{EC2PrivateDNSName}}
   mapUsers: |
     []
 kind: ConfigMap
 metadata:
   creationTimestamp: "2020-01-01T00:00:00Z"
   name: aws-auth
   namespace: kube-system
   resourceVersion: "0"
   selfLink: /api/v1/namespaces/kube-system/configmaps/aws-auth
   uid: 01234567-89ab-cdef-0123-456789abcdef
```

ここで `rolearn` フィールドの `arn:aws:iam::012345678901:role/eksctl-yourclustername-nodegroup-NodeInstanceRole-MNOPQRSTUVWX` は `<インスタンスのロールの ARN (インスタンスプロファイルではない)>` の値で置き換えます。

# SecurityGroupの設定

`aws-auth` ConfigMapを適用してしばらくするとARM64 NodeGroupがEKS上のNodeとして見えるようになります。

ただこれだけではARM64 NodeとAMD64 Node間での通信ができません。これはSecurityGroupの設定が不十分なためと考えられます。

`aws` コマンドやAWSのWebコンソールでAMD64 NodeGroupのNodeとして動作しているEC2インスタンスのSecurityGroupを確認すると、おそらく `SharedNodeSecurityGroup` という文言を含む SecurityGroupが見つかると思います。

当該のSecurityGroupをA1 NodeGroupのインスタンスに追加することでARM64/AMD64 Node間の通信が行えるようになります。