マネージド Cloud Service Mesh でマルチクラスタ メッシュを設定する

このガイドでは、Mesh CA または Certificate Authority Service を使用して 2 つのクラスタを単一の Cloud Service Mesh に追加し、クラスタ間でロード バランシングを行う方法について説明します。このプロセスを拡張することで、任意の数のクラスタをメッシュに組み込むことができます。

マルチクラスタの Cloud Service Mesh 構成を使用すると、大規模な組織で重要な課題(スケール、ロケーション、分離など)を解決できます。詳細については、マルチクラスタのユースケースをご覧ください。

前提条件

このガイドでは、次の要件を満たす 2 つ以上の Google CloudGKE クラスタがあることを前提としています。

  • Cloud Service Mesh がクラスタにインストールされている。asmcliistioctl ツール、--output_dir で指定したディレクトリに asmcli がダウンロードするサンプルが必要です。
  • Cloud Service Mesh を構成する前に、メッシュ内のクラスタがすべての Pod に接続する必要があります。また、異なるプロジェクトのクラスタを追加する場合は、同じフリート ホスト プロジェクトにクラスタを登録する必要があります。さらに、共有 VPC 構成でクラスタを同じネットワークに接続する必要があります。1 つのプロジェクトに共有 VPC をホストし、2 つのサービス プロジェクトでクラスタを作成することをおすすめします。詳細については、共有 VPC を使用したクラスタの設定をご覧ください。
  • Certificate Authority Service を使用する場合、すべてのクラスタでそれぞれの下位 CA プールが同じルート CA プールにチェーンでつながっている必要があります。それ以外の場合、すべてのクラスタに同じ CA プールを使用する必要があります。

プロジェクトとクラスタ変数を設定する

  1. プロジェクト ID、クラスタゾーンまたはリージョン、クラスタ名、コンテキストのために、次の環境変数を作成します。

    export PROJECT_1=PROJECT_ID_1
    export LOCATION_1=CLUSTER_LOCATION_1
    export CLUSTER_1=CLUSTER_NAME_1
    export CTX_1="gke_${PROJECT_1}_${LOCATION_1}_${CLUSTER_1}"
    
    export PROJECT_2=PROJECT_ID_2
    export LOCATION_2=CLUSTER_LOCATION_2
    export CLUSTER_2=CLUSTER_NAME_2
    export CTX_2="gke_${PROJECT_2}_${LOCATION_2}_${CLUSTER_2}"
    
  2. 新しく作成されたクラスタの場合は、次の gcloud コマンドを使用して、各クラスタの認証情報を取得する必要があります。そうしないと、関連する context をこのガイドの次のステップで使用できません。

    これらのコマンドは、クラスタのタイプ(リージョンまたはゾーン)によって異なります。

    リージョン

    gcloud container clusters get-credentials ${CLUSTER_1} --region ${LOCATION_1}
    gcloud container clusters get-credentials ${CLUSTER_2} --region ${LOCATION_2}
    

    ゾーン

    gcloud container clusters get-credentials ${CLUSTER_1} --zone ${LOCATION_1}
    gcloud container clusters get-credentials ${CLUSTER_2} --zone ${LOCATION_2}
    

ファイアウォール ルールを作成する

場合によっては、クラスタ間トラフィックを許可するファイアウォール ルールを作成する必要があります。たとえば、次のような場合にファイアウォール ルールを作成する必要があります。

  • メッシュ内のクラスタに異なるサブネットを使用している。
  • Pod が 443 と 15002 以外のポートを開く。

GKE は、同じサブネット内のトラフィックを許可するファイアウォール ルールを各ノードに自動的に追加します。メッシュに複数のサブネットが含まれている場合は、サブネット間トラフィックを許可するようにファイアウォール ルールを明示的に設定する必要があります。送信元 IP CIDR ブロックを許可し、すべての受信トラフィックのポートをターゲットにできるように、サブネットごとに新しいファイアウォール ルールを追加する必要があります。

