EKS Anywhere on vSphere

## はじめに

EKS AnywhereをvSphere上で実行してみました。ついでにGitOpsして、EKS ConnectorでAWSマネジメントコンソールに登録してみました。

## Amazon EKS Anywhereとは

AmazonのEKSで利用されている、KubernetesディストリビューションであるEKS Distroをオプレミス環境で利用するためのツール。詳しくはこの辺を。

- [Amazon EKS Anywhere](https://aws.amazon.com/eks/eks-anywhere/?nc1=h_ls)
- [Amazon EKS Anywhere – Now Generally Available to Create and Manage Kubernetes Clusters on Premises](https://aws.amazon.com/jp/blogs/aws/amazon-eks-anywhere-now-generally-available-to-create-and-manage-kubernetes-clusters-on-premises/)
- [EKS Anywhere Documentation](https://anywhere.eks.amazonaws.com/docs/overview/)

## 準備

### 最新のeksctl cliのダウンロード

```bash
curl "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" \
    --silent --location \
    | tar xz -C /tmp
sudo mv /tmp/eksctl /usr/local/bin/
```

バージョン確認

```bash
$ eksctl version
0.66.0
```

anywhereサブコマンドは使えるのか？プラグインが必要

```bash
$ eksctl anywhere
"eksctl-anywhere" plugin was not found on your path
```

### eksctl anywhereを利用するためのプラグインのインストール

```bash
export EKSA_RELEASE="0.5.0" OS="$(uname -s | tr A-Z a-z)"
curl "https://anywhere-assets.eks.amazonaws.com/releases/eks-a/1/artifacts/eks-a/v${EKSA_RELEASE}/${OS}/eksctl-anywhere-v${EKSA_RELEASE}-${OS}-amd64.tar.gz" \
    --silent --location \
    | tar xz ./eksctl-anywhere
sudo mv ./eksctl-anywhere /usr/local/bin/
```

anywhereサブコマンドが利用できるようになったことを確認

```bash
$ eksctl anywhere
Use eksctl anywhere to build your own self-managing cluster on your hardware with the best of Amazon EKS

Usage:
  anywhere [command]

Available Commands:
  create      Create resources
  delete      Delete resources
  generate    Generate resources
  help        Help about any command
  upgrade     Upgrade resources
  version     Get the eksctl anywhere version

Flags:
  -h, --help            help for anywhere
  -v, --verbosity int   Set the log level verbosity

Use "anywhere [command] --help" for more information about a command.
```

### eksを作成するためのマニフェストテンプレートの作成

クラスター名を指定して```eksctl anywhere generate clusterconfig```を実行するとクラスターの構成情報を記述するためのコンフィグファイル(マニフェスト)が生成される。

```bash
CLUSTER_NAME=prod
eksctl anywhere generate clusterconfig $CLUSTER_NAME \
   --provider vsphere > eksa-cluster.yaml
```

コンフィグファイル(eksa-cluster.yaml)は以下のリソースを定義する。```kind: VSphereMachineConfig```が3つあるが、それぞれコントロールプレーン(Master)、ノード、etcdを構成するためのものになっている。

```bash
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: Cluster
--
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: VSphereDatacenterConfig
--
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: VSphereMachineConfig
metadata:
  name: prod-cp
--
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: VSphereMachineConfig
metadata:
  name: prod
--
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: VSphereMachineConfig
metadata:
  name: prod-etcd
```

### コンフィグファイルの編集

#### Cluster

```.spec.controlPlaneConfiguration.endpoint.host```としてKubernetes APIのIPアドレスを指定する。このIPアドレスはノードがDHCPで取得するIPアドレスと同じCIDRで、DHCPのレンジ外のものを固定IPアドレスとして指定する。

```yaml
spec:
  controlPlaneConfiguration:
    endpoint:
      host: "192.168.201.100"
```

GitOpsを利用する場合は、specに以下を追加する。(※GitOpsConfigを別途定義する必要がある)

```yaml
spec:
  gitOpsRef:
    kind: GitOpsConfig
    name: eksacl
```

#### VSphereDatacenterConfig

EKS Anywhereクラスターを作成するvSphereクラスターの情報を定義する。

```yaml
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: VSphereDatacenterConfig
metadata:
  name: prod
spec:
  datacenter: "Tanzu-DC"
  insecure: true
  network: "/Tanzu-DC/Network/eks"
  server: "tanzu-vc.example.com"
  thumbprint: ""
```

#### VSphereMachineConfig

cp, worker, etcdそれぞれに対して```datastore```、```resourcePool```、```sshAuthorizedKeys```を指定する。

```yaml
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: VSphereMachineConfig
metadata:
  name: prod
spec:
  datastore: "vsanDatastore"
  diskGiB: 25
  folder: ""
  memoryMiB: 8192
  numCPUs: 2
  osFamily: bottlerocket
  resourcePool: "/Tanzu-DC/host/Tanzu-Cluster/Resources/eks"
  users:
  - name: ec2-user
    sshAuthorizedKeys:
    - ssh-rsa AAAA...
```

現在EKS Anywhereで利用可能なOSイメージはbottlerocketとubuntuを利用可能であるため、```osFamiliy```としてどちらかを指定する。ubuntuのOVAが8.54GBであるのに対し、bottolerocketは1.27GBと非常に小さいため、クラスター構築時間を大きく短縮できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/2df907a0-d902-213e-a401-6a9318a4689b.png)


#### GitOps

GitOpsを有効化する場合は、GitOpsConfigを追加する。

```yaml
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: GitOpsConfig
metadata:
  name: eksacl
spec:
  flux:
    github:
      branch: main
      clusterConfigPath: clusters/eksacl
      fluxSystemNamespace: flux-system
      owner: masanara
      personal: true
      repository: eksagitops
```

クラスター作成時にGitHubにリポジトリを作成するため、```repo```に対してアクセスを許可するPersonal Access Tokenを生成して```EKSA_GITHUB_TOKEN```環境変数として構成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/5dea361e-b009-c436-c4ba-f56de1de6348.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/40fcc629-286b-7167-d8a5-c7fdd561ad1e.png)


```bash
export EKSA_GITHUB_TOKEN=ghp_MyValidPersonalAccessTokenWithRepoPermissions
```

> GitOpsを有効化したクラスターを削除する際も、EKSA_GITHUB_TOKEN環境変数が求められる。

```bash
$ eksctl anywhere delete cluster eksacl
Error: failed to delete cluster: failed to set up git ooptions: error Git provider: github access token environment variable EKSA_GITHUB_TOKEN is invalid; could not get var from environment
```

### vSphere環境の準備

EKSAクラスターを構成する仮想マシンはAWSが配布するOVAテンプレートを利用する。OVAテンプレートはコンテンツライブラリに登録された後、仮想マシンインベントリのTemplatesフォルダに仮想マシンテンプレートとして配置されるが、Templatesフォルダは自動的に生成されないため、あらかじめ用意しておく必要がある。

```bash
$ wget https://github.com/vmware/govmomi/releases/download/v0.26.1/govc_Linux_x86_64.tar.gz
$ tar zxvf govc_Linux_x86_64.tar.gz govc
$ mv govc /usr/local/bin
$ export GOVC_URL='tanzu-vc.example.com'
$ export GOVC_USERNAME='administrator@vsphere.local'
$ export GOVC_PASSWORD='VMware1!'
$ govc folder.create /Tanzu-DC/vm/Templates
$ govc folder.info /Tanzu-DC/vm/Templates
Name:        Templates
  Path:      /Tanzu-DC/vm/Templates
  Types:     Folder,VirtualMachine,VirtualApp
  Children:  0
```

## クラスターの作成

vSphereクレデンシャルを環境変数として設定する

```bash
export EKSA_VSPHERE_USERNAME='administrator@vsphere.local'
export EKSA_VSPHERE_PASSWORD='VMware1!'
```

作成したマニフェストを利用して、クラスターの作成を実行する。GitOpsを有効にした場合、クラスター作成時に```EKSA_GITHUB_TOKEN```環境変数が設定されていない場合、エラーとなる。

```bash
$ eksctl anywhere create cluster -f eksa-cluster.yaml
Checking Github Access Token permissions
✅ Github personal access token has the required repo permissions
Performing setup and validations
Warning: VSphereDatacenterConfig configured in insecure mode
VSphereMachineConfig folder for control plane is not set or is empty. Will default to root vSphere folder.
VSphereMachineConfig folder for worker nodes is not set or is empty. Will default to root vSphere folder.
VSphereMachineConfig folder for etcd machines is not set or is empty. Will default to root vSphere folder.
✅ Connected to server
✅ Authenticated to vSphere
✅ Datacenter validated
✅ Network validated
✅ Datastore validated
✅ Resource pool validated
✅ Datastore validated
✅ Resource pool validated
✅ Datastore validated
✅ Resource pool validated
Creating template. This might take a while.
✅ Control plane and Workload templates validated
✅ Vsphere Provider setup is valid
✅ Flux path
Creating new bootstrap cluster
Installing cluster-api providers on bootstrap cluster
Provider specific setup
Creating new workload cluster
Installing networking on workload cluster
Installing storage class on workload cluster
Installing cluster-api providers on workload cluster
Moving cluster management from bootstrap to workload cluster
Installing EKS-A custom components (CRD and controller) on workload cluster
Creating EKS-A CRDs instances on workload cluster
Installing AddonManager and GitOps Toolkit on workload cluster
Adding cluster configuration files to Git
Enumerating objects: 76, done.
Counting objects: 100% (76/76), done.
Compressing objects: 100% (39/39), done.
Total 76 (delta 11), reused 71 (delta 6), pack-reused 0
Finalized commit and committed to local repository      {"hash": "8094dc2f18b850bfc6954dbfc5c5d20edf7c67c8"}
Writing cluster config file
Deleting bootstrap cluster
🎉 Cluster created!
```

クラスター作成中に指定したリポジトリに、クラスターの構成が記述されたマニフェストファイルがclusters/クラスター名/eksa-system配下に作成される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/29bda17a-7d4c-8c6d-af58-9763f96f81ca.png)

