Anthos Service Mesh を使用した Anthos クラスタ間の通信の保護と暗号化

このチュートリアルでは、Anthos Service Mesh の下りと上り(内向き)ゲートウェイを使用して、相互 Transport Layer Security(mTLS)でクラスタ間のトラフィックを保護する方法について説明します。このチュートリアルは、ネットワーク、セキュリティ、プラットフォームの各側面を担当する Kubernetes クラスタ管理者を対象としています。ここで説明する制御は、組織のセキュリティ要件が厳しくなっている場合や、規制の前提条件を満たす場合に特に役立ちます。このチュートリアルには、補足のコンセプト ガイドが付属しています。

このチュートリアルは、Kubernetes と Anthos Service Mesh に精通していることを前提としています。

目標

  • Terraform を使用してインフラストラクチャを設定します。
    • 2 つのプライベート サブネットを持つカスタム VPC ネットワークを作成する
    • Anthos Service Mesh を有効にして 2 つの Kubernetes クラスタを作成します。
    • クラスタを GKE Hub に登録します。
  • GKE クラスタに MySQL クライアントをデプロイします。
  • kOps クラスタに MySQL サーバーをデプロイします。
  • mTLS を使用してサーバーを公開するように下り(外向き)ゲートウェイと上り(内向き)ゲートウェイを構成します。
  • さまざまなクラスタまたは VPC で実行されている MySQL クライアントを使用して、MySQL サーバーへのアクセスをテストします。

料金

このチュートリアルでは、課金対象である次の Google Cloud コンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このチュートリアルを終了した後、作成したリソースを削除すると、それ以上の請求は発生しません。詳しくは、クリーンアップをご覧ください。

始める前に

このチュートリアルでは、Google Cloud プロジェクトが必要です。新しいプロジェクトを作成することも、すでに作成済みのプロジェクトを選択することもできます。

  1. Google Cloud Console で [プロジェクトの選択] ページに移動します。

    プロジェクト セレクタに移動

  2. Google Cloud プロジェクトを選択または作成します。

  3. Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Cloud Console で Cloud Shell に移動します。

    Cloud Shell に移動

    Cloud Console の下部で Cloud Shell セッションが開き、コマンドライン プロンプトが表示されます。Cloud Shell はシェル環境であり、gcloud コマンドライン ツールをはじめとする Cloud SDK がすでにインストールされています。セッションが初期化されるまで数秒かかることがあります。

  5. Cloud Shell で、作成または選択したプロジェクトで作業していることを確認します。
    export PROJECT_ID=PROJECT_ID
    gcloud config set project ${PROJECT_ID}
    

    PROJECT_ID を実際のプロジェクト ID に置き換えます。

  6. Google Cloud で使用するメールアドレスの環境変数を作成します。
    export GOOGLE_CLOUD_EMAIL_ADDRESS=GOOGLE_CLOUD_EMAIL_ADDRESS
    

    GOOGLE_CLOUD_EMAIL_ADDRESS を、Google Cloud で使用するメールアドレスに置き換えます。

  7. コンピューティング リソースのリージョンとゾーンを設定します。
    export REGION=us-central1
    export ZONE=us-central1-b
    gcloud config set compute/region ${REGION}
    gcloud config set compute/zone ${ZONE}
    

    このチュートリアルでは、リージョンに us-central1 を使用し、ゾーンに us-central1-b を使用します。選択したリージョンにデプロイできます。

  8. 必要な Identity and Access Management(IAM)のロールを設定します。プロジェクト オーナーの場合は、インストールを完了するために必要なすべての権限が付与されています。それ以外の場合は、Cloud Shell で次のコマンドを実行して、管理者に Identity and Access Management(IAM)のロールを付与するよう依頼してください。
    ROLES=(
    'roles/container.admin' \
    'roles/gkehub.admin' \
    'roles/iam.serviceAccountAdmin' \
    'roles/iam.serviceAccountKeyAdmin' \
    'roles/resourcemanager.projectIamAdmin' \
    'roles/compute.securityAdmin' \
    'roles/compute.instanceAdmin' \
    'roles/storage.admin' \
    'roles/serviceusage.serviceUsageAdmin'
    )
    for role in "${ROLES[@]}"
    do
     gcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member "user:${GOOGLE_CLOUD_EMAIL_ADDRESS}" \
      --role="$role"
    done
    
  9. チュートリアルに必要な API を有効にします。
    gcloud services enable \
        anthos.googleapis.com \
        anthosgke.googleapis.com \
        anthosaudit.googleapis.com \
        compute.googleapis.com \
        container.googleapis.com \
        cloudresourcemanager.googleapis.com \
        serviceusage.googleapis.com \
        stackdriver.googleapis.com \
        monitoring.googleapis.com \
        logging.googleapis.com \
        cloudtrace.googleapis.com \
        meshca.googleapis.com \
        meshtelemetry.googleapis.com \
        meshconfig.googleapis.com \
        iamcredentials.googleapis.com \
        gkeconnect.googleapis.com \
        gkehub.googleapis.com
    

