Kubernetes (K8S) 中Traefik自动申请证书

Traefik自动申请证书

Traefik实现自动申请HTTPS证书要使用Let’s Encrypt自动生成证书,需要使用ACME。需要在静态配置中定义 “证书解析器”,Traefik负责从ACME服务器中检索证书。

然后,每个 “路由器 “被配置为启用TLS,并通过tls.certresolver配置选项与一个证书解析器关联。

Traefik的ACME验证方式主要有以下三种:

  • tlsChallenge
  • httpChallenge
  • dnsChallenge

如果使用tlsChallenge,则要求Let’s Encrypt到 Traefik 443 端口必须是可达的。如果使用httpChallenge,则要求Let’s Encrypt到 Traefik 80端口必须是可达的。如果使用dnsChallenge,则需要对应的providers。

部署cert-manager

借助 Kubernetes,我们获得了一个强大且可扩展的平台来解决许多复杂的场景。cert-manager是一个功能强大的解决方案,可以帮助我们自动化和管理与 TLS 证书相关的几乎所有内容。它提供了一套针对各种场景的自定义资源定义(CRD),并与原生IngressGateway资源很好地集成。

cert-manager 在 Kubernetes 机密中存储和缓存证书和私钥,使它们高度可用,可供入口控制器(如 Traefik Proxy)或应用程序进一步使用。

注意:默认情况下,cert-manager 不会自动清除机密,从而允许它重新附加到已颁发的证书并避免颁发新证书。当您需要创建和删除大量资源并且不希望受到速率限制时,这变得非常方便。

cert-manager 可以与各种来源交互来颁发证书,包括 Let's EncryptHashiCorpVault 以及私有 PKI。对于AWS 私有证书颁发机构Google Cloud 证书颁发机构服务Cloudflare Origin CA 等不受支持的情况,外部颁发者允许您扩展证书管理器功能。

先决条件

要学习本教程,您需要具备以下条件:

  • Kubernetes 集群 >= v1.20
  • Let’s Encrypt 的公共托管 DNS 域 — 出于本文的目的,我将使用 腾讯云
  • Traefik 2.10,您可以通过这篇文章安装部署 Kubernetes部署升级Traefik2.6

您可以使用以下命令安装 cert-manager 1.12:

1
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.3/cert-manager.yaml

提供 Web 端口的服务。在本教程中,我将使用 whoami 作为示例

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
cat > whoami.yml <<EOF 
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: whoami
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: whoami
labels:
app: whoami
spec:
replicas: 1
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: containous/whoami
ports:
- name: web
containerPort: 80
EOF

Traefik 代理与 cert-manager 和 Let’s Encrypt

让我们探索如何结合 Kubernetes 入口控制器(如 Traefik Proxy 和 cert-manager)来保护 Web 应用程序的安全。Let’s Encrypt 提供多种质询类型来验证域名的控制权。根据您的要求,您可以选择HTTP-01您的服务何时可供公共访问或DNS-01专用端点。

使用 Let Encrypt 时请注意速率限制。为了避免出现令人不快的意外,建议使用 Let’s Encrypt暂存环境

1
2
#测试(staging):https://acme-staging-v02.api.letsencrypt.org/directory
#生产(production):https://acme-v02.api.letsencrypt.org/directory

自动化 HTTPS

Let’s Encrypt 使用 ACME 协议来校验域名是否真的属于你,校验成功后就可以自动颁发免费证书,证书有效期只有 90 天,在到期前需要再校验一次来实现续期,而 cert-manager 是可以自动续期的,所以事实上并不用担心证书过期的问题。目前主要有 HTTP 和 DNS 两种校验方式。

HTTP-01 校验

HTTP-01 的校验是通过给你域名指向的 HTTP 服务增加一个临时 location,:在校验的时候 Let’s Encrypt 会发送http 请求到 http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>,其中 YOUR DOMAIN 就是被校验的域名,TOKEN 是 ert-manager 生成的一个路径,它通过修改 Ingress 规则来增加这个临时校验路径并指向提供TOKEN 的服务。Let’s Encrypt 会对比 TOKEN 是否符合预期,校验成功后就会颁发证书了,不过这种方法不支持泛域名证书。

