博客 | NGINX

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

NGINX-F5-horiz-black-type-RGB 的一部分
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”已添加到您的存储库 
  3. 更新存储库。
  4. $ helm repo update 请稍等,我们会从您的图表存储库中获取最新信息...
    ...已成功从“nginx-stable”图表存储库获取更新
    更新完成。⎈祝您 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 
    名称:nginx-kic
    最后部署: 2022 年 9 月 1 日星期四 15:58:15
    命名空间:nginx-ingress
    状态:已部署
    修订: 1
    测试套件: 无
    备注:
      NGINX Ingress Controller 已安装。 
  7. 检查部署并检索 Ingress 控制器的出口 IP 地址。 请注意,如果没有有效的 IP 地址,您将无法继续。
  8. $ kubectl get deploys --namespace nginx-ingress 名称 就绪 最新 可用 年龄
    nginx-kic-nginx-ingress 1/1 1 1 23s
    $ kubectl get services --namespace nginx-ingress 名称 类型 集群 IP 外部 IP 端口 年龄
    nginx-kic-nginx-ingress 负载均衡器10.128.60.190   www.xxx.yyy.zzz   80:31526/TCP,443:32058/TCP   30 秒 

添加您的 DNS A 记录

此处的过程取决于您的 DNS 提供商。 此 DNS 名称需要从 Let's Encrypt 服务器解析,这可能需要您等待记录传播才能起作用。 有关更多信息,请参阅 SiteGround 文章DNS 传播是什么以及为什么它需要这么长时间?

一旦您可以解析所选的 FQDN,您就可以继续下一步。

$ host cert.example.com cert.example.com 有地址 www.xxx.yyy.zzz

部署证书管理器

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

  1. 添加 Helm 存储库。
  2. $ helm repo add jetstack https://charts.jetstack.io “jetstack” 已添加到您的存储库 
  3. 更新存储库。
  4. $ helm repo update 请稍等,我们会从您的图表存储库中获取最新信息...
    ...已成功从“nginx-stable”图表存储库获取更新
    ...已成功从“jetstack”图表存储库获取更新
    更新完成。⎈祝您掌舵愉快!⎈ 
  5. 部署证书管理器。
  6. $ helm install cert-manager jetstack/cert-manager \ --namespace cert-manager --create-namespace \ --version v1.9.1 --set installCRDs=true 名称:cert-manager 最后部署: 2022 年 9 月 1 日星期四 16:01:52 命名空间:cert-manager 状态:已部署 修订: 1 测试套件: 无 注意:cert-manager v1.9.1 已成功部署!
    为了开始颁发证书,您需要设置 ClusterIssuer 或 Issuer 资源(例如,通过创建“letsencrypt-staging”颁发者)。
    有关不同类型的发行人及其配置方法的更多信息,请参阅我们的文档:
    https://cert-manager.io/docs/configuration/
    有关如何配置 cert-manager 以自动为 Ingress 资源提供证书的信息,请查看“ingress-shim”文档:
    https://cert-manager.io/docs/usage/ingress/
  7. 验证部署。
  8. $ kubectl get deploys --namespace cert-manager 名称 已就绪 最新 可用 年龄
    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 克隆到“kubernetes-ingress”...
    远程: 枚举对象: 44979,完成。
    远程: 计数对象: 100% (172/172),完成。
    远程: 压缩对象: 100% (108/108),完成。
    远程: 总计 44979(增量 87),重用 120(增量 63),打包重用 44807
    接收对象: 100% (44979/44979),60.27 MiB | 27.33 MiB/s,完成。
      解决差异: 100%(26508/26508),完成。 
  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 创建
    service/coffee-svc 创建
    deployment.apps/tea 创建
    service/tea-svc 创建
  7. 使用kubectl get 命令验证部署和服务。 您要确保 Pod 显示为READY ,并且 Services 显示为running 。 下面的示例显示了您正在寻找的代表性样本。 请注意, kubernetes服务是在与 NGINX Cafe 示例相同的命名空间(默认)中运行的系统服务。
  8. $ kubectl get deploys,services  --namespace default NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
    deploy.apps/coffee   2/2     2              2            69s
    deploy.apps/tea      3/3     3              3            68s
    NAME                   TYPE         CLUSTER-IP        EXTERNAL-IP   PORT(S)   AGE
    service/coffee-svc   ClusterIP   10.128.154.225  <无> 80/TCP 68s
    服务/kubernetes ClusterIP 10.128.0.1 <无> 443/TCP 29m
    服务/tea-svc ClusterIP 10.128.96.145 <无> 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
    种类: ClusterIssuer
    元数据:
      名称:prod-issuer
    规范:
      acme:
        电子邮件:example@example.com
        服务器:https://acme-v02.api.letsencrypt.org/directory
        privateKeySecretRef:
          名称:prod-issuer-account-key
        求解器:
        - http01:
           入口:
              类:nginx
    EOF
    clusterissuer.cert-manager.io/prod-issuer 创建 
  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
    种类: 机密
    元数据:
      名称:cloudflare-api-token-secret
      命名空间:cert-manager
    类型: 不透明
    stringData:
      api-token: <API Token> 
    EOF 
  3. 使用 DNS-01 创建发行者以进行挑战。
  4. $ cat << EOF | kubectl apply -f apiVersion: cert-manager.io/v1
    种类: ClusterIssuer
    元数据:
      名称:prod-issuer
    规格:
      acme:
        电子邮件:example@example.com
        服务器:https://acme-v02.api.letsencrypt.org/directory
        privateKeySecretRef:
          名称:prod-issuer-account-key
        求解器:
          - dns01:
               cloudflare:
                 apiTokenSecretRef:
                    名称:cloudflare-api-token-secret
                    密钥:api-token
    EOF 
  5. 验证发行人(应显示为已准备就绪)。
  6. $ kubectl 获取 clusterissuer 名称 READY AGE
    prod-issuer True 31 分钟 

