EC2インスタンスへ接続するsshクライアントとして VSCode Remote SSH を利用した時にサーバーがビジーになる現象の考察

# はじめに

VSCode はエディタであると同時に、非常に拡張性の高いツールでもある。 その中には、VSCode 上でサーバーへ SSH 接続を行い、サーバー上のコンテンツを編集することも可能な拡張機能 (Remote SSH) も存在する。

https://code.visualstudio.com/docs/remote/ssh

https://blog.serverworks.co.jp/tech/2020/02/20/vscode-remote-ssh/

記事を見ると単に「便利」とだけ記述されているが、実際に使ってみるとサーバー運用者としてみるとよろしくない仕様・挙動を示したため、それらの調査結果を書き記す。

# TL; DR

- 接続先サーバーが貧弱な場合 (AWS だとメモリが2GBもない場合は特に) は利用を推奨しない
    - 接続先サーバーに複数の `node` プロセスを立ててリソースを消費する
    - 接続元が切断してもサーバー上に `node` プロセスが残ったままになる (正常な状態であれば接続が切れて 5-10分後ぐらいに `node` プロセスはダウンする模様)
- 接続先サーバーが FUSE などの遅いディスクIOを持っている場合に利用を推奨しない
    - クライアント側にディレクトリ構造を渡すために、接続時にディレクトリ以下を一通り読み込むため、FUSE部分などを読み込んでしまうと非常に遅い処理が長い間走り続ける
    - [goofys](https://github.com/kahing/goofys) で S3 をマウントしている場合などが考えられる
    - ドキュメントルート以下に FUSE 部分をシンボリックリンクしていても、VSCode側で読み込んでしまう
- EC2 インスタンスに対して ssh 接続利用した場合の利用では EBS のバーストクレジットを使い切ってしまい、サーバーが正常に動かなくなることがある
    - 正確には I/O が 1/10 ぐらいの速度になるので、これまでの速度で処理ができなくなり、正常に動作していないように見える
    - ssh が接続できないだけでなく EC2 シリアルコンソールを使ってもログインすら難しい状態になる
    - EBS のクレジットが足りない状態なので、サーバーを停止⇒起動すれば収まる（クレジットが復活する）


# VSCode Remote SSH の仕組み

## サーバープロセス

VSCode Remote SSH の特徴として「サーバーコンテンツをローカルに保持せず、直接 VSCode を用いて編集できる」ということが挙げられる。 そのため、この仕組みを使ってサーバー上のコンテンツを編集する場合、逐一ローカルにファイルをコピーしてきて編集し、それをアップロードする必要はない。

では、これはどのような仕組みで実現されているかというと、以下の様な方法で実現されている。

- **リモートサーバー側に `node` で動作するサーバーを立てる**
    - 初回の接続設定時に `node` バイナリや必要なコンテンツをダウンロードして展開する
    - 個人的にはこの時点で使う気力をなくした……
- ローカル -> リモートを ssh トンネリングで接続する
- ssh トンネリングを通してデータをやり取りして、VSCode 側ではファイルがあたかもローカルに存在するように操作ができるようにする

ssh で接続して `ps` で動作プロセスを見てみると、以下の様なプロセスが走っていることが確認できる。

```
ec2-user  2804     1  0 04:50 ?        00:00:00 sh /home/ec2-user/.vscode-server/bin/7f6ab5485bbc008386c4386d08766667e155244e/server.sh --start-server --host
=127.0.0.1 --enable-remote-auto-shutdown --port=0 --connection-secret /home/ec2-user/.vscode-server/.7f6ab5485bbc008386c4386d08766667e155244e.token
ec2-user  2811  2804  0 04:50 ?        00:00:09 /home/ec2-user/.vscode-server/bin/7f6ab5485bbc008386c4386d08766667e155244e/node /home/ec2-user/.vscode-server
/bin/7f6ab5485bbc008386c4386d08766667e155244e/out/vs/server/main.js --start-server --host=127.0.0.1 --enable-remote-auto-shutdown --port=0 --connection-secre
t /home/ec2-user/.vscode-server/.7f6ab5485bbc008386c4386d08766667e155244e.token
ec2-user  2880  2811  0 04:50 ?        00:00:03 /home/ec2-user/.vscode-server/bin/7f6ab5485bbc008386c4386d08766667e155244e/node /home/ec2-user/.vscode-server
/bin/7f6ab5485bbc008386c4386d08766667e155244e/out/bootstrap-fork --type=ptyHost
ec2-user  3167  2811  0 04:56 ?        00:00:08 /home/ec2-user/.vscode-server/bin/7f6ab5485bbc008386c4386d08766667e155244e/node /home/ec2-user/.vscode-server
/bin/7f6ab5485bbc008386c4386d08766667e155244e/out/bootstrap-fork --type=extensionHost --uriTransformerPath=/home/ec2-user/.vscode-server/bin/7f6ab5485bbc0083
86c4386d08766667e155244e/out/vs/server/uriTransformer.js
ec2-user  3178  2811  0 04:56 ?        00:00:03 /home/ec2-user/.vscode-server/bin/7f6ab5485bbc008386c4386d08766667e155244e/node /home/ec2-user/.vscode-server
/bin/7f6ab5485bbc008386c4386d08766667e155244e/out/bootstrap-fork --type=watcherService
```

## 接続時に発生する高負荷

例えばこちらの記事でも説明してくれているが、SSH 接続を行った直後に様々な初期化が行われてサーバーそのものに非常に大きな負荷がかかる。

https://zaki-hmkc.hatenablog.com/entry/2021/01/06/230727

今回用意したサーバーは t3a.nano とわざとスペックの弱いサーバーを用意したが、接続後に `rg` や `node` で高CPU/高メモリ利用が確認できた。

![SnapCrab_NoName_2021-10-4_13-15-36_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/9042b1ae-f380-efc2-3b40-8ae1e9ef6fe0.png)
![SnapCrab_NoName_2021-10-4_13-16-23_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/b8d6272c-5aa2-eebc-c55d-7f444a0aa9ea.png)


## サーバーの `/` 以下を読み込んでみる

Remote SSH では接続先のパスを指定できるが、挙動を見ていると接続先パス以下の全てのファイルパスを取得してくるように見える。 それを踏まえて `/` を指定し、手元の VSCode 側で何度か「再読み込み」をかけてやるとサーバー側に簡単に過負荷をかけることができる。

スワップ未使用の t3a.nano (メモリ512MB) の場合、しばらく置いておくとディスクI/Oに過負荷がかかる状態になって制御不能に。 t3a.micro (メモリ1GB) + スワップ512MB の場合、接続直後から `node` プロセス (下図の PID: 3293) のメモリ利用率が上がっていき、最終的にはシェル上でのコマンドも `-bash: fork: Cannot allocate memory` として実行できなくなってしまった。 さらに待つとこのプロセスは終了し、VSCode 側も「再接続できません」というエラーメッセージを発して、サーバー側プロセスは破棄されるが、ちょっとした接続で想定する以上のメモリが利用されることは間違いない。

![SnapCrab_NoName_2021-10-4_17-15-25_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/a380dd3d-3757-412e-b5cd-04fb713c6a34.png)


## goofys (FUSE) を利用している場合

ここでは、[goofys](https://github.com/kahing/goofys) を使って `/mnt/s3` に S3 バケットをマウントしているものとする。  先に述べた通り、VSCode Remote SSH は接続先のディレクトリ以下全てを事前に読み込もうとするので、`/mnt/s3` 以下のパスが通っている or シンボリックリンクが貼られている場合はそのコンテンツを読み込もうとする。

しかし、FUSE は一般アクセスに比べるとかなり低速である。 goofys の場合、ファイルの R/W そのものは早いかもしれないが、ここで行われているのは `ls` 相当なので、体感、かなり遅い。

![SnapCrab_NoName_2021-10-4_17-31-41_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/95e04798-930c-a840-64c1-429971177f83.png)

上記は t3a.micro + 512MB Swap の環境で `/mnt/s3` 以下に VSCode でアクセスした場合の EBSのアイドルタイムであるが、S3 マウントされた領域に接続した 10/4 08:26 UTC 以降はビジー状態が続いている。 今回は5分後に goofys プロセスが OOM Killer によってキルされたため、ディスクI/Oは正常に戻った。

その後、SWAP を 2GB に増やして再度同様のオペレーションを行うと、同様にビジーが発生した (10/4 08:40 頃)。 青線がボリュームのアイドルタイム、オレンジがEBSのクレジットであり、これが0になるとディスクI/Oが極端に遅くなる。 
原因は SWAP への書き込みが考えられるが、この時、インスタンスそのものの CPU 利用率は低いままになっている。 今回もスワップが低い場合と同様だが、約10分で OOM Killer により goofys がキルされた。

![SnapCrab_NoName_2021-10-4_17-48-34_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/02c01001-e641-8fb6-b110-949dd33699b2.png)

これをさらに続けてクレジットをなくすと、以下の様になる。

![SnapCrab_NoName_2021-10-4_18-8-45_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/94d9cf8a-bcf0-d197-da3a-49405b1969bc.png)

この状態になってしまうと、サーバーに対する ssh / EC2 Serical Console も入力がビジーとなり、なかなか入力を受け付けなくなってしまう。 手っ取り早く復旧するには、サーバーを停止⇒起動すればよい。 ただし、停止はサービスの停止が反応しないため、５分での AWS 側の強制終了になる。

# まとめ

何度か試してみたが、完全な再現条件の特定・手法の確立はできなかった。 ただ、VSCode で情報を更新しようとした場合、並列にパスを読み込もうとした場合に一気に負荷がかかるような挙動をするように思える。 慎重に使えばよいと思うかもしれないが、そういった爆弾込みのツールを使いたいとは個人的には思わない。

以上を踏まえて、接続先サーバーのスペックが低い場合は VSCode Remote SSH の利用はおすすめできない。 もちろん、詳細な設定をすれば回避は可能かもしれないが、潜在的に問題を引き起こしかねない、という意味で。