LoginSignup
8
10

devcontainerの運用ベストプラクティス

Posted at

Devcontainerとは?

こちらの記事が参考になる

この機能を使う目的

  • devcontainerはコンテナ内で環境を作成できるので、「作業者の環境にパッケージなどが依存しており、本番環境で動かない!」ということがない
  • いちいちdocker runしてdocker attach してdockerコンテナ内に入る、という作業がなくなる
  • Git, Githubの設定をホストマシンから引き継いで、Githubにプッシュできる
  • Dockerコンテナの起動方法を記述して、開発環境に特化させた環境を記述できる。例えば、開発環境のみに必要なGitや開発依存のnpmパッケージをインストールする記述が可能

現状の運用最適解

  • 環境差異をなくすため。devのための環境はできるかぎり作らない。その環境差異はdevcontainerに吸収させる。

  • onCreateCommandでgit, openssh-client、npmの開発依存packageなどのインストール。
    Git, openssh-clientをインストールするのは、コンテナ内でGithubにプッシュするため。

  • postStartCommandで開発用の永続プロセス(サーバーなど)を起動

さらに便利にするなら

  • 実行中のコンテナでも、サーバーを起動してdevcontainerを立ち上げたい。しかし、Attachする前に起動された永続プロセスが起動の邪魔になる。
    →なので、postAttachCommandで以前に実行されたサーバーkillをするコマンド+永続プロセス(サーバーなど)を追記。以下はpythonのuvicornの場合。
"kill $(ps aux | grep 'uvicorn' | grep -v 'grep' | awk '{print $2}') || true && sleep 1 && DEBUG=1 python3 -m debugpy --listen 0.0.0.0:5679 -m uvicorn  app:app --host '0.0.0.0' --port 8080 --reload"
  • postStartCommandではなく、postAttachCommandでVSCodeのコマンド 「実行中のコンテナにアタッチ」 でも動作する上位互換コマンドだから。

devcontainer.jsonファイルの書き方

  • プロジェクトのルートディレクトリに.devcontainer/devcontainer.jsonを用意する
  • 自分のPCの環境をホスト(host)、もしくはローカル(local)と呼ぶ
  • コンテナ内の環境を、コンテナ(Container)、もしくはリモート(remote)と呼ぶ

dockerファイルを使用する場合

重要な要素のみ解説します。

name

VSCodeの上部の枠に表示される名前(コンテナ名ではないので注意)

build/image

dockerfileを指定するパターンとimageを指定するパターンがある。buildとimageは並列できない。

"build" : {
    "dockerfile": "../Dockerfile",
    "context": "./"
    "target": "development"
  }

もしくは

"image": "YOUR DOCKER IMAGE NAME"

runArgs

docker runで指定する引数を指定できる。-vや-p相当のものは用意されているので、この設定ファイルでカバーしてない引数を指定する。例えば、コンテナの名前を定義する—-name や終了後コンテナを削除する—-rmなどが候補である。

  "runArgs": [
    "--name=CONTAINER_NAME",
    "--rm"
  ]

workspaceFolder

“workspaceFolder": "/workspace"と指定することでワークスペースを変更できる。

workspaceMount

dockerコマンドの-v、docker-composeのvolumesに相当。

mounts

-v引数に相当。volumeやworkspace外のディレクトリをマウントする際に使用する。

sourceはローカルのフォルダを指定。targetはコンテナ内のフォルダを指定。typeはbindだとホストとコンテナ内のフォルダを同期する。volumeだと新規docker volumeを作成する。volumeの場合、sourceはvolumeの名前になる。

“”で括った範囲でカンマ(,)の前後でスペースを入れてはいけない。エラーとなる。

"source=${localEnv:HOME}/.aws,target=/root/.aws,type=bind,consistency=cached",
"source=node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"

appPort

  • ホストマシンからアクセスできるコンテナのポート番号を指定する。
  • docker runするときの-pオプションで9000:9000と指定するのと同じ。

containerEnv

コンテナ内の環境変数を指定する。

overrideCommand

true or falseで指定。devcontainerはdockerのCMDコマンドをデフォルトで上書きする。上書きされたくない場合はfalseを指定する。

initializedCommand

コンテナーの作成中およびその後の起動時を含む、初期化中にホストマシン上で実行される

initializedCommand→onCreateCommand→updateContentComannd→postCreateCommand→postStartCommand→postAttachCommandと続く

onCreateCommand

initializedCommandの後に実行。

  • イメージをビルドした後にコンテナ内で実行されるコマンドを書くことができる
  • dockerのCMDコマンドがこのコマンドで上書きされるわけではない(それがやりたいなら、overrideCommand: Trueで無効化)
  • gitのインストールなど、VSCodeが開く前に実行したいコマンドを記述。postCreateCommand以降でgitやopenssh-clientをインストールすると、gitの設定がホストから引き継がれず、Gitが使用できない。リロードすれば使用できる。

