コンテンツにスキップ

プラクティス集

はじめに

本サイトにつきまして、【IT技術の知見】はじめに - 俺の技術ノート をご認識のほど宜しくお願いいたします。


01. セットアップ

少ないコントロールプレーン

クラウドプロバイダーで Istio を稼働させる場合、各 AZ や各リージョンにコントロールプレーンを 1 個だけセットアップし、できるだけ多くのマイクロサービスのサービスメッシュとなるようにする。


冗長化

コントロールプレーンの可用性を高めるために、コントロールプレーンを異なる AZ に冗長化させる。


サービスメッシュに登録しない Pod の選定 (サイドカーモードの場合)

▼ 監視系の Pod

サイドカーモードに登録する Pod が増えると、その分 istio-proxy が増える。

そのため、Pod 当たりのハードウェアリソースの消費量が増えてしまう。

テレメトリーを収集する必要のない Pod (例:監視を責務に持つ Pod) は、サイドカーモードへ登録しないようにする。

▼ Job 配下の Pod

ネイティブサイドカーを使用しない場合、Job 配下の Pod 内でジョブ処理が終了しても istio-proxy が終了せず、Pod のフェーズが Completed にならない問題がある。

Job 配下の Pod は、基本的にはサービスメッシュへ登録しない。

ただし、HTTP リクエストを定期的に送信するジョブは、トラフィック管理のために登録する。

ネイティブサイドカーを使用する場合、ジョブ処理の完了後に istio-proxy が自動で終了する。

ネイティブサイドカーを使用せずにサービスメッシュへ登録する場合は、Pod 内のコンテナで、ジョブ処理の成否にかかわらず istio-proxy の『localhost:15020/quitquitquit』へ POST リクエストを送信する。

apiVersion: batch/v1
kind: Job
metadata:
  name: foo-job
spec:
  template:
    metadata:
      name: foo-job
    spec:
      containers:
        - name: foo
          command:
            - /bin/bash
            - -c
          args:
            - |
              trap 'curl -fs -X POST http://localhost:15020/quitquitquit || echo "Failed to shutdown istio-proxy"' EXIT
              until curl -fsI http://localhost:15021/healthz/ready; do
                echo "Waiting for Sidecar to be healthy";
                sleep 3;
              done
              echo "Sidecar available. Running job command..."
              <CronJobのコマンド>
              exit $?


GitHub Actions 上で実行する場合

▼ .github/workflows/test-istio.yml ファイル

GitHub Actions の runner 上で Cluster を作成し、Istio をデプロイする。

リポジトリに setup-asdf.sh、.tool-versions、Helmfile の設定を配置し、Repository variables に AWS_ACCOUNT_ID と AWS_DEFAULT_REGION、Actions secrets に AWS の資格情報を登録する。

name: Test Istio

on:
  push:
    branches: [master, develop]
  pull_request:
  workflow_dispatch:

permissions:
  contents: read

env:
  # ブランチとイベントに応じてHelmfileの実行環境を切り替える
  ENV: ${{ (github.head_ref || github.ref_name) == 'master' && 'prd' || (github.head_ref || github.ref_name) == 'develop' && 'stg' || 'tes' }}
  AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }}
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
  # 検証するKubernetesとIstioのバージョン
  K8S_CURRENT_VERSION: "1.24.0"
  K8S_NEXT_VERSION: "1.26.0"
  ISTIO_CURRENT_VERSION: "1.15.3"
  ISTIO_NEXT_VERSION: "1.17.5"
  CLUSTER_NAME: ci-${{ github.run_id }}-${{ github.run_attempt }}

