博客 | NGINX

在 Kubernetes 环境中自动化证书管理

Jason Schmidt 缩略图
杰森·施密特
2022 年 10 月 5 日发布

有效的 SSL/TLS 证书是现代应用环境的核心要求。 不幸的是,在部署应用时,管理证书(或证书)更新通常是事后才想到的。 证书的有效期有限, DigiCert 证书的有效期约为 13 个月, Let's Encrypt 证书的有效期为 90 天。 为了保持安全访问,这些证书需要在到期之前进行更新/重新颁发。 鉴于大多数运营团队的工作量巨大,证书更新有时会出现疏漏,导致证书接近(或更糟的是超过)到期日期时出现混乱。

没必要这样。 通过一些规划和准备,证书管理可以实现自动化和简化。 在这里,我们将研究使用三种技术的 Kubernetes 解决方案:

在这篇博客中,您将学习如何通过为您的端点提供唯一的、自动更新和更新的证书来简化证书管理。

Kubernetes 环境中的证书

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

  • 证书
  • 私钥

证书和私钥均由Let's Encrypt颁发。 有关 TLS 证书如何工作的完整说明,请参阅 DigiCert 的文章《TLS/SSL 证书如何工作》

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

cert-manager 简介

cert-manager项目是一个与 Kubernetes 和 OpenShift 协同工作的证书控制器。 当部署在 Kubernetes 中时,cert-manager 将自动颁发 Ingress 控制器所需的证书,并确保它们有效且最新。 此外,它还将跟踪证书的到期日期并尝试按照配置的时间间隔进行续订。 虽然它与众多公共和私人发行商合作,但我们将展示它与 Let's Encrypt 的集成。

证书管理器图

两种挑战类型

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

该问题可以通过质询来解决,它要求您回答只有有权访问特定域的 DNS 记录的人才能提供的验证请求。 挑战有两种形式:

  1. HTTP-01 : 您可以通过拥有颁发证书的 FQDN 的 DNS 记录来回答这个挑战。 例如,如果您的服务器 IP 为www.xxx.yyy.zzz ,且您的 FQDN 为 cert.example.com,则质询机制将在www.xxx.yyy.zzz的服务器上公开一个令牌,并且 Let's Encrypt 服务器将尝试通过 cert.example.com 访问它。 如果成功,则挑战通过,并颁发证书。

     

    HTTP-01 是生成证书的最简单方法,因为它不需要直接访问 DNS 提供商。 此类挑战始终通过端口 80 (HTTP) 进行。 请注意,当使用 HTTP-01 挑战时,cert-manager 将利用 Ingress 控制器来提供挑战令牌。

HTTP 01 图

  1. DNS-01 : 此质询会创建一个带有令牌的 DNS TXT 记录,然后由发行者进行验证。 如果令牌被识别,则表示您证明了该域的所有权,并且可以为其记录颁发证书。 与 HTTP-01 质询不同,当使用 DNS-01 质询时,FQDN 不需要解析为您服务器的 IP 地址(甚至不存在)。 此外,当端口 80 被阻止时可以使用 DNS-01。 这种易用性的缺陷在于需要通过 API 令牌向 cert-manager 安装提供对 DNS 基础设施的访问权限。

DNS 01 图

入口控制器

Ingress 控制器是 Kubernetes 的一项专门服务,它将流量从集群外部引入,将其负载均衡到内部Pod (一组或多个容器),并管理出口流量。 此外,Ingress 控制器通过 Kubernetes API 进行控制,并将在 Pod 添加、删除或失败时监视和更新负载均衡配置。

要了解有关 Ingress 控制器的更多信息,请阅读以下博客:

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

证书管理示例

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

您还需要一个可以调整 A 记录的 DNS 提供商和 FQDN。 如果您使用 HTTP-01 质询,则只需要添加 A 记录(或让别人为您添加一条)即可。 如果您使用 DNS-01 质询,则需要对受支持的 DNS 提供商受支持的 webhook 提供商进行 API 访问。

部署 NGINX Ingress 控制器

最简单的方法是通过Helm部署。 此部署允许您同时使用 Kubernetes Ingress 和 NGINX 虚拟服务器 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 控制器。
  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 控制器的出口 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。 再次,我们将使用 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. 部署证书管理器。
  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 示例来提供我们的后端部署和服务。 这是 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 控制器的各种配置。 我们正在使用 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 命令验证部署和服务。 您要确保 Pod 显示为READY ,并且 Services 显示为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 处理。