vSphere上には以下の仮想マシンが作成される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/7f6b49f2-1741-66a4-72b4-68255b95ca0e.png)


## GitOpsの利用

GitOpsを有効化してクラスターを構成すると、flux-systemネームスペースが作成され、[Flux](https://fluxcd.io/)が利用可能な状態でクラスターが作成される。

```bash
$ kubectl get pod -n flux-system
NAME                                       READY   STATUS    RESTARTS   AGE
helm-controller-6d875c9745-rkz9g           1/1     Running   0          5h48m
kustomize-controller-74c85f9944-lhvlk      1/1     Running   0          112m
notification-controller-7c59756d9d-wlz2v   1/1     Running   0          5h48m
source-controller-65dcfdf7f7-c8dpr         1/1     Running   0          5h48m
```

github上のマニフェストファイルを編集してGitOpsの動きを確認してみる。

### クラスターの構成変更

デフォルトEKS Anywhereのマニフェストファイルの構成では2台のワーカーで構成されている。

```bash
$ kubectl get node
NAME              STATUS   ROLES                  AGE     VERSION
192.168.201.150   Ready    control-plane,master   5h37m   v1.21.3
192.168.201.152   Ready    control-plane,master   5h33m   v1.21.3
192.168.201.38    Ready    <none>                 5h35m   v1.21.3
192.168.201.76    Ready    <none>                 5h35m   v1.21.3
```

[eksa-systems配下のeksa-cluster.yaml](https://github.com/masanara/eksagitops/blob/main/clusters/eksacl/eksa-system/eksa-cluster.yaml)ファイルのワーカーノード数(```.spec.workerNodeGroupConfigurations.count```)を修正・コミットし、暫く待つ仮想マシンが追加され、EKSクラスターのワーカーノードが増える。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/f4da8ec4-39e2-69e6-8bf5-ffe7f7a7076c.png)


```bash
$ kubectl get node
NAME              STATUS   ROLES                  AGE     VERSION
192.168.201.150   Ready    control-plane,master   5h57m   v1.21.3
192.168.201.152   Ready    control-plane,master   5h53m   v1.21.3
192.168.201.157   Ready    <none>                 36s     v1.21.3
192.168.201.38    Ready    <none>                 5h55m   v1.21.3
192.168.201.76    Ready    <none>                 5h55m   v1.21.3
192.168.201.79    Ready    <none>                 33s     v1.21.3
```


### リソースの作成

github上に作成されたクラスターディレクトリ内にマニフェストを作成して、githubにpushするとクラスター上のfluxが変更を検出してマニフェストの内容をクラスターに反映させる。

クラスターディレクトリ内にtestディレクトリを作成してマニフェスト(nginx.yaml)を置く。

```bash
.
└── clusters
    └── eksacl
        ├── eksa-system
        │   ├── eksa-cluster.yaml
        │   └── kustomization.yaml
        ├── flux-system
        │   ├── gotk-components.yaml
        │   ├── gotk-patches.yaml
        │   ├── gotk-sync.yaml
        │   └── kustomization.yaml
        └── test
            └── nginx.yaml
```

nginx.yamlは以下の通り

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
  namespace: test
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:alpine
        name: nginx
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: test
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
```

しばらくするとクラスターにはネームスペースが作成されて、マニフェストの内容が反映される。

```bash
$ kubectl get ns test
NAME   STATUS   AGE
test   Active   113s
$ kubectl get deploy,pod,svc -n test
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   3/3     3            3           115s

NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-cf4d5b4c8-2tghp   1/1     Running   0          115s
pod/nginx-cf4d5b4c8-8cx2n   1/1     Running   0          115s
pod/nginx-cf4d5b4c8-f2csm   1/1     Running   0          115s

NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/nginx   ClusterIP   10.99.191.46   <none>        80/TCP    115s
```

### helmによるリソースの作成

fluxはhelmチャートを利用したリソースの作成も可能。helmを利用する場合は、```HelmRepository```と```HelmRelease```を定義する。

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: podinfo
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmRepository
metadata:
  name: podinfo
  namespace: podinfo
spec:
  interval: 1m
  url: https://stefanprodan.github.io/podinfo
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: podinfo
  namespace: podinfo
spec:
  interval: 5m
  chart:
    spec:
      chart: podinfo
      version: ">=5.0.0"
      sourceRef:
        kind: HelmRepository
        name: podinfo
        namespace: podinfo
      interval: 1m
  values:
    replicaCount: 3
```

作成したネームスペースに```HelmRepository```と```HelmRelease```が作成されており、```helm list```で追加されたパッケージを確認することが可能。

```bash
$ kubectl get ns podinfo
NAME      STATUS   AGE
podinfo   Active   85s

$ kubectl get helmrepository -n podinfo
NAME      URL                                      READY   STATUS                                                       AGE
podinfo   https://stefanprodan.github.io/podinfo   True    Fetched revision: 8411f23d07d3701f0e96e7d9e503b7936d7e1d56   95s

$ kubectl get helmrelease -n podinfo
NAME      READY   STATUS                             AGE
podinfo   True    Release reconciliation succeeded   104s
$ kubens podinfo
✔ Active namespace is "podinfo"

$ helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
podinfo podinfo         1               2021-09-13 09:51:04.350035442 +0000 UTC deployed        podinfo-6.0.0   6.0.0

$ kubectl get deploy,pod,svc
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/podinfo   5/5     5            5           2m5s

NAME                           READY   STATUS    RESTARTS   AGE
pod/podinfo-5c6c96c87b-5rfnq   1/1     Running   0          2m5s
pod/podinfo-5c6c96c87b-m648v   1/1     Running   0          2m5s
pod/podinfo-5c6c96c87b-ngxx4   1/1     Running   0          2m5s
pod/podinfo-5c6c96c87b-pbwz4   1/1     Running   0          2m5s
pod/podinfo-5c6c96c87b-ssgsr   1/1     Running   0          2m5s

NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
service/podinfo   ClusterIP   10.96.150.177   <none>        9898/TCP,9999/TCP   2m5s
```

## EKS Connectorの利用

プレビューリリースとして提供されているEKS Connectorを利用するとAWSマネジメントコンソールにクラスターを登録することが可能。(ドキュメント:[Amazon EKS Connector](https://docs.aws.amazon.com/eks/latest/userguide/eks-connector.html))

### Amzon EKS Connector Roleの作成

aws cliを利用してEKS Connectorに必要になるRole/Polciyを作成する。

```bash
aws iam create-service-linked-role --aws-service-name eks-connector.amazonaws.com
```

### Amazon EKS Connector Agent IAM Roleの作成

以下の内容でポリシーファイルを作成

- eks-connector-agent-trust-policy.json

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SSMAccess",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "ssm.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

- eks-connector-agent-policy.json

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SsmControlChannel",
            "Effect": "Allow",
            "Action": [
                "ssmmessages:CreateControlChannel"
            ],
            "Resource": "arn:aws:eks:*:*:cluster/*"
        },
        {
            "Sid": "ssmDataplaneOperations",
            "Effect": "Allow",
            "Action": [
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenDataChannel",
                "ssmmessages:OpenControlChannel"
            ],
            "Resource": "*"
        }
    ]
}
```

作成したjsonファイルを利用してEKSConnectorAgentRole/AmazonEKSConnectorAgentPolicyを作成する。

```bash
aws iam create-role \
     --role-name AmazonEKSConnectorAgentRole \
     --assume-role-policy-document file://eks-connector-agent-trust-policy.json
```

```bash
iam put-role-policy \
     --role-name AmazonEKSConnectorAgentRole \
     --policy-name AmazonEKSConnectorAgentPolicy \
     --policy-document file://eks-connector-agent-policy.json
```

### クラスターの登録

EKS Connectorを利用してオンプレミスのEKSクラスターをマネジメントコンソールに登録するためには、コンソール側でクラスターを登録し、生成されたマニフェストをクラスターに適用し、EKS Connectorコンポーネントをクラスター上で実行する必要がある。

AWSにマネジメントコンソールの「Elastic Kubernetres Service」を開き、「クラスター」を選択し、「クラスターを追加」メニューから「登録」を選択する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/dccdd64e-7f2e-5049-a1ca-9f974a26663e.png)

登録するクラスターの名前、プロバイダーを指定し、接続設定として作成したEKSコネクタロール(AmazonEKSConnectorAgentRole)を選択し、「クラスターを登録」を実行する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/905b3a66-0440-6fca-4df0-3df662e8690f.png)

登録したクラスターに対して適用するマニフェストをダウンロードする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/0f85c319-d084-3865-ec1b-a1b49afe7072.png)

ダウンロードしたyamlファイルにより、クラスターにeks-connectorを作成する。作成に成功すると、eks-connectorネームスペースにeks-connector-0/eks-connector-1 Podが作成される。

```bash
$ kubectl apply -f eks-connector.yaml
namespace/eks-connector created
secret/eks-connector-activation-config created
role.rbac.authorization.k8s.io/eks-connector-secret-access created
serviceaccount/eks-connector created
secret/eks-connector-token created
rolebinding.rbac.authorization.k8s.io/eks-connector-secret-access created
configmap/eks-connector-agent created
statefulset.apps/eks-connector created
$ kubectl get pod -n eks-connector
NAME              READY   STATUS    RESTARTS   AGE
eks-connector-0   2/2     Running   0          46s
eks-connector-1   2/2     Running   0          24s
```

ここまでの手順でEKSクラスター自体は「アクティブ」な状態となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/ad8af3db-236b-632f-d61b-d2dea65cd254.png)


AWSマネジメントコンソールにアクセスするユーザーが、EKS Connectorを介してKubernetesクラスターにアクセスできるようClusterRole/ClusterRoleBindingを設定する。[eks-connector-clusterrole.yaml ](https://amazon-eks.s3.us-west-2.amazonaws.com/eks-connector/manifests/eks-connector-console-roles/eks-connector-clusterrole.yaml)と、[eks-connector-console-dashboard-full-access-group.yaml](https://amazon-eks.s3.us-west-2.amazonaws.com/eks-connector/manifests/eks-connector-console-roles/eks-connector-console-dashboard-full-access-group.yaml)を利用してClusterRole/ClusterRoleBinding/RBAC Authorizationを設定する。各マニフェスト内の```%IAM_ARN%```をIAMユーザーのARNに置き換えて適用する。

```bash
$ kubectl apply -f eks-connector-clusterrole.yaml
clusterrolebinding.rbac.authorization.k8s.io/eks-connector-service created
clusterrole.rbac.authorization.k8s.io/eks-connector-service created
$ kubectl apply -f eks-connector-console-dashboard-full-access-group.yaml
clusterrole.rbac.authorization.k8s.io/eks-connector-console-dashboard-full-access-clusterrole created
clusterrolebinding.rbac.authorization.k8s.io/eks-connector-console-dashboard-full-access-clusterrole-binding created
```

上記のClusterRole/ClusterRoleBinding/RBAC Authorizationの設定が正しく完了していない場合、クラスター内部のリソースを表示することができない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/bd80d9d8-5a01-4c97-37b5-14b7ba5ed851.png)


ClusterRole/ClusterRoleBinding/RBAC Authorizationを構成することで、クラスターを構成するノードはPod等の情報をマネジメントコンソールから確認することが可能になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/c9a22de8-8426-2b2a-bb9d-38c8de4aa7e4.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/711fcafc-0150-d7f0-2389-8baf76ca0332.png)