jobs:
  test-istio:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha || github.sha }}
      - name: Set up tools
        run: |
          source setup-asdf.sh
          # 後続ステップでもasdfで導入したコマンドを使用する
          echo "$HOME/.asdf/bin" >> "$GITHUB_PATH"
          echo "$HOME/.asdf/shims" >> "$GITHUB_PATH"
          curl -fsSL https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
          k3d version
          curl -fsSLO "https://dl.k8s.io/release/v${K8S_NEXT_VERSION}/bin/linux/amd64/kubectl"
          chmod +x kubectl
          sudo mv kubectl /usr/local/bin/kubectl
          kubectl version --client
      - name: Set up k3d registry configuration
        env:
          AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
        run: |
          AWS_ECR="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com"
          cat <<EOF > k3d-config.yaml
          apiVersion: k3d.io/v1alpha5
          kind: Simple
          registries:
            config: |
              mirrors:
                ${AWS_ECR}:
                  endpoint:
                    - "https://${AWS_ECR}"
              configs:
                ${AWS_ECR}:
                  auth:
                    username: AWS
                    password: $(aws ecr get-login-password --region "$AWS_DEFAULT_REGION")
          EOF
      - name: Create cluster
        run: |
          # GitHub-hosted runnerのDockerを使用し、同じジョブ内でClusterを検証する
          k3d cluster create --config k3d-config.yaml "$CLUSTER_NAME" \
            --image "rancher/k3s:v${K8S_NEXT_VERSION}-k3s1" --agents 2 || \
          k3d cluster create --config k3d-config.yaml "$CLUSTER_NAME" \
            --image "rancher/k3s:v${K8S_NEXT_VERSION}-rc1-k3s1" --agents 2
          kubectl label node "k3d-${CLUSTER_NAME}-agent-0" node.kubernetes.io/nodetype=ingress --overwrite
          kubectl label node "k3d-${CLUSTER_NAME}-agent-1" node.kubernetes.io/nodetype=system --overwrite
          k3d cluster list
          kubectl get node --show-labels
      - name: Deploy and verify Istio
        run: |
          kubectl apply -f "https://raw.githubusercontent.com/istio/istio/${ISTIO_NEXT_VERSION}/manifests/charts/base/crds/crd-all.gen.yaml"
          kubectl create ns istio-ingress
          kubectl label ns istio-ingress istio.io/rev=default
          kubectl create ns istio-system
          kubectl get ns -L istio.io/rev
          helmfile -e "$ENV" -f "helmfile_istiod_${ISTIO_NEXT_VERSION//./-}.yaml" apply --skip-crds --skip-diff-on-install
          helmfile -e "$ENV" -f helmfile_istio-base.yaml apply --skip-diff-on-install
          helmfile -e "$ENV" -f helmfile_istio-ingressgateway.yaml apply --skip-diff-on-install
          istioctl version
          istioctl proxy-status
          kubectl get all -n istio-ingress
          kubectl get all -n istio-system
      - name: Delete cluster
        run: k3d cluster delete "$CLUSTER_NAME"

▼ setup-asdf.sh ファイル

#!/bin/bash

# asdfをインストールする
git clone --depth 1 --branch v0.14.1 https://github.com/asdf-vm/asdf.git ~/.asdf
export ASDF_DIR=~/.asdf
source "$ASDF_DIR/asdf.sh"

# コマンドをインストールする
asdf plugin add helm https://github.com/Antiarchitect/asdf-helm.git
asdf plugin add helmfile https://github.com/feniix/asdf-helmfile.git
asdf plugin add istioctl https://github.com/virtualstaticvoid/asdf-istioctl.git
asdf install
asdf list

# 正しいバージョンをインストールできていることを確認する
helm version
helmfile version
istioctl version


02. トラフィック管理

Istio Ingress Gateway について

▼ Istiod コントロールプレーンとは異なる Namespace におく

セキュリティ上の理由から、Istio Ingress Gateway と Istiod コントロールプレーンは異なる Namespace におくほうがよい。

▼ NodePort Service を選ぶ

Istio Ingress Gateway では、内部的に作成される Service のタイプ (NodePort Service、ClusterIP Service、LoadBalancer Service) を選べる。

