NGINX.COM
Web Server Load Balancing with NGINX Plus

有效的 SSL/TLS 证书是现代应用环境的核心要求。但遗憾的是,在部署应用时,证书的更新管理很容易被忽略。证书的有效期有限且时长不等,DigiCert 证书的有效期在 13 个月左右Let’s Encrypt 证书的有效期为 90 天。为了确保安全访问,这些证书需要在到期前更新/重新颁发。由于大多数运维团队的工作任务繁重,证书更新有时会被搁置,因此在证书临近(或更糟糕)超过到期日时,手忙脚乱地管理证书的现象时常发生。

其实,情况大可不必如此。只需做好一些规划和准备,就可以简化并实现证书管理的自动化。在这里,我们将介绍一款解决方案,面向使用以下三种技术的 Kubernetes 环境:

通过本文,您将了解如何通过向端点提供自动更新的证书来简化证书管理。

 

Kubernetes 环境中的证书

在讨论技术细节之前,我们需要先定义一些术语。“TLS 证书”是指在我们的 Ingress controller(Ingress 控制器)上启用 HTTPS 连接所需的两个组件:

  • 证书
  • 私钥

证书和私钥都由 Let’s Encrypt 颁发。关于 TLS 证书如何工作的详细说明,请参阅 DigiCert 文章《TLS/SSL 证书如何工作》

在 Kubernetes 中,这两个组件被存储为 Secrets。Kubernetes 工作负载,例如 NGINX Ingress Controllercert-manager 可以写入和读取这些 Secret,也可以由有权访问 Kubernetes 安装的用户进行管理。

cert-manager 简介

cert-manager 项目是一个可以与 Kubernetes 和 OpenShift 协同工作的证书控制器。当部署在 Kubernetes 中时,cert-manager 将自动颁发 Ingress controller 所需的证书,并确保它们是有效和最新的。此外,它将跟踪证书的到期日,并按照配置的时间间隔尝试更新。虽然它与大量公共和私有证书颁发机构合作,但在这里我们将只介绍它与 Let’s Encrypt 的集成。

两种 challenge 类型

当使用 Let’s Encrypt 时,所有证书管理都可以自动进行。虽然这提供了极大的便利,但也带来了一个问题:这项服务如何确保您拥有完全限定域名 (FQDN)?

这个问题可使用 challenge 来解决,challenge 会要求您回答一个仅有权访问特定域名的 DNS 记录的人员才能发起的验证请求。challenge 采取以下两种形式中的一种:

  1. HTTP-01:可通过为要将证书颁发给的 FQDN 创建 DNS 记录来解决该 challenge。例如,如果您的服务器位于 IP www.xxx.yyy.zzz,并且您的 FQDN 是 cert.example.com,那么该 challenge 机制将在位于 www.xxx.yyy.zzz 的服务器上暴露一个令牌,Let’s Encrypt 服务器将试图通过 cert.example.com 访问该令牌。如果成功,challenge 通过并颁发证书。

     

    HTTP-01 是生成证书的最简单方法,因为它不需要直接访问 DNS 提供商。这种类型的 challenge 总是通过 HTTP 80 支持的DNS 端口 进行。注意,当使用 HTTP-01 challenge 时,cert-manager 将利用 Ingress controller 来提供 challenge 令牌。

  1. DNS-01:这个 challenge 使用令牌创建 DNS TXT 记录,然后由颁发机构进行验证。如果令牌被认可,则证明您拥有对该域名的所有权,并且可以立即为其记录颁发证书。与 HTTP-01 challenge 不同的是,使用 DNS-01 challenge 时,FQDN 不需要解析服务器的 IP 地址(甚至不存在)。此外,当 80 端口被屏蔽时,可以使用 DNS-01。不方便的是需要通过访问 cert-manager 安装的 API 令牌提供对 DNS 基础设施的访问权限。

Ingress Controllers

