変化の激しいウェブ開発の世界で、継続的インテグレーションと継続的デリバリー(CI/CD)は、高品質のソフトウェアを効率的に生み出すために不可欠な概念となっています。CI/CDによって、ビルド、テスト、デプロイのプロセスを自動化、さらにヒューマンエラーによるリスクを減らしながら、素早い反復を可能にします。

この記事では、CI/CDの重要性、CIパイプラインの作成方法、そして、GitHubリポジトリ(Actions)とKinsta APIを使ってCIパイプラインの継続的デプロイをプログラムを介して実行できるようにする方法をご説明します。

CI/CDの存在意義

Kinstaのアプリケーションホスティングプラットフォームには、ホストするGitリポジトリの特定のブランチに変更があるたびにそれを検知する、自動デプロイオプションがあります。しかし、これは複数のチームメンバーがいる大規模なプロジェクトでは、必ずしも理想的でない可能性があります。多くの開発者が、さまざまな理由から自動デプロイメントの有効化を避ける傾向にあります。

その理由のひとつとして、複数の開発者が同じプロジェクトに携わっているような共同作業環境で、ひとりの開発者がリポジトリを変更したことをきっかけに自動デプロイが始まると、不安定になったり予期せぬ問題が発生したりすることがあります。適切なテストと検証を行わないと、小さなコードの変更でさえ本番サイトに混乱を引き起こし、ダウンや好ましくないユーザー体験につながる可能性があります。

そんな状況を解決するのに便利なのが、CI/CDパイプラインです。入念に設計されたCI/CDワークフローを用意することで、コードの変更内容を本番サイトにデプロイする前に、確実にテストと検証を行うことができます。ソフトウェア開発にCI/CDを導入するツールはたくさんありますが、こちらの記事では、GitHub Actionsを使います。

GitHub Actionsとは

GitHub ActionsはGitHubが提供する強力な自動化ツールです。ソフトウェア開発プロジェクト内の様々なタスクやプロセス、ワークフローを自動化することができます。GitHubリポジトリと統合されているので、簡単に使いこなすことができます。

GitHub ActionsとKinsta APIを使えば、プロジェクトの要件に合ったカスタムワークフローを定義可能です。例えば、今回ご紹介するように、アプリケーションをテストし、KinstaへのデプロイをトリガーするCIパイプラインを設定することができます。

GitHub Actionsを利用する

GitHub Actionsは、ワークフローというコンセプトで機能します。ワークフローは、特定のイベントによってトリガーされる、または定期的にスケジュールされる自動化されたタスクのセットと定義できます。イベントには、コードのプッシュ、プルリクエスト、課題の作成などがあります。このようなイベントが発生すると、GitHub Actionsにより関連するワークフローが自動的に実行され、定義済みの一連のステップが実行されるというものです。

ワークフローの各ステップは、コードのビルドやテストの実行、デプロイ、通知の送信といった特定のアクションを表します。3つのタスクからなるワークフローを作成してみましょう。

  1. ESLintで構文をチェックする
  2. テストを実行する
  3. アプリケーションを再デプロイする

ステップ 1. GitHubリポジトリのセットアップ

GitHub Actions を使い始めるには、GitHubリポジトリが必要です。ここでは「ChatGPTクローンアプリケーションをReactとOpenAI APIで構築、デプロイする方法」で利用したこちらのGitHubリポジトリを使います。

GitHub のリポジトリに移動して「Use this template」>「Create a new repository」を選択してください。

このReactアプリケーションでは、各コンポーネントをテストするためにユニットテストが作成されています。また、完璧な構文とコードフォーマットを強制するためにESLintを使用しています。リポジトリにプッシュされたプルリクエストやマージされたコードがワークフローのテストに失敗すると、CIパイプライン内でデプロイがブロックされる仕組みです。

ステップ2. ワークフローファイルの作成

