Kubernetesディストリビューション — 自分で立てる
stable チャネルに従っていたらマイナーを二段飛ばしていた
このラボは本物のk3sで動きます
VMの中にk3s v1.34.11+k3s1が1台起動しています。このクラスターを1.35へ1つ上げて、上げる前の準備と、上げた後の照合を実際にやってみます。最初の起動には2分前後かかります。作業ディレクトリは/root/upgradeです。
目標
k3sをマイナーバージョン1つ分アップグレードしながら、出発バージョンの記録、非推奨APIの点検、バックアップ、drainの判断を順に行い、アップグレード後に同じノードと同じワークロードが生きているかをUIDで証明します。最後に、次の1つをsystem-upgrade-controllerのPlanとして書きます。
なぜ重要なのか
インストールスクリプトを再実行すればアップグレードが終わるので、バージョンを選ぶことが軽く見られがちです。ところが、チャネル(stable)は、現在のクラスターがどのバージョンかを知らず、最新の推奨バージョンに向かいます。1.34からstableに上げると、stableが1.36の日にはマイナーバージョンを2つ飛ばしますが、Kubernetesのスキューポリシーは、インスタンスが1つだけのクラスターでもこれを許可しません。飛ばすことを防いでくれるツールはないので、人がバージョンを固定して1つずつ上げる必要があります。
そして、「上がった」ことは証拠になりません。上げる前にバージョンとUIDを書き留めておかなければ、上げた後に、同じクラスターが同じワークロードを持って上がったかを確認する方法がありません。
ステップ
- APIサーバーのバージョン、kubeletのバージョン、ノード名、ノードのUIDを保存してください(保存先:
/root/upgrade/before.json)。 upgネームスペースにDeploymentweb(nginx 1.27-alpine、レプリカ2)とPDBweb(minAvailable 1)を作成し、UIDを書いてください(書き込み先:/root/upgrade/workload.json)。apiserver_requested_deprecated_apisメトリクスを書き写し(書き込み先:/root/upgrade/deprecated.txt)、removed_by_target=を書いてください。- SQLiteのDBとトークンをバックアップし(バックアップ先:
/root/upgrade/backup/)、ファイルを書いてください(書き込み先:/root/upgrade/backup.json)。 - drainを試みて出力を残し(保存先:
/root/upgrade/drain.txt)、uncordonしてからdecision=の行を追加してください。 v1.35.8+k3s1にアップグレードして、ファイルを書いてください(書き込み先:/root/upgrade/upgrade.json)。- アップグレード後の状態とスキューの許容範囲を書いてください(書き込み先:
/root/upgrade/after.json)。 - Plan CRDを入れ、次の1つのバージョンに固定したPlan
k3s-server-nextを適用してください(ファイル:/root/upgrade/plan.yaml)。 - 5つの行と説明を書いてください(書き込み先:
/root/upgrade/report.md)。
参考
- このVMのk3sの設定は
/etc/rancher/k3s/config.yamlにあります(disable: traefik)。インストール時に環境変数で渡した値は、再実行するときにもう一度渡さないと消えますが、設定ファイルは残ります。 - イメージは
public.ecr.aws/docker/library/...を使います。Docker Hubは取得制限にかかることがあります。 - よくあるミス1: アップグレードを先にして、before.jsonを後で書くこと。採点ツールは、記録がアップグレードより先に書かれたかどうかを確認します。
- よくあるミス2: drainが失敗した後、uncordonを忘れること。代わりのPodがPendingのままになり、後のステップのReady確認がすべて失敗します。
- system-upgrade-controllerのコントローラーと
rancher/k3s-upgradeイメージはDocker Hubにあるため、この環境では実行しません。Planは保存されるだけで、実際のアップグレードは起きません。
出発バージョンを先に書く
APIサーバーのバージョン、kubeletのバージョン、ノード名、ノードのUIDを、server_version、kubelet_version、node_name、node_uidのキーで保存してください(保存先: /root/upgrade/before.json)。
アップグレードの後は、以前のバージョンをどこからも見直せません。kubectl version -o jsonのserverVersionと、ノードオブジェクトのstatus.nodeInfo、metadata.uidを見てください。UIDは、後のステップで「同じノードが上がったか」を照合する値です。
PDBが設定されたワークロードを起動する
upgネームスペースにDeploymentweb(イメージはpublic.ecr.aws/docker/library/nginx:1.27-alpine、レプリカ2)と、app=webを選ぶPodDisruptionBudgetweb(minAvailable 1)を作成し、DeploymentのUIDをdeployment_uid、pdb_min_availableのキーで保存してください(保存先: /root/upgrade/workload.json)。
PDBは、「自発的な中断(eviction)のときに、最低いくつは残してほしい」という約束です。kubectl create deploymentとkubectl create pdbで作成でき、2つのPodがReadyになってからUIDを書いてください。Docker Hubのイメージは使いません。
誰がまだ古いAPIを呼んでいるのか
APIサーバーのメトリクスからapiserver_requested_deprecated_apisの行をそのまま書き写し(書き込み先: /root/upgrade/deprecated.txt)、そのうち目標バージョン1.35までに削除される(removed_releaseが1.35以下の)ものの個数を、removed_by_target=<수>の行として書いてください(プレースホルダーは個数です)。
kubectl get --raw /metricsで、APIサーバーのメトリクスを見られます。ラベルのうちremoved_releaseが空なら、非推奨になっただけで削除の予定はないという意味です。このクラスターは、何もしなくても1行が出ます。
DBとトークンを一緒にバックアップする
SQLiteのデータストアをバックアップし(バックアップ先: /root/upgrade/backup/state.db)、サーバートークンもバックアップして(バックアップ先: /root/upgrade/backup/token)、バックアップしたバージョンとトークンのハッシュをk3s_version、token_sha256のキーで書いてください(書き込み先: /root/upgrade/backup.json)。
k3sのSQLiteは、/var/lib/rancher/k3s/server/db/にあります。使用中のDBは、ファイルのコピーよりも、sqlite3のオンラインバックアップのコマンドのほうが安全です。トークンがなぜ必要かは、理論の記事のバックアップの節をもう一度見てください。
drainが終わらない
ノードをkubectl drain --ignore-daemonsets --delete-emptydir-data --timeout=40sで空にしてみて、出力の全体を保存し(保存先: /root/upgrade/drain.txt)、そのあとノードをuncordonしてください。最後の行に、決定をdecision=upgrade-without-drainまたはdecision=add-node-firstとして書きます。
drainは、cordonしたあとeviction APIでPodを追い出し、evictionはPDBを守ります。ノードが1台なら、追い出されたPodの代わりのPodがどこへ行くべきかを考えてみてください。drainが失敗で終わっても、出力はファイルに残す必要があります。
マイナーバージョンを1つだけ上げる
インストールスクリプトを目標バージョンのタグから取得し、INSTALL_K3S_VERSION=v1.35.8+k3s1でアップグレードして、結果をfrom、to、node_uidのキーで書いてください(書き込み先: /root/upgrade/upgrade.json)。
チャネルの代わりにバージョンを固定します。インストールスクリプトは、raw.githubusercontent.comのk3s-io/k3sリポジトリから、そのバージョンのタグのinstall.shを取得します。このVMの設定は/etc/rancher/k3s/config.yamlにあるので、もう一度渡す必要はありません。終わったらAPIが応答するまで待ってから、値を書いてください。
同じものが上がったかを照合する
アップグレード後の状態を、server_version、deployment_uid、web_ready、web_restarts(webのPodの再起動の合計)、traefik_deployed、kubelet_allowed、kubectl_allowedのキーで書いてください(書き込み先: /root/upgrade/after.json)。最後の2つは、現在のAPIサーバーのバージョンに対してスキューポリシーが許容するマイナーバージョンの一覧("1.xx"の文字列)です。
ワークロードはUIDで、設定はtraefikが復活したかどうかで照合します。スキューポリシーで、kubeletはAPIサーバーよりいくつ低いバージョンまで、kubectlは上下いくつまで許されるかは、理論の記事の表を見てください。
次の1つをPlanとして書く
system-upgrade-controller v0.20.1のcrd.yamlを適用し、system-upgradeネームスペースにPlank3s-server-nextを作成して適用してください(ファイル: /root/upgrade/plan.yaml)。現在のバージョンのすぐ次のマイナーバージョンのk3sをversionで固定し、サーバーノードだけを選び、1台ずつcordonして上げるようにします。
CRDは、GitHubリリースの固定バージョンのアドレスから取得します。コントローラーは起動しないので、Planは実行されず、保存されるだけです。CRDはversionもchannelもないPlanをそのまま受け入れるので、適用が成功したからといって正しいPlanであるわけではありません。k3s公式ドキュメントのサーバーPlanの例で、channelを何に置き換えるべきかを考えてください。
stableに従っていたらいくつ飛ばしていたか
initial=、current=、next_plan=、stable_channel=(現在k3sのstableチャネルが指しているバージョン)、stable_minor_jump=(出発バージョンからそのバージョンまでのマイナーバージョンの差)の5行と、なぜチャネルの代わりにバージョンを固定したかの説明を150字以上書いてください(書き込み先: /root/upgrade/report.md)。
チャネルが指すバージョンは、JSONで取得できます(https://update.k3s.io/v1-release/channels)。マイナーバージョンの差は、マイナーの数字の差です。説明には、スキューポリシーと、何がその飛ばしを防いでくれなかったかを入れてください。