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

Kubernetesディストリビューション — 自分で立てる

SSH で入ろうとしたらシェルがなかった

TT Labで続きを見る

目標

シェルもSSHもないTalos Linuxのノードを、talosctlとAPIだけで調べ、マシン設定を再起動なしで変更して元に戻し、検証が防ぐミスと防げないミスをログで見分けます。

なぜ重要なのか

k3s・k0s・kubeadmはLinuxの上にKubernetesをインストールする方式なので、問題が起きたらノードに入ってログを見てファイルを直す習慣が通用します。TalosはOS自体がKubernetes専用に書き直されていて、シェル・SSH・パッケージマネージャーがなく、ルートファイルシステムは読み取り専用です。 その代わり、ノードごとにgRPC API(apid)があり、すべての確認と変更がそのAPIを通り、マシン設定はバージョンが付いたリソースとして残ります。この設計は、「そのノードにだけ誰かが手を加えた設定」というスノーフレークサーバーを根本的に防ぐ代わりに、入って直す応急処置を不可能にします。 そのため運用者は、設定が検証で拒否される場合と、検証は通ったのにサービスが止まる場合を、APIで読み取って見分けられる必要があります。このラボでは、その2つの場合をわざと起こしてみます。

ステップ

  1. コントロールプレーンのノードのコンテナtalos-default-controlplane-1にdocker execでshを実行してみて、ノードIPのTCPポート22とポート50000が開いているかを確認してください。結果を次のフィールドで書いてください(書き込み先: /root/talos-lab/noshell.json)。container、node_ip(Dockerネットワークtalos-defaultのアドレス)、exec_sh_exit(docker execの終了コード、数値)、exec_sh_error(エラーメッセージ1行)、port22_open、port50000_open(ブール値)です。
  2. talosctlで2つのノード(コントロールプレーン10.5.0.2、ワーカー10.5.0.3)のサービス一覧を読み取り、次のフィールドを書いてください(書き込み先: /root/talos-lab/services.json)。controlplane、worker(各ノードのサービスIDをソートした配列)とcontrolplane_only(コントロールプレーンにだけあるサービスIDをソートした配列)です。
  3. talosctl get membersでメンバーを読み取り、次のフィールドを書いてください(書き込み先: /root/talos-lab/members.json)。talos_version(10.5.0.2のサーバーのTalosタグ。例: v0.0.0の形式)、members(ホスト名 → {"type": 머신 종류, "addresses": 주소 배열}。プレースホルダーはマシンの種類とアドレスの配列です)、worker_mc_version(ワーカー10.5.0.3のMachineConfigリソースv1alpha1のmetadata.version、数値)です。
  4. talosctl kubeconfigでコントロールプレーン(10.5.0.2)からkubeconfigを取得して保存し(保存先: /root/talos-lab/kubeconfig。シンボリックリンクは不可)、そのファイルでkubectlを使って次のフィールドを書いてください(書き込み先: /root/talos-lab/cluster.json)。api_server(そのkubeconfigのserverアドレス)、nodes(ノード名 → InternalIP)、kubelet_version、pod_subnets、service_subnets(コントロールプレーンのマシン設定のcluster.networkの値)、overlaps_host(2つのCIDR範囲のどちらかでもホストクラスターの10.244.0.0/16か10.96.0.0/12と重なればtrue)です。
  5. ワーカーのmachine.nodeLabelsにlab.talos.dev/pool: blueを追加するstrategic mergeパッチを書き(書き込み先: /root/talos-lab/05-labels.yaml)、ワーカー(10.5.0.3)にだけ--mode=no-rebootで適用してください。適用コマンドの出力(標準出力とエラー出力)を保存し(保存先: /root/talos-lab/patch-out.txt)、次のフィールドを書いてください(書き込み先: /root/talos-lab/patch.json)。node、mc_version_before、mc_version_after(適用前後のワーカーのMachineConfig version)、container_started_at(ワーカーのコンテナtalos-default-worker-1のState.StartedAt)です。KubernetesのNodeにラベルが付いている必要があります。
  6. ワーカーにラベルlab.talos.dev/canary: "on"を--mode=try --timeout=30sで適用してください。適用直後にワーカーのMachineConfig versionを読み取り、KubernetesのNodeにラベルが付くことを確認してから、元に戻ってラベルが消えるまで待ち、もう一度versionを読み取ってください。次のフィールドを書いてください(書き込み先: /root/talos-lab/try.json)。timeout_sec(数値)、version_during、seen_on_node(ブール値)、version_after_revertです。ステップ5のpoolラベルは残っている必要があります。
  7. ワーカーのmachine.nodeLabelsに、名前がlab.talos.dev/team name(空白を含む名前)で値がplatformのラベルを追加するパッチを書き(書き込み先: /root/talos-lab/07-bad.yaml)、ワーカーに適用してみてください。出力を保存し(保存先: /root/talos-lab/rejected.txt)、適用前後のワーカーのMachineConfig versionをversion_before、version_afterとして書いてください(書き込み先: /root/talos-lab/rejected.json)。
  8. ワーカーのmachine.kubelet.extraArgsにmax-pod: "150"を追加するパッチを書き(書き込み先: /root/talos-lab/08-kubelet.yaml)、ワーカーに適用してください。受け入れられた直後にversionを読み取り、talosctlでkubeletのサービスの状態とログを見て原因を探し、次のフィールドを書いてください(書き込み先: /root/talos-lab/kubelet-diag.json)。accepted_version(適用直後のワーカーのMachineConfig version)、service_state(そのとき見たkubeletサービスのSTATE)、error_line(原因が書かれたログ1行)です。そのあと、$patch: deleteでその引数だけを削除するパッチを書いて適用し(書き込み先: /root/talos-lab/08-fix.yaml)、kubeletが正常で、ワーカーのNodeがReadyに戻るようにしてください。
  9. 次のフィールドを書いてください(書き込み先: /root/talos-lab/report.json)。shell_in_node(ブール値)、ssh_port_open(ブール値)、api_port(Talos APIのポート番号、数値)、worker_config_changes(ワーカーのMachineConfigが最初から変わった回数 = 現在のversion - 1)、worker_restarts(ワーカーのコンテナがステップ5以降に再起動された回数)、rejected_field(ステップ7で検証に引っかかった設定パス。例: machine.xの形式)、broken_service(ステップ8で止まったサービスID)、apply_log_line(ワーカーのtalosctl logs machinedで、設定適用のAPI呼び出しが記録された1行)、summary(イミュータブルOSとAPIベースの管理がこのラボで何を意味したかを80字以上で)です。