Ingress controller 是 Kubernetes 的一种 service,它被专门用于从集群外部引入流量,将流量负载均衡到内部 Pods(一组单个或多个容器),并管理 egress 出向流量。此外,Ingress controller 通过 Kubernetes API 进行控制,当添加、移除 Pods 或 Pods 出现故障时,它将监控并更新负载均衡配置。

有关 Ingress controller 的更多信息,请参阅以下博客:

在下面的示例中,我们将使用由 F5 NGINX 开发和维护的 NGINX Ingress Controller。

 

证书管理 示例

这些示例假定您拥有一个可供测试使用的有效 Kubernetes 安装,并且该安装可以分配一个外部 IP 地址(Kubernetes LoadBalancer 对象)。此外,它还假定您可以在 80 和443端口(如果使用 HTTP-01 challenge)或只在 443 端口(如果使用 DNS-01 challenge)上接收流量。这些示例都使用 Mac OS X 进行演示,但也可以使用 Linux 或 WSL。

您还需要一个 DNS 提供商以及允许调整 A 记录的 FQDN。如果您使用 HTTP-01 challenge,则只需要添加一个 A 记录(或让人代您添加一个)。如果您使用 DNS-01 challenge,则需要能够对支持的 DNS 提供商支持的 webhook 提供商进行 API 访问。

部署 NGINX Ingress Controller

最简单的方法是通过 Helm 进行部署。这种部署允许您同时使用 Kubernetes Ingress 和 NGINX Virtual Server CRD。

  1. 添加 NGINX 仓库。
  2. $ helm repo add nginx-stable https://helm.nginx.com/stable
      "nginx-stable" has been added to your repositories 
  3. 更新仓库。
  4. $ helm repo update
      Hang tight while we grab the latest from your chart repositories...
      ...Successfully got an update from the "nginx-stable" chart repository
      Update Complete. ⎈Happy Helming!⎈ 
  5. 部署 Ingress controller。
  6. $ helm install nginx-kic nginx-stable/nginx-ingress \
      --namespace nginx-ingress  --set controller.enableCustomResources=true \ 
      --create-namespace  --set controller.enableCertManager=true 
      NAME: nginx-kic
      LAST DEPLOYED: Thu Sep  1 15:58:15 2022
      NAMESPACE: nginx-ingress
      STATUS: deployed
      REVISION: 1
      TEST SUITE: None
      NOTES:
      The NGINX Ingress Controller has been installed. 
  7. 检查部署,并检索面向 Ingress controller 的 egress 的 IP 地址。注意,如果没有有效的 IP 地址,则无法继续。
  8. $ kubectl get deployments --namespace nginx-ingress
      NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
      nginx-kic-nginx-ingress   1/1     1            1           23s
      $ kubectl get services --namespace nginx-ingress
      NAME                      TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE
      nginx-kic-nginx-ingress   LoadBalancer   10.128.60.190   www.xxx.yyy.zzz   80:31526/TCP,443:32058/TCP   30s 

添加 DNS A 记录

具体流程将视您的 DNS 提供商而定。这个 DNS 名称需要能够从 Let’s Encrypt 服务器进行解析,在此之前,您可能需要等待记录传播。有关这方面的更多信息,请参阅 SiteGround 文章《什么是 DNS 传播,为什么需要这么长时间?》

如果能够解析所选择的 FQDN,则可以进入下一步。

$ host cert.example.com
  cert.example.com has address www.xxx.yyy.zzz

部署 cert-manager

