makeでWebサービスを管理したい

# はじめに

「秘伝のタレ」で google 検索すると、秘伝のタレを売ってる広告が出てきて、ついつい「秘伝　とは」で検索してしまいましたね。

## TL;DR

- `Makefile` と `make` コマンドを使って、サービスの管理をしたいなぁという内容です。
- `秘伝のタレ` になるからこういう形の管理方法は良くないという話もあるのですが、シェルスクリプトで管理するよりは秘伝化の速度は遅いのではと思っています。
- 開発、ステージング、本番といった環境の違いをどのように吸収すべきかについては全然煮詰まっていないが、煮詰まったら秘伝のタレになりそう。

## Makefileとかmakeってなんだ

そもそも Makefile とは https://e-words.jp/w/Makefile.html によると

> Makefileとは、UNIX系OSで一般的なビルドツールである「make」（makeコマンド）に実行させたい手順を記述したテキストファイル。

とのことで、本来はソースからプログラムをコンパイルしたり、それらをインストールしたりするための処理を記述するファイルです。

## シェルスクリプトとかではダメなのか

サービスとかアプリとかの操作をするだけなら、シェルスクリプトでいいんじゃないの？なんでわざわざ Makefile なの？というのはもっともな疑問だと思いますし、もちろん、ダメということはないです。

ですが、個人的には以下のような理由から Makefile のほうがちょうどいい不便さなのかなと思います。

- make コマンド自体が他の理由によりインストールされている可能性が高いため、(Windowsじゃないなら)動かすための障壁自体は低い。
- シェルスクリプトに比べると記法や呼び出し方の縛りが強いため、`なんでもできちゃう`・`なにが書かれているのかさっぱりわからない` という状況になりにくい。

## Makefileのイメージ
ざっくりとこんなイメージです。ローカル開発環境の管理という感じで

- docker コンテナを管理する。
- web という名前の php が動いて と laravel が入っているようなコンテナがある。
- db という名前の何らかのデータベースが動いているコンテナがある。
- 基本の docker-compose.yml とは別に docker-compose.override.yml などが用意されている。 (例えば開発用にポートがより公開されるなどの記載がある)

みたいなプロジェクトだとお考えください。

```makefile
.DEFAULT_GOAL := help
TARGET=
DOCKER := docker-compose 

.PHONY: help
help: ## Display this help screen.
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

.PHONY: build
build: ## Build coutainer images. If TARGET=web, only the web container will be built.
	${DOCKER} build ${TARGET}

.PHONY: build-clean
build-clean: ## Build coutainer images without cache. If TARGET=web, only the web container will be built.
	${DOCKER} build --pull --no-cache ${TARGET}

.PHONY: install
install: ## Install applications. If .env file exists, it will be skipped. 
	@if [ -e ".env" ]; then \
		echo "The application is already installed. The installation process will be skipped."; \
	exit 1; \
	fi
	@touch .env
	@echo APP_URL=http://localhost/ >> .env
	@echo APP_KEY= > ../src/.env
	@cp compose/docker-compose.override.yml.forinstall docker-compose.override.yml
	${DOCKER} up -d --build
	${DOCKER} exec web composer install --ignore-platform-reqs
	${DOCKER} exec web php artisan key:generate
	@${DOCKER} stop
	@cat ../src/.env >> .env; rm docker-compose.override.yml ../src/.env
	@${DOCKER} up -d
	${DOCKER} exec web php artisan storage:link
	@make migrate-fresh
	chmod 777 ../src/bootstrap/cache/
	chmod 777 ../src/storage/app/public/
	chmod 777 ../src/storage/framework/cache/
	chmod 777 ../src/storage/framework/cache/data/
	chmod 777 ../src/storage/framework/sessions/
	chmod 777 ../src/storage/framework/testing/
	chmod 777 ../src/storage/framework/views/
	chmod 777 ../src/storage/logs/

.PHONY: destory
destroy: ## Destory container, images and volumes.
	${DOCKER} down --rmi all --volumes
	rm -rf ../src/vendor
	rm -f ../src/storage/logs/*.log
	rm -f .env

.PHONY: reinstall
reinstall: ## Install application after destroy.
	@make destroy
	@make install

.PHONY: up
up: ## Up container images.
	@${DOCKER} up -d --remove-orphans

.PHONY: down
down: ## Down container images.
	@${DOCKER} down

.PHONY: restart
restart: ## Restart containers.
	@make down
	@make up

.PHONY: logs
logs: ## Show container's log. If TARGET=web, only the web container's log will be shown.
	@${DOCKER} logs -f ${TARGET}

.PHONY: web
web: ## Run Bash in php-apache container.
	@${DOCKER} exec web bash

.PHONY: web-reload
web-reload: ## Reload apache2 service in container.
	@${DOCKER} exec web service apache2 reload

.PHONY: composer-update
composer-update: ## Run `composer update`
	@${DOCKER} exec -e COMPOSER_MEMORY_LIMIT=-1 web composer update

.PHONY: migrate
migrate: ## Migrate database.
	@${DOCKER} exec web php artisan migrate
	@${DOCKER} exec web php artisan migrate --env=testing

.PHONY: migrate-fresh
migrate-fresh: ## Migrate database with fresh.
	@${DOCKER} exec web php artisan migrate:fresh
	@${DOCKER} exec web php artisan migrate:fresh --env=testing
	@make seed

.PHONY: seed
seed: ## Seeding database.
	@${DOCKER} exec web php artisan db:seed
	@${DOCKER} exec web php artisan db:seed --class=DevelopDataSeeder
	@${DOCKER} exec web php artisan db:seed --env=testing

.PHONY: autoload
autoload: ## Create autoload files.
	@${DOCKER} exec web composer dump-autoload

.PHONY: mainte-down
mainte-down: ## Enable maintenance mode.
	@${DOCKER} exec web php artisan down

.PHONY: mainte-up
mainte-up: ## Disable maintenance mode.
	@${DOCKER} exec web php artisan up

.PHONY: db
db: ## Run Bash in Database container.
	@${DOCKER} exec db bash
```

