NGINX Full Version

在 Kubernetes 中实现自助 DNS 和证书管理

应用开发的最终目标无疑是将应用暴露到互联网上。对于开发人员来说,Kubernetes 提供了 Ingress controller(Ingress 控制器)作为将请求路由到应用的机制,在一定程度上简化了这个过程。但并非一切都支持自助服务(尽管您可能希望如此):您仍然需要使用域名系统 (DNS) 中的记录,将应用的域名映射到 Ingress controller 的 IP 地址,并且仍然需要使用 TLS 证书来保护 HTTPS 连接。在大多数组织中,您自己并不拥有 DNS 或 TLS,必须与拥有 DNS 或 TLS 的一个或多个工作组进行协调。

对运维人员来说,事情未必变得更加简单。大多数组织很少需要更新 DNS 记录,因此对应的流程(包括业务规则和技术步骤)往往比较模糊或干脆没有。这意味着,当您需要添加 DNS 记录时,您首先需要查找文档,询问同事,或者(在最坏的情况下)自己想办法。您还需要确保遵守所有安全规则,并确保为防火墙正确标记 Ingress。

幸运的是,有一种方法可以让开发人员和运维人员的工作变得更轻松。在这篇文章中,我们演示了运维人员可如何配置 Kubernetes 部署,以便开发人员能够在 Kubernetes 环境中自助更新 DNS 记录和生成 TLS 证书。通过提前构建基础架构,您可以确保满足所有必要的业务要求和技术要求。

 

概述和先决条件

借助这款解决方案,如果开发人员需要将应用暴露到互联网上,只需按照提供的模板创建一个 Ingress controller 即可,提供的模板中包含一个位于 Kubernetes 安装所管理的域中的全限定域名 (FQDN)。Kubernetes 使用该模板为 Ingress controller 分配一个 IP 地址,创建 DNS A 记录将 FQDN 映射到 IP 地址,为 FQDN 生成 TLS 证书并将其添加到 Ingress controller。清理也同样简单:当 Ingress 被删除时,DNS 记录即被清理。

该解决方案利用了以下技术(安装和配置说明如下):

在配置该解决方案之前,您需要:

我们还假设您对 Kubernetes 有基本的了解(如何应用清单,使用 Helm 图表,以及使用 kubectl 命令来查看输出和排除故障)。了解 Let’s Encrypt 的基本概念会有所帮助,但不是必须的;要想了解概况,请查看我们的博客。您也不需要了解 cert-manager 的工作原理,但如果您对它(以及证书)如何与 NGINX Ingress Controller 协同工作感兴趣,请参阅我最近的帖子《在 Kubernetes 环境中实现证书管理自动化》

我们已经在 MacOS 和 Linux 上测试了该解决方案。虽然我们还没有在 Windows Subsystem for Linux 版本 2 (WSL2) 上进行测试,但预计不会有任何问题。

注意: 该解决方案只是一个示例概念验证,不得用于生产环境。它并未包含所有运维和安全的最佳实践。关于这些主题的信息,请参见 cert-managerExternalDNS 文档。

 

部署解决方案

按照这些部分中的步骤来部署解决方案:

下载软件

  1. 下载您的 Cloudflare API 令牌
  2. 复制 NGINX Ingress Controller 仓库:

    $ git clone https://github.com/nginxinc/kubernetes-ingress.git
    Cloning into 'kubernetes-ingress'...
    remote: Enumerating objects: 45176, done.
    remote: Counting objects: 100% (373/373), done.
    remote: Compressing objects: 100% (274/274), done.
    remote: Total 45176 (delta 173), reused 219 (delta 79), pack-reused 44803
    Receiving objects: 100% (45176/45176), 60.45 MiB | 26.81 MiB/s, done.
    Resolving deltas: 100% (26592/26592), done.
  3. 验证您是否可以连接到 Kubernetes 集群。

    $ kubectl cluster-info
    Kubernetes control plane is running at https://ba35bacf-b072-4600-9a04-e04...6a3d.us-west-2.linodelke.net:443
    KubeDNS is running at https://ba35bacf-b072-4600-9a04-e04...6a3d.us-west-2.linodelke.net:443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
     
    To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

