有效的 SSL/TLS 证书是现代应用环境的核心要求。但遗憾的是,在部署应用时,证书的更新管理很容易被忽略。证书的有效期有限且时长不等,DigiCert 证书的有效期在 13 个月左右,Let’s Encrypt 证书的有效期为 90 天。为了确保安全访问,这些证书需要在到期前更新/重新颁发。由于大多数运维团队的工作任务繁重,证书更新有时会被搁置,因此在证书临近(或更糟糕)超过到期日时,手忙脚乱地管理证书的现象时常发生。
其实,情况大可不必如此。只需做好一些规划和准备,就可以简化并实现证书管理的自动化。在这里,我们将介绍一款解决方案,面向使用以下三种技术的 Kubernetes 环境:
通过本文,您将了解如何通过向端点提供自动更新的证书来简化证书管理。
在讨论技术细节之前,我们需要先定义一些术语。“TLS 证书”是指在我们的 Ingress controller(Ingress 控制器)上启用 HTTPS 连接所需的两个组件:
证书和私钥都由 Let’s Encrypt 颁发。关于 TLS 证书如何工作的详细说明,请参阅 DigiCert 文章《TLS/SSL 证书如何工作》。
在 Kubernetes 中,这两个组件被存储为 Secrets。Kubernetes 工作负载,例如 NGINX Ingress Controller 和 cert-manager 可以写入和读取这些 Secret,也可以由有权访问 Kubernetes 安装的用户进行管理。
cert-manager 项目是一个可以与 Kubernetes 和 OpenShift 协同工作的证书控制器。当部署在 Kubernetes 中时,cert-manager 将自动颁发 Ingress controller 所需的证书,并确保它们是有效和最新的。此外,它将跟踪证书的到期日,并按照配置的时间间隔尝试更新。虽然它与大量公共和私有证书颁发机构合作,但在这里我们将只介绍它与 Let’s Encrypt 的集成。
当使用 Let’s Encrypt 时,所有证书管理都可以自动进行。虽然这提供了极大的便利,但也带来了一个问题:这项服务如何确保您拥有完全限定域名 (FQDN)?
这个问题可使用 challenge 来解决,challenge 会要求您回答一个仅有权访问特定域名的 DNS 记录的人员才能发起的验证请求。challenge 采取以下两种形式中的一种:
HTTP-01 是生成证书的最简单方法,因为它不需要直接访问 DNS 提供商。这种类型的 challenge 总是通过 HTTP 80 支持的DNS 端口 进行。注意,当使用 HTTP-01 challenge 时,cert-manager 将利用 Ingress controller 来提供 challenge 令牌。
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 访问。
最简单的方法是通过 Helm 进行部署。这种部署允许您同时使用 Kubernetes Ingress 和 NGINX Virtual Server CRD。
$ helm repo add nginx-stable https://helm.nginx.com/stable "nginx-stable" has been added to your repositories
$ 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!⎈
$ 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.
$ 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 提供商而定。这个 DNS 名称需要能够从 Let’s Encrypt 服务器进行解析,在此之前,您可能需要等待记录传播。有关这方面的更多信息,请参阅 SiteGround 文章《什么是 DNS 传播,为什么需要这么长时间?》
如果能够解析所选择的 FQDN,则可以进入下一步。
$ host cert.example.com cert.example.com has address www.xxx.yyy.zzz
下一步是部署最新版本的 cert-manager。同样,我们将使用 Helm 进行部署。
$ helm repo add jetstack https://charts.jetstack.io "jetstack" has been added to your repositories
$ 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!⎈
$ 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/
$ 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 示例来提供后端部署和 services。这个示例在 NGINX 提供的文档中十分常见。在此过程中,我们不会部署 Ingress。
$ 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.
$ cd ./kubernetes-ingress/examples/ingress-resources/complete-example
$ kubectl apply -f ./cafe.yaml
deployment.apps/coffee created
service/coffee-svc created
deployment.apps/tea created
service/tea-svc created
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 <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
在 cert-manager 中,ClusterIssuer 可被用来颁发证书。这是一个集群范围对象,可以被任何命名空间引用,也可以被向指定证书颁发机构发起的任何证书请求所使用。在这个示例中,任何对 Let’s Encrypt 证书的请求都可以由这个 ClusterIssuer 来处理。
为选择的 challenge 类型部署 ClusterIssuer。虽然这不在本文的讨论范围内,但简单提一下,您可以通过高级配置选项在 ClusterIssuer 中指定多个解析器(根据选择器字段选择)。
可使用自动证书管理环境 (ACME) 协议来确定您是否拥有某个域名,进而确定能否被颁发 Let’s Encrypt 证书。对于这个 challenge,需要传递以下参数:
这个示例显示了如何设置 ClusterIssuer 以使用 HTTP-01 challenge 来证明域名所有权和接收证书。
$ 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
$ kubectl get clusterissuer NAME READY AGE
prod-issuer True 34s
这个示例显示了如何设置 ClusterIssuer 以使用 DNS-01 challenge 来验证域名所有权。根据具体的 DNS 提供商,您可能需要使用 Kubernetes Secret 来存储令牌。这个示例使用的是 Cloudflare。注意命名空间的使用。部署到 cert-manager 命名空间中的 cert-manager 应用需要访问 Secret。
在这个示例中,您需要一个 Cloudflare API 令牌,您可以从账户中创建该令牌。这将需要放在下面的 <API Token> 行中。如果您使用的不是 Cloudflare,则需要遵循提供商的文档。
$ 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
$ 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
$ kubectl get clusterissuer NAME READY AGE
prod-issuer True 31m
完成上述步骤后,即可为应用部署 Ingress 资源。这将把流量路由到我们先前部署的 NGINX Cafe 应用中。
如果您使用标准的 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
需要检查清单的一些关键部分:
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
$ kubectl get certificates NAME READY SECRET AGE
certificate.cert-manager.io/cafe-secret True cafe-secret 37m
如果您使用 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
需要再次检查的一些关键部分。
spec.tls.secret
Kubernetes Secret 资源存储 Let’s Encrypt 颁发证书时所返回的证书密钥。cert.example.com
是为 spec.host
指定的。我们的 ClusterIssuer 就为这个主机名颁发证书。spec.upstreams
的值指向我们的后端服务,包括端口。spec.routes
定义了路由以及路由匹配时采取的操作。spec.host
的值,但建议您审查配置中的所有参数。$ kubectl apply -f ./cafe-virtual-server.yaml virtualserver.k8s.nginx.org/cafe created
$ 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
测试证书。在这里您可以使用任何想用的方法。下面的示例使用了 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 Ingress Controller。请参阅 NGINX 文档中的安装 Helm 部分,了解如何修改上述 Helm 命令以完成安装。
这主要取决于您的用例。
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。
"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."