Open15

CodeSandbox の Docker インテグレーション

hankei6kmhankei6km

CodeSandbox で Docker が使えるようになったとのメールが配信されてきた。

CodeSandbox はご無沙汰だったので少し試してみる。

hankei6kmhankei6km

どのような対応になるのか?

Sandbox[1] 内に .codesandbox/Dockerfile を配置すれば利用できるとのこと。

https://codesandbox.io/post/introducing-docker-support-in-codesandbox

This was a win already, but it did require some tinkering to make your local Docker config work in CodeSandbox. Well, not anymore! With this update, Docker works out of the box. Simply create a Dockerfile at .codesandbox/Dockerfile and we’ll make everything work, running exactly as it does on local!

ただし、Sandbox 全体がコンテナ内で動くわけではないらしい。

  • コンテナは Sandbox でターミナルを開くと作成される(すでに存在していればそちらが使われる)
  • コンテナは(Sandbox を利用する)ユーザー別に作成される
    • 1 つの Sandbox を複数ユーザーで使う場合、個別に作成される、と思う(未確認)
  • 各コンテナはネットワークなど共有している、らしい(ここも未確認)

ターミナルを開くとその中だけは exec で実行したシェル(zsh だった)に接続されるもよう。

https://codesandbox.io/docs/learn/environment/docker

Whenever you open a terminal, we check if there is a container running for your user. If it's not running, we start it, and we then start an exec session inside the container.

Every container does share the network, files and processes between each-other. This allows you to run an HTTP server in one container, and connect to it from another container.

細かいことをいうと Docker ではなく Podman を使っているとのこと。

While we call this a Docker integration, we use rootless Podman to run the containers.

脚注
  1. CodeSandbox では Git リポジトリを扱う用に(Sandbox とは別に)Repository がある。このスクラップではとくに明記しない場合 Sandbox は Repository も含む扱いとしている。 ↩︎

hankei6kmhankei6km

試してみる

すでに CodeSandbox のアカウントがあるなら、ブラウザーで下記のように操作すると Dockerfile を使える Sandbox を作成できる。

  • ブラウザーで CodeSandbox のダッシュボードを開く
  • 右上の Create をクリック
  • 開いたダイアログで Docker を選択し、一覧から何か選ぶ
  • しばらく待つと Sandbox が作成されて IDE で操作できるようになる

コンテナはターミナルを開くと作成されるということで ctrl + p でコマンドパレットを開き New Terminal を実行[1]

試しに Dockerfile を編集してから Rebuild and Start させると反映されていることは確認できた。

実際にコードを編集する場合ではどうなるのかななと、deno 用のテンプレートを使ってみた。

たしかにターミナル内では deno のコンテナなのだが、ウェブ IDE 側はそうではないのでエディターにエラーが表示された。この辺を調整する方法は不明。

脚注
  1. 日本語配列のキーボードを使ったからか ctrl + @ ではターミナルは開かなかった。 ↩︎

hankei6kmhankei6km

ライフサイクル

(Sandbox と Repository で違う可能性もあるが)Repository の場合は下記のような感じだった。

  • Branch を開くと Repository 開始の段階で新しいイメージが作成される
    • tasks.json はコンテナが作成されてその中で動く、と思う
  • ターミナルを開くとコンテナが利用できる
  • Sandbox がサスペンド(用語的に正しいかは不明)後に再接続した場合、イメージは再作成されない
  • ターミナルを開くとコンテナを利用できるが前回と同じものかは不明
    • ホスト名は同じで、/root/workspace が保持されているのも確認できるが同一コンテナかは不明

コマンドパレットから明示的に Rebuild and Restart Cotainers を実行した場合も(現時点では)上記のファイルなどは再利用できる(この辺は Codespaces と少し違うのかな)

Rebuild and Restart Cotainers を実行するとターミナルなどで使っていたコンテナは作成され、/workspace以外は保持されない(/root は保持されなかった)。
また、左上のメニューから Restart Branch を実行すると削除される。

hankei6kmhankei6km

どのような場合にコンテナが再利用されているか?

コンテナ ID(的なもの?) を使って確認した。

コンテナ内からコンテナの ID を調べる方法は下記を参考に /proc/self/mountinfo を利用する。
(cat /proc/self/mountinfo| grep overlay で確認している)

https://github.com/lucaslorentz/caddy-docker-proxy/issues/207
https://stackoverflow.com/questions/68816329/how-to-get-docker-container-id-from-within-the-container-with-cgroup-v2

  • tasks.json とターミナルは別のコンテナになる。

