はじめに
この記事は BBSakura Networks Advent Calendar 2023 の 21 日目の記事です。 adventar.org
こんにちは、BBSakura Networks のシステム管理部に所属している蟹江(@kanix2929)です。普段は BBIX から委託されているシステムなどの開発・運用がメインで、ネットワークエンジニア / オペレーターを手助けするためのシステム構築・自動化に尽力しています。
最近、複数人で同時並行的に 1 つのコードを触る場合が増えてきたことから、あらためてチーム内で開発プロセス(GitHub の運用方法など)についてまとめたので紹介してみます。
環境について
- 本番環境の構成
- VM 上で Docker コンテナを起動させている
docker-compose.yml
でコンテナ管理している
- コンテナが動いているサーバーはプロキシ配下
- VM 上で Docker コンテナを起動させている
- ステージング環境の構成
- 本番環境を模している(データだけ違う)
- CI/CD ツールとして Github Actions を主に使っている
- コード管理に GitHub を使用しているので運用が楽
開発の流れとルール
ざっと絵にするとこんな感じ
流れを言語化すると
- コードを修正してプッシュし、
main
ブランチへのプルリクを作成- プルリク作成時にブランチ名のタグを打った Docker イメージが自動生成され、Docker Hub にプッシュされる(
build-image.yml
) - 後述の Release Please のためにプルリクのタイトルは Conventional Commits に従う必要あり
- タイトルをチェックするワークフローも作成済み(
lint-PR-title.yml
)
- タイトルをチェックするワークフローも作成済み(
- プルリク作成時にブランチ名のタグを打った Docker イメージが自動生成され、Docker Hub にプッシュされる(
- プルリク作成者が自身でステージング環境にて動作確認
- ステージングのコンテナを入れ替えるときは Slack で一声かける
- 自身での動作確認後、チームメンバーにレビュー & 動作確認を依頼する
- レビュアーは該当プルリクが問題ないことを確認して、プルリクをマージする
- 基本的にはレビュアーがマージする
- プルリクのマージは
Squash and merge
- リポジトリの設定でも制限
- マージすると
main
タグの Docker イメージが生成される(build-image.yml
) - マージ時に Release Please のワークフローも動いて Bot がプルリクを生成するので確認してマージする
- GitHub 上で Semantic Versioning に従ってタグが打たれる
- 上記 Version をタグ名とした Docker イメージが自動生成され、Docker Hub にプッシュされる(
build-image.yml
)
- 本番へリリース
- コンテナを立ち上げるのは基本人手
- デプロイを自動化しても良いが、結局確認が必要なので
- みんなが使っているステージング環境において、どのタイミングでコンテナ立ち上げ直していいのかちょっと決めづらいのでそちらも人手
もろもろの採用理由
Squash and merge
の理由- 開発していくときのコミットがある程度適当でも他人に影響が無い
- 開発する人が開発しやすい粒度でコミットしたい
Create a merge commit
だと、コミット粒度についてチームで統一しておかないと逆に見づらくなると思っている- もしバグ含んだプルリクをマージしてしまった場合は、該当プルリクをまるっと
Revert
するかFix
のコミットを当てる
- 「1 機能 1 プルリク」にすることで、「1 機能 1 コミット」となるので、コミット履歴がスッキリして見やすい
- プルリクのタイトルだけ見れば良いので、
release-please.yml
でのバージョン管理が楽
- 開発していくときのコミットがある程度適当でも他人に影響が無い
- プルリク作成時にイメージを作成する理由
- 各人がステージング環境での動作確認をしやすくするため
おわりに
GitHub Actions をメインとした CI/CD についての紹介は今さら感ありますが、まだ GitHub の運用がざっくりしているチームの方たちの参考になればなあ、と思っています。 今回紹介した方法が完成形というわけではないので、これからも柔軟に運用をアップデートしていく予定です。 また、みなさんの開発体制で「もっと良い方法があるよ」ということがあれば、ぜひ教えていただきたいです!
[参考]
参考として yml ファイルをいくつか貼っておきます。
build-image.yml
単純に Docker イメージを生成するための yml ファイルです。
name: build-and-push-to-dockerhub env: IMAGE_NAME: hogehoge on: workflow_dispatch: push: branches: - "main" tags: - 'v[0-9]+.[0-9]+.[0-9]+*' pull_request: branches: - "main" jobs: docker: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set TAG run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then TAG=$(echo "${{ github.head_ref }}" | tr '/' '-') else TAG=$(echo "${{ github.ref_name }}" | tr '/' '-') fi echo "TAG=${TAG}" >> $GITHUB_ENV - name: Docker meta id: meta uses: docker/metadata-action@v4 with: images: ${IMAGE_NAME} - name: Login to DockerHub uses: docker/login-action@v3 with: username: fugafuga password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v4 with: context: . push: true tags: "${{ env.IMAGE_NAME }}:${{ env.TAG }}" labels: ${{ steps.meta.outputs.labels }}
ちなみに
次のように、本番環境とステージング環境でイメージビルド時に使う変数だけ変更したい場合には、スクリプトで無理やり埋め込んだりしています。
- Docker でマルチステージビルドを使っているのでイメージのビルドとコンテナの起動のステージが違う
NEXT_PUBLIC_*
のようなビルド時に展開される変数を使っている- 本番環境とステージングで別の変数を使いたい
参考として yml はここに折りたたんでおきます。
タグごとに変数を指定する Docker イメージを生成するための yml ファイルです。
name: build-image-and-push-to-dockerhub env: IMAGE_NAME: hogehoge on: workflow_dispatch: push: branches: - main tags: - 'v[0-9]+.[0-9]+.[0-9]+*' pull_request: branches: - main jobs: build-image: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v4 - name: Set TAG run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then TAG=$(echo "${{ github.head_ref }}" | tr '/' '-') else TAG=$(echo "${{ github.ref_name }}" | tr '/' '-') fi echo "TAG=${TAG}" >> $GITHUB_ENV - name: Docker meta id: meta uses: docker/metadata-action@v4 with: images: ${IMAGE_NAME} - name: Login to DockerHub uses: docker/login-action@v3 with: username: fugafuga password: ${{ secrets.DOCKERHUB_TOKEN }} # 以下で変数を設定している - name: SET ENV for Dockerfile run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then TAG=$(echo "${{ github.head_ref }}" | tr '/' '-') NEXT_PUBLIC_EXAMPLE_ENV=http://example-staging.com else TAG=$(echo "${{ github.ref_name }}" | tr '/' '-') NEXT_PUBLIC_EXAMPLE_ENV=http://example.com fi echo "TAG=${TAG}" >> $GITHUB_ENV echo "NEXT_PUBLIC_EXAMPLE_ENV=${NEXT_PUBLIC_EXAMPLE_ENV}" >> $GITHUB_ENV - name: Build and push uses: docker/build-push-action@v4 with: context: hogehoge file: ./docker/Dockerfile push: true tags: "${{ env.IMAGE_NAME }}:${{ env.TAG }}" labels: ${{ steps.meta.outputs.labels }} build-args: | NEXT_PUBLIC_EXAMPLE_ENV=${{ env.NEXT_PUBLIC_EXAMPLE_ENV }}
Dockerfile
で ARG
コマンドと ENV
コマンドで環境変数を外から指定できるようにしておく必要があります。
ARG NEXT_PUBLIC_EXAMPLE_ENV=http://example.com ENV NEXT_PUBLIC_EXAMPLE_ENV=${NEXT_PUBLIC_EXAMPLE_ENV} ...
release-please.yml
Release Please の yml ファイルです。Version タグを打った Docker イメージも生成するようにしています。
name: release-please env: IMAGE_NAME: hogehoge on: push: branches: - main permissions: contents: write pull-requests: write jobs: release-please: runs-on: ubuntu-latest steps: - name: Run release-please id: release uses: google-github-actions/release-please-action@v3 with: release-type: go package-name: "hogehoge" - name: Set TAG from release-please if: ${{ steps.release.outputs.release_created }} run: | echo "Release Tag - ${{ steps.release.outputs.tag_name }}" if [ -n "${{ steps.release.outputs.tag_name }}" ]; then echo "TAG=${{ steps.release.outputs.tag_name }}" >> $GITHUB_ENV else echo "TAG=latest" >> $GITHUB_ENV fi - name: Checkout for tag created if: ${{ steps.release.outputs.release_created }} uses: actions/checkout@v4 - name: Docker meta for tag created if: ${{ steps.release.outputs.release_created }} id: meta uses: docker/metadata-action@v4 with: images: ${IMAGE_NAME} - name: Login to DockerHub for tag created if: ${{ steps.release.outputs.release_created }} uses: docker/login-action@v3 with: username: fugafuga password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push for tag created if: ${{ steps.release.outputs.release_created }} uses: docker/build-push-action@v4 with: context: . push: true tags: "${{ env.IMAGE_NAME }}:${{ env.TAG }}" labels: ${{ steps.meta.outputs.labels }}
lint-PR-title.yml
プルリクのタイトルが Conventional Commits に従っているかチェックするための yml ファイルです。 https://github.com/dreampulse/action-lint-pull-request-title をそのまま使っています。
name: "Lint PR Title" on: pull_request_target: types: - opened - edited - synchronize jobs: main: runs-on: ubuntu-latest steps: - uses: dreampulse/action-lint-pull-request-title@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto-test.yml
テストを実行するための yml ファイルです。このあたりは各プロジェクトでいい感じに。
name: Lint, Test & Build on: push: branches: - main pull_request: branches: - main jobs: lint-test-build: runs-on: ubuntu-latest steps: - name: Setup Go uses: actions/setup-go@v4 with: go-version: ^1.21 - name: Checkout code uses: actions/checkout@v4 - name: Lint uses: golangci/golangci-lint-action@v3 with: version: latest args: --timeout=5m - name: Test run: make test # make コマンドを使用してテストしている場合 - name: Build run: go build .