下一步是部署最新版本的 cert-manager。同样,我们将使用 Helm 进行部署。

  1. 添加 Helm 仓库。
  2. $ helm repo add jetstack https://charts.jetstack.io
      "jetstack" has been added to your repositories 
  3. 更新仓库。
  4. $ helm repo update
      Hang tight while we grab the latest from your chart repositories...
      ...Successfully got an update from the "nginx-stable" chart repository
      ...Successfully got an update from the "jetstack" chart repository
      Update Complete. ⎈Happy Helming!⎈ 
  5. 部署 cert-manager。
  6. $ helm install cert-manager jetstack/cert-manager \
      --namespace cert-manager --create-namespace \
      --version v1.9.1  --set installCRDs=true 
      NAME: cert-manager
      LAST DEPLOYED: Thu Sep  1 16:01:52 2022 
      NAMESPACE: cert-manager
      STATUS: deployed
      REVISION: 1 
      TEST SUITE: None
      NOTES:
      cert-manager v1.9.1 has been deployed successfully!
    In order to begin issuing certificates, you will need to set up a ClusterIssuer or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).
    More information on the different types of issuers and how to configure them can be found in our documentation:
    https://cert-manager.io/docs/configuration/
    For information on how to configure cert-manager to automatically provision Certificates for Ingress resources, take a look at the `ingress-shim` documentation:
    https://cert-manager.io/docs/usage/ingress/
  7. 验证部署。
  8. $ kubectl get deployments --namespace cert-manager
      NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
      cert-manager              1/1     1            1           4m30s
      cert-manager-cainjector   1/1     1            1           4m30s
      cert-manager-webhook      1/1     1            1           4m30s 

部署 NGINX Cafe 示例

我们将使用 NGINX Cafe 示例来提供后端部署和 services。这个示例在 NGINX 提供的文档中十分常见。在此过程中,我们不会部署 Ingress。

  1. 克隆 NGINX Ingress GitHub 项目。
  2. $ git clone https://github.com/nginxinc/kubernetes-ingress.git
      Cloning into 'kubernetes-ingress'...
      remote: Enumerating objects: 44979, done.
      remote: Counting objects: 100% (172/172), done.
      remote: Compressing objects: 100% (108/108), done.
      remote: Total 44979 (delta 87), reused 120 (delta 63), pack-reused 44807
      Receiving objects: 100% (44979/44979), 60.27 MiB | 27.33 MiB/s, done.
      Resolving deltas: 100% (26508/26508), done. 
  3. 进入到示例目录。这个目录中包含几个示例,演示了 Ingress controller 的各种配置。我们使用的是 complete-example 目录下提供的示例。
  4. $ cd ./kubernetes-ingress/examples/ingress-resources/complete-example 
  5. 部署 NGINX Cafe 示例。
  6. $ kubectl apply -f ./cafe.yaml
      deployment.apps/coffee created
      service/coffee-svc created
      deployment.apps/tea created
      service/tea-svc created
  7. 使用 kubectl get 命令验证部署和 services。您需要确保 Pods 显示为 READY,而 service 显示为 running。下面的示例为您提供了一个代表性样本。注意,kubernetes 服务是系统服务,与 NGINX Cafe 示例在同一个命名空间(默认)中运行。
  8. $ kubectl get deployments,services  --namespace default
      NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
      deployment.apps/coffee   2/2     2            2           69s
      deployment.apps/tea      3/3     3            3           68s
    NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
      service/coffee-svc   ClusterIP   10.128.154.225   <none>        80/TCP    68s
      service/kubernetes   ClusterIP   10.128.0.1       <none>        443/TCP   29m
    	service/tea-svc      ClusterIP   10.128.96.145    <none>        80/TCP    68s 

部署 ClusterIssuer

在 cert-manager 中,ClusterIssuer 可被用来颁发证书。这是一个集群范围对象,可以被任何命名空间引用,也可以被向指定证书颁发机构发起的任何证书请求所使用。在这个示例中,任何对 Let’s Encrypt 证书的请求都可以由这个 ClusterIssuer 来处理。

为选择的 challenge 类型部署 ClusterIssuer。虽然这不在本文的讨论范围内,但简单提一下,您可以通过高级配置选项在 ClusterIssuer 中指定多个解析器(根据选择器字段选择)。

ACME challenge 基础知识

