将 HTTPS 负载平衡中的多个 SSL 证书与 Ingress 搭配使用

本页面介绍如何将 Ingress 的多个 SSL 证书与内部和外部负载平衡搭配使用。

概览

如果您想要接受来自客户端的 HTTPS 请求,则内部或外部 HTTP(S) 负载平衡器必须具有证书,这样才能向客户端证明其身份。负载平衡器还必须具有私钥才能完成 HTTPS 握手。

当负载平衡器接受来自客户端的 HTTPS 请求时,客户端与负载平衡器之间的流量使用 TLS 进行加密。但是,负载平衡器可终止 TLS 加密,并将未加密的请求转发给应用。通过 Ingress 配置 HTTP(S) 负载平衡器时,可以将负载平衡器配置为最多向客户端提供 10 个 TLS 证书。

负载平衡器使用服务器名称指示 (SNI),根据 TLS 握手中的域名确定要向客户端提供哪个证书。如果客户端不使用 SNI,或者客户端使用的域名与其中一个证书中的公用名 (CN) 不匹配,则负载平衡器将使用 Ingress 中列出的第一个证书。下图展示了根据请求中使用的域名将流量发送到不同后端的负载平衡器:

显示多个 SSL 证书使用 Ingress 系统的示意图

您可以使用以下三种方法的一种向 HTTPS 负载平衡器提供 SSL 证书:

  • Google 管理的 SSL 证书。如需了解如何使用它们,请参阅托管式证书页面。

  • 您自己管理的 Google Cloud SSL 证书。 它使用先前上传到您的 Google Cloud 项目的预共享证书。

  • Kubernetes Secret。 Secret 中包含您自行创建的证书和密钥。如需使用 Secret,请在 Ingress 清单的 tls 字段中添加其名称。

您可以在同一 Ingress 中使用多个方法。这可实现各方法之间的无停机时间迁移。

GKE 最低版本

要使用预共享证书或指定 Ingress 中的多个证书,您必须具有 GKE 1.10.2 或更高版本。

前提条件

为完成本页中的练习,您必须拥有两个域名。您可以使用 Google Domains 或其他注册商。

概览

本主题的步骤概述如下:

  1. 创建一个 Deployment

  2. 创建一个 Service

  3. 创建 2 个证书文件和 2 个密钥文件。

  4. 创建使用 Secret 或预共享证书的 Ingress。由于创建了 Ingress,GKE 会创建并配置 HTTP(S) 负载平衡器。

  5. 测试 HTTP(S) 负载平衡器。

准备工作

在开始之前,请确保您已执行以下任务:

使用以下任一方法设定默认的 gcloud 设置:

  • 使用 gcloud init(如果您想要在系统引导下完成默认设置)。
  • 使用 gcloud config(如果您想单独设置项目 ID、区域和地区)。

使用 gcloud init

如果您收到 One of [--zone, --region] must be supplied: Please specify location 错误,请完成本部分。

  1. 运行 gcloud init 并按照说明操作:

    gcloud init

    如果您要在远程服务器上使用 SSH,请使用 --console-only 标志来防止命令启动浏览器:

    gcloud init --console-only
  2. 按照说明授权 gcloud 使用您的 Google Cloud 帐号。
  3. 创建新配置或选择现有配置。
  4. 选择 Google Cloud 项目。
  5. 选择默认的 Compute Engine 区域。

使用 gcloud config

  • 设置默认项目 ID
    gcloud config set project PROJECT_ID
  • 如果您使用的是可用区级集群,请设置默认计算可用区
    gcloud config set compute/zone COMPUTE_ZONE
  • 如果您使用的是区域级集群,请设置默认计算区域
    gcloud config set compute/region COMPUTE_REGION
  • gcloud 更新到最新版本:
    gcloud components update

创建 Deployment

下面是 Deployment 的清单:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-mc-deployment
spec:
  selector:
    matchLabels:
      app: products
      department: sales
  replicas: 3
  template:
    metadata:
      labels:
        app: products
        department: sales
    spec:
      containers:
      - name: hello
        image: "gcr.io/google-samples/hello-app:2.0"
        env:
        - name: "PORT"
          value: "50001"
      - name: hello-again
        image: "gcr.io/google-samples/node-hello:1.0"
        env:
        - name: "PORT"
          value: "50002"

