CircleCIでformat、lint、buildが通るかをチェックしたい
経緯
本業の方で、Next.JS14(この前13 → 14にバージョン上げました🙌🏻)を用いて開発、というか既存のレガシーな作りを取っ払ってNextにreplaceしちゃおうぜ計画を行っている。
その際に技術的に気になったことをメモしていく。できれば継続的に🐟
今回は、prettierのformatと、ESlintのcheck、nextのbuildを、CircleCIを使って一括checkし、一つでも通らなかったらGitHubでプルリクを作成した際にマージできないようにしたので、採用理由や実装ポイント、詰まった点などをメモしておく。
CircleCIで行う理由
これに関しては、既存のサービスにおいて、stg環境や本番環境にデプロイする時などに、CircleCIを使用していたためというのが大きい。
ただ、普段CIの管理をしているのはSREチームなので、僕は普段触る機会がない。
今回ほぼ初めて触ったのだが、不明瞭な点があってもSREチームの方にサポートいただけたので、そういう意味でも安心感があった。
huskyは?
最近のフロントエンド開発だと、huskyとlint-stagedを組み合わせて、commit時にコード整形やlintを行う方がデファクトスタンダードなのかもしれないし、その方法も検討したのだが、現状のアプリのディレクトリ構成だと厳しかったので断念した😵
実装
今回の実装のポイントは主に以下である。
- 普段Dockerコンテナ上で開発を行っているため、CircleCiの実行環境もDockerを使用
- Node.jsがインストールされた Docker イメージである
cimg/node
を使用 - feature/~ブランチ、developブランチに対してpushが行われたことをジョブのトリガーとする
実際のコードはこちら(サービス名の箇所はour-serviceとします。)
version: 2.1
jobs:
build-and-format-and-lint:
docker:
- image: cimg/node:lts
steps:
- checkout
- setup_remote_docker:
version: 20.10.18
- run:
name: Install Docker Compose
command: |
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- run:
name: Run Shared Network
command: |
docker network create shared-network
- run:
name: Build images of services declared in docker-compose.yml
command: docker compose build
- run:
name: Run Docker Compose
command: |
docker compose up -d
- run:
name: Copy src directory to container
command: |
docker cp ./src/. our-service:/app
- run:
name: Install npm dependencies
command: |
docker exec our-service npm install
- run:
name: Run npm scripts
command: |
docker exec our-service npm run lint
docker exec our-service npm run lint:prettier
docker exec our-service npm run build
workflows:
version: 2
push-feature-commit:
jobs:
- build-and-format-and-lint:
filters:
branches:
only:
- /^feature\/.*/
- develop
主に以下を参考にして作成した。
実装時、特に詰まったポイント
事象:npm installのrun実行時に、app配下にpackage.jsonがないと言われる
前提として、アプリのディレクトリ構成では、src配下にpackage.jsonがあり、
docker compose.ymlで、src配下はコンテナ内の/appにバインドマウントされるように設定している。
なので、アプリの方では、コンテナのapp内にpackage.jsonが存在し、これを元にnpm install
などが走るようになっているのだが、
CircleCI上のコンテナでは、何故か/app配下にpackage.jsonが存在してないことになっていた。
appへのバインドが上手くいってないのでは?
と思い、ジョブ内でdocker-compose config
を実行し、docker composeの設定を確認したのだが、上手くできてそう。
なぜだ・・?
原因:Setup_remote_dockerとアプリのdockerの差異
CI上で動作させるリモートDocker環境をアクティブ化するために、setup_remote_dockerステップを追加しているのだが、どうやらこのリモートDocker環境とアプリdockerの環境差異が原因のようだった。(これに気づくのに半日かかった泣)
よく見たら公式にもそのように記載があった。
docker を setup_remote_docker と組み合わせて使用すると、docker-machine を使用して作成した場合と同様のリモートエンジンを作成できます。 ただし、このセットアップでは、ボリュームのマウントとポート転送は同じようには機能しません。 リモート Docker デーモンは、Docker CLI や Docker Compose とは異なるシステム上で動作するため、これを機能させるにはデータの移動が必要です。
解決:CI上のリモートDockerに、アプリのdockerコンテナデータを移動
公式には続きで、
マウントは通常、Docker ボリュームでコンテンツを利用可能にすることで解決できます。 docker cp を使用して、CLI ホストから Docker リモートホスト上で実行しているコンテナにデータを取得することで、Docker ボリュームにデータをロードできます。
デプロイ用の Docker イメージをビルドする場合は、この組み合わせが必要です。
と記載あったのでこれに従い、アプリのdockerコンテナデータを、CI上のdockerの/app内にcpするrunを加えることによって、正常にnpm installが走るようになった。
docker cp ./src/. our-service:/app
GitHubでブランチ保護ルールを設定
最後にGitHubのリポジトリの、
settings → Branches → Branch protection rule
で、developに対して、ciのbuild-and-format-and-lint(作成したjob名)を、Require status checks to pass before mergingに設定してあげて完成!
これで、developに向けて作成された、feature/~のプルリクは、絶対にformat、lint、buildを通さないとマージできないようにできる。
まとめと今後の展望
初めてCIの設定をしたが難しい。。
でもjobがしっかり通るようになって実用可能になった瞬間は嬉しかった😆
ただ難しい。。dockerなどについても、もっと勉強しなくてはというモチベになったいい機会だった💪🏻
インテグレーションテストが実装出来次第、
このジョブの中に実行コマンドを加えて、テストもCIでcheckするようにしたい!🔥
Discussion