使用 cert-manager 签发免费证书
前置条件:
使用 HTTPS 需要向权威机构申请证书,并且需要付出一定的成本,如果需求数量多,则开支也相对增加。cert-manager 是 Kubernetes 上的全能证书管理工具,支持利用 cert-manager 基于 ACME 协议与 Let's Encrypt 签发免费证书并为证书自动续期,实现永久免费使用证书。
本地虚拟机 k8s 集群,也是可以使用 cert-manager 和 Let's Encrypt 组合的。
前提是,你有一个属于你自己的域名。比如 todoit.tech 就是作者本人的域名。
安装 cert-manager
cert-manager 将证书和证书颁发者作为资源类型添加到 Kubernetes 集群中,并简化了获取、更新和使用这些证书的过程。
它可以从各种受支持的来源颁发证书,包括 Let’s Encrypt、HashiCorp Vault 和 Venafi 以及私有 PKI。
它将确保证书有效且是最新的,并在到期前的配置时间尝试更新证书。
上图显示,cert-manager 拥有 Issuer 和 Certificate 等自定义资源,我们稍后会创建这些资源。
添加 cert-manager 仓库
helm repo add jetstack https://charts.jetstack.io
helm repo update
生成 values.yaml
helm show values jetstack/cert-manager > values.yaml
修改 values.yaml
installCRDs: true
prometheus:
enabled: false
webhook:
timeoutSeconds: 10
如果想查看生成的清单,可以使用
helm template cert-manager jetstack/cert-manager -n cert-manager -f values.yaml > cert-manager.yaml
安装 cert-manager
helm install cert-manager jetstack/cert-manager -n cert-manager --create-namespace -f values.yaml
等待
kubectl wait --for=condition=Ready pods --all -n cert-manager
# pod/cert-manager-55d85487cb-bmn5k condition met
# pod/cert-manager-cainjector-58448cbc99-wwrbg condition met
# pod/cert-manager-webhook-89f77959f-gmxvs condition met
创建 Issuer
Let’s Encrypt 利用 ACME (Automated Certificate Management Environment) 协议校验域名的归属,校验成功后可以自动颁发免费证书。免费证书有效期只有 90 天,需在到期前再校验一次实现续期。使用 cert-manager 可以自动续期,即实现永久使用免费证书。校验域名归属的两种方式分别是 HTTP-01 和 DNS-01,校验原理详情可参见 Let's Encrypt 的运作方式。
DNS-01 校验支持泛域名, 但是是不同 DNS 提供商的配置方式不同,DNS 提供商过多而 cert-manager 的 Issuer 不能全部支持。部分可以通过部署实现 cert-manager 的 Webhook 服务来扩展 Issuer 进行支持。例如阿里 DNS 就是通过 Webhook 的方式进行支持。
由于作者在编写本文时,未能找到能在本地虚拟机 k8s 集群正常工作的阿里 DNS Webhook,所以将域名迁移至 cloudflare。或许待作者日后有能力自己写一个。
关于如何将域名迁移到 cloudflare,请参考其它资料。
添加 DNS 记录,服务器可以用阿里云的
前往个人资料 -> API 令牌, 使用编辑区域 DNS 模版, 创建一个 token。
测试令牌是否能正常工作:
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer <yout api token>" \
-H "Content-Type:application/json"
作者将得到的 token,保存到 .env 文件中,并将 .env 添加到 .gitignore 中,毕竟这是敏感信息呢。
# .env
# api-token=<token>
api-token=Anbv2XXQ4pOqKYHGLtx5uSnnAqzTkvCPzxogjLKP
我们使用 Kustomize 来管理该 token。
编写 kustomization.yaml
# kustomization.yaml
resources:
- letsencrypt-issuer.yaml
namespace: cert-manager
secretGenerator:
- name: cloudflare-api-token-secret
envs:
- .env # token 就存放在这里,这个文件不会被提交到 Git 仓库中
generatorOptions:
disableNameSuffixHash: true
我们使用 Kustomize 来生成一个名为 cloudflare-api-token-secret 的 Secret, 该 Secret 被下面的清单使用
# letsencrypt-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
privateKeySecretRef:
name: letsencrypt-dns01
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
cloudflare:
email: [email protected] # 替换成你的 cloudflare 邮箱账号
apiTokenSecretRef:
key: api-token
name: cloudflare-api-token-secret # 引用保存 cloudflare 认证信息的 Secret
所生成的 Secret 可以使用下面的命令来检查
kubectl kustomize ./
如无问题,运行如下命令,创建 issuer
kubectl apply -k ./
查看 Let't Encrypt 注册状态
kubectl describe clusterissuer letsencrypt-dns01
如下表示注册成功
创建 Certificate
kubectl apply -f certificate.yaml
# certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-todoit-tech
namespace: default
spec:
dnsNames:
- "*.todoit.tech" # 要签发证书的域名,替换成你自己的
issuerRef:
kind: ClusterIssuer
name: letsencrypt-dns01 # 引用 ClusterIssuer,名字和 letsencrypt-issuer.yaml 中保持一致
secretName: wildcard-letsencrypt-tls # 最终签发出来的证书会保存在这个 Secret 里面
查看证书是否签发成功
kubectl get certificate -w
# NAME READY SECRET AGE
# wildcard-todoit-tech False wildcard-letsencrypt-tls 8s
# wildcard-todoit-tech False wildcard-letsencrypt-tls 94s
# wildcard-todoit-tech False wildcard-letsencrypt-tls 94s
# wildcard-todoit-tech True wildcard-letsencrypt-tls 94s
注意事项:Let's Encrypt 一个星期内只为同一个域名颁发 5 次证书,todoit.tech 和 whoami.todoit.tech 被视为不同的域名。
可以通过以下方式来了解为何未能颁发成功的原因:
测试
kubectl apply -f whoami.yaml
# cert/whoami.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
labels:
app: containous
name: whoami
spec:
replicas: 2
selector:
matchLabels:
app: containous
task: whoami
template:
metadata:
labels:
app: containous
task: whoami
spec:
containers:
- name: containouswhoami
image: containous/whoami
resources:
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- name: http
port: 80
selector:
app: containous
task: whoami
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- "whoami.todoit.tech"
secretName: wildcard-letsencrypt-tls
rules:
- host: whoami.todoit.tech
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami
port:
number: 80
在浏览器中输入 whoami.todoit.tech,可以看到由 R3 给我们颁发了一张合法的证书
参考