可使用自动证书管理环境 (ACME) 协议来确定您是否拥有某个域名,进而确定能否被颁发 Let’s Encrypt 证书。对于这个 challenge,需要传递以下参数:

  • metadata.name:ClusterIssuer 名称,在 Kubernetes 安装中需要是唯一的。这个名称将在后面的证书颁发示例中用到。
  • spec.acme.email:这是您为生成证书而使用 Let’s Encrypt 注册的电子邮件地址。这应该是您的电子邮件。
  • spec.acme.privateKeySecretRef:这是您将用来存储私钥的 Kubernetes secret 的名称。
  • spec.acme.solvers:这应保持不变——它指出了您所使用的 challenge 类型(或者 ACME 所说的解析程序)(HTTP-01 或 DNS-01),以及它应该应用于哪种 Ingress 类型(在这种情况下,将是 nginx)。

使用 HTTP-01

这个示例显示了如何设置 ClusterIssuer 以使用 HTTP-01 challenge 来证明域名所有权和接收证书。

  1. 创建 ClusterIssuer,使用 HTTP-01 challenge。
  2. $ cat << EOF | kubectl apply -f 
      apiVersion: cert-manager.io/v1
      kind: ClusterIssuer
      metadata:
        name: prod-issuer
      spec:
        acme:
          email: example@example.com
          server: https://acme-v02.api.letsencrypt.org/directory
          privateKeySecretRef:
            name: prod-issuer-account-key
          solvers:
          - http01:
             ingress:
               class: nginx
      EOF
      clusterissuer.cert-manager.io/prod-issuer created 
  3. 验证该 ClusterIssuer(应该显示为 Ready)。
  4. $ kubectl get clusterissuer
      NAME          READY   AGE
    	prod-issuer   True    34s 

使用 DNS-01

这个示例显示了如何设置 ClusterIssuer 以使用 DNS-01 challenge 来验证域名所有权。根据具体的 DNS 提供商,您可能需要使用 Kubernetes Secret 来存储令牌。这个示例使用的是 Cloudflare。注意命名空间的使用。部署到 cert-manager 命名空间中的 cert-manager 应用需要访问 Secret。

在这个示例中,您需要一个 Cloudflare API 令牌,您可以从账户中创建该令牌。这将需要放在下面的 <API Token> 行中。如果您使用的不是 Cloudflare,则需要遵循提供商的文档

  1. 为 API 令牌创建 Secret。
  2. $ cat << EOF | kubectl apply -f 
      apiVersion: v1
      kind: Secret
      metadata:
        name: cloudflare-api-token-secret
        namespace: cert-manager
      type: Opaque
      stringData:
        api-token: <API Token> 
      EOF 
  3. 创建颁发机构,使用 DNS-01 challenge。
  4. $ cat << EOF | kubectl apply -f 
      apiVersion: cert-manager.io/v1
      kind: ClusterIssuer
      metadata:
        name: prod-issuer
      spec:
        acme:
          email: example@example.com
          server: https://acme-v02.api.letsencrypt.org/directory
          privateKeySecretRef:
            name: prod-issuer-account-key
          solvers:
            - dns01:
                cloudflare:
                  apiTokenSecretRef:
                    name: cloudflare-api-token-secret
                    key: api-token
      EOF 
  5. 验证颁发机构(它应显示为 ready)。
  6. $ kubectl get clusterissuer
      NAME          READY   AGE
    	prod-issuer   True    31m 

部署 Ingress

完成上述步骤后,即可为应用部署 Ingress 资源。这将把流量路由到我们先前部署的 NGINX Cafe 应用中。

使用 Kubernetes Ingress

如果您使用标准的 Kubernetes Ingress 资源,则要使用以下部署 YAML 来配置 Ingress 并请求证书。

