🌷

GitHub Actions で AWS と連携する CI/CD 環境を構築する

2024/09/01に公開

はじめに

GitHub Actions は、GitHubのイベントに応じて様々なワークフローを自動で実行できるGitHubの機能です。
GitHub Actions を使うと、GitHub への push などのイベントをトリガーに自動テストやビルドを実行する CI (Continuous Delivery) に加えて、デプロイ準備やデプロイまでを自動で実行する CD (Continuous Deployment) 環境を簡単に実現できます🐱

今回は GitHub Actions でバックエンドのソースを AWS EC2 へ、フロントエンドのソースを AWS S3 へデプロイする CI/CD 環境を構築しましたので、その備忘録です。

フォルダ構成としては、下記の通りです。

/
├── backend/              # バックエンド関連のコードや設定
├── frontend/             # フロントエンド関連のコードや設定
├── .github/              # GitHub関連の設定
│   └── workflows/        # GitHub Actionsのワークフロー定義
│       └── <ワークフローファイル名>.yaml  # ワークフローファイル
└── README.md             # プロジェクトの説明書

GitHub Actionsの基本構造

GitHub Actionsは、ワークフロー、トリガー、ジョブ、ステップ、アクションから構成され、YAML 構文を使用してCI/CDプロセスのワークフローを定義します。
各ワークフローは、コード リポジトリ内の .github/workflows という名前のディレクトリに個別の YAML ファイルとして格納します。
まずは基本的なワークフローの例を見てみましょう👀

name: CI/CD Workflow Sample              # ワークフローの名前

on:                                      # トリガーの定義
  push:                                  # プッシュイベント
    branches:
      - main                             # mainブランチへのプッシュ

jobs:                                      # ジョブの定義
  build:                                   # ジョブ名
    runs-on: ubuntu-latest                 # 実行環境
    steps:                                 # ステップの定義
      - name: Checkout Code                # ステップ名
        uses: actions/checkout@v4          # リポジトリのチェックアウト
      - run: echo "Hello World!"           # コマンドを実行
1. ワークフロー (Workflow)
  • ワークフローは、GitHub Actionsの一連のプロセスを定義します。
  • YAML形式で記述され、特定のイベント(例: プッシュやプルリクエスト)に応じて自動的に実行されます。
2. トリガー (Triggers)
  • ワークフローが実行される条件を指定します。
  • 主なトリガーにはpushpull_requestscheduleworkflow_dispatchworkflow_runなどがあります。
3. ジョブ (Jobs)
  • ジョブは、ワークフロー内で実行される一連のステップです。
  • 各ジョブには、実行環境(runs-on)を指定します。
  • ジョブごとに異なる実行環境(マシン)が起動するイメージです。
4. ステップ (Steps)
  • ステップは、ジョブ内で実行される個々の操作です。
  • ステップは、外部アクション(uses)を呼び出したり、シェルコマンド(run)を実行したりします。
5. アクション (Actions)
  • アクションは再利用可能なコードの単位で、特定の機能を提供します。
  • uses: actions/~ で定義しています。
  • GitHub Marketplaceからアクションを取得したり、自分で作成したりできます。
    参考:Enhance your workflow with extensions

EC2 へ自動デプロイする ワークフロー

GitHub Actionsから EC2 へ、バックエンドのソースを自動デプロイするワークフロー定義の例です。

  • .github/workflows/backend.ymlファイルの中身
# ワークフローの名前
name: backend                                 

# ワークフローのトリガーの定義
on:                                           
  push:                                       # プッシュイベントをトリガーとする
    branches:
      - main                                  # mainブランチにプッシュされたとき
    paths:
      - 'backend/**'                          # backendディレクトリ内の変更
      - 'github/**'                           # githubディレクトリ内の変更

# すべてのジョブで共通して使用される設定を定義
defaults:
  run:                                        
    working-directory: backend                # 作業ディレクトリをbackendに設定

