Open14

CodeSandbox の podman で Dev Containers を試す

hankei6kmhankei6km

以前に CodeSandbox の Dokcer(インテグレーションではないコマンドライン用)で Dev Containers を使えないか試してみてうまくいかなかった(直接的な原因は rootless docker なのでワークスペースのマウントに失敗する)。

その後、VSCode の Dev Containers 拡張機能で podman を使う方法をみかける。これならいけるのでは?ということで試してみている。

https://code.visualstudio.com/remote/advancedcontainers/docker-options#_podman

hankei6kmhankei6km

2023-02-09 時点でのまとめ

Codespaces などで利用していたリポジトリがそれなりに動作するようになった。

imageや dockerfile を使う(コンテナが 1 つの場合)

imagebuild.dockerfile を利用する場合(コンテナは 1 つの場合)は、下記で利用可能になる。

  • GitHub のリポジトリを CodeSandbox へインポート
  • CodeSandbox でリポジトリに環境変数 PODMAN_USERNS=keep-id を追加する
  • CodeSandbox でブランチを作り、VSCode から開く
  • devcontainer.jsoncontainerEnvHOME を設定する(コンテナの USERremoteUser が合致してない場合のみ必須?)
  • Dev Containers 拡張機能の設定で Docker pathpodman へ変更する

この状態で Reopen in Container すると利用できる。また、GitHub の認証情報については VSCode で拡張機能を利用している場合は、そちらの認証情報が利用できる(GitHub CLI は別途考える必要がある)。

問題点は下記の通り。

  • Docker path は状況によって手動で切り替える必要がある
    • ローカル環境で Docker を使う場合は毎回変更することになる
    • Codespaces は(.devcontaienr を共通化できるようにしておけば)影響されない
  • 最近開いた項目から開きにくい(どれも /workspace の Dev Container になるので判別しにくい)
  • CodeSandbox の機能が利用できない
    • IDE にアバターなどが表示されない(IDE への同期は普通に行われる)
    • (おそらく)VM の稼働状態にカウントされないのでサスペンドしやすい
      • これが地味にめんどう
    • Dev Contaner を使っていなくても)よくわからないタイミングで VM がリスタートされる
      • CodeSandbox の拡張機能から開きなおす必要がある
      • コンテナとイメージも消える(ワークスペースは残っている)

少し使ってみたが VM のリスタートに巻き込まれなければ「ちょっと重たい」くらいで使えるかも。Codespaces のように Git のブランチ別にコンテナを持ちやすいのもポイント高い。CodeSandbox でブランチを作れば分岐元のコンテナも複製されるので、ワークスペースの外側にちょっと保存していおたファイルも複製される。

正規の使い方でないし少し安定しないので、Codespaces の補完的な位置付けで使うとよさそうかな。

dependabot の bump でエラーになったときとか、github.dev だと難しいようなときに便利そう。

左は bump でエラーになったブランチで作った devconrainer、右は bump 前のブランチで作った devcontainer。同時に異なる依存パッケージをインストールして比較できるのは便利。

VSCode のウィンドウを 2 つ並べて、左側はエラーが表示されているスクリーンショット

あとはターミナルの環境のカスタマイズをどれくらい利用するか(Codesapces にあわせてちょっとカスタマイズしすぎた)と、GitHub CLI の認証情報をどうするか、かな。

dockerComposeFile を使う場合

現状では少しむずかしい。

  • GitHub のリポジトリを CodeSandbox へインポート
  • CodeSandbox でリポジトリに環境変数 PODMAN_USERNS=keep-id を追加する
  • CodeSandbox でブランチを作り、VSCode から開く
  • pdoman-composedevelop 版をインストールする(それようの channel を作ってある)
  • podman-compose の絶対 PATH を dev.containers.dockerComposePath へ指定する
  • docker-compose.yml を修正する
    • 各種 PATH を絶対 PATH へ変更する
    • コンテナで USER を指定していない場合で root を使う場合は明示的に指定(USER がないと PODMAN_USERNS の影響を受ける)
  • docker-compose.yml が他環境と共通化できない場合は devcontainer.json を別途用意する

