AWS 上で Tanzu Community Edition をデプロイ、Contour と AWS Load Balancer Controller による Ingress の実現

# Chapter I： AWS 上で Tanzu Community Edition のデプロイ
　デプロイ方法はこちら VMware JP Blog に記載があるため、詳細を省きます。
[TKG 1.4.0 on AWS の構築][link-01]
[link-01]:https://blogs.vmware.com/vmware-japan/2021/10/tkg-1-4-0-on-aws.html

　Management Cluster と Workload Cluster 構成後の AWS アーキテクチャは下記の図になります。
![TUNA01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385161/e97ca1b1-51b3-94d3-e994-04cfd94a4bab.jpeg)

　2つの ELB(Classic）は自動的に作られ、Control Plane の API サーバアクセスとして構成されます。

# Chapter I.5 TKG on AWS の LB Service の検証
　Ingress を検証する前に、Default の LB Service を検証します。
下記シンプルな LB Service を Workload Cluster でデプロイします。

```yaml:lb-test.yaml
apiVersion: v1
kind: Service
metadata:
  name: lb-svc-1
spec:
  type: LoadBalancer
  selector:
    app: lb-svc-1
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
```

結果として

```
$ k get service -n default
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP                                                              PORT(S)        AGE
kubernetes   ClusterIP      100.64.0.1       <none>                                                                   443/TCP        5d5h
lb-svc-1     LoadBalancer   100.68.125.217   aebc093baba6d43e18262210da62530b-296707719.us-east-2.elb.amazonaws.com   80:30904/TCP   29s
```

　ELB(Classic) が自動的に作成されます。Instance に Workload Cluster の Worker Node が設定されています。
![TUNA03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385161/19306d4a-dbdb-1b86-e3eb-6686dc91095d.jpeg)
　結論として、TCE on AWS をデプロイしたら、ELB が LB Service のバックエンドとして自動的にインテグレートされます。

# Chapter II： Contour を使った Ingress の実現
　Contour のインストールは[こちらの記事][link-02]を参照します。
[link-02]:https://ik.am/entries/674
　Contour は VMware が管理する Open Source Ingress Controller です。データープレーンは Envoy Proxy を使います。
　TCE での Contour デプロイは [tanzu package の仕組み][link-03]を利用すれば容易にデプロイできます。
[link-03]:https://tanzucommunityedition.io/docs/latest/package-management/

#####Step 1: リポジトリを追加

```
tanzu package repository add tce-repo --url projects.registry.vmware.com/tce/main:0.9.1 --namespace tanzu-package-repo-global
```

#####Step 2: 使用可能な package を確認

```
$ tanzu package available list
- Retrieving available packages...
  NAME                                           DISPLAY-NAME        SHORT-DESCRIPTION                         
  cert-manager.community.tanzu.vmware.com        cert-manager        Certificate management                    
  contour.community.tanzu.vmware.com             Contour             An ingress controller                     
  external-dns.community.tanzu.vmware.com        external-dns        This package provides DNS synchronization functionality.
```

:::note warn
正しい Package を利用するためには TCE を最新版にアップグレードした方がいいです。
:::

#####Step 3: Contour をデプロイします。

```
tanzu package install contour --package-name contour.community.tanzu.vmware.com --version 1.19.1
```

　これで Contour と Envoy がデプロイされます。

```
$ k get pods -n projectcontour
NAME                            READY   STATUS      RESTARTS   AGE
contour-649bdcf7fd-6ttd4        1/1     Running     0          66m
contour-649bdcf7fd-zblx5        1/1     Running     0          66m
contour-certgen-v1.18.1-m5q8h   0/1     Completed   0          66m
envoy-8hf85                     2/2     Running     0          66m
```

#####Step 4: Ingress をデプロイしてみます。

```yaml:contour-ingress-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lb-svc-nginx
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: lb-svc-1
  template:
    metadata:
      labels:
        app: lb-svc-1
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: storage
          mountPath: /usr/share/nginx/html
    volumes:
    - name: storage
      hostPath:
        path: "/data/html"
        type: Directory
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lb-svc-nginx-old
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: lb-svc-2
  template:
    metadata:
      labels:
        app: lb-svc-2
    spec:
      containers:
      - name: nginx
        image: nginx:1.20.1
        ports:
        - containerPort: 80
        volumeMounts:
        - name: storage
          mountPath: /usr/share/nginx/html/old
    volumes:
    - name: storage
      hostPath:
        path: "/data/html/old"
        type: Directory
---
apiVersion: v1
kind: Service
metadata:
  name: lb-svc-1
spec:
  type: NodePort
  selector:
    app: lb-svc-1
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: lb-svc-2
spec:
  type: NodePort
  selector:
    app: lb-svc-2
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: contour-test-ingress
  annotations:
    kubernetes.io/ingress.class: contour
spec:
  rules:
  - host:
    http:
      paths:
      - path: /old
        pathType: Prefix
        backend:
          service:
            name: lb-svc-2
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: lb-svc-1
            port:
              number: 80
```
　上記は2つバージョン(Latest & 1.20.1)の Nginx をデプロイし、それぞれの deployment に対して NodePort を作り、さらに、URL path (/ & /old)で区別するように Ingress を構成します。
　見た目上分けるために、host path volume を使って、簡易な html ファイルをマウントしています。

:::note warn
　host path volume を使うので、workload cluster の worker node にアクセスし、/data/html ディレクトリと index.html ファイルを作る必要があります。
:::
:::note warn
　worker node にアクセスするのには、踏み台の workload-cluster-bastion インスタンスを経由してアクセスします。
:::

　デプロイ後、Ingress と Service を確認します。

```
$ k get ingress
NAME                   CLASS    HOSTS   ADDRESS                                                                  PORTS   AGE
contour-test-ingress   <none>   *       xxxx.us-east-2.elb.amazonaws.com   80      50m
$ k get service
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   100.64.0.1      <none>        443/TCP        19h
lb-svc-1     NodePort    100.66.23.98    <none>        80:32549/TCP   50m
lb-svc-2     NodePort    100.65.103.44   <none>        80:31707/TCP   50m
```

　Ingress に ELB のアドレスが関連されていることが分かりました。
さらに AWS コンソルで確認します。
![TUNA04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385161/ab88ebb0-59a9-cc7e-d70a-4fa0a73045ac.jpeg)
　ELB（classic) は確かに払い出されています。
　ELB の DNS 名を使ってアクセスしてみますと
![TUNA05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385161/6503485c-08d3-fbe0-ff74-54e731ac1d6d.jpeg)
　確かに、想定通りに振り分けています。

