チーフエンジニアとして一年間、仕事してみた話

こんにちは!ゲーム事業部、技術部所属の須藤@p_chin)です!o(`ω´ )o

来年4月で新卒入社してもう7年が経ちます...年を感じます。

先日入った謎のベトナム料理店で『大学生かと思った』と言われたのでまだ大丈夫かもしれませんが、年をとった大学生も世には存在するので安心できません。

この記事はTech KAYAC Advent Calendar 2019の22日目の記事となります。

前置き

自分は現在運用中のモバイルゲームプロジェクトにて、チーフエンジニアという役割でお仕事してます。

元々このプロジェクトが開発中の頃はバトル部分メインで全体のリードプログラマをしていたのですが、開発終盤にかけてエンジニアの人数も増えてきてリリース直前あたりからは協業先との技術的な窓口だったり、エンジニアメンバーのマネジメントの様な仕事も担当する様になりました。

なんか固そうな話題なのですが、ちょうど社内でも評価の時期で自己評価についても振り返ったりしてたので今年のAdventCalenderは人生の記録もかねてこの話題にしようと思います。

この役割になった経緯

今年の初めあたりにQA期間にて、バグがなかなか収束しなかった状況からエンジニア観点でやり方を改善して欲しいと依頼を受けたのが発端です。

今まで社内の企画職が担当してた協業先との打ち合わせなどにも頻繁に参加する様になり、チーム内の技術的な窓口になりました。

それから無事に4月頃にリリース出来たのですが、それくらいの時期にエンジニアが多すぎてタスク管理仕切れずうまく仕事を回せていない感触に違和感を感じ始めていて自分1人で全員マネジメントするのが難しくなってきました。(当時はクライアントだけで14名のエンジニアがいました。)

他チームのエンジニアにもやり方の相談した結果『これはセクション毎にチームを分割してリーダーを立ててセクション単位で自分が管理できる様にしないといけないな〜』と悩んでた頃に丁度、協業先のPMの方に『もう少しマネジメントの比重を増やしてチーフエンジニアをやらないか?もちろん今後のキャリアをどう考えてるか次第だけど..』と誘われて、ものは試しにやってみようという気持ちで始めてみました。

チーフエンジニアとしてやってた事はこんな感じ

自分が今年やっていた仕事についてざっくり箇条書きした所、チーフエンジニアについてググった内容とだいたい合ってそうでした。

ちなみに自分はクライアント側の出身でサーバー側のアプリやインフラにはあまり詳しくなかったので、サーバー側の諸々の事に関してはサーバー側のリードエンジニアに基本的にお願いしていました。

エンジニアのプレイングマネージャ的な立ち位置なのかなと勝手に思ってます。

チーム内エンジニアリソースの管理

1on1を実施して、各々のやりたい事をヒアリングしつつ、適材適所でタスクのアサインをしてました。

今アウトゲームやってるがインゲームも担当してみたい、shaderハマってるのでshader書く仕事ください...などの要望がメンバーから出てくるので、その仕事を担当してもらったり。

タスクの偏りや、残業の多いメンバーがいたら別のメンバーをヘルプに付けたり..などの調整もしていました。

技術的な課題の洗い出し+管理

協業先から依頼されたものや、社内で起案された問題を時にはディレクターなどにも相談して優先順位つけて整理してます。

技術的負債への対策や、他職能などから依頼されてる業務効率化に関する課題など...様々です。 platformやOS側の仕様変更に伴う対応なども良く含まれています。最近だとiOS13βが出たのでその対応とかですね。

QAが必要な修正内容が多いので、QAチームやアップデート開発を管理してるPMなどと修正を入れるアプリバージョンについて相談します。 デバッグ機能など本番の製品に含まれない開発内容の場合は例外ですが。

P(プロデューサー)/D(ディレクター)/PM(プロジェクトマネージャ)との連携

開発コストやチームマネジメントの課題などの観点について主に自分の立場では関与していて、『稼働が多すぎたり体調壊しがちなメンバーが居ないか』、『異動/退職するメンバーが抜けた後の体制どうするか』、『(プロジェクト全体に関する重要な情報があった場合に)全体への伝え方をどうするか』『このコストが高すぎるのでなんとかしたい』...などについて話したりもします。

自分は今までリードエンジニアとしてエンジニア組織内のみにしか関与していなかったのですが、お金含めたコストのお話も多かったりするので個人的には新しい視点があって面白いです。

普通にエンジニアとして開発に参加

開発メンバーからの『機能追加時の設計相談』『github上でのpull-requestのレビュー』などは日常的にしています。 割とマネジメントを始める際には確実にコードを読み書きする時間が減ってしまうのが自身のキャリア的にも不安だったのですが、設計やコードレビューのレイヤーなら問題なくこなせてました。

最近久々に大きめの新規機能実装の仕事が来て、詳しい書き方は割と忘れてましたが設計に関してはコードレビューなどで『読んでた』経験と設計の相談などにも関与していた『擬似コードレベルの設計』なら日常的に行なっていたのでそこに関しては昔からは最低限劣化しない品質の仕事ができていた気がします。

とはいえ、基本的には『長い時間を使いがっつりコードを書く』『締め切りの厳しい実装』のタスクは持たない様にしてるのでちょっとしたバグを直したりデバッグ機能を追加したりなどでコードを書く機会が多い一年でした。

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

結論としてはこの後もこの役割を一生続けるつもりはないですが学びが多くあったのでやって良かったです。 初めての役割で失敗も多く、反省も多いのですが少しづつうまくてやれてる感触はあります。

最初はまだ20代だしコード書く機会が減るとエンジニアとしての成長も止まってしまうなという気持ちもありましたが、 自分は元々ゲームが面白くない失敗をしたらゲームデザインに興味を持ったし、プロジェクトが炎上してたらタスクマネジメントなどと、プログラム以外でもゲーム制作に関する事なら、自分にも手を出せる範囲の物事には全て興味があったので、チーフエンジニアの役割にもポジティブに取り組めていました。

今回チーフエンジニアをやってみて得た経験はエンジニアの普段の仕事にも活かせそうでした

他社との応対をして交渉や合意形成の取り方を学んだ

  • エンジニアとして課題や試したい事を見つけた時に正当な理由を説明して正式にタスクとして取り組むまでの段取りを組むのに慣れた
  • もちろん勝手に動くもの作って『どう?、これ入れたら良くならない?』とディレクターなどに見せるやり方も好きです!が、それが通用しにくい場合もあるので手段として使えます

お金の話を聞いてお金の流れを学んだ(元々自分がお金に関してとても疎かったので気づきが多かっただけかもですが...)

  • あらゆるコストについて意識する様になった(QA費、アセットの外注費、人件費など)のでそれ前提の提案なども考える様になった
  • 業務改善した場合に実際のコスト感を意識できる様になったのでコスパ観点での優先順位を組み立てられるし、他職種へ改善の効果を前より説明しやすくなった気がする

個人ではなく職能の垣根を超えたチーム全体の集団としてモノを見れる様になった

  • 集団に対してどの様に情報を伝えれば良いか、集団は情報を伝えるとどの様に反応するかの事例を多く見れた
  • 製品やプロジェクト全体レベルでの課題からエンジニアの仕事を見つけられる様になった
    • 企画がボトルネックになってるので企画の雑務を減らす
    • アートがアセット作成してゲームに表示されるまでのワークフローに無駄が多いので減らす
    • 今後この機能は多く拡張する機会があるけどコードが汚いのでリファクタリングのコストを払う...など

辛い時もあった

腕の自信が無くなる

時々、全くプログラムを仕事で書いてないので自分の腕が本当に大丈夫なのか不安にとらわれてしまう瞬間がありました。

チームの若者はshaderやCleanArchitectureについても勉強していたりするし、何より他のプログラマから『指示は出すけどこいつプログラムろくに書いてないしな』と思われてしまってるかも...と気分が落ち込んでる時につい事実とは(多分)異なる事を考えてしまうわけです。

そういった焦燥感はたまにあった方が時間を見つけて勉強したり勉強会で発表する後押しにもなるし、個人的に今は丁度良いプレッシャーだと思ってますが今の役割を始めたての頃はなかなか辛いものでした。

終わりに

今年はなかなか良い経験が出来たと思います。最初はネガティブになることもあったし若者からも『ぴーちんさん(コード書けないで)かわいそう』と言われたりもしましたが、新しい武器になる兆しが少しでも芽生えた感触があります。

カヤックの面白には『面白がる』という意味も含まれてるらしいのですが、今年の後半からは面白がれたのかなと思ってます。

と、なかなかスピリチュアルというか技術とはズレた方向性の話題になってしまったかもしれませんがお読みいただきありがとうございました!

来年は技術ネタもたくさん用意できたらと思います。

それでは!良いお年をo(^-^)O

NuxtJS製のWebサービスをECSに移行したはなし

SREチームの長田です。 Advent Calendar Migration Track 22日目の記事です。

今回は弊社で運用しているLobiというサービスの、Webブラウザ版(Web版)をECSに移行したはなしです。

web.lobi.co

なぜ移行したのか

おなじみ、Amazon Linux1 EoL対応です。 すべてのアプリケーションをEC2から移行するプロジェクトの一環です。

移行前

LobiのWeb版はNuxtJSを使って実装されています *1。 各APIにリクエストし、サーバーサイドレンダリング(SSR)した結果を、Webブラウザに返しています。 NuxtJSアプリは他のアプリケーションも同居するEC2インスタンスで実行していました。

f:id:handlename:20191220165016p:plain
移行前の構成

(実際にはクライアントで動的にコンテンツを更新するためのAPIリクエストも発生しますが、今回の話題には関わってこないので省略しています)

移行

設定値の切り分け

ECSに移行するにあたり、本番環境でも開発環境でも、同じコンテナイメージを使うというルールにしていました。 環境ごとにイメージを用意するのは管理コストがかかりますし、「本番環境と同じものが手元でも動く」(=デバッグしやすい)というコンテナの旨味が半減してしまいます。 どの環境でもイメージは同じものを使い、環境依存の値は実行時に環境変数として与えています。

NuxtJSはデプロイ前にアプリケーションをビルドする必要があります。 ビルド後の成果物をコンテナイメージに含めることになるのですが、nuxt.config.js内に環境変数を参照する設定値を書いてしまうと、 ビルド時点での環境変数が成果物に埋め込まれてしまいます。 EC2インスタンス上で動かしているうちは、

実行環境上でビルド → 成果物を配布してアプリケーション再起動

という手順をとっていたため、問題になっていませんでした。

環境変数を参照するということは、サーバーサイドレンダリング時に実行されるコードということです。 nuxt.config.jsに書く代わりに、Server Middleware内で環境変数を参照することで、アプリケーション実行時に設定値を渡すようにしています。

具体的には、SSR時にAPIリクエストするドメインがこれにあたります。 APIリクエスト先は環境によって異なるので、ビルド時に埋め込まれてしまっては困るというわけです。

デプロイの仕組み

EC2時代の手順は、以下のようになっていました。

  1. Github上でデプロイ対象のPull Requestをmasterにmergeする
  2. デプロイサーバー上でリポジトリをpullする
  3. デプロイサーバー上でNuxtJSアプリをビルドする
  4. アプリケーションサーバーにビルド成果物を配布し、アプリケーションを再起動する

f:id:handlename:20191220170251p:plain
移行前のデプロイフロー

(成果物の配布にはstretcherを使っているので間にS3 Bucketがいたりconsulがいたりするのですが、本筋ではないので省略しています)

ECSに移行することで、以下のように変わりました。

  1. Github上でデプロイ対象のPull Requestをmasterにmergeする
  2. CircleCIがmergeを検知してgit pull
  3. CircleCI上でNuxtJSアプリをビルドする
  4. CircleCI上でコンテナイメージをビルドしECRにpushする
  5. Task Definitionを更新しECS Serviceに反映する

f:id:handlename:20191220172132p:plain
移行後のデプロイフロー

手順が増えたように見えますが、CircleCIが行おう処理は自動で行われるので、人間が行う作業としてはほとんど変わりありません。

canaryデプロイの仕組み

少量のタスクを試験的にデプロイするcanaryデプロイの需要がありました。 EC2時代には、1インスタンスにのみデプロイ操作を行うことで実現していました。

ECSにはcanaryデプロイの仕組みはありません。 ひとつのECS Serviceについて、複数のTask Definitionを混在させることはできません。

そこで、1Taskしか起動しないECS Serviceを別途用意し、 それに対してデプロイを行うことでcanaryデプロイを実現しました。 メインのECS Serviceと同じTarget Groupに入れることで、全体の1/nのリクエストをcanaryタスクに回すことができます。

f:id:handlename:20191220173243p:plain
ひとつのTarget GroupにメインTaskとcanary Task両方を入れる

ちなみに、現在はALBでWeighted Target Groupsが使えるので、 canary用のTarget Groupを別途用意することで任意の割合だけリクエストを流すことができるようになりました。

aws.amazon.com

メモリリーク問題

未解決の問題です。

ある時期から、NuxtJSアプリのメモリ使用量が上昇し続けるようになってしまいました。 一定を超えるとGCが走るのですが、実行コストが高く、HTTPリクエストに対するレスポンスに如実に現れてしまっていました。

根本的な解決方法としては、メモリリークの原因を突き止めて解消することなのですが、 これがどうにも解決できなかったので、「対処療法としてメモリ使用量が一定を超えたらECS Taskを入れ替える」という方法をとっています。

f:id:handlename:20191220174541p:plain
ECS Taskを入れ替える仕組み

対象ECS ServiceのMemoryUtilizationメトリクスを元に、Alarmを発火させます。 発火情報はSNS Topicに送信されるので、Bash Layerで動いているLambda functionがそれを受け取り、 ECS Serivceに対して aws ecs update-service コマンドを実行しています。

おかげでメモリ使用量のグラフがこんな感じになってしまいましたが、現状致し方無いといった状況です・・・。

f:id:handlename:20191220172537p:plain
不連続なMemory Usage

移行後

コンテナイメージをビルドする時点でつまずき、デプロイしてからもメモリリーク問題に苦しみ・・・と苦労が多かったプロジェクトでした。

副作用として良い効果もありました。 EC2インスタンス上で動かしていた頃は、「Googlebotの訪問が多すぎて同居している他のアプリケーションまでパフォーマンスが悪化する問題」があり、 botの訪問頻度を制限していました*2。 ECS Serviceとして独立してスケールするようになったことで制限を解除することができました。

Amazon Linux1 EoL対応はまだまだ残っています。 それらについてもいずれ本ブログで紹介する予定です。

*1:実はこの直前にAngularJSからNuxtJSに移行しているのですが、それはまた別の機会に・・・

*2:https://support.google.com/webmasters/answer/48620?hl=ja