これは何?
VSCodeのDev ContainersというDockerコンテナ内でVSCodeを開く機能を使っていて
- ビルド時間が長いこと
- サービスの再起動をするのに再ビルドが必要
の2つの問題を改善したら開発体験がよくなったので同じようなところでストレスを感じている人の助けになればと
対象リポジトリ
去年のアドベントカレンダーでも何回か登場したOpenRestyのリバースプロキシを対象にした。
変更前の状態
{
"name": "dev_container", // 任意
"dockerComposeFile": [
"../compose.yaml",
"compose.yaml"
],
"service": "reverse_proxy_app", // compose.yamlのサービス名
"workspaceFolder": "/usr/local/openresty",
// Dev Container起動時に開発ツールをインストール
"postCreateCommand": "./.devcontainer/install-pkg.sh",
"customizations": {
"vscode": {
"extensions": [
"DavidAnson.vscode-markdownlint",
"exiasr.hadolint",
"oderwat.indent-rainbow",
"ionutvmi.path-autocomplete",
"sumneko.lua",
"trixnz.vscode-lua",
"gccfeli.vscode-lua"
]
}
}
}
# Dev Containerではこちらが優先される。
services:
reverse_proxy_app:
build:
target: devcontainer
context: ./reverse_proxy
dockerfile: Dockerfile
image: reverse_proxy_devcontainer:latest
container_name: reverse_proxy-devcontainer-container
volumes:
- ./reverse_proxy/:/usr/local/openresty/reverse_proxy
# NOTE: install-pkg.shをコンテナに追加する
- ./.devcontainer:/usr/local/openresty/.devcontainer
redis_app:
build:
context: ./redis
dockerfile: Dockerfile
image: redis-img:latest
container_name: redis_container
ports:
- 6379:6379 # localport:dockerport
# NOTE: redis_appに初期データを投入するために一時的なコンテナを立ち上げている。
redis_client:
image: redis:latest
volumes:
- ./redis/initial_data_redis.sh:/tmp/initial_data_redis.sh
command: >
/bin/bash -c 'source /tmp/initial_data_redis.sh'
depends_on:
- redis_app
"postCreateCommand": "./.devcontainer/install-pkg.sh",
の部分で必要なライブラリをインストールしている。
#!/bin/bash
package_list="net-tools \
curl \
wget \
rsync \
unzip \
zip \
vim \
jq \
less \
git \
ca-certificates \
iputils-ping \
dnsutils \
iproute2 \
tcpdump \
procps
"
apt-get update -y
apt-get install -y --no-install-recommends ${package_list[@]}
rm -rf /var/lib/lists
# hadolint
wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/download/v2.10.0/hadolint-Linux-x86_64
chmod 755 /usr/local/bin/hadolint
# redis-cli
REDIS_VERSION=7.4.1
# NOTE: `./redis-cli -h redis_app -p 6379`で接続可能
wget https://github.com/redis/redis/archive/refs/tags/${REDIS_VERSION}.tar.gz
tar xzvf ${REDIS_VERSION}.tar.gz
cd redis-${REDIS_VERSION}
make
cp src/redis-cli /usr/local/bin
cd ..
rm ${REDIS_VERSION}.tar.gz
rm -rf redis-${REDIS_VERSION}
個人的に気になっていた部分は以下。
- Dev Containerでビルドすると通常のDockerビルドよりもかなり時間がかかる
- nginxの再起動をするためには再ビルドが必要: コンテナ永続化プロセスである,nginxを止めることになるのでコンテナが落ちてしまうため
ビルド速度の改善のためVolumeマウントをキャッシュ代わりに使う
このinstall-pkg.shの中でredisのビルドを行って,redis-cli
だけ抽出してパスを通すみたいなことをやっていたため,(redis-cliを単体でinstallする方法が多分ないので謎のこだわりを発揮していた)のでそこそこビルド時間がかかっていた。
Dev Container内でのみ実行するスクリプトには(多分)Dockerのレイヤキャッシュが効かないはず
TODO: 効かせる方法がないか今度調べてみる。
そこで,初回のビルド時に作成したredis-cliをvolumeマウント使ってみた。
自分はDev Container用のcompose.yamlを作っており,.devcontainer/cacheをコンテナの/cacheにvolumeマントする設定を追記した。
# NOTE: ./install-pkg.shでインストールしたパッケージをキャッシュするためのディレクトリ
- ./.devcontainer/cache:/cache
# redis-cli
setup_redis_cli() {
# 2回目以降のbuild時にはvolumeマウントしている/cacheをつかう
cp /cache/redis-cli /usr/local/bin/redis-cli
if command -v redis-cli > /dev/null 2>&1; then
return 0
fi
REDIS_VERSION=7.4.1
# NOTE: `./redis-cli -h redis_app -p 6379`で接続可能
wget https://github.com/redis/redis/archive/refs/tags/${REDIS_VERSION}.tar.gz
tar xzvf ${REDIS_VERSION}.tar.gz
cd redis-${REDIS_VERSION}
make
cp src/redis-cli /usr/local/bin
cp src/redis-cli /cache # NOTE: ビルド時間短縮のために/tmpをvolumeマウントしてキャッシュする
cd ..
rm ${REDIS_VERSION}.tar.gz
rm -rf redis-${REDIS_VERSION}
}
setup_redis_cli
この変更により,初回ビルド以外にはredisnビルドが走らなくなった。万歳!
そもそもビルドする回数を減らす: コンテナ永続化プロセスをnginxにするのをやめる
コンテナは起動しつづけるためには永続的なプロセスが必要である。
DockerfileのCMD
とかENTRYPOINT
とかに書いてあるあれ
自分はCLIツールを作る時のような永続化プロセスを持たないコンテナでDevContainerを使う時のみ,devcontainer.jsonに"overrideCommand": true
を使ってコンテナを永続化していた。
Dockerfileがnginxのような永続化プロセスを持っている際にはわざわざこの機能を使う意味はないなと思っていたのだが,以下の記事を見て,永続化プロセスを持っているDockerifileを扱う場合でも"overrideCommand": true
を使うべきだと知った。
-
"overrideCommand": true
でコンテナを永続化させる。 - 変わりにnginxはdevcontainer.jsonの中で
"postStartCommand": "openresty",
として起動させる
これを実施したことにより,nginxを再起動してもコンテナが止まることがなくなった。
そのため,実装を変更するたびにDev Containerを再ビルドする必要がなくなった。万歳!
まとめ
- Dev Container内でのみ実行するスクリプトには(多分)Dockerのレイヤキャッシュが効かないので,
- なるべく処理を減らす
- 重い処理はなるべく再実行しなくて済む方法を考える。
- コンテナ永続化プロセスはDev Containerの機能でOverrideしてやってもらう。変わりにサービスは通常起動することで止めてもコンテナが止まらなくなる。