Kubernetesのアップグレードの光と闇

吉瀬淳一氏:kubeadmによるKubernetesのアップグレード、光と闇というタイトルで……。

前2つのセッションが、かなりプロダクションでのノウハウみたいな話だったんですけれど、私のセッションはkubeadmなので、普通のみなさんのご家庭にあるKubernetesで気軽に試していただけるような軽めの内容になっています。

私はこんな人ですという、いつものやつなんですけれど。Hewlett Packard Enterpriseでワールドワイドのチームでクラウドネイティブコンピューティング関連のチーフアーキテクトをやっています。2019年は日本にいなかったときのほうが多かったんですけれど、2020年の今年はコロナウイルスの影響もありまして、今のところ1週間しか海外にいなくて、あとはずっと日本にいるというそんな状況です。

「人のためにKubernetesを作る」という仕事なので、自社でKubernetesの上でサービスを運用しているとかそういうものではないです。ですからプロダクション運用でのノウハウとかそういうものが溜まりにくいといったところはあるんですけれど、そういったこともあって、今日はkubeadmで、みなさんのご家庭で試していただける内容を目指したいと思います。

ちょっとこっちのほうが有名になってしまったかもしれないというので、お騒がせしておりまして申し訳ございませんということです。

kubeadmとは

「kubeadmって何?」というお話です。kubeadmはKubernetes本家のSIGクラスタライフサイクルのプロジェクトです。目指しているところは、標準的なKubernetesクラスタのセットアップと管理をするツールです。2018年、バージョン1.13のころにGAになって、そこから先はプロダクションレディですよみたいな言い方がされています。

ベンダーとかインフラに対して依存しないのが特徴です。なので、インフラのVMとかそういうレベルのプロビジョニングだったりネットワークの構成というところはkubeadmの対象外です。kubesprayとかKRIBとかでも使われていたり、いわゆるベンダーの製品の中で使われていたり、いろいろなもののコアになる標準的なデプロイツールになっていたりもします。

これからお話しするのは、どちらかと言うとオンプレの話です。わざわざパブリッククラウドの上でkubeadmで作るという人はいないと思うので……。

Kubernetesのセットアップ手順って?

「ではセットアップ手順はどうなんですか?」と、ちょっとおさらい的な話です。マニュアルは、公式のドキュメントを見れば書いてあります。

まずはノードを準備します。OSをインストールしないといけないですよね。コンテナランタイム、DockerなりCRI-Oなりそういったものをインストールしないといけないですよね。

ノード間がザっと通信できるように、名前解決だったりとかSSHが通るようにだったりとかそういうのをやっておいて……。CNIはいろんなものが対応しているんですけどCNI用の設定を予めしておくと。

そして次の手順がkubeadmとkubeletとkubectl。これを全ノードに対してインストールしないといけないです。

このkubeletというのがポイントで、要するにkubeletの管理自体は、kubeadmはやらないということですね。Ubuntuだったらaptでインストールするかたちですね。そしてハイ・アベイラビリティ構成をサポートしていて、そのためにkube-api用のロードバランサーを外で用意しておかないといけないですよね。

ここから先は、わりと簡単というか手順通りにやっていけばよくて、1台目のコントローラノードでkubeadm initというコマンドを叩きます。ここで忘れちゃいけないのがCNIプラグインのインストール。flannelならflannelを、この1台だけ立ち上がったKubernetesの上にデプロイすると。残りのコントローラノードでkubeadmをjoinして、あとはワーカーノードでkubeadmをjoinする。

こういう感じの、わりと簡単なセットアップ手順にはなっています。ただ、このコマンドを手で叩いていくと間違えるところとかもけっこうあります。例えばCNIでflannelを使うんだったら、CIDRを予め決められた値に指定しておかないといけないというところに後から気付くと、もう1回やり直しになってしまうとかですね。

アップグレードの勘所

これでできる構成はどんなものかというと、ハイ・アベイラビリティ構成だと、普通にこういう。

