GitHub Actionsを使ってみよう！

# 1.初めに
N番煎じネタですが、やはり使っていて便利だとしみじみ思うGitHub Actionsをご紹介したいと思います。
（こういった記事を書くのは初めてですので若干緊張しています。。。）
この記事の目標はGitHub Actionsがどういうものかを知っていただくことと、実際にサンプルを作れるようになっていただくこととしています。

# 2.GitHub Actionsって何？
GitHub Actions は簡単に言うと GitHub が用意した CI/CD サービスのことです。  
yaml形式の定義ファイルを指定のディレクトリに入れておくだけで、勝手に処理を行なってくれます。  
ちなみに CI/CD サービスと書いていますが、CI/CD 以外にも利用できます。  
あんまり長々書いてもよろしくないので、もっと詳細が知りたい方は[公式ドキュメント](https://github.co.jp/features/actions)を見て下さい。

# 3.使い方
GitHub に用意したリポジトリに「.github/workflows」ディレクトリを用意してその中にyaml形式の定義ファイルを入れるだけです。  
yamlファイルは以下のように記載します。
```yaml
name: GitHub Actions Demo
on:
  push:
    branches:
      - 'main'
jobs:
  Sample:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: run command
        run: echo "Hello GitHub Actions!"
```
これだけでもGitHub Actionsは動かせます。  
このサンプルでやっていることを簡単に説明すると、

  1. mainブランチにコードがプッシュされたら処理を開始する。
  1. ubuntuの最新バージョンの仮想環境を用意する。
  1. リポジトリのソースコードをチェックアウトする。
  1. "Hello GitHub Actions!"と出力する。

こんな感じです。  
この steps のところに処理を追加し、組み合わせることでお好みのワークフローを構築することができるのです。
ではこのサンプルの中身を詳細に見ていきましょう。

## 3.1.on:
「on:」ではワークフローの発火条件を定義しています。  
このサンプルでは発火条件を以下としています。
```yaml
on:
  push:
    branches:
      - 'main'
```
これは main ブランチに push したら開始するよ、という条件です。  
このほかにも
```yaml
on:
  schedule:
    - cron: 0 15 * * *
```
で日本時間深夜00:00に開始する、という条件にしたり、
```yaml
on:
  workflow_dispatch:
```
でブラウザ上から手動起動する、という条件にしたりすることができます。    
[公式ドキュメント](https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions#on)からお好みの発火条件を探してみて下さい。

## 3.2.jobs:
「jobs:」では実際のワークフローの内容を定義しています。  
このサンプルでは一つのみですが、複数の job を用意して並列処理を行わせることもできます。  
```yaml
jobs:
  Sample:
```
「Sample」が job の名前です。  
複数の job を用意する場合はこの名前を使って処理の順番などを決めることができます。  
job の名前が決まったら、まずワークフローの実行環境を定義します。
```yaml
runs-on: ubuntu-latest
```
この「runs-on」でワークフローの実行環境を定義しています。  
このサンプルでは最新バージョンの ubuntu 上で処理を行わせています。  
GitHub Actionsでは ubuntu の他に
```yaml
runs-on: windows-latest
```
で Windows の実行環境にしたり、
```yaml
runs-on: macos-latest
```
で MacOS の実行環境にすることもできます。  
また、「セルフホストランナー」と呼ばれる機能を使うことで自分の好きな環境を用意し、実行環境に利用することもできます。
実行環境を定義したら、次はいよいよ処理を定義していきます。  
```yaml
steps:
  - name: Check out repository code
    uses: actions/checkout@v3
  - name: run command
    run: echo "Hello GitHub Actions!"
```
「steps」にワークフローで行わせる処理を記載していきます。  
このサンプルでは、まずリポジトリに存在するソースコードをチェックアウトし、次に"Hello GitHub Actions!"と出力させています。  
処理を「uses」と「run」で書いていますが、「uses」はGitHubの[Marketplace](https://github.com/marketplace)で公開されているワークフローを実行するよ、と言う宣言です。  
本来であれば、Gitコマンドを使ってソースコードをチェックアウトする処理を書く必要があるのですが、その一連の処理を自分で書かずに済む便利なものです。  
いろいろなワークフローが公開されていますので、それを利用すれば、想像以上に簡潔な定義ファイルになるかもしれません。  
「run」では通常のCLIコマンドを実行しています。  
ライブラリを追加する処理を入れれば様々なコマンドを実行することも可能です。

# 4.実際に CI/CD ツールとして使ってみた。
GitHub Actions は CI/CD 用に用意されたサービスですので実際に CI/CD 環境を用意してみました。  
それがこちらになります。
```yaml
# ECS on Fargate Deployment Action
name: deploy to ECS on Fargate - Develop Environment

# setting trigger
on:
  push:
    # developブランチ、かつnode-express/app配下に変更があった場合のみ実行
    branches:
      - 'develop'
    paths:
      - 'node-express/app/**'

# setting enviroment
env:
  DEPLOY_ENV: develop
  TARGET_DIRECTORY: node-express
  AWS_REGION: ap-northeast-1
  CLUSTER_NAME: ecs-demo
  ECR_REPOSITORY_NAME: ecs-demo/node-express
  IMAGE_TAG: ${{ github.sha }}
  ECS_SERVICE_NAME: ecs-demo-container-service
  AWS_WEB_IDENTITY_TOKEN_FILE: /tmp/awscreds

jobs:
  build-and-deploy:
    name: build docker image and deploy ecs
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

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

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
        aws-region: ${{ env.AWS_REGION }}

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
      run: |
        cd ./${{ env.TARGET_DIRECTORY }}/app
        docker build -t ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY_NAME }}:${{ env.IMAGE_TAG }} .
        docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY_NAME }}:${{ env.IMAGE_TAG }}
        echo "::set-output name=image::${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY_NAME }}:${{ env.IMAGE_TAG }}"

    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: node-express/task-definition.json
        container-name: ecs-demo-container
        image: ${{ steps.build-image.outputs.image }}

    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE_NAME }}
        cluster: ${{ env.CLUSTER_NAME }}
        wait-for-service-stability: true
```
今回は Docker コンテナイメージをビルドして、 Amazon ECR　（以下ECRとします。）にプッシュ後、 AWS の Amazon ECS （以下ECSとします。）にプッシュしたコンテナイメージを反映させるところまでを GitHub Actions にやらせています。  
（ECS や ECR についての説明は[公式](https://aws.amazon.com/jp/ecs/)[サイト](https://aws.amazon.com/jp/ecr/)をご参照ください。）
では簡単にやっている事を説明していきます。  

まずは GitHub Actions の発火条件ですね。
```yaml
on:
  push:
    # developブランチ、かつnode-express/app配下に変更があった場合のみ実行
    branches:
      - 'develop'
    paths:
      - 'node-express/app/**'
```
コメントに書いている通りですが、developブランチのnode-express/appディレクトリ内に変更があった場合に実行されるようにしています。  
何でもかんでも変更があったら動くようにするのは困るので制限をつけています。


```yaml
env:
  DEPLOY_ENV: develop
  TARGET_DIRECTORY: node-express
  AWS_REGION: ap-northeast-1
  CLUSTER_NAME: ecs-demo
  ECR_REPOSITORY_NAME: ecs-demo/node-express
  IMAGE_TAG: ${{ github.sha }}
  ECS_SERVICE_NAME: ecs-demo-container-service
  AWS_WEB_IDENTITY_TOKEN_FILE: /tmp/awscreds
```
こちらではワークフロー内で使用する環境変数を定義しています。  
このサンプルを流用したときに変更の必要がある場所を変数化したつもりです。  

ジョブは長いので分割して説明して行きます。  
```yaml
jobs:
  build-and-deploy:
    name: build docker image and deploy ecs
    runs-on: ubuntu-latest
```
今回はジョブは一つのみで行なっています。  
このジョブで何をやっているかの説明と ubuntu の最新バージョンの環境で実行するとしています。

```yaml
permissions:
  id-token: write
  contents: read
```
こちらは後述する AWS の認証処理で必要な権限を付与させています。  
この権限を付与しておかないと AWS 連携がうまく行きませんのでご注意ください。

```yaml
    - name: Checkout
      uses: actions/checkout@v3
```
こちらではリポジトリからソースコードをチェックアウトしています。

```yaml
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
        aws-region: ${{ env.AWS_REGION }}
```
こちらでは AWS 認証処理を行なっています。  
通常はアクセスキーを使用して認証を行うことが多いですが、漏洩などのリスクがありますので[こちらの記事](https://dev.classmethod.jp/articles/github-actions-aws-sts-credentials-iamrole/)を参考に IAM ロールを使用した認証処理で行なっています。  

```yaml
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
```
Docker コンテナイメージのアップロード先となる ECR にログインしています。

```yaml
    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
      run: |
        cd ./${{ env.TARGET_DIRECTORY }}/app
        docker build -t ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY_NAME }}:${{ env.IMAGE_TAG }} .
        docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY_NAME }}:${{ env.IMAGE_TAG }}
        echo "::set-output name=image::${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY_NAME }}:${{ env.IMAGE_TAG }}"
```
こちらで Docker コンテナイメージをビルドし、ECR にプッシュしています。  
次の処理に使うので、プッシュしたコンテナイメージの ECR 情報を環境変数に設定しています。

```yaml
    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: node-express/task-definition.json
        container-name: ecs-demo-container
        image: ${{ steps.build-image.outputs.image }}
```
こちらで先ほどビルドして ECR にプッシュしたコンテナイメージを使用するように ECS のタスク定義ファイルを更新しています。  

```yaml
    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE_NAME }}
        cluster: ${{ env.CLUSTER_NAME }}
        wait-for-service-stability: true
```
最後に更新したタスク定義ファイルを ECS にデプロイしています。  

以上が今回用意した CI/CD 環境となります。  
100行にも満たない定義ファイルを用意するだけで以降はビルドからデプロイまでを勝手に実行してくれるので非常に便利ですね。

# 5.最後に
今回は単純なサンプルや CI/CD 環境の構築サンプルを用意して説明させていただきましたが、他にも様々な使い道があると思います。  
例えばソースコードをリポジトリにプッシュするたびに、ビルドして、単体テストや静的解析を実行させたり、定期スクリプトを実行させたり用途は様々です。  
特にAWSについてはMarketplace上に公開されているワークフローが充実しているので、簡単に連携ができます。  
CI/CD 環境を用意したい、とある作業を自動化させたいなど、効率化するためのツールを探している方は GitHub Actions を検討してみては如何でしょうか？