Closed37

vscodeのRemote Containers環境を真面目に構築してみる

現在windowsでRemote WSLを使っており、そのwsl環境はansibleでガンガン管理されている。

https://github.com/cumet04/dotfiles

これはこれで完成しているのだが、のちにくる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とかやってない分だけ楽かも。

wslではなく親windows側のagentが転送されるのかと思いpowershellでもやってみたが変わらず。
なお検証はssh-add -lおよびssh -AT git@github.com

はい。wsl側のdefault shellをfishからbashにしたら通りました。
試行錯誤が多かったため色々実験して再現条件を絞りにいく。

  • wsl2からやる場合はwsl側にsocatをインストールされている
  • wsl側の$HOME/.ssh/ssh-agentファイルにbash式のssh-agentのスクリプトが書かれている

が必要。後者は要するにssh-agent -sの結果が上記ファイル名で存在すること。
fishの場合はssh-agent -cでやるので、これではダメ。

ということでハックすると

config.fish
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用にハックが入っている。

  1. まずfish用にagentの環境変数セットスクリプトを吐き出す。$HOME/.ssh/ssh-agent-fishとしておく
  2. 上記生成後、bash互換に書き換えたものを$HOME/.ssh/ssh-agentとして出力しておく(vscode用)
  3. 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

こんな感じのログが見えた。

完全に手詰まりなので、ゴリ押しではあるが

config.fish
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ではない、というのがちと不明だが、まぁ共通側を設定しておけばよかろう。

せっかくなのでcodespacesの方も確認しておく。ドキュメント

  • publicなdotfilesリポジトリを使う(変更できなそう)
  • 初期スクリプトは[install|bootstrap|setup][.sh]?で固定
  • 初期スクリプトがなければ、dot-prefixなファイルを自動展開する

※ベータ版なので注意ではある

vscodeのやつのconfigureできない版といった感じか。

そもそも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

https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/common-debian.sh
ざっくり、
  • 実行ユーザの確認 (root or not)
  • パッケージインストール
  • locale設定
  • ユーザ作成
  • bash/zshのスニペット作成

をしてるっぽい。

docker-debian.sh

https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docker-debian.sh
ざっくり、
  • 実行ユーザの確認 (root or not)
  • docker使うための関連パッケージインストール
  • docker/docker-composeインストール
  • docker-init.shの設置

ぶっちゃけコンテナ内でdocker使わなければ自前で必要な部分だけ書けば良さそう。
敢えて公式のを外す理由もあんまないかもだが。

とはいえ、「セットアップスクリプト」の書き方として参考になる。

このスクラップは2021/01/11にクローズされました
ログインするとコメントできます