control plane node、いわゆるマスターですね、マスターのところにはAPIサーバやコントローラマネージャー、スケジューラがあって、etcdはマスターのノードに同居させてもいいですし、別出しにetcdのクラスタを作ってもいいです。

これ(マスター)がkubelet上で動くというところがポイントです。これは後のアップグレードにもけっこう効いてくる話になります。

「じゃあアップグレードはどうするの?」は、マニュアルを読みなさいという話になるんですけれども。公式ドキュメントでは、アップグレードの手順がけっこう詳しく書かれています。バージョンごとに手順が微妙に違うので、今だったら17から18に上げる手順というのが一番上に出てきて、あとは過去のバージョンに関してもちゃんとマニュアルが用意されているので、正しいバージョンのドキュメントを読む。

あとはこれもポイントなんですが、最初に全部しっかり読み込んでから実行する。一個一個ドキュメントを読みながらやっていくと、途中でけっこう「あ、思ってたのと違う」って、ハマったりもします。

アップグレードの実際

「実際にやってみましょう」というので、ライブデモをしようかと思ったんですが、さすがに誰もいないところで滑るとちょっとつらいので、動画を録ってきました。

これは1台目のマスターのアップグレードです。私の自宅のクラスタです。マスターが3ノード、ワーカーが4ノード、そのうち1ノードはGPUが載っているみたいなやつですね。

ネームスペースはいろいろ切られてますけれども、これは私がいつも使っている検証用のセットみたいなのがあって、RookとMetalLBとIstioは、だいたいいつも入れているのでこれが動いているという状態です。例えばRookとかだけでも、これだけいろんなものが動いている状態ですね。これでIstioとかもいろんなものが動いていたりとかもします。CRDとかもけっこういろいろなものがあって……。

ここでapt updateして……。kubeadmのバージョンも今何があるか、1.18が出ちゃっているんですけれども、ここでは1.16.4から1.17.4に上げるということをやっていきます。

ここは何をしているかというと、まずkubeadmをインストールしています。kubeadmの1.17.4というバージョンを普通にaptで取ってくるということをしています。このあとkubeadmのバージョンを確認して、アップグレードする前に、いったんドレインする。これはマスターなのでとくに何も動いていないんですけれども、いったんノードをドレインしてCoreDNS、podとかが動いているので、これがevacuationするというかたちです。

そしてここでkubeadm upgrade planとすると、アップグレード可能なバージョンがここにズラッと出てきます。今が1.16.4なので1.17.4までは上げれますよ。etcdはこのバージョンまで上げれますよみたいなそういうものが出てくると。

ここでkubeadm upgrade applyと叩くと、17.4に上げると。「いいですか」と。「いいですよ」と。

わりとサクッと進んじゃうんですね。このスケジューラ、APIサーバ、コントローラマネージャーは、kubeletの上で動いているものです。これは何をしているかと言うと、podが新しいマニフェストで上がってくるということです。Kubernetesのコアのコンポーネント自体がKubernetesのリソースとしてアップグレードするというかたちです。

この状態でAPIサーバとかスケジューラとかそういったものはもう新しいバージョンになっているんですけれども、ここで、これですね。kubelet get nodeとやってもまだバージョンが、今m1というのを上げたんですけど1.16.4のままです。これはなぜかというと、kubeletをまだ上げていないからですね。ここで出てくるバージョンというのはkubeletのバージョンなのでkubeletとしてはまだ1.16.4という状態になっています。

このあとはまた2台目とか同じようにkubeadmを上げて……。こっちはkubeadm upgrade nodeというコマンドを使います。このときにこの2台目のコントローラの上で動いているものもpodとしては入れ替わるというかたちですね。

コントローラ3台のアップグレードが終わったら今度はkubeletのアップグレードです。先ほど「kubeletがまだ上がっていない」と言ったんですけど、kubeletを上げていく。これは普通にまだ16.4のままになっているんですけど、ここでkubeletとkubectlもついでに17.4というバージョンを普通にaptでインストールすると。

