有效的SSL/TLS证书是现代应用环境的核心要求。遗憾的是,在部署应用时,证书续期管理往往未得到充分重视。证书的有效期有限,从DigiCert证书约13个月到Let’s Encrypt证书90天不等。为确保访问安全,必须在证书到期前续期或重新签发。大多数运维团队工作繁重,有时难免疏忽证书续期,导致证书临近到期,甚至已经过期时才匆忙处理。
情况不必如此。通过适当的规划和准备,可以实现证书管理的自动化并简化流程。下面将介绍一种使用三项技术的Kubernetes解决方案:
本博客将介绍如何为端点提供唯一且可自动续订和更新的证书,从而简化证书管理。
Kubernetes环境中的证书
在深入了解技术细节之前,需要先定义一些术语。“TLS证书”是指在Ingress控制器上启用HTTPS连接所需的两个组件:
- 证书
- 私钥
证书和私钥均由Let’s Encrypt签发。有关TLS证书工作原理的完整说明,请参阅DigiCert的文章《TLS/SSL证书的工作原理》。
在Kubernetes中,这两个组件存储为Secret。Kubernetes工作负载(例如NGINX Ingress Controller和cert-manager)可以写入和读取这些Secret。能够访问Kubernetes安装环境的用户也可以管理这些Secret。
cert-manager 简介
The cert-manager项目是一款适用于Kubernetes和OpenShift的证书控制器。部署到Kubernetes后,cert-manager会自动签发Ingress控制器所需的证书,并确保这些证书有效且为最新版本。此外,它还会跟踪证书的到期日期,并按配置的时间间隔尝试续期。虽然它支持众多公共和私有证书颁发机构,但本文将介绍它与Let’s Encrypt的集成。
两种质询类型
使用 Let’s Encrypt 时,所有证书管理工作都会自动完成。这虽然非常方便,但也带来了一个问题:该服务如何确认相关完全限定域名(FQDN)归您所有?
此问题可通过质询解决。您需要响应一项验证请求,而只有能够访问特定域名DNS记录的人员才能提供所需信息。质询分为以下两种形式:
- 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控制器提供质询令牌。
- DNS-01:此质询会创建一条包含令牌的DNS TXT记录,随后由颁发机构进行验证。如果令牌通过验证,即可证明您拥有该域,并可为其记录签发证书。与HTTP-01质询不同,使用DNS-01质询时,FQDN无需解析到服务器的IP地址,甚至无需实际存在。此外,端口80被阻止时也可使用DNS-01。虽然这种方式使用方便,但需要通过API令牌向cert-manager安装实例授予访问DNS基础设施的权限。
Ingress 控制器
An Ingress控制器是Kubernetes的一项专用服务,可将集群外部流量引入集群,对流量进行负载均衡并将其分配到内部Pod(由一个或多个容器组成),同时管理出站流量。此外,Ingress控制器通过Kubernetes API进行控制,并会在添加、移除Pod或Pod发生故障时监控并更新负载均衡配置。
要详细了解Ingress控制器,请阅读以下博客:
在以下示例中,我们将使用由F5 NGINX开发和维护的NGINX Ingress Controller。
证书管理示例
这些示例假定您有一个可用于测试且正常运行的Kubernetes安装,并且该安装可以分配外部IP地址(Kubernetes LoadBalancer对象)。此外,还假定您可以通过端口80和端口443接收流量(使用HTTP-01质询时),或仅通过端口443接收流量(使用DNS-01质询时)。这些示例使用Mac OS X进行演示,但也适用于Linux或WSL。
还需要一个DNS提供商和一个可调整A记录的FQDN。如果使用HTTP-01质询,只需能够添加A记录(或由他人代为添加)。如果使用DNS-01质询,则需要通过API访问受支持的DNS提供商或受支持的Webhook提供商。
部署 NGINX Ingress Controller
最简单的方法是通过Helm进行部署。此部署支持同时使用Kubernetes Ingress和NGINX Virtual Server CRD。
- 添加NGINX仓库。
$ 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!⎈ - 部署Ingress控制器。
$ 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. - 检查部署,并获取Ingress控制器出口的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提供商。Let’s Encrypt服务器必须能够解析此DNS名称,因此可能需要等待记录传播后才能生效。有关详细信息,请参阅SiteGround文章什么是DNS传播,为什么需要这么长时间?
所选的FQDN能够正常解析后,即可继续下一步。
$ host cert.example.com cert.example.com has address www.xxx.yyy.zzz部署 cert-manager
下一步是部署最新版本的 cert-manager。我们仍将使用 Helm 进行部署。
- 添加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!⎈ - 部署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:
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示例
我们将使用NGINX Cafe示例来提供后端Deployment和Service。这是NGINX文档中常用的示例。本次不会部署Ingress。
- 克隆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. - 切换到 examples 目录。该目录包含多个示例,用于演示 Ingress 控制器的各种配置。此处使用 complete-example 目录下提供的示例。
$ cd ./kubernetes-ingress/examples/ingress-resources/complete-example - 部署NGINX Cafe示例。
$ kubectl apply -f ./cafe.yaml
deployment.apps/coffee created
service/coffee-svc created
deployment.apps/tea created
service/tea-svc created- 使用
kubectlget命令验证部署和服务。确保Pod显示为READY,服务显示为running。以下示例展示了预期结果的典型样例。请注意,kubernetes服务是一项系统服务,与NGINX Cafe示例在同一命名空间(default)中运行。
$ 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证书。此质询需要传递以下参数:
- metadata.name:ClusterIssuer名称,在Kubernetes安装环境中必须唯一。稍后在示例中颁发证书时将使用此名称。
- spec.acme.email:这是您为生成证书而向Let’s Encrypt注册的电子邮件地址。请使用您本人的电子邮件地址。
- spec.acme.privateKeySecretRef:这是用于存储私钥的Kubernetes Secret的名称。
- spec.acme.solvers:此项应保持不变。它指定所使用的质询类型(ACME称之为求解器),即HTTP-01或DNS-01,以及应应用到的Ingress类;在本例中为nginx。
使用HTTP-01
此示例说明如何设置ClusterIssuer,以使用HTTP-01质询验证域名所有权并获取证书。
- 创建使用HTTP-01进行质询的ClusterIssuer。
$ 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 - 验证ClusterIssuer(其状态应显示为“就绪”)。
$ 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令牌,可在账户中创建。需要将该令牌填入下方的<API Token>行。如果不使用Cloudflare,则需要按照所用提供商的文档操作。
- 为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: <API Token>
EOF - 创建使用DNS-01完成质询的颁发者。
$ 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
现在,我们终于可以部署一直以来为之准备的应用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”。此值为必填项,用于调整质询的提供方式。有关详细信息,请参阅《支持的注解》文档。也可以使用主节点/从节点配置来处理此问题。 - The
spec.ingressClassName指我们已安装并将使用的NGINX Ingress控制器。 - The
spec.tls.secretKubernetes Secret资源用于存储Let’s Encrypt颁发证书时返回的证书密钥。 - 主机名
cert.example.com已在spec.tls.hosts和spec.rules.host中指定。ClusterIssuer为此主机名颁发了证书。 - The
spec.rules.http部分定义路径,以及处理这些路径请求的后端服务。例如,发往/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 Virtual Server和Virtual Routes
如果使用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是NGINX专用于VirtualServer资源的k8s.nginx.org/v1。
- The
spec.tls.secretKubernetes Secret资源用于存储Let’s Encrypt签发证书时返回的证书密钥。 - 我们的主机名
cert.example.com在spec.host中指定。ClusterIssuer为此主机名颁发了证书。 - The
spec.upstreams的值指向后端服务,包括相应端口。 - The
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 Secret的一个弱点,即任何拥有必要访问权限的人都可以读取它们。)
$ 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命令来完成安装。
应该使用哪种质询类型?
这主要取决于您的用例。
HTTP-01质询方法要求将端口80向互联网开放,并为Ingress控制器的IP地址正确配置DNS A记录。除创建A记录外,此方法无需访问DNS提供商。
当无法将端口80开放至互联网时,可以使用DNS-01质询方法。此方法只要求cert-manager能够通过出站连接访问DNS提供商。不过,您必须能够访问DNS提供商的API,具体所需的访问权限级别因提供商而异。
如何排查问题?
由于Kubernetes非常复杂,很难提供有针对性的故障排除信息。如果遇到问题,欢迎通过NGINX Community Slack向我们咨询(NGINX Plus订阅用户可以使用常规支持渠道)。
立即开始
申请免费30天试用,开始体验集成NGINX App Protect WAF和DoS的NGINX Ingress Controller,并下载永久免费使用的NGINX Service Mesh。
相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。