apiVersion: networking.k8s.io/v1 
  kind: Ingress 
  metadata: 
    name: cafe-ingress 
    annotations: 
      cert-manager.io/cluster-issuer: prod-issuer 
      acme.cert-manager.io/http01-edit-in-place: "true" 
  spec: 
    ingressClassName: nginx 
    tls: 
    - hosts: 
      - cert.example.com 
      secretName: cafe-secret 
    rules: 
    - host: cert.example.com 
      http: 
        paths: 
        - path: /tea 
          pathType: Prefix 
          backend: 
            service: 
              name: tea-svc 
              port: 
                number: 80 
        - path: /coffee 
          pathType: Prefix 
          backend: 
            service: 
              name: coffee-svc 
              port: 
number: 80 

需要检查清单的一些关键部分:

  • 被调用的 API 是标准 Kubernetes Ingress。
  • 该配置的一个关键部分位于 metadata.annotations 下,在这里我们可以把 acme.cert-manager.io/http01-edit-in-place 设置为 “true”。这个值是必填项,可以调整 challenge 的解决方式。如欲了解更多信息,请参阅支持的注释文档。这也可以通过使用 master/minion setup 来处理。
  • spec.ingressClassName 是指我们已安装并将使用的 NGINX Ingress Controller。
  • spec.tls.secret Kubernetes Secret 资源存储 Let’s Encrypt 颁发证书时所返回的证书密钥。
  • 我们的主机名 cert.example.com 是为 spec.tls.hostsspec.rules.host 指定的。我们的 ClusterIssuer 就为这个主机名颁发证书。
  • spec.rules.http 部分定义了路径以及满足这些路径上的请求的后端 Services。例如,到 /tea 的流量将被引导到 tea-svc 上的 80 端口。
  1. 根据您的安装修改上述清单。至少需要更改 spec.rules.hostspec.tls.hosts 的值,但建议您审查配置中的所有参数。
  2. 应用清单。
  3. $  kubectl apply -f ./cafe-virtual-server.yaml
      virtualserver.k8s.nginx.org/cafe created 
  4. 等待证书颁发,READY 字段的值会变为 “True”。
  5. $ kubectl get certificates
      NAME                                      READY   SECRET        AGE
      certificate.cert-manager.io/cafe-secret   True    cafe-secret   37m 

使用 NGINX 虚拟服务器/虚拟路由

如果您使用 NGINX CRD,则需要使用以下部署 YAML 来配置 Ingress。


  apiVersion: k8s.nginx.org/v1 
  kind: VirtualServer 
  metadata: 
    name: cafe 
  spec: 
    host: cert.example.com 
    tls: 
      secret: cafe-secret 
      cert-manager: 
        cluster-issuer: prod-issuer 
    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

需要再次检查的一些关键部分。

  • 被调用的 API 是面向 VirtualServer 资源的 NGINX 特定的 k8s.nginx.org/v1。
  • spec.tls.secret Kubernetes Secret 资源存储 Let’s Encrypt 颁发证书时所返回的证书密钥。
  • 我们的主机名 cert.example.com 是为 spec.host 指定的。我们的 ClusterIssuer 就为这个主机名颁发证书。
  • spec.upstreams 的值指向我们的后端服务,包括端口。
  • spec.routes 定义了路由以及路由匹配时采取的操作。
  1. 根据您的安装修改上述清单。至少需要更改 spec.host 的值,但建议您审查配置中的所有参数。
  2. 应用清单。
  3. $  kubectl apply -f ./cafe-virtual-server.yaml
      virtualserver.k8s.nginx.org/cafe created
  4. 等待证书颁发。您应该看到状态为 “Valid”。
  5. $ kubectl get VirtualServers
      NAME   STATE   HOST                    IP             PORTS      AGE
      cafe   Valid   cert.example.com   www.xxx.yyy.zzz   [80,443]   51m 

查看证书

您可以通过 Kubernetes API 查看证书。这将显示有关该证书的细节,包括其大小和关联私钥。