これをやってから、kubeletだけはsystemdで上がっているので、systemctlでkubeletを再起動する。そうすると、この状態でkubectl get nodeとやると、kubeletのバージョンが上がっているので1.17.4になっています。こんな感じです。

ワーカーノードもコントローラの2台目、3台目と同じで、単純にkubeadmをインストールして、それからupgrade nodeというコマンドを叩いて、それからkubeletを再起動する、そういう流れになります。こちらの動画は共有してあるので、後ほど見てもらえればと思います。

この今の動画はコマンドを手で叩いてますけど、けっこういろんなコマンドを叩きます。kubeadmは基本的にrootで叩くものなんですが、「kubectlは普通rootで叩かないでしょ」だったりとか、そういったいろいろミスをしがちなところがけっこうあるので、やっぱりコード化してから実行したほうがいいです。

ちょっとこのあとに失敗した話とかも出てくるんですけれども、そういう失敗を避けるためにもAnsibleとかでコード化してから実行したほうが絶対にいいです。

アップデートでのやらかし

失敗は、たくさんやっています。私、たぶんkubeadmでクラスタを動かし始めたのは、1.8とかそのくらいのときからで、ずっと秘伝のたれを継ぎ足しで使っているので、何度も失敗をしています。

よくあるパターンが、先にaptでkubeadmの新しいバージョンを持ってくるときにkubeletのバージョンをその時点で上げちゃうとか。kubeletのaptだったら、apt-mark holdをして、それからunholdしてアップデートしてとやりますけど、そのholdが外れた状態で放置してしまって普通にapt upgradeのときにkubeletだけが簡単に上がっちゃってとか、そういうことが起こりえます。

kubelet上ではkube-apiserverとかが動いているので、古いkube-apiserverが新しいkubelet上で動いていると。なので、自己矛盾が起きて何もできなくなってしまうみたいな状態になるんですね。先ほどの手順で言うとkubeadm upgradeというコマンドを叩いたときに新しいkubelet用の設定だったりとか、kube-api自体が新しいものになったりするので、そういうことです。

こうした矛盾が起きてしまうと潰して作り直し。先ほどのゼットラボさんの発表にもあったんですけど、kubeadmが必ずしもkubeletよりも新しくないといけないということがあるので、ここはありがちなミスです。

ワークロードが対応していない

あと実際にあったのは、ワークロードが対応していないことですね。これはマニュアルをちゃんと読んでおけという話なんですけれども……。

例えばKubeflow。そもそも今回これをやろうとした理由は、Kubeflowが1.15のクラスタの上で動いていて、今度1.18が出てくる段階で1.15のサポートは外れちゃうじゃないですか。そうすると少なくともクラスタを1.16に上げておきたいよねとなったときに、Kubeflowをよくよく見ると1.16をサポートしていないんですよ。

これはサポートしていないだけでなく普通に動かないんです。それに気付かずにクラスタを上げてしまったと。Kubeflowが死にましたと。これは「ちゃんと調べておけ」という話ですね。

certificateのexpire

それからcertificateのexpire。kubeadmはKubernetesの各コンポーネントの通信に使うcertificateというところもkubeadmがばら撒くような仕組みになっています。デフォルトだとこのcertificateの有効期限が1年です。1年というのはKubernetesのマイナーバージョンがちょうど4つなので、偶然去年の同じ日に同じ作業をしていたということが起こりえます。

私が実際に踏んだのはコントローラのアップグレードのときです。コントローラ1台目のアップグレードが無事に終わりました。2台目、3台目は明日やりましょうと。翌日2台目と3台目をやってみようとしたら、なんか知らないけどAPIと通信ができない。手順を間違えたのかとかいろいろ見ていって、わけがわからなくなってクラスタが壊れたあとで「あ、certificateが切れてるじゃん」ということに気付いてしまったと。そういうことが起こりえます。

