🐳

Dev Containerを使ってみよう

2024/04/23に公開

Dev Containerを使ってみよう

Dev Containerを使う上で知っておくと良さげな情報のまとめ記事です
前にRemote SSHでDev Containerの環境を構築する記事を書いたので、今回はDev Container全般の情報をまとめてみました

https://zenn.dev/bells17/articles/remote-ssh-devcontainer

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とは?

https://code.visualstudio.com/docs/devcontainers/containers

  • Development Containerの略
  • 開発環境をまるっとコンテナで構築できる
    (Dev Container側の思想としてはCI環境でもDev Container使おうぜ!というのもあるみたい)
  • ローカルに必要なのはdockerとDev Container対応エディタだけ!
  • devcontainer.json という設定ファイル元にDev Containerを構成する

https://containers.dev/overview

https://containers.dev/

https://containers.dev/overview

https://code.visualstudio.com/docs/devcontainers/containers

デモ

https://github.com/bells17/compose-devcontainer-example

  • goとredisのwebアプリケーションを
  • docker composeで立ち上げて
  • 開発したアプリケーションをDocker in Dockerでビルドしてイメージ作れて
  • devcontainer接続時のメッセージをカスタムしていて
  • ローカルに自作したfeatureを使ったredis-cliを(雑に)インストールするようになってる

って感じのサンプルリポジトリです

TODO template機能も試した例にしてみたい

サポートされてる機能

Dev Containerを利用できるエディタ

https://containers.dev/supporting#editors

  • VSCode
  • Visual Studio
  • IntelliJ IDEA

頑張ればneovimとかでも使えるっぽい

https://blog.kanezoh.com/entry/2024/02/17/112722

emacsもやってる人がいる

https://blog.agile.esm.co.jp/entry/2022/03/01/090000

https://happihacking.com/blog/posts/2023/dev-containers-emacs/

Dev Container環境を構築するツール

https://containers.dev/supporting#tools

Dev Containerを利用できるリモート開発環境

https://containers.dev/supporting#services

その他VSCodeを使用してる場合はVSCodeのRemote SSHとDev Containerを組み合わせたり

https://zenn.dev/bells17/articles/remote-ssh-devcontainer

基本となるDev Containerイメージの中身

https://github.com/devcontainers/images

ubuntu

golang

ライフサイクルスクリプト

ライフサイクルスクリプトを使うといくつかのタイミングでコマンドの実行を行うことができる

例えば onCreateCommandapt-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
→ 要はエディタとかが接続する前に完了しておくコマンドを設定できる感じ?

https://containers.dev/implementors/json_reference/#lifecycle-scripts

このあたりの細かい部分についてはdevcontainer cliの実装を見るのが一番ちゃんと理解できそうな気がしてる
例えば initializeCommand の実行はこのあたりで行われているよう

https://github.com/devcontainers/cli/blob/c1c8b08263c6dca7cd79c97a2d0bc581fcef4f6c/src/spec-node/configContainer.ts#L66

devcontainer.json 内で使える変数一覧とtemplate

https://containers.dev/implementors/json_reference/#variables-in-devcontainerjson

逆にここにない値を使って devcontainer.json 外からユーザー独自の設定を入れるのは難しい

このあたりtemplate機能を使うとある程度自由度を持って作れる可能性もある?

https://containers.dev/implementors/templates/

devcontainer.json 内で ${templateOption:favorite} のような変数が使えるようになる

(そこまで柔軟性が高いわけではなくて、あんまりちゃんとしたイメージが湧かないので良い感じの例が欲しい)

templateの公式情報としてはこのあたりが役立つかもしれない

feature

要はシェルスクリプトによるプラグイン機能

https://containers.dev/features

https://github.com/devcontainers/features

https://containers.dev/implementors/features/

e.g. kubectl-helm-minikube

↓のcommon-utilsは大体のfeatureのdependenciesとして利用されていて、軽く眺めた感じも必要そうな基本的な設定とか入れてくれるっぽいので、他のfeatureは使わなくてもcommon-utilsは必須で導入しておくと良さそう
(むしろcommon-utilsが導入されてないリポジトリがあったら導入するPR送りつけても良いくらいには入れておいた方がよいと思ってる)

https://github.com/devcontainers/features/tree/main/src/common-utils

ローカルにあるファイルもfeatureとして読み込むことができる

https://github.com/kubernetes/kubernetes/blob/52bcdbcd9bafc520e7b5676eb484976de85c425f/.devcontainer/devcontainer.json#L39-L41

featureのスクリプトをいくつか見るとなんとなくわかると思うけど

  • apt or apk などがあって
  • 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がインストールされていることを前提に書いてる)

https://github.com/bells17/devcontainers-dotfiles

上記リポジトリの中身を見ればわかるんだけど、意識しておくべきこととしては下記の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://containers.dev/implementors/reference/

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 が紹介されてる

https://github.com/devcontainers/cli/tree/main/example-usage

その他TIPS

https://twitter.com/bells17_/status/1778387925144936947

個人的な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について詳しい方色々教えて下さい

https://github.com/gitpod-io/openvscode-server

他にもはこのあたりとか

https://github.com/coder/code-server

Discussion