external-dnsを試す

external-dnsを試してみたメモ。

前回AWS Load Balancer Controllerを試したクラスターをそのまま使う。

コンポーネント バージョン 備考
eksctl 0.30.0
Kubernetes バージョン 1.18
プラットフォームのバージョン eks.1
aws-load-balancer-controller 2.0.0
external-dns 1.7.3

以下に従って導入する。

準備

パブリックホストゾーンを作成する。

aws route53 create-hosted-zone --name "sotoiwa.dev." --caller-reference "external-dns-test-$(date +%s)"

お名前.comの側にて、ここで出力されたネームサーバーを設定する。

導入

IAM ポリシーを作成する。

cat <<EOF > iam-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets"
      ],
      "Resource": [
        "arn:aws:route53:::hostedzone/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}
EOF
aws iam create-policy \
    --policy-name ExternalDNSIAMPolicy \
    --policy-document file://iam-policy.json

IAMロールとServiceAccountを作成する。

eksctl create iamserviceaccount \
  --cluster=mycluster \
  --namespace=kube-system \
  --name=external-dns \
  --attach-policy-arn=arn:aws:iam::XXXXXXXXXXXX:policy/ExternalDNSIAMPolicy \
  --override-existing-serviceaccounts \
  --approve

ServiceAccountを確認する。

$ kubectl get sa external-dns -n kube-system -o yaml | kubectl neat
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-mycluster-addon-iamserviceaccount-kub-Role1-AQ9HDHDEEAMI
  name: external-dns
  namespace: kube-system
secrets:
- name: external-dns-token-s8pn8

external-dnsをデプロイする。

Helmチャートもある。公式のは非推奨となり、Bitnamiが作成しているものが推奨のよう。

$ helm search repo external-dns
NAME                    CHART VERSION   APP VERSION DESCRIPTION
bitnami/external-dns    3.5.0           0.7.4       ExternalDNS is a Kubernetes addon that configur...
stable/external-dns     2.20.4          0.7.0       DEPRECATED ExternalDNS is a Kubernetes addon th...

今回はチュートリアルに記載のあるマニフェストを適用する。チュートリアルで公開されているものから以下を変更する。

  • ServiceAccountは作成済みでアノテーションも設定済みなので、その記載に合わせる
  • --domain-filterを作成したホストゾーンのドメイン名に合わせる
  • ClusterRoleBindingでdefault Namespaceが指定されているので、今回デプロイするNamespaceに合わせる
  • レコードの削除もさせるため、--policysyncにする
  • IRSAを使用するためDeploymentでのIAMロールのアノテーションは削除
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  # If you're using Amazon EKS with IAM Roles for Service Accounts, specify the following annotation.
  # Otherwise, you may safely omit it.
  annotations:
    # Substitute your account ID and IAM service role name below.
    eks.amazonaws.com/role-arn: arn:aws:iam::XXXXXXXXXXXX:role/eksctl-mycluster-addon-iamserviceaccount-kub-Role1-AQ9HDHDEEAMI
---
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: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: k8s.gcr.io/external-dns/external-dns:v0.7.3
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=sotoiwa.dev # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
        - --provider=aws
        - --policy=sync # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
        - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
        - --registry=txt
        - --txt-owner-id=my-hostedzone-identifier
      securityContext:
        fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files

デプロイする。Namespaceは専用に作ってもよいかも知れないが、今回はkube-system Namespaceにデプロイする。

$ kubectl apply -f external-dns.yaml -n kube-system
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
serviceaccount/external-dns configured
clusterrole.rbac.authorization.k8s.io/external-dns created
clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created
deployment.apps/external-dns created

Podが起動していることを確認する。

$ kubectl get pod -n kube-system
NAME                                            READY   STATUS    RESTARTS   AGE
aws-load-balancer-controller-6c4d8d8f64-6gsw7   1/1     Running   0          3d1h
aws-node-chr59                                  1/1     Running   0          10d
aws-node-xzsx5                                  1/1     Running   0          7d14h
coredns-86f7d88d77-d4969                        1/1     Running   0          10d
coredns-86f7d88d77-fntwt                        1/1     Running   0          10d
external-dns-869d587bf7-pvf89                   1/1     Running   8          26m
kube-proxy-bmp9g                                1/1     Running   0          7d14h
kube-proxy-r7hwz                                1/1     Running   0          10d

動作確認

準備

default Namespaceで試す。

Deploymentのyamlを用意する。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: echoserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echoserver
  template:
    metadata:
      labels:
        app: echoserver
    spec:
      containers:
      - image: gcr.io/google_containers/echoserver:1.4
        imagePullPolicy: Always
        name: echoserver
        ports:
        - containerPort: 8080

Deploymentを作成する。

$ kubectl apply -f echoserver-deployment.yaml
deployment.apps/echoserver created

Podの起動を確認する。

$ kubectl get pod
NAME                          READY   STATUS    RESTARTS   AGE
echoserver-7c48fd4b7c-9jkq4   1/1     Running   0          70s

Service

Serviceのyamlを用意する。

apiVersion: v1
kind: Service
metadata:
  name: echoserver
  annotations:
    external-dns.alpha.kubernetes.io/hostname: test1.sotoiwa.dev
spec:
  type: LoadBalancer
  ports:
  - port: 80
    name: http
    targetPort: 8080
  selector:
    app: echoserver

Serviceを作成する。

$ kubectl apply -f echoserver-service-lb.yaml
service/nginx created

Serviceを確認する。

$ kubectl get svc
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)        AGE
echoserver   LoadBalancer   10.100.243.210   a7ef2970caed5415088bba5a7f3e9dc9-1308473638.ap-northeast-1.elb.amazonaws.com   80:30046/TCP   42s
kubernetes   ClusterIP      10.100.0.1       <none>

