既存のALBを削除せずにALB Ingress ControllerからALB LoadBalancer Controllerに移行する

# はじめに

[ALB LoadBalancer Controller](https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/)はKubernetesからELBを操作するためのものです。
元々はALB Ingress Controllerという名称で、複数NamespaceのIngressリソースを1つのALBで対応することができませんでした。

これは以前記事に書いた通り、[NGINX Ingress Controller](https://kubernetes.github.io/ingress-nginx/)をALBとアプリケーションの間に挟むことで解決することができました。

https://qiita.com/samskeyti/items/4b5b25f92f66bbaa9a36

ALB Ingress ControllerとNGINX Ingress Controllerを使用するときの構成は下図になります。

![alb-loadbalancer-controller-Page-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/f134e5d0-b309-7528-f200-42bf674b857a.png)

一方、ALB LoadBalancer Controllerでは下図のようにIngressリソースなしで1つのALBで複数Namespace上のアプリケーションにルーティングさせることができます。

![alb-loadbalancer-controller-Page-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/1a408d30-271b-7457-4dd6-b097e9e40981.png)

今回は既存のALBを削除せずにALB Ingress Controller + NGINX Ingress Controllerの構成からALB LoadBalancer Controllerの構成に移行する手順を説明します。

# 事前準備

ALB Ingress ControllerとNGINX Ingress ControllerをHelmでデプロイします。IAM Role for ServiceAccountを使用しています。

```yaml:aws-alb-ingress-controller-values.yaml
clusterName: <CLUSTER_NAME>
awsRegion: ap-northeast-1
awsVpcID: <VPC_ID>
rbac:
  serviceAccountAnnotations: 
    eks.amazonaws.com/role-arn: <IAM_ROLE_ARN>
```

```yaml:nginx-ingress-values.yaml
controller:
  service:
    type: NodePort
```

```console
$ helm install aws-alb-ingress-controller \
  incubator/aws-alb-ingress-controller \
  -f aws-alb-ingress-controller-values.yaml \
  --version 0.1.11 \
  -n kube-system
$ helm install nginx-ingress \
  stable/nginx-ingress \
  -f nginx-ingress-values.yaml \
  --version 1.39.0 \
  -n kube-system
```

ALB Ingress Controllerで使用するIAM RoleにアタッチするIAM Policyは以下になります。
後述するALB LoadBalancer Controllerでも同じIAM Roleを使用します。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "acm:DescribeCertificate",
                "acm:ListCertificates",
                "acm:GetCertificate"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:CreateSecurityGroup",
                "ec2:CreateTags",
                "ec2:DeleteTags",
                "ec2:DeleteSecurityGroup",
                "ec2:DescribeAccountAttributes",
                "ec2:DescribeAddresses",
                "ec2:DescribeInstances",
                "ec2:DescribeInstanceStatus",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeTags",
                "ec2:DescribeVpcs",
                "ec2:ModifyInstanceAttribute",
                "ec2:ModifyNetworkInterfaceAttribute",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddListenerCertificates",
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticloadbalancing:CreateRule",
                "elasticloadbalancing:CreateTargetGroup",
                "elasticloadbalancing:DeleteListener",
                "elasticloadbalancing:DeleteLoadBalancer",
                "elasticloadbalancing:DeleteRule",
                "elasticloadbalancing:DeleteTargetGroup",
                "elasticloadbalancing:DeregisterTargets",
                "elasticloadbalancing:DescribeListenerCertificates",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeLoadBalancerAttributes",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:DescribeSSLPolicies",
                "elasticloadbalancing:DescribeTags",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetGroupAttributes",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:ModifyLoadBalancerAttributes",
                "elasticloadbalancing:ModifyRule",
                "elasticloadbalancing:ModifyTargetGroup",
                "elasticloadbalancing:ModifyTargetGroupAttributes",
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:RemoveListenerCertificates",
                "elasticloadbalancing:RemoveTags",
                "elasticloadbalancing:SetIpAddressType",
                "elasticloadbalancing:SetSecurityGroups",
                "elasticloadbalancing:SetSubnets",
                "elasticloadbalancing:SetWebACL"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole",
                "iam:GetServerCertificate",
                "iam:ListServerCertificates"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cognito-idp:DescribeUserPoolClient"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "waf-regional:GetWebACLForResource",
                "waf-regional:GetWebACL",
                "waf-regional:AssociateWebACL",
                "waf-regional:DisassociateWebACL"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "tag:GetResources",
                "tag:TagResources"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "waf:GetWebACL",
                "wafv2:GetWebACLForResource"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags",
                "ec2:DeleteTags"
            ],
            "Resource": "arn:aws:ec2:*:*:security-group/*",
            "Condition": {
                "Null": {
                    "aws:ResourceTag/ingress.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:RemoveTags",
                "elasticloadbalancing:DeleteTargetGroup"
            ],
            "Resource": [
                "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*"
            ],
            "Condition": {
                "Null": {
                    "aws:ResourceTag/ingress.k8s.aws/cluster": "false"
                }
            }
        }
    ]
}
```

次に、ALBに設定するためのIngressリソースをデプロイします。
annotationsに`alb.ingress.kubernetes.io/load-balancer-attributes`で削除保護を設定しています。これによりIngressリソースを削除してもALBが削除されることはなくなります。

```yaml:alb-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/load-balancer-attributes: deletion_protection.enabled=true
  name: alb-ingress
  namespace: kube-system
