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

Kubespray と Terraform でクラスターを構築する

1 段ずつ、インベントリから — Kubespray アップグレードの決まり

TT Labで続きを見る

一言でいうと

kubesprayのアップグレードは、インベントリのバージョンを1つ上げてupgrade-cluster.ymlを実行することであり、ノードごとにcordon → drain → 更新 → uncordonを行うので、ノード1台のクラスターでは、その間がそのまま中断時間になります。

なぜ必要なのか

Kubernetesプロジェクトは、直近の3つのマイナーバージョンにだけパッチを提供します。そのため、1年に2、3回は上げる必要があり、上げずに耐えていると、一度に複数のバージョンを飛ばすことになりますが、それは許可されていません。Kubernetesのバージョンスキューポリシー(Version Skew Policy)は、kube-apiserverは一度にマイナーバージョンを1つずつしか上げられないと定め、kubeadmのアップグレードのドキュメントも、マイナーバージョンを飛ばさないよう求めています。kubesprayはここに、独自のルールをもう1つ加えます。kubesprayのタグも1つずつ上げるというルールです。タグごとに、ロールのデフォルト値とダウンロードするファイルの一覧が変わるため、古いタグから最新のタグへ一気に飛ぶと、何が壊れるかを誰もテストしていません。

どう動くのか

バージョンはインベントリで上げます。アップグレードのドキュメントの要点は、こうです。インベントリにkube_versionを書いてあるなら、upgrade-cluster.ymlの前にその値を直すか、-e kube_version=...で新しいバージョンを渡すこと、そうしないと「インベントリに書かれた同じバージョンのまま留まる」ということです。2つの方法のうち、ファイルを直すほうがよいです。-eだけで上げると、クラスターは新しいバージョンなのにインベントリは古いバージョンのままで、次に誰かがそのインベントリでcluster.ymlを実行した瞬間に、食い違った状態から始まります。

upgrade-cluster.ymlは順序を守ります。このプレイブックは、すでにあるクラスターにだけ使い、コントロールプレーンとetcdを先に、ワーカーを後で上げます。ノードごとに、pre-upgradeロールがcordonとdrainを行い、kubeadm upgradeで静的Podのマニフェストを新しいバージョンに変え、kubeletを上げた後、post-upgradeロールがuncordonします。一度に上げるノード数はserial(デフォルトは20%)で決め、ドキュメントは、upgrade_node_confirmとupgrade_node_pause_secondsで、ノードごとに止まって確認する方法も教えています。ノードを分けて上げるには、まずfacts.ymlを制限なしで実行してから、--limit "kube_control_plane:etcd"でコントロールプレーンから上げるよう求めています。

すべてが一緒に上がるわけではありません。kubesprayは、etcd・CoreDNS・pauseイメージのバージョンを、Kubernetesのマイナーバージョンごとの表(etcd_supported_versions、coredns_supported_versionsなど)で選びます。2つのマイナーバージョンが表で同じ値を指していれば、そのコンポーネントはそのまま残ります。今回の実測がそうでした。1.35.8も1.36.4もetcd 3.6.14で、逆にCoreDNSは、表が指すバージョンが異なり、1.12.4から1.14.2に変わりました。「Kubernetesを上げたのだから、etcdも上がっただろう」は、確認するまでは仮定にすぎません。

次の1つは、次のタグから来ます。タグが受け入れる最も高いバージョンは、チェックサム一覧の最初のキーで、v2.32.0では1.36.4です。このバージョンまで来たら、このkubesprayでは、これ以上は上げられません。ドキュメントの「Multiple upgrades」の節のように、kubesprayを次のタグに上げて(そのタグのrequirements.txtでAnsibleもインストールし直して)、upgrade-cluster.ymlを実行するのが、次の1つです。

現場での姿

このコースを作りながら、medium VMで1.35.8 → 1.36.4を実測しました。upgrade-cluster.yml全体が368秒で、そのうち、kubeadmが最初のコントロールプレーンを上げるタスクが94秒、drainが16秒でした。メモリは最大1.56GiBまで上がり、インストール時(最大1.36GiB)より少し高くなりましたが、4GiBの中では余裕がありました。ノードのUIDはそのままで、drainされたwebのPodは、uncordonの後に新しいUIDで再び起動しました。つまり、1台構成のクラスターでdrainは、「移す」ではなく、「止めて再び起動する」ことです。

現場でよく見る事故が2つあります。1つ目、PodDisruptionBudgetがminAvailableですべてのレプリカを縛っていると、drainが終わりません。pre-upgradeロールのデフォルトはdrain_timeout: 360sで、3回(drain_retries: 3)試したあと失敗し、upgrade_node_uncordon_after_drain_failure: trueなので、ノードを再びuncordonしたままプレイブックを止めます。ノードは古いバージョンのままです。2つ目、drainは通過したのに、その後のステップで止まると、ノードがcordonされたまま残ります。再実行せずに帰宅すると、翌日にスケジュールが止められたノードが見つかります。そのため、アップグレード後のチェックリストの一番上に、「unschedulableのノードがないか」があります。

次のラボですること

1.35.8で構築してワークロードを起動してから、上げる前の「写真」を撮ります。インベントリのバージョンを1.36.4に変更してupgrade-cluster.ymlを実行したあと、同じ「写真」をもう一度撮って、ノード・ワークロード・etcd・CoreDNSがそれぞれどうなったかを比べます。ログでcordon・drain・uncordonを読み取り、このkubesprayで次の1つが可能かを判断します。