🐳

Visual Studio Codeで使えるリモート環境のdevcontainerが意外と便利そうだったのでまとめ

2022/09/02に公開

試してたらたまたまVisual Studio Code(vscode)のdevcontainer(Remote Container)が、Remote SSH経由でリモート環境でも使えることを知ったので、devcontainer用の環境構築方法やdevcontainerの構築方法についてまとめてみた

今まではローカル環境のdockerか、codespaceでしか利用できないのかなと思っていたのだけど、リモート含めて利用できるとかなり便利そうな印象だったので一通り試してみました

最近はRemote SSHでリモート環境を利用するケースが多いのでリモート環境で使えないならそんなに使えないかなと思ってたんだけど、普通にRemote SSH経由でdevcontainer使えたのでかなり便利そうだった

devcontainerについてはこちらを参照してもらえればと

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

はじめに

結論から言うと以下のようなことを試した結果をこの記事にまとめている

  • devcontainer(Remote Container)用のサーバー環境構築
  • 構築したサーバーで実際に動くdevcontainer設定を行ったサンプルリポジトリの作成

そのためこの記事の構成は以下の内容

  • devcontainerサーバーの環境構築例
  • リポジトリの設定の説明
  • 補足

devcontainer関連の記事は色々あるけど、devcontainer周りは公式ドキュメント含めて実際に利用する際に必要なレベルの情報が一纏めになっていないと感じていたので、今回試した内容+調べたことをまとめた感じ
今回はGo言語のWebアプリを想定したサンプルだけど、個人的には普通にアプリ開発をする上で必要なdevcontainer周りの知識を学ぶための情報やリンクはこの記事でまとめられたんじゃないかという気がしている

作成したサンプルリポジトリはこちら
(多分codespaceでも動くと思うので気になったら一度動かしてもらえるとわかりやすいと思う)

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

サンプルリポジトリについて

サンプルリポジトリは

  • Go言語のWebサーバー(Hello World)を実装
  • devcontainer側で 8080 ポートを固定で開けてあるので、 http://localhost:8080 でサンプルアプリにアクセス可能
  • airを使用したLive reloadを使った開発が可能
  • .vscode/launch.json を設定してあるのでvscodeのメニューからデバッグ実行が可能
  • おまけレベルのユニットテストを実装
  • Docker Composeを利用してGo言語 + postgresqlのコンテナを起動
    • postgresqlは以下の理由で入れているだけでサンプルアプリ的には実際には利用していない
      • devcontainerでDocker Composeを利用する方法の勉強用
      • Go言語コンテナからpostgresqlへ接続できることを示すため
  • devcontainer内でdocker build ~ pushが可能(docker-in-docker構成)

あたりを入れてあるので、個人的にはコンテナ化されたGo言語のWebアプリケーションをdevcontainerで使用する例としてはまぁ最低限の内容になってるかなという気持ち

リポジトリのベースとして下記を参考に作成した

https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/go-postgres

下記の方法で構築したサーバーでこのリポジトリをcloneしてきてvscodeで開くとdevcontainerで開き直すかを聞かれるので開き直すとこんな感じになる

あたりまえなんだけど普通にvscodeのまんまで、左下とかを見るとcodespaceみたいな感じで接続してるコンテナの情報が表示される

このリポジトリの試し方は、はじめに

$ make setup

で必要なツールをインストールしてから

$ make run

