Makefile で環境構築を確実に一瞬で終わらせる話
はじめに
ラブグラフ 開発チーム インターン の こるく です。
私がラブグラフに Join してまず感動したのが、コマンド一発で完了する超お手軽な環境構築でした。
普通プロジェクトに Join するときは面倒な環境構築をする必要がありますが、ラブグラフではそれが全くありませんでした。
ということで今回は、それを実現している Make と Docker を使って、開発、テスト、CI、本番のすべての環境で、ランタイムの環境と環境変数の設定をすべてコードベース ( IaC というやつ? ) でラクに共有して開発体験を爆アゲしようと思います。
この構成が目指すところ
✅ 環境で悩むことをなくして開発体験を爆アゲする
✅ 環境構築をコマンド一発でできるようにする
✅ ついでにテストもコマンド一発でできるようにする
✅ 環境変数をホストマシンのシェルから排除し、コードの一部としてリポジトリ内で管理する
✅ 環境変数をセットすることなく CI ( GitHub Actions ) と本番環境でも開発環境と全く同じ環境を用意する
ココがつらいよ環境構築
環境変数はつらいよ
エンジニアなら絶対に避けて通れない環境変数ですが、これが結構開発体験を悪くしています。
というのも、環境変数はその名の通り環境ごとにセットの方法が異なる上に、Git でのバージョン管理が難しいからです。
例えばローカル環境ではシェルの設定ファイルに環境変数を定義しますが、GitHub Actions では GitHub の Web UI から設定します。
環境ごとにこのような差が生まれるせいで、シンプルに面倒ですし、おま環の原因になりがちです。
今回はこれを .env ファイルを複数作ることで解決したいと思います。
CI もつらいよ
ある程度プロジェクトの規模が大きくなってくると、テストを行う必要が出てきます。
しかも、そのテストをするために DB が必要になったりします。
これがかなり厄介です。
今回はこれを Make と Docker Compose を使うことで解決します。
結論 ( Django の例 )
ディレクトリ構造
.
├── Makefile
├── docker
│ ├── postgres
│ │ ├── Dockerfile
│ │ └── initdb
│ │ └── 01.sql
│ └── python
│ └── Dockerfile
└── compose.yml
Makefile
ifneq (,$(wildcard ./.env.production))
include .env.production
export
else
ifneq (,$(wildcard ./.env.local))
include .env.local
export
endif
.PHONY: build
build:
docker compose build
.PHONY: up
up:
docker compose up -d --build
$(MAKE) online_migrate
$(MAKE) logs
.PHONY: down
down:
docker compose down
.PHONY: logs
logs:
docker compose logs -f
.PHONY: online_migrate
online_migrate:
docker compose run --rm django bash -c "python manage.py migrate"
.PHONY: flake8
flake8:
docker compose run --rm api bash -c "python run_command.py flake8 ./"
.PHONY: mypy
mypy:
docker compose run --rm api bash -c "python run_command.py mypy ./"
.PHONY: black
black:
docker compose run --rm api bash -c "python run_command.py black ./"
.PHONY: black_check
black_check:
docker compose run --rm api bash -c "python run_command.py black ./ --check"
.PHONY: isort
isort:
docker compose run --rm api bash -c "python run_command.py isort ./"
.PHONY: isort_check
isort_check:
docker compose run --rm api bash -c "python run_command.py isort ./ --check-only"
.PHONY: pytest_html
pytest_html:
docker compose run --rm api bash -c "python run_command.py pytest -v ./test/ --cov=./src/ --cov-report=html --html=report.html"
.PHONY: pytest_xml
pytest_xml:
docker compose run --rm api bash -c "python run_command.py pytest -v ./test/ --cov=./src/ --cov-report=xml"
.PHONY: pytest_ci
pytest_ci:
docker compose run --rm api bash -c "python run_command.py pytest -v ./test/ --cov --junitxml=pytest.xml --cov-report=term-missing:skip-covered | tee pytest-coverage.txt"
.github/workflows/python.yml
name: Python CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
IMAGE_CACHE_DIR: /tmp/cache/docker-image
IMAGE_CACHE_KEY: cache-image
jobs:
build:
name: Build docker image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
id: checkout
uses: actions/checkout@v2
- uses: satackey/action-docker-layer-caching@v0.0.11
continue-on-error: true
- name: Docker build
run: make build
flake8:
name: Run flake8
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v2
- uses: satackey/action-docker-layer-caching@v0.0.11
continue-on-error: true
- name: Run flake8
run: |
make flake8
mypy:
name: Run mypy
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v2
- uses: satackey/action-docker-layer-caching@v0.0.11
continue-on-error: true
- name: Run mypy
run: |
make mypy
pytest:
name: Run pytest
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v2
- uses: satackey/action-docker-layer-caching@v0.0.11
continue-on-error: true
- name: Run pytest
run: |
make pytest_ci
compose.yml
services:
postgres:
build:
context: .
dockerfile: ./docker/postgres/Dockerfile
container_name: "back-postgres"
networks:
back-nw:
ports:
- "5432:5432"
volumes:
- ./docker/postgres/initdb:/docker-entrypoint-initdb.d
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
tty: true
django:
build:
context: .
dockerfile: ./docker/python/Dockerfile
container_name: "back-django"
networks:
back-nw:
ports:
- "8000:8000"
volumes:
# .venv をマウントしないようにするために Volume として分離させる
- django-venv:/deploy/.venv
- ./:/deploy
working_dir: /deploy
command: >
bash -c "
python manage.py migrate &&
python manage.py runserver 0.0.0.0:8080
"
restart: unless-stopped
tty: true
networks:
back-nw:
driver: bridge
ipam:
driver: default
volumes:
postgres-data:
driver: local
django-venv:
driver: local
使い方
ローカルでも本番でも CI でも全く同じコマンドを使えるような構成になっています。
起動 / 停止 / ログ閲覧
すべてのサービスを 起動 / 停止 / ログ閲覧 します。
make up # 起動
make down # 停止
make logs # ログ閲覧
Lint / Test
make up
コマンドでサービスが起動されていれば、どの環境でも以下のワンライナーで全て完了します。
make flake8 # flake8
make mypy # mypy
make pytest # pytest
その他
実行したいコマンドを Makefile
に追加して、コマンドを記述すると便利です。
あとがき
なんと今回は こるく@ラブグラフ として 2 回目の記事です。
私は飽き性なのでアウトプットを習慣にすることができなかったのですが、ラブグラフの開発チームでは通称Zenn 記事タイムという時間があり、なんとか 2 回目のアウトプットに成功しました。
ラブグラフの開発チームでは勉強会や輪読会など、エンジニアの自己成長の場が用意されていてとても楽しく働けています!
( そう書けと言われたわけじゃないです!!!本心です!!! )
ラブグラフの技術ブログの裏話も併せてどうぞ!
Discussion