リモート開発環境で何ができるのか

コロナウイルスの影響で、Indeedも3/3付で完全リモートワークに切り替えられた。

press.indeed.com

自分は会社支給の強いLinux laptopに全てを入れているので、開発環境に関しては特に普段と変わるところはない。しかし社内には、オフィスに置いてあるLinux workstationにsshして開発を続けている人もいるらしい。こういった環境構築系の話題は泥沼なので普段はあまり深入りしないようにしているけど、いいタイミングだしリモートに開発環境を置くということについて考えてみたいと思う。

この記事では純粋にプログラムを開発するために必要なセットアップについてだけ考える。物理的に一箇所に集まらない状態でチームとして動くテクニックの話はしないので、チームワークについて知りたい人はGitLab Handbookでも読んでください。

about.gitlab.com

リモート環境構築の動機と環境の再現性

そもそもなぜリモートに開発環境を構築したいのか。ざっくり考えると以下のようなケースが思いつく。

  • 自宅と会社等、2箇所以上で開発する必要があり環境を使いまわしたい
  • AWSとかで強力なサーバを借りて超並列ビルドをしたい
  • 強力なサーバを借りて巨大データをtry & errorでスクリプト書きながらいじりたい

このうち最初のケースでは常にサーバを立ち上げっぱなしにしておきたいが、他2つは必要に応じて立ち上げたり落としたりするワークフローが想定される。

1つめのケースは、会社に置いとけるワークステーションや、外からsshできる自宅サーバなどがあると考えると、基本的には一度構築した環境をずっと使い続けつつ、何かを更新したければその都度インストールしていけばいい。例外は新しくサーバを作った、HDDがクラッシュした等の理由でOSをクリーンインストールする時で、このときはゼロから環境構築をやりなおす必要が出てくる。強力なサーバを必要に応じて立ち上げる場合は、その都度ゼロから手動でセットアップするのは面倒なので、イメージファイルを作っておいたりAnsible等で環境設定をコード化しておくなどといった対応がほぼ必須になる。もちろん1つ目のケースでも、AnsibleのようなInfrastructure as Codeの枠組みで自動化すると管理しやすくはなる。

毎回Ansibleを走らせて環境を作るのではなく、必要なものを焼き込んだイメージを作っておくという方針だと、AMIのように構築済みの環境のスナップショットを直接サーバにマウントする方針と、DockerやVirtualBoxのように仮想マシンのイメージを作っておいてサーバ上で環境構築済みのコンテナやVMを走らせるという方針の2通りが考えられる。このどちらを選ぶかは、運用方針によってほぼ一意に決まるんじゃないかと思う。開発者1人につき1台EC2インスタンスを与えるという運用で開発環境が頻繁に変わらないなら前者がやりやすいだろうし、開発環境が頻繁に更新されたり、サーバ以外の環境(Windowsラップトップとか)でも開発環境を立ち上げたいという要望があるならVMイメージの方がいいだろう。

AMIやVirtualBoxのイメージの場合はともかく、Dockerの場合はイメージの管理が煩雑になる可能性がある。特定のアプリケーションのバージョンを上げることを考えると、おそらくDockerfileに手を加えることになるので、それ以降のコマンドはキャッシュが使えず全部やり直しになる。AMIやVMのスナップショットを配布する場合は、そのスナップショットから仮想マシンなりサーバなりを起動して、手動でアップデートを掛けることで部分的に更新するという戦略が可能になる。ただしこれも、何を変更したか管理するためにAnsibleやPuppetで管理したくなるだろうから、本質的には毎回Ansibleを走らせる方針と変わらなくなってくる。

リモート開発環境の使い方

リモート開発環境が構築できたとして、実際にはどう使うことになるのか。言語や抽象化レベルの違いによって多少の差異はあるだろうが、おおむね以下のような手順になると思われる。

  • ソースコードをローカルで編集する
  • 編集したソースコードをリモート環境にアップロードする
  • リモート環境でビルド・実行する
  • 出力結果等をローカルへコピーする(または何らかの手段でリアルタイムに共有する。たとえばDBの中身なら簡単にリモートアクセスできるし、Webサーバならブラウザでリモートマシンにアクセスすれば良い)
  • デバッガを使いたい場合、リモートでデバッガを走らせるか、ローカルのIDEからTCP経由でアタッチする

一連の操作をシーケンシャルに実行してくれるスクリプトIDEの補助無しでこの手順を踏もうとすると、間違いなくいくつかのポイントでハマるであろうことが容易に想像できる。

  • ソースコードをアップロードしないままビルド・実行してしまい、結果が予想と食い違う
  • 出力結果をローカルへコピーし忘れ、古いログを参照してしまう
  • ターミナルをローカルとリモートの2枚開いていて、間違った方でコマンドを実行しようとして失敗する
  • 普段はGUIのデバッガを使っているのに、リモートではCLIのデバッガしか使えない
  • リモートで動いているプロセスにアタッチできるようなGUIベースのデバッガが提供されていない

はじめの3つのポイントについては、これらを順番に実行するスクリプトを書いたり、IDEに任せることで解決できる。IDEの場合、たとえばIntelliJはRemote Developmentといって適切なタイミングでリモートにソースコードを送ってくれる機能があるし、VSCodeのRemote Development extension packはサーバ側とクライアント側の両方でVSCodeを走らせてそれらが通信することで、ローカルから透過的にリモート環境が触れるようになる。

