使用 [EKS] [Terraform] 和 external-dns 在配置文件中进行记录注册

这是什么?

只需要在配置文件中编写想要注册的记录,使用external-dns会自动将记录与Ingress的URL进行关联,并将记录注册到Route53。

我使用了Terraform来实现在AWS上进行配置的部分。我在很大程度上参考了先人留下的文章,可能是一种再创作的内容,但我也整理了一些与先人们不同的实现部分。

构成

我认为许多示例都使用了Service中的external-dns,但在本例中,我们使用了它来处理Ingress。

Terraform所需准备之物

使用kubectl_manifest资源在TF内定义了k8s资源。如果要使用WAF、安全组和ACM等,将其作为变量传递应该能简化构建过程。但是,这里唯一的困难是k8s和TF工作的边界变得有些模糊…

eks.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          = "環境名"
外部_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策略并将其附加到角色上。我将策略的JSON文件与其他文件分开。我为角色添加了环境名称,并将其与服务账号关联。

外部_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一侧准备的东西

外部_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

我們在這裡提供了在TF中創建的服務帳戶的名稱。

适用顺序: 应用的次序

基于本次设计的构造,感觉稍微有点麻烦,但还是写下来吧。

1. 进行 Terraform 应用

部署 external-dns.yaml。

3.删除 Ingress 并重新部署。

如果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一直無法正常啟動,一直重複執行重新啟動的動作。

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存在问题。

以下是对两个网址的原文的中文释义:

1. https://github.com/kubernetes-sigs/external-dns/issues/2407:
外部DNS GitHub问题页面的编号为2407。

2. https://stackoverflow.com/questions/71407994/kubernetes-error-context-deadline-exceeded:
Kubernetes错误报告中的错误代码为”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 

然后问题得以解决。

参考
以下内容的中文本地化表述

・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
・Internal expert on the subject.

我已经参考了您给我的意见。非常感谢。

bannerAds