# Chapter III: AWS ALB を使って Ingress を実現
　Contour の代わりに AWS ALB と AWS Load Balancer Controller の組み合わせで Ingress を実現してみます。

　AWS Load Balancer Controller の[デプロイおよびユーザガイド][link-04]を参照します。
[link-04]:https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/deploy/installation/

#####Step 1: IAM の設定
　Ingress Controller が ALB/NLB を操作できるようにするためには IAM role を設定してあげる必要があります。
　EKS を使用するなら、Controller 用 Service Account と IAM role を作り、eksctl コマンドで関連付けますが、TCE などの 3rd Party K8s を使用する場合、手動で追加する必要があります。
　Service Account を使用するためには OIDC provider が必要になるので、今回の検証は Service Account を使わず、直接 Workload Cluster の Worker Node に IAM Role を付与します。
　Controller に必要な [IAM Policy][link-05] をダウンロードし、Policy を作成します。
[link-05]:https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.3.1/docs/install/iam_policy.json
　専用の IAM Role を作成してもいいですが、今回は TCE デプロイ時に worker node に付与された **nodes.tkg.cloud.vmware.com** Role に上記 Policy を追加します。

:::note info
"nodes.tkg.cloud.vmware.com" Role は management cluster の worker node にも適用されています。
:::
　既存の policy に controller 用の policy もアタッチします。
　![TUNA06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385161/4b5a22ae-19e8-f415-2208-629c9f85d578.jpeg)

