使用 Cert-Manager 加密 Kubernetes Ingress

这个例子演示了在 Istio 中使用 Let’s Encrypt 签发 TLS 证书为 Kubernetes Ingress controller 提供安全加固的过程。Istio 中的 gatewayvirtual service 提供了高级的流量管理功能,虽然他们的功能很强大,但是 Istio 本身也对 Kubernetes Ingress 提供支持,这样就可以降低将传统解决方案和第三方解决方案集成到服务网格中的难度,并且使其受益于 Istio 提供的遥测和跟踪功能。

您将从全新的 Istio 安装开始,创建示例服务,使用 Kubernetes Ingress 把它开放出去,并使用 cert-manager(与 Istio 捆绑在一起)管理 TLS 证书的签发和续订来确保它的安全,这个证书之后会给 Istio ingress gateway 使用,并根据需要通过 Secrets Discovery Service (SDS) 提供 hot-swapped 功能。

开始之前

  1. 安装 Istio 并确认已经启用支持 Kubernetes Ingress 和 SDS 的 ingress gateway。下面是例子:

    $ istioctl manifest apply \
      --set values.gateways.istio-ingressgateway.sds.enabled=true \
      --set values.global.k8sIngress.enabled=true \
      --set values.global.k8sIngress.enableHttps=true \
      --set values.global.k8sIngress.gatewayName=ingressgateway
    
  2. 安装 cert-manager 以便实现证书的自动管理。

配置 DNS 域名和 gateway

记录一下 istio-ingressgateway 服务的外部 IP:

$ kubectl -n istio-system get service istio-ingressgateway

对您的 DNS 进行设置,给 istio-ingressgateway 服务的外部 IP 分配一个合适的域名。为了能让例子正常执行,您需要一个真正的域名,用于获取 TLS 证书。让我们把域名保存为环境变量,以便于后面的使用:

$ INGRESS_DOMAIN=mysubdomain.mydomain.edu

Istio 安装中包含了一个自动生成的 gateway ,用于提供 Kubernetes Ingress 定义的路由服务。默认情况下,它不会使用 SDS,所以您需要对其进行修改,使其能通过 SDS 来为 istio-ingressgateway 签发 TLS 证书:

$ kubectl -n istio-system edit gateway

然后修改 https-default 端口对应的 tls 内容:

$ kubectl -n istio-system \
  patch gateway istio-autogenerated-k8s-ingress --type=json \
  -p='[{"op": "replace", "path": "/spec/servers/1/tls", "value": {"credentialName": "ingress-cert", "mode": "SIMPLE", "privateKey": "sds", "serverCertificate": "sds"}}]'

现在就可以部署一个演示应用了。

部署演示应用

接下来使用一个简单的 helloworld 应用来进行演示。下面的命令会为示例应用创建 DeploymentService,并使用 Istio Ingress 开放服务。

$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  ports:
  - port: 5000
    name: http
  selector:
    app: helloworld
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld
spec:
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: helloworld
        image: istio/examples-helloworld-v1
        resources:
          requests:
            cpu: "100m"
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5000
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: istio
  name: helloworld-ingress
spec:
  rules:
    - host: "$INGRESS_DOMAIN"
      http:
        paths:
          - path: /hello
            backend:
              serviceName: helloworld
              servicePort: 5000
---
EOF

现在,您应该能够通过 HTTP 访问演示应用:

$ curl http://$INGRESS_DOMAIN/hello
Hello version: v1, instance: helloworld-5d498979b6-jp2mf

因为没有配置任何的 TLS 证书,所以现在还不能使用 HTTPS 访问,下面就开始进行配置。

使用 cert-manager 获取 Let’s Encrypt 签发的证书

目前,您的 Istio 中应该已经启动了 cert-manager,并带有两个 ClusterIssuer 对象(分别对应 Let’s Encrypt 的生产和测试 ACME-endpoints)。这个例子中使用测试 ACME-endpoint(将 letsencrypt-staging 替换为 letsencrypt 就能获得浏览器信任的证书)。

为了用 cert-manager 进行证书的签发和管理,需要创建一个 Certificate 对象:

$ cat <<EOF | kubectl apply -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: ingress-cert
  namespace: istio-system
spec:
  secretName: ingress-cert
  issuerRef:
    name: letsencrypt-staging
    kind: ClusterIssuer
  commonName: $INGRESS_DOMAIN
  dnsNames:
  - $INGRESS_DOMAIN
  acme:
    config:
    - http01:
        ingressClass: istio
      domains:
      - $INGRESS_DOMAIN
---
EOF

注意这里的 secretName 要匹配前面配置 gateway 时的 credentialName 字段值。Certificate 对象会被 cert-manager 处理,最终会签发新证书。为了看到这个过程我们可以查询 Certificate 对象的状态:

$ kubectl -n istio-system describe certificate ingress-cert
-> 状态最终会切换为 'Certificate issued successfully'

这样一来,该服务也应通过 HTTPS 访问了:

$ curl --insecure https://$INGRESS_DOMAIN/hello
Hello version: v1, instance: helloworld-5d498979b6-jp2mf

注意,您需要使用 --insecure 参数,因为测试 ACME-endpoints 签发的证书不受信任。

从测试到生产

现在换成生产 letsencrypt。首先,我们重新申请一下证书。

$ cat <<EOF | kubectl apply -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: ingress-cert
  namespace: istio-system
spec:
  secretName: ingress-cert
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
  commonName: $INGRESS_DOMAIN
  dnsNames:
  - $INGRESS_DOMAIN
  acme:
    config:
    - http01:
        ingressClass: istio
      domains:
      - $INGRESS_DOMAIN
---
EOF
certificate.certmanager.k8s.io/ingress-cert configured

现在删除 secret 来强制 cert-manager 从生产 ACME-endpoints 请求新证书:

$ kubectl delete secret -n istio-system ingress-cert

等等看证书是否成功签发了:

$ watch -n1 kubectl describe cert ingress-cert -n istio-system

您应该能看到如下显示:

Normal  CertIssued     13m   cert-manager  Certificate issued successfully