次の手順では、プロジェクト内のすべてのクラスタ間、または $CLUSTER_1$CLUSTER_2 間の通信のみを許可します。

  1. クラスタのネットワークに関する情報を収集します。

    すべてのプロジェクト クラスタ

    クラスタが同じプロジェクト内にある場合は、次のコマンドを使用して、プロジェクト内のすべてのクラスタ間の通信を許可できます。公開したくないクラスタがプロジェクトにある場合は、[特定のクラスタ] タブでコマンドを使用します。

    function join_by { local IFS="$1"; shift; echo "$*"; }
    ALL_CLUSTER_CIDRS=$(gcloud container clusters list --project $PROJECT_1 --format='value(clusterIpv4Cidr)' | sort | uniq)
    ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
    ALL_CLUSTER_NETTAGS=$(gcloud compute instances list --project $PROJECT_1 --format='value(tags.items.[0])' | sort | uniq)
    ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
    

    特定のクラスタ

    次のコマンドを使用すると、$CLUSTER_1$CLUSTER_2 間の通信を許可します。プロジェクト内の他のクラスタは公開されません。

    function join_by { local IFS="$1"; shift; echo "$*"; }
    ALL_CLUSTER_CIDRS=$(for P in $PROJECT_1 $PROJECT_2; do gcloud --project $P container clusters list --filter="name:($CLUSTER_1,$CLUSTER_2)" --format='value(clusterIpv4Cidr)'; done | sort | uniq)
    ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
    ALL_CLUSTER_NETTAGS=$(for P in $PROJECT_1 $PROJECT_2; do gcloud --project $P compute instances list  --filter="name:($CLUSTER_1,$CLUSTER_2)" --format='value(tags.items.[0])' ; done | sort | uniq)
    ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
    
  2. ファイアウォール ルールを作成します。

    GKE

    gcloud compute firewall-rules create istio-multicluster-pods \
        --allow=tcp,udp,icmp,esp,ah,sctp \
        --direction=INGRESS \
        --priority=900 \
        --source-ranges="${ALL_CLUSTER_CIDRS}" \
        --target-tags="${ALL_CLUSTER_NETTAGS}" --quiet \
        --network=YOUR_NETWORK
    

    Autopilot

    TAGS=""
    for CLUSTER in ${CLUSTER_1} ${CLUSTER_2}
    do
        TAGS+=$(gcloud compute firewall-rules list --filter="Name:$CLUSTER*" --format="value(targetTags)" | uniq) && TAGS+=","
    done
    TAGS=${TAGS::-1}
    echo "Network tags for pod ranges are $TAGS"
    
    gcloud compute firewall-rules create asm-multicluster-pods \
        --allow=tcp,udp,icmp,esp,ah,sctp \
        --network=gke-cluster-vpc \
        --direction=INGRESS \
        --priority=900 --network=VPC_NAME \
        --source-ranges="${ALL_CLUSTER_CIDRS}" \
        --target-tags=$TAGS
    

エンドポイント ディスカバリを構成する

宣言型 API を使用して一般公開または限定公開のクラスタ間のエンドポイント ディスカバリを有効にする

Fleet API でマネージド Cloud Service Mesh を有効にすると、このクラスタのエンドポイント ディスカバリが有効になります。別のツールでマネージド Cloud Service Mesh をプロビジョニングした場合は、asm-options configmap で構成 "multicluster_mode":"connected" を適用して、フリート内の一般公開クラスタまたは限定公開クラスタ間でエンドポイント ディスカバリを手動で有効にできます。同じフリート内にある、この構成が有効なクラスタ間では、クラスタ間サービス ディスカバリが自動的に有効になります。

これは、マネージド(TD)のコントロール プレーンの実装を使用している場合にマルチクラスタ エンドポイント ディスカバリを構成する唯一の方法であり、マネージド(Istiod)の実装を使用している場合に推奨される構成方法です。

続行する前に、ファイアウォール ルールを作成しておく必要があります。

有効にする

クラスタに asm-options configmap がすでに存在する場合は、クラスタのエンドポイント ディスカバリを有効にします。

      kubectl patch configmap/asm-options -n istio-system --type merge -p '{"data":{"multicluster_mode":"connected"}}'

クラスタに asm-options configmap がまだ存在しない場合は、関連データを使用して作成し、クラスタのエンドポイント ディスカバリを有効にします。

      kubectl --context ${CTX_1} create configmap asm-options -n istio-system --from-file <(echo '{"data":{"multicluster_mode":"connected"}}')

無効にする

クラスタのエンドポイント ディスカバリを無効にします。

      kubectl patch configmap/asm-options -n istio-system --type merge -p '{"data":{"multicluster_mode":"manual"}}'

エンドポイント ディスカバリを無効にせずにフリートからクラスタの登録を解除した場合、シークレットがクラスタに残る場合があります。残りのシークレットは手動でクリーンアップする必要があります。

  1. クリーンアップが必要なシークレットを確認するには、次のコマンドを実行します。

    kubectl get secrets -n istio-system -l istio.io/owned-by=mesh.googleapis.com,istio/multiCluster=true
    
  2. 各シークレットを削除します。

    kubectl delete secret SECRET_NAME
    

    残りのシークレットごとに、この手順を繰り返します。

