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 つの鍵ファイルを作成します。

  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 は 2 つのコンテナで構成されています。一方のコンテナでは 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

証明書と鍵を作成する

このページの演習を行うには、それぞれ対応する鍵とともに 2 つの証明書が必要になります。各証明書には、所有しているドメイン名と同じ共通名(CN)を設定する必要があります。共通名として適切な値が設定された 2 つの証明書ファイルがすでに存在する場合は、次のセクションまでスキップできます。

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

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

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"

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

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

2 番目の鍵を作成します。

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

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"

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 は、1 つ目の証明書ファイルのパスです。
  • first-key-file は、1 つ目の証明書に対応する鍵ファイルのパスです。
  • first-domain は、所有しているドメイン名です。
  • first-secret-name は、最初の証明書と鍵が含まれている Secret の名前です。
  • second-cert-file は、2 つ目の証明書ファイルのパスです。
  • second-key-file は、2 つ目の証明書に対応する鍵ファイルのパスです。
  • second-domain は、所有している 2 つ目のドメイン名です。
  • second-secret-name は、2 つ目の証明書と鍵が含まれている Secret の名前です。

Ingress 用の証明書を指定する

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

  • Secret
  • 事前共有証明書

[SECRETS] タブまたは [PRE-SHARED CERTS] タブを選択して、2 つの手段のいずれかを選択します。

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

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
  ----              ----  --------
  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 プロジェクトで 2 つ目の証明書リソースを作成します。

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

ここで

  • second-cert-file は、2 つ目の証明書ファイルです。
  • second-key-file は、2 つ目の鍵ファイルです。

証明書リソースを表示します。

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

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

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

このステップを行うには、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
...
&lt;
Hello, world!
Version: 2.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 Kubernetes!

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'

次のステップ