中身が謎な闇と化したEC2に光を当ててECSに移した話

エオルゼアの情勢が落ち着いたので次は王になろうと狭間の地へ向かい、大ルーンを集めていたら流れでラニ様にお仕えすることになり星の世紀を開いたAzoopのソフトウェアエンジニアの武田です。ELDEN RINGはトロコンしました。DLCが楽しみです。

 

以前、会社のnoteに書いたトラッカーズマネージャーのECS on Fargateへの移行に続く第2弾として中古トラックのオンライン売買プラットフォームのトラッカーズもほぼ同じ構成で移行したのでこれまた同じように語りたいと思います。

サービスの命に関わる「闇」

下の図は移行前のトラッカーズの構成です。

(移行後の構成については先述のnoteに書いたトラッカーズマネージャーとほぼ同じ構成になっているので省略します。)

トラッカーズのECS移行前

移行前

冗長化のため複数台で構成されたアプリサーバとデプロイに用いるJenkinsサーバがありそれぞれEC2に構築されています。

デプロイはJenkinsによって行われており、デプロイしたい人がデプロイ用ブランチに差分をマージしPipelineの実行ボタンを押すことで本番に反映されます。ここまではどこかしらで似たような構成を見たり聞いたことがあるような気がする構成です。

そしてこの構成に多くの人が開発と運用に関わり、時には履歴が追跡できない方法で変更されるなど歴史が積み重なった結果巨大な「闇」と化しました。

EC2ホストのディレクトリをマウントしたDockerコンテナ

アプリサーバが起動しているEC2にはDockerがインストールされており、Rubyやアプリケーションが利用するパッケージ類はコンテナ化されていました。ところがアプリケーション本体はイメージに含まれておらずホストのディレクトリがマウントされていました。設定はリポジトリ内のdocker-compose.ymlの環境変数、クレデンシャルはデプロイ時にJenkinsから配布されるenvファイル内に書かれていましたが長年の運用によりホストに直接設置されたファイルがいくつか存在している無法地帯と化していました。

また、デプロイはJenkinsから各サーバへSSHしコマンドを発行しておりソースコードGithubからクローンしコンテナイメージのビルド、アセットパイプラインの実行まですべて本番環境上で実行されていました。このため時々リソース特にメモリ不足によりデプロイに失敗することがありました。そのためアプリケーションの動作に必要なリソース量の1.5倍~2倍のスペックを用いておりコストが増える要因になっていました。

アップデートも復元もできないJenkins

デプロイに使っているJenkinsは自分が入社した頃から使われておりおそらくサービスの開発を始めた頃からあるものです。社内のどこにもこのJenkinsを構築した資料やツールが見当たらないことから手動で構築されたものと思われます。Jenkins Pipelineをはじめとする設定類はバージョン管理されておらずバックアップはEC2のスナップショットのみ、さらに最後のバックアップは1年以上前と障害等で再構築が必要になっても救出できないかもしれない状態でした。*1

また構築されてから移行までアップデートを実施しておらず最新版とはかなり乖離があったため素直にアップデートができなくなっていました。

闇に光を当てる

トラッカーズマネージャーの移行作業で得たノウハウをもとにトラッカーズの移行を始めることが決まりました。(調査に結構な時間がかかることからまずはトラッカーズマネージャーの移行をしてノウハウを得て実施する、と判断していました)

いよいよ闇と化したEC2に光を当てるときが来たのです。

本番環境にしかない必要なファイルを救出し「正しいフロー」に乗せる

まずは「プロダクトのリポジトリには存在しないがEC2上にはあるファイルやディレクトリ」の調査から取り掛かりました。これはEC2上にgit cloneされ、デプロイ時にgit pullしていたためgit statusを実行しバージョン管理外になっているファイルを1つずつ確認していく作業を繰り返します。
*2

一通り洗い出しが終わったらオープンにして大丈夫なファイルはプロダクトのリポジトリへ、クレデンシャルはJenkinsから配布するように設定します。

他にもデプロイでしか使わないスクリプトが見つかったためひとまずGithubに専用のリポジトリを作って避難させました。*3

Dockerイメージを作る

これまでホストのディレクトリをマウントすることを前提にしていたDockerイメージをアプリケーションに必要なコンポーネントをまとめたものに作り直します。
トラッカーズ本体はごくごく普通のRailsアプリなので難しいことはなくマルチステージビルドを使って最終的に出来上がるイメージには動作に必要なもののみ入ってる状態にしています。

また、移行完了まで並行運用期間が存在するため既存のDockerfileとは独立して作成しています。

脱・Jenkins