docker-compose.yml の例、こうなってしまうと共通化はできないので devcotainer.json はわけてある。

  • context: "$HOME/workspace/.devcontainer/node" などで絶対 PATH にしている
  • pubsub.userroot を指定している

https://github.com/hankei6km/test-functions-framework-pubsub-ts/blob/68cec82800931122848df15905916637eb746b78/.devcontainer/csb/docker-compose.yml

こちらも少し使ってみたが、Dev Container ができてしまえば、あとは上記のコンテナ 1 つのときとあまりかわらない、かな。

hankei6kmhankei6km

とりあえず試す

下記のリポジトリで試す。これを選んだ理由はあまり凝ったことをしてないため(使っているイメージが一般的なもの)。また、ホームディレクトリに FontConfig の設定を配置してあるので、ユーザー環境の設定がうまくいっているかの確認もできる

https://github.com/hankei6km/test-marp-ogimage

まずは Dev › Containers: Docker Path のみ変更して試してみた。

  • CodeSandbox で import して拡張機能からブランチを開く
  • Dev Container で reopen する

とくにエラーはなくマウントはできた。が、マウントされたファイル(ディレクトリー)はコンテナ内からは root:root になっている。

次に .devcontainer/devcontainer.json も変更してみる。下記のようにドキュメントにあったワークアラウンドを追加してコンテナをリビルドすると今度は node:nodeになっていた。追記、ワークアラウンドは既存のコードに影響でないように設定できそう、詳細は返信の方に記述

devcontainer/devcontainer.json
{
  "name": "node",
  "build": {
    "context": ".",
    "dockerfile": "Dockerfile"
  },
  "remoteUser": "node",
  "runArgs": ["--userns=keep-id"],
  "containerEnv": {
    "HOME": "/home/node"
  },
  "customizations": {
    "vscode": {
      "extensions": ["esbenp.prettier-vscode", "marp-team.marp-vscode"],
      "settings": {
        "markdown.marp.themes": ["./themes/ogimage.css"],
        "[typescript]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[json]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[javascript]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[jsonc]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        },
        "[html]": {
          "editor.defaultFormatter": "esbenp.prettier-vscode"
        }
      }
    }
  }
}

ユーザーに設定した Fontconfig が反映されている(ホームディレクトリの設定が想定通り)。

node ➜ /workspaces/workspace (draft/kind-franklin) $ fc-match
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" "Regular"

試しにスクリプトを動かすと想定通りに動作した。

上記リポジトリを Dev Container として開いて動作しているスクリーンショット

引数の意味など細かいことはあとで調べるとして、問題はいくつかある。

  • CodeSandbox 側の機能が使えなくなる
  • 上記設定をするとおそらく通常の Docker 環境(と Codespaces)では動作しなさそう
    • CodeSandbox のときだけ設定変更する方法を考えてみる?

あとは dokcer-compose 的なものを使う方法も調べる。

hankei6kmhankei6km

"runArgs": ["--userns=keep-id"], は環境変数を追加することでも同じことができる。

https://github.com/containers/podman-compose/issues/166

Only workaround I found is to set it in .bashrc/.zshrc:

export PODMAN_USERNS=keep-id

(補足しておくと、上記 issue にもあるけど、podman-compose では docker-compose.yml で指定できる)

CodeSandbox ではリポジトリに環境変数を設定できるので試してみたら runArgs を変更しなくても良くなった。

下記の環境変数はコンテナ側のユーザー指定があればとくに指定しなくてもいけるもよう(こっちはあっても他に影響はなさそうだし、指定しておいてもよいかも)。

  "containerEnv": {
    "HOME": "/home/node"
  },

