TerraformでEKS+ExternalDNSでサービスを公開する方法

## はじめに
[お名前.com](https://www.onamae.com)で取得したドメインを利用して、  
EKSにExternalDNS用のポッドを作成して、指定のFQDNでサービスにアクセスする方法について記載します。

## 前提
- AWSのリソースは[terraform](https://www.terraform.io/)を利用してデプロイします。
  - terraformバージョン: `v0.15.1`
- kubernetesはkubectlのcliを利用して操作します。
  - kubernetesバージョン: `1.19`
- EKSは作成済みとします。
- ドメインは既に取得できているものとします。
   - 本ページでは取得したドメインを`sample.com`とします。

## ドメインをドメインをroute53に割り当て
ドメイン名は取得したドメインそのまま記載してデプロイする

```bash
resource "aws_route53_zone" "main" {
  name = "sample.com"
}
```

## ポリシーを作成
ExternalDNSからroute53を操作するためのポリシーを設定します。
以下ポリシーを作成します。

```bash
resource "aws_iam_policy" "external-dns-policy" {
  name   = "external-dns-policy"
  policy = <<POLICY
{
"Version": "2012-10-17",
 "Statement": [
   {
     "Effect": "Allow",
     "Action": [
       "route53:ChangeResourceRecordSets"
     ],
     "Resource": [
       "arn:aws:route53:::hostedzone/*"
     ]
   },
   {
     "Effect": "Allow",
     "Action": [
       "route53:ListHostedZones",
       "route53:ListResourceRecordSets"
     ],
     "Resource": [
       "*"
     ]
   }
 ]
}
POLICY
}
```

また、EKSが発行したトークンを信頼させるため、IAMのOIDCプロバイダーというエンティティを利用したjson形式のポリシーを作成する。

```bash
data "aws_iam_policy_document" "external-dns-assume-role-policy" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"

    condition {
      test     = "StringEquals"
      variable = "${replace(aws_eks_cluster.eks-cluster.identity[0].oidc[0].issuer, "https://", "")}:sub"
      values   = ["system:serviceaccount:default:external-dns"]
    }

    principals {
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(aws_eks_cluster.<eksクラスタのリソース名>.identity[0].oidc[0].issuer, "https://", "")}"]
      type        = "Federated"
    }
  }
}
```

## IAMロールの作成とポリシーのアタッチ
ExternalDNS用のIAMロールを作成し、
先ほど作成したポリシーを適用する。  

```bash
resource "aws_iam_role" "external-dns-role" {
  name               = "external-dns-role"
  assume_role_policy = data.aws_iam_policy_document.external-dns-assume-role-policy.json
}

resource "aws_iam_role_policy_attachment" external-dns-policy-attachment" {
  policy_arn = aws_iam_policy.external-dns-policy.arn
  role       = aws_iam_role.external-dns-role.name
}
```

作成したロールにポリシーが適用されているか確認
![ポリシー確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271464/961e2218-bbfe-7052-368d-8144ba906174.png)



信頼されたエンティティが登録されているか確認
![oidc-provider.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271464/1d9331c2-614b-bc80-3ceb-b5b5338d1a78.png)


## ExternalDNSをデプロイ


### ServiceAccountの作成
ExternalDNSを操作するためのsaを作成します。
annotationsには先ほど作成したロール(external-dns-role)のarnをアタッチします。

以下コマンドでデプロイを実行
`kubectl apply -f external_dns_sa.yml`

- external_dns_sa.yml

```yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: default
  annotations:
      eks.amazonaws.com/role-arn: <external-dns-roleのarn>
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services","endpoints","pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
- kind: ServiceAccount
  name: external-dns
  namespace: default
```

### ExternalのDeploymentの作成
ExternalDNSをデプロイするためのマニフェストファイルを作成します。
annotationsに先ほど作成したロール(external-dns-role)のarnをアタッチします。

以下コマンドでExternalDNSをデプロイします。
`kubectl apply -f external_dns_deployment.yml`

- external_dns_deployment.yml

```yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
  namespace: default
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
      annotations:
        iam.amazonaws.com/role: <external-dns-roleのarn>
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: k8s.gcr.io/external-dns/external-dns:v0.8.0
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=sample.com
        - --provider=aws
        - --policy=sync
        - --aws-zone-type=public
        - --registry=txt
        - --txt-owner-id=<samplr.comのホストゾーンID>
      securityContext:
        fsGroup: 65534
```

正常にデプロイできたか確認

```bash
$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
external-dns-xxxxxxxxxx   1/1     Running   0          10m
```

コマンドを実行して以下のような最後の方にログが出力されていれば正常に終了している

```bash
$ kubectl logs external-dns-xxxxxxxxxx
-------
time="2020-05-05T02:57:31Z" level=info msg="All records are already up to date"
```

## サンプルアプリケーションのデプロイ
以下マニフェストファイルを基にサンプルアプリケーションをデプロイして
名前解決できるか確認する
- sample_deployment.yml

```yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  selector:
    matchLabels:
      app: sample-app
  replicas: 2
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: nginx-container
        image: nginx:latest
        ports:
        - containerPort: 80
```

- sample_service.yml
annotationsに任意のサブドメインを設定する。

```yml
apiVersion: v1
kind: Service
metadata:
  name: sample-service
  annotations:
    external-dns.alpha.kubernetes.io/hostname: test.sample.com
spec:
  type: LoadBalancer
  selector:
    app: sample-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
```

以下コマンド実行してwebブラウザから
`http://test.sample.com`にアクセスできるか確認

```bash
kubectl apply -f sample_deployment.yml
kubectl apply -f sample_service.yml
```

以下のようなnginxのトップ画面が表示されていれば、設定完了です。
![nginx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271464/48d63d57-0c98-30fe-69ff-ea848533d06f.png)



# 参考
- [Amazon EKS　ExternalDNSでサービスを公開する](https://qiita.com/Brutus/items/0fef45ff87811f1f0c5b)
- [AWS EKSでDNSレコードの作成／変更を楽にする「ExternalDNS」](https://www.atmarkit.co.jp/ait/articles/2002/17/news006.html)
- [Not authorized to perform sts:AssumeRoleWithWebIdentity- 403
](https://stackoverflow.com/questions/66405794/not-authorized-to-perform-stsassumerolewithwebidentity-403)