ある日、Goを含むweb開発環境のDockerイメージを作ろうと思い立ちました。
実際に作成しようとしたのはnode.jsとGoの両方を含む開発環境なのですが、Goの部分でつまづいたところがあったので解決(したことにする)案を共有します。
想定する流れは以下の通りです。
- Dockerイメージを起動
- VS Codeでファイルを編集(Dockerコンテナの外側)
- Dockerコンテナ内側のAirでホットリロード
DockerもGoのセットアップも慣れているので、組み合わせるだけですぐできるだろうと思っていました。
つまづいたところ
Airがホットリロードしません。
docker exec
を使用して内側からecho "" >> main.go
などの変更を加えるとホットリロードします。
どうやらコンテナの外側からのファイル変更イベントがうまく走ってないようです。
環境
現象を確認したのは以下の環境です。
Macを使っている方やGo公式イメージを使用されている方の中にはAirがうまく動作している方もいるようなので、筆者の環境がよくない説はあります。
- PC環境
- OS
- Windows 10 Pro 22H2
- メモリ
- 16GB
- Docker
- version 20.10.14
- エディタ
- VS Code 1.75.1
- OS
- 仮想環境(Docker内)
- OS
- debian stable-20230202
- Go
- version 1.20
- Air
- version 1.41.0
- OS
解決(したことにする)案
Windows環境ではAirを使うのはやめて、PowerShellでファイルの変更を検知して、外側から通知する
ようにしました。
「それだと開発環境のセットアップをDockerで完結できるというメリットがなくなっちゃわない?」というド正論はあるのですが、以下二点の理由により容認できると考えています。
- WindowsであればPoweshellは標準搭載なので、複雑な構成にはなるが環境移行の手順は増えない
- 本番環境のイメージを作ってるわけでもないのに、時間をかけるのはもったいない
Docker内のLinuxに配置するシェル
Dockerfileのentrypointには以下のようなスクリプトを指定します。
環境変数DEVELOP_BUILD
にGORIOSHI
が入ってきたら、Airを使わずにゴリ押しでホットリロードします。
#!/bin/bash
export PATH=$PATH:/develop/app/go/bin
export GOPATH=/root/go/bin
export PATH=$PATH:/root/go/bin
if [ "$DEVELOP_BUILD" == "AIR" ]
then
# Airでホットリロード
echo Go Airで起動します
echo ソースコードは自動でホットリロードされます
air -c .air.toml
elif [ "$DEVELOP_BUILD" == "GORIOSHI" ]; then
# ゴリ押しホットリロード
# Airが意図したとおりに実行されない場合にはこれをつかう
echo ゴリ押しデバッグモードで起動します
echo ソースコードはビルドされ続けます
for ((i=0; 1 == 1; i++)); do
# ビルド&実行
bash /develop/script/go/run.sh
# 実行が終了すると1秒待って再度ビルド&実行しつづける
sleep 1
done
else
echo "環境変数'DEVELOP_BUILD'の値'${DEVELOP_BUILD}'を識別できません"
sleep 5
exit 1
fi
プロセスを終了するスクリプトを配置します。
#!/bin/bash
# 実行しているプロセス「main」を終了するスクリプト
pid=`pidof main`
if [[ "$pid" != "" ]]; then
echo "プロセスID$pidを停止しています..."
kill $pid
fi
ソースコードをビルドして起動するスクリプトを配置します。
tmp
フォルダの中にmain
という名前の実行ファイルを生成して実行します。
#!/bin/bash
# Goのソースコードをビルドして実行するスクリプト
#プロセスを終了
bash /develop/script/go/kill.sh
# フォルダ作成
if [ ! -d ./tmp ]; then
mkdir ./tmp
fi
# ビルド
echo ビルドしています...
go build -o ./tmp/main .
echo 起動します...
./tmp/main
Windows側で使用するスクリプト
3秒ごとに拡張子.go
と.html
の更新日時の変化を監視して、変化があった場合はDockerコンテナのkill.sh
を叩くように命令を送ります。
コンテナ内ではmain
のプロセスが停止すると、entrypoint.sh
のループが一周して、再度run.sh
が実行されます。
※PowerShellならファイル変更イベントをトリガーにすることもできますが、以前に別件で使用した際スパゲティコードになったので今回は不採用
$path = ${PSScriptRoot}
cd $path
$list = ''
while ($true) {
$prelist = $list
$list = (ls "${path}\src" -r | ?{$_.Extension -in '.go','.html'} | %{ "$($_.Name) $($_.LastWriteTime)" })
if ("$list" -ne "$prelist") {
$ErrorActionPreference = "silentlycontinue"
docker-compose exec develop bash /develop/script/go/kill.sh
$ErrorActionPreference = "continue"
}
sleep 3
}
完成
以下のようにホットリロードができるようになりました。
Dockerイメージ
DockerHubにイメージをアップロードしたほか、GitHubにサンプル環境をアップロードしました。
git clone
していただいて、serve.cmd
を実行していただければホットリロード用のウィンドウとコンテナのウィンドウが開きます。
src
フォルダ内のコードを改変するとホットリロードします。
https://github.com/HoppingGanon/gorioshi-air
以上です。
本当にゴリ押しコードばかりになっているので、根本的な原因のわかる方はコメントで教えていただけると嬉しいです。
駄文にお付き合いいただき、ありがとうございました。