AWS ALB Ingress Controller を External DNS と Cognito と連携させる on EKS

以下の構成を作成し、Cognito 認証を終えたトラフィックのみ eks の世界に到達できるようにします。

![20200228_CognitoALBExternalDNSonk8s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39442/07d453e6-35ba-55a1-5d90-544517c42e0b.png)

-   参考: [AWS ALB Ingress Controller](https://kubernetes-sigs.github.io/aws-alb-ingress-controller/)

## 環境

Kubernetes バージョン : 1.14
AWS ALB Ingress Controller バージョン : v1.1.5

## EKS クラスター作成

-   まずクラスターを作成

`eksctl create cluster --name=k8s-cluster`

## ALB Ingress Controller のデプロイ

- Ingress リソースがデプロイされたタイミングで ALB を作成させる

1.  IAM OIDC(OpenID Connect) プロバイダーを作成してクラスターに関連付ける
    - k8s 内のリソース ServiceAccount と IAM の橋渡しをさせる
    - 参考: [[アップデート] EKS で IAM ロールを使った Pod 単位のアクセス制御が可能になりました！](https://dev.classmethod.jp/cloud/aws/eks-supports-iam-roles-for-service-accounts/)

    `$ eksctl utils associate-iam-oidc-provider --region=ap-northeast-1 --cluster=k8s-cluster --approve`

1.  AWS IAM => ID プロバイダーを確認し、タイプ OpenIDConnect のプロバイダが作成されていることを確認

1.  ALB Ingress Controller Pod にアタッチする IAM ポリシー作成

    ```bash
    aws iam create-policy \
        --policy-name ALBIngressControllerIAMPolicy \
        --policy-document https://kubernetes-sigs.github.io/aws-alb-ingress-controller/examples/iam-policy.json
    ```
1. 返ってきたARNを控える

    ```json
    {
        "Policy": {
            "PolicyName": "ALBIngressControllerIAMPolicy",
            "PermissionsBoundaryUsageCount": 0,
            "AttachmentCount": 0,
            "IsAttachable": true,
            "PolicyId": "POLICYID",
            "DefaultVersionId": "v1",
            "Path": "/",
            "Arn": "{ALBIngressControllerIAMPolicyのARN}"
        }
    }
    ```

1.  ALB Ingress Controller マニフェストを DL

    ```bash
    wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.5/docs/examples/alb-ingress-controller.yaml
    ```

1.  クラスター名を作成したクラスター名に編集

    `- --cluster-name=k8s-cluster`

1.  RBAC ロールマニフェストをデプロイする

    `kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.5/docs/examples/rbac-role.yaml`

1.  作成したサービスアカウントに IAM ポリシーをアタッチする

    ```bash
    eksctl create iamserviceaccount \
        --region ap-northeast-1 \
        --name alb-ingress-controller \
        --namespace kube-system \
        --cluster k8s-cluster \
        --attach-policy-arn {さっき控えたALBIngressControllerIAMPolicyのARN} \
        --override-existing-serviceaccounts \
        --approve
    ```

1.  ALB Ingress Controller をデプロイする

    ```bash
    kubectl apply -f alb-ingress-controller.yaml
    ```

1.  ALB Ingress Controller の作成を確認

    ```bash
    kubectl logs -n kube-system $(kubectl get po -n kube-system | egrep -o "alb-ingress[a-zA-Z0-9-]+")
    ```

## ExternalDNS のデプロイ

-   ingress リソースのホスト情報に基づいて DNS レコードをプロビジョニングする
-   alb-ingress-controller がデプロイした ALB を指す Route 53 のレコードを作成する

1.  IAM ポリシー(AllowExternalDNSUpdates)を作成
    -  ExternalDNSPod に Route53 リソースレコードセットとホストゾーンを更新させるために IAM ポリシーを作成
    -  IAM ポリシーの[元ネタ](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#iam-policy)を get

    ```bash
    aws iam create-policy --policy-name AllowExternalDNSUpdates --policy-document file://AllowExternalDNSUpdates.json
    ```
1. 返ってきたARNを控える

    ```json
    {
    "Policy": {
    "PolicyName": "AllowExternalDNSUpdates",
    "PermissionsBoundaryUsageCount": 0,
    "AttachmentCount": 0,
    "IsAttachable": true,
    "PolicyId": "POLICYID",
    "DefaultVersionId": "v1",
    "Path": "/",
    "Arn": "{AllowExternalDNSUpdatesのARN}"
        }
    }
    ```

1.  サービスアカウント(external-dns)と IAM ロールを作成

    ```bash
    eksctl create iamserviceaccount \
        --region ap-northeast-1 \
        --name external-dns \
        --namespace kube-system \
        --cluster k8s-cluster \
        --attach-policy-arn {AllowExternalDNSUpdatesのARN} \
        --override-existing-serviceaccounts \
        --approve
    ```

1.  IAM -> ポリシー AllowExternalDNSUpdates -> ポリシーの使用状況タブを見て、ロールが作成されていることを確認

1.  Route 53 ホストゾーンを設定する
    -    public に レコードと SOA レコードを作成する

1.  ExternalDNS を deploy する
    -   externaldns.yaml の[元ネタ](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#manifest-for-clusters-with-rbac-enabled)を get する
    -   externaldns の ServiceAccount の annotaions に AllowExternalDNSUpdates ポリシーがアタッチされているロールの arn を記載する
    -   `$ kubectl apply -f externaldns.yaml`

1.  ExternalDNS の作成を確認

    ```bash
    kubectl logs -n kube-system $(kubectl get po -n kube-system | egrep -o "external-dns[a-zA-Z0-9-]+")
    ```

1.  ExternalDNSPod にロールがアタッチされていることを確認

    ```bash
    kubectl exec -n kube-system external-dns-576fb7578c-zmvlt env | grep AWS
    AWS_ROLE_ARN={ロールのarn}
    ```

## Cognito / ALB Ingress Controller のセットアップ

-   Cognito ユーザープール作成

1. ベースは[インフラエンジニアが一切コードを書かずにWebサーバーに認証機能を実装した話](https://dev.classmethod.jp/cloud/alb-cognito-user-pool/)をみつつ作成する
    - アプリクライアントのシークレットを生成するに
    - ALLOW_USER_PASSWORD_AUTH にチェック

1. Certificate Manger (バージニア北部) にてドメインの証明書を発行する
    - 証明書を Cognito でリクエストまたはインポートするには、ACM コンソールで AWS リージョンを米国東部 (バージニア北部) に変更する必要がある。

1. Route53 でサブドメイン無しの A レコードを適当に設定
    - 1.1.1.1 とかでも よい

1. アプリケーション統合の構成
    - Callback URL
        - `https://<your-domain>/oauth2/idpresponse`
    - 許可されている OAuth フロー
        - Authorization code grant
    - 許可されている OAuth スコープ
        - openid
    - ドメイン名
        - `auth.<your-domain>`
        - AWS マネージド証明書に CM(バージニア北部)で発行した証明書を指定

1. ドメインのステータスが 15 分程度かかって Active になったらドメイン名とエイリアスターゲットを Route53 に登録する
    - タイプ CNAME
    - 例) auth.<your-domain> → xxxxxxxxxxxxxx.cloudfront.net

## Ingress をデプロイ

```ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: "ingress"
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'

    # ドメインの証明書のarn
    alb.ingress.kubernetes.io/certificate-arn: {ドメインの証明書のarn}
    # http -> https のリダイレクト
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'

    # Authentication type must be cognito
    alb.ingress.kubernetes.io/auth-type: cognito

    # Required parameter for ALB/Cognito integration
    alb.ingress.kubernetes.io/auth-scope: openid

    # Session timeout on authentication credentials
    alb.ingress.kubernetes.io/auth-session-timeout: '3600'

    # Session cookie name
    alb.ingress.kubernetes.io/auth-session-cookie: AWSELBAuthSessionCookie

    # Action to take when a request is not authenticated
    alb.ingress.kubernetes.io/auth-on-unauthenticated-request: authenticate

    # Cognito parameters required for creation of authentication rules
    # The subdomain name only is sufficient for `UserPoolDomain`
    # e.g. if `FQDN=app.auth.ap-northeast-1.amazoncognito.com` then `UserPoolDomain=app`
    alb.ingress.kubernetes.io/auth-idp-cognito: '{"UserPoolArn": "arn:aws:cognito-idp:<region>:<account-id>:userpool/<region><cognito-id>","UserPoolClientId":"<user-pool-client-id>","UserPoolDomain":"<user-pool-authentication-domain>"}'


  labels:
    app: ingress
spec:
  rules:
    - host: <your-domain>
      http:
        paths:
          - path: /*
            backend:
              serviceName: "bff-service"
              servicePort: 3000
```

1.  ドメインの証明書の arn(alb.ingress.kubernetes.io/certificate-arn) を変更
    -   クラスターのあるリージョンで申請した SSL 証明書の ARN を設定しないとエラーがでた

1.  Cognito 設定(alb.ingress.kubernetes.io/auth-idp-cognito)を変更

    `kubectl apply -f ingress.yaml`

1.  確認(alb リソースなど完全にできるまで時間がかかる)

## アプリをデプロイ
- アプリは適当に好きなマニフェストでやってください

-   backend deploy

    -   kubectl apply -f backend.yaml

    - <details><summary>backend.yaml 例</summary><div>

        ```yaml
##################################################################################################
# backend services
##################################################################################################
apiVersion: v1
kind: Service
metadata:
    name: backend
spec:
    type: ClusterIP # クラスタ内でのみ利用可能
    ports:
        - name: http
          protocol: TCP
          port: 8080
          targetPort: 8080
    selector:
        app: backend-pod # 転送先Podを設定
---
apiVersion: v1
kind: ServiceAccount
metadata:
    name: backend-serviceaccount
---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: backend-deployment
spec:
    replicas: 2
    selector:
        matchLabels:
            app: backend-pod
            version: v1
    template:
        metadata:
            labels:
                app: backend-pod
                version: v1
        spec:
            serviceAccountName: backend-serviceaccount
            containers:
                - name: backend-container
                  image: { container_url }
                  imagePullPolicy: Always
                  ports:
                      - containerPort: 8080
---


        ```
    
        </div></details>

-   bff deploy

    -   kubectl apply -f bff.yaml

    - <details><summary>bff.yaml 例</summary><div>

        ```yaml
##################################################################################################
# bff service
##################################################################################################
apiVersion: v1
kind: Service
metadata:
    name: "bff-service"
spec:
    type: NodePort # k8sノードのIP:Portで受けたトラフィックをコンテナに転送する
    ports:
        - protocol: TCP
          port: 3000
          targetPort: 3000
    selector:
        app: "bff-pod" # 転送先Podを設定
---
apiVersion: v1
kind: ServiceAccount
metadata:
    name: bff-serviceaccount
---
apiVersion: apps/v1
kind: Deployment
metadata:
    name: bff-deployment
spec:
    replicas: 2
    selector:
        matchLabels:
            app: bff-pod
            version: v1
    template:
        metadata:
            labels:
                app: bff-pod
                version: v1
        spec:
            serviceAccountName: bff-serviceaccount
            containers:
                - name: bff-container
                  image: { container_url }
                  imagePullPolicy: Always
                  ports:
                      - containerPort: 3000
---

        ```
    
        </div></details>


## 確認

1. ALB が作成されていること
1. Route 53 に ALB への A レコードと TXT レコードが作成されていること

## でCognito認証 + ALB → EKS内への連携が出来ている筈