はじめに
はじめまして!
2023年9月6日〜10月6日の1ヶ月間「CA Tech JOB」というインターンシップに参加しました、早川恭平と申します。現在は東京工業大学の修士1年です。
私が配属されたのはABEMAのサービスグロース バックエンドチームです。
この記事ではインターンシップを通して学んだABEMA サービスグロースバックエンドチームの開発フローや、タスクを通して学んだ技術についてご紹介させていただきます!
インターンシップ参加の目的
私は普段、Pythonを使用した個人開発のようなことをしていましたが、ユーザがいるサービスの開発をほとんどしたことがありませんでした。そこで、Go言語を使用して、ユーザがいるサービスのチーム開発をしたいと思い、インターンシップに応募しました。
これをもとにトレーナーの菅さんと話し、目標設計をする中で以下の目標が定まりました。
- ABEMAの開発フローを理解する
- 自分の知らない技術を一つでも多く理解し、自分のものにする
これらの目標のもとタスクを振っていただき、1ヶ月で多くのことを学ぶことができました。
開発フロー
まず、タスクの説明をする前にABEMA バックエンドチームの開発フローについて紹介します。今までの個人開発では意識してこなかった開発フローを学ぶことができ、とても勉強になりました。
開発フローは以下のようになっています。
設計
設計レビュー
実装
実装レビュー
デプロイ
設計・設計レビュー
アサインされたタスクに対して、どのような実装をするのかをドキュメントでまとめ、設計を行います。シーケンス図で処理のフローを書く、インターフェースを定義するなどを行います。
設計レビューでは、この設計をチーム全員に共有し、夕方にあるレビュー会でフィードバックをいただきます。設計レビューでは、気になったところを全員が意見し、議論が行われているのが印象的でした。
実装・実装レビュー
設計をもとに実装を行います。
実装レビューは、プルリクエストを出し、CIによるテストが通ったらチーム内からランダムで2人がレビュワーに割り当てられます。サービスグロース バックエンドチームでは、あとで説明するトランクベース開発を取り入れており、レビューによる開発の遅延を防ごうという共通認識を持っていました。具体的には、プルリクエストの粒度を小さくしてレビュワーの負担を小さくする、プルリクエストのレビューは最優先で行うというような認識を持っており、実装からデプロイまでのスピード感が速いと感じました。
デプロイ
ABEMAではトランクベース開発を取り入れています。トランクベース開発とは、開発者が自分の作業を小さなバッチに分けて、トランク(ここではmainブランチを指します)に1日1回以上マージする開発方法です。開発者は変更をトランクへ直接プッシュするため開発用ブランチのようなものはありません。これにより、開発ラインを減らし、マージする複雑さを解消し、小規模かつ頻繁にマージを実行することでコードを常に最新に保つことができるという利点があります。
トランクベース開発により、チーム全体で1日に何十回もデプロイが行われているため、デプロイを効率的に行えるようになっています。今回は、abema-backendというABEMAのバックエンドのマイクロサービスの実装を行っているリポジトリを例に、デプロイのフローを紹介します。具体的には以下のフローとなっており、開発者はmainブランチに変更をマージするだけで自動でデプロイされるようになっています。また、実際にはabema-backendにプルリクエストを出すと、CIが自動でRELEASEファイルのバージョンをインクリメントするように設定されており、開発環境と本番環境へ同時にデプロイが行われます。
開発環境へのデプロイ
- abema-backendにプルリクエストを出す。
- CIが走る。チーム内でレビューをもらう(2 approves)。
- プルリクエストをmainブランチにマージする。
- Github ActionsでDocker Imageがbuildされ、Docker Imageが開発環境のArtifact Registryにpushされる。このとき、Docker Imageのダイジェストを取得し、PipeCDに渡される。
- PipeCDが、別のリポジトリで管理されている開発環境マニフェストのyamlを更新し、mainブランチにマージする。
- PipeCDによって、開発環境のマニフェストを使用したデプロイが開始される。
本番環境へのデプロイ
- abema-backendで、対象マイクロサービスの現在リリースしているtagのバージョンを記載しているRELEASEファイルのバージョンをインクリメントする。
- 1の変更をmainにマージする。
- GitHub ActionsでDocker Imageがbuildされ、Imageが本番環境のArtifact Registryにpushされる。このとき、PipeCDにリリースのバージョンが渡される。
- PipeCDが、別のリポジトリで管理されている本番環境マニフェストのyamlを更新し、mainブランチにマージする。
- PipeCDによって、本番環境のマニフェストを使用したデプロイが開始される。
タスク
サービスグロース バックエンドチームは2つのユニットに別れており、私はその中のunit2というチームにjoinし、マイクロサービス統合というタスクを行いました。ABEMAではマイクロサービスアーキテクチャを採用しており、その数は100個以上あるため、サービスを責務ごとに切り分けて管理する必要があります。マイクロサービス統合では、このサービスの切り分けを見直し、必要であれば統合・分割を行います。
今回のタスクでは、abema-miscという複数の小さなサービスをまとめたアンチパターンとなっているマイクロサービスから一つのサービスを新たなマイクロサービスとして切り出すということを行いました。
設計
まずは、既存のコードやドキュメントを読み、依存関係を確認しました。これにより、abema-admin-gateway, abema-gatewayというマイクロサービスから呼び出される、abema-userfileというマイクロサービスを呼び出す、MongoDBとGoogle Cloud Strageを使用していることがわかりました。
その後、実際にどのような実装を行うのか、実装後に行う必要のあることは何か、デプロイ時の動作確認はどうするのかを考慮し、ロードマップを作成しました。
実装
今回は、gRPCのAPI定義の変更はないので、主にサーバーの起動、メトリクス、ヘルスチェック、グレースフルシャットダウンの実装をしました。また、新たなマイクロサービスを作成するために必要なGCPリソースをTerraformで作成し、環境変数やマシンスペックなどのデプロイに関する設定をKubernetesで行いました。
サーバー
今回はgRPCサーバーとは別に、メトリクス取得とヘルスチェック用にHTTPサーバーを立てました。
メトリクス
今まで、サーバーのメトリクスを必要とする機会がなかったため、今回初めて知りました。
ABEMAでは、Prometheusを使用しており、各リクエストにおいてステータスごとのカウントと、レスポンスタイムの計測を行っています。これをGrafanaで可視化し、サーバーの監視を行っています。
abema-go-kitという独自のパッケージでは、カウントとレスポンスタイムのメトリクスを収集するために2種類のCollectorが登録されています。これらのCollectorがリクエストの処理後にメトリクスを収集するために、gRPCのインターセプタで実装をしました。そして、httpサーバーに/metricsエンドポイントを作成し、これらのPrometheus形式のメトリクスを取得できるようにしました。
ヘルスチェック
Kubernetesでは、Liveness Probe、Readiness Probeを使用してサーバーの状態を確認します。これにより、コンテナをいつ再起動するか、PodをServiceのロードバランシングから切り離すかを決定します。
今回はこれらのためのエンドポイントを実装しました。Liveness Probe用のエンドポイントは単純に200ステータスのレスポンスを返すように実装しました。Readiness Probe用のエンドポイントでは、gRPCサーバーに登録されているヘルスチェック用のAPIを叩き、そのレスポンスのステータスによってレスポンスを変更するように実装しました。
グレースフルシャットダウン
サーバーである処理中にシャットダウンをする必要が出た場合に、すべての実行中の処理を終えてからシャットダウンを行うようにする必要があります。Go の os/signal パッケージの signal.NotifyContext() を使って SIGTERM ,SIGINT のシグナルを検知した場合はサービスを終了させ、RPC の失敗をしないようにグレースフルシャットダウンを使うようにしました。
Terraform
ABEMAでは、Terraformを使用してインフラの構成を管理しています。Terraformを使用することで、サービスごとにどのリソースを使用しているかの管理が行いやすいだけでなく、ヒューマンエラーを減らすことができます。ABEMAでは新たなリソースの作成をするためにTerraformを書き、プルリクエストを出すとチーム内のレビューと、Cloud Platformチームというインフラ周りの管理をしているチームのレビューを受けることが義務付けられています。
approveされたあと、実際にTerraformを適用し、リソースを作成します。ABEMAでは、Atlantisが使用されており、プルリクのコメントでplanやapplayが実行でき、コメントで実行結果が返ってきます。これにより、操作をプルリク上で完結でき、楽にリソースの作成が行えました。
今回は、GKEのワークロードがWorkload Identityを使用してGCPのリソースにアクセスするために、サービスアカウントの作成を行い、適切なroleを付与しました。
Kubernetes
今回は、Deploment、Service、Namespace、ServiceAccountのマニフェストを書き、kustomizeを使用しました。kustomizeを使用することで、本番環境と開発環境の共通部分はまとめて管理することができます。また、サービスメッシュとしてIstioを使用し、podに対してEnvoy Proxyを自動で組み込みました。
Kubernetesは今回初めて使用したため、設計時にすべき考慮が足りず、想定より時間がかかってしまいました。また、ふんわりとした概念しか抑えられなかったので、使いこなせるように勉強します。
開発環境へのデプロイ
開発環境用マニフェストのプルリクエストを出し、mainにマージすることでPipeCDによるデプロイが行われます。ここでは、新しいマイクロサービスを作成したため、開発環境へのデプロイの5の処理を手動で行いました。デプロイしたpodを、今回はGKEのコンソールから確認しました。K9sを使用すればCLIからスマートに確認できるのでこちらも使いこなせるようになりたいです。
デプロイが問題なく行われたことを確認したあと、実際にgRPCを叩いてみます。Evansを使用して、クラスタ外からgRPCサーバーにリクエストを送りました。このとき、ポートフォワードを使用する必要があったのですが、自分に権限がない&時間がなかったのでトレーナーさんに動作確認をお願いしました。
実際に開発環境で動作していることが確認できました。
今回は、ここでインターンシップの期間が終了してしまい、本番環境までデプロイすることができませんでした。
インターンシップでの学び
タスクの反省
今回のタスクでは、設計時に全体のリファクタリングも同時に行おうとしていました。理由としては、コードが5年前くらいに書かれており、現在のコードスタイルとは違ったためです。
しかし、そのリファクタリングによるメリットデメリットを考えたとき、メリットはあまりなく、むしろコーディングミスによるデグレが起きる可能性が大きいためしないほうがいいという結論になりました。一方で、ABEMA独自の古いパッケージを参照している部分は今後使わないという方針になっていたので、その部分のみリファクタリングを行いました。
理想では、設計したものは変更せずに実装することが出戻りがなく良いのですが(むしろそうなるように設計すべき)、今回はこの部分を詰めきることができず、出戻りが発生してしまい、結果として時間が足りなくなってしまいました。
また、デプロイに関する設計も甘かったです。
今回、Kubernetesを使用するのが初めてだったため、実際に考慮すべきことが足りていませんでした。たとえば、リソースの割り当て計画、そのために負荷試験をするのか、負荷試験をしないならどのようなスケジュールで適切なリソースを決定するのか、本番環境デプロイ後、gatewayのむき先の切り替えはどうするのか(一気に全てを切り替えるのか、Istio等で徐々にリクエストを切り替える)を本来なら考えたほうがよかったというフィードバックをいただきました。
インターンシップを通しての感想
今回のCA Tech JOBでは、タスクを通して多くのことを学べました。自分のできることよりも大きいタスクに実際に取り組むことで、多くのことをキャッチアップし、成長ができたと思います。特に、トレーナーさんや他の社員さんからは多くのサポートをいただき、自分一人では知ることができなかったことをたくさん知ることができました。今までは、ほとんど一人で開発を行っていましたが、もっと早くからこのようなインターンシップに参加しておけばよかったと思いました。
また、実際に出社することで会社やチームの雰囲気を知ることができ、サイバーエージェントに対する理解度も上がりました。特に、チーム内のコミュニケーションが活発で働いて楽しかったです。もともとはリモート派でしたが、実際に出社してチームメンバーとコミュニケーションをとることの楽しさ、大切さを知ることができました。さらにランチでは、毎日さまざまなチームのエンジニアの方と交流し、お話を聞くことができました。どの時間も余すことなく学びの多いインターンシップでした。
さいごに
この1ヶ月で多くの成長ができたのは、トレーナーの菅さんをはじめ、サービスグロースチームのみなさんのおかげです。この経験をもとに、今後さらに成長できるようにがんばります。ありがとうございました。