でairを使ったサーバーの起動
(portforwardしてるのでこの状態で http://localhost:8080 を開くとレスポンスが返ってくるはず)

$ make lint
$ make test

でそれぞれlint(golangci-lint)や go test の実行などを行えるようになっている

$ make build

でDocker Imageのビルドが可能というのが基本的なところ一通り

devcontainerの設定

devcontainer.json

devcontainer.json は基本下記のリファレンスを見ながら書いていくことになる

https://code.visualstudio.com/docs/remote/devcontainerjson-reference

もしくはRemote Containerの Remote-Containers: Add Development Container Configuration Files コマンドを使って生成するか

https://code.visualstudio.com/docs/remote/create-dev-container#_automate-dev-container-creation

今回のサンプルリポジトリでの設定例と項目の簡単な説明は以下のような感じ

{
  // 名前
  "name": "devcontainer-example",

  // この例ではdocker-composeを利用しているので、そのパスを指定している
  // docker composeのcontextは .devcontainer になるらしい
  "dockerComposeFile": "docker-compose.yml",

  // devcontainerでshellなど?で使用するdocker composeのservice名
  "service": "app",
  // 指定したserviceコンテナのworkspaceフォルダ
  "workspaceFolder": "/workspace",
  // 指定したポートが始めからvscodeのポート機能で手元環境にforwardされるようになる
  "forwardPorts": [8080],

  // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/script-library/docs
  // にあるスクリプトをdevcontainerで実行できるプラグイン機構
  // 今回の例では見ての通りdocker-in-dockerの設定を行ってくれるfeatureを設定してる
  "features": {
    "docker-in-docker": {
      "version": "latest",
      "dockerDashComposeVersion": "v2"
    }
  },

  // devcontainerとして開くvscode側の設定カスタマイズ
  "customizations": {
    "vscode": {
      "settings": {
        "go.toolsManagement.checkForUpdates": "local",
        "go.useLanguageServer": true,
        "go.gopath": "/go",
        "go.goroot": "/usr/local/go"
      },

      // devcontainerに自動でインストールするvscode extention
      // extentionのページの歯車アイコンをクリックすると出てくる「拡張機能 ID のコピー」というやつからここに貼るIDを知ることができる
      "extensions": [
        "golang.Go",
        "ms-azuretools.vscode-docker",
        "shardulm94.trailing-spaces",
        "eamodio.gitlens",
        "donjayamanne.githistory",
        "mhutchie.git-graph"
      ]
    }
  },

  // host側の実行ユーザー(だと思う)で、devcontainerで開いたフォルダのファイルはこのユーザーに権限が無いと保存などができない
  "remoteUser": "vscode"
}

Dockerfile

基本はdocker-composeを利用しているが、メインとなるGo言語用のイメージについては一部手を加えているためDockerfileで作成している

実際には特に制限はないのかも知れないが、devcontainer用のベースイメージをMicrosoftが公開しているため、これをベースにイメージを作成した

以下のDockerfileに手を加えてpsqlコマンドをインストールしているだけ

https://github.com/microsoft/vscode-dev-containers/blob/v0.245.2/containers/go/.devcontainer/Dockerfile

Dockerfileの中身(&& apt-get -y install --no-install-recommends postgresql-client のところだけ追加している)

# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.18, 1.17, 1-bullseye, 1.18-bullseye, 1.17-bullseye, 1-buster, 1.18-buster, 1.17-buster
ARG VARIANT=1-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT}

# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
#     && apt-get -y install --no-install-recommends <your-package-list-here>
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    && apt-get -y install --no-install-recommends postgresql-client

# [Optional] Uncomment the next lines to use go get to install anything else you need
# USER vscode
# RUN go get -x <your-dependency-or-tool>
# USER root

# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

docker-compose.yml

docker-compose.yml は下記をベースに作成した

https://github.com/microsoft/vscode-dev-containers/blob/v0.245.2/containers/go-postgres/.devcontainer/docker-compose.yml

基本的にはただの docker-compose.yml ファイルで、説明があった方が良さそうな設定は以下のあたりかなと思う

  • VARIANT: 1.18-bullseye: DockerfileのベースとなるGo言語のバージョンを指定
  • security_opt:cap_add:: デバッガー用に設定
  • init: true docker-in-docker用に設定を追加

version: '3.8'

volumes:
  postgres-data:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        # [Choice] Go version 1, 1.18, 1.17
        # Append -bullseye or -buster to pin to an OS version.
        # Use -bullseye variants on local arm64/Apple Silicon.
        VARIANT: 1.18-bullseye
        # Options
        NODE_VERSION: "lts/*"
    env_file:
        # Ensure that the variables in .env match the same variables in devcontainer.json
        - .env

    # Security Opt and cap_add allow for C++ based debuggers to work.
    # See `runArgs`: https://github.com/Microsoft/vscode-docs/blob/main/docs/remote/devcontainerjson-reference.md
    security_opt:
      - seccomp:unconfined
    cap_add:
      - SYS_PTRACE
    init: true

    volumes:
      - ..:/workspace:cached

    # Overrides default command so things don't shut down after the process ends.
    command: sleep infinity

    # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
    network_mode: service:db

    # Uncomment the next line to use a non-root user for all processes.
    # user: vscode

    # Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
    # (Adding the "ports" property to this file will not forward from a Codespace.)

  db:
    image: postgres:latest
    restart: unless-stopped
    volumes:
      - postgres-data:/var/lib/postgresql/data
    env_file:
      # Ensure that the variables in .env match the same variables in devcontainer.json
      - .env


    # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
    # (Adding the "ports" property to this file will not forward from a Codespace.)

launch.json

.vscode/launch.json を以下の用に設定することでvscodeのメニューからデバッグ実行が可能になるっぽい
ドキュメントは下記のようだけどdevcontainer.jsonのリファレンスと比べるとだいぶ読みにくい

https://code.visualstudio.com/docs/cpp/launch-json-reference

{
  "version": "0.2.0",
  "configurations": [
      {
          "name": "Launch Server",
          "type": "go",
          "request": "launch",
          "mode": "debug",
          "program": "${workspaceFolder}/server.go"
      }
  ]
}

