1. 程式人生 > 其它 >Kubernetes 監控:CertManager 自動 HTTPS

Kubernetes 監控:CertManager 自動 HTTPS

cert-manager 是一種自動執行證書管理的工具,它可以與 Istio Gateway 整合以管理 TLS 證書,當然也可以很方便地和前面我們配置的 ingress-nginx 或者 traefik 配合使用。對於和 Istio 的整合使用,無需特殊配置即可。

在進行本節實驗之前記得將前面章節的實驗內容進行清空。

安裝

要在 Kubernetes 叢集上安裝 cert-manager 也非常簡單,官方提供了一個單一的資源清單檔案,包含了所有的資源物件,所以直接安裝即可:

# Kubernetes 1.15+
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.1/cert-manager.yaml

# Kubernetes <1.15
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.1/cert-manager-legacy.yaml

上面的命令會建立一個名為 cert-manager 的名稱空間,安裝大量的 CRD 以及 AdmissionWebhook 物件,可以通過如下命令來檢視是否安裝成功:

$ kubectl get pods --namespace cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-7ddc5b4db-56nln               1/1     Running   0          4m46s
cert-manager-cainjector-6644dc4975-zthcj   1/1     Running   0          4m47s
cert-manager-webhook-7b887475fb-9fbp4      1/1     Running   0          4m45s

正常情況下可以看到 cert-manager、cert-manager-cainjector 以及 cert-manager-webhook 這幾個 Pod 處於 Running 狀態。我們可以通過下面的測試來驗證下是否可以簽發基本的證書型別,建立一個 Issuer 資源物件來測試 webhook 工作是否正常(在開始簽發證書之前,必須在群集中至少配置一個 Issuer 或 ClusterIssuer 資源):

$ 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

這裡我們建立了一個名為 cert-manager-test 的名稱空間,建立了一個 Issuer 的證書頒發機構,然後使用這個 Issuer 來建立一個證書 Certificate 物件,直接建立上面的資源清單即可:

$ 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 describe certificate -n cert-manager-test
Name:         selfsigned-cert
Namespace:    cert-manager-test
......
Spec:
  Dns Names:
    example.com
  Issuer Ref:
    Name:       test-selfsigned
  Secret Name:  selfsigned-cert-tls
Status:
  Conditions:
    Last Transition Time:  2020-08-16T01:50:40Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2020-11-14T01:50:39Z
  Not Before:              2020-08-16T01:50:39Z
  Renewal Time:            2020-10-15T01:50:39Z
  Revision:                1
Events:
  Type    Reason     Age   From          Message
  ----    ------     ----  ----          -------
  Normal  Issuing    64s   cert-manager  Issuing certificate as Secret does not exist
  Normal  Generated  64s   cert-manager  Stored new private key in temporary Secret resource "selfsigned-cert-25ftw"
  Normal  Requested  64s   cert-manager  Created new CertificateRequest resource "selfsigned-cert-p2sbx"
  Normal  Issuing    63s   cert-manager  The certificate has been successfully issued

從上面的 Events 事件中我們可以證書已經成功簽發了,到這裡證明我們的 cert-manager 已經安裝成功了。而且我們需要注意的是 cert-manager 的功能非常強大,不只是可以支援 ACME 型別的證書籤發,還支援其他眾多的型別,比如 SelfSigned(自簽名)、CA、Vault、Venafi、External、ACME,只是我們一般主要是使用 ACME 來幫我們生成自動化的證書。

環境配置

由於通過 ACME 做自動化證書的時候,需要暴露 80 和 443 埠,當然如果我們使用 DNS 校驗方式也可以,但是有時候我們根本就沒有域名的情況下想要實現自動化證書,我們可以使用 xip.io 這類的服務來實現。前面我們部署 istio-ingressgateway 的時候是通過 NodePort 類暴露的服務,所以我們需要在前面加一個 LB 來轉發下請求。這裡為了簡單,我直接使用 haproxy 來監聽節點的 80 和 443 埠,將請求轉發到後端的 NodePort 埠。

首先安裝 haproxy:

$ yum install -y haproxy

然後配置 haproxy,配置檔案 /etc/haproxy/haproxy.cfg,內容如下所示:

listen stats
  bind    *:9000
  mode    http
  stats   enable
  stats   hide-version
  stats   uri       /stats
  stats   refresh   30s
  stats   realm     Haproxy\ Statistics
  stats   auth      Admin:Password

frontend istio-https
    bind *:443
    mode tcp
    option tcplog
    tcp-request inspect-delay 5s
    tcp-request content accept if { req.ssl_hello_type 1 }
    default_backend istio-https-svc

backend istio-https-svc
    mode tcp
    option tcplog
    option tcp-check
    balance roundrobin
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
    server istio-http-svc-1 127.0.0.1:30951 check

frontend istio-http
    bind *:80
    mode tcp
    option tcplog
    default_backend istio-http-svc

backend istio-http-svc
    mode tcp
    option tcplog
    option tcp-check
    balance roundrobin
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
    server istio-http-svc-1 127.0.0.1:32193 check