www.jetbrains.com

code.visualstudio.com

後半2つに関しては、VSCodeはリモート側でもVSCode本体が動いているようなアーキテクチャなので、これも解決できるらしい(確認はしてない)。ローカルのVSCodeはリモートを操作するガワのように振る舞うので、ローカルのIDEでアクションをを実行するとリモートで実行されるが、使い勝手はあたかもローカルで全てが動いているかのようという都合の良いシステムになる。IntelliJにはこういう機能はない。言語によってはリモートで動いているアプリケーションにアタッチできるようなデバッガを提供していることがあるので、運良くそういう言語を使っており、GUIがリモートアタッチに対応していれば一応ローカル側のGUI経由でリモートのプロセスのデバッグはできる。IntelliJでは(少なくとも)Java, Go, Python, Ruby, Node.jsの各言語に対して、それぞれの言語が提供しているリモートデバッガを扱えるようになっているが、リモート側でプロセスが立ち上がってからアタッチする必要があるため、実行が高速に終わってしまうプロセスや、最初の方でちょっとだけ実行されるコード等ををデバッグするのは難しくなる(Java等ではプロセス側からリモートのデバッガに繋ぎにいくオプションもあるっぽい)。

また、IDEの利点として、ファイルツリーから特定のファイルだけを選んでテストを実行するといった機能がある。VSCodeでは(おそらく)普通に実行できる。IntelliJのようにリモート側でIDE本体を動かさない仕組みの場合は実装依存になるが、少なくともIntelliJの場合はテストの実行はIntelliJが走っている環境内でしか行えないため、リモート開発環境では使えない。自力で特定のテストケースだけを実行するようなコマンドを叩くことになる。また、IntelliJはテストを走らせた後にコードカバレッジをインラインで色つけ表示したり、テストの実行中にもブレークポイントを仕掛けて実行を止めたりできるが、こういった機能もリモート開発環境では使えないか、大幅に制限されることになる。

エディタがGUIである事にこだわらず、EmacsVim等でガリガリ開発できる場合には、エディタもリモート環境で起動することでローカル環境のことを一切考えずに開発することができる。この場合、間にネットワークが挟まることによる多少の不安定さを除けば、ローカル環境での開発と大差ない状況になる。全てがリモートで動いている状態でX転送やVNCを使うことでGUIも使えなくはないが、回線が細い場合は描画が遅くて使い物にならないと思う。

ローカルとリモートの住み分け

ローカルとリモートのそれぞれにどういったファイルやアプリケーションを置けばいいのかを考える。

リモート側は開発環境なので、当然コンパイラや各種ライブラリ、ビルドツール等が入る。DBやDockerなど、開発しているアプリケーションが依存しているアプリケーションもリモート側になる。構成によってはデプロイ用のツールチェインや秘密鍵もリモート側に置くことになるかもしれない(特に常設型の開発サーバを作る場合)。

一方のローカル側では、まずリモート環境に接続するための機構が必要になる。リモート環境がサーバやVMならssh, scp (rsync)と秘密鍵くらいが最低限必要になる。IDEを始めとしたGUIベースのアプリケーションを使いたい場合は、これも基本的にローカル側に置いておくことになる。逆に言えば、それ以外のアプリケーションはローカルで動かす必要がない。

リモート環境ではできないこと

リモートでどんなに高性能なマシンが用意できるとしても、本質的にリモートではできない、もしくはとても難しいタイプの作業も存在する。ここまでで何回か挙げたように、ネイティブGUI(ブラウザではないもの)の比重が大きいアプリケーションは、リモートで動かすことは困難なので、必然的にローカルで動かすことになる。分かりやすい例としては画像や音声の編集ソフトがある。

また、モバイルアプリや組み込みシステムなど、プログラムを動かすために別のハードウェアが必要な作業もリモート環境とは相性が悪い。多くの場合はプログラムを転送するためにLANやUSB、シリアルケーブル経由で接続する必要がある。開発用リモートマシンと自分が物理的に同じ家にいるならいいが、リモートマシンをクラウドに載せるには多少なりとも困難が伴うし、開発マシンから端末へデバッガをアタッチしたい時などはほぼ確実に有線での接続が求められる。

こういった作業が避けられない場合は、ローカルとリモートという分け方ではなく、その時手元にあるマシンだけで作業できるように環境を整えることが重要になる。

まとめ

  • 開発環境はAnsible等、Infrastructure as Codeで管理することがほぼ必須と言って良い。Playbookさえあれば、それを直接サーバに適用するかイメージの構築に使うかは好きにに選ぶことができる。
  • 開発プロセスは、ビルドや実行に関わるコマンドはすべてリモートで実行されることを念頭に置く必要がある。IDEによって差異が吸収される部分もある。特にVSCodeを使う場合、ローカル環境で閉じているときとほぼ同じ感覚で開発することができる(と思われる)。
  • ローカル開発環境にはリモート環境に接続するためのツール群と、GUIベースのアプリケーションを入れる。それ以外はすべてリモート環境に配置する。
  • GUI主体の画像いじりや、物理的な接続を要求されるモバイルアプリ開発はリモート開発環境と相性が悪い。そういう時は諦めて手元のマシンを増強したほうが良い。