updateCreateCommand

onCreateCommandの後に実行。

Codespaceの設定。ローカルでは使用しなくて良い

postCreateCommand

updateCreateCommandの後に実行。

VSCodeの操作が可能になる。gitのインストールなど、VSCodeが開く前に実行したいコマンドはこれよりも前に記述すべき。

postStartCommand

postCreateCommandの後に実行。
VSCodeの操作が可能になる。gitのインストールなど、VSCodeが開く前に実行したいコマンドはこれよりも前に記述すべき。

前回開いた時のプロセスが止まっていない場合、ポートを占拠し続けて、起動するサーバーが起動できない。

postAttachCommand

postStartCommandの後に実行。

コンテナにAttachするたびに実行されるコマンド。すでに起動されているコンテナにVScodeのコマンドパレット「実行中のコンテナにアタッチ」を実行した場合、唯一実行されるコマンド

前回開いた時のプロセスが止まっていない場合、ポートを占拠し続けて、起動するサーバーが起動できない

shotdownAction

none, stopContainerが指定可能。defaultではstopContainer。

VSCodeを閉じた時にコンテナの扱いを定義する。stopContainerはVSCodeがコンテナから接続を終了した際に、コンテナを停止する。コンテナの削除はされない。

stopContainerを指定をした場合の挙動は以下の通り。

VScode左下の”><”の「フォルダーをローカルから開く」「リモート接続を終了する」を選択すると停止する。またvscodeを❌で閉じても停止する。

VSCode右下の「><」から起動
Screenshot 2024-06-03 at 15.20.16.png

コンテナ内で起動した永続プロセス(サーバー起動など)を停止しない場合、コンテナは停止しない

remoteUser

コンテナイメージ内に存在するユーザー名を指定することで、コンテナを開いたときに使用されるユーザーを指定できる。Dockerfileでユーザーの追加記述が必要。

extensions

コンテナ側のVS Codeにインストールされる拡張機能を指定できる。

ただし、開発メンバーによっては、どんな拡張機能を導入しているかまばらなことがあるので、基礎的なものだけを記述することを推奨する。

または、VScodeの設定で、Containers: Default Extensions If Installed LocallyからコンテナをVSCodeで開くたびに導入する拡張機能を指定しても良い。

変数

devcontainer.json内で使用できる変数を紹介する

${localWorkspaceFolder}

ホストマシンのワークスペースディレクトリを表す。.devcontainerが存在するフォルダ。

${containerWorkspaceFolder}

コンテナ内のワークスペースディレクトリを表す

${localEnv:VARIABLE_NAME}

ホストマシンの変数を指定できる。

例:${localEnv:HOME}

→ホストマシンの$HOME変数を表している。

${containerEnv:VARIABLE_NAME}

コンテナの変数を指定できる

例:${containerEnv:HOME}

→コンテナの$HOME変数を表している。

devcontainer.json例

{
  "name": "Rag LLM" //VScodeの上部の枠に表示される名前
  ,"build" : {
    "dockerfile": "../Dockerfile",
    "target": "development",
    "context": "."
  } // ビルドするDockerファイルを指定
  ,"runArgs": [
    "--name=rag-llm",
    "--platform=linux/arm64"
  ] // devcontainer.jsonでは定義されていない引数を実装
  ,"workspaceFolder": "/usr/src/app"
  ,"workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached" // -v $(pwd):$HOME のようなホストPCとコンテナ内のファイルをマウントする引数
  ,"mounts":[
	  "source=${localEnv:HOME}/.aws,target=/root/.aws,type=bind,consistency=cached",//AWSの認証情報をリモートにマウント
  ] // -v引数
  ,"appPort": ["15999:8080", "5679:5679"] // -p引数
  ,"containerEnv": {
    "PYTHON_ENV": "development"
  }, // コンテナ内の環境変数を定義
  ,"onCreateCommand": "apt install -y curl git openssh-client procps && poetry config virtualenvs.create false && poetry install --only dev --no-root" // gitやopenssh-client, 開発パッケージをインストール
  ,"postAttachCommand": "kill $(ps aux | grep 'uvicorn' | grep -v 'grep' | awk '{print $2}') || true && sleep 1 && DEBUG=1 python3 -m debugpy --listen 0.0.0.0:5679 -m uvicorn  app:app --host '0.0.0.0' --port 8080 --reload" // サーバーが存在したらプロセスキルして、新しいデバッグ用サーバーを設置
  ,"overrideCommand": true // DockerfileのCMDを無効化
  ,"shutdownAction": "stopContainer"
  ,"customizations": {
		"vscode": { 
			"extensions": [ // 拡張機能を定義
        "ms-python.python",
        "ms-python.autopep8",
        "ms-python.debugpy"
      ]
		}
		,"settings": { // VScodeの設定を記載
			"files.autoSave": "afterDelay" // ファイルのオートセーブ
			,"files.watcherExclude": { // ファイルの監視除外
				"**/.git/objects/**": true,
				"**/.git/subtree-cache/**": true
			}
		}
	}
}

