etcdを運用していたら壊しかけた話 - MIXI DEVELOPERS

etcdを運用していたら壊しかけた話

Taiga ASANO
MIXI DEVELOPERS
Published in
Dec 17, 2020

この記事は ミクシィグループ Advent Calendar 18日目の記事です。

今年のアドベントカレンダーのネタとして、ゼロトラストプロキシを内製して運用し、機能の追加を行った話を前回書きました。

今回の話もこのゼロトラストプロキシに関係します。普段運用しているプロキシがある日突然使えなくなり、原因を探って行ったら運用しているetcdを壊しかけてしまった話を書きます。改めて事象について振り返り、原因や対策を整理しておこうと思います。

終わりの始まり

私自身が4月に新卒入社で配属され、リモートワークでずっと働いていましたが、3ヶ月弱ほどリモートで働いたあと、弊社のマーブルワークスタイル(詳しくはオウンドメディアのミクシルをご覧ください)に基づき、はじめてオフィスに出社した7月初旬に、これから書く出来事は起こりました。

午前中に、開発チームとやり取りするSlackチャンネルで、ゲームの開発環境の管理画面につながらないという声がちらほらと出てきました。スクリーンショット等を見ると、TLSの証明書エラーが出ているようで、バックエンド側のコード等ではなくプロキシ側の問題であることがすぐに分かりました。

ここから、プロキシ側のTLS証明書の無効になったため、エラーが出ていることが分かりましたが、更新が漏れることは考慮していませんでした。この仮説にたどり着くために、まずプロキシの構成をご紹介します。

構成

以下にこのゼロトラストプロキシの大まかな構成を載せます。プロキシ内部のコンポーネント(RBAC機能やエージェント)についてはこちらの記事を参照してください。ストレージにはcoreos/etcd-operatorを使ってetcdを、TLS証明書の発行と更新にはjetstack/cert-managerとLet’s Encryptを利用していました。今回の話で出てくるetcdは、Kubernetesのストレージとして利用されるetcdではなく、自前でOperatorを使って運用しているetcdを指します。

ブラウザから見えるTLS証明書はcert-managerとACMEプロトコルによりLet’s Encryptの証明書が更新されるはずでしたし、今までは更新期限を意識せずに更新されていました。

つまり、cert-managerに何らかのエラーが起きているのではないかと仮説を立てて、そこから原因と解決へのヒントを辿ってみることにしました。

何が起きたのか

cert-managerのログを確認したところ、Let’s Encryptのサーバとの通信が正常に行われておらず、更新に失敗し続けていることが分かりました。その結果、cert-managerが発行してくれる証明書のリソース(Certificate)の有効期限が古いままとなっていました。

リトライはすでにcert-managerが行っていたこともあって、Podの再作成などを行っても特にリソースの状況に変化はありませんでした。

影響範囲としては、TLS証明書の有効期限が切れているため、プロキシを通過するすべてのリクエストが通らなくなりました。このことをまずチャンネルで共有しました。

解決に向けてやった事、起こった事

まず、cert-managerの設定などで回避できる方法などがないかを探しました。GitHubのIssue等を見ていたところ、cert-manager自体のバージョンのアップデートを推奨するコメントを発見しました。

利用しているcert-managerのバージョンを確認したところ、確かにかなり古いバージョンを利用していたことが分かりました。導入から1年ほどのはずだったものの、かなり頻繁にリリースされていました。

cert-managerをアップデートした

cert-manager自体のバージョンのアップデートが推奨されていたこともあり、アップデートを実施することにしました。利用しているバージョンのリリースノートからその時点での最新版のリリースノートをまとめて、変更点をマニフェストを管理するリポジトリのIssueに書きました。

その結果、CRDのAPIのバージョンが上がっていることが分かりました。実際にアップデートガイドにも古いcert-managerと周辺リソースの削除を行った上で適用することが推奨されていたので、cert-managerを抜いて入れ替えることにしました。

cert-managerの古いマニフェストをベースに削除を行い、新しいマニフェストを適用しました。この時点で気づくべきでしたが古いCertificateリソースを削除していませんでした。

cert-managerをアップデートしたがHelmが当てられなくなった

cert-managerをアップデートして、次はCertificateリソースのAPIバージョンをアップデートする必要がありました。このプロキシのマニフェストはHelmで管理されており、CertificateRequestリソースやEtcdClusterリソース(etcdクラスタを立ち上げるためのetcd-operatorのCRD)、プロキシのDeploymentなどを一つのHelmリリースで管理していました。

このため、古いAPIバージョンを握ったリソースが居た状態でCRDを削除してしまい、Helmリソースに変更を加えることができなくなってしまいました。これを解決するために、最終的には古いバージョンのマニフェストからCRDのみを入れ直してkubectlなどから利用できる状態にしましたが、ここにたどり着くまでに自分自身が混乱していたのでいくつかやってしまったことがあります。

プロキシには、主に新機能の検証などに利用していますステージング環境があるため、そのHelmリソースに変更を加えたり削除したりして動作を確認していました。上で書いた古いCRDを入れるという解決の前に削除をしていたりしたので、今振り返ると、結果としてそのようにする必要はなかったと思います。しかし、削除したことで気づけたことがありました。

削除して新しいAPIバージョンのCertificateを入れ直したステージング環境は正常に証明書の発行まで行き、正常にPod自体は動作するようになりました。

ステージング環境が正常に動作しなかったので直した

ステージング環境のプロキシは正常に起動しましたが、上手く接続することができませんでした。開発環境をどう復旧させるかを検討していたので、この部分の解決はチームメンバーの方が行ってくれました。

ステージング環境が動作したように見えたが、データが飛んでいた

