ローカル・リモートお好きにどうぞな、HRBrainのマイクロサービス開発環境
はじめに
こんにちは!HRBrainでエンジニアをしている鈴木(善)です。
みなさん、開発環境はどのように整えていますか?
HRBrainでは、エンジニアが自由に使え、壊しても問題のない開発環境を用意しています。
以前は各自のPC上で環境を立ち上げていましたが、PCのリソースを圧迫する問題が無視できなくなり、リモートマシン上でも開発できるように改善しました。運用を始めて1年ほど経ち、課題はあるものの、安定した運用ができています。
本記事では以下について紹介します!
- 開発環境の概要
- なぜ、どのように構築したのか
- そこから得られた知見
ざっくりまとめると次のような内容です。
- ローカルでもリモートでも開発体験が大きく変わらず、各自が自由に使える環境を整えた。
- TiltとGoogle Cloud Workstationsという2つの技術をベースに構築した。
- PCとリモートマシン間の通信について試行錯誤を重ねた。
こんな開発体験です
まずは、HRBrainのエンジニアが日々どのように開発しているのかをご紹介します。
HRBrainのシステムはマイクロサービス構成になっており、エンジニアは自分専用の環境で開発を行います。開発したいサービス(および、それが依存するサービス)を立ち上げ、実装や動作確認を進める流れです。
サービスを立ち上げるマシンは、次の2つから自由に選択できます。
- 自分のPC(以下、
ローカル環境
と呼ぶ) - 一人一台配布されるリモートマシン(以下、
リモート環境
と呼ぶ)
イメージとしては、次のような形になります。
エンジニアが好きに選べる開発環境のイメージ図
Tilt と Google Cloud の Cloud Workstations の2つの技術をベースに、開発体験の認知負荷を下げる工夫を加えて実現しています。
ローカル環境とリモート環境、それぞれの開発フローを紹介します。
リモート環境のほうが若干手順は多いものの、どちらの環境でも開発体験に大きな差はありません。
まずはローカル環境から説明します。
(説明の都合上、サービスの起動が②番から始まっていますが、その点は一旦無視してください🙏)
ローカル開発の場合
ローカル開発のフロー
すべてエンジニアのPC内で実行されており、特別なことはしていません。
まず、サービスを立ち上げます。コードを編集するとホットリロードで即時反映されるため、ブラウザで http://localhost:{port}
のようなURLを指定して動作確認する、という流れです。
エディタは VSCode や JetBrains系、Vim を使うエンジニアが多いです。
リモート環境では、次のような流れになります。
違いは、①番の 「リモートマシン準備」 が追加される点のみで、それ以外の操作は基本的に同じです。
リモート開発の場合
リモート開発のフロー
まず、リモートマシンを起動し、SSHトンネルを張ります。その後、SSHでリモートマシンに接続し、ターミナルからサービスを起動します。
ソースコードとサービスはリモートマシン側にあるため、エンジニアのPCのリソース負担が軽減されます。
エディタは、SSH経由でリモート編集が可能なものであれば、自由に選べます。VSCode や JetBrains系 は対応しており、SSHトンネルで接続したターミナルから Vim を使用することも可能です。
サービスはリモートマシン上で動作していますが、SSHローカルポートフォワーディング を活用する(詳細は後述)ことで、ブラウザからはローカル環境と同じURL(http://localhost:{port}
)でアクセスできます。
そのため、環境ごとに異なるURLを使い分ける必要はありません。
好きな開発環境を選べる一方で、操作性のギャップが少ない開発体験を実現しています。
リモート環境の便利な点😄・不便な点😢
一年ほど運用してきた中で、この開発体験の便利な点と不便な点が見えてきました。それぞれ紹介します。
便利な点
-
😄 ローカル環境が重くなったら、すぐにリモート環境へ切り替えられる。
PRを1つ作成するだけでリモートマシンが配布される仕組みにしたため、希望者は簡単に利用を開始できます。(詳細は後述)
-
😄 動作確認時にURLを使い分ける必要がなく、認知負荷が低い。
ローカル環境でもリモート環境でも、同じURL(
http://localhost:{port}
)で開発中のサービスにアクセスできるため、環境ごとにURLを切り替える手間がありません。 -
😄 リモート環境の調子が悪くなっても、すぐにローカル環境へ戻れる。
操作性がほぼ同じなので、通信障害などでリモート開発が難しくなっても、作業中のコードをプッシュしておけば、すぐにローカル環境で作業を再開できます。
-
😄 Docker Desktop for Mac を常駐させる必要がなく、PCのバッテリーに優しい。
開発をしながらミーティングで離席することも多い弊社CTOのケースですが、Docker Desktop for Mac を起動しなくて済むことで、充電器を持ち歩く頻度が減ったそうです。
不便な点
-
😢 ローカルPCとリモートマシン間の通信が不安定になることがある。
MacBookを閉じるとリモートマシンとの接続が切れるなど、SSHトンネルを張り直す必要がある場合があります。(とはいえ、コマンド一発で復旧できるため、それほど大変ではありません)
-
😢 エディタの補完やサジェストが遅くなることがある。
ソースコードがリモートマシン側にあるため、インデックスの作成が完了するまで補完のサジェストの表示に時間がかかることがあります。
不便な点については、弊社特有の課題というより、リモート開発全般で発生しがちな問題が多い印象です。
なぜリモート開発に乗り出したのか?
一言で言うと、サービスが増えるにつれ、依存するものに絞ってもPCの負荷が高くなってしまったからです。
もう少し詳しく経緯を説明します。
HRBrainでは、2021年に「秘伝のシェルスクリプト」から「Tiltをベースとした開発環境」へ移行しました。
これは、依存関係の管理やホットリロードの問題を解決するためでした。
このあたりの詳細は、以下のブログで紹介しています。よければご覧ください。
事業の成長に伴い、共通機能をサービスとして切り出したことで依存サービスが増加しました。
その結果、すべてのサービスを立ち上げるとPCの負荷が高くなり、無視できない問題になってきました。
[出典] マイクロサービスのローカル開発環境をTiltを使って2年運用して得た知見
■サービスをすべて立ち上げない方法はないの? 🤔
あると思います。ただ、設計や運用の見通しが立っておらず、実現に至っていないというのが正直なところです。
必要なサービスだけを自分で立ち上げ、それ以外は共用環境を利用したり、モックを活用したりするアプローチが考えられます。しかし、共用環境のデータが壊れても他の人に影響を与えない仕組みや、モックのメンテナンスを現実的な工数に収める設計が課題です。
今後さらにサービスが増えても破綻しないよう、引き続き模索はしていきたいと考えています。
どんな構成?
リモート環境の構成は以下のようになっています。
(Google Cloudのネットワークや認証に関する詳細は省略しています。Cloud Workstationsのアーキテクチャについては、公式ドキュメントをご参照ください。)
HRBrainのリモート開発環境の構成
ワークステーションについて
まずは、図の右側にある「ワークステーション」について説明します。
これまで「リモートマシン」と呼んでいたものが、このワークステーションに該当します。
Cloud Workstationsでは、「ワークステーション」という単位で実行環境を配布できます。実態は「VM + Dockerコンテナ」です。弊社では、希望者に専用のワークステーションを1つ割り当てています。
ワークステーションはコンテナを含むため、その基となるDockerイメージが存在します。Cloud Workstationsでは、いくつかのベースイメージが提供されています。弊社では、VSCodeに最適化された標準的な Code-OSS
イメージ をカスタマイズして使用しています。(カスタマイズの詳細は後述)
■ファイルの永続化について
ワークステーションでは、/home
ディレクトリが永続ディスクにマウントされるため、ワークステーションを再起動してもファイルは消えません。一方、それ以外のディレクトリはリセットされるため、永続化したいデータがある場合は、イメージのカスタマイズなどの工夫が必要です。
■ワークステーションへの通信経路
ワークステーションには、次の2つの通信経路があります。
-
VM Gateway経由(HTTPSアクセス)
例えば、ワークステーション内で
http://localhost:8080
で動作するサーバーに対して、外部からは以下のようなURLでアクセスできます。https://8080-{ワークステーションの識別子}.cluster-{クラスタの識別子}.cloudworkstations.dev/
また、Code-OSSイメージには、ブラウザで利用可能な VSCode Server が搭載されており、この経路を通じてアクセス可能です。
📝ワークステーションで実行されている HTTP サーバーにアクセスする | Cloud Workstations | Google Cloud -
SSH Server経由(SSHアクセス)
gcloud CLI の
gcloud workstations
サブコマンドを使用して、SSH接続やSSHトンネルを確立できます。
弊社では技術的な制約により、この経路をメインで使用しています。(詳細は後述)
📝SSH サポート | Cloud Workstations | Google Cloud
PCについて
次に、図の左側にある「自分のPC」について説明します。
自分のPCに必要なのは、「エディタ」「ブラウザ」「ターミナル」の3つだけです。PCとワークステーションの間にはSSHトンネルを張り、エディタはそれを経由してワークステーション上のファイルを編集します。いわゆるリモート開発の形態です。
また、動作確認を行う場合も、SSHトンネルを通じてリモート側のサービスにアクセスする仕組みになっています。
次章では、この構成をどのように構築したのか、そしてどのような課題があったのかを紹介していきます。
どうやって作っていったの?
リモート環境の大きな方針はGoogle Cloudの担当者と詰め、実際の構築や使い勝手の改善については、社内で試行錯誤しながら進めました。
リモート開発を検討し始めたタイミングで、ちょうど「TAP」というイベントでGoogle Cloudの方と議論する機会があり、そこで大きな方針を固めることができました。
TAP(Tech Acceleration Program)とは、Google Cloudのテクノロジーを活用し、内製化を支援するワークショップです。
参加レポートもありますので、興味のある方はぜひご覧ください。
環境の構築には、大きく3つのステップがありました。
- 各エンジニアにリモートマシンを配布する仕組みを作る。
- リモートマシン上で動作確認用のサービスを立ち上げられるようにする。
- ローカルPCからリモートマシンへの通信を、手間なく安定して確立させる。
1つ目は、Terraformをベースに、PR1行でリモートマシンの配布・廃棄ができる仕組みを用意しました。現在はPreview段階ですが、今のところ問題は発生していません。
📝TerraformによるCloud Workstationsのサポート
2つ目は、Tiltさえあればリモートマシン上でもサービスを立ち上げられるため、特に問題はありませんでした。
3つ目が最も試行錯誤した部分です。ここについては、もう少し詳しく紹介します。
ローカルPCとリモートマシン間通信の試行錯誤⚔️
■SSHトンネルの採用
構成の章でも触れましたが、Cloud Workstationsでは、VM Gatewayを介したHTTPSアクセスを提供しています。
しかし、弊社の環境では、この経路では設計上必要なCORSのPreflightリクエストが失敗するという問題が発生しました。
一方で、もう一つの経路であるSSHトンネルを使用したSSH Server経由であれば、問題なく通信できたため、こちらの方式を採用しました。
■通信の安定化とメモリ消費の改善
構築初期は、Cloud Workstations の SSH トンネルを必要なポート数分、個別に作成していました。
以下は、ポート8080に対してトンネルを張る例です。
$ gcloud workstations start-tcp-tunnel --local-host-port=localhost:8080 ...(省略)...
弊社の場合、この方式には次の2つの課題がありました。
- トンネルの通信がたまに切れて、安定性に欠ける。
-
1つのトンネルが約65MBのメモリを消費する。
各トンネルが独立した Python プロセスとして動作するため、その分メモリを使用します。たとえば、10ポート分のトンネルを張ると合計で650MBを消費し、無視できない負荷となります。
試行錯誤の結果、「gcloud の SSH トンネルを1つ使用し、SSH ローカルポートフォワーディングを組み合わせる」という方式に落ち着きました。
ローカルポートフォワーディングとは、SSH を利用してローカルマシンのポートをリモートサーバーのポートに安全にマッピングし、リモートサービスへアクセスする技術です。
この方式では、必要なポート数分だけフォワーディングを行うため、実験的な運用では通信が安定し、消費メモリも大幅に削減されました。具体的には、
Python: 65MB(gcloud SSH トンネル1つ) + ssh: 5MB未満
で合計70MB未満となり、メモリの負担が大幅に軽減されました。
改善前後のイメージです。
gcloudのSSHトンネルとローカルポートフォワーディングの関係性
その他の細かいTips💡
■ワークステーションのカスタムイメージを用意する
前述の「どんな構成?」でも触れたように、Cloud Workstationsでは/home
以外のディレクトリは永続化されません。そのため、特定のツールを必ず導入しておきたい場合は、ワークステーションのカスタムイメージを作成する必要があります。
HRBrainでは、以下の3つを実施するためにカスタムイメージを作成しています。
- Tiltをインストールする。
- タイムゾーンを
Asia/Tokyo
に設定する。 - 古いDockerイメージなどによる永続ディスクの容量圧迫を防ぐため、クリーンアップスクリプトを起動時に実行する。
カスタムイメージの作成方法については、公式ドキュメントを参照してください。
📝コンテナ イメージをカスタマイズする | Cloud Workstations | Google Cloud
また、公式ドキュメントに加えて、以下のハンズオン資料も参考になります。こちらも分かりやすく、おすすめです。
📝Cloud Workstations ハンズオン
■自動停止で運用コストを抑える
Cloud Workstationsには、リモートマシンが一定時間使用されなかった場合に自動でシャットダウンする機能があります。この設定を有効にすることで、不要な起動を防ぎ、運用コストを削減できます。
HRBrainでは、この自動停止の時間を2時間に設定しています。
■コマンド一発でリモートマシンを準備する
エンジニアは毎朝、「リモートマシンの起動 → SSHトンネルの開通」 という準備を行います。
毎日のことなので、できるだけ手間を減らしたいですよね。そのため、シェルスクリプトを作成し、コマンド一発で済むようにしました。(※事前に gcloud
コマンドで認証は必要です。)
シェルスクリプトの処理の流れは以下の通りです。
- gcloud コマンドで、自分のワークステーションを起動する。
$ gcloud workstations start ...
- gcloud コマンドで、SSHトンネルを1つ張る。(これがメインのトンネルとなる)
$ gcloud workstations start-tcp-tunnel ...
-
yqコマンド を使って
docker-compose.yaml
を解析し、ローカルポートフォワーディングするポート番号を抽出する。
(HRBrainでは、開発環境を Docker Compose で管理しています。)# YAMLファイルからポート番号を抽出する例 # 名前の末尾に"-front"と"-apidoc"が付くものと、"server1", "service2"という名前のサービスのポートを抽出する例 ports=$(yq eval '.services | to_entries | map(select(.key | test("-front$|-apidoc$|service1|service2")) | .value.ports[] | split(":") | .[0]) | .[]' docker-compose.yaml)
- 抽出したポートを使って、ローカルポートフォワーディングのためのSSH接続情報を作成する。
例:# Workstation内のコンテナにポートフォワードするSSHセッション設定 Host tiltforward HostName localhost Port 12345 # gcloudで通したSSHトンネルのポート User user # Workstationの制約である以下に対応するため、ホストの鍵検証を無効にする。 # see: https://cloud.google.com/workstations/docs/develop-code-using-local-vscode-editor?hl=ja StrictHostKeyChecking no UserKnownHostsFile /dev/null # 入力はしないフォワーディング専用セッションなのでnoneでよい SessionType none LocalForward 11111 localhost:11111 LocalForward 22222 localhost:22222 LocalForward 33333 localhost:33333 (...続く...)
- 作成した接続情報を ssh コマンドに渡し、ローカルポートフォワーディングを開始する。
$ ssh -F "{接続情報のパス}" -f {Host名}
このシェルスクリプトを用意しておくことで、エンジニアは毎朝コマンドを一発実行するだけで、リモートマシンの準備を簡単に完了できます🚀。
作るのにどれくらいかかったの?
環境構築にかかった工数を、前述のステップごとにまとめると、概ね以下のような時間がかかりました。
- 各人へのリモートマシンを配布する仕組みを作る。
→ 1人で数日 - リモートマシン上で動作確認用のサービスを立ち上げられるようにする。
→ 1人で1週間 - ローカルPCからリモートマシンへ、通信を手間なく安定して確立させる。
→ 1人で1.5ヶ月(試行錯誤でボツになった施策も含む)
■運用コストについて
ワークステーションのマシンタイプや永続ディスクのスペック、利用人数などによって、運用コストは変わります。
また、これに加えて管理に関するコストも発生します。
公式ページに料金の例や料金体系が掲載されているので、まずはそちらをご確認ください。
📝料金 | Cloud Workstations | Google Cloud
まとめ
HRBrainのリモート環境の導入・運用を通じて得られた知見を紹介しました。
社内にはローカル開発派とリモート開発派の両方がおり、それぞれが自分に合った環境で日々開発を進めています。
事業の成長に伴い、プロダクトは増え変化しています。同時に、エンジニアにとって開発環境は毎日使う重要なインフラの一つです。インフラも一緒に成長しないと老朽化して開発の負担になってしまいます。
HRBrainでは、ロードマップに沿った開発はもちろん、課題をきちんと伝えればサイドプロジェクトにも取り組める風土があります。今回のリモート環境の構築も、サイドプロジェクトとして進めました。
これからも成長は続いていくので、興味がある方はぜひ一緒に環境を作っていけると嬉しいです!
Discussion