使用 Cert-Manager 加密 Kubernetes Ingress

这个例子演示了在 Istio 中使用 Let’s Encrypt 签发 TLS 证书为 Kubernetes Ingress controller 提供安全加固的过程。

您将从全新的 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: cert-manager.io/v1alpha2
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: cert-manager.io/v1alpha2
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.cert-manager.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
这些信息有用吗?
Do you have any suggestions for improvement?

Thanks for your feedback!