K8s 中使用 cert-manager 申请免费 Https 证书
Intro
最近在尝试将自己的应用从自己用 kind 部署的一个 k8s 集群迁移到 Azure 的 AKS 上,其中一个问题就是 https 证书,原来的 k8s 集群是放在 nginx 后端的并没有直接管理 https 证书,https 证书是放在 nginx 层面的,完全运行在 AKS 上的 k8s 集群中就要解决 https 证书的问题,我们可以使用 cert-manager 来申请由 Let's encrypt 提供的免费 https 证书,因为我的域名是在阿里云上的,并不在官方的 DNS 服务商支持范围内,所以需要使用第三方的实现的 webhook 支持,而网上的一些第三方的很多 API version 都不对了,而且使用起来感觉也不太方便,所以自己基于别人的魔改了一版,完善了 helm 的支持,基本可以一个命令创建所有需要的资源
Cert-manager
随着 HTTPS 不断普及,越来越多的网站都在从 HTTP 升级到 HTTPS,现在如果不使用 https Chrome 浏览器都会提示不安全,而且有些服务可能会要求必须是 https 才可以,而 Let's encrypt 为大家提供了免费的 https 证书,免费证书的时间是三个月,我们可以在过期之前重新申请,对于个人来说是个不错的选择。
cert-manager 是 Kubernetes 上的全能证书管理工具,如果对安全级别和证书功能要求不高,可以利用 cert-manager 基于 ACME 协议与 Let's Encrypt 来签发免费证书并自动续期,实现永久免费使用证书。
下面这张图是一个 cert-manager 的总体架构图
Issuer 是指证书的来源,它可以从各种受支持的来源颁发证书,包括 Let's Encrypt、HashiCorp Vault 和 Venafi 以及私有 PKI。
Certificates 就是指的证书,证书是由指定的 issuer 来签发的
Kubernetes secrets 一般是指保存 https 证书的 secret,使用 https 的时候会用到这种 kubernetes.io/tls
类型的 secret
Issuer/ClusterIssuer: 用于指示 cert-manager 用什么方式签发证书,本文主要讲解签发免费证书的 ACME 方式。ClusterIssuer 与 Issuer 的唯一区别就是 Issuer 只能用来签发自己所在 namespace 下的证书,ClusterIssuer 可以签发任意 namespace 下的证书。
Certificate: 用于告诉 cert-manager 我们想要什么域名的证书以及签发证书所需要的一些配置,包括对 Issuer/ClusterIssuer 的引用
Let’s Encrypt 利用 ACME 协议来校验域名是否真的属于你,校验成功后就可以自动颁发免费证书,证书有效期只有 90 天,在到期前需要再校验一次来实现续期, cert-manager 可以自动续期,这样就可以基本使用永久免费的证书了。如何校验这个域名是否属于你呢?主要有两种校验方式是 HTTP-01
和 DNS-01
HTTP-01 校验原理
HTTP-01 的校验原理是给你域名指向的 HTTP 服务增加一个临时 location ,Let’s Encrypt 会发送 http 请求到 http:///.well-known/acme-challenge/
,YOUR_DOMAIN
就是被校验的域名,TOKEN
是 ACME 协议的客户端负责放置的文件,在这里 ACME 客户端就是 cert-manager,它通过修改或创建 Ingress 规则来增加这个临时校验路径并指向提供 TOKEN
的服务。Let’s Encrypt 会对比 TOKEN
是否符合预期,校验成功后就会颁发证书。此方法仅适用于给使用 Ingress 暴露流量的服务颁发证书,并且不支持泛域名证书。
DNS-01 校验原理
DNS-01 的校验原理是利用 DNS 提供商的 API Key 拿到你的 DNS 控制权限, 在 Let’s Encrypt 为 ACME 客户端提供令牌后,ACME 客户端 (cert-manager) 将创建从该令牌和您的帐户密钥派生的 TXT 记录,并将该记录放在 _acme-challenge.
。然后 Let’s Encrypt 将向 DNS 系统查询该记录,如果找到匹配项,就可以颁发证书。此方法不需要你的服务使用 Ingress,并且支持泛域名证书。
HTTP-01 的校验方式配置简单通用,不管使用哪个 DNS 提供商都可以使用相同的配置方法;缺点是:需要依赖 Ingress,如果你的服务不是用 Ingress 暴露流量的就不适用,而且不支持泛域名证书。
DNS-01 的校验方式不依赖 Ingress,也支持泛域名;缺点就是不同 DNS 提供商的配置方式不一样,而且 DNS 提供商有很多,cert-manager 的 Issuer 不可能每个都去支持,不过有一些可以通过部署实现了 cert-manager 的 Webhook 的服务来扩展 Issuer 进行支持,比如 DNSPod 和 阿里 DNS,详细 Webhook 列表请参考: https://cert-manager.io/docs/configuration/acme/dns01/#webhook
Sample
这里假定以后安装好了 helm3,如果没有安装可以参考:https://helm.sh/docs/intro/install/ 先安装 helm
首先我们需要安装 cert-manager
,你可以执行下面的命令来通过 helm 安装 cert-manager
helm repo add jetstack https://charts.jetstack.iohelm repo updatehelm install cert-manager jetstack/cert-manager -n cert-manager --create-namespace --set installCRDs=true --version v1.6.1
安装完 cert-manager 之后,就可以配置 issuer 和 证书了
HTTP-01
比较简单,基本按照文档就可以走通了,这里不多介绍了 https://cert-manager.io/docs/tutorials/acme/http-validation/
如果你的 DNS 服务商官方就支持了也是跟着文档配置就可以了 https://cert-manager.io/docs/tutorials/acme/dns-validation/
而阿里云目前并非官方支持的,你需要使用第三方的,觉得不好用的,可以像我一样基于别人的配置改造一下,下面我们就以阿里云为例来介绍使用 cert-manager 来配置阿里云的 DNS,以我改造的 AliDNS webhook 为例
首先克隆项目 https://github.com/WeihanLi/certmanager-webhook-alidns 到本地,然后在项目根目录下执行下面的命令来配置证书
# 切换目录到 helm chart 包目录
cd deploy/certmanager-webhook-alidns# render template,只展示渲染后的 yaml 文件,不安装
helm template certmanager-webhook-alidns . --set issuer.create=true --set issuer.host=weihanli.top --set issuer.email=weihanli@outlook.com --set issuer.secret.accessKeyId=AliAccessKeyId --set issuer.secret.accessKeySecret=AliAccessKeySecret -n cert-manager# install without creating ClusterIssuer,安装 webhook,但是不创建证书 issuer 和申请证书
helm install certmanager-webhook-alidns . -n cert-manager# install with creating ClusterIssuer,安装 webhook 并创建证书 issuer 和申请 https 证书
helm install certmanager-webhook-alidns . --set issuer.create=true --set issuer.host=weihanli.top --set issuer.email=weihanli@outlook.com --set issuer.secret.accessKeyId=AliAccessKeyId --set issuer.secret.accessKeySecret=AliAccessKeySecret -n cert-manager
可以使用 helm template
来测试自己要安装的资源情况
默认只安装 webhook 不会创建 ClusterIssuer
以及证书,如果要创建需要提供更多参数,需要指定 issuer.create
为 true
,然后通过 issuer.email
来指定申请证书的邮箱,并且需要提供阿里云的 accessKeyId 和 accessKeySecret 来修改 DNS 解析记录以实现自动地 DNS 验证进而自动签发证书,默认地创建 ClusterIssuer
的时候也会创建一个证书,如果不需要创建证书则配置 issuer.createCert
为 false
,如果要创建需要指定一个域名,比如 weihanli.top
上面最后一个命令会自动创建证书,安装完成后,我们就可以去检查我们的证书了,执行kubectl get cert -n cert-manager
来查看 cert-manager
命名空间下的证书,上面命令创建的证书名称是 weihanli-top-tls-cert
,会是 host
名字把 .
替换成 -
然后加上 -tls-cert
,证书签发完成 kubectl get secret -n cert-manager
会看到和证书同名的一个 secret
类似下面这样
NAME | TYPE | DATA | AGE |
---|---|---|---|
weihanli-top-tls-cert | kubernetes.io/tls | 2 | 4d |
上面的命令会创建一个 ClusterIssuer 和 Certificate,yaml 定义如下:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:name: letsencryptlabels:app: webhook-alidnschart: webhook-alidns-0.1.0release: certmanager-webhook-alidnsheritage: Helm
spec:acme:server: https://acme-v02.api.letsencrypt.org/directoryemail: "weihanli@outlook.com"privateKeySecretRef:name: letsencryptsolvers:- dns01:webhook:config:accessKeyId: AliAccessKeyIdaccessKeySecretRef:key: accessKeySecretname: ali-credentialregionId: "cn-shanghai"ttl: 600groupName: certmanager.webhook.alidnssolverName: alidns
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:name: weihanli-top-tls-certlabels:app: webhook-alidnschart: webhook-alidns-0.1.0release: certmanager-webhook-alidnsheritage: Helm
spec:commonName: "weihanli.top"dnsNames:- "weihanli.top"- '*.weihanli.top'issuerRef:kind: ClusterIssuername: letsencryptsecretName: weihanli-top-tls-cert
同时还会有一个保存阿里云 accessKeyId 和 accessKeySecret 的 secret 生成
apiVersion: v1
kind: Secret
metadata:labels:app: webhook-alidnschart: webhook-alidns-0.1.0release: certmanager-webhook-alidnsheritage: Helmname: ali-credential
type: Opaque
data:accessKeyId: "QWxpQWNjZXNzS2V5SWQ="accessKeySecret: "QWxpQWNjZXNzS2V5U2VjcmV0"
有了证书之后我们就可以配置成 Ingress 默认的 https 证书,这样就不需要每个 ingress 都指定证书 secret 了,我们使用基于 nginx 的 ingress-nginx 来配置,通过 helm 来安装配置 ingress-nginx
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginxhelm repo updatehelm install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --set controller.extraArgs.default-ssl-certificate=cert-manager/weihanli-top-tls-cert --create-namespace
controller.extraArgs.default-ssl-certificate
配置为我们的证书对应的 secret,格式为:<namespace>/<secret-name>
然后我们就可以在 ingress 中配置 https 了,下面是一个配置示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:annotations:cert-manager.io/cluster-issuer: letsencryptkubernetes.io/ingress.class: nginxname: homepagenamespace: default
spec:rules:- host: weihanli.tophttp:paths:- backend:service:name: homepageport:number: 80path: /pathType: Prefixtls:- hosts:- weihanli.top
这里我们不需要指定证书的 secret
,因为我们已经设置了默认的证书配置,再看一个示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:annotations:kubernetes.io/ingress.class: nginxnginx.ingress.kubernetes.io/rewrite-target: /$1name: sparktodo-api-ingress
spec:rules:- host: "sparktodo-api.weihanli.top"http:paths:- pathType: Prefixpath: "/?(.*)"backend:service:name: sparktodo-apiport:number: 80- pathType: Prefixpath: "/monitor/?(.*)"backend:service:name: sparktodo-apiport:number: 52323tls:- hosts:- sparktodo-api.weihanli.top
这个示例稍微复杂一些,主要是 /monitor/
开头的请求转给 52323
端口,其他的请求转给 80
端口
More
按照上面的 helm chart 来配置自我感觉还是比较简单的,更多配置选项可以参考 helm chart 包的定义
https://github.com/WeihanLi/certmanager-webhook-alidns,
如果想自己玩一下,可能会遇到一些问题,cert-manager 的文档可能会帮到你 https://cert-manager.io/docs/faq/acme/,你也可以基于我魔改的再魔改一下哈
另外 cert-manager 文档上有一张非常详细的图来介绍证书的申请、验证、颁发整个过程,值得一看
References
https://www.cnblogs.com/tencent-cloud-native/p/13883790.html
https://github.com/WeihanLi/certmanager-webhook-alidns
https://cert-manager.io/docs/installation/helm/
https://cert-manager.io/docs/faq/acme/
https://helm.sh/docs/intro/install/