
cert-manager 是一個雲原生證書管理開源項目,用於在 Kubernetes 集群中提供 HTTPS 證書並自動續期,支持 Let’s Encrypt, HashiCorp Vault 這些免費證書的簽發。在Kubernetes集群中,我們可以通過 Kubernetes Ingress 和 Let’s Encrypt 實現外部服務的自動化 HTTPS。
1、前置條件
在Kubernetes集群中使用 HTTPS 協議,需要一個證書管理器、一個證書自動簽發服務,主要通過 Ingress 來發布 HTTPS 服務,因此需要Ingress Controller並進行配置,啟用 HTTPS 及其路由。
本文環境:
- k8s v1.17.0
- Ingress Controller為nginx,且有對應暴露的公網ip地址
2、部署cert-manager
從cert-manager v0.11.0開始,Kubernetes的最低支持版本是v1.12.0。仍在運行Kubernetes v1.11或更低版本的用戶應在安裝cert-manager之前升級到受支持的版本。
cert-manager可以通過官方yaml安裝或者通過helm快速安裝,本文記錄通過官方yaml安裝的過程
2.1、創建一個namespace
# namespace.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
或者
kubectl create namespace cert-manager
2.2、安裝cert-manager
官方的yaml地址為
https://github.com/jetstack/cert-manager/releases/download/v0.13.1/cert-manager.yaml
yaml中有三個鏡像,分別為
- cert-manager-controller:v0.13.1
- cert-manager-cainjector:v0.13.1
- cert-manager-webhook:v0.13.1
默認是從quay.io
獲取鏡像,如果quay.io
的鏡像無法獲取,修改image
為國內源,例如Azure
中國的地址quay.azk8s.cn
部署,會在集群中創建一系列的crd
資源
# kubectl apply -f cert-manager.yaml
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
namespace/cert-manager unchanged
serviceaccount/cert-manager-cainjector created
serviceaccount/cert-manager created
serviceaccount/cert-manager-webhook created
clusterrole.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-cainjector created
role.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-webhook:auth-delegator created
rolebinding.rbac.authorization.k8s.io/cert-manager-webhook:webhook-authentication-reader created
clusterrole.rbac.authorization.k8s.io/cert-manager-webhook:webhook-requester created
role.rbac.authorization.k8s.io/cert-manager:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager:leaderelection created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrole.rbac.authorization.k8s.io/cert-manager-view created
clusterrole.rbac.authorization.k8s.io/cert-manager-edit created
service/cert-manager created
service/cert-manager-webhook created
deployment.apps/cert-manager-cainjector created
deployment.apps/cert-manager created
deployment.apps/cert-manager-webhook created
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
檢查對應的pod狀態
# kubectl get pods -n cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-5cbcb9f4f5-7k6j4 1/1 Running 0 90s
cert-manager-cainjector-8df55567d-bspds 1/1 Running 0 90s
cert-manager-webhook-5d9c55bb4c-tmlck 1/1 Running 0 88s
2.3、測試
在正式使用前,先通過官方的示例做一個測試來確認正確設置了cert-manager
並能夠頒發基本證書類型
測試Webhook
正常工作
# cat <<EOF > test-resources.yaml
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager-test
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: test-selfsigned
namespace: cert-manager-test
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: selfsigned-cert
namespace: cert-manager-test
spec:
dnsNames:
- example.com
secretName: selfsigned-cert-tls
issuerRef:
name: test-selfsigned
EOF
創建測試資源
# kubectl apply -f test-resources.yaml
namespace/cert-manager-test created
issuer.cert-manager.io/test-selfsigned created
certificate.cert-manager.io/selfsigned-cert created
查新創建證書的狀態。可能需要等待幾秒鍾,然后cert-manager
才能處理證書請求
# kubectl -n cert-manager-test describe certificate selfsigned-cert
...
Spec:
Dns Names:
example.com
Issuer Ref:
Name: test-selfsigned
Secret Name: selfsigned-cert-tls
Status:
Conditions:
Last Transition Time: 2020-03-05T10:01:06Z
Message: Certificate is up to date and has not expired
Reason: Ready
Status: True
Type: Ready
Not After: 2020-06-03T10:01:06Z
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal GeneratedKey 61s cert-manager Generated a new private key
Normal Requested 61s cert-manager Created new CertificateRequest resource "selfsigned-cert-504566127"
Normal Issued 61s cert-manager Certificate issued successfully
清理測試資源
# kubectl delete -f test-resources.yaml
namespace "cert-manager-test" deleted
issuer.cert-manager.io "test-selfsigned" deleted
certificate.cert-manager.io "selfsigned-cert" deleted
3、創建clusterissuer
為了配置cert-manager
以開始頒發證書,必須先創建Issuer
或ClusterIssuer
資源。這些資源代表特定的簽名機構,並詳細說明如何滿足證書請求。Issuer
只能用來簽發自己所在namespace
下的證書,ClusterIssuer
可以簽發任意namespace
下的證書,這里以ClusterIssuer
為例創建一個簽發機構
# cat clusterissuer.yaml
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ssgeek@ssgeek.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
說明:
- metadata.name 創建的簽發機構的名稱,創建證書的時候會引用
- spec.acme.email 郵箱,證書快過期的時候會有郵件提醒,不過
cert-manager
會利用acme
協議自動給我們重新頒發證書來續期 - spec.acme.server acme 協議的服務端,由官方給出
- spec.acme.privateKeySecretRef 指示此簽發機構的私鑰將要存儲到哪個
Secret
對象中 - pec.acme.solvers.http01 指示簽發機構使用
HTTP-01
的方式進行acme
協議 (還可以用DNS
方式,acme
協議的目的是證明這台機器和域名都是屬於你的,然后才准許給你頒發證書)
4、為域名創建certificate
這里通過一個我自己的域名blog.ssgeek.com
來進行測試,此域名已經修改dns
為公網地址
# cat certificate.yaml
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: blog
namespace: default
spec:
secretName: blog-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
duration: 2160h
renewBefore: 360h
keyEncoding: pkcs1
dnsNames:
- blog.ssgeek.com
說明:
- spec.secretName 指示證書最終存到哪個 Secret 中
- spec.issuerRef.kind 值為 ClusterIssuer 說明簽發機構不在本 namespace 下,而是在全局
- spec.issuerRef.name 我們創建的簽發機構的名稱 (ClusterIssuer.metadata.name)
- spec.duration 證書過期時間
- spec.renewBefore 在過期前自動更新
- spec.dnsNames 指示該證書的可以用於哪些域名
- 更多選項可以參照官方文檔
創建並檢查相應資源
# kubectl apply -f certificate.yaml
certificate.cert-manager.io/blog created
# kubectl get certificate
NAME READY SECRET AGE
blog True blog-tls 36s
# kubectl get secrets |grep blog-tls
blog-tls kubernetes.io/tls 3 52s
# kubectl describe secrets blog-tls
Name: blog-tls
Namespace: default
Labels: <none>
Annotations: cert-manager.io/alt-names: blog.ssgeek.com
cert-manager.io/certificate-name: blog
cert-manager.io/common-name: blog.ssgeek.com
cert-manager.io/ip-sans:
cert-manager.io/issuer-kind: ClusterIssuer
cert-manager.io/issuer-name: letsencrypt-prod
cert-manager.io/uri-sans:
Type: kubernetes.io/tls
Data
====
ca.crt: 0 bytes
tls.crt: 3558 bytes
tls.key: 1675 bytes
在創建時查看cert-namager
的日志
# kubectl -n cert-manager logs -f cert-manager-5cbcb9f4f5-4kks2
...
I0305 05:50:13.817322 1 controller.go:129] cert-manager/controller/certificates "msg"="syncing item" "key"="default/blog"
I0305 05:50:14.317351 1 conditions.go:155] Setting lastTransitionTime for Certificate "blog" condition "Ready" to 2020-03-05 05:50:14.317341236 +0000 UTC m=+2213.785243238
I0305 05:50:14.525738 1 controller.go:135] cert-manager/controller/certificates "msg"="finished processing work item" "key"="default/blog"
I0305 05:50:14.525812 1 controller.go:129] cert-manager/controller/certificates "msg"="syncing item" "key"="default/blog"
I0305 05:50:14.526251 1 sync.go:367] cert-manager/controller/certificates "msg"="no existing CertificateRequest resource exists, creating new request..." "related_resource_kind"="Secret" "related_resource_name"="blog-tls" "related_resource_namespace"="default" "resource_kind"="Certificate" "resource_name"="blog" "resource_namespace"="default"
I0305 05:50:14.774094 1 controller.go:129] cert-manager/controller/certificaterequests-issuer-ca "msg"="syncing item" "key"="default/blog-109727931"
I0305 05:50:14.774118 1 controller.go:129] cert-manager/controller/certificaterequests-issuer-selfsigned "msg"="syncing item" "key"="default/blog-109727931"
I0305 05:50:14.774135 1 sync.go:379] cert-manager/controller/certificates "msg"="created certificate request" "related_resource_kind"="Secret" "related_resource_name"="blog-tls" "related_resource_namespace"="default" "resource_kind"="Certificate" "resource_name"="blog" "resource_namespace"="default" "request_name"="blog-109727931"
如果創建出來的certificate
狀態為False
,可以通過以下命令查看相關信息
# kubectl get challenge
如果有相應的challenge
,通過kubectl describe
檢查,例如我這里之前創建失敗時檢查的錯誤信息如下
出現此問題的原因是我把此域名的解析設置為了內網地址,官方的頒發證書機構接口地址無法訪問到,因此必須解析在公網,並保證服務暴露在公網
5、在ingress中引用對應的secret
生成的證書最終綁定在對應的域名服務下,這里我運行了一個nginx pod
,創建了對應的service
和ingress
資源,在ingress
資源中聲明了此secret
,由於部署了cert-maganer
,在ingress
中,還支持更多的注解,可以參考官方文檔
yaml
內容如下
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.15
imagePullPolicy: IfNotPresent
name: nginx
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
selector:
app: nginx
ports:
- name: nginx
port: 80
targetPort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: default
annotations:
kubernietes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- blog.ssgeek.com
secretName: blog-tls
rules:
- host: blog.ssgeek.com
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80
然后通過域名訪問,檢查證書是否正常
6、自動化頒發證書
上述內容是通過根據域名創建certificate
最終得到的簽名證書,再配置到ingress
中使用,還不夠自動化。沒錯,其實官方給出了自動通過ClusterIssuer
頒發證書的做法,只需要在ingress
中添加相應注解即可
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: default
annotations:
kubernietes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
kubernetes.io/tls-acme: "true"
spec:
tls:
- hosts:
- blog.ssgeek.com
secretName: blog-tls
rules:
- host: blog.ssgeek.com
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80
創建ingress
資源,就會發現自動創建了certificate
,得到secret
瀏覽器訪問,出現307
的http
臨時重定向到https
,也可以繼續添加一個注解強制進行強制重定向
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: default
annotations:
kubernietes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
kubernetes.io/tls-acme: "true"
spec:
tls:
- hosts:
- blog.ssgeek.com
secretName: blog-tls
rules:
- host: blog.ssgeek.com
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80