環境の準備

  1. Cloud Shell で、次のリポジトリのクローンを作成します。

    git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samples
    cd anthos-service-mesh-samples/docs/mtls-egress-ingress
    
  2. 環境に合わせて Terraform を更新します。デフォルトでは、Cloud Console には Terraform 0.12 が付属しています。このチュートリアルでは、Terraform 0.13.5 以降がインストールされていることを前提としています。次のコマンドを実行して、一時的に別のバージョンの Terraform を使用できます。

    mkdir ~/bin
    curl https://releases.hashicorp.com/terraform/0.13.5/terraform_0.13.5_linux_amd64.zip -o ~/bin/terraform.zip
    unzip ~/bin/terraform.zip -d ~/bin/
    
  3. terraform サブフォルダに移動して、Terraform を初期化します。

    cd terraform/
    ~/bin/terraform init
    
  4. (以前に作成した環境変数に基づいて)terraform.tfvars ファイルを作成します。

    cat << EOF > terraform.tfvars
    project_id = "${PROJECT_ID}"
    region = "${REGION}"
    zones = ["${ZONE}"]
    EOF
    

    次のステップでは、初期インフラストラクチャを作成します。これを行うには、この構成に対して Terraform の実行プランを作成して適用します。このプランのスクリプトとモジュールにより、次の要素が作成されます。

    • 2 つのプライベート サブネットを持つカスタム VPC ネットワーク
    • Anthos Service Mesh が有効になっている 2 つの Kubernetes クラスタ
    • 1 つの GKE クラスタ
    • カスタム VPC ネットワークで実行されている 1 つの kOps クラスタ

    この実行プランでは GKE Hub にもクラスタを登録します。

  5. 実行プランを実行します。

    ~/bin/terraform plan -out mtls-terraform-plan
    ~/bin/terraform apply "mtls-terraform-plan"
    

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

    Apply complete! Resources: 27 added, 0 changed, 0 destroyed.
    Outputs:
    server_token = <sensitive>
    

    <sensitive> 部分は、コンソールには表示されませんが、たとえば、~/bin/terraform output server_token のようにクエリできます。

  6. terraform ディレクトリからサーバー クラスタの kubeconfig ファイルを取得します。次に、取得したファイルを client-cluster 構成ファイルとマージします。

    cd ..
    export KUBECONFIG=client-server-kubeconfig
    cp ./terraform/server-kubeconfig $KUBECONFIG
    gcloud container clusters get-credentials client-cluster --zone ${ZONE} --project ${PROJECT_ID}
    

    これで、client-server-kubeconfig ファイルに両方のクラスタの構成が保持されるようになりました。この構成を確認するには、次のコマンドを実行します。

    kubectl config view -ojson | jq -r '.clusters[].name'
    

    次のような出力が表示されます。

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    
  7. 後で使用できるように、2 つのクラスタのコンテキストを取得します。

    export CLIENT_CLUSTER=$(kubectl config view -ojson | jq -r '.clusters[].name' | grep client)
    export SERVER_CLUSTER=$(kubectl config view -ojson | jq -r '.clusters[].name' | grep server)
    echo -e "${CLIENT_CLUSTER}\n${SERVER_CLUSTER}"
    

    出力は(ここでも)次のようになります。

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    

    これで、これらのクラスタ名を今後の kubectl コマンドのコンテキストとして使用できるようになりました。

  8. クライアント クラスタを参照します。

    kubectl --context ${CLIENT_CLUSTER} get pods -n istio-system
    
  9. サーバー クラスタを参照します。

    kubectl --context ${SERVER_CLUSTER} get pods -n istio-system
    

クライアント側の構成

コンセプト ガイドで説明されているように、クライアント側では、Anthos Service Mesh で下り(外向き)ゲートウェイを構成する必要があります。

