【CI/CDシリーズ②】Github Actionsを利用したすぐに出来るCI/CD(コードのテスト・S3への自動デプロイ)

CI/CDシリーズとして、GitHub Actionsを用いたAWSインフラおよびコンテナアプリケーションの構築やテストの自動化について解説します。

第二回では、早速、簡単なアプリケーション・下記CI/CDフローの作成について説明します。

  • プルリクエストされたコードの自動リンティング
  • プルリクエストされたコードの自動デリバリー・デプロイ

dashboard

CI/CDとは

実際にアプリケーションやワークフローを作成する前に、そもそもCI/CDとは何かについて軽く説明します。

結論

  • CI: コードの変更を頻繁に統合し、早期に問題を発見すること
  • CD(Delivery): 本番環境にデプロイする準備を自動化し、リリースを行うこと
  • CD(Deployment): コードの変更を自動で本番環境にデプロイし、リリースサイクルを高速化すること

CI(Continuous Integration:継続的インテグレーション)

CIとは、開発者が行ったコード変更を頻繁に統合し、早期に検出できるようにすることです。 以下は簡単なプロセスの例です。

  • 開発者がコードをリポジトリにプッシュする
  • 自動ビルドとテストがトリガーされる
  • 問題があれば早期にフィードバックを得る

CD(Continuous Delivery:継続的デリバリー)

CDとは、コードの変更を自動でビルドし、テストし、準備が整い次第本番環境にデプロイできる状態に保つことです。 以下は簡単なプロセスの例です。

  • CIのプロセスを通過したコードは、自動的にステージング環境にデプロイされる
  • 追加のテストや承認プロセスを経て、本番環境にデプロイされる準備が整う

CD(Continuous Deployment:継続的デプロイ)

CDとは、コードの変更を自動で本番環境にデプロイすることです。 以下は簡単なプロセスの例です。

  • Continuous Deliveryのプロセスをさらに進め、ステージング環境での承認プロセスを省略する
  • テストに合格したコードは自動的に本番環境にデプロイされる

今回の構成

今回は、CloudFormationとS3バケットによる静的サイトホスティングを例にして、ローカルのコードをGitHubレポジトリにプルリクエストすることで、以下が自動的に行われます。

dashboard

  • CI:自動リンティング(コードに問題がないか確認)
  • CD:AWS上のS3にレポジトリのコードを同期
  • CD:AWS上のCloudFrontのキャッシュを削除

前提

CloudFrontとS3の構成がなくても、S3の同期はできます。 イメージさえ掴めればすぐに他のことにも応用が利くようなワークフローになっています。

各CloudFormationテンプレートも後述のGitHubレポジトリに格納しています。

利用するコード・フォルダ構成

今回は、こちらのコード・GitHubを利用します。

dashboard

フォルダ構成は以下の通りです。

※ワークフローフォルダはGitHub Actionsを利用するのに決まったフォルダ構成のため変更不可です。

Repogitory/
├── package.json
├── package-lock.json
├── eslint.config.mjs
├── .github/                           # Github Actionsワークフローフォルダ
│   └── workflows/                     # Github Actionsワークフローフォルダ
│       ├── ci.yaml    
│       └── cd.yaml    
├── AWS/             
│   └── cloudfront-s3.yml              # CloudpFormationテンプレート

└── Frontend/                          # フロントエンド用コード
    ├── images/                        # 画像ファイル
    |   ├── banana.png
    |   └── grape.png
    ├── index.html
    ├── styles.css
    └── scripts.js

各コードの説明(CI/CD 実装のための workflow のみ説明)

cd.yml

cd.ymlでは、Frontend/以下のいずれかのコードに対して、プルリクエストが行われた場合に、S3への同期、CloudFrontのキャッシュ削除が自動で行われます。

name: S3 sync and CloudFront cache invalidation

on:
  pull_request:
    paths: ['Frontend/*']
  • name:はワークフローの名前
  • on.pullrequest:はプルリクエストをトリガーに、ワークフローを起動すること
  • paths:はトリガーであるプルリクエストの対象のパス
  • つまり、Frontendフォルダ内のファイルがプルリクエストされた場合に、このワークフローが実行される