NodePort Service を選ぶ場合、Node の送信元に開発者がロードバランサーを作成し、NodePort Service にインバウンド通信をルーティングできるようにする。

パブリックネットワーク
⬇⬆️︎
Amazon Route 53
⬇⬆️︎
# L7 ロードバランサー (単一の L7 ロードバランサーを作成し、異なるポートを開放する複数の L4 ロードバランサーの振り分ける)
AWS ALB
⬇⬆️︎
# L4 ロードバランサー
NodePort Service (Istio Ingress Gateway)
⬇⬆️︎
Istio Ingress Gateway (Gateway と VirtualService の設定を使用)
⬇⬆️︎
# Service を経由せず、Istio Ingress Gateway から宛先 Pod へ直接ルーティング
Pod (istio-proxy)

一方で、LoadBalancer Service を選ぶ場合、クラウドプロバイダーのロードバランサーが自動的に作成される。

そのため、このロードバランサーから LoadBalancer Service へルーティングできるようにする。

LoadBalancer Service では、クラウドプロバイダーのリソースと Kubernetes リソースの責務の境界が曖昧になってしまうため、NodePort Service を選ぶようにする。

補足として、デフォルトでは Istio Ingress Gateway の内部では LoadBalancer Service を作成されてしまう。

NodePort Service を選ぶためには、Istio Ingress Gateway ではなく、IstioOperator や istio チャート上で Service のタイプを設定し、Istio Ingress Gateway を作成する必要がある。

▼ ClusterIP Service を選ぶ (AWS Load Balancer Controller を使用する場合のみ)

AWS Load Balancer Controller を使用する場合、Istio Ingress Gateway で ClusterIP Service を使用できる。

Ingress にて、alb.ingress.kubernetes.io/target-type キー値を ip とすると、AWS Load Balancer Controller は Pod にリクエストを直接的に L7 ロードバランシングする。

パブリックネットワーク
⬇⬆️︎
Amazon Route 53
⬇⬆️︎
# L7 ロードバランサー (単一の L7 ロードバランサーを作成し、異なるポートを開放する複数の L4 ロードバランサーの振り分ける)
AWS Load Balancer ControllerによるAWS ALB
⬇⬆️︎
# ClusterIP Service を経由せず、ALB から Pod へ直接ルーティング
Istio Ingress Gateway の Pod (Gateway と VirtualService の設定を使用)
⬇⬆️︎
# Service を経由せず、Istio Ingress Gateway から宛先 Pod へ直接ルーティング
Pod (istio-proxy)

▼ 共有入口と占有入口を選ぶ

Istio Ingress Gateway を複数の Namespace で共有する共有入口パターンと、特定の Namespace が占有する占有入口パターンがある。

入口の責任範囲を分割する必要がない場合は共有し、流入経路や管理の責任範囲を分けたい場合は Namespace ごとに配置する。

占有入口パターンでは、問題の原因を切り分けやすくなる一方で、Istio Ingress Gateway の管理が煩雑になる。


デフォルトルートにサブセットを指定する

複数のサブセットを定義する場合でも、デフォルトルートでは特定のサブセットを指定する。

これにより、個別のルーティング条件に一致しない通信も、特定のバージョンの Pod にルーティングできる。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: foo-virtual-service
spec:
  hosts:
    # 特定のマイクロサービスへのリクエストのみを扱うため、ホスト名もそれのみを許可する
    # ただし、gateways オプションがある VirtualService ではワイルドカードする
    - foo
  http:
    - route:
        - destination:
            host: foo
            subset: v1


Istio リソースのリクエスト可能な範囲を限定する

Istio リソースを同じ Namespace 内だけに公開する場合、.spec.exportTo キーでは『. (ドット) 』を設定する。