tasks.json について

  • タスクを複数回実行しても同じコンテナが使われる(タスク実行毎に破棄していないもよう)
  • 異なるタスク(tasks.json内で別のフィールドを作った)でも同じコンテナが使われる
  • VSCode から接続して VSCode 側で実行した場合も同じコンテナが使われる

ターミナルについて

  • ターミナルをすべて閉じて新規作成しても同じコンテナが使われる(コンテナがないときだけ新規作成するのはドキュメントにあったはず)
  • 複数のターミナルを作成しても同じコンテナが使われる
  • VSCode から接続して VSCode 側のターミナルを開いた場合も同じコンテナが使われる

Rebuild and Restart Cotainers を実行した場合は別のコンテナになる。

hankei6kmhankei6km

Dockerfile の変更(イメージ作成)

結構注意点がある。

ベースにできるイメージは debianubuntu

現時点はベースとなるイメージは debianubuntu を使う方が良いとのこと。ビルドのログを見るとユーザーの Dockerfile とは別にツールをインストールしているようなのでその辺の関係かと。

At this time, CodeSandbox currently only supports Debian and Ubuntu based images, for the best compatibility and user experience.

キャッシュ保存に利用できる容量は少ない

イメージのキャッシュは Sandbox に割り当てられているディスクから普通に使われる。Personal Free プランだと 4G 6G となる。また、明示的にキャッシュを消すコマンド(docker system prune 的なもの)は見当たらなかった。

よって、テンプレートなどから Sandbox を作成して DockerfileFROM を変更すると容量不足のエラーになりやすい。

この辺は Dockerfileはある程度作っておいて Sandbox 作成時に指定することで対応している(GttHub のリポジトリから読み込んで作成するなど)。

ユーザーは root でないと厳しい

コンテナ内にマウントされる workspace(/workspace) は root:root になっている。

ドキュメントなどでは「コンテナ内は root が使えるぜ」的なことが書いてあるが、どちらかと言うと root でないと厳しい。

その他

Sandbox 側からコンテナ側に結構介入しているぽい。きちんとは調べていないが /root の下などはコンテナ開始時にそこそこ手が入っているように見える。

hankei6kmhankei6km

再ビルド

ビルド時に docker build--no-cache--pull のようなことはできなさそう。
Dockerfile は変わっていないのだけど、パッケージが更新されているはずなので再ビルドしたい」ということはできないかな。

hankei6kmhankei6km

VSCode から利用(はそのままだと難しいかな)追記 2023-01-27

前提として、CodeSandbox はウェブの IDE とは別に VSCode の拡張機能から Sandbox を利用できる。これはどちらかというと CodeSpaces(DevContainers) ではなく Remote SSH ベースとなっている[2]

では、今回の Docker インテグレーションはどのような使い方になるかというと。

  • 接続した時点では Sandbox をホストとした Remote - SSH のような扱い
    • 通常の手順でターミナルを開くと コンテナではなく Sandbox の環境になる
    • 拡張機能なども コンテナではなく Sandbox 側の環境で動く
  • コマンドパレットから CodeSandbox のターミナルを開くとコンテナの環境になる

ということで、たとえば Rust の環境を作ろうとしても VSCode 側でインストールした Rust Analyzer 拡張機能はコンテナ側で動作しないのでうまく動かない。またターミナルから code src/main.rs のようにやってもエラーになる。

Sandbox では nix が使えるので頑張れば動かせるかもしれないが、それはあまりやりたくない。

脚注
  1. きちんと比較できるメモがなかったので推測だが、コンテナ内に(podman のソケットなど)以前にはなかったものがいくつかマウントされるようになったなどの変化がある(そんなおもしろそうなものがマウントされてたら印象に残っているはず、たぶん)。 ↩︎

  2. Remote - SSH はホスト側の Docker を使って Dev Container を作るこもできる。CodeSandbox でもその方法を利用できるかは別のスクラップで試している↩︎

hankei6kmhankei6km

VSCode について追記 2023-01-27

CodeSandbox 拡張機能は 0.2.67 で試している。

通常の手順でターミナルを開いたときも Docker 環境になった。また、まだきちとした確認はできていないが、拡張機能などもコンテナ側で動くもよう。Rust も拡張機能をインストールしたら普通に動いた。ただし、最近 Rust がサポートされたのでその影響も捨てきれない(何か別の方法で検証がしたいが、どうやる?)。