# ワークフローで実行するジョブの定義
jobs:
  ####### Build(CI) #######
  build:
    runs-on: ubuntu-latest                     # ジョブを実行するOS(ランナー)
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4              # リポジトリのチェックアウト
      - name: Setup Node.js
        uses: actions/setup-node@v4            # Node.jsのセットアップ
        with:
          node-version: 18.x                   # 使用するNode.jsのバージョン

      - name: Install Dependencies
        run: npm ci                             # 依存関係をインストール
      - name: Run Tests
        run: npm run test -- --coverage         # テストを実行し、カバレッジを取得
      - name: Build Application
        run: npm run build                       # プロダクションビルドを実行

      - name: Start Application
        run: node dist/index.js &               # ビルドしたアプリケーションをバックグラウンドで起動
      - name: Wait for Application
        run: npx wait-on -t 10000 http://localhost:8080/api/health  # アプリケーションが起動するまで待機
      - name: Run API Tests
        run: npm run test:api                   # APIテストを実行

      # アーティファクトの保存
      - name: Upload Coverage Artifact
        uses: actions/upload-artifact@v4
        if: always()                           # 常にアーティファクトをアップロード
        with:
          name: coverage                        # アーティファクトの名前
          path: backend/coverage/**            # 保存するカバレッジデータのパス
          retention-days: 5                     # 保存期間(5日)

      - name: Upload Build Artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist                           # アーティファクトの名前
          path: backend/dist/index.js          # 保存するビルド成果物のパス
          retention-days: 5                     # 保存期間(5日)

  ####### Deploy(CD) #######
  deploy:
    needs: build                                # buildジョブが完了していることが前提
    if: github.event_name == 'push'            # pushイベントの場合のみ実行
    runs-on: ubuntu-latest                      # ジョブを実行するOS(Ubuntuの最新バージョン)
    steps:
      - name: Download Build Artifact
        uses: actions/download-artifact@v4      # buildジョブからアーティファクトをダウンロード
        with:
          name: dist                            # ダウンロードするアーティファクトの名前
          path: backend/dist                    # ダウンロード先のパス

      - name: Install SSH Key
        uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.BACKEND_SERVER_SSH_KEY }}  # 秘密鍵を使用
          known_hosts: ${{ secrets.BACKEND_SERVER_KNOWN_HOSTS }}  # known_hostsの設定

      - name: Upload Application
        run: rsync -v dist/index.js ${SERVER_USER}@${SERVER_IP}:~/index.js  # rsyncでファイルをアップロード
        env:
          SERVER_USER: ${{ secrets.BACKEND_SERVER_USER }}  # サーバーユーザー名
          SERVER_IP: ${{ secrets.BACKEND_SERVER_IP }}      # サーバーIP

      - name: Restart Backend Service
        run: ssh ${SERVER_USER}@${SERVER_IP} sudo systemctl restart backend  # サーバーでサービスを再起動
        env:
          SERVER_USER: ${{ secrets.BACKEND_SERVER_USER }} 
          SERVER_IP: ${{ secrets.BACKEND_SERVER_IP }}      

      - name: Check Backend Service Status
        run: ssh ${SERVER_USER}@${SERVER_IP} sudo systemctl status backend    # サーバーでサービスの状態を確認
        env:
          SERVER_USER: ${{ secrets.BACKEND_SERVER_USER }}  
          SERVER_IP: ${{ secrets.BACKEND_SERVER_IP }}      

補足
  • test:api:package.jsonのscripts内に任意のコマンド名を定義できます。ここに記載されたscriptは、npm run [スクリプト名]で実行できます。
    "scripts": {
      "dev": "nodemon",
      "test": "jest",
      "test:api": "stepci run stepci/workflow.yml",
      ・・・
    },
    
  • --coverage::テストフレームワーク(例えば、JestやMochaなど)に対するオプションで、テストカバレッジのレポートを生成することを指示します。テストカバレッジは、実行されたテストによってコードのどの部分がカバーされているかを示す指標で、品質保証やバグの早期発見に役立ちます。
  • GitHub Actionsのアーティファクトとは、ワークフローの実行中に生成されるファイルやデータのことを指します。これには、ビルド成果物、テスト結果、ログファイル、設定ファイルなどが含まれます。後で参照したり他のワークフローで使用したりするために使用します。
  • シークレット情報:GitHub > [Settings] > [Secrets and variables] > [Actions]に環境変数として保存しています。
  • Jobの間で実行されている環境は共有されない(別の綺麗な環境で実行される)為、buildジョブで生成されたartifactをdeployジョブでダウンロードして使用するようにしています。

AWSとGitHub ActionsをOpenID Connectで連携する

GitHub Actionsから S3 へ、フロントエンドのソースを自動デプロイする際の認証方法としては OpenID Connect を使用します。
GitHub Actionsのマニュアル:アマゾン ウェブ サービスでの OpenID Connect の構成 を参考にしながら設定を進めます。

手順としては、以下の通りです。

  1. AWS への ID プロバイダーの追加
  2. 自動デプロイのためのIAMロール設定
  3. GitHub Actions ワークフローを更新する

「2. 自動デプロイのためのIAMロール設定」では、GitHub Actions での自動デプロイ用にIAMロールを作成します。[信用されたエンティティタイプ]に、IAM Role のカスタム信頼ポリシーを下記のように設定します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "<作成したIDプロバイダのARN>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:<GitHubの組織またはアカウント名>/<GitHubのリポジトリ名>:ref:refs/heads/main"
        }
      }
    }
  ]
}

次に、S3にソースをアップロードするためのIAMポリシーを付与します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowListBucket",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::<バケット名>"
        },
        {
            "Sid": "AllowPutObject",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::<バケット名>/*"
        }
    ]
}

S3 へ自動デプロイする ワークフロー

GitHub Actionsから S3 へ自動デプロイするワークフロー定義の例です。

  • .github/workflows/frontend.ymlファイルの中身
name: frontend                            # ワークフローの名前

on:                                       # ワークフローのトリガーを定義
  push:                                   # プッシュイベントで実行
    branches:
      - main                              # mainブランチへのプッシュ時にトリガー
    paths:
      - 'frontend/**'                     # frontendディレクトリ内の変更
      - 'github/**'                       # githubディレクトリ内の変更

defaults:
  run:                                    # ジョブ実行時のデフォルト設定
    working-directory: frontend           # 作業ディレクトリをfrontendに設定

permissions:                              # ワークフローに必要な権限を指定
  id-token: write                         # JWTリクエストに必要な権限
  contents: read                          # actions/checkoutに必要な権限

jobs:                                     # ワークフロー内のジョブを定義
  deploy:                                 # デプロイジョブ
    runs-on: ubuntu-latest                # ジョブを実行するOS(Ubuntuの最新バージョン)
    steps:                                # ジョブ内で実行されるステップのリスト
      - name: Checkout Code
        uses: actions/checkout@v4        # リポジトリの内容をチェックアウトするアクション
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v3  # AWSの認証情報を設定するアクション
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-oidc-Role  # 使用するIAMロールのARN
          aws-region: ap-northeast-1    # AWSリージョンを指定

      - name: Sync to S3
        run: aws s3 sync . s3://<バケット名> --exact-timestamps --region ap-northeast-1  # S3にファイルを同期アップロード

さいごに


無事 GitHub Actions でフロントエンド/バックエンドのソースを各サービスへデプロイする CI/CD 環境を構築できました💮
以上、えみり〜でした|ωΦ)ฅ

参考

Discussion