Ingress による HTTPS 負荷分散における複数の SSL 証明書の使用

このページでは、Ingress で内部ロード バランシングと外部ロード バランシングを構成する際に複数の SSL 証明書を使用する方法を説明します。

概要

クライアントから HTTPS リクエストを受け入れるには、内部または外部の HTTP(S) ロードバランサに証明書を設置し、その正当性をクライアントに証明する必要があります。さらに、ロードバランサには、HTTPS handshake を完了するための秘密鍵も必要です。

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

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

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

次の 3 つの方法のいずれかを使用して、HTTPS ロードバランサに SSL 証明書を提供できます。

  • Google マネージド SSL 証明書。使用方法については、マネージド証明書のページをご覧ください。

  • ご自身で管理している Google Cloud SSL 証明書。以前に Google Cloud プロジェクトにアップロードされた事前共有証明書を使用します。

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

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

最小 GKE バージョン

Ingress で事前共有証明書を使用する、または複数の証明書を指定するには、GKE バージョン 1.10.2 以降が必要です。

前提条件

このページの演習を行うには、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 は 2 つのコンテナで構成されています。一方のコンテナでは 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

証明書と鍵を作成する

このページの演習を行うには、それぞれ対応する鍵とともに 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 ドメインからのリクエストを処理するとします。証明書署名リクエストは次のようになります。

    openssl req -new -key test-ingress-1.key -out test-ingress-1.csr \
        -subj "/CN=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 ドメインからのリクエストを処理するとします。証明書署名リクエストは次のようになります。

    openssl req -new -key test-ingress-2.key -out test-ingress-2.csr \
        -subj "/CN=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 番目のドメイン

Ingress 用の証明書を指定する

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

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

使用する方法のタブを選択してください。

Secret

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 を作成する

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 Ingress コントローラにより HTTP(S) ロードバランサが作成されます。GKE によってロードバランサに外部 IP アドレスが割り当てられるまで待ちます。

  3. Ingress の説明を表示します。

    kubectl describe ingress my-mc-ingress
    

    出力には、2 つの 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
    

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

  2. Google Cloud プロジェクトで 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
    

    出力には 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 は、ManagedCertificate オブジェクトで作成した Google マネージド証明書を参照するように ingress.gcp.kubernetes.io/pre-shared-cert アノテーションと ingress.kubernetes.io/ssl-cert アノテーションを自動的に埋め込みます。出力には、外部ロードバランサの 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
    

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

GKE によるロードバランサの構成が終了するまで 5 分程度待ちます。Google マネージド証明書の場合、システムで証明書のプロビジョニングとドメインの DNS 構成の検証が必要になるため、構成が完了するまでに時間がかかる場合があります。

このステップを行うには、2 つのドメイン名を所有していて、かつ両方のドメイン名によって HTTP(S) ロードバランサの外部 IP アドレスが解決される必要があります。

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

curl -v https://FIRST_DOMAIN

出力には、TLS handshake で 1 つ目の証明書が使用されたことが示されます。1 番目のドメインが 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
...

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

curl -v https://SECOND_DOMAIN

出力には、TLS handshake で 2 つ目の証明書が使用されたことが示されます。2 番目のドメインが 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 には、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'

次のステップ