cert-manager 是一种自动执行证书管理的工具,它可以与 Istio Gateway 集成以管理 TLS 证书,当然也可以很方便地和前面我们配置的 ingress-nginx 或者 traefik 配合使用。对于和 Istio 的集成使用,无需特殊配置即可。
在进行本节实验之前记得将前面章节的实验内容进行清空。
安装
要在 Kubernetes 集群上安装 cert-manager 也非常简单,官方提供了一个单一的资源清单文件,包含了所有的资源对象,所以直接安装即可:
# Kubernetes 1.15+
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.1/cert-manager.yaml
# Kubernetes <1.15
$ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v0.16.1/cert-manager-legacy.yaml
上面的命令会创建一个名为 cert-manager 的命名空间,安装大量的 CRD 以及 AdmissionWebhook 对象,可以通过如下命令来查看是否安装成功:
$ kubectl get pods --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-7ddc5b4db-56nln 1/1 Running 0 4m46s
cert-manager-cainjector-6644dc4975-zthcj 1/1 Running 0 4m47s
cert-manager-webhook-7b887475fb-9fbp4 1/1 Running 0 4m45s
正常情况下可以看到 cert-manager、cert-manager-cainjector 以及 cert-manager-webhook 这几个 Pod 处于 Running 状态。我们可以通过下面的测试来验证下是否可以签发基本的证书类型,创建一个 Issuer 资源对象来测试 webhook 工作是否正常(在开始签发证书之前,必须在群集中至少配置一个 Issuer 或 ClusterIssuer 资源):
$ cat <<EOF > test-resources.yaml
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager-test
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: test-selfsigned
namespace: cert-manager-test
spec:
selfSigned: {} # 配置自签名的证书机构类型
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: selfsigned-cert
namespace: cert-manager-test
spec:
dnsNames:
- example.com
secretName: selfsigned-cert-tls
issuerRef:
name: test-selfsigned
EOF
这里我们创建了一个名为 cert-manager-test 的命名空间,创建了一个 Issuer 的证书颁发机构,然后使用这个 Issuer 来创建一个证书 Certificate 对象,直接创建上面的资源清单即可:
$ kubectl apply -f test-resources.yaml
namespace/cert-manager-test created
issuer.cert-manager.io/test-selfsigned created
certificate.cert-manager.io/selfsigned-cert created
创建完成后可以检查新创建的证书状态,在 cert-manager 处理证书请求之前,可能需要稍微等几秒:
$ kubectl describe certificate -n cert-manager-test
Name: selfsigned-cert
Namespace: cert-manager-test
......
Spec:
Dns Names:
example.com
Issuer Ref:
Name: test-selfsigned
Secret Name: selfsigned-cert-tls
Status:
Conditions:
Last Transition Time: 2020-08-16T01:50:40Z
Message: Certificate is up to date and has not expired
Reason: Ready
Status: True
Type: Ready
Not After: 2020-11-14T01:50:39Z
Not Before: 2020-08-16T01:50:39Z
Renewal Time: 2020-10-15T01:50:39Z
Revision: 1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Issuing 64s cert-manager Issuing certificate as Secret does not exist
Normal Generated 64s cert-manager Stored new private key in temporary Secret resource "selfsigned-cert-25ftw"
Normal Requested 64s cert-manager Created new CertificateRequest resource "selfsigned-cert-p2sbx"
Normal Issuing 63s cert-manager The certificate has been successfully issued
从上面的 Events 事件中我们可以证书已经成功签发了,到这里证明我们的 cert-manager 已经安装成功了。而且我们需要注意的是 cert-manager 的功能非常强大,不只是可以支持 ACME 类型的证书签发,还支持其他众多的类型,比如 SelfSigned(自签名)、CA、Vault、Venafi、External、ACME,只是我们一般主要是使用 ACME 来帮我们生成自动化的证书。
环境配置
由于通过 ACME 做自动化证书的时候,需要暴露 80 和 443 端口,当然如果我们使用 DNS 校验方式也可以,但是有时候我们根本就没有域名的情况下想要实现自动化证书,我们可以使用 xip.io 这类的服务来实现。前面我们部署 istio-ingressgateway 的时候是通过 NodePort 类暴露的服务,所以我们需要在前面加一个 LB 来转发下请求。这里为了简单,我直接使用 haproxy 来监听节点的 80 和 443 端口,将请求转发到后端的 NodePort 端口。
首先安装 haproxy:
$ yum install -y haproxy
然后配置 haproxy,配置文件 /etc/haproxy/haproxy.cfg,内容如下所示:
listen stats
bind *:9000
mode http
stats enable
stats hide-version
stats uri /stats
stats refresh 30s
stats realm Haproxy\ Statistics
stats auth Admin:Password
frontend istio-https
bind *:443
mode tcp
option tcplog
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
default_backend istio-https-svc
backend istio-https-svc
mode tcp
option tcplog
option tcp-check
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server istio-http-svc-1 127.0.0.1:30951 check
frontend istio-http
bind *:80
mode tcp
option tcplog
default_backend istio-http-svc
backend istio-http-svc
mode tcp
option tcplog
option tcp-check
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server istio-http-svc-1 127.0.0.1:32193 check
其中的 32193 与 30951 端口是 istio-ingressgateway 的 NodePort 端口:
$ kubectl get svc istio-ingressgateway -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway NodePort 10.102.120.128 <none> 15020:31093/TCP,80:32193/TCP,443:30951/TCP,31400:31871/TCP,15443:31367/TCP 68d
配置完成后直接启动 haproxy 即可:
$ sudo systemctl start haproxy
$ sudo systemctl enable haproxy
$ sudo systemctl status haproxy
然后我们可以通过上面 9000 端口监控我们的 haproxy 的运行状态:
与 Istio 集成
这里我们来通过 cert-manager 为前面的 httpbin 应用配置自动的 HTTPS,首先单独创建一个用户 cert-manager 的自定义的 Gateay,如下所示:
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: acme-gateway
labels:
app: ingressgateway
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- "*.xip.io"
port:
number: 80
name: http
protocol: HTTP
# tls:
# httpsRedirect: true
- hosts:
- "*.xip.io"
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: "xip-cert"
---
EOF
gateway.networking.istio.io/acme-gateway created
需要注意的是在配置 Gateway 的时候 tls 的 credentialName 代表的是 cert-manager 自动生成的证书名称。
接下来创建 VirtulService 对象,这里我们使用 https 方式的 ACME 证书校验方式,除了 http 方式之外还有 tls 与 dns 方式的校验,dns 方式的证书校验支持通配符的域名。所以我们需要为 /.well-known/ 这个 PATH 路径做一个正确的配置,方便进行 http 校验:
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
gateways:
- acme-gateway
hosts:
- httpbin.123.59.188.12.xip.io
http:
- route:
- destination:
host: httpbin
port:
number: 8000
EOF
virtualservice.networking.istio.io/httpbin created
部署完成后我们可以通过访问 http://httpbin.123.59.188.12.xip.io 来验证是否已经部署成功:
接下来创两个 ClusterIssuer,一个用于测试,一个用于正式使用:
$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: icnych@xxx.com
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: istio
EOF
clusterissuer.cert-manager.io/letsencrypt-staging created
$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1beta1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: icnych@xxx.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: istio
EOF
clusterissuer.cert-manager.io/letsencrypt-prod created
$ kubectl describe clusterissuer
......
Status:
Acme:
Last Registered Email: icnych@xxx.com
Uri: https://acme-v02.api.letsencrypt.org/acme/acct/94057742
Conditions:
Last Transition Time: 2020-08-16T07:37:40Z
Message: The ACME account was registered with the ACME server
Reason: ACMEAccountRegistered
Status: True
Type: Ready
Events: <none>
然后创建一个 Certificate 对象来获取证书,由于 Istio 需要在它的命名空间下面有证书,所以我们需要在 istio-system 这个命名空间下面创建:
$ cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1beta1
kind: Certificate
metadata:
name: httpbin-cert
namespace: istio-system # 这里必须是 istio-system 空间
spec:
secretName: xip-cert # 这个就是上面 gateway 所配置的证书名称
issuerRef:
kind: ClusterIssuer
name: letsencrypt-staging
commonName: httpbin.123.59.188.12.xip.io
dnsNames:
- httpbin.123.59.188.12.xip.io
EOF
certificate.cert-manager.io/httpbin-cert created
创建完成后这时候查看 istio-system 空间应该会有一个 cm-acme-http-solver- 这个开头的 Pod、Servie、Ingress 资源对象:
$ kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
cm-acme-http-solver-8tpbn 1/1 Running 0 16s
$ kubectl get svc -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cm-acme-http-solver-w6sf7 NodePort 10.106.174.20 <none> 8089:32135/TCP
$ kubectl get ingress -n istio-system
NAME HOSTS ADDRESS PORTS AGE
cm-acme-http-solver-jkrs2 httpbin.123.59.188.12.xip.io 80 45s
隔一小会儿去查看上面部署的 Certificate 对象的状态:
$ kubectl describe certificate httpbin-cert -n istio-system
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Issuing 13m cert-manager Issuing certificate as Secret does not exist
Normal Generated 13m cert-manager Stored new private key in temporary Secret resource "http-cert-6cml8"
Normal Requested 13m cert-manager Created new CertificateRequest resource "http-cert-9wcg2"
Normal Issuing 6m17s cert-manager The certificate has been successfully issued
看到最后的信息 The certificate has been successfully issued 证明我们的证书获取成功了,但是这个时候如果我们通过 https 去访问的话还是会提示证书错误的,因为我们获取的是 staging 环境的证书:
这个时候我们重新更新 httpbin-cert 这个 Certificate 对象中引用的 issuer,更改为正式环境的 issuer,或者使用正式的签名机构新建一个证书对象,正常就可以获得线上环境的证书了。