このような感じなので、CodeSandbox で Dev Container を使う場合でも Git リポジトリのコードに手を加えなくてよさそう

あとは Docker Path の指定をどうにかできればよいのだが。Reomote - SSH でつないでいるから、そっちに設定を持たすことはできないか?

hankei6kmhankei6km

コンテナ内のユーザーの id

$ id
uid=1002(node) gid=1002(node) groups=1002(node),998(nvm),999(npm)
hankei6kmhankei6km

--userns=mode

https://docs.podman.io/en/latest/markdown/options/userns.container.html#userns-mode

--userns を指定しない場合 root になる。その場合、Dev Container の id マッピングは動作しない(すでに存在している id にはマッピングされない)ので、マウントされたファイルの所有者はコンテナ側のユーザーではなく root:root に見えるのかなと。

指定すると 1002 (これは CodeSandbox の VM 側の id)になり、さらに Dev Container の id マッピングが動作したということかと。

ということは、 keep-id:uid=200,gid=210 形式を使うと、id のコンフリクト問題を回避できるのかな?

hankei6kmhankei6km

あとは Docker Path の指定をどうにかできればよいのだが。Reomote - SSH でつないでいるから、そっちに設定を持たすことはできないか?

VSCode の 2023-01(1.75.0)でグッドタイミングな機能追加が来たと思ったがダメだった。

https://code.visualstudio.com/updates/v1_75#_profiles

CodeSandox 用の Profile を作って Docker Path を変更してたが、各 Profie に反映されてしまう。

エディターで profile 用の settings.jsondev.containers.dockerPath をコピーしてみたが、マウスホバーで下記にようになる。

This setting has an application scope and can be set only in the user settings file.

hankei6kmhankei6km

dockerComposeFile を使っている場合の対応

ドキュメントでは pdoman-compose もいけるようなことも書いてあったが、CodeSandbox には入っていない。よって対応を考える必要がある。が、いまのところうまくいかない。
(これはもしかして、「世の中には podman-compose っていうのもあるのですよ」と紹介しただけ?)

Nix でインストール

パッケージがあったのでインストールしてみる。

$ nix-env -iA nixpkgs.podman-compose

VSCode の設定で docker-compose の PATH を podman-compose へ変更。

これは Dev Container 作成時にエラーとなる。PATH の整合性がとれないのかと思って、スクリプトを挟んで確認してみた。

#!/bin/sh

#set -e

echo "${PATH}" > /tmp/chk1.txt
command -v podman-compose > /tmp/chk2.txt
echo "${@}" > /tmp/chk3.txt
podman-compose "${@}" > /tmp/pc-stdout.txt 2> /tmp/pc-stderr.txt
echo "${?}" > /tmp/chk5.txt

結果、podman-compose は実行されるのだが下記のエラーになっていた。

$ cat /tmp/pc-stderr.txt 
usage: podman-compose [-h] [-v] [-f file] [-p PROJECT_NAME]
                      [--podman-path PODMAN_PATH] [--podman-args args]
                      [--podman-pull-args args] [--podman-push-args args]
                      [--podman-build-args args] [--podman-inspect-args args]
                      [--podman-run-args args] [--podman-start-args args]
                      [--podman-stop-args args] [--podman-rm-args args]
                      [--podman-volume-args args] [--no-ansi] [--no-cleanup]
                      [--dry-run]
                      {help,version,pull,push,build,up,down,ps,run,exec,start,stop,restart,logs}
                      ...
podman-compose: error: argument command: invalid choice: 'config' (choose from 'help', 'version', 'pull', 'push', 'build', 'up', 'down', 'ps', 'run', 'exec', 'start', 'stop', 'restart', 'logs')

このときの引数は下記のようになっている。

$ cat /tmp/chk3.txt 
-f /project/home/hankei6km/workspace/.devcontainer/docker-compose.yml config

回避する方法は不明。ということで docker-compose から podman を使う方法で試す。