リポジトリの.github/workflowsディレクトリにYAMLファイルを作成してワークフローを定義します。このディレクトリはリポジトリのルートレベルにあるはずです。ワークフローファイルの命名規則は、name-of-the-workflow.ymlです。

  1. リポジトリ内に.githubディレクトリを作成します。
  2. .githubディレクトリの中に、workflowsという新しいディレクトリを作成します。
  3. workflowsディレクトリの中に、build-test-deploy.ymlのような名前で新しいファイルを作成します。

ステップ3. CI/CDワークフローの記述

ワークフローファイルを作成したので、ESLintで構文をチェック、テストを実行し、アプリケーションをデプロイするために必要なステップをワークフローとして定義します。

CIイベントの作成

CIパイプラインを作成する場合、最初のステップとして、ワークフローに名前を付け、ワークフローのトリガーとなるイベントを設定します。この例では、プルリクエストとメインブランチへのプッシュという2つのイベントを設定します。

name: Build, Test, and Deploy

on:
  push:
    branches: "main"
  pull_request:
    branches: "main"

特定のタスクに対して定期的なジョブ(cronジョブ)を設定したい場合には、これをワークフローに追加することができます。例えば、データベースのバックアップやデータのクリーニング、その他定期的なメンテナンス作業などがこれにあたります。

以下は、ワークフローにcronジョブを追加する方法の一例です。

on:
  # pushとpull_requestの既存のイベントトリガー

  # CRONジョブのスケジュールを追加
  schedule:
    - cron: "0 0 * * *"

上記の例では、cronスケジュールが0 0 * * * に設定されているため、毎日午前0時(UTC時間)にワークフローがトリガーされます。状況に合わせてcronスケジュールをカスタマイズすることができます。

別の例として、毎週月曜日の午前8時にCI/CDワークフローを実行するようにスケジュールを設定したいとします。scheduleイベントを使用して、以下のようにcronジョブを設定することができます。

name: Build, Test, and Deploy

on:
  push:
    branches: "main"
  pull_request:
    branches: "main"

  #ワークフローを毎週月曜日の午前8時(UTC時間)に実行するように設定
  schedule:
    - cron: "0 8 * * 1"

jobs:
 #ジョブを追加

GitHub Actionsワークフローのscheduleイベントで使われているスケジュール構文は、UNIXのcron構文に基づいています。このようにワークフローが自動的に実行される時間や間隔を定義することができます。この構文は、スケジュールの様々な側面を表す5つのフィールドで構成され、各フィールドはスペースで区切られています。スケジュール構文の一般的なフォーマットは以下の通りです。

* * * * *
┬ ┬ ┬ ┬ ┬
│ │ │ │ │
│ │ │ │ └─ Day of the week (0 - 7) (Sunday to Saturday, where both 0 and 7 represent Sunday)
│ │ │ └─── Month (1 - 12)
│ │ └───── Day of the month (1 - 31)
│ └─────── Hour (0 - 23)
└───────── Minute (0 - 59)

では、各フィールドを分解してみましょう。

  • 分(0 – 59):cronジョブがトリガーされる分。例えば、15は、毎時15分にワークフローが起動することを意味します。
  • 時間(0 – 23):cronジョブが起動する時間。例えば、8は、ワークフローが午前8時にトリガーされることを意味します。
  • 日(1 – 31):cronジョブが起動する月の日。例えば、1は、月の1日にワークフローが起動することを意味します。
  • 月(1 – 12):cronジョブが起動する月。例えば、6は、ワークフローが6月に起動することを意味します。
  • 曜日(0 – 7):cronジョブが起動する曜日。07は日曜日を表し、1は月曜日を表します。例えば、4 は木曜日を表すことになります。

