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

原文作者:Jason Schmidt - F5 NGINX 解决方案架构师

原文链接:在 Kubernetes 环境中实现证书管理的自动化

转载来源:NGINX 中文官网


NGINX 唯一中文官方社区 ,尽在 nginx.org.cn

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

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

  • Jetstack 的 cert-manager

  • Let’s Encrypt

  • NGINX Ingress Controller

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

Kubernetes 环境中的证书

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

  1. 证书

  2. 私钥

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

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

cert-manager 简介

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

在 Kubernetes 环境中实现证书管理的自动化_第1张图片

两种 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 令牌。

在 Kubernetes 环境中实现证书管理的自动化_第2张图片

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

在 Kubernetes 环境中实现证书管理的自动化_第3张图片

Ingress Controllers

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

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

  • Kubernetes 网络入门

  • Ingress Controller 选型指南,第四部分:NGINX 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 仓库。

$ helm repo add nginx-stable https://helm.nginx.com/stable
  "nginx-stable" has been added to your repositories 

2. 更新仓库。

$ 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!⎈ 

3. 部署 Ingress controller。

$ 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. 

4. 检查部署,并检索面向 Ingress controller 的 egress 的 IP 地址。注意,如果没有有效的 IP 地址,则无法继续。

$ 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 仓库。

$ helm repo add jetstack https://charts.jetstack.io
  "jetstack" has been added to your repositories 

2. 更新仓库。

$ 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!⎈ 

3. 部署 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: 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:
  Issuer Configuration
  For information on how to configure cert-manager to automatically provision
  Certificates for Ingress resources, take a look at the `ingress-shim`
  documentation:
  Securing Ingress Resources

4. 验证部署。

$ 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 项目。

$ 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. 

2. 进入到示例目录。这个目录中包含几个示例,演示了 Ingress controller 的各种配置。我们使用的是 complete-example 目录下提供的示例。

$ cd ./kubernetes-ingress/examples/ingress-resources/complete-example

3. 部署 NGINX Cafe 示例。

$ kubectl apply -f ./cafe.yaml
  deployment.apps/coffee created
  service/coffee-svc created
  deployment.apps/tea created
  service/tea-svc created

4. 使用 kubectl get 命令验证部署和 services。您需要确保 Pods 显示为 READY,而 service 显示为 running。下面的示例为您提供了一个代表性样本。注意,kubernetes 服务是系统服务,与 NGINX Cafe 示例在同一个命名空间(默认)中运行。

$ 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           80/TCP    68s
  service/kubernetes   ClusterIP   10.128.0.1               443/TCP   29m
	service/tea-svc      ClusterIP   10.128.96.145            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。

$ cat << EOF | kubectl apply -f 
  apiVersion: cert-manager.io/v1
  kind: ClusterIssuer
  metadata:
    name: prod-issuer
  spec:
    acme:
      email: [email protected]
      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 

2. 验证该 ClusterIssuer(应该显示为 Ready)。

$ 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 令牌,您可以从账户中创建该令牌。这将需要放在下面的 行中。如果您使用的不是 Cloudflare,则需要遵循提供商的文档。

  1. 为 API 令牌创建 Secret。

$ cat << EOF | kubectl apply -f 
  apiVersion: v1
  kind: Secret
  metadata:
    name: cloudflare-api-token-secret
    namespace: cert-manager
  type: Opaque
  stringData:
    api-token:  
  EOF 

2. 创建颁发机构,使用 DNS-01 challenge。

$ cat << EOF | kubectl apply -f 
  apiVersion: cert-manager.io/v1
  kind: ClusterIssuer
  metadata:
    name: prod-issuer
  spec:
    acme:
      email: [email protected]
      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 

3. 验证颁发机构(它应显示为 ready)。

$ 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.hosts 和 spec.rules.host 指定的。我们的 ClusterIssuer 就为这个主机名颁发证书。

  • spec.rules.http 部分定义了路径以及满足这些路径上的请求的后端 Services。例如,到 /tea 的流量将被引导到 tea-svc 上的 80 端口。

  • 根据您的安装修改上述清单。至少需要更改 spec.rules.host 和 spec.tls.hosts 的值,但建议您审查配置中的所有参数。

  • 应用清单。

$  kubectl apply -f ./cafe-virtual-server.yaml
  virtualserver.k8s.nginx.org/cafe created 
  • 等待证书颁发,READY 字段的值会变为 “True”。

$ kubectl get certificates
  NAME                                      READY   SECRET        AGE
  certificate.cert-manager.io/cafe-secret   True    cafe-secret   37m 

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

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

更多实践代码点击NGINX社区官网查看

查看证书

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

$ kubectl describe secret cafe-secret
  Name:         cafe-secret
  Namespace:    default
  Labels:       
  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:  404 Page not found
  ====
  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 请求新的证书时,它将自动请求新的证书。简单至极。

常见问题

我应该使用哪种 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 唯一中文官方社区 ,尽在 nginx.org.cn

更多 NGINX 相关的技术干货、互动问答、系列课程、活动资源: 开源社区官网 | 微信公众号

你可能感兴趣的:(nginx,云原生,微服务,kubernetes)