デプロイに使っていたJenkinsは前述の通りとても古いためこのまま使い続けるわけにはいかず、またJenkinsをアップデートしようにも再構築に近い作業になりそうでした。また既存のインフラとの並行運用期間が存在する以上そちらに支障をきたすような事象は避けなければなりません。このため新たにCodePipelineでデプロイフローを構築しました。

本番環境の切り替え

ここまでで一通り準備ができたのでいよいよ本番を切り替えます。(Cronはトラマネと同様ECS Scheduled task + ecscheduleで管理するようにしました)

アプリサーバの切り替え

トラッカーズはお客様はもちろん社内でも出品の代行など業務での使用者が多いプロダクトです。そのためテストに協力をしてもらいやすかったためHTTPヘッダーに特定のキーと値をセットした状態でアクセスするとECS側に接続されるようにALBのリスナールールを設定しました。この状態で協力してもらったスタッフに1週間5営業日程度使用してもらいログやコンテナの監視を入れつつ最後の調整を行いました。

完全切り替え時はALBのリスナールールを修正するだけなのでスムーズに移行することができました。

サイトマップの配信

トラッカーズではSEO対策としてサイトマップを配信しています。EC2で動作していたときにはcronスクリプトでXMLをDockerコンテナにマウントされたホストのディレクトリに生成していました。

ところがFargate on ECSを使うとデプロイ時にコンテナが破棄されるため別途永続ストレージを用意しなければ生成したサイトマップが消失してしまいます。

EFSをコンテナにマウントする方法もありますが、今回はシンプルにS3にサイトマップを生成しそこから配信することにしました。
ただし配信するドメインには注意が必要で、基本的には同一ドメインから配信する必要があるようです。

CloudFrontから配信することも考えましたがアプリと同じドメインから配信する必要がある制約から「できないことはないがちょっとしんどい」*4ということがわかりs3からサイトマップを取得するLambdaを作成しそれをALBにぶら下げて配信に使用することにしました。このLambda関数の中身はaws-sdkを使って書いた単純なnode.jsのハンドラになっています。これをGithub Actionsを使ってDockerイメージにビルドしLambdaにデプロイしています。

まとめ

多くの人が開発と運用に関わり、長い時が経過した結果構成の意図が行方不明になってしまい「闇」となったインフラに光を当てた話でした。

当時はこれが最速で立ち上げることができる手段として選択されなんやかんやで表に出てしまうような問題がなく運用できていたのは先人たちのおかげなので感謝を捧げつつ必要な改善が必要なタイミングで実行できる環境に作り変えました。

そして「できたから安心」としていると何ヶ月何年経過したときに将来のメンバーにまた同じような負債を押し付けてしまうことになるのでこれからも引き続き改善に取り組んでいく所存であります。

*1:救出できたところで使っていたものとも乖離しているため使い物にならないでしょう

*2:一時ファイルやcrontabに設定されているスクリプトによって生成されるファイルがそこそこの数あったため骨が折れる作業でした……

*3:最終的に不要になったので削除しました

*4:同一ドメインで配信するためにCFの前にnginxを建てるなど

Azoop Tech Blogをはじめました

はじめまして、Azoop CTOの庄司です。

私たちAzoopは経済の血流とも言われる運送業にフォーカスし、利益最大化、業務効率化を実現するプラットフォーム(トラッカーズ、トラッカーズマネージャー)を開発しているスタートアップです。

このAzoop Tech Blogでは、社内で日々取り組んでいる開発技術、技術組織として取り組んだ施策はもちろんのこと、UI/UX 、プロジェクトマネジメントなど、プロダクト開発な話をバラエティ豊かに発信してゆくことを目指します。

なぜブログを書くのか?

ブログを始める目的は下記の通りです。

  • Azoopという会社の存在をより多くの方に知っていただきたい
  • Azoopに興味いただいた方に我々のプロダクト開発組織がどんな感じなのか知っていただきたい
  • 執筆者・プロダクト開発組織の成長
  • コミュニティへの恩返しをしていきたい
  • あわよくば読者の皆様にAzoopの仲間になっていただきたい

いずれも大事な目的ですが、個人的には『執筆者・プロダクト開発組織の成長』を促す機会として、ブログが楽しく使われるようにしていきたいと考えています。

我々ソフトウェアエンジニアとって成長とは何か? もちろん技術力向上は必須でしょう。 私は技術力と同等に『主張を伝える力』を高めることも非常に大切と考えます。

