k8s 上利用 cert-manager 自动签发 TLS 证书
很多博主的 https
证书经常容易忘记更新,虽说证书过期前都会有邮件提醒,但是万一确实忙得没时间去处理,忘记了,就会出现证书过期的情况了。
之前在服务器上自己搭博客服务的时候,用 Let's Encrypt
来自动创建并续签证书,确实省了不少事。
在我的博客部署到 k8s
之后,就一直用的一年一签的免费证书,每年更新一次,也不算特别麻烦,但是总归不够高端,我又怀念起了 Let's Encrypt
。
Let's Encrypt
是个好东西,k8s
也是个好东西,两个好东西怎么结合呢?搜寻了一番确实有方案,经过几天的尝试,终于弄好了。花了几天是因为第一天因为有个粗心导致的问题,导致搞了好久没成功,休息了几天再次尝试,才找到问题。
有关 k8s
的基础知识,这里不做赘述,网上教程很多,这里假设大家对 k8s
都有一定了解。
安装 cert-manager
安装 helm
到本地
$ brew install helm
添加仓库和命名空间
$ kubectl create namespace cert-manager # 创建 cert-manager 命名空间
$ kubectl label namespace cert-manager certmanager.io/disable-validation=true # 标记 cert-manager 命名空间以禁用资源验证
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager-legacy.crds.yaml # 安装 CustomResourceDefinition 资源,注意 k8s 版本低于 1.15 需要用 legacy 版本
$ helm repo add jetstack https://charts.jetstack.io # 添加 Jetstack Helm repository
$ helm repo update # 更新本地 Helm chart repository
安装 cert-manager
$ helm install cert-manager --namespace cert-manager --version v0.14.1 jetstack/cert-manager
查看 cert-manager
安装情况
$ kubectl get pods --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-6cff8dc7b9-8vxws 1/1 Running 0 4d10h
cert-manager-cainjector-795c46858f-txczb 1/1 Running 0 4d10h
cert-manager-webhook-5dfc77cd74-skgsv 1/1 Running 0 4d10h
更新 cert-manager
$ kubectl delete -n cert-manager deployment cert-manager cert-manager-cainjector cert-manager-webhook
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.14.1/cert-manager-legacy.crds.yaml
$ helm repo update
$ helm upgrade --version v0.14.1 cert-manager jetstack/cert-manager -n cert-manager
创建 ClusterIssuer
我们需要创建一个签发机构,cert-manager
提供了Issuer
和 ClusterIssuer
两种类型的签发机构,Issuer
只能用来签发自己所在命名空间下的证书,ClusterIssuer 可以签发任意命名空间下的证书,我这里用 ClusterIssuer
为例,创建 letsencrypt-prod.yaml
文件:
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
labels:
name: letsencrypt-prod
name: letsencrypt-prod # 自定义的签发机构名称,后面会引用
spec:
acme:
email: [email protected] # 你的邮箱,证书快过期的时候会邮件提醒,不过我们可以设置自动续期
solvers:
- http01:
ingress:
class: nginx
privateKeySecretRef:
name: letsencrypt-prod # 指示此签发机构的私钥将要存储到哪个 Secret 对象中
server: https://acme-v02.api.letsencrypt.org/directory # acme 协议的服务端,我们用 Let's Encrypt
应用 yaml
$ kubectl create -f letsencrypt-prod.yaml
查看状态
$ kubectl get clusterissuer
NAME READY AGE
letsencrypt-prod True 51s
手动签发证书
手动签发证书,创建 test-monkeyrun-net-cert.yaml
文件
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: test-monkeyrun-net-cert
namespace: test
spec:
secretName: tls-test-monkeyrun-net # 证书保存的 secret 名
duration: 2160h # 90d
renewBefore: 720h # 30d
dnsNames:
- test.monkeyrun.net
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
group: cert-manager.io
应用 yaml
$ kubectl apply -f test-monkeyrun-net-cert.yaml
检查是否生成证书文件
$ kubectl get certificate -n test
NAME READY SECRET AGE
test-monkeyrun-net-cert True test-monkeyrun-net-tls 99m
将该证书配置到 test.monkeyrun.net
的 ingress
上,测试 https
访问,成功。
创建Deployment时自动签发证书
创建 test-nginx.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: test-nginx
namespace: test
spec:
replicas: 1
template:
metadata:
labels:
run: test-nginx
spec:
containers:
- name: test-nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: test-nginx
namespace: test
labels:
app: test-nginx
spec:
ports:
- port: 80
protocol: TCP
name: http
selector:
run: test-nginx
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-nginx
namespace: test
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: 'true'
certmanager.io/cluster-issuer: letsencrypt-prod
spec:
rules:
- host: test.monkeyrun.net
http:
paths:
- backend:
serviceName: test-nginx
servicePort: 80
path: /
tls:
- secretName: tls-test-monkeyrun-net
hosts:
- test.monkeyrun.net
删除之前手动创建的 Deployment
、Service
、 Ingress
和 Secret
后, 应用 yaml
来自动创建
$ kubectl apply -f test-nginx.yaml
打开 https://test.monkeyrun.net
测试,成功!
不知为何再次使用自动签发证书的时候会报错:
E0330 07:46:30.070412 1 sync.go:57] cert-manager/controller/ingress-shim "msg"="failed to determine issuer to be used for ingress resource" "error"="failed to determine issuer name to be used for ingress resource" "resource_kind"="Ingress" "resource_name"="xxx" "resource_namespace"="xxx"
解决了半天都没能找到问题,所以还是用手动签发吧,反正也是一次性的操作。
通过 DNS 验证域名
刚才通过 http01 的方式验证域名会有个问题,对于已经部署上线的项目,没办法去验证,所以可以通过 dns 的方式来验证。
经过搜寻,找到了几篇文章,都是利用 kevinniu666 这位仁兄基于 jetstack/cert-manager-webhook-example 改成 alidns
的版本来搞的,不过尝试了下,他这里面 cert-manager
版本太老已经跑不起来了,从 GitHub 的 forks 树里面找到了最新的一个 fork,colprog/cert0manager-webhooks-alidns,尝试了下,也不行,他应该是改了镜像,但是不可用了。重新尝试了下上一代 fork pangzineng/cert-manager-webhook-alidns,可用。
$ git clone https://github.com/pangzineng/cert-manager-webhook-alidns.git
$ cd cert-manager-webhook-alidns
$ helm install cert-manager-webhook-alidns --namespace=cert-manager ./deploy/webhook-alidns
创建 alidns AccessKey Id 和 Secret
$ kubectl -n cert-manager create secret generic alidns-access-key-id --from-literal=accessKeyId='xxxxxxx'
$ kubectl -n cert-manager create secret generic alidns-access-key-secret --from-literal=accessKeySecret='xxxxxxx'
更新:使用 pragkent/alidns-webhook
修改我们之前创建的 letsencrypt-prod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
labels:
name: letsencrypt-prod
name: letsencrypt-prod # 自定义的签发机构名称,后面会引用
spec:
acme:
email: [email protected] # 你的邮箱,证书快过期的时候会邮件提醒,不过我们可以设置自动续期
solvers:
- dns01:
webhook:
groupName: yourgroup.com
solverName: alidns
config:
region: ''
accessKeySecretRef:
name: alidns-secret
key: access-key
secretKeySecretRef:
name: alidns-secret
key: secret-key
privateKeySecretRef:
name: letsencrypt-prod-account-key # 指示此签发机构的私钥将要存储到哪个 Secret 对象中
server: https://acme-v02.api.letsencrypt.org/directory # acme 协议的服务端,我们用 Let's Encrypt
应用 yaml
$ kubectl create -f letsencrypt-prod.yaml
查看状态
$ kubectl get clusterissuer
NAME READY AGE
letsencrypt-prod True 51s
重新手动签发证书,验证,成功!
PS:需要注意的是,从 http01 认证修改到 dns01 认证后,有个坑,会一直失败,查看 cert-manager 的 Pod 日志,会发现如下错误:
cert-manager/controller/orders "msg"="Failed to determine the list of Challenge resources needed for the Order" "error"="no configured challenge solvers can be used for this challenge" "resource_kind"="Order" "resource_name"="xxx"
研究了半天都没成功,后来在 GitHub 上找到了这个 Issue,按照 demisx 这位仁兄的建议,把所有和 cert-manager
相关的东西全部删除重新用 dns01 的方式部署一遍就 OK 了。
另外,cert-manager 的 API group 从 certmanager.k8s.io
改到 certmanager.io
了,不少老教程里面仍然是前者,需要改为后者才能正常执行。
参考链接