spec:
  rules:
    - http:
        paths:
          - backend:
              serviceName: nginx-ingress-controller
              servicePort: 80
```

```console
$ kubectl apply -f alb-ingress.yaml
```

次に、2つのNamespaceにアプリケーションをデプロイします。今回はNGINXを使用しています。
FQDNは例としてそれぞれ`app1.sample.com`、`app2.sample.com`と記述しています。

```yaml:app.yaml
kind: Namespace
apiVersion: v1
metadata:
  name: app1
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: app1-ing
  namespace: app1
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: app1.sample.com
      http:
        paths:
          - backend:
              serviceName: app1-svc
              servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: app1-svc
  namespace: app1
spec:
  type: ClusterIP
  ports:
    - name: http-port
      protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: app1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app1-deploy
  namespace: app1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app1
  template:
    metadata:
      labels:
        app: app1
    spec:
      containers:
        - name: app1
          image: nginx:1.13
          ports:
            - containerPort: 80
---
kind: Namespace
apiVersion: v1
metadata:
  name: app2
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: app2-ing
  namespace: app2
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: app2.sample.com
      http:
        paths:
          - backend:
              serviceName: app2-svc
              servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: app2-svc
  namespace: app2
spec:
  type: ClusterIP
  ports:
    - name: http-port
      protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: app2
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app2-deploy
  namespace: app2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app2
  template:
    metadata:
      labels:
        app: app2
    spec:
      containers:
        - name: app2
          image: nginx:1.13
          ports:
            - containerPort: 80
```

```console
$ kubectl apply -f app.yaml
```

# ALB LoadBalancer Controllerへの移行

ここでは、移行する手順を説明します。

## 1. ALB Ingress Controllerの削除

既存のALB Ingress Controllerを削除します。Ingressリソースは削除しないことに注意します。

```console
$ helm uninstall aws-alb-ingress-controller -n kube-system
```

## 2. ALB LoadBalancer Controllerのデプロイ

TargetGroupBindingのCRDをデプロイします。

```console
$ kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master"
```

ALB LoadBalancer ControllerをHelmでデプロイします。バージョンに気をつけます。

```yaml:aws-load-balancer-controller-values.yaml
clusterName: <CLUSTER_NAME>
region: ap-northeast-1
vpcId: <VPC_ID>
serviceAccount:
  annotations:
    "eks.amazonaws.com/role-arn": <IAM_ROLE_ARN>
```

```console
$ helm install aws-load-balancer-controller \
  eks/aws-load-balancer-controller \
  -f aws-load-balancer-controller-values.yaml \
  --version 1.0.8 \
  -n kube-system
