🏈

オンライン家庭教師マナリンクを支えるGitHub Actionsを一気に解説!

2021/09/09に公開

本記事ではオンライン家庭教師マナリンクのシステム開発で活躍している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アプリと近い開発体験で開発しています。

https://zenn.dev/manalink/articles/manalink-react-vite

デプロイ

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」を公開しています。

https://for-teachers.manalink.jp/

こちらは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を実行できます。

https://docs.github.com/ja/actions/reference/events-that-trigger-workflows#workflow_dispatch

onトリガーにworkflow_dispatchを加えるだけで、GitHubのActionsタブ上からポチッと手動実行することができるようになります。

何らかの理由で再デプロイしたり、開発中のブランチを一時的にステージング環境に適用したいときなどに便利です。

まとめ

GitHubを使っていれば特に何の設定も必要なく、いきなり無料で始められるGitHub Actionsは本当に便利ですね。

まだまだ知らない機能やユースケースがあると思うので、今後も定期的に見直し、改善していこうと思います。
ぱっと浮かぶのは、フロントエンドでreg-suitなどを使って見た目のデグレを検知する体制を整えたいな、と思っています。
あとはGitHub Actionsとは関係がないですが、Dependabotを使いこなして、ライブラリのバージョンアップを習慣化したいな、とか思ったりします。

GitHub Actionsの公式ドキュメント輪読会、とか開催したらニーズあるかな・・・?

一緒に開発速度、開発体験の向上に取り組みながら、オンライン教育業界にプロダクトを提供していくエンジニアを募集しています。ぜひお話しましょう!

マナリンク Tech Blog

Discussion