将每个Docker Compose容器的配置设置都从一个env文件中注入
这次的目的
-
- 開発環境でdockerを利用することで開発環境を統一できるようにはなるものの、laravelなど設定ファイルの記述が多いものや、コンテナ自体が多い場合などにdockerを立ち上げるまでにいろいろ設定ファイルを記述する必要があります。
- これが少し面倒だなと感じ、自動的に注入できるようにはできないかと考えました(単なる横着です?)。
前提知識 (qian ti zhi shi)
-
- Dockerのcompose.ymlの記述方法
- 使用したいアプリケーションの設定ファイルの記述方法
示例项目结构
myproject(root) /
├─ .vscode /
│ ├─ setting.json
│
├─ apps /
│ ├─ api / # laravel用のコンテナ
│ ├─ scripts /
│ ├─ entrypoint.sh
│ ├─ templates /
│ ├─ .env.template
│ ├─ myproject-api /
│ ├─ .env
│ ├─ その他laravelディレクトリ群
│ ├─ Dockerfile
│ ├─ docker.conf
│ ├─ php.ini
│ ├─ web / # react(vite)用のコンテナ
│ ├─ scripts /
│ ├─ entrypoint.sh
│ ├─ templates /
│ ├─ .env.local.template
│ ├─ myproject-api /
│ ├─ .env.local
│ ├─ その他reactディレクトリ群
│ ├─ Dockerfile
│
├─ postgres /
│ ├─ Dockerfile
│
├─ nginx-proxy /
│ ├─ frontend
│ ├─ sh
│ ├─ entrypoint-nginx.sh
│ ├─ templates
│ ├─ default.conf.template
│ ├─ log /
│ ├─ backend
│ ├─ sh
│ ├─ entrypoint-nginx.sh
│ ├─ templates
│ ├─ default.conf.template
│ ├─ log /
│ ├─ sh
│ ├─ entrypoint-nginx.sh
│ ├─ templates
│ ├─ default.conf.template
│ ├─ domain_list.template
│ ├─ log /
│ ├─ .gitignore
│
├─ .env
├─ .env.example
├─ .dockerignore
├─ .gitignore
├─ compose.yml
└─ README.md
- 汎用的に使えることを示すためにnginxの構成など少し複雑になってはいますが、単純化(nginxプロキシを一つにするなど)も自由に変えてください。
实施过程
-
- イメージしやすいようにまず実装のある程度の流れを説明した後に実際のコード記述に入っていきます。
通常通り、Dockerfileやcompose.yml、その他必要なファイルを構成していく。
プロジェクト直下に.envファイルを作成して、プロジェクト全体での設定を記載していく。
この.envを各々のコンテナに適用させることでこれがコンテナ内の環境変数として渡される。
後はこれを起動時に実行するシェルスクリプトと各アプリケーションの設定ファイルのテンプレートを書いていく。
わかりにくいところがあると思うので、githubにてサンプル用のコードを貼っておきます。
サンプルコード
项目的启动
项目的克隆
git clone https://github.com/ablankz/docker-env-injection.git
预先准备和追加hosts文件
今回は作成するプロジェクトをmyproject、api のプロジェクト名はmyproject-api、web のプロジェクト名はmyproject-webとします
また使用するドメインは、web フロントをmyproject.com、api サーバーをapi.myproject.comとします
今回、localで動作するように設定するのですが、本番でもそのまま使えるコード(あくまでアプリケーション内のコード)にするために、ドメインもwebはmyproject.com、apiはapi.myproject.comのように動作するようにしたいのでまずはこれらの名前解決をlocalで行わせるようにします。
unix系の場合なら/etc/hostsを編集。windowsなら[共通] hostsファイルの編集(WindowsOSのPCの場合)など多く記事が上がっているのでこれを確認して以下のような設定を追記してください(元の記述は残しておいてください)。
127.0.0.1 myproject.com
127.0.0.1 api.myproject.com
创建一个API服务器的项目。
apps/apiまで移動した後、以下のコマンドでプロジェクトを作成する
myproject-apiの部分は先程決めたapiのプロジェクト名が入る
composer create-project laravel/laravel –prefer-dist myproject-api
创建一个Web服务器项目
apps/webまで移動した後、以下のコマンドでプロジェクトを作成する
projectが聞かれたときに先程決めたwebのプロジェクト名を入力する
他の設定は基本的には以下のような設定で良さそう
npm create vite
Need to install the following packages:
create-vite@4.4.1
Ok to proceed? (y) y
✔ Project name: … myproject-web
✔ Select a framework: › React
✔ Select a variant: › TypeScript + SWC
Scaffolding project in /home/blank/docker-env-injection/apps/web/myproject-web…
Done. Now run:
cd myproject-web
npm install
npm run dev
ターミナルにも書いてくれてるコマンドを実行する(npm run devは今はいらない)
cd myproject-web
npm install
apps/web/myproject-web/vite.config.tsを以下のように書き換える(docker内でhotreloadを解決するため)
import { defineConfig } from ‘vite’
import react from ‘@vitejs/plugin-react-swc’
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: true,
watch: {
usePolling: true,
},
hmr: {
path: “_vite/ws-hmr”,
},
},
})
环境文件的记录
- プロジェクト直下の.envにコメント記述通りの設定を記載していく
创建容器
docker compose up -d
当失败时(或容器无法启动时)
-
- 大抵の場合はwebやapiの作成したプロジェクト名と.envで設定したプロジェクト名が異なったり、hostsで設定したドメインと.envで記述したドメインが異なるなどだと思います
-
- 以下のような感じのエラーが出た場合
-
- Error response from daemon: error while mounting volume ‘/var/lib/docker/volumes/docker-env-injection_node_modules/_data’: failed to mount local volume: mount /path/to/project/apps/web/myproject-web/node_modules:/var/lib/docker/volumes/docker-env-injection_node_modules/_data, flags: 0x1000: no such file or directory
.envのAPP_ROOT_PATHの記述ミスだと思うのですが、このエラーになるとdockerがvolumeを記憶させてしまうらしく、通常は.envを修正後、docker compose downした後、docker compose up -dをすると良い(docker compose restartでも良い)のですが、この場合は一度以下のコマンドでvolumeも削除してから再度startを行ってください(もちろん.envの修正を行ったあとで)
docker compose down –volumes –remove-orphans
重要代码的阅读部分
- 実装の流れを追っていきたいところですがファイル数が多いため、お手数ですが必要な設定ファイルなどだけ追っていくので、残りはgithubのリポジトリの方をご確認ください?
compose.yml 和 .env 文件
-
- プロジェクト直下の.envファイルは以下のように記述します
.env
変数名=値
####### 以下に例を示す ######
# プロジェクトのrootパス
# unix系ならプロジェクト直下でpwdで確認したものを貼る
APP_ROOT_PATH=/path/to/project
# デフォルトのデータベースの設定
POSTGRES_DB=myproject # はじめに作るdb(基本的にはプロジェクト名でok)
POSTGRES_USER=postgres # 基本的には変更なし
POSTGRES_PASSWORD=postgres # 基本的には変更なし
POSTGRES_PORT=5432 # 基本的には変更なし
…
その.envファイルをcompose.ymlの以下の部分などでenvfileに設定しています
compose.yml
web: # reactWebサーバー
container_name: ${APP_NAME}-web
tty: true
build:
context: “./apps/web”
volumes:
– ./apps/web/${WEB_APP_NAME}:/usr/src/app
– node_modules:/usr/src/app/node_modules
– ./apps/web/scripts:/docker-init/scripts
– ./apps/web/templates:/docker-init/env_templates
environment:
– TZ=Asia/Tokyo
– CHOKIDAR_USEPOLLING=true
env_file: ./.env # ?ここ
command: sh -c “chmod +x /docker-init/scripts/entrypoint.sh && /docker-init/scripts/entrypoint.sh”
これを設定したコンテナでは環境変数としてこのenvファイルの変数たちがはじめから設定されているような感じになります。
模板和脚本
-
- 次にこの環境変数をもとにそれぞれの設定ファイルを記述する必要があるのですが、はじめに実行できるコマンドを設定するのは後述しますが、ここでは例としてnginx-proxy/backend/shとnginx-proxy/backend/templatesについて説明します。
- おそらく1コンテナさえ理解できればあとは、templateだけが変わって残りは同じです
nginx反向代理/后端/模板/default.conf.template
server {
listen ${BACKEND_PROXY_PORT};
listen [::]:${BACKEND_PROXY_PORT};
server_name "${PROXY_SERVER_BACKEND}";
root /var/www/${API_APP_NAME}/public;
...
}
-
- このファイルは単に埋め込みたい環境変数の箇所を${BACKEND_PROXY_PORT}のように記述しているだけのtemplateファイル。
-
- これと次に説明するsh/entrypoint-nginx.shはcompose.ymlの以下の部分でvolumeに登録しているのでこのファイルは起動時、コンテナにマウント?される
compose.yml
…
backend-server:
container_name: ${APP_NAME}-backend-server
image: nginx
volumes:
– ./apps/api/${API_APP_NAME}:/var/www/${API_APP_NAME}
– ./nginx-proxy/backend/templates:/etc/nginx/conf.d/templates # ?ここ
– ./nginx-proxy/backend/sh:/etc/nginx/conf.d/sh # ?ここ
…
nginx代理/后端/脚本/入口-nginx.sh
#!/bin/bash
expand_variables() {
local content
content=$(< "$1") # テンプレートファイルの内容を読み込む
# 環境変数を正規表現で検索し、対応する値で置き換える
for var in $(compgen -A variable); do
value="${!var}" # 環境変数の値を取得
content="${content//\$\{$var\}/$value}" # 置換
done
echo "$content" > "$2"
}
expand_variables /etc/nginx/conf.d/templates/default.conf.template /etc/nginx/conf.d/default.conf
nginx -g 'daemon off;'
-
- 簡単に言えば、/etc/nginx/conf.d/templates/default.conf.templateファイル(compose.ymlにてvolumesに登録済み)の${変数名}となっている部分に環境変数のその変数名を埋め込んで/etc/nginx/conf.d/default.conf(nginxの設定ファイルを記述する場所)に貼り付けるというシェルスクリプトです。
-
- 例えば${変数名}の形がいやな場合はtemplateは別の形式の$\変数名\などにしたりして、このスクリプトファイルのexpand_variables関数の中身を変更すればok。
-
- ここで重要なのは一番最後の行のnginx -g ‘daemon off;’。これはnginxを立ち上げるためのコマンドです。
実はdockerではコマンドは一つしか登録することができず、nginxのimageでコンテナを立ち上げた場合、これがコマンドに登録されているわけです。
しかし、このシェルスクリプトを後述するcompose.ymlにてcommandに登録すると、このshスクリプトがこのコンテナのコマンドとなり、本来実行されるべきnginx -g ‘daemon off;’が実行されず、最終的に実行するべきプロセスがなくなり、コンテナは落ちてしまいます。
nginxではこのコマンドですが、他のコンテナでこの方法を利用するときはこのことに十分注意してください。
またexpand_variables関数では上書き>で上書きしているのでもとの/etc/nginx/conf.d/default.confの設定は残らないことになります。
これの解決策としてはapi(laravel)の方のこのファイルを見ていただければ確認できると思います。
apps/api/scripts/entrypoint.sh
#!/bin/bash
expand_variables() {
# 省略
}
# envの作成
app_key=`grep ‘^[[:space:]]*APP_KEY=’ /var/www/${API_APP_NAME}/.env`/.env`
expand_variables /docker-init/env_templates/.env.template /var/www/${API_APP_NAME}/.env #それ以外を追記
echo ${app_key} >> /var/www/${API_APP_NAME}/.env # key保存
# fpmの起動
sh -c “/usr/local/sbin/php-fpm”
laravelではAPP_KEYを保存しとくべきなのですが、何もしなければただ上書きされてこの情報が消えてしまいます。
そこでAPP_KEY=から始まる行を元の/var/www/${API_APP_NAME}/.envから探してきて、見つかれば、それをapp_keyに保存。
expand_variablesで上書きした後に今回は>>でapp_keyに保存された行を追記しています。
保存はこのように行い、他にも様々な設定ファイルに合わせてこのシェルスクリプトをカスタマイズしていけば、コンテナごとにうまく設定できるはずです
将命令设置到容器中
backend-server:
...
volumes:
- ./apps/api/${API_APP_NAME}:/var/www/${API_APP_NAME} # laravelのアプリケーションサーバーにはアクセスせず、同じディレクトリ参照するだけ
- ./nginx-proxy/backend/templates:/etc/nginx/conf.d/templates
- ./nginx-proxy/backend/sh:/etc/nginx/conf.d/sh
- ./nginx-proxy/backend/log:/var/log/nginx
restart: always
command: sh -c "chmod +x /etc/nginx/conf.d/sh/entrypoint-nginx.sh && /etc/nginx/conf.d/sh/entrypoint-nginx.sh" # ?ここ
...
-
- 最後はコンテナ立ち上げ時に毎回このシェルスクリプトを実行するようにします
- まずコンテナ内にvolumeで置かれただけではshの実行権限がないので、まずは権限を付与します
在Chinese中翻译如下:
不需要每次运行,权限授予不是通过volume,而是在Dockerfile中,首次构建完成后,通过复制sh文件并在运行时授予权限来实现,但是如果sh文件发生变化,则需要重新构建,但考虑到该处理过程并不重负重,因此将其设定为每次都执行。
- そしてそのshを実行
试过之后的感受
-
- これでコンテナを立ち上げる度に各コンテナのtemplateとプロジェクト直下の.envのみからコンテナごとの設定ファイルを自動注入できるようになりました
- こういった設定ファイルは通常、GitHubなどに挙げるわけにもいきませんが、templateをあげるのにはなんら問題ないため、プロジェクト開始後は複数人で開発するにしても設定ファイルを一つ書くだけで済むようになるというのが意外と便利だなと思ってます
请注意
-
- コンテナすべてに.envに記載した設定情報が環境変数として組み込まれる(これがはじめに言ったセキュリティの問題)
もともと開発環境のために作った構成なので、本番環境では使用しない
もしくはapi.envやweb.envなど、設定ファイルを分けてenvfileの指定にそれぞれ設定すれば良いが、これだとそれぞれの設定ファイルを記述するのと変わらない気がする?
例えば、laravelのenvファイルを変更した際、そのコンテナが立ち上がっている際はその設定が保たれるが、一度落とすと.env.templateの設定に合わせて.envの内容が消えるので注意
laravelのAPP_KEYなど残したい設定がある場合はapps/api/scripts/entrypoint.shにて以下のように記述する
…
app_key=`grep ‘^[[:space:]]*APP_KEY=’ /var/www/${API_APP_NAME}/.env` # APP_KEYの値をapp_keyに保存
expand_variables /docker-init/env_templates/.env.template /var/www/${API_APP_NAME}/.env # templateを元に環境変数を埋め込み追記
echo ${app_key} >> /var/www/${API_APP_NAME}/.env # keyを後から追記
…
postgresサーバーなどdocker内のネットワークで閉じているのでコンテナの外だとからphp artisan migrate:freshなどデータベースアクセスコマンドが失敗する
portをコンテナ外部に出した後、laravelの設定テンプレートのdb_hostやdb_portをコンテナ内部から外に出たものに変更する
もしくは毎回、コンテナ内に入ってから実行する、僕は以下のようなスクリプトを作成して簡単にartisanコマンドを実行できるようにしています
container-artisan.sh
#!/bin/bash
args=(“$@”)
docker compose exec api php artisan “${args[@]}”
sudo chmod -R a+w ./database
sudo chmod -R a+w ./app
sudo chmod -R a+w ./config
sudo chmod -R a+w ./tests
./container-artisan migrate:freshのように使用できる