```

## 3. Ingressリソースの削除

ALBの設定をKubernetesリソースから操作することはやめます。Ingressリソースは不要になるため削除します。

```console
$ kubectl delete -f alb-ingress.yaml
```

## 4. TargetGroupの作成

Serviceへのルーティングを設定するためにTargetGroupをAWSコンソールから作成していきます。下画像ではALB Ingress Controllerが作成したものが残っていますが、まだ削除しません。

![スクリーンショット 2021-03-14 18.55.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/3faf9a62-a0e4-c860-ff8f-8d29d4a7dca7.png)

target typeはIP addressにします。

![スクリーンショット 2021-03-14 19.03.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/98678180-ad45-59aa-b309-cdcfb435e9d5.png)

Register targetsには何も記入せず作成します。

![スクリーンショット 2021-03-14 19.03.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/92c253aa-d599-e53c-6a25-1dcac06b02fa.png)

今回は2つのアプリケーションをデプロイするので2つ分TargetGroupを追加しました。

![スクリーンショット 2021-03-14 19.06.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/946e61a1-01de-1f2c-f1c0-aa81d335fb2c.png)

## 5. リスナールールの変更

ALB Ingress Controllerが作成したALBのリスナールールを変更していきます。

![スクリーンショット 2021-03-14 19.10.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/46d84ca2-7e0c-2707-e6a9-0f2bd012f7f5.png)

全てのリクエストがNGINX Ingress Controllerに向かうようになっています。これにルールを追加します。

![スクリーンショット 2021-03-14 19.09.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/f4e2827a-7535-58cf-a664-845b4016f640.png)

ホスト名に合わせて宛先を先ほど作成したTargetGroupになるようにしています。

![スクリーンショット 2021-03-14 19.19.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/332da3c3-3c6a-9e21-c2e4-f43e229d041a.png)

## 6. TargetGroupBindingリソースの作成

TargetGroupとServiceを紐づけるための設定を加えます。SecurityGroupはALBに設定されているものを使用します。

```yaml:targetgroupbinding.yaml
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  name: app1-tgb
  namespace: app1
spec:
  serviceRef:
    name: app1-svc
    port: 80
  targetGroupARN: <TARGET_GROUP_ARN>
  targetType: ip
  networking:
    ingress:
    - from:
      - securityGroup:
          groupID: <SECURITY_GROUP_ID>
      ports:
      - protocol: TCP
---
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
  name: app2-tgb
  namespace: app2
spec:
  serviceRef:
    name: app2-svc
    port: 80
  targetGroupARN: <TARGET_GROUP_ARN>
  targetType: ip
  networking:
    ingress:
    - from:
      - securityGroup:
          groupID: <SECURITY_GROUP_ID>
      ports:
      - protocol: TCP
```

```console
$ kubectl apply -f targetgroupbinding.yaml
```

## 7. ゴミ掃除

ALBリスナールールからNGINX Ingress Controller向けの設定を削除します。

![スクリーンショット 2021-03-14 19.19.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/332da3c3-3c6a-9e21-c2e4-f43e229d041a.png)

![スクリーンショット 2021-03-14 19.26.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/a68d1db3-d455-5f03-e8d3-df611ac02245.png)

IngressリソースとNGINX Ingress Controllerは不要なので削除します。

```console
$ kubectl delete ing app1-ing -n app1
$ kubectl delete ing app2-ing -n app2
$ helm uninstall nginx-ingress -n kube-system
```

# 確認

移行前のURLでアプリケーションにアクセスできることを確認します。

![スクリーンショット 2021-03-14 19.37.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/72cb582d-c1dc-37be-fe01-5a30971e2ee3.png)

![スクリーンショット 2021-03-14 19.38.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307853/69dad52d-a73c-d5c7-9330-d4d79d2ee1e7.png)

# まとめ

既存のALBを削除せずにALB Ingress Controller + NGINX Ingress ControllerからALB LoadBalancer Controllerに移行する手順を説明しました。
これによりALBをKubernetes側で管理する必要がなくなり、且つ複数Namespace上のアプリケーションにアクセスする時も1つのALBで事足りるので移行するメリットは大きいです。


# 参考

https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/upgrade/migrate_v1_v2/