TT Lab
はじめる
学ぶ 学習パス コース

Istio深化 — なぜそう流れるのか

ブートストラップを作り、起動しない二つの場合を見分ける

TT Labで続きを見る

目標

注入の出力物からpilot-agentの材料を取り出し、istiod式のブートストラップを手で書いて検査し、サイドカーが起動しない2つの場合(設定の拒否と、設定サーバーの不在)を、自分で作って見分けます。

なぜ重要なのか

サイドカー障害の半分は、2つの症状で現れます。コンテナがCrashLoopしているか、起動しているのにReadyでないかです。前者はブートストラップが間違っていて、後者はistiodから設定を受け取れていません。ブートストラップが何から作られるかを知っていれば、ログ1行と管理ポート1回で、2つを見分けられます。

ステップ

  1. /root/ist2-bootでistioctl kube-injectで/opt/lab/fixtures/istio/inject-target.yamlにサイドカーを入れて、/root/ist2-boot/inject.yamlとして保存してください(注入設定の3つのファイルをすべて渡します)。次に、istio-proxyコンテナのargsを読んで、/root/ist2-boot/01-args.txtに4行を書いてください。role=(2つ目の引数)、domain=(--domainの次の引数をそのまま)、proxy_log_level=(--proxyLogLevel=の値)、component_log_level=(--proxyComponentLogLevel=の値)です。
  2. /root/ist2-boot/inject.yamlのistio-proxyの環境変数から7つを選んで、/root/ist2-boot/02-env.txtに이름=값の形式で書いてください(プレースホルダーは名前と値です)。CA_ADDR、PILOT_CERT_PROVIDER、ISTIO_META_CLUSTER_ID、TRUST_DOMAIN、ISTIO_META_WORKLOAD_NAMEはvalueをそのまま、POD_NAMESPACE、SERVICE_ACCOUNTは、値がPodから来るので、fieldRef:<fieldPath>の形(例: fieldRef:metadata.name)で書きます。
  3. /opt/istio/mesh-config.yamlを読んで、/root/ist2-boot/03-mesh.txtに4行を書いてください。discovery_address=(defaultConfig.discoveryAddress)、root_namespace=、trust_domain=、そしてca_addr_same=には、そのdiscoveryAddressがステップ2のCA_ADDRと文字どおり同じならyes、そうでなければnoを書きます。
  4. /root/ist2-boot/bootstrap.yamlにブートストラップを書いてください。管理ポートは9982です。node.idは、4つの欄sidecar、10.0.0.7、payments-6d5f9.default、default.svc.cluster.localを、この順序でチルダ(U+007E)でつないだものです。node.clusterはpayments.default、node.metadataには、CLUSTER_ID・MESH_ID・NAMESPACE・WORKLOAD_NAMEの4つのキーを置きます(値は、ステップ2のISTIO_META_変数と、ネームスペースdefault)。dynamic_resourcesは、ADS(gRPC)でcds_config・lds_configを受け取り、initial_fetch_timeout: 0sにします。ADSが指す静的クラスターの名前はxds-grpcで、STRICT_DNSでステップ3のdiscovery_addressを指し、HTTP/2を使います。envoy --mode validateの出力と終了コードを、/root/ist2-boot/04-validate.txtに入れてください(最後の行はrc=0)。
  5. /root/ist2-boot/bootstrap.yamlを/root/ist2-boot/boot-typo.yamlにコピーしてから、ADS側(ads_configのcluster_name)だけをistiod-xdsに変えてください(静的クラスター名xds-grpcはそのまま)。envoy --mode validateで検査して、出力と終了コードを/root/ist2-boot/05-typo.txtに入れてください(最後の行はrc=で)。
  6. /root/ist2-boot/bootstrap.yamlでEnvoyを起動してください(このPodにはistiodがありません)。管理ポートの/config_dumpから、ブートストラップのnodeのうち、id・cluster・metadataの3つのフィールドだけを取り出して/root/ist2-boot/06-node.jsonに保存し、/root/ist2-boot/06-ready.txtに3行を書いてください。ready_code=(/readyのHTTPコード)、ready_body=(その本文)、connected_state=(統計control_plane.connected_stateの値)です。
  7. /root/ist2-boot/inject.yamlから身元の材料を探して、/root/ist2-boot/07-identity.txtに5行を書いてください。token_volume=(serviceAccountTokenを投影するボリューム名)、token_audience=(そのトークンのaudience)、token_mount=(そのボリュームがistio-proxyにマウントされるパス)、ca_configmap=(ボリュームistiod-ca-certが読むConfigMap名)、ca_mount=(istiod-ca-certがistio-proxyにマウントされるパス)です。
  8. /root/ist2-boot/08-report.mdに、xds_address=、xds_cluster=、typo_rc=、ready_without_istiod=の4行を書き(それぞれ、xDSを受け取るアドレス、ADSが指すべき静的クラスターの名前、ステップ5の終了コード、ステップ6で見た/readyの本文)、その下に、- で始まる説明を4行以上書いてください。