## 細かい話

### make 特有の指定

`.DEFAULT_GOAL` make とだけ打って実行した場合に実行されるターゲットを記載します。この場合は make とだけ打って実行すると make help が実行されます。

`.PHONY` ディレクトリにターゲットと同じ名前のファイルやフォルダがあるとうまく動かないため、それを避けるための記述になります。

### make help

```makefile
.PHONY: help
help: ## Display this help screen.
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
```

ターゲットの後ろにある ## 以降を表示させることができます。この処理自体はぐぐるといっぱい出てくるので、どこが出展なのかもはやわかりません……。

実行すると以下のような出力ができます。

```shell
$ make help
help                 Display this help screen.
build                Build coutainer images. If TARGET=web, only the web container will be built.
build-clean          Build coutainer images without cache. If TARGET=web, only the web container will be built.
install              Install applications. If .env file exists, it will be skipped. 
destroy              Destory container, images and volumes.
reinstall            Install application after destroy.
up                   Up container images.
down                 Down container images.
restart              Restart containers.
logs                 Show container's log. If TARGET=web, only the web container's log will be shown.
web                  Run Bash in php-apache container.
web-reload           Reload apache2 service in container.
composer-update      Run `composer update`
migrate              Migrate database.
migrate-fresh        Migrate database with fresh.
seed                 Seeding database.
autoload             Create autoload files.
mainte-down          Enable maintenance mode.
mainte-up            Disable maintenance mode.
db                   Run Bash in Database container.
```

## その他

その他の部分は特別 make 的な何かがあるわけではありません。

コンテナの操作をしたり、コンテナ内のWEBアプリケーションの操作をしたり、と行った内容になります。

## というわけで今回のオチ

今回は、ローカル開発環境をイメージした内容を書きましたが、ステージングだったり本番環境だったりでは、そもそも許可されるターゲットや動作の内容が異なるはずです。（例えばローカルはdockerのコンテナ管理だけど、本番はnginxを管理するなど）

そうなったときに、この Makefile での管理をどう行うかについては、まだ私の中でビシッとくるものがありません。

- 開発なのか本番なのかを示す環境変数を定義し、それによって処理を切り替える。
  - ファイルは一つで済むが、Makefile自体が複雑になるため秘伝化が強まるのではという懸念あり。
- そもそも Makefile 自体の配置場所を変えてしまう
  - 実行する場所が環境によって違うこと自体がリスクを伴う。

結局の所、銀の弾丸はまだ見つかってないというオチてないオチで今回はここまで。