kubeadmでも、kubeadm alpha certs check-expirationというcertificateの賞味期限を確認できるコマンドが用意されています。なので予めチェックしておくことと、普通に長く使うKubernetesだったらcertificateも自動更新するように設定しておけという話です。

ちゃんと自動更新をするようなやり方もドキュメントに書いてありますので、これも長く使うんだったらやっておいたほうがいいと思います。

ステートフルなpodの謎の死

最後に書いてあるステートフルなpodが謎の死を遂げる。これはちょっとオンプレのKubernetesなので、外部のストレージサービスとかの利用をあまり想定していなくて、ストレージサービスなんかもKubernetesの上で動かしたりとか、私の場合はRookをよく使っている感じです。Rook-CephのOSDが、なぜか知らないけど死んでしまった。

これはつい最近起きた話で原因究明する余裕がなくて、このときはRook自体を再デプロイしてしまったという感じですね。

稼働中クラスタのアップグレード/h2>

実際に動いているKubernetesの上で動いているワークロードは、podがいろいろ動いていたりとか、サービスとかのそういうリソースが動いているんですけれども、podと一口に言ってもデーモンセットで動いているpodと、デプロイメントが生成するpodと、直接マニフェストがデプロイしているpod。

例えばRookとかで言うとそのRookのインストーラーというか、標準的なyamlのセットがあって、普通はそれでインストールをするんですが、その中でpodが素で書かれているものだったりとかデプロイメントして書かれているものとかであったりとか、デーモンセットとして書かれているものだったりがあります。

中にはローカルのディスクを使うものもあります。RookのCephの場合は、普通に入れるとローカルのディスクをOSDのディスクとして使うようになっています。こういったものが動いている状態でクラスタをアップグレードするということは、普通の、先ほどあったようなローリングアップグレードみたいな、1ノードを追加して新しいバージョンにして、動いていたのを外してみたいなことをやろうとしても、なかなかうまくいかないことがあったりするんですね。

例えば先ほどkubeadm upgradeというコマンドを叩く前に、kubectl drain nodeと、やりますよね。ノードがUnscheduledの状態になるんですけれども、ローカルのディスクを使っているpodに関しては待避されないです。そういうところがある。そのまま動かしても別に動くんですが、そういったことがあったりしていろいろ注意しないといけないところがあると……。

アップグレードのトラブルシューティングガイド

それで上がってこないようだったり通信できないようだったりとか、そういうFAQみたいなのがどんどん上がってきています。トラブルシューティングガイドのURLをここに貼っておきますが、ここもだいぶ充実してきました。裏を返せばそれだけトラブルを起こしている人が世の中にはけっこういっぱいいることになるんですね。

これを言ってしまうと身も蓋もないんですけど、壊れることを前提にしたほうがいいかもしれない。なぜならふだん遭遇するようなちょっとしたトラブル、先ほどの例で言うとcertificateが切れていたみたいなことがアップグレード中に起きてしまうと、prodだったらcertificateを取り直して入れ直してとやればいいんですけど、これをアップグレード中に起きると何がなんだかわからなくなります。

なので、トラブルシューティングもなかなか難しくなる。ワークロードが増えれば増えるほどバージョンアップの影響を事前にすべて調査するのは困難。

自分の作ったアプリケーションならまだいいです。でもいわゆるOSSのエコシステムみたいなものを自分のクラスタの上に適用している。 例えばRookであったりとかIstioといった、そういったカスタムコントロールの類だったりとか、あとはCRDがバージョンアップの影響をどれだけ受けるかというところを事前に網羅するというのがだんだん難しくなってくるというところがあります。

そしてワーカーノードについてですが、先ほどの動画の例でいうと普通に動いているノードにkubeadm upgradeというコマンドを叩いてアップグレードしましたけど、マスターが一通りアップグレードし終わったあとは普通にkubeadm joinで新しいノードを新しいバージョンで追加できるので、新規のノードを追加して新規のノードがある状態で、ワークロードを新しいところに移動して、大丈夫だとなってから古いノードを削除することをやったほうがいいのかもしれないです。