2023-02-09 追記: 拡張機能もコンテナ側で動いていた(というか Remote SSH の接続先がコンテナ側になっている)。

  • コンテナ側の $HOME の下に .vscode-server ができている
    • CodeSandbox 側が何かしているのでなければ、接続先はこのサーバーのインスタンス
  • Nix IDE 拡張機能は nixpkgs-fmt を外部コマンドとして実行する
    • コンテナ側だけにダミーの nixpkgs-fmt を置いて実行されるのを確認できた

ということで、普通に VSCode から CodeSandbox に接続して Rust のデバッグしているスクリーンショット。

なお、ワークスペース別に拡張機能をインストールする方法として、以前のコメントではターミナルからスクリプトを動かしていたが、下記のような方法もあった。

https://codesandbox.io/docs/learn/repositories/editors#default-workspace-extensions

これで、Dev Container に近いノリで使えそう。

あとは、1 良くない点があった。VSCode から CodeSandbox のブランチを直接作るとコンテナが開始されないぽい。これは、いったん dashboard でブランチを開けば大丈夫そう。

hankei6kmhankei6km

それでも VSCode から使いたい場合

Remote - Tunnels を使うとある程度は解決できた。下記のリポジトリは Rust 用の環境設定しつつ、code server で外部から接続できるようになっている。

https://github.com/hankei6km/test-csb-docker-rust

Sandbox の機能は使えなくなるが、拡張機能やターミナルはコンテナ内の環境で動くので、Remote - Tunnels(code server)をコンテナで使っているようなノリで使えている。

Sandbox のコンテナに接続している VSCode で、Rust Analyzer から想定通りの警告が表示されているスクリーンショット

Sandbox のコンテナに接続している VSCode で Rust のソースファイルをデバッガーで扱っているスクリーンショット

ただし、VSCode 側からの操作では Sandbox のサスペンドは回避できないので気を抜くとサスペンドしてしまう。

作り方は下記の通り

code-server

Dockerfle で code の CLI 版をインストールして動かすだけ、なのだが少し注意点があった。

最初は実行ファイルを /usr/local/bin/code へ配置したのだが、これだと Sandbox 側から別のファイルを上書きされてしまう。今回は /usr/local/bin/code-cli として配置した。

サーバーとして動かしたいので tasks.json でタスク化したくなるが、これをやると他のユーザーが Sandbox へ接続するとログが丸見えになってしまう(デバイスを登録するときの文字列などが見えるのはよろしくない)。

しかたがないので、面倒だけど手動でターミナルを開いてコマンドを実行している。

拡張機能

インストールしたい拡張機能を Sandbox 側から指定できない、と思う。とりあず下記のようなスクリプトを配置しておき、新しいサーバーを利用する場合に VSCode 側のターミナルで手動実行することにより対応している。なお、Sandbox 側のターミナルや tasks.json で実行してもインストールできない[1]

#!/bin/bash

code --install-extension "rust-lang.rust-analyzer"
code --install-extension "vadimcn.vscode-lldb"
code --install-extension "esbenp.prettier-vscode"
脚注
  1. 昔は VSCode の外側からでもインストール先ディレクトリーを指定すればできた記憶があるけどこの辺は厳しくなった? ↩︎

hankei6kmhankei6km

GitHub の認証情報

コンテナ内でも /root/.githubtoken にトークンが保存されている。ブランチのプッシュなどはこれを利用して行われるので、SSH の鍵などを設定する必要はない、と思う。

[credential]
        helper = "!f() { echo \"username=$(cat ~/.githubtoken)\npassword=\"; }; f"

また、GitHub CLI でも export GITHUB_TOKEN="$(cat ~/.githubtoken)" のように環境変数を設定することで利用できた。が、やってよいのかは不明。

あと、少し気になったのは、トークンをローテーションしていなさそうなところ。時間を空けて確認してみたが変化しなかった(とりあえず「24 時間は同じ値だった」というのは確認している)。よって、漏れたら怖いので取り扱いは要注意。

hankei6kmhankei6km

Dev Containers は?

https://codesandbox.io/blog/introducing-docker-support-in-codesandbox

We are also actively working on getting full support for Docker Compose, so you can easily build any full-stack project in CodeSandbox. This will most likely ship next week.

Oh, and did anyone say Dev Containers? 🤔 Stay tuned!

ということらしいので、期待してもよいのかな?

できれば Codespaces のノリで使えるようになるとうれしいけど、やはり(devcontainer-cli を使うような感じで)ターミナルの中だけかな?