これにより、Istiod が設定を同じ Namespace のプロキシだけに配布する。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: foo-virtual-service
spec:
  exportTo:
    - "."
  # 特定のマイクロサービスへのリクエストのみを扱うため、ホスト名もそれのみを許可する
  # ただし、gateways オプションがある VirtualService ではワイルドカードする
  hosts:
    - foo
  http:
    - route:
        - destination:
            host: foo


DestinationRule を最初に更新する

新しいサブセットを追加する場合、DestinationRule を最初に更新する。

これにより、ダウンタイムなしでサブセットを追加できる。

DestinationRule を更新する前に新しいサブセットを持つ VirtualService を更新してしまうと、VirtualService は新しいサブセットを持つ DestinationRule を見つけられず、503 レスポンスを返信してしまう。

DestinationRule を最初に更新し、正常に完了することを待機した後に、VirtualService を更新する。


02-02. 通信ルーティングのパターン

LoadBalancer Service の場合

LoadBalancer Service を使用する場合、以下のようなネットワーク経路がある。

パブリックネットワーク
⬇⬆️︎
Amazon Route 53
⬇⬆️︎
# L4 ロードバランサー
LoadBalancer Service (Istio Ingress Gateway) によるAWS NLB
⬇⬆️︎
Istio Ingress Gateway (Gateway と VirtualService の設定を使用)
⬇⬆️︎
# Service を経由せず、Istio Ingress Gateway から宛先 Pod へ直接ルーティング
Pod (istio-proxy)


NodePort Service の場合

▼ L7 ロードバランサーがない場合

Node の NIC の宛先情報は、Node 外から宛先 IP アドレスとして指定できるため、インバウンド通信に Ingress を必要としない。

パブリックネットワーク
⬇⬆️︎
# L4 ロードバランサー
NodePort Service (Istio Ingress Gateway)
⬇⬆️︎
Istio Ingress Gateway (Gateway と VirtualService の設定を使用)
⬇⬆️︎
# Service を経由せず、Istio Ingress Gateway から宛先 Pod へ直接ルーティング
Pod (istio-proxy)

▼ L7 ロードバランサーがある場合

パブリックプロバイダーのロードバランサー (例:AWS ALB) を別に配置する。

単一の L7 ロードバランサーを作成し、複数の L4 ロードバランサーに振り分けるとよい。

パブリックネットワーク
⬇⬆️︎
Amazon Route 53
⬇⬆️︎
# L7 ロードバランサー (単一の L7 ロードバランサーを作成し、異なるポートを開放する複数の L4 ロードバランサーの振り分ける)
AWS ALB
⬇⬆️︎
# L4 ロードバランサー
NodePort Service (Istio Ingress Gateway)
⬇⬆️︎
Istio Ingress Gateway (Gateway と VirtualService の設定を使用)
⬇⬆️︎
# Service を経由せず、Istio Ingress Gateway から宛先 Pod へ直接ルーティング
Pod (istio-proxy)


ClusterIP Service の場合

AWS Load Balancer Controller を使用する場合、Istio Ingress Gateway で ClusterIP Service を使用できる。

Ingress にて、alb.ingress.kubernetes.io/target-type キー値を ip とすると、AWS Load Balancer Controller は Pod にリクエストを直接的に L7 ロードバランシングする。

パブリックネットワーク
⬇⬆️︎
Amazon Route 53
⬇⬆️︎
# L7 ロードバランサー (単一の L7 ロードバランサーを作成し、異なるポートを開放する複数の L4 ロードバランサーの振り分ける)
AWS Load Balancer ControllerによるAWS ALB
⬇⬆️︎
# ClusterIP Service を経由せず、ALB から Pod へ直接ルーティング
Istio Ingress Gateway の Pod (Gateway と VirtualService の設定を使用)
⬇⬆️︎
# Service を経由せず、Istio Ingress Gateway から宛先 Pod へ直接ルーティング
Pod (istio-proxy)


03. アップグレード

設計規約

▼ サポート期間

