我编写了一个bash脚本,使用helm和kubectl在kubernetes集群中自动部署应用程序。我使用cert-manager来自动发布和更新应用程序本身所需的TLS证书,这些证书由lets加密获得。
该脚本可以根据需要使用不同的设置和清单将应用程序部署在许多环境中的任何一个,例如测试(test)和生产(prod)。对于每个环境,我创建一个单独的命名空间并在其中部署所需的资源。在生产环境中,我使用lets加密正式服(spec. acme.server:https://acme-v02.api.letsencrypt.org/directory),而在任何其他环境中,例如测试,我使用登台服务器(spec.acme.server:https://acme-staging-v02.api.letsencrypt.org/directory)。根据环境,我请求证书的主机名是不同的集合:测试中的xyz.test.mysite.tld与生产中的xyz.mysite.tld。我为所有环境提供相同的联系电子邮件地址。
以下是用于测试的lets加密发行人的完整清单:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-staging
spec:
acme:
email: operations@mysite.tld
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-issuer-private-key
solvers:
- http01:
ingress:
class: public-test-it-it
这里是lets加密发行人的完整清单,用于生产:
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-production
spec:
acme:
email: operations@mysite.tld
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-production-issuer-private-key
solvers:
- http01:
ingress:
class: public-prod-it-it
当我第一次部署应用程序时,无论是在测试环境还是prod环境中,一切都按预期工作,cert-manager都会获得由lets加密签署的TLS证书(分别是staging或正式服)并秘密存储。但是当我在另一个环境中部署应用程序时(这样我就可以同时运行test和prod),cert-manager无法再获得证书签名,并且链证书请求-
kubectl describe challenge xyz-tls-certificate
...
Status:
Presented: true
Processing: true
Reason: Waiting for HTTP-01 challenge propagation: wrong status code '404', expected '200'
State: pending
Events: <none>
我可以验证我在尝试卷曲任何挑战的URL时确实得到了404:
curl -v http://xyz.test.mysite.tld/.well-known/acme-challenge/IECcFDmQF_fzGKcA9hJvFGEWRjDCAE_fs8dnBXlr_wY
* Trying vvv.xxx.yyy.zzz:80...
* Connected to xyz.test.mysite.tld (vvv.xxx.yyy.zzz) port 80 (#0)
> GET /.well-known/acme-challenge/IECcFDmQF_fzGKcA9hJvFGEWRjDCAE_fs8dnBXlr_wY HTTP/1.1
> Host: xyz.test.mysite.tld
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< date: Thu, 21 Jul 2022 09:48:08 GMT
< content-length: 21
< content-type: text/plain; charset=utf-8
<
* Connection #0 to host xyz.test.mysite.tld left intact
default backend - 404
所以lets加密不能访问挑战的URL,也不会签署TLS证书。
我尝试调试404错误,发现我可以成功地curl pod和服务支持来自集群/命名空间中运行的另一个pod的挑战,但我从外部世界得到404。这似乎是入口控制器的问题(在我的例子中是HAProxytech/kubernetes-ingress),但我无法解释为什么该机制在首次部署时起作用,然后不再起作用…
我检查了cert-manager日志,发现了以下行:
kubectl logs -n cert-manager cert-manager-...
I0721 13:27:45.517637 1 ingress.go:99] cert-manager/challenges/http01/selfCheck/http01/ensureIngress "msg"="found one existing HTTP01 solver ingress" "dnsName"="xyz.test.mysite.tld" "related_resource_kind"="Ingress" "related_resource_name"="cm-acme-http-solver-8668s" "related_resource_namespace"="app-test-it-it" "related_resource_version"="v1" "resource_kind"="Challenge" "resource_name"="xyz-tls-certificate-hwvjf-2516368856-1193545890" "resource_namespace"="app-test-it-it" "resource_version"="v1" "type"="HTTP-01"
E0721 13:27:45.527238 1 sync.go:186] cert-manager/challenges "msg"="propagation check failed" "error"="wrong status code '404', expected '200'" "dnsName"="xyz.test.mysite.tld" "resource_kind"="Challenge" "resource_name"="xyz-tls-certificate-hwvjf-2516368856-1193545890" "resource_namespace"="app-test-it-it" "resource_version"="v1" "type"="HTTP-01"
这似乎证实了cert-manager可以从集群内部自我检查挑战的URL是否已到位,但外部世界无法访问这些URL(传播检查失败)。看起来cert-manager设置挑战的pod/services/ing的情况不错,但发送到挑战URL的请求不会路由到后备pod/services。这只是我第二次尝试部署应用程序…
我还验证了,在第一次部署时颁发证书后,cert-manager(正确地)从相关命名空间中删除了所有相关的pod/services/入口,因此不应该与重复挑战的资源有任何冲突。
我在这里重申,在我第一次部署应用程序时,无论是在测试环境还是prod环境中,证书都是完美颁发的,但如果我在不同的环境中再次部署应用程序,它们将不再颁发。
知道为什么会这样吗?
我终于发现了问题所在…
基本上,我为每个环境(test/prod)安装了一个单独的HAProxy入口控制器(haproxytech/kubernetes-ingress),因此每个命名空间都有自己的入口控制器,我在清单中引用了它。这在原则上应该有效,但结果是cert-manager在设置lets加密挑战时无法引用正确的入口控制器。
解决方案包括创建单个HAproxy入口控制器(在其自己单独的命名空间中)来服务于整个集群,并被所有其他环境/命名空间引用。这样,测试和正式生产环境都面临挑战,其中cert-manager正确设置并由lets加密验证,它签署了所需的证书。
最后,我强烈建议每个集群使用一个HAproxy入口控制器,安装在它自己的命名空间中。这种配置冗余较少,并消除了我面临的潜在问题。