Deployment 有 3 个 Pod,每个 Pod 有两个容器。一个容器运行 hello-app 并侦听 TCP 端口 50001。另一个容器运行 node-hello 并侦听 TCP 端口 50002。

将此清单复制到名为 my-mc-deployment.yaml 的文件,然后创建该 Deployment:

kubectl apply -f my-mc-deployment.yaml

创建 Service

下面是 Service 的清单:

apiVersion: v1
kind: Service
metadata:
  name: my-mc-service
spec:
  type: NodePort
  selector:
    app: products
    department: sales
  ports:
  - name: my-first-port
    protocol: TCP
    port: 60001
    targetPort: 50001
  - name: my-second-port
    protocol: TCP
    port: 60002
    targetPort: 50002

Service 清单中的 selector 字段指示所有带 app: products 标签和 department: sales 标签的 Pod 均为该 Service 的成员。因此,您在上一步中创建的 Deployment 的 Pod 是 Service 的成员。

Service 清单的 ports 字段是一组 ServicePort 对象。当客户端将请求发送到 my-first-port 上的 Service 时,该请求将被转发到端口 50001 上的某个成员 Pod。当客户端将请求发送到 my-second-port 上的 Service 时,该请求将被转发到端口 50002 上的某个成员 Pod。

将此清单复制到名为 my-mc-service.yaml 的文件,然后创建该 Service:

kubectl apply -f my-mc-service.yaml

创建证书和密钥

要完成本页中的练习,您需要两个证书,每个证书都有一个相应的密钥。每个证书的公用名 (CN) 必须与您拥有的域名一致。如果您的两个证书文件的公用名都有适当的值,可跳转至下一部分。

创建第一个密钥:

openssl genrsa -out test-ingress-1.key 2048

创建第一个证书签名请求:

openssl req -new -key test-ingress-1.key -out test-ingress-1.csr \
    -subj "/CN=first-domain"

其中 first-domain 是您拥有的域名。

例如,假设您想让负载平衡器处理来自 example.com 网域的请求。您的证书签名请求将如下所示:

openssl req -new -key test-ingress-1.key -out test-ingress-1.csr \
    -subj "/CN=example.com"

创建第一个证书:

openssl x509 -req -days 365 -in test-ingress-1.csr -signkey test-ingress-1.key \
    -out test-ingress-1.crt

创建第二个密钥:

openssl genrsa -out test-ingress-2.key 2048

创建第二个证书签名请求:

openssl req -new -key test-ingress-2.key -out test-ingress-2.csr \
    -subj "/CN=second-domain"

其中 second-domain 是您拥有的另一个域名。

例如,假设您想让负载平衡器处理来自 examplepetstore.com 网域的请求。您的证书签名请求将如下所示:

openssl req -new -key test-ingress-2.key -out test-ingress-2.csr \
    -subj "/CN=examplepetstore.com"

创建第二个证书:

openssl x509 -req -days 365 -in test-ingress-2.csr -signkey test-ingress-2.key \
    -out test-ingress-2.crt

如需详细了解证书和密钥,请参阅 SSL 证书概览

您现在有了 2 个证书文件和 2 个密钥文件。

其余任务使用以下占位符来引用您的网域、证书和密钥:

  • first-cert-file 是第一个证书文件的路径。
  • first-key-file 是与第一个证书对应的密钥文件的路径。
  • first-domain 是您拥有的域名。
  • first-secret-name 是包含第一个证书和密钥的 Secret 的名称。
  • second-cert-file 是第二个证书文件的路径。
  • second-key-file 是与第二个证书对应的密钥文件的路径。
  • second-domain 是您拥有的另一个域名。
  • second-secret-name 是包含第二个证书和密钥的 Secret 的名称。

为 Ingress 指定证书

下一步是创建 Ingress 对象。在 Ingress 清单中,您可使用下列任一方法为负载平衡器提供证书:

  • Secret
  • 预共享证书