ステージング環境は正常に動作していましたが、ユーザーやロール、エージェント用のPKIを管理する画面のデータが飛んでいたことが分かりました。EtcdClusterをdescribeしていたところ、etcdのPodのボリュームがemptyDirだったことが分かりました。emptyDirはPodがノードにある期間保存されるボリュームなので、Helmのリリースを削除するなどを行ってPodが削除されると一緒に削除されるものです。

今回はそれを特に確認せず削除を行ってしまったので、データが削除されてしまいました。本来であれば、EtcdClusterはetcdのクォーラムを充足する数のPodが動くはずなので、例えば1つPodが削除されたぐらいではデータのリカバリが行われ、正常に動作し続けますので、この設定が間違いというわけではありません。また、何らかの原因でetcdクラスタが復旧不可能になった場合でもバックアップを定期的に行う機能がOperatorにあり、それを有効にしていたはずでした。

バックアップしていたはずだが、されていなかった

開発環境で実際にこのプロキシが使われてから、バックアップからの復旧がはじめてでした。バックアップからのリストア方法については他のチームメンバーが調べてくれました。バックアップの場所を探し、バックアップされた日付を確認しましたが、定時バックアップされるはずがかなり古いものでした。なので、このバックアップの失敗の原因を特定し、バックアップを再開させる必要がありました。

エラー等から、クラウドストレージ側のライブラリの問題の可能性や、etcdに原因がある可能性などを検討し、修正を行いましたが解決しませんでした。そもそもetcd-operatorがアーカイブされていることもあり、とりあえず解決に向けて、このバックアップ機構に頼るのではなく他の方法でバックアップする方法を考えました。

etcdにはスナップショットの機能があり、バックアップを行うことができます。機能の利用はetcdのCLIであるetcdctlなどを通して利用することができます。今回はPodにアタッチしてetcdctlを利用することができたので、Pod内でスナップショットの取得を行い、バックアップを作成することができました。

etcd本体の機能でバックアップの作成ができたので、これを戻す方法を考えました。etcdctlのsnapshotの復元でもよかったのですが、プロキシのデータ構造として、期限付きで利用するものがあり、バックアップからの復元の際にはetcdの特定のキーを弾いてリストアする必要があったので、そのようなキーを弾いて空のetcdにリストアするツールを書き、スナップショットから復元したetcdから空のetcdにリストアする方法を取りました。

図のような方式でリストアを行い、プロキシの動作確認を一通り行いました。ツールを書いたことで特定のキーのリストアを抑制したり、実際のリストアのログも取ることができました。

リストアが一段落したところでcert-managerを直した

Pod自体は動作していましたが、証明書の発行が正常に完了していなかったことが分かりました。これは、cert-managerの要求するYAMLの形式が変わっていたことで正常に設定が取得できていなかったことが分かりました。このあたりの調査や解決もこれまた、他のチームメンバーがやってくれました。

以上の手順を実施後、開発環境のプロキシが正常に動作するようになりました。

原因と対策

今回の原因を振り返ります。

Kubernetesクラスタの運用方法についてルールがあまりなかった

  • 今回利用していたKubernetesクラスタは基本的に本番サービスに直接関係ないような、開発環境に利用していたものであったため、運用や監視についてはベストエフォートで行っていました
  • 監視や運用についてルールやドキュメントを残したり、普段からアップデートのメンテナンスを行うようにしていく
  • 利用しているOSSの最新情報を受け取るチャンネルを作成し、チーム全体で把握するようになりました

バックアップが動いてなかった

  • 開発環境で利用するものであり、実験的に作られたものだったので、実際にバックアップから復元するケースをあまり想定していませんでした
  • そのため、復元方法についてドキュメントがありませんでした
  • バックアップの方法を見直し、バックアップの実行を監視するようにしました
  • バックアップの実施を検証し、ドキュメントを作成しました

Kubernetesリソースのアラートをしていなかった

  • リソースの監視は行っていましたが、アラートをしていませんでした
  • バックアップ自体をCronJobで行うようにし、Kubernetesリソースとして管理、監視、アラートをするようにしました

etcdのOperatorの運用がそもそも難しかった

  • etcdのオペレータの動作は複雑で、理解するのが難しいです
  • coreos/etcd-operatorはすでにアーカイブされていて、代替を探すか開発する必要があります
  • 現状はetcdのOperator固有の機能(バックアップ)に頼らないことにしました
  • 今後はetcd以外のストレージについても検討する必要があるかもしれません
  • プロキシのデータのバックアップはetcdなどのストレージに依存しない方法で行い、復元もプロキシの機能で行えるように実装し直しました

おわりに

はじめて出社した日に起こったので焦りましたが、チームメンバーの方々全員に助言をいただいたり、復旧に関係する事を手伝っていただいたこともあり無事解決できて感謝しています。

チームの文化としてオンラインで完結させるという文化もありましたし、同じ島に居ましたがSlackでやりとりしながら解決に向うことができたので、今回のように振り返ることができました。

今回の事象での対策や、やることはまだあります。coreos/etcd-operatorの代替を探す(もしくは開発する)必要があったり、我々でコンポーネントの動作を把握し、必要であれば各Operatorの動作について深く理解する必要も感じました。今後はこれらにも着手していきたいと思います。今回はetcdのOperatorの動作の複雑さやetcd自体にはあまり触れていませんが、今後機会があれば調べてまとめたいと思います。

--

--

Published in MIXI DEVELOPERS

株式会社MIXIに所属の開発者による技術知見や開発ノウハウ、カンファレンスレポートなど、開発に関する情報を共有するエンジニア・ブログです。

No responses yet