🏋️

Platform チームが発足してから整備したこと

2023/12/07に公開

https://adventar.org/calendars/9131

はじめに

株式会社 COUNTERWORKS の Platform チームでエンジニアをしている @yuuAn です。

弊社には SHOPCOUNTER と SHOPCOUNTER Enterprise という 2 つの主力商品があり、それぞれの開発を行うチームがあるのですが、それらのチームがユーザーに価値を届けることに専念できるよう技術面からサポートするのが、我々 Platform チームになります。

この Platform チームですが、立ち上げたのは 2 年程前。
それまでは多少詳しいアプリケーションエンジニアが面倒を見ていた、インフラ、セキュリティなどの課題を、専門家の力も借りながら本腰を入れて解決していったのが始まりでした。

スタートアップあるあるだとは思いますが、創業期から運営している SHOPCOUNTER はアドホックな環境で動いており、ビジネスが成長するにつれて解決しないといけない課題が山積していました。

この記事では、これまで我々がどんな課題を解決してきたのかを紹介したいと思います。

想定読者

  • 急成長する Web サービスをどのように整備していったらいいか悩んでいる方
  • COUNTERWORKS の歴史を知りたい方

取り組んだこと

1. Infrastructure as Code の導入

Infrastructure as Code (IaC) とは、サーバーなどのインフラ構成をコードで管理することです。
メリットはいろいろあるのですが、再現性があることと、ドキュメントとして機能することが、我々が IaC を導入した大きな理由でした。
IaC の実現には Terraform を使いました。

🔄 再現性がある

例えば本番環境を IaC で構築すると、それと全く同じ構成のステージング環境を簡単に作ることができます。Ruby のバージョンアッププロジェクトのためにもうひとつ環境が欲しいと言われても、簡単にその環境を作ることができます。

📜 ドキュメントとして機能する

AWS のマネージドコンソールを見ても、どのリージョンのどのサービスが使われていて、どんな設定がなされているかを確認するのは困難です。
インフラ構成をコードで管理することで、コードを見ればどのリージョンのどのサービスにどんな設定がなされているか、簡単に確認することができるようになりました。コードなので、差分を見るのも簡単です。

2. 本番環境とステージング環境の AWS アカウントを分離

もともとひとつの AWS アカウントの中に本番環境とステージング環境が同居していたのですが、以下の理由でこれらを別々のアカウントに分けました。

🛡️ ステージング環境を踏み台とした本番環境へのハッキングが難しくなる

最も懸念していたのがこれで、もし万が一ステージング環境のサーバーにセキュリティホールがあった場合に、それを踏み台にして本番環境に侵入されてしまう懸念がありました。
アカウントを分けてしまえば、ネットワーク的にも権限的にも完全に分離することができます。
ネットワークが分かれていれば、ステージング環境のサーバーに侵入できたとしても本番サーバーに侵入することは困難ですし、権限が分かれていれば S3FullAccess みたいな雑な権限の付け方をしていたとしても、環境を跨いでデータにアクセスすることはできなくなります。

🔄 ステージング環境を本番環境とまったく同じ構成にできる

IaC と組み合わせることで、新しいアカウントに本番環境と全く同じ構成の環境を作ることができるようになりました。アカウントが分かれているので、名前が衝突するなどの問題も起こりません。

🔒 AWS Service Quotas の回避

AWS ではアカウントごとに Service Quotas といういくつかの制限が設けられています。
例えば使える ElasticIP の数や、サブネットの最大数がそれにあたります。
環境ごとにアカウントを変えてしまえば、これらの制限に引っかかりにくくなります。

3. AWS アカウントの SSO

従業員が AWS のマネジメントコンソールや API にアクセスする際に、今までは IAM ユーザーを使っていたのですが、これを AWS SSO[1] を使う形に変更しました。

🆔 環境ごとにユーザーを作らなくてもよくなった

前述の通り環境ごとに別々のアカウントを用意していたため、その全てに IAM ユーザーを作るのは手間でした。
AWS SSO を使えばユーザーを一元管理できるので、アカウントごとに IAM ユーザーを作らなくてもよくなります。

🔑 アクセスキーの管理から解放されログインが簡単になった

いくつもの IAM ユーザーを使うということは、その分だけアクセスキーを管理するということでもあります。
AWS SSO を使えば、AWS のマネジメントコンソールには Google のアカウントを使ってログインし、CLI を使う際もブラウザによる認可を挟むことで手軽に臨時のアクセスキーを発行できるようになりました。

4. IAM ロール

サーバーで使う IAM ユーザーも IAM ロールに切り替えていきました。

🔑 アクセスキーの管理から解放された

こちらもアクセスキーの管理をしなくてよくなりました。
もしアクセスキーが漏洩してしまったらという心配もなくなりました。

5. 環境に依存した設定の環境変数化

弊社のサービスはバックエンドに Ruby on Rails を使っているのですが、秘匿情報の管理には secrets.yml が使われていました。
これには、秘密鍵さえ共有できればメンバー間で簡単に秘匿情報を共有できるというメリットがあるのですが、欠点もありました。
本番環境やローカル環境など、環境ごとに YAML ファイルを用意するため、臨時で新しい環境を作りたくなった際に融通が利きません。
そこで、環境に依存する全ての設定値を環境変数で管理するように修正しました。