参考

プロキシコンテナはEnvoyではなくpilot-agentを起動する

/root/ist2-bootでistioctl kube-injectで/opt/lab/fixtures/istio/inject-target.yamlにサイドカーを入れて、/root/ist2-boot/inject.yamlとして保存してください(注入設定の3つのファイルをすべて渡します)。次に、istio-proxyコンテナのargsを読んで、/root/ist2-boot/01-args.txtに4行を書いてください。role=(2つ目の引数)、domain=(--domainの次の引数をそのまま)、proxy_log_level=(--proxyLogLevel=の値)、component_log_level=(--proxyComponentLogLevel=の値)です。

イメージのエントリポイントはpilot-agentで、引数の最初の2つの言葉proxy sidecarは、「サイドカーの役割のプロキシを起動せよ」というサブコマンドです。--domainの値に入っている$(POD_NAMESPACE)は、シェルではなくkubeletが、コンテナを起動するときに同じ名前の環境変数に置き換えて入れる、Kubernetesの文法です。マニフェストには文字どおり残っているので、そのまま書き写してください。--x=값(プレースホルダーは値です)の形は、sed 's/^--x=//'で値だけを切り出せば済みます。

環境変数がプロキシのラベルとアドレス帳になる

/root/ist2-boot/inject.yamlのistio-proxyの環境変数から7つを選んで、/root/ist2-boot/02-env.txtに이름=값の形式で書いてください(プレースホルダーは名前と値です)。CA_ADDR、PILOT_CERT_PROVIDER、ISTIO_META_CLUSTER_ID、TRUST_DOMAIN、ISTIO_META_WORKLOAD_NAMEはvalueをそのまま、POD_NAMESPACE、SERVICE_ACCOUNTは、値がPodから来るので、fieldRef:<fieldPath>の形(例: fieldRef:metadata.name)で書きます。

環境変数は2種類です。注入するときにすでに決まっている値(value)と、Podが起動するときにkubeletが埋める値(valueFrom.fieldRef)です。Podの名前・ネームスペース・サービスアカウントは、注入の瞬間にはわからないので、後者です。ISTIO_META_で始まるものは、pilot-agentがプレフィックスを取り除いて、Envoyのnode.metadataへ移します。istiodがこのプロキシを見分けるラベルです。yqで.env[] | select(.name=="CA_ADDR")のように、1つずつ選んでください。

メッシュ設定のデフォルト値とCAのアドレスを照合する

/opt/istio/mesh-config.yamlを読んで、/root/ist2-boot/03-mesh.txtに4行を書いてください。discovery_address=(defaultConfig.discoveryAddress)、root_namespace=、trust_domain=、そしてca_addr_same=には、そのdiscoveryAddressがステップ2のCA_ADDRと文字どおり同じならyes、そうでなければnoを書きます。

defaultConfigは、メッシュ全体のプロキシのデフォルト設定で、そのうちdiscoveryAddressが、xDSを受け取るアドレスです。istiodは設定サーバーであり、同時に認証局(CA)でもあるので、デフォルトのインストールでは、2つのアドレスが同じ15012ポートを指します。2つが違ってくるのは、外部のCAをつないだときです。値はyqで取り出し、比較はシェルの[ "$a" = "$b" ]で行ってください。

istiod式のブートストラップを手で書いて検査する

/root/ist2-boot/bootstrap.yamlにブートストラップを書いてください。管理ポートは9982です。node.idは、4つの欄sidecar、10.0.0.7、payments-6d5f9.default、default.svc.cluster.localを、この順序でチルダ(U+007E)でつないだものです。node.clusterはpayments.default、node.metadataには、CLUSTER_ID・MESH_ID・NAMESPACE・WORKLOAD_NAMEの4つのキーを置きます(値は、ステップ2のISTIO_META_変数と、ネームスペースdefault)。dynamic_resourcesは、ADS(gRPC)でcds_config・lds_configを受け取り、initial_fetch_timeout: 0sにします。ADSが指す静的クラスターの名前はxds-grpcで、STRICT_DNSでステップ3のdiscovery_addressを指し、HTTP/2を使います。envoy --mode validateの出力と終了コードを、/root/ist2-boot/04-validate.txtに入れてください(最後の行はrc=0)。