部署 Ingress

这就是我们一直在努力实现的目标——为我们的应用部署 Ingress 资源。 这会将流量路由到我们之前部署的 NGINX Cafe应用。

使用 Kubernetes Ingress

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

api版本:networking.k8s.io/v1 种类: 入口 
元数据: 
  名称:cafe-ingress 
  注释: 
    cert-manager.io/cluster-issuer:prod-issuer 
    acme.cert-manager.io/http01-edit-in-place:“true” 
规格: 
  ingressClassName:nginx 
  tls: 
  - 主机: 
    - cert.example.com 
    secretName:cafe-secret 
  规则: 
  - 主机:cert.example.com 
    http: 
      路径: 
      - 路径:/tea 
        路径类型: 前缀 
        后端: 
           服务: 
             名称:tea-svc 
             端口: 
               号码: 80 
      - 路径:/咖啡 
        路径类型: 前缀 
        后端: 
          服务: 
             名称:coffee-svc 
             端口: 
号码: 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 创建 
  4. 等待证书颁发。 您正在寻找 READY 字段的“True”值。
  5. $ kubectl 获取证书 名称                                            READY   SECRET        AGE
    certificate.cert-manager.io/cafe-secret   True    cafe-secret   37m 

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

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

  api版本: k8s.nginx.org/v1 
种类: 虚拟服务器 
元数据: 
  名称:cafe 
规格: 
  主机:cert.example.com 
  tls: 
    秘密:cafe-secret 
    证书管理器: 
      集群发行者:prod-issuer 
  上游: 
  - 名称:tea 
    服务:tea-svc 
    端口: 80 
  - 名称:coffee 
    服务:coffee-svc 
    端口: 80 
  路线: 
  - 路径:/茶 
    动作: 
      通行证:茶 
  - 路径:/咖啡 
    动作: 
通行证:咖啡

再次强调,值得回顾一下清单中的一些关键部分:

  • 正在调用的 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 创建 
  4. 等待证书颁发。 您应该看到有效状态。
  5. $ kubectl 获取虚拟服务器名称 状态 主机 IP 端口 年龄 咖啡馆 有效 cert.example.com www.xxx.yyy.zzz [80,443] 51m 

查看证书

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

$ kubectl describe secret cafe-secret 名称: cafe-secret
命名空间: default
标签: <none>
注释:  cert-manager.io/alt-names: cert.example.com
                cert-manager.io/证书名称:咖啡馆的秘密
                cert-manager.io/通用名称: 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 :类型: kubernetes.io/tls数据 ==== tls.crt:  5607 字节 tls.key:  1675 字节 

如果您想查看实际的证书和密钥,可以通过运行以下命令来实现。 (笔记: 这确实说明了 Kubernetes Secrets 的弱点。 也就是说,任何拥有必要访问权限的人都可以阅读它们。)

$ kubectl 获取秘密 cafe-secret -o yaml

测试 Ingress

测试证书。 您可以在此处使用任何您希望的方法。 下面的示例使用cURL 。 成功通过与之前显示的类似块来表示,其中包括服务器名称、服务器的内部地址、日期、所选的 URI(路由)(咖啡或茶)以及请求 ID。失败将采用 HTTP 错误代码的形式,最有可能是 400 或 301。

$ curl https://cert.example.com/tea
服务器地址: 10.2.0.6:8080
服务器名称:tea-5c457db9-l4pvq
日期: 2022 年 9 月 2 日:15:21:06 +0000
URI:/tea
请求 ID:d736db9f696423c6212ffc70cd7ebecf
$ curl https://cert.example.com/coffee
服务器地址: 10.2.2.6:8080
服务器名称:coffee-7c86d7d67c-kjddk
日期: 2022 年 9 月 2 日:15:21:10 +0000
URI:/coffee
请求 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 内容。”