hankei6kmhankei6km

とりあえずの感想

イメージの作り方が少し面倒なのと、コードを編集する場合にターミナルの中だけコンテナ環境というのはちょとクセが強いかなという印象。

どちらかというと、コード編集には直接関係しない(フォーマッターなどではない)開発用の DB サーバーとか動かしておくとよいのかなと。

あとは、tmux + vim のようなターミナル内で完結するような環境を作るという感じかな(ウェブ IDE があるのにそれはどうなのよ、という気もするけど)。

なお、Remote - tunnles(code server)を使う方法はそれなりに利用できそうかなと思っていたのだが、Dev Containers を使う方法もあるのでそっちを使うかなという感じである。

hankei6kmhankei6km

感想追記

VSCode ではターミナルだけなく IDE 側でもコンテナの環境になったので、感想がかなり変わる。

コンテナの扱いにいくつか注意点は必要だが、 DevContainer に近いノリで使える。

  • (プランにもよるけど)大きなイメージをどっかんすっかんするのには向いていない
  • リモートユーザー(コンテナ内のユーザー)は root でないと厳しい
  • rootless podman を意識しないとけいない(rootless というのがめんどう)
    • コンテナ内からコンテナを使えるがおそらく outside of の構成、よってワークスペースをマウントするのが面倒など

個人的には「ちょっとした実験やワークフローの変更なら CodeSandbox で良いかも」という感じになってきている(そして実際に少し使ってみているが、わりと良い感触だったりする)。

このような感じなので、Dev Containers サポートがあるならかなり期待したくなる。
(逆に言うと、いまの時点で CodeSandbox 用のイメージとか作らない方がよいのかなと、
少し悩んでしまう)

hankei6kmhankei6km

ブランチとコンテナとイメージ

CodeSandbox のブランチは VM の環境をまるごと分岐させる、と思う。
イメージ的には Codespaces でブランチ別の Codespace を作る感じ(CodeSandbox のブランチはその辺を自動化し、環境のコピーも結構はやい)。

そして、これはかなり徹底しているようで、(おそらく)コンテナも分岐元からコピーされている。(※同一のインスタンスは再度確認すること)

Dockerfile を変更して再ビルドするとイメージはブランチ内で保存される(分岐元との共有はなさそう)。よって、ブランチごとに df をとると下記のようになる。

main ブランチ

➜  /workspace git:(main) df -h
Filesystem      Size  Used Avail Use% Mounted on
fuse-overlayfs  6.2G  1.7G  4.2G  29% /
tmpfs            64M     0   64M   0% /dev
tmpfs           2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/vdc        6.2G  1.7G  4.2G  29% /workspace
shm             2.0G  252K  2.0G   1% /dev/shm
overlay         3.9G   11M  3.7G   1% /etc/hosts
overlay         3.9G   11M  3.7G   1% /usr/local/bin/lsp
run             2.0G   88K  2.0G   1% /run/podman/podman.sock
tmpfs           2.0G     0  2.0G   0% /sys/kernel
tmpfs           2.0G     0  2.0G   0% /proc/scsi
tmpfs           2.0G     0  2.0G   0% /sys/firmware
tmpfs           2.0G     0  2.0G   0% /sys/fs/selinux
tmpfs           2.0G     0  2.0G   0% /sys/dev/block

Dockerfile を変更して再ビルドしたブランチ

➜  /workspace git:(draft/sweet-cookies) ✗ df -h
Filesystem      Size  Used Avail Use% Mounted on
fuse-overlayfs  6.2G  3.8G  2.0G  66% /
/dev/vdc        6.2G  3.8G  2.0G  66% /workspace
tmpfs           2.0G     0  2.0G   0% /sys/fs/cgroup
tmpfs            64M     0   64M   0% /dev
overlay         3.9G   11M  3.7G   1% /etc/hosts
shm             2.0G  252K  2.0G   1% /dev/shm
overlay         3.9G   11M  3.7G   1% /usr/local/bin/lsp
run             2.0G   92K  2.0G   1% /run/podman/podman.sock
tmpfs           2.0G     0  2.0G   0% /sys/kernel
tmpfs           2.0G     0  2.0G   0% /proc/scsi
tmpfs           2.0G     0  2.0G   0% /sys/firmware
tmpfs           2.0G     0  2.0G   0% /sys/fs/selinux
tmpfs           2.0G     0  2.0G   0% /sys/dev/block