curlでALBのドメイン名でアクセスしてみる。

$ curl a7ef2970caed5415088bba5a7f3e9dc9-1308473638.ap-northeast-1.elb.amazonaws.com
CLIENT VALUES:
client_address=192.168.78.38
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://a7ef2970caed5415088bba5a7f3e9dc9-1308473638.ap-northeast-1.elb.amazonaws.com:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=a7ef2970caed5415088bba5a7f3e9dc9-1308473638.ap-northeast-1.elb.amazonaws.com
user-agent=curl/7.54.0
BODY:
-no body in request-

マネジメントコンソールでレコードが作成されていることを確認する。

f:id:sotoiwa:20201030031519p:plain

curlで登録したドメイン名でアクセスしてみる。

$ curl test1.sotoiwa.dev
CLIENT VALUES:
client_address=192.168.78.38
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://test1.sotoiwa.dev:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=test1.sotoiwa.dev
user-agent=curl/7.54.0
BODY:
-no body in request-

Serviceを削除する。

$ kubectl delete svc echoserver
service "echoserver" deleted

Ingress

Ingressで試す。

ServiceをClusterIPで作り直す。yamlを用意する。

apiVersion: v1
kind: Service
metadata:
  name: echoserver
spec:
  ports:
  - port: 8080
    name: http
    targetPort: 8080
  selector:
    app: echoserver

Serviceを作成する。

$ kubectl apply -f echoserver-service.yaml
service/echoserver created

Serviceを確認する。

$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
echoserver   ClusterIP   10.100.39.29   <none>        8080/TCP   24s
kubernetes   ClusterIP   10.100.0.1     <none>        443/TCP    10d

Ingressyamlを用意する。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: echoserver
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
  - host: test2.sotoiwa.dev
    http:
      paths:
      - path: /*
        backend:
          serviceName: echoserver
          servicePort: 8080

Ingressを作成する。

$ kubectl apply -f echoserver-ingress.yaml
ingress.networking.k8s.io/echoserver created

Ingressを確認する。

$ kubectl get ingress
NAME         CLASS    HOSTS               ADDRESS                                                                     PORTS   AGE
echoserver   <none>   test2.sotoiwa.dev   k8s-default-echoserv-91c6428909-23022959.ap-northeast-1.elb.amazonaws.com   80      80s

マネジメントコンソールでレコードが作成されていることを確認する。

f:id:sotoiwa:20201030031545p:plain

リスナーのルールは以下のようになっている。ホストヘッダーを限定するルールが入ることに注意が必要。

f:id:sotoiwa:20201030031603p:plain

curlでALBのドメイン名でアクセスしてみる。ホストヘッダーを指定しないとリスナールールによって404となる。

$ curl -H "Host: test2.sotoiwa.dev" k8s-default-echoserv-91c6428909-23022959.ap-northeast-1.elb.amazonaws.com
CLIENT VALUES:
client_address=192.168.12.31
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://test2.sotoiwa.dev:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=test2.sotoiwa.dev
user-agent=curl/7.54.0
x-amzn-trace-id=Root=1-5f9b0580-28ae7d2723400d580d0e7a61
x-forwarded-for=27.0.3.145
x-forwarded-port=80
x-forwarded-proto=http
BODY:
-no body in request-

curlで登録したドメイン名でアクセスしてみる。

$ curl -H "Host: test2.sotoiwa.dev" test2.sotoiwa.dev
CLIENT VALUES:
client_address=192.168.12.31
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://test2.sotoiwa.dev:8080/

SERVER VALUES:
server_version=nginx: 1.10.0 - lua: 10001

HEADERS RECEIVED:
accept=*/*
host=test2.sotoiwa.dev
user-agent=curl/7.54.0
x-amzn-trace-id=Root=1-5f9b05b3-5a3242ae4294f5327c88c281
x-forwarded-for=27.0.3.145
x-forwarded-port=80
x-forwarded-proto=http
BODY:
-no body in request-