介绍
- 使用 HTTPS 需要向CA申请证书,并且需要付出一定的成本,如果需求数量多,则开支也相对增加。cert-manager 是 Kubernetes 上的全能证书管理工具,支持利用 cert-manager 基于 ACME 协议与Let's Encrypt CA 签发免费证书并为证书自动续期,实现永久免费使用证书。
- certmanager官网:Cert-Manager Helm Installation
- helm chart官网:cert-manager helm chart
- release note: cert-manager v1.17.1
- pod间TLS通信:YouTube - Pod TLS
- certmanager教程:YouTube - Cert-Manager
证书分类
| 证书类型 | 用途 | 特征 |
|---|---|---|
| Root CA证书 | 证书链的根,签发中间CA | 自签名,无域名限制 |
| 中间CA证书 | 签发终端实体证书 | 由Root CA签发,无域名限制 |
| 终端实体证书 End-entity Certificate | 实际服务使用 | 绑定具体域名,不能签发其他证书 |
拉取
helm repo add jetstack https://charts.jetstack.io
helm repo update jetstack
helm pull jetstack/cert-manager --version v1.16.1
配置
#参照ado code调整
#注意:crds.enabled额外设置成true,crds.keep设置成true,因为cert-manager-cainjector需要这个crd才能正常工作
安装
helm upgrade -i cert-manager -n cert-manager . --values values.yaml --create-namespace
#可以使用下面命令检查pod是否是ready
kubectl wait --for=condition=Ready pods --all -n cert-manager
使用
- cert-manager服务部署好之后,可以创建issuer和certificate资源:(refer: 创建 Issuer)
- issuer/clusterissuer是让cert-manager认出证书颁发机构,比如指定letsencrypt
- certificate资源就是绑定issuer,创建存放certificate的k8s secret
使用Self-signed certificate
创建self-signed cluster issuer
clusterIssuer文件是证书颁发者的配置模板,用于告诉 cert-manager 如何从Let's Encrypt等CA机构申请证书,而不是证书本身。它定义了申请 SSL/TLS 证书的方法和参数。
- Lab为简化处理,不去连接CA机构,用自签证书代替。Certificate示例
tee cluster-issuer-selfsigned <<'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
EOF
创建Certificate颁发给svc
注意:certificate和它生成的secret是一定在同一个namespace的
tee certificate-app01.yaml <<'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app01
spec:
secretName: app01-tls-cert-secret
privateKey:
rotationPolicy: Always
commonName: app01.default.svc.cluster.local #写的是后面要部署的svc的name
dnsNames:
- app01.default.svc.cluster.local
usages:
- digital signature
- key encipherment
- server auth
issuerRef:
name: selfsigned
kind: ClusterIssuer
EOF
#查看证书
k get clusterissuer,secret,certificate
NAME READY AGE
clusterissuer.cert-manager.io/selfsigned True 5m27s
NAME TYPE DATA AGE
secret/app01-tls-cert-secret kubernetes.io/tls 3 16s
NAME READY SECRET AGE
certificate.cert-manager.io/app01 True app01-tls-cert-secret 16s
pod挂载certificate
- pod用volume挂载secret,用环境变量指定证书位置。app中需要提前配置好https
tee deploy-app01.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: app01
name: app01
spec:
replicas: 1
selector:
matchLabels:
app: app01
template:
metadata:
labels:
app: app01
spec:
restartPolicy: Always
volumes:
- name: app01-tls
secret:
secretName: app01-tls-cert-secret
containers:
- name: app01
image: us-docker.pkg.dev/google-samples/containers/gke/hello-app-tls:1.0
ports:
- containerPort: 8443
volumeMounts:
- name: app01-tls
mountPath: /etc/tls
readOnly: true
env:
- name: TLS_CERT
value: /etc/tls/tls.crt
- name: TLS_KEY
value: /etc/tls/tls.key
---
apiVersion: v1
kind: Service
metadata:
name: app01
#namespace: monitoring
labels:
app: app01
spec:
ports:
- port: 443
protocol: TCP
targetPort: 8443
selector:
app: app01
type: ClusterIP
EOF
- 创建完成之后,可以用命令行测试pod间访问:
kubectl run nginx --image=nginx
kubectl exec -it nginx -- curl --insecure https://app01.default.svc.cluster.local
kubectl exec -it nginx -- curl --insecure -v https://app01.default.svc.cluster.local
Certificate颁发给ingress
-
创建certificate时common name和dns name写ingress的hostname,证书保存到secret
-
创建ingress时,配置tls字段,指定secret name,例如:
ingress:
enabled: true
ingressClassName: nginx-default
annotations:
nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.oauth2-proxy.svc.cluster.local/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://oauth2proxy.hanxux.local/oauth2/start?rd=https%3A%2F%2Fkyverno.hanxux.local"
hosts:
- host: kyverno.hanxux.local
paths:
- path: /
pathType: Prefix
tls:
- secretName: policy-reporter-tls-cert-secret
hosts:
- kyverno.hanxux.local
注意:pod只能挂载位于同一个namespace下的secret
使用Letsencrypt certificate
参考
- azure文档:AKS Ingress TLS
- letsencrypt官网:Let's Encrypt Getting Started
- challenge的方式:Challenge Types - DNS-01
- 创建Issuer和ClusterIssuer可以去官网找例子:ACME DNS01
过期时间
有效期90天,无法更改,建议每60天更新一次证书
校验原理
Let’s Encrypt CA 利用 ACME (Automated Certificate Management Environment,自动证书管理) 协议校验域名的归属,校验成功后可以自动颁发免费证书。免费证书有效期只有 90 天,需在到期前再校验一次实现续期。使用 cert-manager 可以自动续期,即实现永久使用免费证书。
Let's Encrypt 不颁发CA证书,只颁发终端实体证书。
校验域名归属的两种方式分别是 HTTP-01 和 DNS-01,校验原理详情可参见 Let's Encrypt 的运作方式
校验流程
- 用户向clusterissuer提出申请证书请求,clusterissuer向letsencrypt机构申请证书,letsencrypt机构进行审核,假如申请的域名为www.xxx.com,机构会审核www.xxx.com这个站点是不是你的?审核的方式有:http01和dns01。
- 使用http01的审核方式:letsencrypt机构让你往网站的某个路径里面写一个文件。然后他登录到www.xxx.com,看看能不能访问成功,访问成功就证明是你的网站。
- 这种方式对于内网web环境,letsencrypt访问不了。
- 这种方式也无法签发wildcard certificate
- 使用dns01的审核方式:如果www.xxx.com是你的站点,你肯定有dns服务器的操控权,在DNS服务器上生成一个api token,letsencrypt会尝试使用这个api token往DNS服务器写入内容,如果能写入成功,则说明www.xxx.com站点是你的,就会审核通过。
- 适用于wildcard certificate
- 需要dns resolver支持提供API token
使用cloudflare作为dns resolver
获取cloudflare的API Token
-
公网域名注册可以使用Cloudflare,拿到cloudflare的API token。
-
测试API Token:
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-
letsencrypt通过我们创建的API令牌就可以往我们的cloudflare DNS服务器里写入内容,如果成功写入内容,则证明站点是我们的,则审核通过。
-
写入k8s secret
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
type: Opaque
stringData:
api-token: <API Token>
创建cluster issuer
- 创建ClusterIssuer时提供cloudflare的token,这样cert-manager可以自动化向域名机构添加CA要求的认证信息,完成证书申请和renew。
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
#ClusterIssuer名字
name: letsencrypt-dns01
spec:
acme:
#指定一个
privateKeySecretRef:
name: letsencrypt-dns01
#ClusterIssuer去https://acme-v02.api.letsencrypt.org/directory申请证书
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
# clusterissuer支持的resolver类型有限,必须选一种dns server来配置。
# 通过命令查看:k explain clusterissuer.spec.acme.solvers.dns01
cloudflare:
email: xxxxx
#指定存储着API token的secret,secret的名字为cloudflare-api-token-secret,secret的key为api-token
apiTokenSecretRef:
key: api-token
name: cloudflare-api-token-secret
申请证书
- 创建certificate,指定ClusterIssuer和要保存到哪个secret,完成证书的签发。(注意Let's Encrypt 一个星期内只为同一个域名颁发 5 次证书,todoit.tech 和 whoami.todoit.tech 被视为不同的域名。)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
#cert-zheli-com是申请的证书名字
name: cert-zheli-com
spec:
dnsNames:
#www.rengshengdezheli.xyz表示申请的证书只给www.rengshengdezheli.xyz用
- www.rengshengdezheli.xyz
issuerRef:
kind: ClusterIssuer
#name: letsencrypt-dns01表示使用哪个ClusterIssuer申请证书
name: letsencrypt-dns01
#secretName: cert-zheli-com-tls表示申请到的证书放在哪个secret里面
secretName: cert-zheli-com-tls
- 查看相关资源
kubectl get certificate -o wide
kubectl get secrets -o wide
kubectl get certificaterequests.cert-manager.io -o wide
#查看challenges,challenges用来验证证书请求是否成功,当证书申请成功之后,challenges会消失,certificaterequests的READY状态变为True。
kubectl get challenges.acme.cert-manager.io -o wide
#secret由xxxx-btt2t变为xxxx-tls,现在证书就在xxxx-tls里面了。
创建ingress挂载证书
- 通过ingress manifest配置secretName的方式,让ingress找到证书:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
ingressClassName: nginx-default
tls:
- hosts:
- www.rengshengdezheli.xyz
secretName: cert-zheli-com-tls
rules:
- host: www.rengshengdezheli.xyz
http:
paths:
#访问网址目录
- path: /
pathType: Prefix
backend:
service:
name: nginx1svc
port:
number: 80
- 创建业务pod和svc,例如:
# 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
配置clusterissuer自动挂载证书
-
我们申请证书的步骤是创建certificate,然后clusterissuer使用certificate申请证书,最后带有certificate信息的secret和ingress一起使用。
-
我们现在使用另外一种方法,创建ingress的时候自动让clusterissuer申请证书,不用创建certificate yaml文件。
-
添加cert-manager.io/cluster-issuer: "letsencrypt-dns01"表示使用名为letsencrypt-dns01的clusterissuer申请证书。
当我们创建ingress规则之后,会自动使用名为letsencrypt-dns01的clusterissuer申请证书,申请的证书放在名为cert-zheli-com-tls的secret里
- 证书到期之后,clusterissuers会自动续约。
自动续约
为配置certificate自动续约,可以给ingress添加annotation
- ingress支持的annotation:Supported Annotations
- How it works:Ingress - How it works
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-dns01"
spec:
ingressClassName: nginx-default
tls:
- hosts:
- www.rengshengdezheli.xyz
secretName: cert-zheli-com-tls
rules:
- host: www.rengshengdezheli.xyz
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx1svc
port:
number: 80
配置wildcard证书
一个父域里面有很多个子域,直接用一个wildcard certificate给父域申请证书。
子域的ingress资源不再需要额外单独配置cert-manager,自动沿用父域的certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: private-wildcard-certificate-onepilot-tls
namespace: cert-manager
spec:
secretName: private-wildcard-certificate-onepilot-tls
secretTemplate:
annotations:
kubed.appscode.com/sync: "kubernetes.io/metadata.name in (external-nginx,rabbit)"
dnsNames:
- "dev.onepilot.azurecn.autoheim.net"
- "*.onepilot.azurecn.autoheim.net"
issuerRef:
name: letsencrypt
kind: ClusterIssuer
keystores:
pkcs12:
create: true
passwordSecretRef: # Password used to encrypt the keystore
key: password
name: pkcs12-password-secret
使用azure dns作为dns resolver
cert-manager官网教程:Getting Started with AKS and Let's Encrypt
- cluster issuer中的dns配置为azureDNS
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: dl_team_xxx@xxxx.com
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- dns01:
azureDNS:
managedIdentity:
clientID: {{ .Values.workload_identity_client_id }}
subscriptionID: {{ .Values.dns.subscription_id }}
resourceGroupName: {{ .Values.dns.resource_group_name }}
hostedZoneName: {{ .Values.dns.domain }}
environment: {{.Values.dns.environment}}
- cert-manager本体的配置中,podLabel和service account都要配置workload identity
podLabels:
azure.workload.identity/use: "true"
serviceAccount:
labels:
azure.workload.identity/use: "true"
annotations:
azure.workload.identity/client-id:
Staging Server
对于azureDNS,cert-manager定义了一个staging server用于前期测试,避免对于一个domain用尽cert的quota:
参考:
- cert-manager教程:Create a ClusterIssuer for Let's Encrypt Staging
- letsencrypt教程:Staging Environment
流程如下:
- 先在cluster issuer中配置连接到letsencrypt staging server
- 创建出来的certificate是staging签发的证书,是insecure的
- web server先用这个insecure证书,测试流程都是通的。
- 再在cluster issuer中配置连接到letsencrypt prod server,签发正式证书
Troubleshooting
- 本地虚机中有时候操作certificate时会报错:
Error from server (InternalError): Internal error occurred: failed calling webhook "webhook.cert-manager.io": failed to call webhook: Post "https://cert-manager-webhook.cert-manager.svc:443/validate?timeout=30s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2024-12-23T02:30:04Z is after 2024-12-20T02:19:54Z
删掉cert-manager-webhook-86b8dc6c77-hczkm这个pod重建就好了
- cert-manager-config中部署的certificate会自动帮你创建secrets,但是经历certificate重建之后,关联的secret并不会被删除,需要手动删除掉之后,会自动重建。
helm管理配置文件
-
cluster issuer、certificate等yaml文件都可以用helm去管理。
-
创建cert-manager-config目录,下面放Chart.yaml和values.yaml
tee Chart.yaml <<"EOF"
apiVersion: v2
name: commoninfra-cert-manager-config
description: A Helm chart for cert manager configurations
type: application
version: 0.0.1
appVersion: "0.0.1"
EOF
-
创建template目录,里面放所有需要创建的yaml文件
-
安装
helm upgrade -i commoninfra-cert-manager-config . --values ./values.yaml