将 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 个密钥文件或 2 个 ManagedCertificate 对象。请确保在负载均衡器部署所在的项目和命名空间中配置这些证书。

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

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

准备工作

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

  • 确保您已启用 Google Kubernetes Engine API。
  • 启用 Google Kubernetes Engine API
  • 确保您已安装 Cloud SDK
  • 使用以下方法之一为您的项目设置默认 gcloud 命令行工具设置:
    • 使用 gcloud init(如果您想要在系统引导下完成项目默认设置)。
    • 使用 gcloud config(如果您想要单独设置项目 ID、可用区和区域。

    gcloud init

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

      gcloud init

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

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

    gcloud config

    1. 设置默认项目 ID
      gcloud config set project PROJECT_ID
    2. 设置默认的 Compute Engine 区域(例如 us-central1):
      gcloud config set compute/region COMPUTE_REGION
    3. 设置默认的 Compute Engine 可用区(例如 us-central1-c):
      gcloud config set compute/zone COMPUTE_ZONE
    4. gcloud 更新到最新版本:
      gcloud components update

    通过设置默认位置,您可以避免 gcloud 工具中出现以下错误:One of [--zone, --region] must be supplied: Please specify location

创建 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: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0"
        env:
        - name: "PORT"
          value: "50001"
      - name: hello-again
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:2.0"
        env:
        - name: "PORT"
          value: "50002"

Deployment 有 3 个 Pod,每个 Pod 有两个容器。一个容器运行 hello-app:1.0 并侦听 TCP 端口 50001。另一个容器运行 hello-app:2.0 并侦听 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) 必须与您拥有的域名一致。您可以手动创建这些证书,也可以使用 Google 管理的证书。如果您的两个证书文件的公用名都有适当的值,可跳转至下一部分。

用户管理的证书

  1. 创建第一个密钥:

    openssl genrsa -out test-ingress-1.key 2048
    
  2. 创建第一个证书签名请求:

    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"
    
  3. 创建第一个证书:

    openssl x509 -req -days 365 -in test-ingress-1.csr -signkey test-ingress-1.key \
        -out test-ingress-1.crt
    
  4. 创建第二个密钥:

    openssl genrsa -out test-ingress-2.key 2048
    
  5. 创建第二个证书签名请求:

    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"
    
  6. 创建第二个证书:

    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 的名称。

Google 管理的证书

如需创建 Google 管理的证书,您必须将 ManagedCertificate 对象添加到 Ingress 的命名空间。您可以使用以下模板为网域定义证书:

  apiVersion: networking.gke.io/v1
  kind: ManagedCertificate
  metadata:
    name: FIRST_CERT_NAME
  spec:
    domains:
      - FIRST_DOMAIN
  ---
  apiVersion: networking.gke.io/v1
  kind: ManagedCertificate
  metadata:
    name: SECOND_CERT_NAME
  spec:
    domains:
      - SECOND_DOMAIN

替换以下内容:

  • FIRST_CERT_NAME:第一个 ManagedCertificate 对象的名称。
  • FIRST_DOMAIN:您拥有的第一个网域。
  • SECOND_CERT_NAME:第二个 ManagedCertificate 对象的名称。
  • SECOND_DOMAIN:您拥有的第二个网域。

为 Ingress 指定证书

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

  • Secret
  • 预共享证书
  • Google 管理的证书

如需选择其中一种方法,您可以选择其中一个标签页:

Secret

创建 Secret

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

    kubectl create secret tls FIRST_SECRET_NAME \
        --cert FIRST_CERT_FILE --key FIRST_KEY_FILE
    
  2. 创建包含第二个证书和密钥的 Secret:

    kubectl create secret tls SECOND_SECRET_NAME \
        --cert SECOND_CERT_FILE --key SECOND_KEY_FILE
    

创建 Ingress

以下是 Ingress 的清单:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-mc-ingress
spec:
  tls:
  - secretName: FIRST_SECRET_NAME
  - secretName: SECOND_SECRET_NAME
  rules:
  - host: FIRST_DOMAIN
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: my-mc-service
            port:
              name: my-first-port
  - host: SECOND_DOMAIN
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: my-mc-service
            port:
              name: my-second-port
  1. 将此清单复制到名为 my-mc-ingress.yaml 的文件。将 FIRST_DOMAINSECOND_DOMAIN 替换为您拥有的域名,例如 example.comexamplepetstore.com

  2. 创建 Ingress:

    kubectl apply -f my-mc-ingress.yaml
    

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

  3. 描述 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
      ----              ----  --------
      FIRST_DOMAIN
                         my-mc-service:my-first-port (<none>)
      SECOND_DOMAIN
                         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
    

预共享证书

