バイセルテクノロジーズAdvent Calendar 2023の17日目の記事です。
昨日、16日は前原さんの「無線サーベイツールを用いて無線LAN環境を可視化してみた」でした。
こんにちは、SRE部の佐々木です。バックエンドエンジニアをしていたのですが今年の1月からSREとして入社しました。 今までの経験を生かしつつプロダクトの改善に日々尽力しています。
さて、今回は私がEmbedして改善のお手伝いさせていただいているプロダクトのOSリプレースとその周辺対応について共有できそうな出来事が多かったので紹介します。
既存環境の簡単な説明
私が関わっているプロダクトはバイセルで買取が行われた商品の在庫管理を行います。 バイセルには買取や販売などユースケースに分かれたプロダクトが複数存在しており、当プロダクトはそれぞれと連携して在庫の状況管理をしています。
AWSのEC2上で展開しているRuby on Railsで動いており、EC2の構成管理などはTerraformとElastic Beanstalkを使って管理していました。
入社した当時私が抱いた感想は「案外普通なんだな」というものでした。もちろんDB周りは多少なりとも工夫がなされているのですが(今回こちらは本題ではないのでとりあげません)、ソフトウェアの構成についてはオーソドックスなWebシステムの構成だったのには多少の驚きがありました。 日々在庫の入れ替えや移動、一覧取得といった情報量の多い操作をするため、かなり複雑なシステムなのではないかと構えていたのですが、この点は安堵しました。
既存環境の問題点
安堵したのも束の間、こんな依頼をされます。 OSがEoLを迎えるので構成の再検討を含めたOSリプレースをお願いしたい。
なるほど確かに動いているOSが古そう…という訳でいろいろ構成周りを調べてみると大きな問題点がいくつか発見されたのでした。
以下、私が実際に確認して発見した問題点の一部抜粋です。
- OSがAmazon Linux
- 今年(2023年)の年末でEoLを迎える
- Elastic Beanstalkが現在サポートしていないカスタムイメージを未だに使い続けている
- Rubyが2.5、Railsが5.2を利用
- 共にEoL済
- Node.jsのバージョンが6
- こちらもEoL済
この話に興味を持たれている方はご存じかと思われますが、EoLとは「End of Life」の略で製品の保守がすでに切られたものになります。全くサポートが無くなるので後方互換性を切る対象にも入る訳ですね。
他にもいろいろと問題になる構成が多かったのですが今回は省きます。
この情報を見たときに非常に戦慄したのを強く覚えています。
というのも、前職時代に私は似たようなRailsプロジェクトの更新を任されことがありました。
その際、「Rubyのバージョンが古すぎて gem install
, gem update
ができず、システムリプレースを余儀なくされた」経験があるため 一刻も早くバージョンを上げなくてはいけない という危機感を抱きました。
ではRubyのバージョンを真っ先に上げるべきかというとそうでもありませんでした。 Amazon Linuxの新規インスタンスの生成が既にできなくなっており、バージョンを上げようにも環境の生成ができない状況になっていたためです。 新しい環境をElastic Beanstalkで用意しようにもカスタムイメージが利用できないので環境を増やすこともできませんでした。
私には途方も無い山が目の前に見えてました。SRE1年目がやることなのか。と。
既存環境入れ替えプロジェクト開始
まずは差し迫った脅威に対応する必要があります。 着手できるところがなかったため、問題の大きさを正しく認識することから始めました。
本件を整理すると以下の問題への対処が必要でした。
- OS
- Amazon LinuxがEoLを迎えるので別のOSにするかAL2023にする必要がある
- 構成管理
- Elastic Beanstalkでカスタムイメージが使えなくなったので別のシステムを利用する必要が出てきた
- 新しい環境は現状の環境を再現するためRuby 2.5の環境を新しいOS上で作ってAMI化する必要が出たため、カスタムイメージを利用する事が前提になった
- Elastic BeanstalkではRuby 2.5のサポートが無くなっていた
- GitHub ActionsでCDを行っているが、ここも対応するスクリプトに変更が必要
- 踏み台問題
- 踏み台のOSもAmazon Linux
- 踏み台からDBアクセスなどを行っているので年末までに入れ替えを実施する必要がある
- 言語のバージョン問題
- Ruby、Node.jsのそれぞれのバージョンを上げる必要がある
プロジェクト開始当初は締め切りが夏前と伝えられていたため、戦慄度が増していました。 とりあえず千里の道も一歩からとありますしOSの選定から始める事にしました。
OS編
この時はインフラ構成をEC2とECSのどちらにするかまだ検討していなかったため、それらで対応できるOSから選択することにしました。
候補として挙がったのは Ubuntu
や Amazon Linux2
、 AL2023
、 Debian
、 Alpine
の5つ。AL2023
はこの時まだリリースされていない状態だったので見送って実質4つのOSで検討しました。
今回のOS選定では、最低要件として以下を据えました。
- LTSがあり、比較的長期間であること
- Ruby 2.5環境がつくれること
- EC2、ECSで使える事
この視点で見ていくと
Debian
,Alpine
の選択肢が消えました。サポート期間が短かったりApkパッケージマネージャで古い環境を作るのに難しさを感じたためです。
Amazon Linux2
はEoLが2025年予定と比較的短いですが、 Amazon Linux
と(ほぼ)同一のプラットフォームの延長線のOSなのでこの時点での資産を大きく生かすことが期待できました。
環境構築のコマンドも多くを変える必要はありませんし amazon-linux-extra
パッケージを使うと環境構築しやすそうだったのも良かったです。
Ubuntu
はLTSの期間が非常に長いことが魅力的でした。Ubuntu20.04は2030年、Ubuntu22.04は2032年までサポートをしてくれます。
最終的にLTSの長さを取り Ubuntu 20.04
を新OSにしました。どちらかというと私がUbuntuでの運用の方が得意だったからという点もありました。
Rubyはもちろん公式のパッケージにはありませんでしたのでソースインストールという選択をしています。 gdm
, gdbm
が無いなどの困難はありますが無事インストールできました。
ここで初めてのAMI作りをしつつ次のステップに行くこととしました。
構成編
OSはできたとなると次に決めるべきはインフラ構成です。
チームからはEC2
と ECS
の選択肢で迷ってるという話を受けていました。
ここは過去の経験から EC2
上に展開する方向で決定しました。
確かに EC2
の利用自体、流行りでは有りませんし、ECSのオートスケーリング機能や手軽さもとても良い機能だと思っています。
ECS Taskなども使用したかったのが本心でしたが、懸念に感じたのはその負荷傾向です。
プロダクトの負荷傾向は実行されるSQLに起因されるところが多く、負荷のグラフは壁のようなグラフを示します。 これまた前職時代の知見にはなりますが、直登するような負荷が掛かる場合にはECSのオートスケーリングが間に合わないため、自前のスケジューリング機能を作って管理していました。 本プロダクトでは同じような負荷傾向にあり、今回はECSを選択しませんでした。
結果は次の通りに決定しました。
- EC2上にアプリケーションを展開
- 本番環境はオートスケーリンググループで展開(これはリリースの為の措置)
- Pumaを直接起動
- Systemdで起動させるようにした
- Port 80で起動
- 前段にALBを配置
- ALBはPort 443で受け取ってEC2を登録したTarget Groupへ転送する仕組み
オーソドックスな構成にしました。
CD編
構成ができると次はCDです。
今まではGithubActrions上でApplicationをビルドした後にS3へPutし、eb deploy
コマンドからデプロイを実行していました。
Elastic Beanstalkが使えなくなったことで、これに変わるDeployツールを複数検討しました。 AWSをせっかく使っているのでAWS CodeDeployを利用してみることにしました。 実際既に一部のDev環境ではCodeDeployを使っていることもあり、この流れで利用することに決めました。
CodeDeployは対象のインスタンスにCodeDeploy Agentをあらかじめインストールし、特定のRoleを付与することで利用できます。 (一連の流れは公式ドキュメントがより分かりやすいです)
構築時、Dev -> Stg -> Prodの順で環境を構築していきました。 CodeDeploy自体、appspec.ymlに処理を書き連ねていくだけなので非常に直観的でした。
hooks: BeforeInstall: - location: ....sh timeout: 10(sec) runas: ubuntu
構築時は以下の点に特に苦労しました。
- AppSpecを環境ごとに分けて記載できないので全ての処理を1つのAppSpecにまとめる必要があり、処理が煩雑になってしまった
appspec.production.yml
の様に分けたかった
- 必然的に処理するShellの中身も煩雑になってしまった
- 環境ごとのIF文を多く記載する必要が出てしまった
- 本番環境など止まってはいけない環境でのリリースではデプロイ設定を工夫するかオートスケーリンググループを使ったBlue/Greenデプロイを実施しないと途中でアプリケーションが止まる
- 同時のリリース処理をするので近いタイミングでアプリケーションへの再起動が掛かるため発生した
- 複数台がある環境の場合にMigrationは実行できない
- 同じスクリプトを実行していくのでMigrationが衝突する
という点に体当たりでぶつかっていったのがちょっと大変でした。特に2台以上で処理をするようになるStg環境以上でより詰まり気味でした。
踏み台編
踏み台についても同じくOS入れ替え対象だったためApplicationサーバーでも利用されているAMIを使って構築しました。 CodeDeployなどの制約は必要無くなったのでこの辺りのアプリケーションを削除しつつ以下の踏み台特有の問題への対処しました。
- 複数環境へSSH接続する
- Dev、Stg、Prod環境それぞれへSSHでログインする必要がある
- DBへの接続
- 踏み台からDBへ接続できるようにDBClientを別途インストールする必要がある
発生した問題点
前項までの内容を実施したことでOS、構成管理、踏み台問題の解決ができました。 とはいえ遭遇した問題も非常に多かったです。そんな問題点の一部を共有します。
- OS編
- 手動ビルドに工数をかけすぎてしまった
- 初回で工数が掛かってしまったことを反省点としてビルドドキュメントを作成した
- ドキュメント作成以降、何度かビルドしたが工数を大きく圧縮できた
- Rubyを手動インストールしたことでCodeDeployAgentをInstallする際にApt側で管理しているRubyがインストールされてしまってパス問題が発生した
- 結果として問題は解決されたがForce installすることになってしまったためAptでのインストールができなくなってしまった
- Rubyのバージョンアップが完了し次第解決予定
- 手動ビルドに工数をかけすぎてしまった
- 構成編
- ログの見方1つとっても今までとは違う慣習を押し付ける形になってしまった
- CloudWatchのロググループをつくったり、LiveTailの方法をドキュメントに書いた
- ログの見方1つとっても今までとは違う慣習を押し付ける形になってしまった
- CD編
- CDに相当する対応をCodeDeployに任せたことで発生したMigration問題
- 複数台のインスタンスがある場合はCodeDeployでDeploy時、二重にMigrationを実施してしまい、リリースに失敗する
- Migrationは別途対応インスタンスにログインして実施してもらっているが別途良い環境を用意する予定
- CDに相当する対応をCodeDeployに任せたことで発生したMigration問題
- 踏み台編
- ElasticIPをあんまり付与しない、共有鍵を作りたくないという点から
ec2-instance-connect
を利用したがこの導入が難しかった- 習熟度が低い人向けに厚くドキュメントを作成し、不明点を都度更新する方法でフォローした
- 各環境のアプリケーションサーバにログインしやすくするようなスクリプト組むことでインスタンスID意識せずに環境ごとへログイン出来る環境を用意した
- ElasticIPをあんまり付与しない、共有鍵を作りたくないという点から
その他の問題点
技術的な問題以外にも問題が発生していました。8月末のタイミングで急にNode.jsをインストールできない問題が発生しました。
問題の原因はどうやら https://rpm.nodesource.com/setup
に変更が出てAmazon Linuxが対象外になったことが原因のようで現状のスクリプトでのリリースができなくなってしまいました。
Node.jsのバージョンを上げる事で回避できそうでしたがElastic Beanstalkの環境を弄るリスクとコストを鑑みて下手に弄る事ができずにいました。
丁度8月末はDev環境での動作が完了した段階でStg、Prodへ環境を展開していく計画をたてていたタイミングだったため計画を早めて2週間でStg、Prod、踏み台環境の整備を実施する必要が出てきました。 結果、過密スケジュールで進行する羽目になり、大きな環境負荷をチームの開発者の方々に強いる形となってしまいました。
移行完了
ともあれ無事リリースが完了し、「Migrationどこでやるんだ」問題に絡んだリリース周りの問題は発生したりしましたが現在まで無事に新環境で動作しています。 初めてのインフラ、SRE業務だったのでよくやったよと自分をねぎらいつつもRubyのバージョンアップに絡んだ環境構築などはまだ続きます。 改めて気を引き締めてより改善に向けて地道な活動をしていこうと思ってます。
まとめ
自分自身でも大変長編になってしまい、驚いていますが今回の事から多くの学びを得ました。
スケジュールの遅延を繰り返してしまいました。想定外のアクシデントが多く発生し、対応に多くの時間を取られたことが原因です。 アクシデントは予期せぬものですが、発生しやすい点を予測して計画を立てることがより重要だと再認識しました。 具体的にはアクシデントが起こるたびにスケジュールを見直すべきだったと感じています。
また、チーム内での変更点の共有が不十分でした。特にリリース方法の変更のような大きな変更については、細かい点までフォローすべきだったと考えています。都度、変更点をまとめた文章を共有しつつ、リリースタイミングにはドキュメントを事前に用意しておくなどの工夫を意識づける事にしました。
エンジニア以外のメンバーもデータベース操作することがあるため、ドキュメントを充実させることの重要性をより強く自覚しました。
CodeDeployを用いて安定的なCD環境を構築できた経験は大きな成果でしたが、AWSのサービスをもっと活用できた可能性もあると感じています。 今回の事例では、CodeBuildやCodePipelineなどを利用してCodeDeployで行っていた行程の一部を最適に分割できました。 自分の使っているSaaSへの知識不足を強く感じました。
インフラに関する知識がまだ不足していることを再認識しました。特にSystemdの設定ファイルを作成する際に時間が掛かってしまいました。
以上でした。インフラ業務って奥深いですね。
そんなSRE一年生もいるBuySell TechnologiesではSREも募集しております。カジュアルな面談からでも是非お話しましょう。
さて明日18日は富澤さんのCloud Runを利用したプルリクエストごとの動作確認環境の構築です。 私は業務上あんまりGCPを使用しませんがPR毎の動作確認環境って欲しくなりますよね。ご期待ください。