选择 SECRETS 标签或 PRE-SHARED CERTS 标签即可选中这两种方法之一:

Secret

创建 Secret

创建包含第一个证书和密钥的 Secret

kubectl create secret tls first-secret-name \
  --cert first-cert-file --key first-key-file

创建包含第二个证书和密钥的 Secret:

kubectl create secret tls second-secret-name \
  --cert second-cert-file --key second-key-file

创建 Ingress

以下是 Ingress 的清单:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-mc-ingress
spec:
  tls:
  - secretName: first-secret-name
  - secretName: second-secret-name
  rules:
  - host: first-domain
    http:
      paths:
      - backend:
          serviceName: my-mc-service
          servicePort: my-first-port
  - host: second-domain
    http:
      paths:
      - backend:
          serviceName: my-mc-service
          servicePort: my-second-port

将此清单复制到名为 my-mc-ingress.yaml 的文件。将 first-domainsecond-domain 替换为您拥有的域名,例如 example.comexamplepetstore.com

创建 Ingress:

kubectl apply -f my-mc-ingress.yaml

创建 Ingress 时,GKE 入站流量控制器会创建一个 HTTP(S) 负载平衡器。请等待一分钟,让 GKE 向负载平衡器分配外部 IP 地址。

描述 Ingress:

kubectl describe ingress my-mc-ingress

此输出显示两个 Secret 都与 Ingress 相关联。它还显示了负载平衡器的外部 IP 地址。

Name: my-mc-ingress
Address: 203.0.113.1
...
TLS:
  first-secret-name terminates
  second-secret-name terminates
Rules:
  Host              Path  Backends
  ----              ----  --------
  example.com
                     my-mc-service:my-first-port (<none>)
  examplepetstore.com
                     my-mc-service:my-second-port (<none>)
Annotations:
...
Events:
  Type    Reason  Age   From                     Message
  ----    ------  ----  ----                     -------
  Normal  ADD     3m    loadbalancer-controller  default/my-mc-ingress
  Normal  CREATE  2m    loadbalancer-controller  ip: 203.0.113.1

预共享证书

使用预共享证书

在 Google Cloud 项目中创建证书资源:

gcloud compute ssl-certificates create test-ingress-1 \
--certificate first-cert-file --private-key first-key-file

其中:

在 Google Cloud 项目中创建第二个证书资源:

gcloud compute ssl-certificates create test-ingress-2 \
--certificate second-cert-file --private-key second-key-file

其中:

  • second-cert-file 是您的第二个证书文件。
  • second-key-file 是您的第二个密钥文件。

查看您的证书资源:

gcloud compute ssl-certificates list

此输出显示您具有名为 test-ingres-1test-ingress-2 的证书资源:

NAME                CREATION_TIMESTAMP
test-ingress-1      2018-11-03T12:08:47.751-07:00
test-ingress-2      2018-11-03T12:09:25.359-07:00

下面是 Ingress 的清单,它在注释中列出了预共享证书资源:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-psc-ingress
  annotations:
    ingress.gcp.kubernetes.io/pre-shared-cert: "test-ingress-1,test-ingress-2"
spec:
  rules:
  - host: first-domain
    http:
      paths:
      - backend:
          serviceName: my-mc-service
          servicePort: my-first-port
  - host: second-domain
    http:
      paths:
      - backend:
          serviceName: my-mc-service
          servicePort: my-second-port

将此清单复制到名为 my-psc-ingress.yaml 的文件。将 first-domainsecond-domain 替换为您的域名。

创建 Ingress:

kubectl apply -f my-psc-ingress.yaml

请等待一分钟,让 GKE 向负载平衡器分配外部 IP 地址。

描述 Ingress:

kubectl describe ingress my-psc-ingress

输出结果显示 Ingress 与名为 test-ingress-1test-ingress-2 的预共享证书相关联。它还显示了负载平衡器的外部 IP 地址:

Name:             my-psc-ingress
Address:          203.0.113.2
...
Rules:
  Host              Path  Backends
  ----              ----  --------
  example.com
                     my-mc-service:my-first-port (<none>)
  examplepetstore.com
                     my-mc-service:my-second-port (<none>)