このセクションでは、Anthos Service Mesh 要素を構成して、送信元に基づいて外部トラフィックを特定し、カスタム証明書を使用して通信を暗号化します。さらに、そのトラフィックのみを宛先(コンテナ内の MySQL DB)にルーティングする必要があります。通常は、Kubernetes でサービスを使用します。この場合、メッシュ通信内でそのトラフィックをキャッチする必要があります。トラフィックをキャッチするには、Istio 要素を使用してサービスの特別な定義を作成します。次の要素を定義します。

  • 下り(外向き)ゲートウェイ
  • サービス エントリ
  • 仮想サービス
  • TLS 証明書(シークレットとして)
  • 宛先ルール
  1. Cloud Shell で、サーバー側のコンテキスト(先ほど作成した $SERVER_CLUSTER)を使用して istio-ingressgateway サービスのロードバランサの IP アドレスに対するクエリを実行し、サーバー側の上り(内向き)ゲートウェイの IP アドレスを取得します。

    INGRESS_HOST=$(kubectl -n istio-system --context ${SERVER_CLUSTER} get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    

    INGRESS_HOST はホストの IP アドレス部分のみであるため、完全修飾ドメイン名(FQDN)を作成する必要があります。このステップは、証明書が適切に機能するためにドメイン名が必要なため必要です。

    このチュートリアルでは、ワイルドカード DNS サービス nip.io を使用して、上り(内向き)IP アドレスの FQDN を作成します。このサービスによって、ドメインを所有せずに FQDN を作成できます。

  2. 環境変数に FQDN サービス URL を格納します。

    export SERVICE_URL="${INGRESS_HOST}.nip.io"
    

    これで、SERVICE_URL を FQDN として定義したため、クライアント クラスタの Istio の部分の定義を開始できます。

下り(外向き)ゲートウェイを作成する

最初に、外部サービスに対して送信されたトラフィックをリッスンする下り(外向き)ゲートウェイを作成します。

外部サービスに対して送信されたトラフィックをリッスンする下り(外向き)ゲートウェイ。

  1. Cloud Shell で、次の YAML ファイルを作成し、client-egress-gateway.yaml という名前を付けます。

    cat <<EOF > client-egress-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
     name: istio-egressgateway-mysql
    spec:
     selector:
       istio: egressgateway
     servers:
     - port:
         number: 15443
         name: tls
         protocol: TLS
       hosts:
       - $SERVICE_URL
       tls:
         mode: ISTIO_MUTUAL
    EOF
    
  2. 前述の YAML ファイルをクライアント クラスタに適用します。

    kubectl --context ${CLIENT_CLUSTER} apply -f client-egress-gateway.yaml
    

    ポートに注意してください。下り(外向き)サーバーのスイッチには default ポート(15443)を使用しました。別のポートを使用する場合は、下り(外向き)ゲートウェイ service オブジェクトを編集して、カスタムポートを追加する必要があります。

    hosts スイッチは、トラフィックの見出しを表示するエンドポイントを定義します。

サービス エントリを定義する

次のステップでは、外部サービスについてサービス メッシュに指示します。Istio には、メッシュのサービス エンドポイントを格納する独自のレジストリがあります。Istio が Kubernetes にインストールされている場合、クラスタで定義されたサービスは Istio レジストリに自動的に追加されます。サービス エントリの定義については、次の図に示すように、新しいエンドポイントを Istio レジストリに追加します。

サービス エントリ定義を使用して、Istio レジストリにエンドポイントを追加します。

  1. Cloud Shell で、次の YAML ファイルを作成し、client-service-entry.yaml という名前を付けます。

    cat <<EOF > client-service-entry.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
     name: mysql-external
    spec:
     hosts:
       - $SERVICE_URL
     location: MESH_EXTERNAL
     ports:
       - number: 3306
         name: tcp
         protocol: TCP
       - number: 13306
         name: tls
         protocol: TLS
     resolution: DNS
     endpoints:
       - address: $SERVICE_URL
         ports:
           tls: 13306
    EOF
    
  2. 前述の YAML ファイルをクライアント クラスタに適用します。

    kubectl --context ${CLIENT_CLUSTER} apply -f client-service-entry.yaml
    

    この YAML ファイルのクライアント サービス定義は、予想されるトラフィックの種類(MySQL L4 - Network Layer 4、ポート 3306 を使用)をサービスに指示します。また、通信が「メッシュ外部」に対して行われるように定義します。エンドポイント セクションで、先ほど設定した FQDN アドレス $SERVICE_URL(サーバー クラスタ(kOps)の Ingress ゲートウェイにマッピングされます)に、フローが配置されることを定義します。

仮想サービスを定義する

仮想サービスは、ホストがアドレス指定されたときに適用される一連のトラフィック ルーティング ルールです。各ルーティング ルールは、特定のプロトコルのトラフィックに対する一致条件を定義します。トラフィックが一致すると、レジストリで定義された名前付きの宛先サービス(またはサブセットまたはバージョン)に送信されます。詳細については、Istio のドキュメントをご覧ください。

外部サービスに到達するトラフィックのルーティングを適用する方法について Istio に指示する仮想サービスを定義する。

仮想サービスの定義は、外部サービスに到達するトラフィックのルーティングを適用する方法を Istio に指示します。次の定義を使用して、クライアントから 下り(外向き)ゲートウェイのポート 15443 にトラフィックをルーティングするようメッシュに指示します。下り(外向き)ゲートウェイから、ポート 13306 のホスト $SERVICE_URL にトラフィックをルーティングします。このポートでは、サーバー側の上り(内向き)ゲートウェイがリッスンしています。

  1. 次の YAML ファイルを作成し、client-virtual-service.yaml という名前を付けます。

    cat <<EOF > client-virtual-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
     name: direct-mysql-through-egress-gateway
    spec:
     hosts:
       - $SERVICE_URL
     gateways:
       - istio-egressgateway-mysql
       - mesh
     tcp:
       - match:
           - gateways:
               - mesh
             port: 3306
         route:
           - destination:
               host: istio-egressgateway.istio-system.svc.cluster.local
               subset: mysql
               port:
                 number: 15443
             weight: 100
       - match:
           - gateways:
               - istio-egressgateway-mysql
             port: 15443
         route:
           - destination:
               host: $SERVICE_URL
               port:
                 number: 13306
             weight: 100
    EOF
    
  2. YAML 定義をクライアント クラスタに適用します。

    kubectl --context ${CLIENT_CLUSTER} apply -f client-virtual-service.yaml
    

    YAML ファイルの gateways スイッチを編集することで、構成を適用するゲートウェイを指定できます。

    この定義で重要な部分は、予約語 mesh の使用です。これは、メッシュ内のすべてのサイドカーを意味しています。Istio のドキュメントに従って、このフィールドを省略すると、デフォルト ゲートウェイ(メッシュ)が使用され、メッシュ内のすべてのサイドカーにルールが適用されます。ゲートウェイ名のリストを指定した場合、ルールはゲートウェイにのみ適用されます。ゲートウェイとサイドカーにルールを適用するには、ゲートウェイ名の 1 つとして mesh を指定します。

次のセクションでは、クライアント prod のプロキシ match.gateways.mesh から発信されたトラフィックの処理方法を定義します。また、match.gateways.istio-egressgateway-mysql スイッチを使用して、下り(外向き)から外部サービスへトラフィックをルーティングする方法も定義します。

クライアントから下り(外向き)ゲートウェイへの宛先ルールを定義する

これで、トラフィックを外部サービスにルーティングする方法を定義できました。次に、適用するトラフィック ポリシーを定義する必要があります。先ほど定義した仮想サービスは、2 つのルーティング ケースを同時に処理します。1 つはサイドカー プロキシから下り(外向き)ゲートウェイへのトラフィックを処理し、もう 1 つは下り(外向き)ゲートウェイから外部サービスへのトラフィックを処理します。

このようなケースと宛先ルールを照合するには、2 つの個別のルールが必要です。次の図は、プロキシから下り(外向き)ゲートウェイへのトラフィックを処理する最初のルールを示しています。この定義では、Anthos Service Mesh に mTLS 通信用のデフォルトの証明書を使用するように指示します。

サイドカー プロキシから下り(外向き)ゲートウェイへのトラフィックの処理方法を定義する宛先ルール。

  1. Cloud Shell で、次の YAML ファイルを作成し、client-destination-rule-to-egress-gateway.yaml という名前を付けます。

    cat <<EOF > client-destination-rule-to-egress-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: egressgateway-for-mysql
    spec:
      host: istio-egressgateway.istio-system.svc.cluster.local
      subsets:
        - name: mysql
          trafficPolicy:
            loadBalancer:
              simple: ROUND_ROBIN
            portLevelSettings:
              - port:
                  number: 15443
                tls:
                  mode: ISTIO_MUTUAL
                  sni: $SERVICE_URL
    EOF
    
  2. 前述の YAML 定義をクライアント クラスタに適用します。

    kubectl --context ${CLIENT_CLUSTER} apply -f client-destination-rule-to-egress-gateway.yaml
    

    前述の YAML ファイルでは、hosts スイッチを使用して、クライアント プロキシから下り(外向き)ゲートウェイにトラフィックをルーティングする方法を定義しました。また、下り(外向き)ゲートウェイで自動的に開いているポートの 1 つであるポート 15443ISTIO_MUTUAL を使用するようにメッシュの構成も行いました。

下り(外向き)ゲートウェイから外部サービスへの宛先ルールを作成する

次の図は、2 番目の宛先ルールを示しています。これは、下り(外向き)ゲートウェイから外部サービスへのトラフィックの処理方法をメッシュに指示します。

2 番目の宛先ルール。下り(外向き)ゲートウェイから外部サービスへのトラフィックの処理方法を定義します。

追加された証明書を外部サービスとの相互 TLS 通信に使用するように、メッシュに指示する必要があります。

  1. Cloud Shell で、anthos-service-mesh-samples/docs/mtls-egress-ingress ディレクトリから証明書を作成します。

     ./create-keys.sh
    

    スクリプトでパスワードを求められたときには、必ずパスワードを入力してください。

  2. 生成されたファイルを現在のディレクトリにコピーします。

    cp ./certs/2_intermediate/certs/ca-chain.cert.pem ca-chain.cert.pem
    cp ./certs/4_client/private/$SERVICE_URL.key.pem client-$SERVICE_URL.key.pem
    cp ./certs/4_client/certs/$SERVICE_URL.cert.pem client-$SERVICE_URL.cert.pem
    
  3. 証明書を格納する Kubernetes シークレットを作成し、後でこの証明書をゲートウェイで参照できるようにします。

     kubectl --context ${CLIENT_CLUSTER} create secret -n istio-system \
      generic client-credential \
      --from-file=tls.key=client-$SERVICE_URL.key.pem \
      --from-file=tls.crt=client-$SERVICE_URL.cert.pem \
      --from-file=ca.crt=ca-chain.cert.pem
    

    前述のコマンドは、次の証明書ファイルをシークレットに追加します。

    client-$SERVICE_URL.key.pem
    client-$SERVICE_URL.cert.pem
    ca-chain.cert.pem
    

    ここでは、ファイル マウントの代わりに Secret Discovery サービス(SDS)を使用して、証明書を配布する場合の現在のベスト プラクティスを説明します。これにより、新しい証明書を追加するときに Pod が再起動されることを回避します。Istio 1.8/1.9 以降では、この方法により、ゲートウェイのシークレットの読み取りアクセス権(RBAC)が不要になりました。

  4. 証明書を DestinationRule に追加し、client-destination-rule-to-external-service.yaml と名付けます。

    cat <<EOF > client-destination-rule-to-external-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
     name: originate-mtls-for-mysql
    spec:
     host: $SERVICE_URL
     trafficPolicy:
       loadBalancer:
         simple: ROUND_ROBIN
       portLevelSettings:
       - port:
           number: 13306
         tls:
           mode: MUTUAL
           credentialName: client-credential
           sni: $SERVICE_URL
    EOF
    
  5. 前述の YAML 定義をクライアント クラスタに適用します。

    kubectl --context ${CLIENT_CLUSTER} apply -f client-destination-rule-to-external-service.yaml
    

    このルールは、事前に Secret を作成した場合にのみ機能します。Secret により、下り(外向き)ゲートウェイから外部エンドポイントへの mTLS 暗号化に証明書が使用されます。

これらの手順を完了すると、クライアント側の設定が終了し次の図のようになります。

クライアント側の最終設定。

サーバー側を構成する

コンセプト ガイドで説明しているように、サーバー側では Anthos Service Mesh で Ingress ゲートウェイを構成する必要があります。必要なファイルを作成する前に、ソリューションのサーバー部分を実現するために必要なコンポーネントを確認することをおすすめします。

基本的には、送信元と証明書に基づいて受信トラフィックを識別します。また、そのトラフィックのみを宛先であるコンテナの MySQL DB にルーティングすることも可能です。これを行うには、通常、Kubernetes の Service を使用しますが、この例ではメッシュ コミュニケーションの受信トラフィックを識別するため、以下を含む Service の特別な定義が必要です。

  • TLS 証明書(シークレットとして)
  • Ingress ゲートウェイ
  • 仮想サービス

上り(内向き)ゲートウェイのシークレットを作成する

下り(外向き)ゲートウェイの場合と同様に、上り(内向き)ゲートウェイの通信を保護するために同じ証明書が必要です。これを行うには、証明書をシークレットに格納し、このシークレットを上り(内向き)ゲートウェイ オブジェクトで定義します。

  1. Cloud Shell で、次のコマンドを実行する場所に、生成されたファイルをコピーします。

    cp ./certs/3_application/private/$SERVICE_URL.key.pem server-$SERVICE_URL.key.pem
    cp ./certs/3_application/certs/$SERVICE_URL.cert.pem server-$SERVICE_URL.cert.pem
    
  2. サーバー Secret を作成します。

    kubectl --context ${SERVER_CLUSTER} create secret -n istio-system \
        generic mysql-credential \
        --from-file=tls.key=server-$SERVICE_URL.key.pem \
        --from-file=tls.crt=server-$SERVICE_URL.cert.pem \
        --from-file=ca.crt=ca-chain.cert.pem
    

    先ほど次の証明書ファイルをシークレットに追加しました。

    server-$SERVICE_URL.key.pem
    server-$SERVICE_URL.cert.pem
    ca-chain.cert.pem
    

上り(内向き)ゲートウェイを定義する

クライアント側クラスタからトラフィックを受信するには、証明書を使用して TLS 通信を復号して検証できる上り(内向き)ゲートウェイを指定する必要があります。

上り(内向き)ゲートウェイを定義して、トラフィックを検査し、セキュリティ基準と転送基準に適合しているかどうかを確認します。

この図は、上り(内向き)ゲートウェイがクラスタ内に存在する場所を示しています。トラフィックが通過すると、セキュリティ基準と転送基準を満たしているかどうか検査されます。

  1. Cloud Shell で、次の YAML ファイルを使用し、server-ingress-gatway.yaml という名前を付けます。

    cat <<EOF > server-ingress-gatway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
     name: gateway-mysql
    spec:
     selector:
       istio: ingressgateway # Istio default gateway implementation
     servers:
     - port:
         number: 13306
         name: tls-mysql
         protocol: TLS
       tls:
         mode: MUTUAL
         credentialName: mysql-credential
       hosts:
       - "$SERVICE_URL"
    EOF
    
  2. 前述の YAML 定義をクライアント クラスタに適用します。

    kubectl --context ${SERVER_CLUSTER} apply -f server-ingress-gatway.yaml
    

    tls: セクションは特に重要であるため、注意してください。このセクションでは、必要な mTLS を定義します。これが想定どおりに機能することを確認するには、証明書を含む作成した Secret を指定する必要があります。

  3. 上り(内向き)サービスをパッチ適用してポート 13306 を有効にします。このポートを有効にするには、次の JSON ファイルを作成し、gateway-patch.json という名前を付けます。

    cat <<EOF > gateway-patch.json
    [{
      "op": "add",
      "path": "/spec/ports/0",
      "value": {
        "name": "tls-mysql",
        "protocol": "TCP",
        "targetPort": 13306,
        "port": 13306
      }
    }]
    EOF
    
  4. パッチをゲートウェイ サービスに適用します。

    kubectl --context ${SERVER_CLUSTER} -n istio-system patch --type=json svc istio-ingressgateway -p "$(cat gateway-patch.json)"
    

上り(内向き)ゲートウェイでポートを開いたら、新しい上り(内向き)ゲートウェイからのトラフィックを取得し、データベース Pod に転送する必要があります。これを行うには、メッシュの内部オブジェクト(仮想サービス)を使用します。

仮想サービスを定義する

前述のように、仮想サービスはメッシュ内のトラフィックを形成するトラフィック一致パターンの定義です。次の図に示すように、上り(内向き)ゲートウェイから MySQL DB サービスへのトラフィックを正しく識別して転送する必要があります。

上り(内向き)ゲートウェイから MySQL DB サービスへのトラフィックの識別と転送を行います。

  1. Cloud Shell で、次の YAML ファイルを作成し、server-virtual-service.yaml という名前を付けます。

    cat <<EOF > server-virtual-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
     name: mysql-virtual-service
    spec:
     hosts:
       - "$SERVICE_URL"
     gateways:
       - gateway-mysql
     tcp:
       - route:
         - destination:
             port:
               number: 3306
             host: mysql.default.svc.cluster.local
    EOF
    

    この仮想サービスがトラフィックが来る上り(内向き)ゲートウェイを参照することが重要です。ゲートウェイは gateway-mysql という名前です。

    この仮想サービスは L4 に適用されるため、MySQL トラフィックを説明する tcp フラグを指定する必要があります。このフラグは、基本的に、このトラフィックに L4 が使用されていることをメッシュに通知します。

    上り(内向き)サービスで、トラフィックを転送するためにポート 13306 が使用されていることにお気づきかと思います。仮想サービスは、その出力を選択して翻訳し 3306 に戻します。

    最後に、トラフィックをサーバー Kubernetes クラスタ内の MySQL サーバーに転送します。この例では、サーバーは標準の MySQL ポート 3306 をリッスンしています。

  2. YAML 定義をサーバー クラスタに適用します。

    kubectl --context ${SERVER_CLUSTER} apply -f server-virtual-service.yaml
    

これら 2 つの定義は、mTLS でカプセル化された MySQL クライアント リクエストを復号し、それをメッシュ内の MySQL データベースに転送します。

メッシュの内部転送も暗号化を使用して行われることを理解しておくことが重要です。この場合、暗号化はメッシュの内部証明書に基づいて行われます。mTLS 終端はゲートウェイで発生します。

これで、MySQL サーバーとの通信の完全に暗号化された相互通信方法ができました。この暗号化形式は MySQL クライアントと MySQL に対して透過的であるため、アプリケーションの変更は不要です。これにより、証明書の変更やローテーションなどのタスクが簡単になります。また、この通信方法は、多くのさまざまなシナリオで使用できます。

セットアップのテスト

クライアント側とサーバー側の配置が完了したため、設定をテストできます。

クライアント側でサーバー側からトラフィックの流れをテストする場合。

ここで、クライアント側からサーバー側へのトラフィックの一部を生成します。トラフィック フローに従って、トラフィックのルーティングと暗号化、復号が想定どおりに行われるようにします。

  1. Cloud Shell で、MySQL サーバーをサーバー クラスタにデプロイします。

    kubectl --context ${SERVER_CLUSTER} apply -f server/mysql-server/mysql.yaml
    
  2. クライアント クラスタで MySQL クライアントを起動します。

    kubectl run --context ${CLIENT_CLUSTER} --env=SERVICE_URL=$SERVICE_URL -it --image=mysql:5.6 mysql-client-1 --restart=Never -- /bin/bash
    

    コンテナが開始されると、次のようになっているシェルが表示されます。

    root@mysql-client-1:/#
    
  3. MySQL サーバーに接続します。

    mysql -pyougottoknowme -h $SERVICE_URL
    
  4. 次のコマンドを使用して、DB とテーブルを追加します。

    CREATE DATABASE test_encrypted_connection;
    USE test_encrypted_connection;
    CREATE TABLE message (id INT, content VARCHAR(20));
    INSERT INTO message (id,content) VALUES(1,"Crypto Hi");
    
  5. テーブルに接続して追加したら、MySQL 接続と Pod を終了します。

    exit
    exit
    

    exit を 2 回入力する必要があります。1 回目は DB 接続を終了し、2 回目は Pod を終了するためです。終了時に Pod が応答しなくなった場合は、Ctrl+C キーを押して bash シェルをキャンセルして終了します。

この手順を行うことで、有意義なロギング出力を生成し、詳細に分析できます。

次のセクションでは、クライアント側トラフィックがプロキシと下り(外向き)ゲートウェイを通過していることを確認します。また、上り(内向き)ゲートウェイを通じてサーバー側に着信するトラフィックも表示されるかどうかをテストします。

クライアント側プロキシと下り(外向き)ゲートウェイをテストする

  1. Cloud Shell のクライアント側プロキシで、Istio プロキシのログを表示できることを確認します。

    kubectl --context ${CLIENT_CLUSTER} logs -l run=mysql-client-1 -c istio-proxy -f
    

    デバッグの出力は次のようになります。

    [2021-02-10T21:19:08.292Z] "- - -" 0 - "-" "-" 176 115 10 - "-" "-" "-" "-" "192.168.1.4:15443" outbound|15443|mysql|istio-egressgateway.istio-system.svc.cluster.local 192.168.1.12:58614 34.66.165.46:3306 192.168.1.12:39642 - -
    

    ログ出力を終了するには、Ctrl+C キーを押します。

    このログエントリでは、クライアントが IP アドレス 34.66.165.46 のポート 3306 で実行されているサーバーをリクエストしていることが確認できます。リクエストは、IP アドレス 192.168.1.4 のポート 15443 をリッスンする istio-egressgateway に転送(outbound)されます。この転送は仮想サービス(client-virtual-service.yaml)で定義したものです。

  2. 下り(外向き)ゲートウェイ プロキシのログを読み取ります。

    kubectl --context ${CLIENT_CLUSTER} logs -n istio-system -l app=istio-egressgateway -f
    

    デバッグの出力は次のようになります。

    [2021-02-10T21:19:08.292Z] "- - -" 0 - "-" "-" 176 115 19 - "-" "-" "-" "-" "34.66.165.46:13306" outbound|13306||34.66.165.46.nip.io 192.168.1.4:53542 192.168.1.4:15443 192.168.1.12:58614 34.66.165.46.nip.io -
    

    ログ出力を終了するには、Ctrl+C キーを押します。

    このログエントリでは、IP アドレス 192.168.1.4 のポート 15443 をリッスンする istio-egressgateway にルーティングされたクライアント リクエストが、さらに IP アドレス 34.66.165.46 のポート 13306. をリッスンするサービス メッシュ外部に存在する外部サービスにルーティングされることを確認できます。この転送は、仮想サービス(client-virtual-service.yaml)の 2 つ目のパートで定義したものです。

サーバー側の上り(内向き)ゲートウェイをテストする

  1. Cloud Shell のサーバー側で、上り(内向き)ゲートウェイ プロキシのログを表示します。

    kubectl --context ${SERVER_CLUSTER} logs -n istio-system -l app=istio-ingressgateway -f
    

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

    [2021-02-10T21:22:27.381Z] "- - -" 0 - "-" "-" 0 78 5 - "-" "-" "-" "-" "100.96.4.8:3306" outbound|3306||mysql.default.svc.cluster.local 100.96.1.3:55730 100.96.1.3:13306 100.96.1.1:42244 34.66.165.46.nip.io -
    

    ログ出力を終了するには、Ctrl+C キーを押します。

    このログエントリでは、IP アドレス 34.66.165.46 のポート 13306 をリッスンしている istio-ingressgateway にルーティングされた外部クライアント リクエストが、ポート 3306. のサービス名 mysql.default.svc.cluster.local によって識別されたメッシュ内の MySQL サービスにさらにルーティングされていることを確認できます。この転送は、上り(内向き)ゲートウェイ(server-ingress-gateway.yaml)で定義したものです。

  2. MySQL サーバーの場合は、Istio プロキシのログを表示します。

    kubectl --context ${SERVER_CLUSTER} logs -l app=mysql -c istio-proxy -f
    

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

    [2021-02-10T21:22:27.382Z] "- - -" 0 - "-" "-" 1555 1471 4 - "-" "-" "-" "-" "127.0.0.1:3306" inbound|3306|mysql|mysql.default.svc.cluster.local 127.0.0.1:45894 100.96.4.8:3306 100.96.1.3:55730 outbound_.3306_._.mysql.default.svc.cluster.local -
    

    ログ出力を終了するには、Ctrl+C キーを押します。

    このログエントリで、IP アドレス 100.96.4.8 のポート 3306 をリッスンしている MySQL データベース サーバーへの受信呼び出しを確認できます。呼び出しは、IP アドレス 100.96.1.3 の上り(内向き)Pod から行われます。

    Envoy のロギング形式の詳細については、Envoy のアクセスログの取得をご覧ください。

  3. データベースをテストして、生成された入力を確認します。

    MYSQL=$(kubectl --context ${SERVER_CLUSTER} get pods -n default | tail -n 1 | awk '{print $1}')
    kubectl --context ${SERVER_CLUSTER} exec $MYSQL -ti -- /bin/bash
    
  4. 作成したデータベースを確認します。

    mysql -pyougottoknowme
    USE test_encrypted_connection;
    SELECT * from message;
    

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

    +------+-----------+
    | id   | content   |
    +------+-----------+
    |    1 | Crypto Hi |
    +------+-----------+
    1 row in set (0.00 sec)
    
  5. MySQL データベースを終了します。

    exit
    exit
    

    exit を 2 回入力する必要があります。1 回目は DB 接続を終了し、2 回目は Pod を終了するためです。

証明書を省略してアクセスをテストする

挿入された証明書を使用してアクセスが機能することをテストおよび確認したら、反対の場合についてもテストします。すなわち、下り(外向き)ゲートウェイと証明書の挿入を省略した場合の動作をテストします。このテストはネガティブ テストとも呼ばれます。

このテストは、サイドプロキシ インジェクションを有効にせずに名前空間で別の Pod を起動することによって実行できます。

  1. Cloud Shell で、新しい名前空間を作成します。

    kubectl --context ${CLIENT_CLUSTER} create ns unencrypted
    
  2. Pod を作成し、コンテナ内で対話型シェルを起動します。

    kubectl --context ${CLIENT_CLUSTER} run -it --image=mysql:5.6 \
    mysql-client-2 --env=SERVICE_URL=$SERVICE_URL \
    -n unencrypted --restart=Never -- /bin/bash
    
  3. 対話型シェルが起動したら、データベースへの接続を試みます。

    mysql -pyougottoknowme -h $SERVICE_URL
    

    30 秒後に次のような出力が表示されます。

    Warning: Using a password on the command line interface can be insecure.
    ERROR 2003 (HY000): Can't connect to MySQL server on '104.154.164.12.nip.io' (110)
    

    この警告は、この Pod が下り(外向き)ゲートウェイを省略し、インターネット経由で上り(内向き)ゲートウェイ($SERVICE_URL)に直接アクセスしようとすることを想定したものです。

  4. サービス IP アドレスの解決を試行します。

    resolveip $SERVICE_URL
    

    出力は次のようになります。実際にご利用の IP アドレスは異なります。

    IP address of 104.154.164.12.nip.io is 104.154.164.12
    

    これで、FQDN が解決可能であり、証明書の挿入を省略したことが原因で実際に接続の失敗が発生することが証明されました。

  5. MySQL の接続と MySQL サーバー Pod を終了します。

    exit
    exit
    

詳細な調査

このチュートリアルの対象外の題目の 1 つは、通常、下り(外向き)構成は、istio-system 名前空間でホストされるため、社内の別のロールまたは組織が所有するということです。ネットワーク管理者だけが、このチュートリアルで説明するリソースを直接作成、変更できるように、Kubernetes RBAC 権限を構成します。

サービス メッシュを使用して安全な通信を行う方法を学びました。次は、データを安全に交換する必要があるアプリケーションと、証明書レイヤまでの暗号化を制御する場所で、サービス メッシュを試してみることをおすすめします。 開始するには、Anthos Service Mesh をインストールします。

2 つの GKE クラスタを使用して、このチュートリアルの手法で統合することを試してみます。この手法は、Anthos プラットフォームでも 2 つの外部 Kubernetes クラスタ間でも機能します。

サービス メッシュは、クラスタ内部と外部サービスのセキュリティを改善するための優れた方法です。試行対象の最後の 1 つのユースケースは、2 つ目の Kubernetes クラスタではなく、サードパーティ プロバイダ(決済機関など)である mTLS エンドポイントを設定することです。

クリーンアップ

このチュートリアルで使用したリソースに対する Google Cloud アカウントへの課金を回避するには、プロジェクトを削除します。

プロジェクトの削除

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