オンライン家庭教師マナリンクを支えるGitHub Actionsを一気に解説!
本記事ではオンライン家庭教師マナリンクのシステム開発で活躍しているGitHub Actionsについて一気に解説します。
マナリンクは、オンライン家庭教師と生徒(と保護者)を繋ぐプラットフォームを運営している事業です。
下図のように、大きく分けて3つの性質の違うプロダクトを開発しています。
- オンライン家庭教師を集客するメディア
- 保護者の集客〜先生のプロフィールを比較〜問合せ〜決済までを受け持つWebサイト
- 宿題やチャットなどの機能でオンライン指導ができるWebアプリおよびネイティブアプリ
このようにさまざまなプロダクト、技術要素に支えられています。
マナリンクは2020年8月に正式リリースした若いサービスなので、まだまだ事業検証するべきことが多く、その分開発効率を高めるために自動化できるところは自動化していきたいと考えています。
本記事では以下の順に、GitHub Actionsの活用場面について解説していきます。
- Laravel
- Nuxt.js
- React Native
- React(Vite)
- Next.js
- 共通で使える便利なGitHub Actions
気になるところから読んでみてください!
Laravel
LaravelでAPIサーバーを開発しており、マナリンクでは先生の検索機能や、オンライン指導開始後の宿題機能などでそのAPIが利用されています。
PHPUnitによる自動テストの実行
まずは定番の自動テストの実行です。
onトリガーではPull Request時にテストを回すように設定しています。まだ、リポジトリ内にはソースコードだけではなく、マークダウンで書いたディレクトリごとのREADMEや、PlantUMLで書いているUML、そしてエクスポートしたPNGなどが置いてあるため、それらのみの差分で構成されたプルリクではテストを回しません。これにはpaths
という機能を使います(https://docs.github.com/ja/actions/reference/workflow-syntax-for-github-actions#excluding-paths)。
on:
pull_request:
branches:
- master
- develop
paths:
- 'src/**'
- '!**.md'
- '!**.pu'
- '!**.puml'
- '!**.png'
コンテナイメージではlaravel-test-runnerを利用させていただいています。また、テスト用のMySQLデータベースの起動はonline_teacher_testingという名前のテスト用の専用データベースに対して実行します。この工夫はローカル開発時にテストコードを回すたびにRefreshDatabaseすると開発中のデータが飛んでしまうので、テスト専用のデータベース名を指定するようにPHPUnit側も設定していることに由来します。
container:
image: kirschbaumdevelopment/laravel-test-runner:7.4
services:
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: online_teacher_testing
ports:
- 33306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
いくつかの設定は割愛しましたが、最後にテストを実行すればOKです。
- name: Run test suite
run: composer run-script test
Amazon ECSへのデプロイ
マナリンクのLaravel APIサーバーはFargateにデプロイされています。そちらのデプロイもGitHub Actionsで自動化しています。
onトリガーは先程のテスト実行のロジックに近く、相違点としてはpushをトリガーにしていることと、dockerディレクトリ以下の修正、すなわちコンテナ設定の変更時にもトリガーすることです。
on:
push:
branches:
- develop
- master
paths:
- 'src/**'
- 'docker/**'
- '!**.md'
- '!**.pu'
- '!**.puml'
- '!**.png'
GitHub ActionsでAWS周りのコマンドを扱うために、https://github.com/aws-actions/configure-aws-credentials で設定します。
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
ちょっと内容を割愛していますが、コンテナのビルドとECRへのプッシュは以下のように実行しています。
Nginxと、PHPのコンテナを同じタスク定義内に含めている構成を取っています。
--build-arg
で動的にDockerfileに引数を渡すことができるので、環境ごとにGitHub Actionsを作り分けなくても、事前に変数をマージ先のブランチによって切り分けておけば少ない数のGitHub Actions yamlファイル数で事足ります。
- name: Build and tagging to docker images
id: build-image
run: |
docker build --build-arg ENV=$ENV --build-arg FRONT_HOST=$FRONT_HOST -t $ECR_REGISTRY/$ECR_REPOSITORY_NGINX:$IMAGE_TAG -f docker/nginx/Dockerfile .
docker build --build-arg ENV=$ENV -t $ECR_REGISTRY/$ECR_REPOSITORY_PHP_FPM:$IMAGE_TAG -f docker/php-fpm/Dockerfile .
- name: Push image to Amazon ECR
id: push-image
run: |
docker push $ECR_REGISTRY/$ECR_REPOSITORY_NGINX:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY_PHP_FPM:$IMAGE_TAG
ECRへのプッシュが終わったら、 https://github.com/aws-actions/amazon-ecs-deploy-task-definition を使って、タスク定義を再度プッシュし直すことで、デプロイも自動で走ります。
Nuxt.js
Nuxt.jsで主に先生の検索機能のフロントエンドを開発しています。
jestによる自動テストの実行
フロントエンドではさほどテストコードを書いていないのですが、ややこしいロジックを持っているコンポーネントや、バグをどうしても混入させたくない部分に絞ってテストを書いています。
onトリガーはプルリク時に実行し、適宜ファイルパスを指定している点は変わらずです。
on:
pull_request:
branches:
- master
- develop
paths:
- "client/**"
- "jest.config.js"
- "jest.setup.ts"
- "jest-mock.config.ts"
- "package-lock.json"
- "tsconfig.json"
あとは特筆すべきところはなく、ライブラリをインストールしてテストを実行しています。
- name: Install Dependencies
run: npm ci
- name: Run test
run: npm test
Amazon ECSへのデプロイ
こちらはLaravelとほぼ同様の内容なので割愛します。
Lambda@Edgeへのデプロイ
マナリンクではNuxt.jsのサーバー構成として、Fargateの前段にCloudFrontを置いています。CloudFrontではx-xss-protection
等のヘッダを付与するためにLambda@Edgeを置いており、この辺もコードで管理したいなと思ったのでNuxt.jsと同じリポジトリにCDKのソースコードを置いています。
onトリガーは他のロジックと変わらず、pathsで制限してdevelop/masterプッシュ時に動かします。
on:
push:
branches:
- develop
- master
paths:
- "aws/lambda_edge/**"
CDKへのデプロイはCLIでサポートされているため、以下のように簡単にできます。
- name: npm install and build
run: |
npm ci
npm run build
working-directory: ./aws/lambda_edge/cdk_lambda_edge
- name: cdk deploy
run: |
ENV=$ENV npx cdk deploy --v --require-approval never
working-directory: ./aws/lambda_edge/cdk_lambda_edge
--require-approval never
あたりはちょっとポイントです。これで途中でYes/Noの質問がなくなるのでCI上で問題なく動きます。
React Native
マナリンクのアプリはReact Nativeで開発しておりAndroid、iOS双方で提供しています。
Expoで開発していることから、日頃リリースするOTAアップデートはGitHub Actionsで完結できます。
Expoは簡単に言うとReact Nativeアプリ上で動かせるソースコード用のブラウザみたいなもので(?)、Expo上にソースをPublishすることで、各ユーザーの端末に最新のソースを行き渡らせることができ、審査提出の手間を削減することができます。もちろん行き渡らせることができるソースはReactで表現できるレイヤー、すなわちUIレイヤーのソースだけなのですが。
expo/expo-github-action
を使わせていただければ簡単な話ですね。細かい話ですが、--config
プロパティはもうDeprecatedなので解消予定なのと、release channelはこうやって決め打ちで書くんじゃなくてバージョンを動的に反映できるように--release-channel staging-v$(cat app.json | jq -r '.expo.version')
のように書くのがよいです。これもそのうち対応予定です。
- uses: expo/expo-github-action@v5
with:
expo-version: 4.x
expo-token: ${{ secrets.EXPO_TOKEN }}
expo-cache: true
- run: yarn install
- run: expo publish --config app.json --release-channel staging
React(Vite)
React Native製でオンライン指導に関する各ツールを開発していますが、PC操作の多い先生方のためにWebで同様の機能提供をしています。そこでReactをVite上で動かして提供することで、React Nativeアプリと近い開発体験で開発しています。
デプロイ
Viteは静的なHTMLを使ったシンプルなSPAを吐いてくれるので、以下のようにS3に置くだけの設定で済みます。
- name: Install Deps and Build
id: install-deps-and-build
run: |
yarn install --frozen-lockfile
yarn build-staging
rm -rf ./dist/vite_assets/*.js.map
- name: Deploy to S3
id: deploy-to-s3
run: |
aws s3 sync dist/ s3://$AWS_S3_ASSET_BUCKET/dist --delete --exact-timestamps
aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths '/*'
Viteのビルドコマンドは、環境ごとにmodeというものを指定して分けることができるのでyarn build-staging
でステージング用ビルドができるように設定したりしています。
S3にファイルを置いて、CloudFrontでcreate-invalidation
することでデプロイしています。ちょっと乱暴ですが。
lintやtestの実行
マージ前に、lintやtest、buildを実行しています。
- name: Install Deps
id: install-deps
run: |
yarn install --frozen-lockfile
- name: Lint
id: lint
run: |
yarn lint
- name: Test
id: test
run: |
yarn test
- name: Build
id: build
run: |
yarn build
yarn lint
コマンドの内容は以下の感じで、ESLintとstylelintを実行しています。
"lint": "run-p lint:*",
"lint:eslint": "eslint -c .eslintrc.js --ext .tsx --ext .ts --quiet src/",
"lint:style": "stylelint 'src/**/*.{css,scss,sass}'",
--quiet
オプションを付与するとwarningを出さないので、CI実行時には便利です。
Next.js
マナリンクではオンライン家庭教師を集客するための専用メディア「マナリンクTeachers」を公開しています。
こちらはNext.js(ISR)でバックエンドをmicroCMSで公開しています。
ISRを使ってはいるものの、記事だけでなくサイトマップまで更新しようとするといい方法がうかばなかったため結局3時間に一度cronをGitHub Actionsで回すというところに落ち着きました。
- name: Recreate all posts
shell: bash
run: |
yarn install --frozen-lockfile
MICROCMS_GET_API_KEY=XXXXX MICROCMS_BASE_URL=XXXXX yarn build
- uses: amondnet/vercel-action@v20
with:
# GitHub Actions の Secrets で作成した値を参照する形で
# Vercel デプロイ時に必要となる各種パラメタを設定する
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
vercel-args: '--prod' # Optional
vercel-org-id: ${{ secrets.ORG_ID}} #Required
vercel-project-id: ${{ secrets.PROJECT_ID}} #Required
vercel-project-name: XXXXX
working-directory: ./
- uses: sonots/slack-notice-action@v3
with:
status: ${{ job.status }}
title: マナリンクForTeachersのサイトマップ定期生成に失敗
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # required, but GitHub should automatically supply
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
if: failure()
ビルド時にサイトマップの再生成もやるので、ちょっと余計な方法ですが簡単にでもできたほうがいいのでこのような方法にしています。
サイトマップの生成はnextjs-sitemap-generator
パッケージでやっています。
失敗時は一応Slackに通知するようにしました。たまに記事の内容がInvalidだったりすると失敗するので、それが放置されないようにです。
共通で使える便利なGitHub Actions
最後に、共通で使える便利なGitHub Actions設定についてお話します。
release用プルリクエストの自動生成
マナリンクでは現在、developにマージするとステージングにデプロイされ、masterマージすると本番デプロイされる、究極にシンプルなフローを採用しているのですが、developにプルリクをマージするたびに、masterマージ用のプルリクを作ってくれるGitHub Actionsがあります。
# @see https://kojirooooocks.hatenablog.com/entry/2020/08/23/225446
# masterブランチへのマージコミットを一覧にしたPRを自動作成
# --squashedは以下のPRで追加された機能で、squash mergeされた差分を反映できる
# https://github.com/x-motemen/git-pr-release/pull/60/files
name: Create a release pull request
on:
push:
branches:
- develop
jobs:
create-release-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- name: Create a release pull request
env:
GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_PR_RELEASE_BRANCH_PRODUCTION: master
GIT_PR_RELEASE_BRANCH_STAGING: develop
GIT_PR_RELEASE_LABELS: release
run: |
gem install -N git-pr-release
git-pr-release --squashed
といっても内容はシンプルで、git-pr-release
というgemを使わせてもらっているだけです。
workflow_dispatch
workflow_dispatchを利用すると、手動でGitHub Actionsを実行できます。
onトリガーにworkflow_dispatchを加えるだけで、GitHubのActionsタブ上からポチッと手動実行することができるようになります。
何らかの理由で再デプロイしたり、開発中のブランチを一時的にステージング環境に適用したいときなどに便利です。
まとめ
GitHubを使っていれば特に何の設定も必要なく、いきなり無料で始められるGitHub Actionsは本当に便利ですね。
まだまだ知らない機能やユースケースがあると思うので、今後も定期的に見直し、改善していこうと思います。
ぱっと浮かぶのは、フロントエンドでreg-suitなどを使って見た目のデグレを検知する体制を整えたいな、と思っています。
あとはGitHub Actionsとは関係がないですが、Dependabotを使いこなして、ライブラリのバージョンアップを習慣化したいな、とか思ったりします。
GitHub Actionsの公式ドキュメント輪読会、とか開催したらニーズあるかな・・・?
一緒に開発速度、開発体験の向上に取り組みながら、オンライン教育業界にプロダクトを提供していくエンジニアを募集しています。ぜひお話しましょう!
オンライン家庭教師マナリンクを運営するスタートアップNoSchoolのテックブログです。 manalink.jp/ 創業以来年次200%前後で売上成長しつつ、技術面・組織面での課題に日々向き合っています。 カジュアル面談はこちら! forms.gle/fGAk3vDqKv4Dg2MN7
Discussion