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


本页面介绍如何为 Google Kubernetes Engine (GKE) 集群中的 Ingress 资源配置多个 SSL 证书。

概览

如果您想要接受来自客户端的 HTTPS 请求,则应用负载均衡器必须具有证书,这样才能向客户端证明其身份。负载均衡器还必须具有私钥才能完成 HTTPS 握手。

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

负载均衡器使用服务器名称指示 (SNI),根据 TLS 握手中的域名确定要向客户端提供哪个证书。如果客户端不使用 SNI,或者客户端使用的域名与其中一个证书中的公用名 (CN) 不匹配,则负载均衡器会使用 Ingress 中列出的第一个证书。

下图展示了负载均衡器根据请求中使用的域名将流量发送到不同后端:

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

您可以使用以下方法为应用负载均衡器提供 SSL 证书:

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

  • Kubernetes Secret。Secret 中包含您自行创建的证书和密钥。您将 Secret 的名称添加到 Ingress 清单的 tls 字段。

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

概览

以下是本文档中的步骤概览:

  1. 创建 Deployment

  2. 创建 Service

  3. 创建 2 个证书文件和 2 个密钥文件或 2 个 ManagedCertificate 对象。您必须在部署负载均衡器的项目和命名空间中配置这些证书。

  4. 创建使用 Secret 或预共享证书的 Ingress。当您创建 Ingress 时,GKE 会创建并配置应用负载均衡器。

  5. 测试应用负载均衡器。

准备工作

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

  • 启用 Google Kubernetes Engine API。
  • 启用 Google Kubernetes Engine API
  • 如果您要使用 Google Cloud CLI 执行此任务,请安装初始化 gcloud CLI。 如果您之前安装了 gcloud CLI,请运行 gcloud components update 以获取最新版本。
  • 您必须拥有两个域名。域名长度不得超过 63 个字符。

限制

  • 只有使用外部应用负载均衡器的 GKE Ingress 支持 Google 管理的证书。Google 管理的证书不支持第三方 Ingress 控制器。
  • 对于内部应用负载均衡器,您必须在 Ingress 清单中停用 HTTP。对于外部负载均衡器,您不需要这样做。
  • 您不得手动更改或更新应用负载均衡器的配置。这意味着您不得修改负载均衡器的任何组件,包括目标代理、网址映射和后端服务。您所做的任何更改都会被 GKE 覆盖。

创建 Deployment

  1. 将以下清单保存为 my-mc-deployment.yaml

    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"
    

    此清单描述了具有三个 Pod 的 Deployment。每个 Pod 都有两个容器。一个容器运行 hello-app:1.0 并监听 TCP 端口 50001。另一个容器运行 hello-app:2.0 并监听 TCP 端口 50002。

  2. 将清单应用到您的集群:

    kubectl apply -f my-mc-deployment.yaml
    

创建一个 Service

  1. 将以下清单保存为 my-mc-service.yaml

    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 的成员。
    • ports:指定当客户端向 my-first-port 上的 Service 发送请求时,GKE 会将请求转发到端口 50001 上的某个成员 Pod。当客户端将请求发送到 my-second-port 上的 Service 时,GKE 会将请求转发到端口 50002 上的某个成员 Pod。
  2. 将清单应用到您的集群:

    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

  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

  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:您拥有的第二个网域。

ManagedCertificate 对象的名称不同于其创建的实际证书的名称。您只需知道 ManagedCertificate 对象的名称,以在 Ingress 中使用它们。

为 Ingress 指定证书

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

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

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

  1. 将以下清单保存为 my-mc-ingress.yaml

    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:
                  number: 60001
      - host: SECOND_DOMAIN
        http:
          paths:
          - pathType: ImplementationSpecific
            backend:
              service:
                name: my-mc-service
                port:
                  number: 60002
    

    FIRST_DOMAINSECOND_DOMAIN 替换为您拥有的域名,例如 example.comexamplepetstore.com

  2. 将清单应用到您的集群:

    kubectl apply -f my-mc-ingress.yaml
    
  3. 描述 Ingress:

    kubectl describe ingress my-mc-ingress
    

    输出类似于以下内容:

    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
    

    此输出显示两个 Secret 都与 Ingress 相关联。它还显示了负载均衡器的外部 IP 地址。 如果未设置外部 IP 地址,请等待几分钟,然后重试该命令。

