k8s 上利用 cert-manager 自动签发 TLS 证书

1493 字
8 分钟
...阅读
...评论
天气晴
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 提供了IssuerClusterIssuer 两种类型的签发机构,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.netingress 上,测试 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

删除之前手动创建的 DeploymentServiceIngressSecret 后, 应用 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 了,不少老教程里面仍然是前者,需要改为后者才能正常执行。

参考链接

评论区
Copyright © Bean Deng