docker-compose をコンテナで動かす

https://zenn.dev/link/comments/6a36621c1de6dd

上のコメントで試したやつ。これは -f でファイルを指定されるのでうまくいかない。

Docker インテグレーションのコンテナ内で試す

実は結構期待していた方法だが、

https://zenn.dev/link/comments/adac7768f1132c

途中までは良い感じなのだがイメージのビルドで失敗する。

devcontainer cli でも試したが結果は同じ。

Building pubsub
Sending build context to Docker daemon  4.608kB
request returned Bad Request for API route and version http://%2Fvar%2Frun%2Fpodman%2Fpodman.sock/v1.41/build?buildargs=%7B%22PUBSUB_EMULATOR_HOST%22%3A%22pubsub%3A8043%22%2C%22PUBSUB_PROJECT_ID%22%3A%22abc%22%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&shmsize=0&t=workspace_devcontainer_pubsub&target=&ulimits=null&version=1, check if the server supports the requested API version
ERROR: Service 'pubsub' failed to build : Build failed
Error: Command failed: docker-compose --project-name workspace_devcontainer -f /workspace/.devcontainer/docker-compose.yml -f /tmp/devcontainercli-root/docker-compose/docker-compose.devcontainer.build-1675272569757.yml build
hankei6kmhankei6km

podman-compose(develop 版)と devcontainer cli の組み合わせは動くもよう

podman-compose での挙動を確認するために devcontainer cli から使ってみたら動いてしまった。

podman-compose は pip から develop 版をインストールする。これで config などのエラーになっていたコマンドが使える。

https://github.com/containers/podman-compose#installation

CodeSandbox は pip が入ってないので手動インストールする。

https://pip.pypa.io/en/stable/installation/

~/.local/bin に PATH が通っていないので、下記のようにする。

~/.local/bin/pip3 install https://github.com/containers/podman-compose/archive/devel.tar.gz

devcontainer up を下記のように実行する

devcontainer up --workspace-folder "${PWD}" --docker-path podman  --docker-compose-path ~/.local/bin/podman-compose

ただし、build の context が正しく扱えていない状態になる。たぶんこれ。

https://github.com/containers/podman-compose/issues/620

context などを調整したら Dev Container が動作してしまった。

context 関連

  • docker-compose.ymlcontext に指定していたディレクトリーをワークスペース直下に コピー する(移動だと他でエラーになる)

その他、プロジェクト固有のもの

  • init: true を外す、Docker in Docker 用に指定していた(と思う)ので無くても困らない、と思う
  • 各サービス(コンテナ)のユーザーを指定する
    • イメージ側でユーザー指定がないとホスト側のユーザーになるのかな(userns の設定が影響?)

ここで作った Dev Container で試してみたが、pubsub のエミュレーターのサービス(コンテナ)に接続できている。

➜  workspace git:(draft/chk-cli) ✗ devcontainer exec --workspace-folder "${PWD}" --docker-path podman  --docker-compose-path ~/.local/bin/podman-compose ping pubsub
PING pubsub (10.89.0.7) 56(84) bytes of data.
64 bytes from 10.89.0.7 (10.89.0.7): icmp_seq=1 ttl=64 time=0.330 ms
64 bytes from 10.89.0.7 (10.89.0.7): icmp_seq=2 ttl=64 time=0.082 ms
^C
➜  workspace git:(draft/chk-cli) ✗ devcontainer exec --workspace-folder "${PWD}" --docker-path podman  --docker-compose-path ~/.local/bin/podman-compose bash -c 'curl -X GET "http://${PUBSUB_EMULATOR_HOST}/v1/projects/${PUBSUB_PROJECT_ID}/topics"'
{
}
{"outcome":"success"}

Dev Container を落とすとき

~/.local/bin/podman-compose -p workspacedevcontainer -f .devcontainer/docker-compose.yml down