参考

SSHで入ろうとしたらシェルがない

コントロールプレーンのノードのコンテナtalos-default-controlplane-1にdocker execでshを実行してみて、ノードIPのTCPポート22とポート50000が開いているかを確認してください。結果を次のフィールドで書いてください(書き込み先: /root/talos-lab/noshell.json)。container、node_ip(Dockerネットワークtalos-defaultのアドレス)、exec_sh_exit(docker execの終了コード、数値)、exec_sh_error(エラーメッセージ1行)、port22_open、port50000_open(ブール値)です。

ノードIPは、docker inspectのNetworkSettings.Networksにあります。ポートの確認は、bashの/dev/tcp/<ip>/<port>をtimeoutと一緒に開いてみればできます。終了コードは、コマンドの直後の$?です。set -eの下なら、|| rc=$?の形で受け取ってください。

シェルの代わりに何が動いているか

talosctlで2つのノード(コントロールプレーン10.5.0.2、ワーカー10.5.0.3)のサービス一覧を読み取り、次のフィールドを書いてください(書き込み先: /root/talos-lab/services.json)。controlplane、worker(各ノードのサービスIDをソートした配列)とcontrolplane_only(コントロールプレーンにだけあるサービスIDをソートした配列)です。

talosconfigにはデフォルトのノードが決められていないので、-nでノードを選ぶ必要があります。サービスはruntimeネームスペースのServiceリソースでもあるので、talosctl get services -o jsonで機械が読める形で取得できます。JSON出力はオブジェクトが並んで出てくるので、jq -sでまとめます。

このクラスターのメンバーは誰か

talosctl get membersでメンバーを読み取り、次のフィールドを書いてください(書き込み先: /root/talos-lab/members.json)。talos_version(10.5.0.2のサーバーのTalosタグ。例: v0.0.0の形式)、members(ホスト名 → {"type": 머신 종류, "addresses": 주소 배열}。プレースホルダーはマシンの種類とアドレスの配列です)、worker_mc_version(ワーカー10.5.0.3のMachineConfigリソースv1alpha1のmetadata.version、数値)です。

メンバーは、1つのノードに尋ねてもクラスター全体が出てきます(discovery)。サーバーのバージョンは、talosctl versionのServer側のTagです。マシン設定もリソースなのでget machineconfigで読み取れ、設定が変わるたびにversionが上がります。この値を、後のステップで比較に使います。

kubeconfigもAPIで取得する

talosctl kubeconfigでコントロールプレーン(10.5.0.2)からkubeconfigを取得して保存し(保存先: /root/talos-lab/kubeconfig。シンボリックリンクは不可)、そのファイルでkubectlを使って次のフィールドを書いてください(書き込み先: /root/talos-lab/cluster.json)。api_server(そのkubeconfigのserverアドレス)、nodes(ノード名 → InternalIP)、kubelet_version、pod_subnets、service_subnets(コントロールプレーンのマシン設定のcluster.networkの値)、overlaps_host(2つのCIDR範囲のどちらかでもホストクラスターの10.244.0.0/16か10.96.0.0/12と重なればtrue)です。

マシン設定の本文は、talosctl get mc v1alpha1 -o jsonpath='{.spec}'でYAMLとして取得でき、複数のドキュメントが---でつながっています。CIDR範囲が重なるかどうかは、Pythonのipaddressのoverlapsで調べられます。同じファイルにkubeconfigを2回取得すると、上書きせずにマージされる(名前に-1が付く)ので、1回だけ取得してください。

