0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

terraformAdvent Calendar 2022

Day 12

[EKS] [Terraform] external-dnsでマニフェストからレコード登録をする

Posted at

What's This

external-dnsを使用すればマニフェストに登録したいレコードを書くだけで、
自動でレコードとIngressのURLを紐づけてくれ、Route53にもレコードを登録してくれます。

今回はterraformを使用してAWS側で設定する部分を実現しました。
先人が残してくれた記事を大いに参考にしており、n番煎じかという内容かもしれませんが、先人たちと異なる実装部分もあるかと思い、まとめました。

構成

Serviceにexternal-dnsを使用している例も多いかと思いますが、今回の例ではIngressに対して使用しております。

Terraform側で用意するもの

kubectl_manifest リソースを使用して、TF内でk8sのリソースを定義しました。
WAFやSecurity Group、ACMなどを使うのであれば、それを変数として渡す方が組み立てが簡単になるかと思います。
k8sとTFの作業の境界がここだけ曖昧になるのが難点ですが…

eks.tf
resource "kubectl_manifest" "ingress_yaml" {
  yaml_body = <<-EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: default
  name: ${var.env}-ingress
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: instance
    alb.ingress.kubernetes.io/wafv2-acl-arn: ${data.terraform_remote_state.waf_acmのoutputs} // WAF-ARN
    alb.ingress.kubernetes.io/security-groups: ${aws_security_group.ingressのsg.id} // SG-ARN
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: ACMのARN
    alb.ingress.kubernetes.io/ssl-redirect: "443"
    external-dns.alpha.kubernetes.io/hostname: "${var.env}-domain name" // 使用したいレコードの名前
spec:
  rules:
    - host: ${var.env}-domain name // 使用したいレコードの名前
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: service
              port:
                number: 80

    EOF
}

// remote_stateを使っている場合、以下の記載も必要

data "terraform_remote_state" "waf_acm" {
  backend = "s3"
  config = {
    bucket = "バケット名"
    key    = "tfstateの名前"
    region = "リージョン"
  }
}
.tfvars

なお、別途tfvarsで以下のように変数を定義しています。
これで環境ごとに名前付けをしてくれるので、上記で${var.env}で分けています。

env          = "環境名"
external_dns.tf
resource "kubernetes_service_account" "external_dns" {
  metadata {
    name      = "external-dns"
    namespace = "default"
    annotations = {
      "eks.amazonaws.com/role-arn" : "${aws_iam_role.external_dns_role.arn}"
    }
  }
}

resource "kubernetes_cluster_role" "external_dns" {
  metadata {
    name = "${var.env}-external-dns"
  }
  rule {
    api_groups = [""]
    resources  = ["services", "endpoints", "pods"]
    verbs      = ["get", "watch", "list"]
  }
  rule {
    api_groups = ["extensions", "networking.k8s.io"]
    resources  = ["ingresses"]
    verbs      = ["get", "watch", "list"]
  }
  rule {
    api_groups = [""]
    resources  = ["nodes"]
    verbs      = ["list", "watch"]
  }
}
resource "kubernetes_cluster_role_binding" "external_dns_viewer" {
  metadata {
    name = "${var.env}-external-dns-viewer"
  }
  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = kubernetes_cluster_role.external_dns.metadata[0].name
  }
  subject {
    kind      = "ServiceAccount"
    name      = kubernetes_service_account.external_dns.metadata[0].name
    namespace = "default"
  }
}

data "aws_iam_policy_document" "external_dns_assume_role_policy" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    effect  = "Allow"

    condition {
      test     = "StringEquals"
      variable = "${replace(module.cluster.oidc_provider, "https://", "")}:sub"
      values   = ["system:serviceaccount:default:external-dns"]
    }

    principals {
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${module.cluster.oidc_provider}"]
      type        = "Federated"
    }
  }
}

resource "aws_iam_role" "external_dns_role" {
  name               = "${var.env}-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
}

resource "aws_iam_policy" "external_dns_policy" {
  name   = "${var.env}-external-dns-policy"
  policy = file("external_dns.jsonのPATH")
}

