Ingress による HTTPS ロード バランシングにおける複数の SSL 証明書の使用


このページでは、Google Kubernetes Engine(GKE)クラスタの Ingress リソースに複数の SSL 証明書を構成する方法について説明します。

概要

クライアントからの HTTPS リクエストを受け入れるには、アプリケーション ロードバランサは証明書を持っていなければなりません。これにより、適切な送信先であることをクライアントに示すことができます。さらに、ロードバランサには、HTTPS handshake を完了するための秘密鍵も必要です。

ロードバランサがクライアントからの HTTPS リクエストを受け入れると、クライアントとロードバランサ間のトラフィックは TLS で暗号化されます。ただし、TLS 暗号化はロードバランサで終端し、アプリケーションへのリクエストの転送は暗号化なしに行われます。Ingress によるアプリケーション ロードバランサを構成する際には、最大 10 個の TLS 証明書をクライアントに提示するようにロードバランサを構成できます。

ロードバランサでは、TLS handshake のドメイン名に基づいて、Server Name Indication(SNI)を使用してクライアントに提示する証明書が決定されます。クライアントで SNI が使用されていないか、いずれの証明書の共通名(CN)とも一致しないドメイン名が使用されている場合は、Ingress で最初に表示される証明書が使用されます。

次の図は、リクエストで使用されているドメイン名に応じて、複数の異なるバックエンドにトラフィックを送信するロードバランサを示しています。

Ingress システムでの複数の SSL 証明書の使用を示す図

次の方法で、アプリケーション ロードバランサに SSL 証明書を提供できます。

  • 自身で管理する Google Cloud SSL 証明書。SSL 証明書は、Google Cloud プロジェクトにアップロードする事前共有証明書を使用します。

  • Kubernetes Secret。Secret には、自身で作成した証明書と鍵が保持されます。Secret の名前を Ingress マニフェストの tls フィールドに追加します。

同じ Ingress で複数の方法を使用できるため、1 つの方法で問題が発生してもダウンタイムなしに別の方法に移動できます。

全体像

このドキュメントの手順の概要は次のとおりです。

  1. Deployment を作成します。

  2. Service を作成します。

  3. 2 つの証明書ファイルと 2 つの鍵ファイルを作成するか、2 つの ManagedCertificate オブジェクトを作成します。これらの証明書は、ロードバランサがデプロイされているプロジェクトと同じ Namespace で構成する必要があります。

  4. Secret または事前共有証明書のいずれかを使用する Ingress を作成します。Ingress を作成すると、GKE によってアプリケーション ロードバランサが作成されて構成されます。

  5. アプリケーション ロードバランサをテストします。

始める前に

始める前に、次の作業が完了していることを確認してください。

  • Google Kubernetes Engine API を有効にする。
  • Google Kubernetes Engine API の有効化
  • このタスクに Google Cloud CLI を使用する場合は、gcloud CLI をインストールして初期化する。すでに gcloud CLI をインストールしている場合は、gcloud components update を実行して最新のバージョンを取得する。
  • 2 つのドメイン名を所有している必要があります。ドメイン名は 63 文字以下にする必要があります。

制限事項

  • Google マネージド証明書は、外部アプリケーション ロードバランサを使用する GKE Ingress でのみサポートされています。Google マネージド証明書は、サードパーティの Ingress コントローラをサポートしていません。
  • 内部アプリケーション ロードバランサの場合は、Ingress マニフェストで HTTP を無効にする必要があります。外部ロードバランサの場合、この操作は必要ありません。
  • アプリケーション ロードバランサの構成を手動で変更または更新しないでください。ターゲット プロキシ、URL マップ、バックエンド サービスなど、ロードバランサのいずれのコンポーネントも編集しないでください。変更を加えたとしても 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"
    

    このマニフェストでは、3 つの Pod を持つ Deployment を記述しています。各 Pod には 2 つのコンテナがあります。一方のコンテナでは 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 の 1 つにリクエストを転送するように指定します。クライアントが my-second-port の Service にリクエストを送信すると、GKE はいずれかのメンバー Pod のポート 50002 にリクエストを転送します。
  2. マニフェストをクラスタに適用します。

    kubectl apply -f my-mc-service.yaml
    

証明書と鍵を作成する

このページの演習を行うには、それぞれ対応する鍵とともに 2 つの証明書が必要になります。各証明書には、所有しているドメイン名と同じ共通名(CN)を設定する必要があります。

これらの証明書を手動で作成することも、Google マネージド証明書を使用することもできます。

共通名として適切な値が設定された 2 つの証明書ファイルがすでに存在する場合は、次のセクションまでスキップできます。

ユーザー マネージド証明書

  1. 1 番目の鍵を作成します。

    openssl genrsa -out test-ingress-1.key 2048
    
  2. 1 番目の証明書署名リクエストを作成します。

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

    FIRST_DOMAIN は、所有しているドメイン名(example.com など)に置き換えます。

  3. 1 番目の証明書を作成します。

    openssl x509 -req -days 365 -in test-ingress-1.csr -signkey test-ingress-1.key \
        -out test-ingress-1.crt
    
  4. 2 番目の鍵を作成します。

    openssl genrsa -out test-ingress-2.key 2048
    
  5. 2 番目の証明書署名リクエストを作成します。

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

    SECOND_DOMAIN は、所有している別のドメイン名(examplepetstore.com など)に置き換えます。

  6. 2 番目の証明書を作成します。

    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: 2 番目の証明書ファイルのパス。
  • SECOND_KEY_FILE: 2 番目の証明書に対応する鍵ファイルのパス。
  • SECOND_DOMAIN: 所有している 2 番目のドメイン名。
  • SECOND_SECRET_NAME: 2 番目の証明書と鍵が含まれている Secret の名前。

