発表概要

中村泰大氏(以下、中村):「Yahoo! JAPANのIaaSを支えるKubernetesクラスタのアップデート苦労話」というタイトルで、ヤフー株式会社の中村と高橋が発表します。よろしくお願いします。

(スライドを示し)こちらが発表の概要です。Yahoo! JAPANのIaaSでは内部でKubernetesを活用しており、そのアップデート作業を行ってきました。

本発表では、最初にIaaSを支えるKubernetesについて簡単に説明したあと、これまで行ってきたKubernetesのアップデートとHelmのアップデートについて紹介します。発表をとおして、大規模Kubernetesクラスタ群のアップデートにおける工夫や失敗、また得られた知見を紹介します。

ここで、スピーカーを紹介させてください。本日は2名で発表します。発表の前半は中村が、IaaSを支えるKubernetesの概要と、Kubernetesのアップデートについて紹介します。後半は高橋が、Helmのアップデートを紹介します。

Yahoo! JAPANのIaaSを支えるKubernetes

それではさっそく、Yahoo! JAPANのIaaSを支えるKubernetesが、どのようなものか紹介します。ヤフーではOpenStackを用いたIaaSを社内で展開しており、メディアや金融、コマースといったヤフーのユーザーに直接届くサービスから、PaaSやCaaSといった、IaaSよりも抽象度が高い社内クラウドのバックエンドに使われており、幅広く用いられています。

これらを支えているOpenStackの規模としては、クラスタ数が200以上、ハイパーバイザーがおよそ21,000、VM数が17万以上と、非常に数が多い環境となっています。ヤフーではこれらはすべてオンプレで構築し、運用しています。

OpenStackクラスタが200以上ともなると、管理するコンポーネントの数も膨大になります。そこで登場するのが、KubernetesとHelmです。

Nova APIやKeystoneといった、OpenStackのコントロールプレーンに当たるコンポーネントをHelmのChartにしKubernetesにデプロイすることで、OpenStackクラスタの構築や運用のコストを削減しています。

このIaaSを支えるKubernetesクラスタは現在10ほどあり、それらの配下にあるノードは400を超える規模となっています。これらはOpenStackをのせるための専用Kubernetesクラスタとして、オンプレで構築しています。

IaaSの縁の下の力持ちとして使われているKubernetesですが、数年前まではアップデートをせずに運用してきました。そうなっていた主な要因としては、オンプレで、かつクラスタ数が膨大なため、アップデート作業に非常に手間がかかることと、kubeadmといったOSSのデプロイツールを使っていないこと、導入期で安定稼働が求められていたことなどがあります。これらの理由もあり、Kubernetesの1.8、Helm 2で長期運用されてきました。

しかし、チーム内でKubernetesの利用が広まり成熟期に達したこともあり、より新しいバージョンのKubernetesに追従するモチベーションが生まれ、アップデートを進めていくことになりました。

Yahoo! JAPANにおけるKubernetesのアップデート方法

ということで、Kubernetesのアップデートについて話を移したいと思います。Kubernetesのアップデートは、基本的にはマスターノードで稼働しているkube-apiserverや、ワーカーノードにあるkubeletといった各種パッケージのバージョンを上げたり、バージョンに合わせた設定ファイルの書き換えが必要となります。私たちは、これらの作業をChefを使って行っています。

Chefは、Rubyを使ってサーバーの構成情報を記述・管理できる構成管理ツールで、すべてのノードの構成をChefを使って管理しています。アップデートの際には、まず事前準備として、各種パッケージをノードから参照できる場所に準備し、Chefに記述した構成情報を新しいバージョンに合わせて書き換えます。

そしてマスターノードから順に、対象のノードをdrainしてクラスタから切り離し、Chefを適用して新しいバージョンの構成を適用、uncordonして、ノードを再投入することを繰り返して、クラスタのバージョンを更新します。

最も単純にアップデートを実施する場合、手元でkubectlを使ったり、ノードにSSHでChefを実行するように手動で行うことになります。

アップデートを実際にどのように進めていったのかという変遷が、スライドのようになっています。Kubernetes 1.8からスタートして、現在は1.15までアップデートしました。(登壇当時)残念ながら、現時点ではサポート対象のバージョンまでアップデートしきれておらず、今後も継続して作業を進める予定です。

アップデート手法も、最初は手動でChefを呼び出すところから始め、回を重ねるごとに作業自体もアップデートしてきました。

全自動化に向けての取り組み

ここからは、作業方法の推移に合わせて自作監視ツールを作ったところから、全自動化に向けてどのように取り組みをしてきたのかを、このスライドに書いたようなそれぞれのトピックに焦点を当てて、紹介したいと思います。

初めは手動で作業を行ってきたわけですが、その結果、最初に行った改善がPodの状態を可視化するCLIを作成することです。ただ構成の更新を進めるだけだと、作業中にPodが異常な状態になったりして、Kubernetesクラスタが不健全な状態にあることを認知できず、対処できなくなってしまいます。

そのため、Podの状態を手軽に監視できるようにクラスタ内のPodを自動収集し、定期的にpingを送信して、その状態を可視化するCLIを作成しました。これにより、Podのクラッシュループや、Podへの通信到達性をすぐに認識できるようになりました。