pilot-agentが作るブートストラップで、静的なクラスターは、事実上xds-grpc1つだけです。リスナーもサービスのクラスターも、すべてそのクラスターを通じて、ADSで受け取ります。gRPCはHTTP/2の上で動くので、クラスターでhttp2_protocol_optionsを有効にする必要があります。node.idの4つの欄は、役割・Pod IP・파드이름.네임스페이스・네임스페이스.svc.cluster.local(プレースホルダーはPod名とネームスペースです)の順序です。initial_fetch_timeout: 0sは、「設定を受け取るまで無限に待て」という意味で、Istioが実際に使っている値です。

クラスター名の1文字がCrashLoopを作る

/root/ist2-boot/bootstrap.yamlを/root/ist2-boot/boot-typo.yamlにコピーしてから、ADS側(ads_configのcluster_name)だけをistiod-xdsに変えてください(静的クラスター名xds-grpcはそのまま)。envoy --mode validateで検査して、出力と終了コードを/root/ist2-boot/05-typo.txtに入れてください(最後の行はrc=で)。

ADSのcluster_nameは、「この名前の静的クラスターでgRPC接続を開け」という意味です。その名前が静的クラスターのリストになければ、Envoyは設定サーバーへ行く道がないので、起動する前に拒否します。Kubernetesでは、コンテナがすぐに終了して、再び起動することを繰り返します。CrashLoopBackOffです。拒否メッセージに、間違った名前がそのまま出力されるので、ログ1行で原因を突き止められます。

istiodに届かないプロキシは準備ができない

/root/ist2-boot/bootstrap.yamlでEnvoyを起動してください(このPodにはistiodがありません)。管理ポートの/config_dumpから、ブートストラップのnodeのうち、id・cluster・metadataの3つのフィールドだけを取り出して/root/ist2-boot/06-node.jsonに保存し、/root/ist2-boot/06-ready.txtに3行を書いてください。ready_code=(/readyのHTTPコード)、ready_body=(その本文)、connected_state=(統計control_plane.connected_stateの値)です。

サイドカーの準備状態は、結局Envoyの/readyです(pilot-agentが、15021でそれを代わりに答えます)。initial_fetch_timeout: 0sで起動したEnvoyは、最初のCDS・LDSを受け取るまで、初期化を終えずに待ちます。この状態でも管理ポートは開いているので、何が適用されたかは、config_dumpで見られます。jq '.configs[0].bootstrap.node | {id, cluster, metadata}'で、3つのフィールドだけを選んでください。/readyのコードは、curl -o /dev/null -w '%{http_code}'で受け取ります。

身元は2つのボリュームから始まる

/root/ist2-boot/inject.yamlから身元の材料を探して、/root/ist2-boot/07-identity.txtに5行を書いてください。token_volume=(serviceAccountTokenを投影するボリューム名)、token_audience=(そのトークンのaudience)、token_mount=(そのボリュームがistio-proxyにマウントされるパス)、ca_configmap=(ボリュームistiod-ca-certが読むConfigMap名)、ca_mount=(istiod-ca-certがistio-proxyにマウントされるパス)です。

pilot-agentは、起動するとすぐにistiod(= CA_ADDR)に、証明書の署名リクエストを送ります。そのとき、「私はこのサービスアカウントです」を証明するのが、audienceがistio-caに絞られたプロジェクテッドトークンで、相手が本物のistiodかを確認するのが、ConfigMapで配布されたルート証明書です。ボリュームは.volumes[]、マウントされるパスは、istio-proxyの.volumeMounts[]にあります。プロジェクテッドボリュームは、.projected.sources[].serviceAccountTokenの下を見てください。

サイドカーが起動しないときの点検表にまとめる

/root/ist2-boot/08-report.mdに、xds_address=、xds_cluster=、typo_rc=、ready_without_istiod=の4行を書き(それぞれ、xDSを受け取るアドレス、ADSが指すべき静的クラスターの名前、ステップ5の終了コード、ステップ6で見た/readyの本文)、その下に、- で始まる説明を4行以上書いてください。

値は前のステップのファイルから移してください。この表は、「サイドカーがCrashLoopしている」「PodがReadyにならない」に遭ったときに、どこから見るかを整理するものです。説明の行には、症状と原因を対にして書くとよいです。