🔄 環境構築の自由度が増した

開発中の新機能の動作確認用に新しい環境を用意したり、ローカルでの検証時に DB だけステージング環境のものを参照するようにしたりなど、環境構築の自由度が増しました。

6. 秘匿情報の一元管理

今まで secrets.yml で管理していた秘匿情報を環境変数で扱うようになると、今度は環境変数の管理に苦労することになりました。
AWS 環境で使う環境変数は AWS のパラメータストアで管理すれば問題ないのですが、ローカル環境で使う環境変数が問題で、動かせている人から .env ファイルを DM で貰うといった悪い習慣ができてしまいました。
そこで、ローカル環境用の環境変数もパラメータストアで管理するようにして、make env という自作コマンドひとつで .env ファイルを自動生成できるようにしました。

⚙️ 環境構築がらくになった

ローカル環境を構築する際は、AWS CLI で AWS にログインしてから make env を叩くだけ。
AWS のユーザーは AWS SSO で管理しているため、新入社員や退職者への対応も簡単です。
誰の手も借りずに環境構築ができるようになりました。

*️⃣ 環境変数の追加がらくになった

新しい環境変数を追加したい場合は、パラメータストアに決まった命名規則で値を追加するだけで済むようになりました。
各自がローカルで動かしてみて、環境変数に関するエラーが出たら make env を実行します。
これでいちいち周りに質問する手間も省けます。

7. マスクした本番データの用意

本番環境の DB のデータから機密情報をマスクしたデータを定期的に S3 にダンプする仕組みを作りました。

🔍 本番環境で発生した問題の調査がしやすくなった

今までは、本番環境で発生した問題を調査する場合、それがデータに起因する問題だった場合は、本番環境に入って調査する必要がありました。
マスクした本番データを用意してからは、それをローカルにインポートすれば、本番環境に影響を及ぼすことなく自由に調査することができるようになりました。
もちろん機密情報はマスクしているので、うっかり情報を漏洩させてしまう心配もありません。

⚙️ 本番環境に近い環境で開発ができるようになった

本番環境とのレコード数の違いから、ローカル環境で動いていたものが本番環境では動かなかったなんてことも珍しくありません。
ローカル環境でマスクした本番データを使うようにしてからは、本番環境に近い環境で開発や動作確認ができるようになりました。

8. ウイルスチェックの導入

弊社のサービスにはユーザーがファイルをアップロードする機能があるのですが、アップロードされたファイルに対してウイルスチェックを行う仕組みを作りました。

🛡️ ユーザーのセキュリティリスクを減らすことができた

サービス開始時からアップロードされた全てのファイルをチェックして、未だ 1 件もウイルスは見つかっていないのですが、ウイルスチェックによって万が一の備えをすることができました。

9. imgix の導入

画像のサムネイル生成や、ページに合わせたサイズ変更に、もともとは smalllight という livedoor さんが作ったツールを使い、専用の ECS コンテナで動かしていました。
しかしこの仕組みを作ってくれた方が退職してしまい、新たに学習コストを払うようなものでもないということで、imgix を使うようにしました。

🖼️ 管理がらくになった

マネージドなサービスを使うことで、管理がとてもらくになりました。
特にバージョンアップについて気を遣わないといけない対象が増えれば増えるほど、Platform チームの負担は増えていくので、許容できる金額であればなるべくマネージドサービスを使っていきたいと思っています。

10. Cloudflare の導入

弊社が運営する SHOPCOUNTER は、スペースの検索や予約ができる SHOPCOUNTER 本体とは別に、CMS で作られた SHOPCOUNTER Magazine など、複数のアプリケーションで構成されていて、それらを shopcounter.jp というひとつのドメインで提供しています。
これらの振り分けは、もともとは SHOPCOUNTER 本体を動かしている Next.js で行っていたのですが、これを Cloudflare Workers で行うようにしました。

🌐 コードがシンプルになった

アプリケーションのコードと振り分けのためのコードを分けることで、コードをシンプルにすることができました。
Next.js のことを知らなくても、Cloudflare Workers のシンプルなスクリプトを直すだけで振り分けルールを追加したり編集したりできます。

⚡ CDN の効果でサイトのレスポンスが早くなった

Cloudflare はもともと CDN 機能を提供してくれるサービスなので、それによってサイトのレスポンスが早くなりました。

🛡️ WAF を使えるようになった

Cloudflare は WAF 機能も提供してくれているため、それを有効にすることができました。
WAF は攻撃とみられるアクセスを検出したり拒否したりできます。

11. メンテナンスモード

Cloudflare Workers を使ってメンテナンスモードを作りました。

🚧 メンテナンスモードへの切り替えが簡単になった

これまでは Next.js のアプリケーションをビルドし直すことでメンテナンスモードに切り替えていたのですが、新しい仕組みでは Worker をセットするだけでメンテナンスモードを有効にできるようになりました。
簡単に切り替えられる上にすぐに反映されるため、リリースの作業時間がだいぶ削減されました。