#####Step 2: Subnet Discovery の設定
　AWS load balancer ingress controller は ALB を利用して Ingress を実現します。AWS ALB をデプロイするためには最低限2つ異なるAZにそれぞれSubenetを用意する必要があります。（[AWS ALB Doc][link-06])
[link-06]:https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html 
　ただ、マルチAZ をコンフィグしない限り、TCE のデプロイは基本1つのAZ内で完結します。ALB を利用するためには、手動でもう1つのAZ用のSubenet を手動で作成します。
　![TUNA07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385161/fb114b8a-4f5f-1467-45fe-4b1ba453ae72.jpeg)
　既存の public subnet と private subnet にまねして、2つ目のAZのsubnetを作ります。
　Ingress Controller に Subnet を識別させるために、Subnet に下記のタグをつける必要があります。

| Subnet 種別 | Key | Value |
|:-----------|:------------|:------------|
| Public subnets       | kubernetes.io/role/elb        | 1 or ``        |
| Private subnets       | kubernetes.io/role/internal-elb        | 1 or ``        |
:::note info
TCE デプロイ時に自動で作られた Subnet にすでに上記タグは設定済み
:::
　本来であれば、新規作成される Subnet に igw や nat も設定すべきですが、TCE worker node はマルチAZ構成していないので、こちら設定しなくてもALBは問題なく動きます。

#####Step 3: AWS Load Balancer Ingress Controller のデプロイ
　helm に EKS の repo を追加します。

```
helm repo add eks https://aws.github.io/eks-charts
```
　helm chart をインストールします。

```
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=<cluster-name>
```
　インストールされたcontroller を確認します。

```
$ k get all -n kube-system | grep aws
pod/aws-load-balancer-controller-6bc577994f-r842f                       1/1     Running   0          21h
pod/aws-load-balancer-controller-6bc577994f-snb77                       1/1     Running   0          21h
service/aws-load-balancer-webhook-service   ClusterIP   100.69.91.239    <none>        443/TCP                  21h
deployment.apps/aws-load-balancer-controller   2/2     2            2           21h
replicaset.apps/aws-load-balancer-controller-6bc577994f   2         2         2       21h
```

#####Step 4: AWS Load Balancer Ingress Controller のテスト
　Contour のテストで作られた Nginx Pod と Nodeport Service を流用します。
　Ingress は下記の　yaml をデプロイします。

```yaml:aws-lb-ingress-test.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: aws-alb-test-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
spec:
  rules:
  - host:
    http:
      paths:
      - path: /old
        pathType: Prefix
        backend:
          service:
            name: lb-svc-2
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: lb-svc-1
            port:
              number: 80
```
:::note warn
テストのために、alb.ingress.kubernetes.io/scheme: internet-facing の Annotation を追加し、インターネットからALBへのアクセスを許可します。
:::

　デプロイ後の Ingress を確認すると、ALB の DNS 名が紐付けられています。

```
 NAME                   CLASS    HOSTS   ADDRESS                                                                  PORTS   AGE
aws-alb-test-ingress   <none>   *       k8s-default-awsalbte-xxxxx.us-east-2.elb.amazonaws.com   80      91m
```

　早速 ALB の public DNS 名でアクセスしてみます。
![TUNA08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2385161/9d18fe5a-2fc2-f93a-d080-b3220fff3ce4.jpeg)
　想定通り、振り分けくれています。
　AWS Load Balancer の詳細設定等は[User Guide](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/guide/ingress/annotations/)を参照してください。 

#Chapter Last
　今回はデプロイと極シンプルのテストをやってみただけですが、ContourとALBにはそれぞれの特性（Contour の HTTP Proxy や ALB の TargetGroup および AWS WAFなど）があるので、それらの比較、そしてTCEとEKSの比較等にも興味がありますので、今後ちょっとずつ書いてみたいです。