使用 cert-manager 加密 Kubernetes Ingress

这个例子演示了在 Istio 中使用 Let’s Encrypt 获取 TLS 证书为 Kubernetes Ingress controller 提供安全加固的过程。虽然 Istio 提供了更强大的功能,例如 GatewayVirtual service,它们可以用于更加高级的流量管理功能,而可选的 Kubernetes Ingress 控制器支持则可以简单的把传统应用和第三方解决方案集成到服务网格之中,并由此获得 Istio 提供的遥测和跟踪能力。

首先要从一个全新安装的 Istio 入手,创建一个示例应用,并利用 Kubernetes Ingress 资源将服务开放出去,Istio 可以为这一过程提供加密服务,它调用自带的 cert-manager 管理 TLS 证书的签发和续期,然后把证书分发给 Istio 的 Ingress gateway,并在必要时使用 SDS 进行证书的热交换。

开始之前

安装 Istio 并确认已经启用 Ingress Gateway 的 Kubernetes Ingress 支持、SDS 以及 cert-manager。下面的例子展示了使用 Helm template 完成设置这些依赖项目的方法:

$ helm template $HOME/istio-fetch/istio \
  --namespace=istio-system \
  --set gateways.istio-ingressgateway.sds.enabled=true \
  --set global.k8sIngress.enabled=true \
  --set global.k8sIngress.enableHttps=true \
  --set global.k8sIngress.gatewayName=ingressgateway \
  --set certmanager.enabled=true \
  --set certmanager.email=mailbox@donotuseexample.com \
  > $HOME/istio-fetch/istio.yaml

配置 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-staging", "mode": "SIMPLE", "privateKey": "sds", "serverCertificate": "sds"}}]'

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

部署演示应用

接下来使用一个简单的 helloworld 应用来进行演示。下面的命令会为示例应用创建 DeploymentService 对象,并使用 istio-ingressgateway 所支持的 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: extensions/v1beta1
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: extensions/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 的生产和演练环境)。这个例子中可以使用演练环境(letsencrypt-staging 替换为 letsencrypt 就能获得受浏览器信任的证书),

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

$ cat <<EOF | kubectl apply -f -
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: ingress-cert-staging
  namespace: istio-system
spec:
  secretName: ingress-cert-staging
  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-staging
-> 状态最终会切换为 'Certificate issued successfully'

这样一来就可以使用 HTTPS 进行访问了:

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

注意,因为演练环境签发的证书不受信任,这里用了 --insecure 参数。