企業におけるソフトウェア開発はチーム戦が基本で、コミュニケーションは欠かせないからです。
ソフトウェアの規模が小さいときは関係者が同じ目線でソフトウェア、プロダクトを把握できるのでツーカーで伝わったりします。
ところが、規模が拡大するにつれて全てを理解することが困難になってゆくため、徐々に関心の分業が進みます。

業務において何か施策を動かすときには否が応でも関係者が増えますし、その関係者は自分が知っていることを100%共有していません。

そんな状況下で、自身が向き合っている技術的な課題を他のメンバーに正しく理解してもらったり、自身が思う課題に対する打ち手のメリット・デメリット、そのうちの最善手を理解してもらった上で議論をスタートしたり、自ら考えたアプリケーション開発におけるガイドラインを理解して実践してもらったり、自分の主張を誰かに伝えなければいけない機会はたくさんあります。

業務の視野を広げれば社内に留まらず、仕事の一環として勉強会やカンファレンスのトーク、LTなどで登壇したり、登壇用のスライドを作成して公開したりすることもあるでしょう。

さらに個人的な活動にまで範囲を広げれば、先の登壇に加えて、自分のブログで技術話を書いたり、Zennで本を執筆したり、同人誌を技術書典で頒布したり、ポッドキャストで自分が思う最強のアプリケーション設計の話をしたり、Youtubeで最新の機械学習の事例を解説する動画を投稿したり、など何かしらの形で発信する機会があるでしょう。

発信を意識すると、学習のループが周りやすくなり、同時にループの質も高まります。結果的に技術力の向上にも寄与すると思っています。

そんなわけで、新しく発見した技術でテンションが上がったこと、苦労して取り組んだ問題をなんとか解決した武勇伝、技術組織を運営する中で自分自身に起きた変化の話、などなど、内容の大小問わず私たちが共有したい話を読んでくださる方に伝わるよう、独りよがりにならずに書いていきたいです。

その結果、Azoopの存在やAzoopが目指すこと、プロダクトや関わるメンバーを知っていただいたり、コミュニティに対して貢献していたり、Azoopの仲間が増えたりしたら嬉しいです。

技術的負債と送りコード🙏

Azoopのエンジニアチームとして、成長するための催し物をいくつか行なっています。 今回はその内の1つである『送りコード』を紹介します。

アプリケーション開発を進めてゆくと規模の大小問わず負の遺産が生まれます。 意思決定のタイミングでベストな選択をしたとしてもです。 外部環境、事業モデルの変化は大小絶えず起きており、インフラ構成、アプリケーション設計や、アルゴリズムの選択といった技術的な意思決定と環境変化との乖離が大きくなってゆきます。 乖離の果てに顕在化した負の遺産は技術的負債と呼ばれております。

我々エンジニアにとってはアプリケーションに限らず、各種メディアで見聞きする親しみある存在です。

技術負債的とは具体的には下記のようなものです。

  • レガシーコード: 古くなったコードのことで、現在の技術にあっていないものを指します。
  • テスト不足: コードの品質を維持するためには、テストコードを書く必要がありますが、テストコードを書くことが面倒であるため、テスト不足になることがあります。
  • 冗長なコード: コードが重複していたり、不必要な部分があることを指します。
  • 安全性の欠如: セキュリティに問題があるコードや、不正アクセスが可能なコードなど、安全性に問題があるものを指します。
  • 複雑性の高いコード: コードが複雑で、保守性が低いものを指します。
  • ドキュメントの不足: コードがどのように動作するかを説明するドキュメントが不足していたり、存在しないことを指します。

これらを放っておくと、意図した変化が困難なアプリケーションになります。

意図した変化が困難なアプリケーションは、意図しない変化が容易なアプリケーションとも言えます。 意図しない変更が忍び込みやすいアプリケーション壊れやすいです。 その脆さに細心の注意を払ってコーディングするため、顧客へ新しい価値を提供するまでに時間がかかります。新しい価値が毎度ホームランだったら良いのですが、残念ながらそんなことはありません。

変化できないプロダクトは市場から求められないでしょうし、運良くヒットしていたとしても、あっという間に競合他社に飲み込まれるでしょう。

技術的負債って怖いですね。

日々の開発を進める中で技術的負債を返済し、新たに借りないことが理想です。 ただ、そうは問屋が卸さないのは皆さんご存知の通りかと思います。

ChatGPTに技術的負債の返済が難しい理由を尋ねてみました。