👀 メンテナンスモード中も従業員がサイトを見られるようになった

Cloudflare Workers がメンテナンスページを返している間も、裏では SHOPCOUNTER がいつも通り動いているので、Worker Script に抜け道を用意すれば、メンテナンスモード中でも SHOPCOUNTER にアクセスすることができます。
この仕組みを作ったことで、本番環境での動作確認を完全に終えてからメンテナンスモードを解除できるようになりました。
また、トラブルが発生していた場合は切り戻しをするといった選択肢もあります。

12. Sentry の導入

アプリケーションで発生したエラーを管理するために Sentry を導入しました。

🐞 エラーの種類によらず統一的な扱いができるようになった

もともと Ruby on Rails の中でエラーごとに Slack 通知を行うコードが書かれており、その作り込み次第でエラー通知が解りやすかったり解りにくかったりしていたのですが、全てを Sentry に任せることで、どんなエラーも等しく扱うことができるようになりました。

📊 エラーが発生した際の詳細なコンテキスト情報がわかるようになった

Sentry では何も設定しなくても、リクエストされた URL やパラメータなどの情報をエラー情報に添えてくれますが、独自に設定すると被害に遭ったアカウント ID や Cookie の情報など、様々な情報をエラー情報に添付できます。
そのお陰でデバッグがし易くなりました。

🛰️ どんなエラーがどれくらい発生しているのか客観的に見られるようになった

これまでは Slack に通知が来たエラーに対して都度都度対応していたのですが、重要度や発生頻度によって対応を変えたりできるようになりました。

13. NewRelic の導入

クラウドサービスごとにバラバラに存在していたメトリクスやログを、一箇所で見られるようにするため、NewRelic を導入しました。
NewRelic はできることも多く、UI も複雑なため、まだ使いこなせているとは言い切れませんが、少しずついろいろな機能を試していっています。

📈 様々なクラウドサービスを一箇所で監視できるようになった

弊社の場合、フロントエンドは Vercel で動いていて、バックエンドは AWS、そしてその手前に Cloudflare があるといったように、いろいろなクラウドサービスを組み合わせてサービスを構成していますが、それらのメトリクスやログを NewRelic で一元管理できるようになりました。

📚 Vercel のログを長期間保存できるようになった

Vercel や Cloudflare ではログは直近のものしか保存してくれないのですが、NewRelic に転送することで長期間保存することができるようになりました。

14. Ruby / Ruby on Rails のアップデート

新しいプロダクトでは比較的新しいバージョンに追従できていたのですが、歴史のあるプロダクトのバージョンを上げるのは一筋縄ではいかず、我々 Platform チームに任されることになりました。

🛡️ サポート切れの心配がなくなった

古いバージョンを使う場合の一番の懸念はサポートが切れることによるセキュリティ上の問題だと思うのですが、新しいバージョンにアップデートすることでその心配がなくなりました。

🎉 新しい機能が使えるようになった

バージョンを新しくすることで新しい機能が使えるようになりました。

15. インフラコストの通知

インフラの中には従量課金で課金されるものも多いですが、コストが大幅に上昇した際には、Slack に通知をするように設定しました。

💸 急激なコスト増に気づけるようになった

今までで予想外のコスト増というのは起きていないのですが、コストが増えた際にエンジニアみんながそれに気づけるようになりました。
また、前よりもコストを意識できるようになってきたと思います。

16. GitHub Actions で Self-hosted Runners を導入

歴史のあるプロダクトはテストも複雑で、全てのテストを終えるのにかなりの時間を要してしまっています。
GitHub Actions はとても便利なのですが、料金は高めで、時間のかかる CI を回しているともの凄いコストがかかってしまいます。
そこで、Self-hosted Runners という仕組みを使って自分達が用意したインフラの上で GitHub Actions を動かすようにしました。

💰 コスト削減

Self-hosted Runners の環境を用意するのは比較的難しく、メンテナンスもらくではないのですが、大幅にコストを削減することができました。
自分達で構築する分スペックなどの自由度も増すため、今後は速度アップのためのチューニングも行っていく予定です。

おわりに

こうして振り返ってみると、2 年程の間に多くの仕組みを構築できていたことがわかりました。
初めはセキュリティ的な懸念に対処するような「守りの施策」が中心だったのですが、最近は開発者体験をよくするような「攻めの施策」もできるようになってきました。
これからもいろんな仕組みを用意して、他のエンジニア達を力強く支えていきたいと思います。

We are hiring!!

COUNTERWORKS では絶賛エンジニア募集中です。
今回の記事を読んで、COUNTERWORKS に興味を持って話を聞いてみたい方がいましたら、まずはカジュアルにお話しましょう!
https://counterworks.co.jp/recruit/?utm_source=zenn&utm_medium=referral&utm_campaign=advent-calendar-2023&utm_content=7

脚注
  1. 今は AWS IAM Identity Center と呼ぶらしい ↩︎

COUNTERWORKS テックブログ

Discussion