其中的 32193 與 30951 埠是 istio-ingressgateway 的 NodePort 埠:

$ kubectl get svc istio-ingressgateway -n istio-system
NAME                   TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
istio-ingressgateway   NodePort   10.102.120.128   <none>        15020:31093/TCP,80:32193/TCP,443:30951/TCP,31400:31871/TCP,15443:31367/TCP   68d

配置完成後直接啟動 haproxy 即可:

$ sudo systemctl start haproxy
$ sudo systemctl enable haproxy
$ sudo systemctl status haproxy

然後我們可以通過上面 9000 埠監控我們的 haproxy 的執行狀態:

與 Istio 整合

這裡我們來通過 cert-manager 為前面的 httpbin 應用配置自動的 HTTPS,首先單獨建立一個使用者 cert-manager 的自定義的 Gateay,如下所示:

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: acme-gateway
  labels:
    app: ingressgateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - "*.xip.io"
    port:
      number: 80
      name: http
      protocol: HTTP
    # tls:
    #   httpsRedirect: true
  - hosts:
    - "*.xip.io"
    port:
      name: https
      number: 443
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: "xip-cert"
---
EOF
gateway.networking.istio.io/acme-gateway created

需要注意的是在配置 Gateway 的時候 tls 的 credentialName 代表的是 cert-manager 自動生成的證書名稱。

接下來建立 VirtulService 物件,這裡我們使用 https 方式的 ACME 證書校驗方式,除了 http 方式之外還有 tls 與 dns 方式的校驗,dns 方式的證書校驗支援萬用字元的域名。所以我們需要為 /.well-known/ 這個 PATH 路徑做一個正確的配置,方便進行 http 校驗:

$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  gateways:
  - acme-gateway
  hosts:
  - httpbin.123.59.188.12.xip.io
  http:
  - route:
    - destination:
        host: httpbin
        port:
          number: 8000
EOF
virtualservice.networking.istio.io/httpbin created

部署完成後我們可以通過訪問 http://httpbin.123.59.188.12.xip.io 來驗證是否已經部署成功:

接下來創兩個 ClusterIssuer,一個用於測試,一個用於正式使用:

$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
    - http01:
        ingress:
          class: istio  
EOF
clusterissuer.cert-manager.io/letsencrypt-staging created
$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1beta1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: istio  
EOF
clusterissuer.cert-manager.io/letsencrypt-prod created
$ kubectl describe clusterissuer                
......
Status:
  Acme:
    Last Registered Email:  [email protected]
    Uri:                    https://acme-v02.api.letsencrypt.org/acme/acct/94057742
  Conditions:
    Last Transition Time:  2020-08-16T07:37:40Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

然後建立一個 Certificate 物件來獲取證書,由於 Istio 需要在它的名稱空間下面有證書,所以我們需要在 istio-system 這個名稱空間下面建立:

$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1beta1
kind: Certificate
metadata:
  name: httpbin-cert
  namespace: istio-system  # 這裡必須是 istio-system 空間
spec:
  secretName: xip-cert  # 這個就是上面 gateway 所配置的證書名稱
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-staging
  commonName: httpbin.123.59.188.12.xip.io
  dnsNames:
  - httpbin.123.59.188.12.xip.io
EOF
certificate.cert-manager.io/httpbin-cert created

建立完成後這時候檢視 istio-system 空間應該會有一個 cm-acme-http-solver- 這個開頭的 Pod、Servie、Ingress 資源物件:

$ kubectl get pods -n istio-system
NAME                                    READY   STATUS    RESTARTS   AGE
cm-acme-http-solver-8tpbn               1/1     Running   0          16s
$ kubectl get svc -n istio-system
NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
cm-acme-http-solver-w6sf7   NodePort    10.106.174.20    <none>        8089:32135/TCP
$ kubectl get ingress -n istio-system
NAME                        HOSTS                          ADDRESS   PORTS   AGE
cm-acme-http-solver-jkrs2   httpbin.123.59.188.12.xip.io             80      45s

隔一小會兒去檢視上面部署的 Certificate 物件的狀態:

$ kubectl describe certificate httpbin-cert -n istio-system
......
Events:
  Type    Reason     Age    From          Message
  ----    ------     ----   ----          -------
  Normal  Issuing    13m    cert-manager  Issuing certificate as Secret does not exist
  Normal  Generated  13m    cert-manager  Stored new private key in temporary Secret resource "http-cert-6cml8"
  Normal  Requested  13m    cert-manager  Created new CertificateRequest resource "http-cert-9wcg2"
  Normal  Issuing    6m17s  cert-manager  The certificate has been successfully issued

看到最後的資訊 The certificate has been successfully issued 證明我們的證書獲取成功了,但是這個時候如果我們通過 https 去訪問的話還是會提示證書錯誤的,因為我們獲取的是 staging 環境的證書:

這個時候我們重新更新 httpbin-cert 這個 Certificate 物件中引用的 issuer,更改為正式環境的 issuer,或者使用正式的簽名機構新建一個證書物件,正常就可以獲得線上環境的證書了。