[快速入门] cert-manager

是用来做什么的

用来签发证书的。

解决了什么问题

TLS 协议在集群内部组件通信以及对外暴露服务时大量使用,伴随着 TLS 证书管理的复杂性。cert-manager 提供了一个集中式的全自动证书管理服务。

典型应用场景

ingress

ingress 可以对外提供七层的 http 和 https 服务,cert-manager 可以自动为 https 服务配置 TLS 证书。

基础概念

证书存储位置

TLS 证书一般以 secret 的形式存储在集群中,可以通过类型 kubernetes.io/tls 来识别

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
> k get secret -A | grep kubernetes.io/tls
argocd                             argocd                        kubernetes.io/tls 
cattle-provisioning-capi-system    capi-webhook-service-cert     kubernetes.io/tls 
cattle-system                      cattle-webhook-ca             kubernetes.io/tls 
cattle-system                      cattle-webhook-tls            kubernetes.io/tls 
cattle-system                      rancher-tls-letsencrypt       kubernetes.io/tls 
cattle-system                      serving-cert                  kubernetes.io/tls 
cattle-system                      tls-ca                        kubernetes.io/tls 
cattle-system                      tls-rancher                   kubernetes.io/tls 
cattle-system                      tls-rancher-ingress           kubernetes.io/tls 
cattle-system                      tls-rancher-internal          kubernetes.io/tls 
cattle-system                      tls-rancher-internal-ca       kubernetes.io/tls 
cert-manager                       argocd-tls                    kubernetes.io/tls 
cert-manager                       codeme-wildcard               kubernetes.io/tls 
derper                             derper-tls                    kubernetes.io/tls 
kube-system                        k3s-serving                   kubernetes.io/tls 

公钥和私钥分别以文本 (pem 格式) 的形式存储在 secret 资源中

1
2
3
4
5
6
7
8
9
> k describe secret argocd -n argocd
Name:         argocd
Namespace:    argocd
Type:  kubernetes.io/tls

Data
====
tls.crt:  3582 bytes
tls.key:  1679 bytes

怎么获取证书

cert-manager 支持多种方式获取证书,详情可见 官方文档

下面介绍一下通过 ACME 的方式获取 Let’s encrypt 证书

certbotacme.sh 等工具的原理是一样的。

简单来说,首先,我们会向 Let’s encrypt 的服务器 (CA) 请求:“我想要 example.com 这个域名的证书。”

接着 CA 变会给我发出 challenge,如果我们能够完成 challenge,便可以证明我们对这个域名的所有权,可以顺利签发证书。

常见的 challenge 有两种:http-01dns-01

前者需要我们有一个服务器,并对公网提供 http 服务。CA 会给我们一个特定的文件,让我们放在一个特定的目录下。如果 CA 能够通过我们的域名访问到上述文件,即完成验证。

后者需要我们在域名的 DNS 中添加一条特定的 TXT 记录,CA 检测到此记录,即完成验证。

在当前集群全自动管理的环境下,我推荐使用 http-01 的方式进行校验。因为 cert-manager 本身就支持使用 ingress 全自动完成认证,可以给每一个服务都单独签发一张证书,因此泛域名证书变得没必要了(泛域名证书只支持 dns-01 验证)。同时,不使用 dns 的方式,还避免了管理 dns 提供商的密钥带来的负担。

基础概念

cert-manager 也引入了一系列的 crd,下面选几个常用的进行讲解

Issuers

配置怎么签发证书,也就是 CA。类似于 StorageClass。

ClusterIssuers 

和前者类似,但是前者的有效范围仅限于当前 namespace,而这个范围的是整个集群。

CertificateRequests 

证书请求,类似于 PVC。声明需要证书的域名和使用的Issuers。

Certificates 

证书,类似于 PV。定义了关联的域名和存储的 secret。

安装

参考 官方文档

1
2
3
4
5
6
7
8
helm repo add jetstack https://charts.jetstack.io --force-update

helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.16.1 \
  --set crds.enabled=true

配置前提醒

一下内容都建立的你已经有了一个域名,并且通过这个域名,可以访问到你的集群的 80 和 443 端口的前提下。

配置 Issuers

可以参考 文档

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: user@example.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik

一般就用这个就够了,记得最后 ingressClassName 要换成自己用的

部署临时服务

出于测试的目的,我们现在起一个 nginx 服务器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginix
  name: nginix
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginix
  strategy: {}
  template:
    metadata:
      labels:
        app: nginix
    spec:
      containers:
      - image: nginx:latest
        name: nginx
        resources: {}
status: {}
---
apiVersion: v1
kind: Service
metadata:
  name: nginix-service
spec:
  selector:
    app: nginix
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginix-ingress
spec:
  rules:
  - host: nginix.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginix-service
            port:
              number: 80

访问对应的域名,出现 nginx 的欢迎页面

image.png

配置自动 TLS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginix-ingress
  annotations:
    cert-manager.io/issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - nginix.example.com
    secretName: nginix-tls
  rules:
  - host: nginix.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginix-service
            port:
              number: 80

接着更新 ingress,加上这一段,注意这些选项一个都不能少,不然 cert-manager 可能不会检测到

1
2
3
4
5
6
7
8
9
metadata:
  name: nginix-ingress
  annotations:
    cert-manager.io/issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - nginix.example.com
    secretName: nginix-tls

稍等片刻,就可以看到创建的证书了

1
2
3
> k get secret
NAME                                   TYPE                 DATA   AGE
nginix-tls                             kubernetes.io/tls    2      106s

此时再打开页面,页面已经自动启用了 https 了

image.png

直接创建证书

cert-manager 也支持直接创建证书。

使用方法是直接创建一个证书请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-cert
  namespace: default
spec:
  secretName: example-tls
  issuerRef:
    name: letsencrypt-prod
    kind: Issuer
  dnsNames:
    - example.com

证书便会直接保存在 example-tls 这个 secret 内

挂载证书到应用

有的时候,我们需要应用直接使用证书,所以还需要把证书挂载到容器内部

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: apps/v1
kind: Deployment
metadata:
  name: example
spec:
  replicas: 1
  selector:
  template:
    spec:
      containers:
        - image: nginx
          name: example
          volumeMounts:
            - name: example-certs
              mountPath: /app/certs
          ports:
            - containerPort: 80
              protocol: TCP
            - containerPort: 443
              protocol: TCP
      restartPolicy: Always
      volumes:
        - name: example-certs
          secret:
            secretName: example-tls  
            items:
              - key: tls.crt
                path: example.com.crt
              - key: tls.key
                path: example.com.key

可以使用这样的方式挂载

TLS 直通

如果我们的应用需要共享主机的 443 端口,但是又不是提供 http 服务,可以使用 SNI 进行分流,然后使用 TLS Passthrough 直接转发到其他后端,具体可以使用 IngressRouteTCP 实现

Licensed under CC BY-NC-SA 4.0