前
这一篇也是基础设施的部署记录。cert-manager用于集群的 ingress https 证书的管理。
他可以自动的完成 证书的签发,然后配合 ingressroute就可以很简单的进行证书的部署。更加适用于K8s 的体系。对比于 nginxproxy manager 这种,虽然也是使用 acme 来进行 https 的证书签发,其无法与集群进行很好的结合。以及证书的管理需要存储在硬盘上,无法很好的兼容 K8 的模式。
完成部署后,可以直接在内网使用 https访问,提升整体的系统美感。后续使用 proxy 进行服务暴露时候也更加的优雅。
这篇记录实际部署以及操作流程。
Cert-manager安装
安装部份直接使用 helm 来进行配置。之前对 helm 还有些抵触,觉得降低了自己的定制性。但是逐渐发现通过 values 也提供了 很好的定制性。而且由于资源是打包的,有更好的整体性。换个方式也可以理解为 K8s 下的 apt
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
安装过程很简单,导入 charts,安装 CRD,然后安装整个 chart
helm repo add jetstack https://charts.jetstack.io
helm repo update
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml
helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.13.2 --values=values.yaml
这里一些参数需要自定义,values 的内容如下。因为 CRD 已经手动安装,所以fasle,这里的PodDNS 配置比较重要,因为需要让他使用 我们的 resolver 的DNS 直接进行DNS 查询,如果使用 本地网路的 LDNS 可能会出现查询缓存,甚至是 TXT 记录查询不到的问题。
installCRD: false
replicaCount: 2
extraArgs:
- --dns01-recursive-nameservers=1.1.1.1:53,9.9.9.9:53,8.8.8.8:53
- --dns01-recursive-nameservers-only
podDnsPolicy: None
podDnsConfig:
nameservers:
- "1.1.1.1"
- "9.9.9.9"
- "8.8.8.8"
ISSUER 配置
issuer 就是我们证书的签发方。我们配置好之后就可以用它来进行配置的签发
CF-token-secret
我们通过Cloudflare 作为我们的 DNS 托管商,在签发证书的时候需要通过 acme 的TXT记录来进行 所有权限的检验。
这个token是 用来操作CF自动新增acme 的TXT 记录所使用。所以需要有DNS edit 的权限。
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-token-secret
namespace: cert-manager
type: Opaque
stringData:
cloudflare-token: "1234"
ISSUER
Issuer 就是我们用来颁发证书的配置,这里我们创建两个,一个是用于生产环境的。 一个是 staging,用于预生产的测试。因为letsencrypt的接口会有限频。如果配置有问题的情况下,可能会导致被 429 导致长时间的等待。所以 这里就直接使用 staging 来用于验证,没问题后切换到 生产。
具体配置可以参考 API文档
Staging
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: "[email protected]"
privateKeySecretRef:
name: letsencrypt-production
solvers:
- dns01:
cloudflare:
email: "[email protected]"
apiTokenSecretRef:
name: cloudflare-token-secret
key: cloudflare-token
selector:
dnsZones:
- "r4y.site"
Production
apiVersion: cert-manager.io/v1
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:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-token-secret
key: cloudflare-token
selector:
dnsZones:
- "r4y.site"
签发证书
有了前面的 issuer 配置之后,就可以创建证书资源来获取证书了。配置也很简单,指定 issuer用于 证书签发,然后配置 commonName和后面的子域名。
# 这个是 Staging 的
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: local-r4y
namespace: default
spec:
secretName: local-r4y
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
commonName: "*.local.r4y.site"
dnsNames:
- "local.r4y.site"
- "*.local.r4y.site
---
# Production 的配置
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: local-r4y-prod
namespace: default
spec:
secretName: local-r4y-prod
issuerRef:
name: letsencrypt-production
kind: ClusterIssuer
commonName: "*.local.r4y.site"
dnsNames:
- "local.r4y.site"
- "*.local.r4y.site"
Apply 之后可以看到创建了 Challenge 的资源,等待完成 challenge的过程。
➜ issuers git:(main) ✗ kubectl get challenges.acme.cert-manager.io -A -o wide
NAMESPACE NAME STATE DOMAIN REASON AGE
default local-r4y-prod-1-2800136040-3102289587 local.r4y.site 2m37s
default local-r4y-prod-1-2800136040-519127018 valid local.r4y.site Successfully authorized domain 2m37s
Challenging 的过程我们可以通过log看到,等待大概5分钟左右,完成证书签发。
kubectl logs -n cert-manager cert-manager-57c9f949b7-2vkx4
...
I1211 13:48:56.816111 1 trigger_controller.go:215] "cert-manager/certificates-trigger: Certificate must be re-issued" key="default/local-r4y" reason="DoesNotExist" message="Issuing certificate as Secret does not exist"
I1211 13:48:56.816245 1 conditions.go:203] Setting lastTransitionTime for Certificate "local-r4y" condition "Issuing" to 2023-12-11 13:48:56.816206109 +0000 UTC m=+1276688.137750553
I1211 13:48:56.832457 1 controller.go:162] "cert-manager/certificates-trigger: re-queuing item due to optimistic locking on resource" key="default/local-r4y" error="Operation cannot be fulfilled on certificates.cert-manager.io \"local-r4y\": the object has been modified; please apply your changes to the latest version and try again"
I1211 13:48:56.832536 1 trigger_controller.go:215] "cert-manager/certificates-trigger: Certificate must be re-issued" key="default/local-r4y" reason="DoesNotExist" message="Issuing certificate as Secret does not exist"
I1211 13:48:56.832555 1 conditions.go:203] Setting lastTransitionTime for Certificate "local-r4y" condition "Issuing" to 2023-12-11 13:48:56.832549773 +0000 UTC m=+1276688.154094209
...
之后查看证书资源,可以看到已经完成签发的证书。
➜ issuers git:(main) kubectl get certificateS -o wide
NAME READY SECRET ISSUER STATUS AGE
local-r4y True local-r4y letsencrypt-staging Certificate is up to date and has not expired 14h
local-r4y-prod True local-r4y-prod letsencrypt-production Certificate is up to date and has not expired 13h
配置ingress
有了证书之后,我们就可以通过 ingress 配置来引用这个证书。配置如下,主要是在 Tls 的部份添加了这个证书的引用。需要注意的是证书需要在同一个命名空间下。
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: echo-ingress-tls
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`traefik-ui.local.r4y.site`) && PathPrefix(`/`)
services:
- kind: Service
name: echo-test
namespace: default
port: 80
middlewares:
- name: basic-auth
namespace: kube-system
- name: echo-header
namespace: default
tls:
secretName: local-r4y-prod
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
namespace: default
name: echo-ingress
spec:
entryPoints:
- web
routes:
- kind: Rule
match: Host(`traefik-ui.local.r4y.site`) && PathPrefix(`/`)
services:
- kind: Service
name: echo-test
namespace: default
port: 80
middlewares:
- name: basic-auth
namespace: kube-system
- name: echo-header
namespace: default
apply 之后,创建/修改 ingress 资源,这样我们就有了一个合法的 HTTPS 证书,用于我们的内网服务。