Google マネージド証明書

Google マネージド証明書を作成するには、Ingress の Namespace に ManagedCertificate オブジェクトを追加する必要があります。ドメインの証明書を定義するには、次のテンプレートを使用します。

  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: 1 つ目の ManagedCertificate オブジェクトの名前。
  • FIRST_DOMAIN: 所有する 1 つ目のドメイン。
  • SECOND_CERT_NAME: 2 番目の ManagedCertificate オブジェクトの名前。
  • SECOND_DOMAIN: 所有する 2 番目のドメイン

ManagedCertificate オブジェクトの名前は、実際に作成する証明書の名前とは異なります。Ingress で使用する ManagedCertificate オブジェクトの名前を確認しておいてください。

Ingress の証明書を指定する

次のステップは Ingress オブジェクトの作成です。Ingress マニフェストで、次のいずれかの方法を使用してロードバランサの証明書を提供します。

  • Secret
  • 事前共有証明書
  • Google マネージド証明書

Secret

  1. 1 つ目の証明書と鍵を保持する Secret を作成します。

    kubectl create secret tls FIRST_SECRET_NAME \
        --cert=FIRST_CERT_FILE \
        --key=FIRST_KEY_FILE
    
  2. 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
    

    出力には、2 つの 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. 2 番目の証明書を作成します。

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

    次のように置き換えます。

    • SECOND_CERT_NAME: 2 番目の証明書の名前。
    • SECOND_CERT_FILE: 2 番目の証明書ファイル。
    • SECOND_KEY_FILE: 2 番目の鍵ファイル。
  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-cert アノテーションと ingress.kubernetes.io/ssl-cert アノテーションに自動的に設定します。また、出力には、ロードバランサの外部 IP アドレスも示されます。外部 IP アドレスが設定されていない場合は、数分待ってからコマンドを再度試してください。

ロードバランサをテストする

GKE によるロードバランサの構成が終了するまで 5 分程度待ちます。

Google マネージド証明書を使用した場合、システムで証明書のプロビジョニングとドメインの DNS 構成の検証が必要になるため、構成が完了するまでに時間がかかることがあります。

ロードバランサをテストするには、2 つのドメイン名を所有し、両方のドメイン名が外部アプリケーション ロードバランサの外部 IP アドレスに解決される必要があります。

  1. 1 つ目のドメイン名を使用してリクエストをロードバランサに送信します。

    curl -v https://FIRST_DOMAIN
    

    curl が自己署名証明書を受け入れるには、curl -k オプションを使用して、安全でない SSL 転送を実行しなければならない場合があります。

    出力は次のようになります。

    ...
    *   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 handshake で 1 つ目の証明書が使用されたことが示されます。

  2. 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 handshake で 2 つ目の証明書が使用されたことが示されます。

Ingress オブジェクトの hosts フィールド

IngressSpec には、IngressTLS オブジェクトの配列である tls フィールドがあります。各 IngressTLS オブジェクトには、hosts フィールドと SecretName フィールドがあります。 GKE では、hosts フィールドは使用されません。GKE では Secret 内の証明書から共通名(CN)が読み取られます。共通名がクライアント リクエストのドメイン名と一致すると、ロードバランサによって該当する証明書がクライアントに提示されます。

証明書提示の仕組み

ロードバランサでは、次のルールに従って証明書が選択されます。

  • Secret と事前共有証明書の両方が Ingress のリストに存在する場合は、事前共有証明書が Secret よりも優先されます。つまり、Secret は含まれますが、事前共有証明書が最初に表示されます。

  • 共通名(CN)がクライアント リクエストのドメイン名と一致する証明書が存在しない場合は、プライマリ証明書が提示されます。

  • tls ブロックに複数の Secret がリストされている場合は、プライマリ証明書はリストの 1 つ目の Secret 内にあります。

  • アノテーションに複数の事前共有証明書がリストされている場合は、リスト内の 1 つ目の証明書がプライマリ証明書になります。

証明書のローテーションのベスト プラクティス

Secret または事前共有証明書のコンテンツをローテーションする場合のベスト プラクティスは次のとおりです。

  • 新しい証明書データが含まれる別の名前の新しい Secret または事前共有証明書を作成します。上記の手順を使用して、このリソースを(既存のリソースとともに)Ingress に接続します。変更が完了したら、Ingress から古い証明書を削除できます。
  • トラフィックの中断を気にしない場合は、Ingress から古いリソースを削除し、同じ名前で内容が異なる新しいリソースをプロビジョニングして、Ingress に再接続します。
証明書のローテーションを自身で管理しない場合は、Google マネージド SSL 証明書を使用するをご覧ください。

トラブルシューティング

無効な Secret または存在しない Secret を指定すると、Kubernetes イベントエラーが発生します。Ingress の Kubernetes イベントは、次のコマンドで確認できます。

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 を構成してユーザーに最も近いリージョンのクラスタにトラフィックをルーティングする。