Annotations:
  ...
  ingress.gcp.kubernetes.io/pre-shared-cert:    test-ingress-1,test-ingress-2
  ...
  ingress.kubernetes.io/ssl-cert:               test-ingress-1,test-ingress-2
Events:
  Type    Reason  Age   From                     Message
  ----    ------  ----  ----                     -------
  Normal  ADD     2m    loadbalancer-controller  default/my-psc-ingress
  Normal  CREATE  1m    loadbalancer-controller  ip: 203.0.113.2

测试负载平衡器

请等待 5 分钟左右,让 GKE 完成负载平衡器的配置。

要执行此步骤,您需要拥有两个域名,并且两个域名都必须解析 HTTP(S) 负载平衡器的外部 IP 地址。

使用您的第一个域名向负载平衡器发送请求:

curl -v https://first-domain

此输出显示您的第一个证书用于 TLS 握手。如果您的第一个网域是 example.com,则输出的结果如下所示:

...
*   Trying 203.0.113.1...
...
* Connected to example.com (203.0.113.1) port 443 (#0)
...
* TLSv1.2 (IN), TLS handshake, Certificate (11):
...
* Server certificate:
*  subject: CN=example.com
...
> Host: example.com
...
&lt;
Hello, world!
Version: 2.0.0
...

使用您的第二个域名向负载平衡器发送请求:

curl -v https://second-domain

此输出显示您的第二个证书用于 TLS 握手。如果您的第二个网域是 examplepetstore.com,则输出的结果如下所示:

...
*   Trying 203.0.113.1...
...
* Connected to examplepetstore.com (203.0.113.1) port 443 (#0)
...
* Server certificate:
*  subject: CN=examplepetstore.com
...
> Host: examplepetstore.com
...
Hello Kubernetes!

Ingress 对象的 hosts 字段

IngressSpec 具有一个 tls 字段,它是一组 IngressTLS 对象。每个 IngressTLS 对象都有一个 hosts 字段和 SecretName 字段。在 GKE 中,不使用 hosts 字段。GKE 在 Secret 中读取证书的公用名 (CN)。如果公用名与客户端请求中的域名匹配,则负载平衡器向客户端提供所匹配的证书。

提供哪种证书?

负载平衡器根据以下规则选择证书

  • 如果 Ingress 中同时列出了 Secret 和预共享证书,则预共享证书优先于 Secret。换句话说,仍然包含 Secret,但首先提供预共享证书。

  • 如果所有证书的公用名 (CN) 与客户端请求中的域名均不匹配,则负载平衡器将提供主证书。

  • 对于 tls 块中列出的 Secret,主证书位于列表中的第一个 Secret 中。

  • 对于注释中列出的预共享证书,主证书是列表中的第一个证书。

证书轮替最佳做法

如果您希望轮替证书的内容(Secret 或预共享),请遵循以下最佳做法:

  • 使用其他名称创建包含新证书数据的新 Secret 或预共享证书。按照前面提供的说明,将此资源(以及现有资源)关联到 Ingress。对更改感到满意后,您可以从 Ingress 中移除旧证书。
  • 如果您不想中断流量,则可以从 Ingress 中移除旧资源,预配名称相同但内容不同的新资源,然后将其重新关联到 Ingress。

为避免自行管理证书轮替,请参阅 Google 管理的 SSL 功能。

问题排查

如果指定的 Secret 无效或不存在,则会发生 Kubernetes 事件错误。您可以按如下方式检查 Kubernetes 事件的 Ingress:

kubectl describe ingress

输出如下所示:

Name:             my-ingress
Namespace:        default
Address:          203.0.113.3
Default backend:  hello-server:8080 (10.8.0.3:8080)
TLS:
  my-faulty-Secret terminates
Rules:
  Host  Path  Backends
  ----  ----  --------
  *     *     my-service:443 (10.8.0.3:443)
Events:
   Error during sync: cannot get certs for Ingress default/my-ingress:
 Secret "my-faulty-ingress" has no 'tls.crt'

后续步骤