部署 NGINX Ingress Controller

  1. 使用 Helm,部署 NGINX Ingress Controller。请注意,我们正在添加三个非标准的配置选项

    • controller.enableCustomResources——指示 Helm 安装用于创建 NGINX VirtualServer 和 VirtualServerRoute 自定义资源的自定义资源定义 (CRD)。
    • controller.enableCertManager——将 NGINX Ingress Controller 配置为与 cert-manager 组件通信。
    • controller.enableExternalDNS——将 Ingress controller 配置为与 ExternalDNS 组件通信。
    $ helm install nginx-kic nginx-stable/nginx-ingress --namespace nginx-ingress  --set controller.enableCustomResources=true --create-namespace  --set controller.enableCertManager=true --set controller.enableExternalDNS=true
    NAME: nginx-kic
    LAST DEPLOYED: Day Mon  DD hh:mm:ss YYYY
    NAMESPACE: nginx-ingress
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    NOTES:
    The NGINX Ingress Controller has been installed.
  2. 验证 NGINX Ingress Controller 是否正在运行,并注意 EXTERNAL-IP 字段的值——NGINX Ingress Controller 的 IP 地址(此处为 www.xxx.yyy.zzz)。为方便阅读,输出结果分成了两行。

    $ kubectl get services --namespace nginx-ingress
    NAME                      TYPE           CLUSTER-IP      ...
    nginx-kic-nginx-ingress   LoadBalancer   10.128.152.88   ... 
    
       ... EXTERNAL-IP       PORT(S)                      AGE
       ... www.xxx.yyy.zzz   80:32457/TCP,443:31971/TCP   3h8m

部署 cert-manager

在该解决方案中,cert-manager 在获取 TLS 证书时,使用 DNS-01 挑战类型,这需要在创建 ClusterIssuer 资源时提供 Cloudflare API 令牌。在该解决方案中,API 令牌作为 Kubernetes Secret 提供。

  1. 使用 Helm 部署 cert-manager

    $ helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.9.1  --set installCRDs=true
    NAME: cert-manager
    LAST DEPLOYED: Day Mon  DD hh:mm:ss YYYY
    NAMESPACE: cert-manager
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    NOTES:
    cert-manager v1.9.1 has been deployed successfully!
  2. 将 Cloudflare API 令牌部署为 Kubernetes Secret,并用它替换 <your-API-token>

    $ kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: Cloudflare-api-token-secret
      namespace: cert-manager
    type: Opaque
    stringData:
      api-token: "<your-API-token>"
    EOF
    secret/Cloudflare-api-token-secret created
  3. 创建一个 ClusterIssuer 对象,指定 Cloudflare-api-token-secret(已在上一步中定义)作为检索令牌的位置。您也可以根据需要将 metadata.name 字段中的 example-issuer(以及 spec.acme.privateKeySecretRef.name 字段中的 example-issuer-account-key)替换为其他名称。

    $ kubectl apply -f - <<EOF
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: example-issuer
      namespace: cert-manager
    spec:
      acme:
        email: example@example.com
        server: https://acme-v02.api.letsencrypt.org/directory
        privateKeySecretRef:
          name: example-issuer-account-key
        solvers:
          - dns01:
              Cloudflare:
                apiTokenSecretRef:
                  name: Cloudflare-api-token-secret
                  key: api-token
    EOF
    clusterissuer.cert-manager.io/example-issuer created
  4. 验证 ClusterIssuer 是否已经部署完毕并准备就绪(READY 字段的值为 True)。

    $ kubectl get clusterissuer
    NAME             READY   AGE
    example-issuer   True    3h9m

部署 ExternalDNS