Istio では、マイナーバージョンごとのアップグレードを推奨しており、またマイナーバージョンのサポートが半年ごとに終了する。

実質的に半年ごとにアップグレード工数が発生する。

▼ マイナーバージョン単位でアップグレード

Istio の開発プロジェクトでは、マイナーバージョンを 1 個ずつ新しくするアップグレードしか検証していない。

そのため、マイナーバージョンを 2 個以上跨いだアップグレードを推奨していない。

▼ Istiod コントロールプレーンでダウンタイムを発生させない

Istiod コントロールプレーンでダウンタイムが発生すると、istio-proxy 内の pilot-agent が最新の宛先情報を取得できなくなる。

そのため、古いバージョンのマイクロサービスの宛先情報を使用してしまう。

Istiod コントロールプレーンをカナリアアップグレードを採用する。

▼ Istio Ingress Gateway でダウンタイムを発生させない

Istio Ingress Gateway でダウンタイムが発生すると、アプリへのインバウンド通信が遮断されてしまう。


インプレース方式

▼ インプレース方式とは

既存の Istiod コントロールプレーンと Istio Ingress Gateway の両方をインプレース方式でアップグレードする。

▼ 手順

  1. CRD を更新する。

    必要なCRDのマニフェストは、リポジトリで確認する必要がある。

    $ git clone https://github.com/istio/istio.git
    
    $ kubectl apply -f manifests/charts/base/crds
    
  2. Istiod コントロールプレーンと Istio Ingress Gateway の両方をインプレース方式でアップグレードする。

    $ istioctl install
    
  3. データプレーンの istio-proxy を再インジェクションする。

    $ kubectl rollout restart deployment app-deployment -n app
    


カナリア方式

▼ カナリア方式とは

▼ istioctl コマンドの場合

▼ helm コマンドの場合

  1. Helm では CRD を管理しないようにし、kubectl コマンドでこれを作成する。

    $ kubectl diff -f https://raw.githubusercontent.com/istio/istio/1.15.3/manifests/charts/base/crds/crd-all.gen.yaml
    
  2. istiod チャートを使用して、古いバージョンの MutatingWebhookConfiguration のみを削除する。

    この時、既存のリリースは古いリリースとして扱う。

    $ helm upgrade <古いバージョンのリリース名> <チャートリポジトリ名>/istiod -n istio-system --version <古いバージョン> --set revisionTags=null
    
  3. istiod チャートを使用して、新しいバージョンの MutatingWebhookConfiguration を作成しつつ、Istiod コントロールプレーンに関する Kubernetes リソースを変更する。

    この時、リリースを新しく命名する。

    $ helm upgrade <新しいバージョンのリリース名> <チャートリポジトリ名>/istiod -n istio-system --version <新しいバージョン>
    
  4. 特定の Namespace をアップグレードする。

  5. 動作確認し、問題なければ、残りの Namespace もアップグレードする。

  6. istiod チャートを使用して、古いリリースで作成した Istiod コントロールプレーンに関する Kubernetes リソースを削除する。

    $ helm upgrade <古いバージョンのリリース名> <チャートリポジトリ名>/istiod -n istio-system --version <古いバージョン> --set revisionTags=null
    
  7. istio-base を使用して、Istio に関する CRD を変更する。

    この時、リリースを新しく命名する。

    $ helm upgrade <新しいバージョンのリリース名> <チャートリポジトリ名>/base -n istio-system --version <新しいバージョン>
    
  8. gateway チャートを使用して、Istio Ingress Gateway に関する Kubernetes リソースを変更する。

    この時、リリースを新しく命名する。

    $ helm upgrade <新しいバージョンのリリース名> <チャートリポジトリ名>/gateway -n istio-ingress --version <新しいバージョン>
    


04. アーキテクチャ特性

性能

リクエストが istio-proxy を経由する分だけレイテンシーが増加し、また istio-proxy ごとに CPU やメモリを消費するため、性能が劣化する。