devcontainerサーバーの環境構築

devcontainerサーバー用のLinuxのリモート環境を構築する
(別に手元のPCのVMでも動くと思う)

devcontainer(Remote Container)用の環境構築

devcontainer環境に必要なものは

  • docker
  • docker compose(plugin)

だけなので、これらをインストールする

それとは別に、devcontainer実行用に vscode ユーザーを作成しておくと root ユーザーを利用しなくても良いようなので作成しておく
実際にどのユーザーを使うかはdevcontainer側の設定次第だが vscodenode ユーザーが一般的なようなので作っておいた方が無難な気がする

docker composeはバージョンが違うとdevcontainerが動かなかったりしたので、もし動かないようなら新しいバージョンを試してみても良いかも

$ apt-get update
$ apt-get upgrade -y

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
$ add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
$ apt update
$ apt-get install -y docker-ce docker-ce-cli containerd.io

$ mkdir -p ~/.docker/cli-plugins
$ curl -sSL https://github.com/docker/compose/releases/download/v2.10.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
$ chmod +x ~/.docker/cli-plugins/docker-compose

$ groupadd vscode
$ useradd -s /bin/bash -m -g vscode vscode
$ useradd -s /bin/bash -m -g vscode node
$ cat << EOS > /etc/sudoers.d/vscode
vscode    ALL=(ALL:ALL) NOPASSWD: ALL
node      ALL=(ALL:ALL) NOPASSWD: ALL
EOS

構築したサーバーでdevcontainerを使う

構築したサーバーでdevcontainerを使うにはvscodeでRemote SSHで .devcontainer.json.devcontainer/devcontainer.json のあるフォルダを開くとvscodeからdevcontainerとして開き直すか聞かれるので開き直すだけ

https://code.visualstudio.com/docs/remote/containers#_create-a-devcontainerjson-file

リポジトリをcloneしたりするユーザーが vscodenode 以外だとパーミッションエラーで保存したり変更できないケースがあるので、下記の例のようにパーミッションの設定を行って置いた方が良い

$ git clone git@github.com:bells17/devcontainer-example.git
$ chmod -R vscode:vscode devcontainer-example

devcontainer(Remote Container)を便利に使うようにするための設定

SSH ForwardAgentの有効化

git操作などでssh-agentを利用できるようにサーバー側で有効化する
(実際にssh-agentを利用するためにはクライアント側でも別途 ssh-add による設定が必要だけど省略)

これをやっておかないとdevcontainerから git push とかできない

$ cat << EOS > /etc/ssh/sshd_config.d/forwardingagent.conf
AllowAgentForwarding yes
EOS
$ systemctl reload sshd

git設定

devcontainer(Remote Container)だとhost側の ~/gitconfig をコンテナ側に同期する仕組みがあるので、エイリアスなどを設定している場合は適時設定しておいた方が良い
個人的には普段自分が設定してるgitのエイリアスそのままでgitが使えるのですごい楽

https://code.visualstudio.com/docs/remote/containers#_sharing-git-credentials-with-your-container

また最低限以下は設定しておかないと git commit できないので設定しておく
(普段使ってる ~/.gitconfig があるならそれをそのままコピーすればOK)

git config --global user.email <email>
git config --global user.name  <name>

パーミッション

先程 vscode ユーザーを作成したが、実際にどのユーザーを使うかについては .devcontainer/devcontainer.jsonremoteUser の設定に依存するみたい
ファイルの保存などについてはこのユーザーに権限が無いとできないので、devcontainerで開くソースコードを置くディレクトリは remoteUser で使用するユーザーがwriteしたりできるよう chmod -R でパーミッション設定しておく必要がある

remoteUser でroot以外を使う話については下記のリンクあたりで解説されている

https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user

なのでそもそもソースコードを置くディレクトリを作るなど、このサーバーで基本的な操作を行うユーザーははじめから vscode でできるようにしておいた方が無難だと思う

devcontainerまとめ

コンテナのベースイメージ

https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers

にせっかくMicrosoftが公開してくれているベースイメージがあるので、特に理由がなければこれらをベースにして独自イメージを作るのが良さげ

remoteUserで指定するユーザー

https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user#_creating-a-nonroot-user

を見る限り

UID/GID が 1000 の非 root ユーザー (通常は vscode または node と呼ばれます)

とあって

https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers

のいくつかを見ても vscode が使われているようなので、基本 vscode 使っておけば無難では?という感じ
(host側でも設定 vscode ユーザーだけ作っておけば良いので)

devcontainerでKubernetesを使いたい

試してないけどdocker-in-dockerができるならkind動きそうだよね