特殊文字

  • *(アスタリスク):そのフィールドのすべての値にマッチします。例えば、分フィールドに*を使用すると、ワークフローが毎分トリガーされることになります。
  • */n(スラッシュ):間隔を指定します。例えば、分フィールドの*/5は、ワークフローが5分毎にトリガーされることを意味します。
  • ,(カンマ):複数の特定の値を指定できます。例えば、分フィールドの1,15,30は、ワークフローが1分、15分、30分毎にトリガーされることを意味します。
  • -(ハイフン):値の範囲を指定できます。例えば、曜日フィールドの1-5 は、ワークフローが月曜日から金曜日(1~5)までトリガーされることを意味します。
  • ?(クエスチョンマーク):特定の値を指定しない場合に使用します。一般的には、曜日フィールドで(日付が指定されている場合に)使用されます。例えば、曜日フィールドに「? 」、日付フィールドに「15 」を指定すると、曜日に関係なく、ワークフローが毎月15日にトリガーされます。

ESLintで構文をチェックするCIジョブの作成

CIプロセスをセットアップするために、必要なジョブやタスクを作成します。各ジョブにはわかりやすい名前をつけましょう。最初のジョブはESLintを使ってコードの構文をチェックするので、eslintとします。

さらに、任意ではありますが、人間が確認しやすいように説明を加えることもできます。次に、ジョブはUbuntu環境で実行するように指定し、2つのNode.jsのバージョン18.x20.xについてコードをテストすることにします。

jobs:
  eslint:
    name: Check Syntax with ESLint
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

次に、「ESLint」ジョブが実行するステップを定義します。実際のステップは、コードのチェックアウト、ESLintを実行するための指定したNode.jsバージョンのセットアップ、npmパッケージのキャッシュ、プロジェクト依存関係のインストール、そして最後にコードの構文を確認するESLintの実行となります。

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }} to Check Lint
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install Dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

上のワークフローでは、GitHub Actionsからワークフローを確認するときにエラーやバグの原因を特定しやすいように、各ステップに名前と説明をつけています。特筆すべきは、3番目のステップで、npm ciコマンドを使って依存関係をインストールするものです。これは、npm installよりもクリーンなインストールができるので推奨されます。さらに、最後のステップでは、npm run lintを使ってESLintを実行しますが、package.jsonファイルでこのコマンドを設定していることを前提としています。

以下は、ESLintでコードの構文をチェックする全体像です。

jobs:
  eslint:
    name: Check Syntax with ESLint
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Use Node.js ${{ matrix.node-version }} to Check Lint
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Install Dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

テストを実行するCIジョブの作成

テストを実行するCIジョブを追加するには、まずジョブを定義し、testsのようなわかりやすい名前をつけます。このジョブはeslintジョブに依存します。つまり、testsジョブが実行される前にeslintジョブが実行されることになります。この依存関係により、テストを実行する前にコードの構文エラーがチェックされます。

  tests:
    name: Run Tests
    needs: eslint
    runs-on: ubuntu-latest

次に、testsジョブのステップを定義します。 前のジョブと同様に、コードをチェックアウトし、テストを実行するためにNode.jsのバージョン18.xをセットアップし、npm ciを使ってプロジェクトの依存関係をインストールし、npm run testコマンドを使ってテストを実行します。

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Use Node.js 18.x to run Test
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
          cache: 'npm'

      - name: Install Dependencies
        run: npm ci

      - name: Run Tests
        run: npm run test

Kinsta APIでデプロイするためのCIジョブの作成

Kinsta APIを使用してKinstaにデプロイするCIジョブを作成するために、ジョブを定義し、名前をdeployとします。このジョブは、eslinttestsジョブに依存します。つまり、コードの構文エラーがチェックされ、テストに合格した後にのみデプロイが実行される構造です。利用可能な最新バージョンを使ってUbuntu環境で実行するようにジョブをセットアップします。

  deploy:
    name: Re-Deploy Application
    needs: [eslint, tests]
    runs-on: ubuntu-latest

次に、ステップを定義します。このケースでは、プログラムを介してKinsta APIとやりとりし、再デプロイメントをトリガーするためにcURLコマンドを実行します。続いては、Kinsta APIの基本、APIとやり取りするために必要な様々な知識、APIキーなどのAPI関連情報を安全にGitHubで取得/保存する方法を理解しましょう。

Kinsta APIを理解する