$ kubectl describe secret cafe-secret
  Name:         cafe-secret
  Namespace:    default
  Labels:       <none>
  Annotations:  cert-manager.io/alt-names: cert.example.com
                cert-manager.io/certificate-name: cafe-secret
                cert-manager.io/common-name: cert.example.com
                cert-manager.io/ip-sans:
                cert-manager.io/issuer-group:
                cert-manager.io/issuer-kind: ClusterIssuer
                cert-manager.io/issuer-name: prod-issuer
                cert-manager.io/uri-sans:Type:  kubernetes.io/tlsData
  ====
  tls.crt:  5607 bytes
  tls.key:  1675 bytes 

如要查看实际的证书和密钥,则可以运行以下命令。(注意:这揭露了 Kubernetes Secrets 的一个弱点。也就是说,它们可以被任何具有必要访问权限的人员读取)。

$ kubectl get secret cafe-secret -o yaml

测试 Ingress

测试证书。在这里您可以使用任何想用的方法。下面的示例使用了 cURL。如果成功,则会出现一个类似于前面所示的代码,其中包括服务器名称、服务器的内部地址、日期、选择的 URI(路由)(coffee 或 tea)以及请求 ID。如果失败,则会生成 HTTP 错误代码,最可能是 400 或 301。

$ curl https://cert.example.com/tea
  Server address: 10.2.0.6:8080
  Server name: tea-5c457db9-l4pvq
  Date: 02/Sep/2022:15:21:06 +0000
  URI: /tea
  Request ID: d736db9f696423c6212ffc70cd7ebecf
  $ curl https://cert.example.com/coffee
  Server address: 10.2.2.6:8080
  Server name: coffee-7c86d7d67c-kjddk
  Date: 02/Sep/2022:15:21:10 +0000
  URI: /coffee
Request ID: 4ea3aa1c87d2f1d80a706dde91f31d54 

 

证书更新

刚开始,我们就提到这种方法将消除管理证书更新的需要。然而,我们尚未解释如何做到这一点。为什么呢?因为这是 cert-manager 的一个核心内置部分。在这个自动化过程中,当 cert-manager 意识到证书不存在、已过期、即将在 15 天内过期时,或者当用户通过 CLI 请求新的证书时,它将自动请求新的证书。简单至极。

 

常见问题

关于 NGINX Plus?

如果您是 NGINX Plus 用户,那么唯一的区别就是要安装 NGINX Ingress Controller。请参阅 NGINX 文档中的安装 Helm 部分,了解如何修改上述 Helm 命令以完成安装。

我应该使用哪种 challenge 类型?

这主要取决于您的用例。

HTTP-01 challenge 方法要求80 端口对互联网开放,并且 DNS A 记录已根据 Ingress controller 的 IP 地址正确配置。除了创建 A 记录以外,这种方法不要求访问 DNS 提供商。

当无法将 80 端口暴露在互联网上时,可以使用 DNS-01 challenge 方法,它只要求 cert-manager 对 DNS 提供商具有 egress 访问权。但这种方法要求您能够访问 DNS 提供商的 API,所需的访问级别因具体提供商而异。

如何进行故障排除?

由于 Kubernetes 错综复杂,因此很难提供有针对性的故障排除信息。当您遇到问题时,请在 NGINX 社区的官方微信群中向我们提问(NGINX Plus 用户可以使用他们的普通支持选项)。

 

立即行动

立即申请 NGINX Ingress Controller 30 天免费试用(带有 NGINX App Protect WAF 和 DoS),并下载始终免费的 NGINX Service Mesh。

Hero image
Kubernetes:
从测试到生产

通过多种流量管理工具提升弹性、可视性和安全性

关于作者

Jason Schmidt

NGINX 解决方案架构师

关于 F5 NGINX

F5, Inc. 是备受欢迎的开源软件 NGINX 背后的商业公司。我们为现代应用的开发和交付提供一整套技术。我们的联合解决方案弥合了 NetOps 和 DevOps 之间的横沟,提供从代码到用户的多云应用服务。访问 nginx-cn.net 了解更多相关信息。