https://kind.sigs.k8s.io/

あとはBridge to Kubernetesというツールで既存のクラスターを使うという方法もあるらしい

https://docs.microsoft.com/ja-jp/visualstudio/bridge/bridge-to-kubernetes-vs-code

featureについて

どうやらdevcontainer.jsonのfeatureで設定すると、対応するscritpを実行して環境構築とかをしてくれるプラグイン機構がこのfeatureというやつのことのよう
何故か探した限りvscodeのドキュメントにもfeature機能の一覧が見当たらないようなんだけど、下記のscript-libraryがこのfeatureの一覧のよう

https://github.com/microsoft/vscode-dev-containers/tree/main/script-library/docs

例えばgoだと以下のように設定して使えるって書いてある

"features": {
    "golang": "latest"
}

また下記を見る限りユーザー独自のfeatureを使うこともできるみたいなので今後に期待
(軽く見た感じまだ開発中な機能なのかなという印象)

https://github.com/microsoft/vscode-dev-containers/tree/main/script-library/container-features

devcontainerの仕組みってどうなってんの?

よくわからないけどエラーになったときのログみると

$HOME/.vscode-server/bin/e4503b30fc78200f846c62cf8091b76ff5547662/node \
  $HOME/.vscode-remote-containers/dist/dev-containers-cli-0.245.2/dist/spec-node/devContainersSpecCLI.js up \
  --workspace-folder /path/to/repo \
  --workspace-mount-consistency cached \
  --id-label devcontainer.local_folder=/path/to/repo \
  --log-level debug \
  --log-format json \
  --config /path/to/repo/.devcontainer/devcontainer.json \
  --default-user-env-probe loginInteractiveShell \
  --mount type=volume,source=vscode,target=/vscode,external=true \
  --skip-post-create

みたいなコマンド実行してるっぽいので

  • vscodeのRemote SSHがhostにインストールしたnodeのバイナリを使って
  • devContainersSpecCLI.js というのを実行してdevcontainerを作ってるっぽい

というのが想像できた

js実行してるのになんでhost側の設定いらないのかな?って思ってたけどRemote SSHがnodeのバイナリ置いてるからなのかってのが結構面白かったポイント

ちなみに devContainersSpecCLI.js は実行ファイル名は下記の devcontainer コマンドとサブコマンドやオプションが同じみたいだから多分同じものなんじゃないかと思ってる

https://github.com/devcontainers/cli

devcontainer cli

個人的には今の所devcontainerってvscodeのRemote SSHでしか使わなそうだしこれなにに使うんかな?と思ってたら使用例についてもいくつか書かれてた

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

軽く見てみた感じ

  • CIでdevcontainerを利用
  • Docker Imageのビルドでdevcontainerを利用
  • vimなどのエディタでdevcontainerを利用

といったサンプルがあるようだった

devcontainer自体について

なんかいつの間にかDevelopment Containersなる仕様が作られてたらしくサイトがあった

https://containers.dev/

ここで仕様とかが公開されていて下記のリポジトリでスキーマ定義とかもあるみたい

https://github.com/devcontainers/spec/blob/main/schemas/devContainer.base.schema.json

もしかしたら今後はvscodeだけじゃなくてdevcontainerの仕様準拠のツールとかも出てくるのかな?
今のところgitpodのissueとかみるとあまり動きはなさそう

https://github.com/gitpod-io/gitpod/issues/7721

と思ったらgitpodがopenvscode-serverなるものを作ってた!
(これ中身調べたい〜)

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

codespace側の仕組み

TODO ↓あたりを調べる

https://docs.github.com/ja/codespaces/getting-started/deep-dive

感想

  • (少なくともリモート環境で)devcontainer使う際に知りたいことは周辺知識含めてだいたい知れた気がしてる
  • この記事でもそれらをまとめられたと思うので自分でわかんなくなったときはこの記事見返そうかなと思ってる
  • host側にはあまり制約事項が無くて、dockerとか最低限のツールがあれば、あとはdevcontainer側が吸収するようになってるのが良さそう
  • わざわざ仕様まで作ったのでvscodeのRemote Containerでも利用するツールをdevcontainer以外にもオプションで選べるようになってればdevcontainerが未対応の機能に対応したツールとか使えて更に良さそう
  • エディタ側もdevcontainerの仕様が公開されているので、vscodeに限らずdevcontainerを使える未来も割とすぐに来そう(LSPみたいに広く使われるようになると開発環境が大きく変わりそう)
  • devcontainerって既にvscodeっていうエディタというプラットフォームを抑えてたのでかなり強いよなーと感じた

おまけ

記事の画像もGithubで管理できるらしかったので今回はこれも試してみた

https://zenn.dev/zenn/articles/deploy-github-images

Discussion