Dev Containerを使ってみよう
Dev Containerを使ってみよう
Dev Containerを使う上で知っておくと良さげな情報のまとめ記事です
前にRemote SSHでDev Containerの環境を構築する記事を書いたので、今回はDev Container全般の情報をまとめてみました
tl;dr
- Dev Containerを使うと開発環境をコンテナで構築できるよ(ランタイムとかツール類含めて!)
- docker composeだとアプリケーションを動作させる環境は作れるけどDev Containerは開発環境ごと構築できて便利
- Dev Containerを使うにはdockerとDev Containerを利用できるエディタが必要
- Dev Container内でdocker composeを利用できるから、Dev Container用のコンテナ+ミドルウェアコンテナを用意すればアプリケーションを開発できる環境がまるっとコンテナで作れる
- Dev Container cliなどを使えばローカルでも利用できるし、codespaceやdevpod、coderなどを使えばリモート開発環境も作れるよ(gitpodはまだDev Container未対応だった)
Dev Containerとは?
- Development Containerの略
- 開発環境をまるっとコンテナで構築できる
(Dev Container側の思想としてはCI環境でもDev Container使おうぜ!というのもあるみたい) - ローカルに必要なのはdockerとDev Container対応エディタだけ!
-
devcontainer.json
という設定ファイル元にDev Containerを構成する
デモ
- goとredisのwebアプリケーションを
- docker composeで立ち上げて
- 開発したアプリケーションをDocker in Dockerでビルドしてイメージ作れて
- devcontainer接続時のメッセージをカスタムしていて
- ローカルに自作したfeatureを使ったredis-cliを(雑に)インストールするようになってる
って感じのサンプルリポジトリです
TODO template機能も試した例にしてみたい
サポートされてる機能
- Dev Containerのベースとなるコンテナイメージ有り(必ずしも使う必要は無い)
- Dockerfileによるdevcontainerの構築
- docker composeによる複数コンテナによる開発環境の構築
- ライフサイクルスクリプトによる構成追加
- ポートフォワード機能
- プラグイン機能(feature)によるツールインストール
-
ホスト側の
$HOME/.gitconfig
の取り込み(vscodeのDev Containerプラグインによる) - ssh-agentのサポート(vscodeのDev Containerプラグインによる)
Dev Containerを利用できるエディタ
- VSCode
- Visual Studio
- IntelliJ IDEA
頑張ればneovimとかでも使えるっぽい
emacsもやってる人がいる
Dev Container環境を構築するツール
- VSCode Dev Container拡張機能
- devcontainer cli
- coder (envbuilder)
- devenv
- devbox
- その他エディタによるDev Containerサポート機能
Dev Containerを利用できるリモート開発環境
その他VSCodeを使用してる場合はVSCodeのRemote SSHとDev Containerを組み合わせたり
基本となるDev Containerイメージの中身
ubuntu
- https://github.com/devcontainers/images/blob/main/src/base-ubuntu/.devcontainer/Dockerfile
- https://github.com/docker-library/buildpack-deps/blob/93d6db0797f91ab674535553b7e0e762941a02d0/ubuntu/jammy/curl/Dockerfile
- https://git.launchpad.net/cloud-images/+oci/ubuntu-base/tree/oci/index.json?h=refs/tags/dist-jammy-amd64-20240405-ae89e7cd&id=ae89e7cda4e21aae14ec1c74cc095c013adf3ecb
golang
- https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile
- https://github.com/docker-library/golang/blob/ea6bbce8c9b13acefed0f5507336be01f0918f97/1.22/bookworm/Dockerfile
- https://github.com/docker-library/buildpack-deps/blob/d0ecd4b7313e9bc6b00d9a4fe62ad5787bc197ae/debian/bookworm/scm/Dockerfile
- https://github.com/debuerreotype/docker-debian-artifacts/blob/44807175c12f847248c046022ef95862e5567c58/bookworm/Dockerfile
- https://hub.docker.com/_/scratch/
ライフサイクルスクリプト
ライフサイクルスクリプトを使うといくつかのタイミングでコマンドの実行を行うことができる
例えば onCreateCommand
に apt-get install -y xxx
のようにして必要なツールをインストールしたりなど
ドキュメントにある情報を表にすると要はこういうことだと思う
実行環境 | 実行タイミング | 実行回数 | |
---|---|---|---|
initializeCommand | ホストマシン | コンテナの作成中およびその後の起動時を含む、初期化中 | 1回以上 |
onCreateCommand | コンテナ | 開発コンテナの作成時 | 多分1回のみ |
updateContentCommand | コンテナ | onCreateCommand作成プロセス中にソース ツリーで新しいコンテンツが利用可能になった後→workspace-folderのmount後? | 少なくとも 1 回 |
postCreateCommand | コンテナ | Dev Containerがユーザーに初めて割り当てられた後 → 要は利用可能状態になったタイミング? |
多分1回のみ |
postStartCommand | コンテナ | コンテナーが正常に起動されるたび | 1回以上 |
postAttachCommand | コンテナ | ツールがコンテナに正常に接続されるたび → 要はエディタで起動したDev Containerを開く度? |
1回以上 |
waitFor | コンテナ | 接続する前にツールが待機する必要があるコマンド (複数指定可能) デフォルト: updateContentCommand → 要はエディタとかが接続する前に完了しておくコマンドを設定できる感じ? |
このあたりの細かい部分についてはdevcontainer cliの実装を見るのが一番ちゃんと理解できそうな気がしてる
例えば initializeCommand
の実行はこのあたりで行われているよう
devcontainer.json 内で使える変数一覧とtemplate
逆にここにない値を使って devcontainer.json
外からユーザー独自の設定を入れるのは難しい
このあたりtemplate機能を使うとある程度自由度を持って作れる可能性もある?
devcontainer.json
内で ${templateOption:favorite}
のような変数が使えるようになる
(そこまで柔軟性が高いわけではなくて、あんまりちゃんとしたイメージが湧かないので良い感じの例が欲しい)
templateの公式情報としてはこのあたりが役立つかもしれない
feature
要はシェルスクリプトによるプラグイン機能
- https://github.com/devcontainers/features/blob/main/src/kubectl-helm-minikube/devcontainer-feature.json
- https://github.com/devcontainers/features/blob/main/src/kubectl-helm-minikube/install.sh
↓のcommon-utilsは大体のfeatureのdependenciesとして利用されていて、軽く眺めた感じも必要そうな基本的な設定とか入れてくれるっぽいので、他のfeatureは使わなくてもcommon-utilsは必須で導入しておくと良さそう
(むしろcommon-utilsが導入されてないリポジトリがあったら導入するPR送りつけても良いくらいには入れておいた方がよいと思ってる)
ローカルにあるファイルもfeatureとして読み込むことができる
featureのスクリプトをいくつか見るとなんとなくわかると思うけど
-
apt
orapk
などがあって -
bash
などもある
のような暗黙の了解が実質的にはあるので、Dev Container用のベースイメージは実質Debian/Alpine系のみとなってる気がしてる
ユーザーの個別設定をDev Containerに導入する
これはDev Containerの仕様レベルで設定されている機能では無いんだけど、Dev Containerにユーザー独自に設定を入れる方法としてはdotfilesを利用する方法が提供されているケースが多いよう
- VSCode DevContainer拡張機能
- devcontainer cli
などについてはこの機能が提供されていて、例えばVSCodeだと下記のような設定項目がある
ここでdofilesのリポジトリを設定しておくと、Dev Containerの構築時に自動でdofilesのリポジトリをclonse ~ 適用までを行っておくことができる
自分用の作成してみたdotfilesのリポジトリはこちら
(Dev Container固有の設定が多い気もするのでリポジトリは分けてる、あと前述のcommon-utils featureがインストールされていることを前提に書いてる)
上記リポジトリの中身を見ればわかるんだけど、意識しておくべきこととしては下記の2点だと思ってる
- OSごとの切り替え処理(Debian系/Alpineなど)
- Dev Containerの利用環境別の設定(例えばGitHub CodeSpacesだとdotfileの読み込み機能が無いから自動で設定するなど)
OSごとの切り替え処理
OSごとの切り替え処理は下記のような感じでいけるっぽい、というのをcommon-utilsから学んだ
# Bring in ID, ID_LIKE, VERSION_ID, VERSION_CODENAME
. /etc/os-release
# Get an adjusted ID independent of distro variants
if [ "${ID}" = "debian" ] || [ "${ID_LIKE}" = "debian" ]; then
ADJUSTED_ID="debian"
elif [[ "${ID}" = "rhel" || "${ID}" = "fedora" || "${ID}" = "mariner" || "${ID_LIKE}" = *"rhel"* || "${ID_LIKE}" = *"fedora"* || "${ID_LIKE}" = *"mariner"* ]]; then
ADJUSTED_ID="rhel"
elif [ "${ID}" = "alpine" ]; then
ADJUSTED_ID="alpine"
else
echo "Linux distro ${ID} not supported."
exit 1
fi
# Install packages for appropriate OS
case "${ADJUSTED_ID}" in
"debian")
setup_debian
;;
"rhel")
setup_redhat
;;
"alpine")
setup_alpine
;;
esac
Dev Containerの利用環境別の設定
Dev Containerは利用環境毎に独自の環境変数が設定されるようなので、それらの値に応じて処理を切り替える感じ
(ただこれに関しては普通にDev Containerの仕様として利用環境の値を入れる環境変数を決めた方が良さげな気はしている)
if [ "$CODESPACES" = "true" ]; then
echo "Running in GitHub Codespaces"
elif [ "$DEVCONTAINER" = "true" ]; then
echo "Running in devcontainer CLI"
elif [ -n "$CODER_WORKSPACE_NAME" ]; then
echo "Running in Coder"
elif [ -n "$GITPOD_WORKSPACE_ID" ]; then
echo "Running in Gitpod"
else
echo "Running in a different environment"
fi
if [ "$REMOTE_CONTAINERS" != "true" ]; then
git config --global init.defaultBranch main
git config --global core.editor vi
git config --global color.ui auto
git config --global push.default current
git config --global merge.ff false
git config --global pull.rebase true
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.sw switch
git config --global alias.di diff
git config --global alias.dic "diff --cached"
git config --global alias.lo "log --graph -n 20 --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(green)- %an, %cr%Creset'"
git config --global alias.lp "log --oneline -n 20 -p"
git config --global alias.ls "log --stat --abbrev-commit"
git config --global alias.br branch
git config --global alias.fe fetch
fi
複数のDev Containerを使い分ける
これもDev Containerの仕様としてあるわけではないので、すべてのDev Containerツールで利用できるわけじゃないと思うけど、VSCodeのエディタなどで .devcontainer
ディレクトリを開いた際に復数のDev Containerから使いたいDev Containerを選ぶことができたりする
こんな感じで .devcontainer
ディレクトリ内にサブディレクトリを作って devcontainer.json
を設置すると利用するDev Containerを選択できたりする。
VSCodeの他にもCode Spacesとdevcontainer cliはできそうな感じ。
devcontainer cli
devcontainer cliを使うとVSCode DevContainer拡張機能とか無しでDev Containerの実行などが可能
https://github.com/bells17/compose-devcontainer-example を使用した場合の例はこんな感じ
$ npm install -g @devcontainers/cli
$ /path/to/dir
$ devcontainer up --workspace-folder .
[1 ms] @devcontainers/cli 0.59.1. Node.js v21.7.3. darwin 23.4.0 arm64.
{"outcome":"success","containerId":"a9959594af9746fc9032a103210611dd6ef67a37ddf8682372ab59a81643c7c1","composeProjectName":"compose-devcontainer-example","remoteUser":"vscode","remoteWorkspaceFolder":"/workspace"}
$ devcontainer exec --workspace-folder . make run
REDIS_HOST=redis REDIS_PORT=6379 PORT=3000 go run ./app/main.go
Server is running at http://localhost:3000
じゃあこれをどうやってエディタから使ったらよいのか、というのが多分前半でリンクを紹介したneovimとかemacsとかのやつだと思う
その他cliリポジトリに example-usage が紹介されてる
その他TIPS
- Dev Containerに関する質問などはGitHub Discussionで可能
- 起動時のメッセージは
/usr/local/etc/vscode-dev-containers/first-run-notice.txt
で設定可能 -
devcontainer.json
の設定を予めコンテナイメージに組み込む-
コンテナイメージで下記のようなラベル設定をすることでいけるらしい
LABEL devcontainer.metadata='[{ \ "capAdd": [ "SYS_PTRACE" ], \ "remoteUser": "devcontainer", \ "postCreateCommand": "yarn install" \ }]'
- 独自のfeatureを公開したい場合
- セキュリティソフトの影響で通信の間に中間プロキシが挟まれるようなケースだと自己?証明書が設定されてしまうケースがある
- そういったケースだと証明書を予めコンテナ側の
/usr/local/share/ca-certificates/
に保存してsudo update-ca-certificates
することで証明書エラーを回避するしか手が思いつかない -
feature
側の通信に失敗するケースの場合、feature
のインストール処理に差し挟めるフックポイントがライフサイクルスクリプトには無いため - そのためこういった環境だとDev Containerの設定を都度書き換えなければいけないという問題がある
- そういったケースだと証明書を予めコンテナ側の
個人的なDev Containerの課題
- dotfilesを使う以外では個人別の設定を組み込みずらい → dotfilesも全部bashとかでやるので設定しずらい感が無くもない(少なくともそのあたりの学習コストが掛かるので壁に感じる人もいる気はする)
- VSCode DevContainer 拡張機能独自の機能がいくつかある(devcontainer cliに含まれていないらしい)
- ドキュメントとexampleが不足してる(e.g. 公式ドキュメントだけではdocker composeを利用した例が無いので手探りで頑張らざるを得ない or あってもリンクなどがまとまっていない)
- VSCodeなどの一部エディタ以外を除いてエディタ側のサポートが公式側にあまり無さそう(LSPのようなエディタとDev Container側のやり取りをする仕様などが無い)
- 上記の中間プロキシ問題
おまけ
TODO Dev Containerを深く理解するにはdevcontainer cliのコードを読むのが一番良さそうなので今度読んでみたい
あとgitpotが作ってるopenvscode-serverについて詳しい方色々教えて下さい
他にもはこのあたりとか
Discussion