同 cert-manager 一样,ExternalDNS 项目需要使用 Cloudflare API 令牌来管理 DNS。 两个项目可以使用相同令牌,但并不是必须的。

  1. 为 NGINX Ingress Controller 创建 ExternalDNS CRD,以实现项目间的集成。

    $ kubectl create -f ./kubernetes-ingress/deployments/common/crds/externaldns.nginx.org_dnsendpoints.yaml
    customresourcedefinition.apiextensions.k8s.io/dnsendpoints.externaldns.nginx.org created
  2. 创建外部 DNS 服务 (external-dns)。由于该清单非常长,在此我们将其分成两部分。第一部分是配置账户、角色和权限。

    • 创建一个名为 external-dns 的 ServiceAccount 对象,以管理与 DNS 管理相关的所有写入和更新操作。
    • 创建一个 ClusterRole 对象(也称 external-dns)以定义所需的权限。
    • 将 ClusterRole 绑定到 ServiceAccount。
    $ kubectl apply -f - <<EOF
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: external-dns
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    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: ["externaldns.nginx.org"]
      resources: ["dnsendpoints"]
      verbs: ["get","watch","list"]
    - apiGroups: ["externaldns.nginx.org"]
      resources: ["dnsendpoints/status"]
      verbs: ["update"]
    - apiGroups: [""]
      resources: ["nodes"]
      verbs: ["list","watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    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
    EOF
    serviceaccount/external-dns created
    clusterrole.rbac.authorization.k8s.io/external-dns created
    clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created

    清单的第二部分创建 ExternalDNS 部署。

    • 创建域过滤器,以限制 ExternalDNS 在管理域时可能造成的影响。 例如,您可以指定预发布环境的域名,以防止对生产环境的更改。 在这个示例中,我们把 domain-filter 设置为 example.com
    • CF_API_TOKEN 环境变量设置为您的 Cloudflare API 令牌。对于 <your-API-token>,可以用实际的令牌或包含令牌的 Secret 进行替换。在后一种情况下,您还需要使用环境变量将 Secret 投射到容器中
    • FREE_TIER 环境变量设置为 "true"(适合 Cloudflare 付费用户)。
    $  kubectl apply -f - <<EOF
     
    ---
    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.12.0
            args:
            - --source=service
            - --source=ingress
            - --source=crd
            - --crd-source-apiversion=externaldns.nginx.org/v1
            - --crd-source-kind=DNSEndpoint
            - --domain-filter=example.com
            - --provider=Cloudflare
            env:
              - name: CF_API_TOKEN
                value: "<your-API-token>"
              - name: FREE_TIER
                value: "true"
    EOF
    serviceaccount/external-dns created
    clusterrole.rbac.authorization.k8s.io/external-dns created
    clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created
    deployment.apps/external-dns created

部署示例应用

使用名为 Cafe 的标准 NGINX Ingress Controller 示例应用进行测试。

  1. 部署 Cafe 应用。

    $ kubectl apply -f ./kubernetes-ingress/examples/ingress-resources/complete-example/cafe.yaml
    deployment.apps/coffee created
    service/coffee-svc created
    deployment.apps/tea created
    service/tea-svc created
  2. 为 Cafe 应用部署 NGINX Ingress Controller。注意以下设置:

    • kind: VirtualServer – 我们使用的是 NGINX VirtualServer 自定义资源,而不是标准的 Kubernetes Ingress 资源。
    • spec.host – 将 cafe.example.com 替换为待部署主机的名称。该主机必须在 ExternalDNS 管理的域内。
    • spec.tls.cert-manager.cluster-issuer – 如果您一直用的是本文中指定的值,则为 example-issuer。如有必要,将其替换为您在“部署 cert-manager” 的第 3 步中选用的名称。
    • spec.externalDNS.enable – 值为 true 时,ExternalDNS 将创建一条 DNS A 记录。

    注意,这一步完成的时间在很大程度上取决于 DNS 提供商,因为 Kubernetes 正在与提供商的 DNS API 进行交互。

    $ kubectl apply -f - <<EOF
    apiVersion: k8s.nginx.org/v1
    kind: VirtualServer
    metadata:
      name: cafe
    spec:
      host: cafe.example.com
      tls:
        secret: cafe-secret
        cert-manager:
          cluster-issuer: example-issuer
      externalDNS:
        enable: true
      upstreams:
      - name: tea
        service: tea-svc
        port: 80
      - name: coffee
        service: coffee-svc
        port: 80
      routes:
      - path: /tea
        action:
          pass: tea
      - path: /coffee
        action:
          pass: coffee
    EOF
    virtualserver.k8s.nginx.org/cafe created

验证解决方案

  1. 验证 DNS A 记录 – 即在 ANSWER SECTION 块中,FQDN(此处为 cafe.example.com)被映射到正确的 IP 地址 (www.xxx.yyy.zzz)。

    $ dig cafe.example.com
     
    ; <<>> DiG 9.10.6 <<>> cafe.example.com
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22633
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
     
    ;; OPT PSEUDOSECTION:
    ; EDNS: version: 0, flags:; udp: 4096
    ;; QUESTION SECTION:
    ;cafe.example.com.		IN	A
     
    ;; ANSWER SECTION:
    cafe.example.com.	279	IN	A	www.xxx.yyy.zzz
     
    ;; Query time: 1 msec
    ;; SERVER: 2607:fb91:119b:4ac4:2e0:xxxx:fe1e:1359#53(2607:fb91:119b:4ac4:2e0:xxxx:fe1e:1359)
    ;; WHEN: Day Mon  DD hh:mm:ss TZ YYYY
    ;; MSG SIZE  rcvd: 67
  2. 检查证书是否有效(READY 字段的值为 True)。

    $ kubectl get certificates
    NAME          READY   SECRET        AGE
    cafe-secret   True    cafe-secret   8m51s
  3. 验证您能否访问应用。

    $ curl https://cafe.example.com/coffee
    Server address: 10.2.2.4:8080
    Server name: coffee-7c86d7d67c-lsfs6
    Date: DD/Mon/YYYY:hh:mm:ss +TZ-offset
    URI: /coffee
    Request ID: 91077575f19e6e735a91b9d06e9684cd
    $ curl https://cafe.example.com/tea
    Server address: 10.2.2.5:8080
    Server name: tea-5c457db9-ztpns
    Date: DD/Mon/YYYY:hh:mm:ss +TZ-offset
    URI: /tea
    Request ID: 2164c245a495d22c11e900aa0103b00f

 

当开发人员部署 NGINX Ingress controller 时后台会发生什么?

在该解决方案部署时,后台会进行很多操作。该图显示了当开发人员使用 NGINX VirtualServer 自定义资源部署 NGINX Ingress Controller 时后台进行的操作。请注意,一些操作细节已被省略。

  1. 开发人员使用 NGINX CRD 部署 VirtualServer 资源
  2. Kubernetes 使用 NGINX Ingress Controller 创建 VirtualServer
  3. NGINX Ingress Controller 调用 ExternalDNS 来创建 DNS A 记录
  4. ExternalDNS 在 DNS 中创建 A 记录
  5. NGINX Ingress Controller 调用 cert-manager 来请求 TLS 证书
  6. cert-manager 添加 DNS 记录,以用于 DNS-01 挑战
  7. cert-manager 联系 Let’s Encrypt 以完成挑战
  8. Let’s Encrypt 根据 DNS 验证挑战
  9. Let’s Encrypt 颁发 TLS 证书
  10. cert-manager 向 NGINX Ingress Controller 提供 TLS 证书
  11. NGINX Ingress Controller 将受 TLS 保护的外部请求路由到应用 pod

 

故障排除

考虑到 Kubernetes 的复杂性以及我们所使用的组件,很难提供一套全面的故障排除指南。也就是说,只能提供一些基本建议来帮助您确定问题。

如果仍有问题,请通过添加“小N助手(微信号:nginxoss)”加入到我们的官方微信群,与我们进行直接交流。

如欲体验基于 NGINX Plus 的 NGINX Ingress Controller,请立即下载 30 天免费试用版,或者联系我们讨论您的用例