预共享证书

  1. 创建证书:

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

    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
    

    输出类似于以下内容:

    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
    

创建 Ingress

  1. 将以下清单保存为 my-psc-ingress.yaml

    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:
                  number: 60001
      - host: SECOND_DOMAIN
        http:
          paths:
          - pathType: ImplementationSpecific
            backend:
              service:
                name: my-mc-service
                port:
                  number: 60002
    

    FIRST_DOMAINSECOND_DOMAIN 替换为您的域名。

    此清单描述了一个在注解中列出预共享证书资源的 Ingress。

  2. 将清单应用到您的集群:

    kubectl apply -f my-psc-ingress.yaml
    
  3. 描述 Ingress:

    kubectl describe ingress my-psc-ingress
    

    输出类似于以下内容:

    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
    

    输出结果显示 Ingress 与名为 FIRST_CERT_NAMESECOND_CERT_NAME 的预共享证书相关联。它还显示了负载均衡器的外部 IP 地址。如果未设置外部 IP 地址,请等待几分钟,然后重试该命令。

Google 管理的证书

创建 Ingress

  1. 将以下清单保存为 my-gmc-ingress.yaml

    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:
                  number: 60001
      - host: SECOND_DOMAIN
        http:
          paths:
          - pathType: ImplementationSpecific
            backend:
              service:
                name: my-mc-service
                port:
                  number: 60002
    

    FIRST_DOMAINSECOND_DOMAIN 替换为您的域名。

    此清单描述了一个在注解中列出预共享证书资源的 Ingress。

  2. 将清单应用到您的集群:

    kubectl apply -f my-gmc-ingress.yaml
    
  3. 描述 Ingress:

    kubectl describe ingress my-gmc-ingress
    

    输出类似于以下内容:

    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
    

    输出结果显示 Ingress 与名为 FIRST_CERT_NAMESECOND_CERT_NAME 的代管式证书相关联。 GKE 会自动使用您使用 ManagedCertificate 对象创建的 Google 管理的证书填充 ingress.gcp.kubernetes.io/pre-shared-certingress.kubernetes.io/ssl-cert 注解。输出还显示了负载均衡器的外部 IP 地址。如果未设置外部 IP 地址,请等待几分钟,然后重试该命令。

测试负载均衡器

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

如果您使用了 Google 管理的证书,则完成配置可能需要相当长的时间,因为系统需要预配证书并验证给定网域的 DNS 配置。

如需测试负载均衡器,您必须拥有两个域名,并且两个域名都必须解析外部应用负载均衡器的外部 IP 地址。

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

    curl -v https://FIRST_DOMAIN
    

    您可能需要使用 curl -k 选项来执行不安全的 SSL 传输,以便 curl 接受自签名证书。

    输出类似于以下内容:

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

    此输出显示您的第一个证书用于 TLS 握手。

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

    curl -v https://SECOND_DOMAIN
    

    输出类似于以下内容:

    ...
    *   Trying 203.0.113.1...
    ...
    * Connected to SECOND_DOMAIN (203.0.113.1) port 443 (#0)
    ...
    * Server certificate:
    *  subject: CN=SECOND_DOMAIN
    ...
    > Host: SECOND_DOMAIN
    ...
    Hello, world!
    Version: 2.0.0
    

    此输出显示您的第二个证书用于 TLS 握手。

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'

后续步骤

  • 如果应用在不同区域的多个 GKE 集群上运行,请配置多集群 Ingress,以将流量路由到离用户最近区域的集群。