監視が強化されて次に出てきた問題が、作業対象が多い点です。クラスタが分かれているとはいえ、400を超えるノードがあると、いちいちコマンドを手動で実行するのはやはり手間がかかり、自動化したいということになりました。

完全自動化するにあたり、チームでどのような要求があるのかを議論し、挙げられたのが、アップデートをするノードの順番を制御したい、そして健全性の確認が自動でできるようにしたい。最後に、チャレンジとしてSSHなしで実行できるようにしたい、という3点があげられました。これらを満たすために、KubernetesのJobを活用したタスクランナーを自作することにしました。

そのツールを実際の作業で利用してみたのですが、とある問題からツール自体がお蔵入りになってしまいました。

このツールでは、まず実行したいJobやPythonの関数、デプロイの順番などを記述します。それをもとに、ツールがKubernetes APIを使ってJobを自動で実行します。JobでChefを実行する際には、ノードのルートディレクトリをPodにマウントして、chroot(change root)し、Chefを実行する流れになります。

この方法で、一見うまくいくように思えたのですが、実際には、Chefで行っていたノード上の一部のプロセスのリスタートをJobから行うことができず、プロセスが終了したままになってしまい、必要な処理を完遂できませんした。

このプロセスの重要性が低ければ問題はなかったのですが、ノードの監視を行うプロセスだったため、見逃せず結局手動でのオペレーションが必要になってしまいました。

この問題はコンテナの利用方法の王道から外れるような使い方をしてしまったことに由来していて、解決する見込みが立てられなかったため、自作ツールの利用は断念して、もう少し素朴な方法でやり直すことにしました。

そこで、自作ツールの代替としてFabricを使うことにしました。FabricはコマンドをSSH経由でリモート実行できるタスクランナーで、そのほかにもローカルのコマンド実行や、Pythonの関数をタスクとして実行できます。Fabricは、チーム内でよく用いているツールのため、採用しました。

各ノードで行う作業を、Fabricのタスクとして定義し、対象ノードに順に適用していきます。初めは監視は入れず、以前説明したPodの状態を目視するツールを使って行いました。

この方法でKubernetesのアップデート自体はうまくいったのですが、OpenStack上でVMの新規作成ができない状態になってしまい、問い合わせがあるまでこの問題に気づけませんでした。

これは自作のタスクツールの時に起こっていたような問題とは違い、Pod内で動かしていたプロセスの挙動に起因するものです。具体的には、メッセージキューのサーバーとなっていたPodがdrainされて移動すると、クライアントとして接続していた他のコンポーネントからメッセージキューへの再接続が、そのコンポーネントのバグにより行われないというものでした。

再接続のされないままPodが維持されてしまうような状態だったため、Podの状態を目視するだけでは発見できないような問題でした。

この問題に対しては、もちろんPodのProbeを改善した上で、さらにVMが作成できるのかのE2Eテストを導入することで改善をしました。Podの移動があった際に発生する問題だったため、drainのあとにVMが作成できるかのE2Eテストを呼び出し、その結果をSlackに通知することで、異常に気づけるようにしました。

今できているのは半自動と呼べるようなレベルのものですが、今後のアップデートに向けて全自動化すべく作業を進めています。

現時点で半自動となってしまっているのは、時間がかかるにもかかわらず、放置できずに、作業中にはずっと人間が張りついてないといけないからです。というのも、Podの状態やpingの監視は、依然として自作CLIを使った目視ですし、作業中に問題が発生しても、自動中断されない状態になっています。

そのため、今後これらを解決するためにPodの状態を定期的に、定常的に監視し、それらの状態やVMの作成テストの結果から、必要に応じて自動中断と通知する仕組みの導入を進めています。

作業中の異常を自動で検知、通知することで拘束時間を減らし、全自動とも言える作業の実現を目指しています。

改善の繰り返しで重要だったこと

ここまでの改善の繰り返しで、重要だとわかったことをまとめます。まずは、膨大な作業は自動化するということです。自動化できたほうが楽ですし、定期的に行う作業なら、実施のコストをぐんと下げられます。

そして、自動化した仕組みは、Step by Stepで組み上げられるとよさそうだということです。途中で話題にした、SSHなしで作業できるタスクランナーを自作するような、大きすぎる構想を描いてしまうと、前提に問題があった時に、思わぬ転び方をしてしまいます。

それよりも作業ごとに改善点を洗い出して、その問題を埋めていけば、必要なものが自然とできあがっていくことがわかりました。

そして、作業中に問題が発生していないかというのを気づける仕組みを構築することも重要だとわかりました。

異常というものも、Kubernetesのものだけでなく、その上に乗っているシステム全体として問題がないかを監視する必要があります。その監視を目視だけではなく、自動で検知し自動で通知するのがよいとわかりました。

Kubernetesのアップデートをつうじて、さまざまな基本的とも言える問題にあたってしまったわけですが、それらをつうじて、やはり地道な改善の積み重ねが結局のところの近道だとわかりました。

ということで、Kubernetesのアップデートについては以上となります。後半を担当する高橋に、バトンパスしたいと思います。

(次回につづく)