IAM Policy 作成して、Roleにアタッチしています。PolicyのJsonは別ファイルで分けています。
Roleに環境名をつけて、それをサービスアカウントに紐づけています。

external_dns.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Kubernetes側で用意するもの

external_dns.yaml
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.10.2
        args:
        - --source=ingress
        - --domain-filter=ホストゾーンの名前 # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
        - --provider=aws
        - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
        - --aws-zone-type=public 
        - --registry=txt
        - --txt-owner-id=ホストゾーンのID
      securityContext:
        fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files

serviceAccountName:で、TFで作成したサービスアカウントの名前を記載しています。

適用の順序

今回の設計構成上、ちょっと面倒な感じですが一応書きます。

1.terraform applyする

2.external-dns.yamlをデプロイする

3.Ingressを削除し、もう一度デプロイする

4.external-dnsがこんなログを出していて、Route53上でレコードが登録されていたら成功

kubectl logs "external-dns Podの名前" -f

~中略~

time="hogehoge" level=info msg="Applying provider record filter for domains: [”ホストゾーンの名前"]"
time="hogehoge" level=info msg="Desired change: CREATE "使用したいレコードの名前" A [Id: /hostedzone/"ホストゾーンのID"]"
time="hogehoge" level=info msg="Desired change: CREATE "使用したいレコードの名前" TXT [Id: /hostedzone/"ホストゾーンのID"]"
time="hogehoge" level=info msg="2 record(s) in zone ”ホストゾーンの名前". [Id: /hostedzone/"ホストゾーンのID"] were successfully updated"

curl https://"使用したいレコードの名前

で期待通りの反応が返ってくるか確認

備考

遭遇したエラーと解決についても記載しておきます。

external-dns Podが正常に立ち上がらず、Restartを繰り返していました。

kubectl get pod | grep dns
external-dns-hogehoge                  0/1     CrashLoopBackOff   38         3h32m

kubectl logs external-dns-hogehoge -f

~中略~
time="hogehoge" level=fatal msg="failed to sync *v1.Ingress: context deadline exceeded"

以下参考にして、サービスアカウントとClusterRoleBindingが問題ありそうだなと考えました。

https://github.com/kubernetes-sigs/external-dns/issues/2407
https://stackoverflow.com/questions/71407994/kubernetes-error-context-deadline-exceeded

なので、両方が同じ名前空間にあることを修正、確認しました。

resource "kubernetes_service_account" "external_dns" {
  metadata {
    name      = "external-dns"
    namespace = "default" // ココ
    annotations = {
      "eks.amazonaws.com/role-arn" : "${aws_iam_role.external_dns_role.arn}"
    }
  }
}

resource "kubernetes_cluster_role_binding" "external_dns_viewer" {
  metadata {
    name = "${var.env}-external-dns-viewer"
  }
  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = kubernetes_cluster_role.external_dns.metadata[0].name
  }
  subject {
    kind      = "ServiceAccount"
    name      = kubernetes_service_account.external_dns.metadata[0].name
    namespace = "default" // ココ
  }
}

後は、--domain-filterを、ホストゾーンの名前ではなく、使用したいレコードの名前で記載していたので、修正しました。

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.10.2
        args:
        - --source=ingress
        - --domain-filter=ホストゾーンの名前 // ココ
        - --provider=aws
        - --policy=upsert-only 
        - --aws-zone-type=public 
        - --registry=txt
        - --txt-owner-id=ホストゾーンのID
      securityContext:
        fsGroup: 65534 

すると解決しました。

Reference

https://aws.amazon.com/jp/premiumsupport/knowledge-center/eks-set-up-externaldns/
https://github.com/kubernetes-sigs/external-dns
https://qiita.com/guile/items/d41399f579d1a62634c0
https://developer.mamezou-tech.com/containers/k8s/tutorial/ingress/external-dns/
https://tech.polyconseil.fr/external-dns-helm-terraform.html
https://febc-yamamoto.hatenablog.jp/entry/2018/12/28/172830
・社内有識者

参考にさせていただきました。ありがとうございます。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?