创建预共享证书

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

    gcloud compute ssl-certificates create FIRST_CERT_NAME \
        --certificate FIRST_CERT_FILE  --private-key FIRST_KEY_FILE
    

    替换以下内容:

    • FIRST_CERT_NAME:您的第一个证书的名称。
    • FIRST_CERT_FILE:您的第一个证书文件
    • FIRST_KEY_FILE:您的第一个密钥文件。
  2. 在 Google Cloud 项目中创建第二个证书资源:

    gcloud compute ssl-certificates create SECOND_CERT_NAME \
        --certificate SECOND_CERT_FILE --private-key SECOND_KEY_FILE
    

    替换以下内容:

    • SECOND_CERT_NAME:您的第二个证书的名称。
    • SECOND_CERT_FILE:您的第二个证书文件。
    • SECOND_KEY_FILE:您的第二个密钥文件。
  3. 查看您的证书资源:

    gcloud compute ssl-certificates list
    

    此输出显示您具有名为 FIRST_CERT_NAMESECOND_CERT_NAME 的证书资源:

    NAME                   CREATION_TIMESTAMP
    FIRST_CERT_NAME      2018-11-03T12:08:47.751-07:00
    SECOND_CERT_NAME     2018-11-03T12:09:25.359-07:00
    

创建 GKE Ingress

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

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-psc-ingress
  annotations:
    ingress.gcp.kubernetes.io/pre-shared-cert: "FIRST_CERT_NAME,SECOND_CERT_NAME"
spec:
  rules:
  - host: FIRST_DOMAIN
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: my-mc-service
            port:
              name: my-first-port
  - host: SECOND_DOMAIN
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: my-mc-service
            port:
              name: my-second-port
  1. 将此清单复制到名为 my-psc-ingress.yaml 的文件。将 FIRST_DOMAINSECOND_DOMAIN 替换为您的域名。

  2. 创建 Ingress:

    kubectl apply -f my-psc-ingress.yaml
    

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

  3. 描述 Ingress:

    kubectl describe ingress my-psc-ingress
    

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

    Name:             my-psc-ingress
    Address:          203.0.113.2
    ...
    Rules:
      Host              Path  Backends
      ----              ----  --------
      FIRST_DOMAIN
                         my-mc-service:my-first-port (<none>)
      SECOND_DOMAIN
                         my-mc-service:my-second-port (<none>)
    Annotations:
      ...
      ingress.gcp.kubernetes.io/pre-shared-cert:    FIRST_CERT_NAME,SECOND_CERT_NAME
      ...
      ingress.kubernetes.io/ssl-cert:               FIRST_CERT_NAME,SECOND_CERT_NAME
    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
    

Google 管理的证书

创建 GKE Ingress

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

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-gmc-ingress
  annotations:
    networking.gke.io/managed-certificates: "FIRST_CERT_NAME,SECOND_CERT_NAME"
spec:
  rules:
  - host: FIRST_DOMAIN
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: my-mc-service
            port:
              name: my-first-port
  - host: SECOND_DOMAIN
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: my-mc-service
            port:
              name: my-second-port
  1. 将此清单复制到名为 my-gmc-ingress.yaml 的文件。将 FIRST_DOMAINSECOND_DOMAIN 替换为您的域名。

  2. 创建 Ingress:

    kubectl apply -f my-gmc-ingress.yaml
    

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

  3. 描述 Ingress:

    kubectl describe ingress my-gmc-ingress
    

    输出结果显示 Ingress 与名为 FIRST_CERT_NAMESECOND_CERT_NAME 的代管式证书相关联。GKE 会自动填充 ingress.gcp.kubernetes.io/pre-shared-certingress.kubernetes.io/ssl-cert 注释,以指向您使用 ManagedCertificate 对象创建的 Google 管理的证书。 此输出还会显示负载均衡器的外部 IP 地址:

    Name:             my-gmc-ingress
    Address:          203.0.113.2
    ...
    Rules:
      Host              Path  Backends
      ----              ----  --------
      FIRST_DOMAIN
                         my-mc-service:my-first-port (<none>)
      SECOND_DOMAIN
                         my-mc-service:my-second-port (<none>)
    Annotations:
      ...
      ingress.gcp.kubernetes.io/pre-shared-cert:    mcrt-a6e41ce4-2b39-4334-84ce-867ff543c424,mcrt-bbff4116-f014-4800-a43a-4095bffeb4f4
      ...
      ingress.kubernetes.io/ssl-cert:               mcrt-a6e41ce4-2b39-4334-84ce-867ff543c424,mcrt-bbff4116-f014-4800-a43a-4095bffeb4f4
      networking.gke.io/managed-certificates:       FIRST_CERT_NAME,SECOND_CERT_NAME
    Events:
      Type    Reason  Age   From                     Message
      ----    ------  ----  ----                     -------
      Normal  ADD     2m    loadbalancer-controller  default/my-gmc-ingress
      Normal  CREATE  1m    loadbalancer-controller  ip: 203.0.113.2
    

测试负载平衡器

请等待 5 分钟左右,让 GKE 完成负载平衡器的配置。 对于 Google 管理的证书,完成配置可能需要相当长的时间,因为系统需要预配证书并验证给定网域的 DNS 配置。

要执行此步骤,您需要拥有两个域名,并且两个域名都必须解析 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
...
Hello, world!
Version: 1.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, world!
Version: 2.0.0

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'

后续步骤