最小限の権限を持つサービスアカウントでGCRへPUSHするGithubActionsワークフローの作成

# はじめに
コンテナをビルドしてGCRへPUSHするGithubActionsのワークフローを作成する。
この時にGCR用のバケットにのみ書き込み権限を持つサービスアカウントを作成し、それを利用して認証を行う。

# 環境
パイプライン上で利用する環境についてはソースコード内に記述されているため、ローカルの操作に必要なもののみ記述  
* macOS Catalina Version 10.15.4
* fish (利用シェル)
* Google Cloud SDK 272.0.0
* gsutil version 4.46
* Docker version 19.03.8

# 前準備

## GCRサービスの有効化
```bash
$ gcloud services enable containerregistry.googleapis.com
```

## GCR用のバケット作成
バケットに関する権限制御を行うためには事前にバケットが存在している必要があるので、何でもよいのでコンテナイメージをpushすることでGCR用のバケットを作成する
既にGCRを利用しており、バケットが存在する場合はこの操作は不要

```bash
# GCPにプロジェクト名を取得して環境変数に設定
$ set GCP_PROJECT (gcloud info --format='value(config.project)')

# GCRへのログイン(GCSの管理者権限があるユーザーで実施)
$ gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://us.gcr.io/

# Imageを作成(us.gcr.ioの部分は他のZONEでも可)
$ echo 'FROM ubuntu:latest' | docker build -t us.gcr.io/$GCP_PROJECT/sample:latest -

# イメージをPUSHする
$ docker push us.gcr.io/$GCP_PROJECT/sample:latest

# 結果を確認
$ gcloud container images list --repository=us.gcr.io/$GCP_PROJECT

# PUSHしたイメージの削除
$ gcloud container images delete us.gcr.io/$GCR_PRIJECT/sample --force-delete-tags
```

## GCRのバケットにのみ書き込み権限を持つサービスアカウントを作成
サービスアカウント名はgcr-writeとして作成する。

```bash
# GCPにプロジェクト名を取得して環境変数に設定
$ set GCP_PROJECT (gcloud info --format='value(config.project)')

# サービスアカウント名を環境変数に設定
$ set GCR_WRITE_SA gcr-write

# プロジェクトと名前を引数にサービスアカウント を作成
$ gcloud iam service-accounts create $GCR_WRITE_SA --project=$GCP_PROJECT --display-name $GCR_WRITE_SA

# 作成したサービスアカウントのID情報を環境変数に設定
$ set GCR_WRITE_SA_EMAIL (gcloud iam service-accounts list --project=$GCP_PROJECT --filter="displayName:$GCR_WRITE_SA" --format='value(email)')

# バケットのURLを環境変数にSET(人によって変わる)
$ set GCR_BUCKET_URL gs://us.artifacts.$GCP_PROJECT.appspot.com/

# バケットに対する読み取り権限付与
$ gsutil acl ch -u $GCR_WRITE_SA_EMAIL:WRITE $GCR_BUCKET_URL

# 結果確認
$ gsutil acl get $GCR_BUCKET_URL

# サービスアカウントのkeyファイル(json)の作成
$ set GCR_WRITE_SA_DEST ~/.gcp/gcr-write.json
$ mkdir -p (dirname $GCR_WRITE_SA_DEST)
$ gcloud iam service-accounts keys create $GCR_WRITE_SA_DEST --iam-account $GCR_WRITE_SA_EMAIL
```

## Github Actionsのワークフロー
作成したサービスアカウントを利用してGCRへコンテナをPUSHするワークフローを作成する
Dockerfileと同じリポジトリ上にこのyamlファイルを配置指定することでワークフローが実施される
まずファイル全体を以下に示し、その後各部分の詳細を解説する

```yaml
name: Container Create(Build & Push)

on:
  push:
    tags:
      - v*

env:
  GCP_PROJECT: ${{secrets.GCP_PROJECT}}
  SA_JSON_KEY: ${{ secrets.SA_JSON_KEY }}
  ZONE: us.gcr.io
  IMAGE: sample-app

jobs:
  container-create:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Container build & Push
        run: |
          echo $SA_JSON_KEY | docker login -u _json_key --password-stdin https://us.gcr.io
          IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/v##g")
          docker build -t $ZONE/$GCP_PROJECT/$IMAGE:$IMAGE_TAG -t $ZONE/$GCP_PROJECT/$IMAGE:latest .
          docker push $ZONE/$GCP_PROJECT/$IMAGE:$IMAGE_TAG
          docker push $ZONE/$GCP_PROJECT/$IMAGE:latest
```

### ワークフローの実行条件
v* の正規表現に一致するタグがPUSHされた時にワークフローが実行される

```yaml
on:
  push:
    tags:
      - v*
```

### 環境変数の設定
ワークフロー内で利用する環境変数の設定
* GCP_PROJECT : GCPのプロジェクト名 (事前にGithubのSecretを作成しておく)
* SA_JSON_KEY : サービスアカウントのJSONKEY (事前にGithubのSecretを作成しておく)
    - 前章で作成したサービスアカウントのkeyファイルの中身を利用して作成する
* ZONE : GCRで利用するZONEの場所を選択して作成する
* IMAGE : 作成するイメージ名

```yaml
env:
  GCP_PROJECT: ${{secrets.GCP_PROJECT}}
  SA_JSON_KEY: ${{ secrets.SA_JSON_KEY }}
  ZONE: us.gcr.io
  IMAGE: sample-app
```

### コンテナのBuildとPushを行う
JOB内で以下の手順を実施している

1. JSON_KEYを利用してサービスアカウントで認証してDocker loginを行う
2. ```github.ref```でタグ情報を取得できるが、取得する値はrefs/tags/v*となっているのでバージョン情報だけになるように整形する
3. GCRのURLを指定したイメージ名とバージョンタグとlatestタグを付与してイメージをビルドする
4. バージョンタグのコンテナイメージをPUSHする
5. latestタグのコンテナイメージをPUSHする

```yaml
      - name: Container build & Push
        run: |
          echo $SA_JSON_KEY | docker login -u _json_key --password-stdin https://us.gcr.io
          IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/v##g")
          docker build -t $ZONE/$GCP_PROJECT/$IMAGE:$IMAGE_TAG -t $ZONE/$GCP_PROJECT/$IMAGE:latest .
          docker push $ZONE/$GCP_PROJECT/$IMAGE:$IMAGE_TAG
          docker push $ZONE/$GCP_PROJECT/$IMAGE:latest
```


## 参考
* [GCPのDocker認証方法](https://cloud.google.com/container-registry/docs/advanced-authentication?hl=ja#gcloud)
* [GCSのアクセス制御リスト（ACL）](https://cloud.google.com/storage/docs/access-control/lists?hl=ja)