使用 HTTP 校验这种方式,首先需要将域名解析配置好,也就是需要保证 ACME 服务端可以正常访闯到你的 HTTP服务。这里我们以上面的 whoami应用为例,我们已经将 whoami.boysec.cn 域名做好了正确的解析。

这里必须要绑定公网IP才可以。

方法一

由于 Let’s Encrypt 的生产环境有着严格的接口调用限制,所以一般我们需要先在 staging 环境测试通过后,再切换到生产环境。首先我们创建一个default范围测试环境和生产环境使用的 HTTP-1 校验方式的证书颁发机构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat <<EOF >staging-http.yml 
apiVersion: cert-manager.io/v1
kind: Issuer # 选择整个集群(ClusterIssuer)还是单个namespace
metadata:
name: staging-http01
spec:
acme:
email: wangxiansen@boysec.cn # 用于 ACME 注册的邮箱
# ACME 服务端地址
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# 用于存放 ACME 帐号 private key 的 secret
name: boysec-staging-http01
solvers:
- http01: # ACME HTTP-01 类型
ingress:
class: traefik # 指定ingress的名称
EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat <<EOF >production-http.yml 
apiVersion: cert-manager.io/v1
kind: Issuer # 选择整个集群(ClusterIssuer)还是单个namespace
metadata:
name: production-http01
spec:
acme:
email: wangxiansen@boysec.cn # 用于 ACME 注册的邮箱
# ACME 服务端地址
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# 用于存放 ACME 帐号 private key 的 secret
name: boysec-http01
solvers:
- http01: # ACME HTTP-01 类型
ingress:
class: traefik # 指定ingress的名称
EOF

创建完成后可以看到两个 issuers 对象:

1
kubectl get issuers

有了 Issuer/ClusterIssuer 证书颁发机构,接下来我们就可以生成免费证书了,cert-manager 给我们提供了 Certificate 这个用于生成证书的自定义资源对象,不过这个对象需要在一个具体的命名空间下使用,证书最终会在这个命名空间下以 Secret 的资源对象存储。我们这里是要结合 traefik 一起使用,实际上我们只需要修改 Ingress 对象,添加上 cert-manager 的相关注解即可,不需要手动创建 Certificate 对象了,修改上面的 whoami应用的 Ingress 资源对象,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cat << EOF >ing.yml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami
annotations:
cert-manager.io/issuer: "staging-http01"
spec:
tls:
- hosts:
- whoami.boysec.cn
secretName: whoami-tls
rules:
- host: whoami.boysec.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami
port:
name: web
EOF

验证

1
2
3
4
5
6
7
8
9
10
11
$ kubectl get issuer -o wide
NAME READY STATUS AGE
production-http01 True The ACME account was registered with the ACME server 18m
staging-http01 True The ACME account was registered with the ACME server 19m
$ kubectl get certificateRequest -o wide
NAME APPROVED DENIED READY ISSUER STATUS
tls-whoami-ingress-http-fdw2x True True le-example-http Certificate fetched from issuer successfully

$ kubectl get certificates
NAME READY SECRET ISSUER STATUS
tls-whoami-ingress-http True tls-whoami-ingress-http le-example-http Certificate is up to date and has not expired

方法二(推荐)

通过修改traefik ConfigMap文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
entryPoints:
web:
address: ":80" ## 配置 80 端口,并设置入口名称为 web
websecure:
address: ":443" # 配置443端口,并设置入口名称为 websecure
certificatesResolvers:
wangxiansen-test: # 可以换成你喜欢的名字
acme:
caServer: https://acme-staging-v02.api.letsencrypt.org/directory # ACME 服务端地址
email: wangxiansen@boysec.cn # 届时你申请签发证书的邮箱
storage: acme-staging-web.json # 存储位置
httpChallenge:
entryPoint: web
wangxiansen: # 可以换成你喜欢的名字
acme:
email: wangxiansen@boysec.cn # 届时你申请签发证书的邮箱
storage: acme-web.json # 存储位置
httpChallenge:
entryPoint: web