为您选择的挑战类型部署 ClusterIssuer。 虽然这超出了本文的范围,但有一些高级配置选项允许您在 ClusterIssuer 中指定多个解析器(根据选择器字段选择)。

ACME 挑战基础知识

自动证书管理环境 (ACME) 协议用于确定您是否拥有域名,因此可以颁发 Let’s Encrypt 证书。 对于此挑战,需要传递以下参数:

  • 元数据.名称: ClusterIssuer 名称,该名称在 Kubernetes 安装中必须是唯一的。 我们稍后颁发证书时将在示例中使用该名称。
  • spec.acme.email : 这是您向 Let's Encrypt 注册以生成证书的电子邮件地址。 这应该是您的电子邮件。
  • 规范.acme.privateKeySecretRef : 这是您将用于存储私钥的 Kubernetes 密钥的名称。
  • spec.acme.solvers : 这应该保持原样 - 它记录了您正在使用的挑战类型(或者,ACME 所称的求解器)(HTTP-01 或 DNS-01)以及它应该应用于哪个 Ingress 类,在本例中是 nginx。

使用 HTTP-01

此示例显示如何设置 ClusterIssuer 以使用 HTTP-01 质询来证明域所有权并接收证书。

  1. 使用 HTTP-01 创建 ClusterIssuer 以进行挑战。
  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(它应该显示为已准备就绪)。
  4. $ kubectl get clusterissuer  NAME          READY   AGE
    	prod-issuer   True    34s 

使用 DNS-01

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

对于此示例,您将需要一个Cloudflare API 令牌,您可以从您的帐户中创建它。 这需要放在下面的 行中。 如果您没有使用 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 创建发行者以进行挑战。
  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. 验证发行人(应显示为已准备就绪)。
  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”。 此值是必需的,用于调整挑战的服务方式。 有关更多信息,请参阅支持的注释文档。 这也可以通过使用master/minion 设置来处理。
  • spec.ingressClassName指的是我们已经安装并将要使用的 NGINX Ingress 控制器。
  • spec.tls.secret Kubernetes Secret 资源存储 Let's Encrypt 颁发证书时返回的证书密钥。
  • 我们为spec.tls.hostsspec.rules.host指定了主机名cert.example.com 。 这是我们的 ClusterIssuer 颁发证书的主机名。
  • spec.rules.http部分定义路径和将在这些路径上提供服务请求的后端服务。 例如,到/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 颁发证书时返回的证书密钥。
  • 我们为spec.host指定了主机名cert.example.com 。 这是我们的 ClusterIssuer 颁发证书的主机名。
  • spec.upstreams值指向我们的后端服务,包括端口。
  • spec.routes定义了路线以及命中这些路线时要采取的操作。
  1. 修改上述清单以适合您的安装。 至少,这将涉及改变spec.host值,但您应该检查配置中的所有参数。
  2. 应用清单。
  3. $  kubectl apply -f ./cafe-virtual-server.yaml  virtualserver.k8s.nginx.org/cafe created
  4. 等待证书颁发。 您应该看到有效状态。
  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(路由)(咖啡或茶)以及请求 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 命令来实现此目的的说明。

我应该使用哪种挑战类型?

这很大程度上取决于您的使用情况。

HTTP-01 质询方法要求端口 80 对 Internet 开放,并且已为 Ingress 控制器的 IP 地址正确配置 DNS A 记录。 除了创建 A 记录之外,此方法不需要访问 DNS 提供商。

当您无法将端口 80 暴露给 Internet 时,可以使用 DNS-01 质询方法,并且仅要求 cert-manager 具有对 DNS 提供商的出口访问权限。 但是,此方法确实要求您能够访问 DNS 提供商的 API,尽管所需的访问级别因具体提供商而异。

如何解决问题?

由于 Kubernetes 非常复杂,因此很难提供有针对性的故障排除信息。 如果您确实遇到问题,我们诚邀您在NGINX Community Slack上向我们提问(NGINX Plus 订阅者可以使用他们的正常支持选项)。

立即开始

首先申请 NGINX Ingress Controller 与 NGINX App Protect WAF 和 DoS 的30 天免费试用版,然后下载始终免费的 NGINX Service Mesh。


“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”