マルチクラスタ接続を確認する

このセクションでは、サンプルの HelloWorld サービスと Sleep サービスをマルチクラスタ環境にデプロイして、クラスタ間でのロード バランシングが機能することを確認する方法について説明します。

サンプル ディレクトリの変数を設定する

  1. asmcli がダウンロードされた場所に移動し、次のコマンドを実行して ASM_VERSION を設定します。

    export ASM_VERSION="$(./asmcli --version)"
    
  2. 作業フォルダを、クラスタ間のロード バランシングが正常に機能することを確認するために使用するサンプルに設定します。サンプルは、asmcli install コマンドで指定した --output_dir ディレクトリのサブディレクトリにあります。次のコマンドで、OUTPUT_DIR を、--output_dir で指定したディレクトリに変更します。

    export SAMPLES_DIR=OUTPUT_DIR/istio-${ASM_VERSION%+*}
    

サイドカー インジェクションを有効にする

  1. 各クラスタにサンプルの名前空間を作成します。

    for CTX in ${CTX_1} ${CTX_2}
    do
        kubectl create --context=${CTX} namespace sample
    done
    
  2. インジェクションの名前空間を有効にします。手順は、コントロール プレーンの実装によって異なります。

    マネージド(TD)

    1. デフォルトのインジェクション ラベルを Namespace に適用します。
    for CTX in ${CTX_1} ${CTX_2}
    do
       kubectl label --context=${CTX} namespace sample \
          istio.io/rev- istio-injection=enabled --overwrite
    done
    

    マネージド(Istiod)

    推奨: 次のコマンドを実行して、デフォルトのインジェクション ラベルを Namespace に適用します。

     for CTX in ${CTX_1} ${CTX_2}
     do
        kubectl label --context=${CTX} namespace sample \
           istio.io/rev- istio-injection=enabled --overwrite
     done
    

    マネージド Istiod コントロール プレーンを使用している既存のユーザーの場合: デフォルトのインジェクションを使用することをおすすめしますが、リビジョンベースのインジェクションもサポートされています。次の手順を行います。

    1. 次のコマンドを実行して、利用可能なリリース チャンネルを探します。

      kubectl -n istio-system get controlplanerevision
      

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

      NAME                AGE
      asm-managed-rapid   6d7h
      

      出力の NAME 列の値は、Cloud Service Mesh バージョンで使用可能なリリース チャンネルに対応するリビジョン ラベルです。

    2. リビジョン ラベルを名前空間に適用します。

      for CTX in ${CTX_1} ${CTX_2}
      do
        kubectl label --context=${CTX} namespace sample \
           istio-injection- istio.io/rev=REVISION_LABEL --overwrite
      done
      

HelloWorld サービスをインストールする

  • 両方のクラスタに HelloWorld サービスを作成します。

    kubectl create --context=${CTX_1} \
        -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
        -l service=helloworld -n sample
    
    kubectl create --context=${CTX_2} \
        -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
        -l service=helloworld -n sample
    

各クラスタに HelloWorld v1 と v2 をデプロイする

  1. HelloWorld v1CLUSTER_1 にデプロイし、v2CLUSTER_2 にデプロイします。これは、後でクラスタ間の負荷分散を確認する際に役立ちます。

    kubectl create --context=${CTX_1} \
      -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
      -l version=v1 -n sample
    kubectl create --context=${CTX_2} \
      -f ${SAMPLES_DIR}/samples/helloworld/helloworld.yaml \
      -l version=v2 -n sample
  2. 次のコマンドを使用して、HelloWorld v1v2 が実行されていることを確認します。次のような出力が表示されていることを確認します。

    kubectl get pod --context=${CTX_1} -n sample
    NAME                            READY     STATUS    RESTARTS   AGE
    helloworld-v1-86f77cd7bd-cpxhv  2/2       Running   0          40s
    kubectl get pod --context=${CTX_2} -n sample
    NAME                            READY     STATUS    RESTARTS   AGE
    helloworld-v2-758dd55874-6x4t8  2/2       Running   0          40s