docker-compose.ymlを使用する場合

docker-compose.ymlファイルを指定する

["../../docker-compose.yml"]のようにプロジェクトルート外から参照することもできる。大きいプロジェクトで、機能やレポジトリーがそれぞれ分かれており、それをまとめるためのdocker-compose.ymlがある場合に便利

{
  "name": "Dev container",
  "dockerComposeFile": "../docker-compose.yml",
  "service": "app", // 起動するアプリ
}

詳しくは公式リファレンスへ

Tips

devcontainer.jsonで指定できる〇〇Commandの挙動

VSCodedeのコマンドパレットから開いた時によって、〇〇コマンドの挙動が変わるので、それを元に解説する。
Screenshot 2024-06-03 at 14.32.36.png
「cmd + shift + p」で起動

「コンテナーでリビルドして再度開く」の挙動

initializedCommand→
onCreateCommand→
updateCreateCommand→
postCreateCommand→
postCreateCommand→
postStartCommand→
postAttachCommand

と順に起動していく

「コンテナーで再度開く」の挙動

「実行中のコンテナにアタッチ」の挙動

postAttachCommandのみ実行される

ホストの.gitconfigが自動でコピーされる

何もしなくてもgitconfigのuser.nameやemailがコピーされる

git config --global user.name "Your Name"
git config --global user.email "your.email@address"

opensshがインストールしてあれば、Githubにpushできる

dockerコンテナ内にopnesshとgitをインストールすることで、ホストのssh-agent経由でGithubの設定を使用できる

RUN apk add --no-cache git openssh

ubuntu

RUN apt install add git openssh

ホストで以下のような設定ファイルが~/.ssh/configにあればOK

Host github.com
  HostName github.com
  AddKeysToAgent yes
  useKeychain yes
  IdentityFile ~/.ssh/id_rsa
  User git

ssh-agentにキーが追加されているか確認

ssh-add -l

ssh-agentにキーを追加していなければ、追加する。id_rsaは適宜変更。

macの場合はPC起動時に自動でこのコマンドが実行されるが、linuxとwindowsは.bashrcなどに追記が必要

ssh-add --apple-use-keychain ~/.ssh/id_rsa

余談

ssh-agentとは、常にユーザーと一緒に行動して、必要な時に「はいっ!」と秘密鍵を渡してくる存在

クラウド上のサーバーを立ててそこからGithubに接続したい時、サーバーにGithubに入れる秘密鍵を置いておくのは危険なので、ssh-agentを使用する。のような使い方ができる。

opensshをdockerファイル内でインストールせずにdevcontainer内でインストールする

onCreateCommandでインストールすることで、コンテナ内でインストールした時と同じ挙動になる(ssh-agentでホストの設定を受け継いでくれる)

  "onCreateCommand": "apk add curl git openssh && npm i && npm i -D",

typescirptでのdevcontainerベストプラクティス

  • workspaceMountでフォルダーを指定

  • mountsでnode_modulesをボリューム化

  • onCreateCommandでボリュームにnodeのパッケージをインストール。ボリュームマウントした場合、Docker内のnode_modulesが上書きされているため。

  • overrideCommand: true, postAttachCommandで開発用サーバーを起動。起動したサーバーはコマンドから停止可能。

{
  "name": "Rag LLM",
  "build" : {
    "dockerfile": "../Dockerfile"
  },
  "runArgs": [
    "--name=rag-llm"
  ],
  "workspaceFolder": "/usr/src/app",
  "workspaceMount": "source=${localWorkspaceFolder},target=${containerWorkspaceFolder},type=bind,consistency=cached",
  "mounts": [
    "source=rag-llm-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
  ],
  "containerEnv": {
    "NODE_ENV": "local"
  },
  "appPort": ["15999:8080"],
  "onCreateCommand": "apk add curl git openssh && npm i && npm i -D",
  "postAttachCommand": "npm run dev",
  "overrideCommand": true
}

Reference

devcontainerの公式リファレンス

devcontainerをわかりやすく解説した記事

ssh-agentの解説

8
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
10