Kubernetes 上部署 cert-manager 及使用
环境信息
- Kubernetes 1.24
- cert-manager v1.7.1
随着 HTTPS 不断普及,越来越多的网站都在从 HTTP 升级到 HTTPS,使用 HTTPS 就需要向权威机构申请证书,需要付出一定的成本,如果需求数量多,也是一笔不小的开支。cert-manager 是 Kubernetes 上的全能证书管理工具,如果对安全级别和证书功能要求不高,可以利用 cert-manager 基于 ACME 协议与 Let’s Encrypt 来签发免费证书并自动续期,实现永久免费使用证书。
cert-manager 工作原理
cert-manager 部署到 Kubernetes 集群后,它会 watch 它所支持的 CRD 资源,我们通过创建 CRD 资源来指示 cert-manager 为我们签发证书并自动续期: [1]
解释下几个关键的资源:
Issuer/ClusterIssuer
: 用于指示 cert-manager 用什么方式签发证书,本文主要讲解签发免费证书的 ACME 方式。ClusterIssuer
与Issuer
的唯一区别就是Issuer
只能用来签发自己所在namespace
下的证书,ClusterIssuer
可以签发任意namespace
下的证书。Certificate
: 用于告诉 cert-manager 我们想要什么域名的证书以及签发证书所需要的一些配置,包括对Issuer/ClusterIssuer
的引用。
免费证书签发原理
Let’s Encrypt 利用 ACME 协议来校验域名是否真的属于你,校验成功后就可以自动颁发免费证书,证书有效期只有 90 天,在到期前需要再校验一次来实现续期,幸运的是 cert-manager 可以自动续期,这样就可以使用永久免费的证书了。如何校验这个域名是否属于你呢?主流的两种校验方式是 HTTP-01 和 DNS-01,详细校验原理可参考 Let’s Encrypt 的运作方式,下面将简单描述下。
HTTP-01 校验原理
HTTP-01 的校验原理是给你域名指向的 HTTP 服务增加一个临时 location ,Let’s Encrypt 会发送 http 请求到 http://
DNS-01 校验原理
DNS-01 的校验原理是利用 DNS 提供商的 API Key 拿到你的 DNS 控制权限, 在 Let’s Encrypt 为 ACME 客户端提供令牌后,ACME 客户端 (cert-manager) 将创建从该令牌和您的帐户密钥派生的 TXT 记录,并将该记录放在 _acme-challenge.
校验方式对比
HTTP-01 的校验方式的优点是: 配置简单通用,不管使用哪个 DNS 提供商都可以使用相同的配置方法;缺点是:需要依赖 Ingress,如果你的服务不是用 Ingress 暴露流量的就不适用,而且不支持泛域名证书。
DNS-01 的校验方式的优点是没有 HTTP-01 校验方式缺点,不依赖 Ingress,也支持泛域名;缺点就是不同 DNS 提供商的配置方式不一样,而且 DNS 提供商有很多,cert-manager 的 Issuer 不可能每个都去支持,不过有一些可以通过部署实现了 cert-manager 的 Webhook 的服务来扩展 Issuer 进行支持,比如 DNSPod 和 阿里 DNS,详细 Webhook 列表请参考: https://cert-manager.io/docs/configuration/acme/dns01/#webhook
选择哪种方式呢?条件允许的话,建议是尽量用 DNS-01 的方式,限制更少,功能更全。 [2]
cert-manager 部署
cert-manager 使用
DNS-01 校验方式签发证书
下面以 cloudflare 为例来签发证书:
登录 cloudflare,点到 My Profile > API Tokens > Create Token 来创建 Token:
复制 Token 并妥善保管:将 Token 保存到 Secret 中:
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
namespace: cert-manager
type: Opaque
stringData:
api-token: <API Token> # 粘贴 Token 到这里,不需要 base64 加密。如果是要创建 ClusterIssuer,Secret 需要创建在 cert-manager 所在命名空间中,如果是 Issuer,那就创建在 Issuer 所在命名空间中。 [3]
创建 ClusterIssuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
privateKeySecretRef:
name: letsencrypt-dns01 # 用于储存 ACME Account 私钥的 Secret
email: [email protected]
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
cloudflare:
email: [email protected] # 替换成你的 cloudflare 邮箱账号,API Token 方式认证非必需,API Keys 认证是必需
apiTokenSecretRef:
key: api-token # 引用 secret 中的配置数据,key 为 secret 中配置的名称
name: cloudflare-api-token-secret # 引用保存 cloudflare 认证信息的 Secret创建 Certificate:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: test-mydomain-com
namespace: default
spec:
dnsNames:
- test.mydomain.com # 要签发证书的域名
- "*.mydomain.com"
issuerRef:
kind: ClusterIssuer
name: letsencrypt-dns01 # 引用 ClusterIssuer,指示采用 dns01 方式进行校验
secretName: test-mydomain-com-tls # 最终签发出来的证书会保存在这个 Secret 里面获取和使用证书
创建好 Certificate 后,等一小会儿,我们可以 kubectl 查看是否签发成功:
$ kubectl get certificate -n prod
NAME READY SECRET AGE
test-mydomain-com True test-mydomain-com-tls 1m如果 READY 为
False
表示失败,可以通过describe
查看event
来排查失败原因:$ kubectl describe certificate test-mydomain-com -n prod
如果为
True
表示签发成功,证书就保存在我们所指定的Secret 中
(上面的例子是default/test-mydomain-com-tls
),可以通过kubectl
查看:$ kubectl get secret test-mydomain-com-tls -n default
...
data:
tls.crt: <cert>
tls.key: <private key>其中
tls.crt
就是证书,tls.key
是密钥。你可以将它们挂载到你需要证书的应用中,或者使用 Ingress,可以直接在 Ingress 中引用
secret
,示例:apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
annotations:
kubernetes.io/Ingress.class: nginx
spec:
rules:
- host: test.mydomain.com
http:
paths:
- path: /web
backend:
serviceName: web
servicePort: 80
tls:
hosts:
- test.mydomain.com
secretName: test-mydomain-com-tls签发证书异常,可以检查 cert-manager 的 pod 的日志,查看报错信息。
nginx 使用 cert-manager 颁发的证书,可以将 cert 和 key 文件内容追加到同一个文件中(pem),Nginx 配置证书和 key 都使用此文件。