こんにちは!SRE部のcw-ozakiです。
今回はその1の続きとして実際にどのよう戦略を持ってKubernetesに移行したのかを解説します。
Kubernetesへの移行戦略
今回はEC2からKubernetesに移行するのに下記のような手順を取りました。
- EC2にspecを追加
- EC2で立ち上げたサービスに簡易的なE2Eを追加
- 手順書をAnsibleでIaC化
- AnsibleでIaC化したものに対してCIでspecとE2Eを実行
- プロダクション環境をIaC化したものに置き換え
- 仕様を整理してリファクタ、Kubernetes化にあたって邪魔になる仕様の排除
- Kubernetes化!!!
これはシンプルな構成だったり、自分が完全に把握できているものであればそのままKubernetes化を行うで問題ありません。 しかし、Chatworkでは数年に渡って積み重ねられてきたEC2環境であり、元々この環境を作られた方は退職済み、私自身引き継いである程度の理解はありますが完全に理解して完璧に移行できるかと言われると自信はないという形でした。
そこで、移行漏れによる障害を防ぐためにも一度このEC環境の仕様を洗い出す必要がありました。
1. EC2にspecを追加
現行のEC2では下記の手順で構築しています。
- 手順書を元にEC2を初期化してベースとなるAMIを作成する
- fabricを使用して、最新の設定ファイルを同期する
- capistranoを使用して、最新のアプリケーションを同期する
- 同期したものをAMI化して、各環境のAuto Scaling Groupに登録して、環境を入れ替え
そのため、手順書とfabric/capistranoである程度は現行仕様はわかっているのですが、いくつかそれでは見えない領域があります。
- そもそもこの手順書は正しく更新されているのか
- fabricでは変更箇所はわかるが、初期値になっている値はわからない
- 例えばPHPのextensionの設定などで初期値で運用しているものはfabricの対象に入っていなかった
- AMI上にしか存在しない変更箇所がないか
- 障害時に緊急で入れた変更などが手順書やfabricに入っていない可能性がある
絶対にこれは移行漏れするという確信と、改めて現行への理解を深めるためにServerSpecを導入し、specを書くことで仕様の言語化することにしました。
ServerSpecを使う
という訳でインフラエンジニアはみんな大好きなServerSpecです。 特にServerSpecの使い方はメインの話ではないのでどういった書き方や使い方をしているかに関しては割愛します。
現行のAMIから立ち上げたインスタンスや手順書、fabricやcapistranoのPR、過去の作業記録から下記を確認するspecを作成します。
- インストールしているアプリケーションとそのバージョン
- インストールしているプラグインとそのバージョン
- インストールしたアプリケーションがサービスとして起動しているのか
- スクリプトなどで利用しているコマンドが存在するか
- カーネルパラメーターやlocaleなど適切に設定されているか
- 必要な環境変数が適切に設定されているか
- アプリケーションやスクリプトで生成するディレクトリやファイルに適切なパーミッションがついているか
- capistranoで使うSSHユーザーは存在するか
- capistranoで使うGitでユーザーを設定できているのか
などなど。 手順書や各種ドキュメント、コードに合わせてspecを書きつつ、関連しそうな箇所を地道に確認していきます。
ここで大事なのは完全性を求めないことだと思います。 specがそもそも存在しておらず、各種ドキュメントなどが完全に揃っておらず、口伝なども喪失している状態で完全なものを作るのはかなり難しいです。そのためFail Fastの考えに則って素早く出して、素早く問題を発見して、より完全性に近くことができたと考える方が精神安定的によろしいです。
2. EC2で立ち上げたサービスに簡易的なE2Eを追加
実際には順番は前後するのですが、ガチャガチャと環境を弄っているとServerSpecは成功しているけどChatworkのサービスが立ち上がらないというケースが出てきます。 もちろん、そういったときには問題となる設定などのSpecは随時追加していきます。しかし、毎回手動でチェックするというのもナンセンスなので、ここはE2E的にHTTPベースで振る舞いを検証する方法が必要になりました。
そこでInfratasterを使い振る舞いを検証することにしました。 InfratasterはRubyで作られており、ServerSpecと組み合わせやすいというのもポイントが高かったです。
また、実はこのときBatsやSeleniumなども検討していました。ただ、Infratasterと比べるとリクエストの検査のしずらさや、ちょっと重厚過ぎてもっと簡易的なもので十分というので、簡易的にE2Eで確認できれば良いという要求にはInfratasterがとても良いバランス感を持っていて扱いやすかったです。
Infratasterでは主に下記の振る舞いを検査しています。
- NGINXのlocation系の設定を網羅
- ログインなど最低限確認しないといけないアプリケーションの機能の確認
Chatworkの場合はE2Eテストがないために、Infratasterを利用しましたが、もし既にE2Eテストを用意しているならそれをそのまま利用するのがベストだと思います。 本当ならこのタイミングでE2Eを用意できれば良かったのですが、どう考えてもそれが一大プロジェクトになってしまい移行作業に支障が出るというので、今回は簡易的なE2Eで妥協しています。
3. 手順書をAnsibleでIaC化
この移行タイミングで手順書とfabricを廃止してAnsibleへの移行も行いました。
これはEC2からKubernetesへの移行作業がどのくらいの期間がかかるのか、当初作業を始めたときに見えなかったためです。特に私は現行のPHPアプリケーションの運用も担っており、そちらで何か問題があれば復旧、恒久対応と行なっていく必要があり、差し込みの分量が見えなかったことが大きいです。
そのため、この移行作業中に仕様の変更が入った場合にその仕様を漏らしてしまい追従できない可能性があります。そこで、後述のCIを含めEC2環境の変更のフローをまるっと入れ替えするためにAnsibleを導入しました。
Ansible自体は粛々と手順書とfabricの内容と、specから必要になりそうならところを書き起こしています。
ただ、このAnsible対応を振り返って上手くいったのかというと少し微妙だったなと思っています。 というのも、EC2インスタンスの台数が一定数より多くなるとAnsibleの適用がスローダウンして時間がかなりかかってしまうという現象により、手順書とfabric時代よりDXが下がってしまったという印象です。
とはいえ、Ansibleを導入したことによって、再現性と継続的にメンテナンスする動機を強くすることができたというのは大きなメリットです。 実際に、PHPのバージョンをv5.3からv7.1、v7.3とアップデートしたり、extensionの追加や、果てにはAmazon LinuxからAmazon Linux2への移行なども行いましたが、無事にspecの維持もできましたし、その作業自体も結構あっさり作って、あっさり検証できたのでDXはなんだかんだでプラマイ0ぐらいで済んだと信じています。
4. AnsibleでIaC化したものに対してCIでspecとE2Eを実行
ここまでの段階でEC2を構築するAnsibleと、想定した通りに構築できるか検証するためのServerSpec、想定した通りにサービスが起動しているか検証できるInfratasterと必要なものが出揃いました。 これをCIに載せることで、これまで一度テスト環境に出してから確認するしかなかったのが、GitHubにPRを投げることで正常に動作することを確認できます。
これにはいくつものメリットがありますが、一番のメリットは開発者はとりあえず変更してPRを作ればよくなるためAnsible変更モチベーションが高めれることです。そしてAnsibleの変更のさいにspecが動作するため、このspecが気づいたら腐っていたということも防げます。
5. プロダクション環境をIaC化したものに置き換え
ここまで作成したフローをプロダクション環境に適用して、specを更新し続けれる状態に切り替えていきます。
とはいえ、1. EC2にspecを追加で言っている通り完全なspecではありません。そしてその完全ではないspecで作っているAnsibleでは必ず問題が起きます。(実際問題は起きました) これはもうしょうがないことで、今動かしているものが分からないと負債化してしまっている時点で、どこかのタイミングで痛みを伴ってでも返さなければいけません。Fast Failしてさらに完全なspecを作るチャンスだと認識して覚悟を決めましょう。
もちろんこういったことをしないで、Kubernetesに置き換えることで一気に負債の返却も可能でした。しかし、同じEC2で同じ構成のところで置き換えた方が問題の発生頻度は下がるし、修正も元の環境を見ればわかるので素早く直せるという判断からKubernetes化の前に仕様を洗い出して維持するようにしました。
6. 仕様を整理してリファクタ、Kubernetes化にあたって邪魔になる仕様の排除
ここに至ってようやく仕様が明確になったEC2環境を作ることができました。 しかし、仕様が明確になったことで不要になったり、昔に組んだところで仕様を変えた方が良いものなどが見えるようになってきます。
例えば、Chatworkでは古い時代はNagiosとZabbixを使っていましたが、2016年頃からDatadogへ移行しています。 NagiosとZabbixを維持したいというモチベーションもないため、このタイミングでDatadogに切り替えなども行なっています。
また、既存のEC2環境では一部のリクエストはIP固定にしているEC2で処理するという仕様がありました。 これは過去にカード決済を行なっていたサービスの切り替えを行なった際の古い仕様です。現状では不要になっているため、そこの仕様を廃止することで、Kubernetes化のさいにIPを固定化する方法を考えなくて済むようにしています。
これまでは構成を変更するために、手順書を作り、それをテスト環境で検証し、プロダクション環境に適用する・・・というフローだったのが、とりあえずAnsibleとServerSpec/Infratasterのコードを書き換えてPRを作れば良いというのはかなり心理的安全性が下がりました。 特に適用するための手順が多いとそこでの人為的ミスに怯えなければいけませんし、変更範囲が他に影響を与えてないかなどの考慮する範囲を狭めることができたので、大きな仕様変更などがとてもやりやすいです。
7. Kubernetes化!!!
という訳で最終的にEC2からKubernetesに移行していかなければいけないミドルウェアは下記に絞り込まれました。
- NGINX
- PHP-FPM
- PHP(+各種extension)
- Postfix
- NewRelic
- Datadog(Kubernetes側のメトリクス収集基盤に統合)
- Fluentd(Kubernetes側のログ収集基盤に統合)
これにChatworkのアプリケーションを乗せればKubernetes化が完了ですね! 次回以降でそれぞれのミドルウェアのKubernetes化について解説していきます。
まとめ
- テストを書いて仕様を明確にしよう
- プロビジョニングの自動化+CIで移行期間に備えよう
- 不要な仕様を整理しよう
という訳で今回は既存のインフラを仕様化していく話でした。 次回はKubernetesを採用するにあたって必ず考える必要のある、シングルテナント vs マルチテナントとライフサイクルにどのように追従していくのかという話をしていきたいと思います。
www.wantedly.com ChatworkではSREメンバーを絶賛募集中です! このKubernetes化の話やChatworkの各種インフラや体制など興味があればカジュアルに話を聞きにきちゃってください。