创建ingressroute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: whoamiauto-tls-http
spec:
entryPoints:
- web
routes:
- match: Host(`whoami.boysec.cn`)
kind: Rule
services:
- name: whoami
port: 80
tls:
certResolver: wangxiansen-test

打开浏览器就会发现申请测试的证书了,然后修改certResolverwangxiansen

DNS-01 校验

DNS-01 的校验是通过 DNS 提供商的 API 拿到你的 DNS 控制权限, 在 Let’s Encrypt 为 cert-manager 提供 TOKEN 后,cert-manager 将创建从该 TOKEN 和你的帐户密钥派生的 TXT 记录,并将该记录放在 _acme-challenge.。然后 Let’s Encrypt 将向 DNS 系统查询该记录,如果找到匹配项,就可以颁发证书,这种方法是支持泛域名证书的。

方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat <<EOF >staging-dns.yml 
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: boysec-staging-dns
namespace: whoami
spec:
acme:
email: user@example.com
# We use the staging server here for testing to avoid hitting
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# if not existing, it will register a new account and stores it
name: boysec-staging-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token

EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat <<EOF >production-dns.yml 
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: boysec-dns
namespace: whoami
spec:
acme:
email: user@example.com
# We use the staging server here for testing to avoid hitting
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
# if not existing, it will register a new account and stores it
name: boysec-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token

EOF

方法二(推荐)

通过修改traefik ConfigMap文件

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
entryPoints:
web:
address: ":80" ## 配置 80 端口,并设置入口名称为 web
websecure:
address: ":443" # 配置443端口,并设置入口名称为 websecure
certificatesResolvers:
tencent-test: # 可以换成你喜欢的名字
acme:
email: wangxiansen@boysec.cn # 届时你申请签发证书的邮箱
storage: acme-staging-dns.json
caServer: https://acme-staging-v02.api.letsencrypt.org/directory # 设置为测试环境,默认为生成环境
dnsChallenge:
provider: tencentcloud # 腾讯云的编码
delayBeforeCheck: 0
resolvers:
- "119.29.29.29:53" # 腾讯云的 DNS 地址
tencent: # 可以换成你喜欢的名字
acme:
email: wangxiansen@boysec.cn # 届时你申请签发证书的邮箱
storage: acme-dns.json
dnsChallenge:
provider: tencentcloud # 腾讯云的编码
delayBeforeCheck: 0
resolvers:
- "119.29.29.29:53" # 腾讯云的 DNS 地址

然后创建密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl create secret generic tencent-token --from-literal=TENCENTCLOUD_SECRET_ID=AKIDoSadfsafdsfasdfsdfLBj23 --from-literal=TENCENTCLOUD_SECRET_KEY=lFTP4afafasdfsfdfsgasfgfMK1Ra7NM -n kube-system

$ kubectl edit -n kube-system pods traefik-v2-6996759d46-d9czt
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 1
containers:
- name: traefik-v2
image: traefik:v2.10
args:
- --configfile=/config/traefik.yaml
envFrom: # 添加环境变量
- secretRef:
name: tencent-token

创建ingressroute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat << EOF > auto-tls-dns.yml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: whoami-auto-tls-dns
spec:
entryPoints:
- websecure
routes:
- match: Host(\`who.boysec.cn\`)
kind: Rule
services:
- name: whoami
port: 80
tls:
certResolver: tencent-test
domains:
- main: boysec.cn
sans:
- '*.boysec.cn' # 匹配所有boysec.cn下的二级域名
EOF

打开可以发现测试证书已下发,可以替换生成环境证书将certResolver替换为生产环境证书

测试环境

再次访问就会发现证书是受信任的了

生产环境

由于写文章测试过多被域名重复申请限制,所以先截图测试环境图,后期补上生成环境图