Kinsta APIを利用することで、プログラムを介してKinstaのサービスを操作することができます。APIを使用するには、MyKinstaで少なくとも1つのWordPressサイト、アプリケーション、またはデータベースのアカウントを所有する必要があります。また、APIを通してアカウントを認証し、アクセスするためにAPIキーの生成が必要になります。APIキーを生成する方法は次の通りです。

  1. MyKinstaにログイン
  2. APIキー」ページに移動((アカウント名)>「企業の設定」>「APIキー」)
  3. APIキーを作成」をクリック
  4. 有効期限を選択するかカスタムで指定
  5. キーに一意の名前を付ける
  6. 生成」をクリック
MyKinstaでAPIキーを作成する
MyKinstaでAPIキーを作成する

APIキーを作成したら、コピーして安全な場所に保管してください(パスワードマネージャを使用することをお勧めします)。作成時にしか表示されませんのでご注意ください。

Kinsta APIでデプロイをトリガーする方法

APIを使用してKinstaにアプリケーションをデプロイするには、アプリケーションIDとブランチという2つのパラメータが必要です。まずアプリケーション一覧を取得し、アプリケーションのIDを把握することができます。

その後、APIの/applications/deploymentsエンドポイントにPOSTリクエストを行います。CIパイプラインには、URLとやりとりするコマンドラインツールであるcURLを使用します。

curl -i -X POST 
  https://api.kinsta.com/v2/applications/deployments 
  -H 'Authorization: Bearer <YOUR_TOKEN_HERE>' 
  -H 'Content-Type: application/json' 
  -d '{
    "app_id": "<YOUR_APP_ID>",
    "branch": "main"
  }'

CI/CDパイプラインでcURLを使用してデプロイメントをトリガーする

Kinsta APIでデプロイをトリガーするには、CIパイプラインのrunコマンドにcURLコマンドを追加します。ただし、APIキーとアプリケーションIDを安全に保存することが重要です。

シークレット(Secrets)をGitHubに保存しGitHub Actionsで使用するには、以下の手順に従ってください。

  1. シークレットを設定したいリポジトリに移動
  2. リポジトリのメニューから「Settings」タブをクリック
  3. 左サイドバーの「Options」カテゴリで「Secrets」を選択
  4. New repository secret」をクリック
  5. シークレットの名前(KINSTA_API_KEYのようなもの)を入力し、「Value」フィールドにKinsta APIキーを入力
  6. 名前と値を入力したら、「Add secret」ボタンをクリックして保存
  7. 他のシークレットでもこのプロセスを繰り返す
GitHubにシークレットを保存
GitHubにシークレットを保存

シークレットを追加したら、${{ secrets.SECRET_NAME }}という構文を使って GitHub Actionsワークフローで参照することができます。

それでは、GitHub Actions CI/CD パイプラインのdeployジョブを完成させましょう。先ほどと同じようにステップを定義し、Kinsta にデプロイするステップをひとつ追加します。まず、envコマンドでシークレットを定義し、デプロイを実行するcURLコマンドを追加します。

    steps:
      - name: Deploy to Kinsta
        env:
          KINSTA_API_KEY: ${{ secrets.KINSTA_API_KEY }}
          APP_ID: ${{ secrets.APP_ID }}
        run: |
          curl -i -X POST 
            https://api.kinsta.com/v2/applications/deployments 
            -H "Authorization: Bearer $KINSTA_API_KEY" 
            -H "Content-Type: application/json" 
            -d '{
              "app_id": "'"$APP_ID"'",
              "branch": "main"
            }'

cURLコマンドでは、環境変数がコマンド内に追加され、デプロイプロセス中に安全にシークレットにアクセスできます。

最終的なCI/CDワークフローは以下のようになります。

name: Build, Test, and Deploy

on:
  push:
    branches: "main"
  pull_request:
    branches: "main"