jobs:
  s3_sync:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
  • jobs:は複数のジョブがあること
  • s3_sync:はジョブの名前
  • runs-on:はジョブの実行マシンを指定
  • steps:は各ジョブを実行していくこと
  • uses: actions/checkout@v4 は、GitHub Actionsがリポジトリのコードをチェックアウト(クローン)するためのアクションを使用すること
  • つまり、Ubuntsuマシン上で、レポジトリのコードをクローンしている
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}          
          aws-region: ap-northeast-1

      - name: Define S3 bucket name
        env: 
          S3_SYNC_BUCKET: ${{ secrets.S3_SYNC_BUCKET }}
        run: |
          echo "S3_SYNC_BUCKET is set to $S3_SYNC_BUCKET"          

      - name: Sync to S3
        env: 
          S3_SYNC_BUCKET: ${{ secrets.S3_SYNC_BUCKET }}
        run: |
          aws s3 sync ./Frontend/ s3://$S3_SYNC_BUCKET --delete --exclude "README.md" --exclude ".git/*" --exclude ".github/*" --exclude ".gitignore" --exclude "AWS"          
  • uses: aws-actions/configure-aws-credentials@v4 はGitHub ActionsがAWSの認証情報を設定して、AWSのサービスにアクセスできるようにするためのアクションを使用すること(with以下で、各クレデンシャルやパラメーターを渡す)
  • aws-session-token:は IAM Identity Centerによって払い出されたIAMユーザーのクレデンシャルを利用する時のみ必要なパラメーター
  • env:は環境変数として、secrets.S3_SYNC_BUCKETの値を受け取ること
  • runs:はコマンドを実行すること(echoやaws cliを実行する)
  • つまり、GitHubシークレットに格納した情報をもとに、AWSへ認証を行い、レポジトリのコードをaws s3 syncコマンドで該当S3へ同期する
  invalidate_cache:
    runs-on: ubuntu-latest
    needs: s3_sync

    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
          aws-region: ap-northeast-1

      - name: Invalidate CloudFront cache
        env: 
          CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
        run: |
          aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/*"          
  • GitHubシークレットに格納した情報をもとに、AWSへ認証を行い、該当のCloudFrontの全てのキャッシュを削除する

ci.yml

ci.ymlでは、以下のコードにより、Node.jsのセットアップとリンティングを実行します。

name: autmated lint

on:
    pull_request:
      paths: ['Frontend/*']  

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
    - run: npm install
    - run: npm run lint

GitHub Secretにクレデンシャルを格納する

ひとつ前の章で、コードの解説を行いましたが、そこで出てきたGitHubシークレットについて説明します。

機密性の高い値はレポジトリのシークレットに登録することができます。

Settings → Secret and variables → Actions と遷移します。 以下画像のように、シークレット分、『New repository secret』をクリックし、NameSecretに値を入力します。

dashboard

ワークフローでは ${{ secrets.AWS_ACCESS_KEY_ID }} という形で参照可能です。

変更前の確認

プルリクエストによって、CI/CDワークフローを起動する前に、現状のファイルによるアプリケーションの画面を確認します。

CloudFrontのディストリビューションドメイン名をブラウザで確認すると、以下画像のように、バナナとブドウのみが降ってくるゲームということがわかります。

※GitHubのコードをクローンして、それら全てをS3にアップロードしてから、CloudFrontディストリビューションを作成する必要があります。

dashboard

プルリクエストによる CI/CD の起動

それでは、リンゴも降ってくるように、コードを変更し、プルリクエストを出していきます。

まずは、下記コマンドを実行し、ブランチを作成する。

git checkout -b feature-br

次に以下の様にコードを修正します。

  • images/ 内に、apple.pngを格納
  • scripts.js 内のコードを、他の果物のようにappleも追加




変更点1:
const fruitImages = ['banana', 'grape', 'apple'];





変更点2:
const appleImg = new Image();
appleImg.src = 'images/apple.png';





変更点3:
function drawFruit(fruit) {
    if (fruit.type === 'banana') {
        ctx.drawImage(bananaImg, fruit.x, fruit.y, fruitWidth, fruitHeight);
    } else if (fruit.type === 'grape') {
        ctx.drawImage(grapeImg, fruit.x, fruit.y, fruitWidth, fruitHeight);
    } else if (fruit.type === 'apple') {
        ctx.drawImage(appleImg, fruit.x, fruit.y, fruitWidth, fruitHeight);
    }
}

次に、下記コマンドを実行し、変更したコードをプッシュします。

git add .
>On branch feature-br
>Changes to be committed:
>  (use "git restore --staged <file>..." to unstage)
>        new file:   Frontend/images/apple.png
>        modified:   Frontend/scripts.js

git commit -m "add apple.png and modify scripts.js so that apple falls as well as banana and grape"
git push origin feature-br

次に、「Compare & pull request」ボタンが表示されるのでクリックします。

dashboard

次に、「Create pull request」ボタンをクリックしてプルリクエストを作成します。

dashboard

そうすると、下記画面の様に、ci.ymlとcd.ymlで定義したジョブが実行され、マージ可能になっています。

各ジョブの詳細は、ジョブの横のDetailをクリックするか、Actionsから確認することが出来ます。

dashboard

では、「Merge pull request」をクリックし、マージします。 S3へのコード同期とCloudFrontのキャッシュ削除は完了しているため、CloudFrontディストリビューションIDをブラウザから開き、リンゴが降ってくることを確認します。

dashboard

以上でハンズオン②は終了です。今回は、Jsコードのテスト、AWSへのデプロイを自動化しました。

最後にコードを戻しておきます。(リンゴが降らないように戻しておきます。)

ただし、今の構成の場合、GitHubシークレットにIAMアクセスキーを登録しており、セキュアではないことに加え、IAM Idenity Centerからユーザーを払い出している場合、都度アクセスキーを登録し直す手間が発生してしまいます。

次回は、OpenID Connect(OIDC)を利用した、上記への対処法のハンズオンを行います。