Lyftのエンジニアリングチームが自社のモノリスを分解して、マイクロサービスの集合体として再構成したのは2018年のことだった。Dockerコンテナを使用したモジュラ開発環境は、後にクラウドへと移行した。最近公開された記事には、時が経ち、マイクロサービスの数が爆発的に増加するのに伴って、同社の開発ツールがそれに追いつこうと苦心した様子が書かれている。開発環境をエンジニアのマシンに戻す必要があったのだ。
当初の計画は、Dockerベースのコンテナオーケストレーション環境を構築して、エンジニアらがテストに使用できるようにする、というものだった。マルチテナント環境を使用して、その運用上のメリットを活かせば、それまでのソリューションよりもスケーラビリティに優れた高性能な環境が安価に実現できる、という考えだ。
Lyftのローカル開発環境はDevbox("Development environment in a box"を省略したもの)と呼ばれていて、データベースのシーディング、パッケージとイメージのダウンロード、インストレーションなど、ローカル仮想マシンを管理するツールとそのコンフィギュレーションで構成されていた。開発者がコマンドをひとつ実行するだけで、要求を受信可能な環境を構築することができた。
最終的に必要となったのは、この環境を共有することだった。Devboxはクラウドに移行し、Oneboxになった。Oneboxは、言わばEC2インスタンス上で動作するDevbox環境だ。能力が大きく、イメージのダウンロードもはるかに高速であることから、開発者らはDevboxよりもこちらを好むようになった。
(出典: https://eng.lyft.com/scaling-productivity-on-microservices-at-lyft-part-1-a2f5d9a77813)
その後、時が経ち、マイクロサービスの数が増えるに従って、Oneboxインスタンスの設定やローンチは難しく、時間を要するようになった。
それぞれのサービスがさまざまなレベルの深さのインタラクションツリーを持つことから、環境インスタンスを実用的なものにするために、非常に多くのリソースを必要とするようになったのだ。可観測性ツールで実行環境全体を追跡することが難しくなってきたため、デバッグも容易ではなくなった。さらに、自身の関与するコンポーネントだけではなく、システム全体を常に考慮することが必要になったことで、エンジニアの認知負荷も高くなった。
Lifyのエンジニアリングチームによると、同社のコード修正のプロセスは"内部開発ループ(inner dev loop)"と"外部開発ループ(outer dev loop)"に分けられる。前者はコードを修正してテストを実行すればよいので、秒単位でフィードバックが得られるが、後者にはCI(Continuous Integration)やコードレビューが関わるため、長い時間を要する場合がある。
Onebox環境はセットアップや起動に時間が掛かる上に、その不安定さが目に付いたため、エンジニアたちは、コード変更のイテレーション毎に外部開発ループのCIテストに頼る部分が多くなっていた。
事業の成長に伴うこのような苦痛や不満を解消するため、開発環境をエンジニアのラップトップに戻すことに重点を移すとともに、内部開発ループを再構築することになった。
コンテナ内でコードを実行するという概念が相応の対価を要することがすでに分かっていたので、コンテナやVMは使用せず、分離された環境内のMacOS上でサービスコードをネイティブに実行する、という方法が決められた。
Lyftでは、バックエンドサービスの大半はPythonあるいはGoで、フロントエンドサービスはNodeで記述されている。
- Pythonのサービスについては、変更不能な仮想環境を使うことで分離を実現している。requirement.txtファイルが変更される毎に、新たな仮想環境が構築されるのだ。
- Goサービスでは、Goモジュールツールチェインのメリットを活用して、コマンドgo runあるいはgo testを実行する時に、すべての依存関係が自動的にダウンロードされてリンクされる。
- nodeenvにはラッパを構築し、各ノードサービスに対して、そのメタデータに基づいた適切な環境を生成するようにした。
データストアなど特別なサービスもローカルに、大部分はコンテナを使って実行する。データストアの起動時に、サービスを所有するチームのメンテナンスするスクリプトを使って最新のデータがロードされる、という仕組みだ。
このような構成のため、サービスのローカル起動は複数ステップによるプロセスとなっており、手作業での実行は煩雑であり、エラーも起きやすい。
Liftでは、tiltを使ってサービスのライフサイクルと環境を管理することで、すべてのステップを手作業で実行する必要を排除した。各サービスには、ローカルで実行するために必要なステップを記述したTiltfileが付属する。また、エンジニアがIDE上でコードを変更すると、実行中のサービス自身が再ロードを行うようになっており、内部開発ルールをさらに短縮している。
サービスは実行するだけでなく、インタラクションも可能でなければならない。gRPC、JSON/HTTP、protobuf/HTTPなど、Lyftではさまざまなトランスポート形式を使用しているため、サービスに要求を行うのも簡単ではないのだ。
Lyftのエンジニアらは、ローカルサービスに対するリクエストの構築と送信に社内開発したツールを使用する。このツールでは、サービスのIDLとの連携によって実現したオートコンプリート機能を活用している。
その結果、"サービスをローカル実行する場合、ユーザは、公開APIを利用するモバイルアプリを使ってテストするのではなく、サービスのAPIを直接叩くことになります。これによってサービスのAPIがより身近な存在になると同時に、エラー発生時のデバッグ対象範囲も小さくなるのです。"
他の専門家らも、ローカルな開発環境のメリットに肯定的な意見を持っている。例えば、Sothebyのエンジニアリング担当VPであるJames Turnbull氏は、クラウドベースの環境よりもエンジニアの作業が効率的で、費用的にも有利であると述べている。