jobs:
  eslint:
    name: Check Syntax with ESLint
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        
      - name: Use Node.js ${{ matrix.node-version }} to Check Lint
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
          
      - name: Install Dependencies
        run: npm ci
        
      - name: Run ESLint
        run: npm run lint

  tests:
    name: Run Tests
    needs: eslint
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        
      - name: Use Node.js 18.x to run Test
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
          cache: 'npm'
          
      - name: Install Dependencies
        run: npm ci
        
      - name: Run Tests
        run: npm run test

  deploy:
    name: Re-Deploy Application
    needs: [eslint, tests]
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to Kinsta
        env:
          KINSTA_API_KEY: ${{ secrets.KINSTA_API_KEY }}
          APP_ID: ${{ secrets.APP_ID }}
        run: |
          curl -i -X POST 
            https://api.kinsta.com/v2/applications/deployments 
            -H "Authorization: Bearer $KINSTA_API_KEY" 
            -H "Content-Type: application/json" 
            -d '{
              "app_id": "'"$APP_ID"'",
              "branch": "main"
            }'

ワークフローをコピーし、build-test-deploy.ymlファイルに貼り付けます。次に、このファイルをリポジトリのメインブランチに追加するプルリクエストを開始します。このプルリクエストが自動でワークフローをトリガーします。

これにより、リポジトリに加えられた変更内容をチェックし、プルリクエストの新しい変更内容が指定のチェック項目を満たしていることを確認した上で、コードベースにマージするかどうかを決めることができます。

GitHubのシークレット
GitHubのシークレット

プルリクエストをマージするGitHubリポジトリの「Actions」タブに移動すると、CI/CDワークフローが実行されていることがわかります。

GitHub Actionsの概要を確認する
GitHub Actionsの概要を確認する

各ジョブをクリックすると、そのジョブの中身を見ることができます(ジョブの各ステップに理解できる説明をつける必要があるのはこのためです)。

CIのステップについての情報
CIのステップについての情報

GitHubでのプルリクエストワークフローの強制

GitHubリポジトリで効果的なコード管理と共同作業を行うには、プルリクエストのワークフローを強制してメインブランチへの直接コミットをブロックするのが有効です。これによって、組織的な開発プロセスを確立することができます。すべての変更内容が、メインブランチにマージする前にプルリクエストとレビューを受けることになります。

この方法を採用することで、開発チームによるコードの質向上、バグを引き起こすリスクの最小化、変更履歴における透明性の維持が容易になるはずです。

以下に、プルリクエストワークフローの実施方法を説明します。

  1. GitHubリポジトリの「Settings」タブをクリック
  2. サイドバーの「Code and Automation」にある「Branches」を選択
  3. ルールがない場合は、「Add branch protection rule」をクリック
  4. ルールの名前を指定し、「Require a pull request before merging」にチェックを入れる(これで、設定のためのオプションがさらに表示されます)
  5. また「Require status checks to pass before merging」にもチェックを入れる
  6. 好みや要件に応じて、その他のオプションをカスタマイズ
  7. Create」ボタンをクリックしてルールを保存
GitHubでプルリクエストのワークフローを実施する
GitHubでプルリクエストのワークフローを実施する

以上の手順で、GitHubリポジトリでプルリクエストのワークフローを強制するルールの設定が完了しました。これで、すべての変更がメインブランチにマージされる前にレビューと自動チェックを経るようになります。信頼性の高い共同開発環境の構築にご活用ください。

まとめ

GitHub ActionsとKinsta APIを組み合わせることで、開発ワークフローを合理化し、開発チームの負担を軽減する効率的な環境を構築することができます。

本番環境への到達前に徹底的なテストが実行されるため、開発チームのメンバーは安心してコードの投稿を行えるはずです。その他利害関係者にとっても、デプロイプロセスが適切に管理される(エラーに強い環境)のは嬉しいものです。

あなたはKinsta APIをどのように使っていますか?APIに追加してほしいエンドポイントはありますか?Kinsta API関連のどんな解説をご希望ですか?コメント欄でお聞かせください。

Joel Olawanle Kinsta

Kinstaでテクニカルエディターとして働くフロントエンド開発者。オープンソースをこよなく愛する講師でもあり、JavaScriptとそのフレームワークを中心に200件以上の技術記事を執筆している。