スリープ サービスをデプロイする

  1. 両方のクラスタに Sleep サービスをデプロイします。この Pod は、デモ用の人為的なネットワーク トラフィックを生成します。

    for CTX in ${CTX_1} ${CTX_2}
    do
        kubectl apply --context=${CTX} \
            -f ${SAMPLES_DIR}/samples/sleep/sleep.yaml -n sample
    done
    
  2. 各クラスタで Sleep サービスが起動するまで待ちます。次のような出力が表示されていることを確認します。

    kubectl get pod --context=${CTX_1} -n sample -l app=sleep
    NAME                             READY   STATUS    RESTARTS   AGE
    sleep-754684654f-n6bzf           2/2     Running   0          5s
    kubectl get pod --context=${CTX_2} -n sample -l app=sleep
    NAME                             READY   STATUS    RESTARTS   AGE
    sleep-754684654f-dzl9j           2/2     Running   0          5s

クラスタ間のロード バランシングを確認する

HelloWorld サービスを数回呼び出し、出力をチェックして v1 と v2 からの交互の返信を確認します。

  1. HelloWorld サービスを呼び出します。

    kubectl exec --context="${CTX_1}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_1}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

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

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...
  2. HelloWorld サービスを再度呼び出します。

    kubectl exec --context="${CTX_2}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_2}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

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

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...

ロード バランシングされたマルチクラスタの Cloud Service Mesh の検証はこれで完了です。

トラフィックをクラスタ内に維持する

デフォルトのクラスタ間ロード バランシングの動作が望ましくない場合があります。トラフィックを「クラスタローカル」に保つ(cluster-a から送信されたトラフィックが cluster-a 内の宛先にのみ届く)には、MeshConfig.serviceSettings を使用してホスト名またはワイルドカードを clusterLocal とマークします。

たとえば、クラスタローカル トラフィックは、個々のサービス、特定の名前空間内のすべてのサービス、またはグローバルにはメッシュ内のすべてのサービスに対して、次のように適用できます。

サービスごと

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "mysvc.myns.svc.cluster.local"

名前空間ごと

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "*.myns.svc.cluster.local"

global

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "*"

グローバル クラスタローカル ルールを設定し、明示的な例外(個別に指定することも、ワイルドカードで指定することも可能)を追加することで、サービス アクセスを絞り込むこともできます。次の例では、myns 名前空間のサービスを除き、クラスタ内のすべてのサービスがクラスタローカルに保持されます。

serviceSettings:
- settings:
    clusterLocal: true
  hosts:
  - "*"
- settings:
    clusterLocal: false
  hosts:
  - "*.myns.svc.cluster.local"

ローカル クラスタ サービスを有効にする

  1. クラスタ内の MeshConfig 構成マップを確認する

    kubectl get configmap -n istio-system
    

    istio-asm-managedistio-asm-managed-rapidistio-asm-managed-stable のいずれかの名前の構成マップが表示されます。

    ISTIOD 実装から TRAFFIC_DIRECTOR 実装に移行した場合、複数の構成マップが表示されることがあります。この場合、次のコマンドを実行してチャネルを確認できます。

    kubectl get controlplanerevision -n istio-system
    

    調整されたコントロール プレーン リビジョンのチャンネルを選択します。

  2. ConfigMap を更新する

    cat <<EOF > config.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: CONFIGMAP_NAME
      namespace: istio-system
    data:
      config: |
        serviceSettings:
        - settings:
            clusterLocal: true
          hosts:
          - "*"
    EOF
    

    CONFIGMAP_NAME をステップ 1 で確認した Config Map の名前に置き換え、Config Map を更新します。

    kubectl apply --context=${CTX_1} -f config.yaml
    
  3. 次のコマンドを使用して、ローカル クラスタ機能が想定どおりに動作していることを確認します。CTX_1 を使用して HelloWorld を呼び出すと、次のような出力が返されます。

    kubectl exec --context="${CTX_1}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_1}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

    出力には v1 からの応答のみが表示されます。

    Hello version: v1, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...
    

    CTX_2 を使用して HelloWorld を呼び出す場合は、次のような出力が返されます。

    kubectl exec --context="${CTX_2}" -n sample -c sleep \
        "$(kubectl get pod --context="${CTX_2}" -n sample -l \
        app=sleep -o jsonpath='{.items[0].metadata.name}')" \
        -- /bin/sh -c 'for i in $(seq 1 20); do curl -sS helloworld.sample:5000/hello; done'
    

    出力には v1 と v2 からの応答が混在する状態で表示されます。

    Hello version: v2, instance: helloworld-v2-758dd55874-6x4t8
    Hello version: v1, instance: helloworld-v1-86f77cd7bd-cpxhv
    ...
    

HelloWorld サービスをクリーンアップする

ロード バランシングの確認が完了したら、クラスタから HelloWorld サービスと Sleep サービスを削除します。

kubectl delete ns sample --context ${CTX_1}
kubectl delete ns sample --context ${CTX_2}