なぜ devcontainer cli では動くか?

CodeSandbox に入ってる devcontainer を使ったのだが、バージョンはこれを書いている時点(2022-02-03)で最新のもよう。

$ devcontainer --version
0.29.0

しかし、VSCode の拡張機能ではログを見る限りでは 0.25.x が使われているもよう。拡張機能の方も更新されると動作するような気もしないでもない。

hankei6kmhankei6km

podman-compose(develop 版) と Dev Containers 拡張機能でも暫定的に動かす方法がわかった

docker-compose の config コマンドと podman-compose の config コマンドの出力を比較してみたところ、拡張機能版で動かない理由が判明。

podman-compose の config コマンドでは各種 PATH が相対 PATH のままになっている。

https://github.com/containers/podman-compose/issues/620

以前の devconfiner cli だとこれを扱えないもよう。

暫定的に docker-compose.yml 内の各 PATH を絶対 PATH へ変更したら拡張機能版でも Dev Container を作成できた。

podman-compose か拡張機能のどちらかが更新されれば解決するはずなので、それまでは一時的に docker-compose.yml を変更する対応でもいいかな。

hankei6kmhankei6km

もう少し汎用的な手順にする

とりあえず動くようになったので、手順を少しまとめる。

環境変数とかは image or Dcokerfile を使うときと同じ。

Dev Contaners に対応できる podman-compose を入れる。

現状では対応できるバージョンはないのでこれを使う。

https://github.com/hankei6km/test-nix-channel-simple

上記バージョンでは docker-compose.yml 内の各種 PATH を絶対 PATH に必要がある。Codespaces などとの共通の設定は難しそうなので devcontainer.json を複数作る。

先日のVSCode の更新で複数 devcontaioner.json を選択する UI がついた。

https://code.visualstudio.com/updates/v1_75#_remote-development

CodeSandbox 用の devcontainer.json を作成し、そちらかはら絶対 PATH にした docker-compose.yml を利用する。

毎回選択するのは面倒だが、podman-compose か devcontainer 拡張機能が対応するまでの暫定なので。

その他、Remote SSH でワークスペースへ再度接続しようとしてエラーになる場合について。

一旦エラーになると Dev Container を落とす必要がある。下記を CodeSandbox の IDE から実行

~/.local/bin/podman-compose -p workspacedevcontainer -f .devcontainer/docker-compose.yml down
hankei6kmhankei6km

podman compose でエラーになる

docker-compose.yml を使っている場合でエラーが出るようになった。

podman compose version --short が原因のもよう(podman-compose ではなく podman compose)。Dev Contaiers 拡張機能のバージョンを 0.275.0 まで下げても同じ。あとで対策を考える、たぶん。

[2023-03-09T05:01:24.551Z] Start: Run: podman compose version --short
[2023-03-09T05:01:24.555Z] Stop (8 ms): Run: podman-compose version --short
[2023-03-09T05:01:24.580Z] Stop (29 ms): Run: podman compose version --short
[2023-03-09T05:01:24.580Z] Error: Command failed: podman compose version --short
hankei6kmhankei6km

podman compose のとき podman-compose を実行する偽のスクリプトを作ってみたが、結果は変わらず。ちょっと保留。

hankei6kmhankei6km

原因がだいたい判明。

podman-compose version --short(- あり)で特定以上のバージョンを返さないとエスカレーションして podman compose version --short も実行しているもよう。

version のときだけ docker-compose を使うコマンドにさしかえたら回避できた。

処理を分岐させるのにバージョン番号を使われると Docker(Docker Compose)以外を使いにくくなるのだけど、VSCode 的には Docker 使えということなのか?

勘違いだった。

dev.containers.dockerComposePath を絶対 PATH にしていないと podman compose version --shortを拡張機能側から実行しているもよう。絶対 PATH にしたら回避できた。
(devcontainer-cli だと絶対 PATH でなくても動く)