再起動なしでノードのラベルを付ける

ワーカーのmachine.nodeLabelsにlab.talos.dev/pool: blueを追加するstrategic mergeパッチを書き(書き込み先: /root/talos-lab/05-labels.yaml)、ワーカー(10.5.0.3)にだけ--mode=no-rebootで適用してください。適用コマンドの出力(標準出力とエラー出力)を保存し(保存先: /root/talos-lab/patch-out.txt)、次のフィールドを書いてください(書き込み先: /root/talos-lab/patch.json)。node、mc_version_before、mc_version_after(適用前後のワーカーのMachineConfig version)、container_started_at(ワーカーのコンテナtalos-default-worker-1のState.StartedAt)です。KubernetesのNodeにラベルが付いている必要があります。

マシン設定を変更する道は、API 1つだけです。talosctl patch machineconfig(略してmc)は、現在の設定を取得してパッチを合成し、送り返します。パッチファイルは@파일で渡します(プレースホルダーはファイル名です)。このクラスターの設定は複数のドキュメントなので、JSON6902(op/path)形式は拒否されます。再起動がなかったことは、コンテナの開始時刻が変わっていないかどうかで確認できます。

30秒後にひとりでに元に戻ったラベル

ワーカーにラベルlab.talos.dev/canary: "on"を--mode=try --timeout=30sで適用してください。適用直後にワーカーのMachineConfig versionを読み取り、KubernetesのNodeにラベルが付くことを確認してから、元に戻ってラベルが消えるまで待ち、もう一度versionを読み取ってください。次のフィールドを書いてください(書き込み先: /root/talos-lab/try.json)。timeout_sec(数値)、version_during、seen_on_node(ブール値)、version_after_revertです。ステップ5のpoolラベルは残っている必要があります。

tryモードは、適用したあと決められた時間内に他の設定変更がなければ、以前の設定に戻します。元に戻すこと自体も設定変更なので、versionがもう一度上がります。待っている間に別のpatchを入れると、元に戻す処理が取り消されるので注意してください。

検証が拒否したパッチ

ワーカーのmachine.nodeLabelsに、名前がlab.talos.dev/team name(空白を含む名前)で値がplatformのラベルを追加するパッチを書き(書き込み先: /root/talos-lab/07-bad.yaml)、ワーカーに適用してみてください。出力を保存し(保存先: /root/talos-lab/rejected.txt)、適用前後のワーカーのMachineConfig versionをversion_before、version_afterとして書いてください(書き込み先: /root/talos-lab/rejected.json)。

マシン設定は、ノードに書き込まれる前に検証を通ります。拒否されたら、何も変わらないはずです。--dry-runは合成した結果を見せるだけで検証はしないので、証拠になりません。オフラインでは、talosctl machineconfig patchで合成したファイルをtalosctl validate -m containerに入れると、同じエラーを見られます。

受け入れられたのにkubeletが止まった

ワーカーのmachine.kubelet.extraArgsにmax-pod: "150"を追加するパッチを書き(書き込み先: /root/talos-lab/08-kubelet.yaml)、ワーカーに適用してください。受け入れられた直後にversionを読み取り、talosctlでkubeletのサービスの状態とログを見て原因を探し、次のフィールドを書いてください(書き込み先: /root/talos-lab/kubelet-diag.json)。accepted_version(適用直後のワーカーのMachineConfig version)、service_state(そのとき見たkubeletサービスのSTATE)、error_line(原因が書かれたログ1行)です。そのあと、$patch: deleteでその引数だけを削除するパッチを書いて適用し(書き込み先: /root/talos-lab/08-fix.yaml)、kubeletが正常で、ワーカーのNodeがReadyに戻るようにしてください。

検証は設定ドキュメントの形式を見るだけで、kubeletがそのフラグを知っているかどうかまでは知りません。シェルがないので、原因はtalosctl services、talosctl service <id>のイベントとtalosctl logs <id>で探します。削除するときは、extraArgs全体ではなく、そのキー1つだけを削除します。

シェルのないノードをどう運用したか

次のフィールドを書いてください(書き込み先: /root/talos-lab/report.json)。shell_in_node(ブール値)、ssh_port_open(ブール値)、api_port(Talos APIのポート番号、数値)、worker_config_changes(ワーカーのMachineConfigが最初から変わった回数 = 現在のversion - 1)、worker_restarts(ワーカーのコンテナがステップ5以降に再起動された回数)、rejected_field(ステップ7で検証に引っかかった設定パス。例: machine.xの形式)、broken_service(ステップ8で止まったサービスID)、apply_log_line(ワーカーのtalosctl logs machinedで、設定適用のAPI呼び出しが記録された1行)、summary(イミュータブルOSとAPIベースの管理がこのラボで何を意味したかを80字以上で)です。

前のステップで残したファイルと現在のクラスターを根拠にして書きます。設定の適用はmachinedのMachineServiceに入るgRPC呼び出しで、成功した呼び出しごとにログに1行ずつ残ります。採点ツールは、数値をクラスターからもう一度数えます。