vscodeのRemote Containers環境を真面目に構築してみる
現在windowsでRemote WSLを使っており、そのwsl環境はansibleでガンガン管理されている。
これはこれで完成しているのだが、のちにくるGithub Codespacesなどを思うとリポジトリごとのdevcontainerだったりdotfilesだったりをコンテナ寄りに整備したほうが良いかもしれない、という思いつき。
そもそもRemote Environmentsが登場した当初、なぜcontainersではなくWSLを選択したかというと、
- 開発系のファイルなどWindows側に置きたくない(全部WSLなどに置きたい)
- wslの中のdockerfileなどを読むような感じでremote containerができなそうで、ファイル群はwindows側に置くしかなかった
ため。
今どうなんだろう。
何も知らない顔をして、wsl内に適当なworkdirを作成し、そこからcode .
する。
コマンドパレットにRemote-containers: Add Development Container Configuration Files
があるので選ぶ。
環境などを選ぶので、webアクセステストとかもしたいということでRailsを選び、Ruby2.7を選んでおく。
なんか.devcontainer/devcontainer.json
が生える。
「フォルダにDev Containerの設定あるけどそれで開くか?」と問われるので、Reopen Containerしておく。
と、なんかDocker buildっぽいのが走る。Dockerfile指定したっけ?と思ったが、.devcontainer/Dockerfile
が生えていた。どうもdevcontainer用のrubyイメージを指定しているもよう。
...と、よく見たらruby versionが2
にしか見えない。2.7を指定したのだが。default
を解釈されたのかな。
なんかterminalの処理が止まったので、readyっぽい。
ログを見ると非常に色々と流れているが、流し読みすると
- ところどころにwslコマンドが見える -> wslからdevcontainerはちゃんといけるっぽい
-
/home/vscode/.vscode-server/*
みたいなパスがいくつも見える -> コンテナ内でそんな感じのパスが切られているっぽい。wslもそんな感じだしな。 - gnupgをなんかしてるっぽい。wsl側とコンテナ側両方であれこれ
- wsl側の
$HOME/.ssh/known_hosts
と$HOME/.gitconfig
がコンテナ側にコピーされている - vscode拡張がコンテナ側でインストールされている。この場合はrebornix.Ruby
...ぼくのgitconfig、$HOMEのやつは~/.config/gitconfig_body
をincludeするだけのやつなんだけど...まぁあとで考えよう
vscodeのterminalを増やすと、見たことのない(つまりコンテナ内の)shellが起動する。
Dockerfileによるとgem install rails
されているとのこと。
vscode ➜ /workspaces/devcon $ rails new --help
Usage:
rails new APP_PATH [options]
Options:
[--skip-namespace], [--no-skip-namespace] # Skip namespace (affects only isolated engines)
[--skip-collision-check], [--no-skip-collision-check] # Skip collision check
-r, [--ruby=PATH] # Path to the Ruby binary of your choice
# Default: /usr/local/bin/ruby
-m, [--template=TEMPLATE] # Path to some application template (can be a filesystem path or URL)
-d, [--database=DATABASE] # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
# Default: sqlite3
...
なるほど。
※devcon
はwsl側に切った作業用ディレクトリの名前
vscode ➜ /workspaces/devcon $ ll /home/vscode/
total 72K
-rw------- 1 vscode vscode 140 Dec 30 08:40 .bash_history
-rw-r--r-- 1 vscode vscode 220 Apr 18 2019 .bash_logout
-rw-r--r-- 1 vscode vscode 3.4K Dec 18 17:32 .bashrc
drwx------ 1 vscode vscode 4.0K Dec 30 08:35 .config/
drwxr-xr-x 3 vscode vscode 4.0K Dec 18 17:32 .gem/
-rw-r--r-- 1 vscode vscode 294 Dec 30 08:35 .gitconfig
drwx------ 2 vscode vscode 4.0K Dec 30 08:35 .gnupg/
drwxr-xr-x 1 vscode vscode 4.0K Dec 18 17:32 .oh-my-bash/
drwxr-xr-x 12 vscode vscode 4.0K Dec 18 17:32 .oh-my-zsh/
-rw-r--r-- 1 vscode vscode 17 Dec 30 08:39 .osh-update
-rw-r--r-- 1 vscode vscode 807 Apr 18 2019 .profile
drwxr-xr-x 1 vscode root 4.0K Dec 30 08:39 .rbenv/
-rw-r--r-- 1 vscode vscode 39 Dec 30 08:39 .rvmrc
-rw-r--r-- 1 vscode vscode 0 Dec 30 08:46 .sdirs
drwxr-xr-x 2 vscode vscode 4.0K Dec 30 08:35 .ssh/
drwxr-xr-x 6 vscode vscode 4.0K Dec 30 08:36 .vscode-server/
-rw-r--r-- 1 vscode vscode 3.5K Dec 18 17:32 .zshrc
ふむ。なんかいっぱい入ってる。oh-my-bashとか初めて聞いた。
zsh系も色々用意されていますが、ぼくはfish派です。
vscode ➜ /workspaces/devcon $ cat ~/.gitconfig
[include]
path = ~/.config/gitconfig_body
[user]
email = xxx
name = xxx
[credential]
helper = "!f() { /home/vscode/.vscode-server/bin/SOME_LONG_HASH_STRING_1/node /tmp/vscode-remote-containers-SOME_LONG_HASH_STRING_2.js $*; }; f"
[credential]
セクションが追加されている。詳しく追っていないが、ホスト側のcredentialでいい感じにやるヘルパーなのだろうきっと。
適当にrails new
してbundle exec rails server
すると見慣れたbootingログが流れる。
するとvscodeの通知が。
そうやな。確かにポート開ける設定してなかったな。forwardPortsみたいな設定がコメントアウトされてるのは見たけどスルーしてたわ...ってあれ?何もしてないけどホスト側にポートフォワードされてんぞ?
See all forwarded portsを押すとコンテナ系のステートが出るサイドバーが出現するが、PORTSのところに確かに3000がフォワードされていると書いてある。
devcontainerには特に何も書いてないし、変更されてる気配も無い。
これつまりコンテナ内のport bindingを検知して全部いい感じにやるってことか?つよない?
よく見たらforwardされてると下になんか出てる。
routesとcontrollerを適当に書くとちゃんとブラウザ側も更新されることを確認。この辺は普通のrails開発。
なおコードなどのファイル群(devconディレクトリ下)はホスト側からのマウントなのでそっちに実体がある。
なおvscodeから普通にファイルをstagedしてcommit打てる。gitconfigがコピーされてるのでAuthorもいつもどおり。
この辺まで完全に普段と何も変わらない開発。むしろ明示的にdocker runとかやってない分だけ楽かも。
しかしpushができない。https認証なら何もせずにできそうだが、ssh keyでやりたい。
なおfishでagentを起動するには: https://wiki.archlinux.org/index.php/Fish#Evaluate_ssh-agent
サッとやった感じでは動かないので調査。
wslではなく親windows側のagentが転送されるのかと思いpowershellでもやってみたが変わらず。
なお検証はssh-add -l
およびssh -AT git@github.com
wsl2からだとsocatを入れろ、という話は既に見かけている。 https://wonwon-eater.com/vscode-remote-containers-git/
その後色々試してはいるが...
はい。wsl側のdefault shellをfishからbashにしたら通りました。
試行錯誤が多かったため色々実験して再現条件を絞りにいく。
- wsl2からやる場合はwsl側に
socat
をインストールされている - wsl側の
$HOME/.ssh/ssh-agent
ファイルにbash式のssh-agentのスクリプトが書かれている
が必要。後者は要するにssh-agent -s
の結果が上記ファイル名で存在すること。
fishの場合はssh-agent -c
でやるので、これではダメ。
ということでハックすると
if [ -z "$SSH_AUTH_SOCK" ]
set RUNNING_AGENT (ps -ax | grep 'ssh-agent -c' | grep -v grep | wc -l | tr -d '[:space:]')
if [ "$RUNNING_AGENT" = "0" ]
ssh-agent -c &> $HOME/.ssh/ssh-agent-fish
cat $HOME/.ssh/ssh-agent-fish | sed 's/setenv \([^ ]*\) /export \1=/g' > $HOME/.ssh/ssh-agent
end
eval (cat $HOME/.ssh/ssh-agent-fish)
ssh-add $HOME/.ssh/id_rsa
end
基本的に公式ドキュメントにあるスクリプトなのだが、fish用にハックが入っている。
- まずfish用にagentの環境変数セットスクリプトを吐き出す。
$HOME/.ssh/ssh-agent-fish
としておく - 上記生成後、bash互換に書き換えたものを
$HOME/.ssh/ssh-agent
として出力しておく(vscode用) - fish側でスクリプトを実行する
実用的にはもうちっとシンプルになるかも。
またdevcontainer側のデフォルトshellをfishにするとどうなるのかもまだわからない。
なお正しい$HOME/.ssh/ssh-agent
が存在する&socatが無い場合、現在のvscodeはsocatを入れろと警告を出すようになっている。
fishの上記スクリプトが存在するとDocker desktopとの連携がうまくいかなくなることが判明した...
動かない状態から一旦該当スクリプトをコメントアウト&fishを再起動すると動くようになり、それ以降は該当スクリプトを元に戻しても大丈夫なもよう。
再現手順はあるのでなんとか回避したいところ。
該当スクリプトをttyのみで実行するようにする、つまりdocker desktopからshell起動された際に実行しないようにすれば解決した。
つまり該当処理を
if tty >/dev/null
...
end
で囲う。
本質的にそういうことなのかはちょっとわからない。
そもそもはおそらくDockerDesktopがssh-agentの存在を検知して何かいい感じにするという仕様だと思うのだが、そこまで調べていない。
追記: これだと本命のdevContainerがダメになる。どうしろと。
わかっているのは
- 例のスクリプトを入れた状態でDockerDesktopを再起動(PC rebootにより初期起動を含む)すると、wslからdockerコマンドが動作しなくなる。
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
- 例のスクリプトを外した状態で、Dockerはそのままでfishを再起動するとdockerコマンドが動くようになる
-
例のスクリプトを外した状態
厳密にはeval (cat $HOME/.ssh/ssh-agent-fish)
をコメントアウト - config.fishの再読み込み(tmuxのsplit paneなど)ではダメ
-
後者より、DockerDesktopの起動時処理にハックを仕込むというよりはfish起動時の処理に工夫するのが正と見える。
ログインシェル実行時に何が起こっているかを追う感じか。
dockerが動かない状態の場合
-
/var/run/docker.sock
が更新されていない -
docker-desktop-proxy
が動いてない
$HOME> ps aux | grep -i docker
user 4827 0.0 0.0 8160 740 pts/5 S+ 21:25 0:00 grep --color=auto -i docker
root 28201 0.0 0.2 1167956 29880 pts/0 Ssl+ 21:21 0:00 /mnt/wsl/docker-desktop/docker-desktop-proxy --distro-name Ubuntu --docker-desktop-root /mnt/wsl/docker-desktop
ことが確認できる。
またconfig.fishにls -al /var/run/docker.sock >> /tmp/aaa
など仕込んでみると、evalをコメントアウトした場合は最初のconfig.fish実行の先頭時点で既にsockファイルが更新されている。
config.fishにps aux | grep docker >> /tmp/a
を仕込んでみたところ、fish再起動、というか新規起動(windows terminalで新規タブ)したときに
user 10770 0.0 0.1 588792 21088 pts/15 Rl+ 21:46 0:00 /usr/libexec/docker/cli-plugins/docker-app docker-cli-plugin-metadata
user 10879 0.0 0.0 161228 6756 pts/15 Ssl+ 21:46 0:00 /usr/bin/fish -c mkdir -p ~/.docker/run
user 10919 0.0 0.0 161228 6936 pts/15 Ssl+ 21:46 0:00 /usr/bin/fish -c cat ~/.docker/config.json
user 10959 0.0 0.0 161228 7028 pts/15 Ssl+ 21:46 0:00 /usr/bin/fish -c cat - > ~/.docker/config.json
user 11182 0.0 0.4 767276 63732 pts/15 Ssl+ 21:46 0:00 docker serve --address unix:///home/medalhkr/.docker/run/docker-cli-api.sock
こんな感じのログが見えた。
完全に手詰まりなので、ゴリ押しではあるが
if tty >/dev/null
if not pgrep -f docker-desktop-proxy >/dev/null
sudo true
sudo nohup /mnt/wsl/docker-desktop/docker-desktop-proxy --distro-name Ubuntu --docker-desktop-root /mnt/wsl/docker-desktop &
end
end
これでちょっと動作確認
再起動したがダメだった。もうだめ。
完全に手詰まりなのでdevContainerの中からssh認証でpushするのは諦める。
そもそも、主に毎度id/passを入れたくないという理由でssh-key認証を使っていたが、vscode経由だと認証情報をvscodeが覚えるっぽいので不要だった。
次はdotfilesを入れる。公式ドキュメントと実際の設定画面を見ると、設定項目は
- dotfilesの初期installスクリプトのパス
- dotfilesのリポジトリ
- dotfilesのリポジトリを展開するパス(たぶんコンテナ内)
とのこと。リポジトリを指定できるということはsandboxリポジトリを作って挙動を試せそう(本命のdotfilesをいきなり変えたくない)。
また設定の下を見るとdotfiles系の設定がもう1セット生えているようだ。こっちがdevContainer用で、上にあるのはRemote Containersの共通設定とのこと。
Remote ContainerだがdevContainerではない、というのがちと不明だが、まぁ共通側を設定しておけばよかろう。
dotfilesの深い沼は別スクラップとした。 https://zenn.dev/cumet04/scraps/1a9861bc18bea7
せっかくなのでcodespacesの方も確認しておく。ドキュメント
- publicな
dotfiles
リポジトリを使う(変更できなそう) - 初期スクリプトは
[install|bootstrap|setup][.sh]?
で固定 - 初期スクリプトがなければ、dot-prefixなファイルを自動展開する
※ベータ版なので注意ではある
vscodeのやつのconfigureできない版といった感じか。
次はdocker-composeとの組み合わせを試す。参考記事やドキュメントはこのへん。
そもそもremote-containerとして使う前提の場合、docker-composeの対象コンテナはどういう状況にしておくべきか?
従来通りにrails serverとか起動しておくべきか、単体devcontainerのようにただの環境として作って何も起動しない(shell loginして何かする前提)べきか。
というかコンテナビルド・起動が通らない。
よくわからんので、devcontainer関連ファイルを一旦消してwslで開き直し、Remote-containers: Add Development Container Configuration Files
からdocker-compose用でファイルを作り直す。
...が、また別のエラーでコケる。プロンプトで作ったファイルなのに...
The Compose file '.../.devcontainer/docker-compose.yml' is invalid because:
Unsupported config option for services.app: 'init'
docker-composeのバージョン問題?
現状 (Ubuntu 20.04のリポジトリ内): docker-compose version 1.25.0, build unknown
最新リリース ( https://github.com/docker/compose/releases ): docker-compose version 1.27.4, build 40524192
後者では動きました。ubuntuの最新ではダメです。
なお生成されるファイルはこちら: https://github.com/microsoft/vscode-dev-containers/tree/v0.154.1/container-templates/docker-compose/.devcontainer
- コンテナ内からdockerアクセスができるように色々やっている
- non root userを定義している
- serviceとしては
sleep infinity
で起動しっぱなしにしている
sleep infinity
とか初めて知ったぞ...
その他スクリプトは読んでみる
/usr/local/share/docker-init.sh
(ENTRYPOINTに指定されてるやつ)
リポジトリとかが見当たらなかった... docker-debian.sh
(後述) の中に書いてあった
ざっくり、root or non-root userの場合の吸収・中からdockerコマンドが使えるかの確認&修正を事前にやるものっぽい。
諸々終わったら引数にあるスクリプトを実行(デフォルトだとsleep infinity
)
common-debian.sh
ざっくり、
- 実行ユーザの確認 (root or not)
- パッケージインストール
- locale設定
- ユーザ作成
- bash/zshのスニペット作成
をしてるっぽい。
docker-debian.sh
ざっくり、
- 実行ユーザの確認 (root or not)
- docker使うための関連パッケージインストール
- docker/docker-composeインストール
-
docker-init.sh
の設置
ぶっちゃけコンテナ内でdocker使わなければ自前で必要な部分だけ書けば良さそう。
敢えて公式のを外す理由もあんまないかもだが。
とはいえ、「セットアップスクリプト」の書き方として参考になる。
このあたりまでの知識・設定で、手元にない環境の開発ブートストラップが一応動いたのでclose
microsoftのdocker base imageが何をやっているのか確認しておく。
base-debianだとこちら
buildpack-depsにユーザ作成およびスクリプトを実行しているだけ。実行しているスクリプトも同階層にあるため、あとは必要に応じて読むなりできるだろう。