技術負債の返済が難しいのは、以下のような理由があります。

  • プロダクトの成長に伴い、コードの規模が大きくなるため、コードの把握が困難になることがある。
  • 開発メンバーが入れ替わることで、コードの担当者が変わり、コードの把握が困難になることがある。
  • プロダクトの仕様変更があると、それに応じてコードの修正が必要になるが、修正が面倒であるため、修正を先送りにすることがある。
  • 開発メンバーが急いでコードを書いたり、急いで修正したりすることがあるため、コードの品質が低くなることがある。
  • コードの品質を維持するためには、テストコードを書く必要があるが、テストコードを書くことが面倒であるため、テストコードを書かないことがある。

以上のような理由から、技術負債の返済は難しくなることがあります。

そういうことなんです。

返済が大事なことは分かっていても、プロダクトの事情や、コードの事情など複数の要因が絡んだ結果、先送りにされがちになります。 なかなか返済できない負債をどのように返してゆくか。 個人の心意気に任せるだけでなく、チームとして確実に返済に取り組んでいきたい。 その取り組み1つとして始めたのが『送りコード』です。

送りコードは、エンジニアメンバー全員が普段の業務から離れて技術的負債を解消に集中する2日間です。

『個々人が思うイイカンジの送りコードをやろう!』は基本ではあるのですが、これだけだと進行する上で迷いがでるかなと思い、妨げにならないようにガイドラインを作りました。 一方でガチガチに中身を作り込むのもシンドいので、下記のゆるいガイドラインに従ってやってみることにしました。

  • 送りコード前日までに解決したい課題を洗い出しておく
  • 2日間で何らかの成果物を出す
    • 成果物の例
      • リファクタリングした差分のプルリクエストを作る
      • 技術的負債の全貌を調査した結果や、技術的負債の返済計画などをドキュメントにまとめる
  • 最終日に成果物の発表をする

ちなみに、送りコードという名称は、お盆に迎えた先祖様の霊を送り出すために火を焚く『送り火』が由来になっています。 これに倣って送りコードでは、プロダクト成長に貢献してくれた技術的負債を供養し送り出しているというわけです。

送りコード当日。メンバー各々が日頃感じていた負債が持ち込まれ送られていきました🙏

などなど...バラエティを感じるラインナップでした。

私はアプリケーションに存在するコードで呼び出されてないもの、いわゆるデットコードの削除に取り組みました。

発表会は大きな会議で飲食(エンジニアなのでピザ)しながら発表を聞くスタイル。

カジュアルな感じでやってみました。

送りコードの様子

実際やってみてどうだったか?

開催後にKPT法(良かったこと、課題と感じたこと、次回トライしたいこと、この3つの観点で振り返る手法)で振り返りをしました。

良かったこと

参加者全員が、技術的負債を返済する機会として有用だった、開催頻度を増やしても良い、と感じてもらえました。

課題と感じたこと

送りコードの意義についてポジティブな意見があった反面、実際の運用面でいくつか課題点が指摘されました。

例えば、2日間で成果物を作りつつ、発表準備も行おうとすると実際の作業時間が思ったより短かったという指摘。シンプルに作業時間の短いことに加えて、発表構成などを個々にお任せしていたため、資料作りで時間を使ってしまったという指摘もありました。

また、発表に関する質疑応答、議論の時間が短かいという指摘も挙がりました。

次回トライしたいこと

良かったこと、課題と感じたことをふまえて次回トライしたいこと決めました。

  • 送りコードをもっとやろう!
    • 開催当初は半年に1回くらいで良いかなと思っていたのですが、実際に返済してみると他にも色々改善できそうな点が見つかったことや、技術的負債の返済をチームの文化としてより浸透させてゆくために、開催頻度を3ヶ月に1回に変更しました。
  • 日々の開発でリファクタリングできそうな箇所を見つけたら、送りコード用のチケットを作成しよう
    • 送りコード開催日にはリファクタするべき箇所のデッキが完成してるので開催日前に「なにやろうか…うーん」と悩まずスムーズに送りコードに臨めます
  • 発表のフォーマットを作ろう
    • 発表内容に何を盛り込めば良いか分かるので、資料作りで困りません
  • 発表時間と質疑応答の枠を作って、タイムキーピングやろう
    • 質疑応答の時間を増やしつつ、オンスケジュールで会を終わらせます

感想

送りコードで実際に技術的負債が返済されただけでなく、日々の開発に取り組む中でリファクタリング用チケットを作成したりと、開発メンバーがリファクタリングと向き合える一助になったのかなと思います。 リファクタリングが日常に溶け込んで、送りコードを送れる日を迎えられるようにやっていきたいです💪

おわりに

今回はこのAzoop Tech Blogをはじめた目的と、チームとして成長するための取り組みである『送りコード』を紹介しました。 Azoopメンバーの日々の活動を通じて得たナレッジを肩肘張らず発信していければと思います😄