はじめに
個人で作成しているサービスにて、ExternalDNSを使って、Route 53からEKSへアクセスできるようになりましたので、ここまでの知見を記事に残します。
このアクセスができる前までは、TerraformでAWSのサービスを構築し、マニフェストファイルにてPod、LoadBalancerの作成までできていました。
このときの構成図は以下のとおりです。
この構成では、EKSで作成したALBが公開したURLを直接指定することで、サービスにアクセスできます。ただしそのURLは、LoadBalancerで作成されるためランダムになってしまいます。
そのためRoute 53経由でEKSへアクセスできるようにしようと考えました。
前提
以下環境で開発、構築を行っています。
- Kubernetes: v1.16
- Terraform: v12.28
- Route 53でドメイン取得済み
Route 53でドメイン取得時、以下のようにNSタイプ、SQAタイプのみ存在していました。
ExternalDNSを使用してEKSとRoute 53の紐付け
こちらの記事「Kubernetesを学ぶ意味とは――AWS EKSでクラスタを構築してみよう (1/4)」、「AWS EKSでDNSレコードの作成/変更を楽にする「ExternalDNS」 (1/4)」を参考にして対応しました。
基本的には上記の記事どおりなのですが、この記事ではTerraformを使った場合や、記事公開時と異なる点に対する対応を載せて説明します。
ExternalDNSについて
ExternalDNSの公式サイトによると、ExternalDNSはKubernetesのサービスやIngressをDNSのプロバイダーと同期してくれるツールです。
ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
この記事でのDNSのプロバイダーはRoute 53となるため、ExternalDNSにて、前提項目でのKubernetesのLoadBalancerとRoute 53を紐付けることを可能にしてくれます。
IAMの設定
参考記事では、以下ロールやポリシーの作成やアタッチを、直接AWSマネジメントコンソール上で行っています。
- Route 53に対するポリシーを作成する。
- ExternalDNS用IAMロールの信頼関係にセットする信頼ポリシーを作成する。
この信頼ポリシーにはEKSのワーカーノードのARNをセットする。 - ExternalDNS用のIAMロールを作成し、1のポリシー、2の信頼ポリシーをセットする。
ここでは上記1~3をTerraformで行った場合を載せます。なお、3でEKSのワーカーノードのARNが必要になるため、こちらもTerraformでワーカーノード用のIAMロールを作成しておきます。
ロールの作成やポリシーのアタッチについては、こちらの記事「【連載】terraform によるAWS環境構築入門 第2回 ~ 権限管理とモジュール化 ~」を参考にしました。
# 1の対応
resource "aws_iam_policy" "route53-external-policy" {
name = "route53-external-policy"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets"
],
"Resource": [
"arn:aws:route53:::hostedzone/*"
]
},
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": [
"*"
]
}
]
}
EOF
}
# EKSのワーカーノードのIAMロール作成
resource "aws_iam_role" "eks_node_group_iam_role" {
name = "eks-node-group-iam-role"
assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
POLICY
}
# 2の対応
data "aws_iam_policy_document" "route53-externaldns-policy" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
#identifiersにEKSワーカーノードのarnをセットする。
identifiers = ["${aws_iam_role.eks_node_group_iam_role.arn}"]
}
}
}
# 3の対応
resource "aws_iam_role" "route53-externaldns-controller" {
name = "route53-externaldns-controller"
assume_role_policy = data.aws_iam_policy_document.route53-externaldns-policy.json
}
# 3の対応
resource "aws_iam_role_policy_attachment" "route53-externaldns-attachment"{
role = aws_iam_role.route53-externaldns-controller.name
policy_arn = aws_iam_policy.route53-external-policy.arn
}
マニフェストファイルの作成
kube2iamとは
kube2iamの公式ページでは、「アノテーションに基づいて、IAMクレデンシャルをKubernetesのクラスター内で実行しているコンテナに与える」とあります。
Provide IAM credentials to containers running inside a kubernetes cluster based on annotations.
また参考にしている記事では、以下に記載してあるように、アプリケーションに対して必要なアクセス権を与えられるとあります。
アクセス権限が必要ないアプリケーションに対してアクセス権限が付与されることを防ぐために、クラスタ内で起動しているアプリケーションそれぞれに対して最低限必要なアクセス権限を与えられる「kube2iam」
kube2iamの作成
さっそくkube2iamのマニフェストファイルを作成します。このファイルは参考記事や公式ページで紹介されているので、そのファイルを参考に作成します。
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube2iam
namespace: kube-system
---
apiVersion: v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube2iam
rules:
- apiGroups: [""]
resources: ["namespaces","pods"]
verbs: ["get","watch","list"]
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube2iam
subjects:
- kind: ServiceAccount
name: kube2iam
namespace: kube-system
roleRef:
kind: ClusterRole
name: kube2iam
apiGroup: rbac.authorization.k8s.io
kind: List
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube2iam
namespace: kube-system
labels:
app: kube2iam
spec:
selector:
matchLabels:
app: kube2iam
template:
metadata:
labels:
app: kube2iam
spec:
serviceAccountName: kube2iam
hostNetwork: true
containers:
- name: kube2iam
image: jtblin/kube2iam:latest
imagePullPolicy: Always
args:
- "--auto-discover-base-arn"
- "--iptables=true"
- "--host-ip=$(HOST_IP)"
- "--host-interface=eni+"
- "--verbose"
env:
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- containerPort: 8181
hostPort: 8181
name: http
securityContext:
privileged: true
ExternalDNSの作成
記事を参考にしたマニフェストファイルを作成した際、以下2点の問題が発生していました。
- DeploymentのapiVersionが
apiVersion: extensions/v1beta1
だが、2020/08/10時点でextensions/v1beta1
は使用不可となっている。 - ClusterRoleにapiGroupが不足しているため、ExternalDNSのマニフェストファイルapply時に
failed to sync cache: timed out waiting for the condition
が発生する。
1については、apiVersionをapps/v1に変更し、不足しているselectorを追加します。
2については、現象発生時の実際のログは以下のとおりです。
time="2020-08-10T12:39:22Z" level=info msg="Instantiating new Kubernetes client"
time="2020-08-10T12:39:22Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2020-08-10T12:39:22Z" level=info msg="Created Kubernetes client https://172.20.0.1:443"
time="2020-08-10T12:40:22Z" level=fatal msg="failed to sync cache: timed out waiting for the condition"
この問題についてExternalDNSにissueがあがっていました。このissueによると、endpointのリソースを持つapiGroupsを加えることで解決できるようです。
After I recreated some nodes, external-dns failed to startup again. It failed after printing the error message "failed to sync cache: timed out waiting for the condition". It seems that endpoints were added and external-dns now requires extra permissions.
Make sure you have added
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get","watch","list"]
to your external-dns ClusterRole. Adding this solved the problem for me.
1、2を含めたマニフェストファイルは以下のとおりです。
また、Deploymentの```iam.amazonaws.com/role```にて、ExternalDNSのIAMロール(route53-externaldns-controller)のARNが必要になります。そのためARNを取得し```iam.amazonaws.com/role```にセットしておきます。
```external-dns-manifest.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get","watch","list"]
---
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
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: external-dns
name: external-dns
spec:
selector:
matchLabels:
app: external-dns
strategy:
type: Recreate
template:
metadata:
labels:
app: external-dns
annotations:
iam.amazonaws.com/role: $ROUTE53_EXDNS_CONTROLLER_ARN
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
args:
- --source=service
- --source=ingress
- --domain-filter=the-weather-report.com
- --provider=aws
- --policy=sync
- --aws-zone-type=public
- --registry=txt
- --txt-owner-id=example-identifier
マニフェストファイルの適用
上記項目で作成したマニフェストファイルをapplyします。
なお、deployment.yamlはVue、Ruby on Railsを1つのpodとして作成したマニフェストファイルです。
kubectl apply -f ./kube2iam/kube2iam.yaml
kubectl apply -f ./deployment.yaml
kubectl apply -f ./external-dns-manifest.yaml
PodがRunningになっていること、ServiceのLoadBalancerにEXTERNAL-IPが割り当てられていることを確認します。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
external-dns-xxxxx 1/1 Running 0 39s
・・・略・・・
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP xxx.xxx.xxx.xxx <none> 443/TCP 13m
weatherreport-service LoadBalancer xxx.xxx.xxx.xxx 
xxxxx.ap-northeast-1.elb.amazonaws.com 80:31619/TCP 61s
ExternalDNSのpodのログを確認し、All records are already up to date
が表示されていれば成功です。
$ kubectl logs external-dns-xxxxx
time="2020-08-11T15:59:20Z" level=info msg="Created Kubernetes client https://172.20.0.1:443"
time="2020-08-11T15:59:29Z" level=info msg="Desired change: UPSERT the-weather-report.com A [Id: /hostedzone/Z11CZOCGRGWAHV]"
time="2020-08-11T15:59:29Z" level=info msg="Desired change: UPSERT the-weather-report.com TXT [Id: /hostedzone/Z11CZOCGRGWAHV]"
time="2020-08-11T15:59:30Z" level=info msg="2 record(s) in zone the-weather-report.com. [Id: /hostedzone/Z11CZOCGRGWAHV] were successfully updated"
time="2020-08-11T16:00:29Z" level=info msg="All records are already up to date"
マネジメントコンソールでRoute 53を見てみると、取得したドメインにAレコード、TXTレコードが作成されていることが分かります。
このAレコードのドメインにアクセスするとアプリケーションが表示されます。
ExternalDNSを使ってRoute 53からアクセスできる構成図は以下のとおりです。
おわりに
ExternalDNSを使用し、EKSとRoute 53を紐付ける方法を説明しました。
ExternalDNSを最初見たときには何をやっているのだろうかと疑問でしたが、実際に動かし説明を読むことで、理解が深まりました。
この記事が誰かのお役に立てれば幸いです。