これはリソースに余裕がある場合で、なかなかオンプレだとその分のリソースを用意していくことも事前に考えていかないといけないなということになります。

クラスタの構成のバックアップ

kubeadmのアップグレードのコマンドで、一応クラスタの構成はバックアップされますとなっています。何をバックアップしているかというとetcdのバックアップ。それから、kubeletの上でマスターのコンポーネントが動きますと言いましたけれども、そのkubeletの上で動くマスターのマニフェストですね。APIサーバとコントローラマネージャーとスケジューラ、そしてetcdです。

一応これ(バックアップ)があるので、マスターが3台構成であれば、1個ミスしてもなんとかロールバックはできるかなというのはあるんですが、腕に覚えがある人じゃないと、なかなかetcdをスナップショットから戻すとかをやりたくないと思うので、一応ありますというのだけですね。

コントローラは冗長化。ワークロードはいつでも再デプロイできるように

壊さないというのと、リカバリーできることが支えにならないと、なかなかアップグレードには踏み切れないと思うんですけれども、そのために何をすればいいか。

コントロールプレーンの冗長化は必須ですと言い切ってしまいます。マスターが1台しかないと復旧はかなり辛いです。マスターが3台構成だったら、1台死んでも、最悪その1台死んだやつをクラスタから外してきれいにして、そのOSの状態からkubeadm joinとすると、普通にjoinできます。なのでこれは必須かなと。

それからワークロードをいつでも再デプロイできるようにしておく。これは要するにGitOpsという話なんですが、GitOpsが何にでも使えるのかというとそんなことはなくて、例えばRookだったりIstioだったりをGitOpsの対象にするのは、できるけれども大変だと思うんです。それぞれのデプロイの仕方というのが用意されていたりするので……。

あとは動的に生成されるリソースですね。マニフェストに書いてあるpodとかならいいんですが、デプロイメントがマニフェストに書いてあってそこから間接的に生成されるpodとかそういうものもあって、それを障害が起きる前の状態にサクッと戻す、再現させるということが必要な場合と必要でない場合があると思います。

それから証明書とかですね。cert-managerが取ってくるやつだったりとか、そういったものはGitOpsの対象にできないことになっているので、これをどうするか。そしてPersistent Volumeをどうやって救うかというところもあります。

Veleroに期待

先ほど、もうWantedlyさんで使っているというVeleroですね。こういういいものがあるらしい。実は私は使ったことがないんですけど、見た感じは良さそうなので試してみたいなと思っています。これはもともとHeptio Arkというやつですね。今はVMware Tanzuのファミリーになっていると。

これは何をするかと言うと、クラスタのすべてのリソースの定義と状態をJSONファイルとしてオブジェクトストレージに保存してくれるというやつですね。「すべてのリソース」というところが本当にすべてなのかというところが私にはよくわからないです。

それからPersistent Volumeのバックアップもしてくれるということで、Wantedlyさんではクラスタマイグレーションに使っていたというところなんですけれども、そういう意味ではクラスタの2面運用ができそうなツールで、ちょっとこれは試してみたいなと思っています。他に良さげなものがあれば教えてください。

クラスタアップグレードは「うまくいけば」簡単

時間もいい感じなのでまとめです。kubeadmでのクラスタアップグレードは「うまくいけば」簡単です。うまくいかなかったときのトラブルシューティングはけっこうつらいです。これはkubeadmに限った話ではないと思うんですけど……。なので予防策とリカバリ策の両方を準備しておいてから望みたいと。

私が使っているのはどちらかと言うと自分用の検証環境なので、壊れたら壊れたで「直せばいいか」という感じなんですけど、プロダクションで使うんだったら、きっちり予防策とリカバリ策を準備しておきたいと思います。あとはいつものWe are hiring!……これはGAFAのほうじゃないです。HPEです。

(会場笑)

ということで以上になります。ありがとうございます。

(会場拍手)