【GitHub Actions】GitHub Package Registryを利用して同一Dockerイメージをjobで共有する

皆さんはGitHub ActionsやGitHub Registryを利用していますか？
まだGAになっていないので使っていない方もいるかもしれませんが、来週の11/13(水)にGAとなるので今のうちにお試しで使ってみることをお勧めします。

それでは本題ですが、GitHub ActionsでビルドしたDockerイメージを後続のjobで使い回したい時にどのようにしていますか?
元々パブリックなリポジトリならDocker Hubにプッシュしたりできますが、プライベートリポジトリではそうできません。プライベートレジストリをもっていれば全て解決しますが、自前で立てたり、課金しなくてはならないので少し大変です。
ちなみに私は以前書いた[記事](https://qiita.com/homines22/items/af96baa6ba51b1d59124)にあるように毎回ビルドしていました :cry:
これでは同じイメージではないので、よろしくありません。せっかくTrivyやDockleを使ってスキャンをしているのに...
そこで今回は、GitHub Package Registryを利用してGitHub Actionsのworkflowの中で1度ビルドしたDockerイメージを使いまわす方法を説明していきたいと思います。workflowは以前書いた[記事](https://qiita.com/homines22/items/af96baa6ba51b1d59124)を使用します。

# GitHub Package Registryとは？
GitHub Package Registryとはソフトウェアパッケージのホスティングサービスです。Docker HubやPyPIなどをイメージしてもらえれば分かりやすいと思います。
以下が[公式ドキュメント](https://help.github.com/en/github/managing-packages-with-github-package-registry/about-github-package-registry)の冒頭の説明文です。

> GitHub Package Registry is a software package hosting service, similar to npmjs.org, rubygems.org, or hub.docker.com, that allows you to host your packages and code in one place. You can host software packages privately or publicly and use them as dependencies in your projects.

# GitHub Package Registry使用方法
## セットアップ
[こちら](https://github.com/features/package-registry)のリンクへ飛んでボタンを二回程クリックすればすぐに使うことができます。めっちゃ簡単:wink:

<img width="630" alt="screenshot 2019-11-07 21.34.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173064/2a655572-3034-cf96-c33b-10afd197baec.png">


## Dockerイメージのプッシュ
GitHubのDocker Registryにプッシュする方法はとても簡単です。他のプライベートレジストリやサードパーティーレジストリへのプッシュ方法と基本的に同じです。
ただ、GitHubへのログインに二段階認証を有効にしている場合は、Registryへログインする際にアクセストークンが必要になります。[こちら](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)を参考にトークンを取得してください。

```bash
# DockerのRegistryにログイン
docker login docker.pkg.github.com -u owner -p password

# DockerfileはCWDにあるとする
docker build -t docker.pkg.github.com/owner/repository_name/sample .

# GitHub Package Registryへプッシュ
docker push docker.pkg.github.com/owner/repository_name/sample
```

イメージ名は後から変更可能なので以下のようにしてもOKです。

```bash
docker build -t sample .
docker tag sample docker.pkg.github.com/owner/repository_name/sample
```

## Dockerイメージのプル

```bash
# DockerのRegistryにログイン
docker login docker.pkg.github.com -u owner -p password

docker pull docker.pkg.github.com/owner/repository_name/sample
```

とりわけ難しい部分もないのでGitHub Actionsでやってみましょう:bangbang:

# GitHub Actions
GitHub ActionsからGitHub Package Registryを利用するTipsは、`docker login`パスワードに環境変`GITHUB_TOKEN`の指定することです。GitHub Actionsが自動生成してくれるので、わざわざボタンをポチポチしてトークンを生成して管理する必要はなくなります。

## 処理の流れ

```
                 +-----------------+
                 | ①   　　        |
                 |     build       |
                 |　Dockerイメージを  |
                /|　ビルドしてプッシュ　 |\
               / |                 | \
              /  +-----------------+  \
             /                         \ 
 +-----------------+                +-----------------+
 | ②    　 　      |                | ②'  　　        |
 |     Trivy       |                |     Dockle      |
 |                 |                |                 |
 +-----------------+                +-----------------+
             \                          /
              \                        /
               \                      /
                \                    /
                 +-----------------+
                 |  ③ 　　         |
                 |   Push to ECR   |
                 |                 |
                 +-----------------+
```

## Workflow例

```yaml
name: Push Docker image to ECR

# リモートブランチにプッシュされたらWorkflowが起動
on: push

env:
  # BuildKitを有効化
  DOCKER_BUILDKIT: 1
  IMAGE_NAME: sample
  IMAGE_TAG: test

jobs:
  build:
    name: Build image
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@master

      - name: Login GitHub Registry
        # 自動生成されるGITHUB_TOKENを使用する
        run: echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com -u owner --password-stdin
      - name: Build image
        # `github.repository`で`owner/repository_name`が取得できる
        run: docker build -t docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG} --file Dockerfile .

      - name: Push image to GitHub Registry
        run: docker push docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG}

      - name: Notify Result to Slack
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          job_name: ':docker: *Build image*'
          channel: '#general'
          url: ${{ secrets.SLACK_WEBHOOK }}

  trivy:
    name: Trivy Scan Vulnerability
    runs-on: ubuntu-18.04
    needs: build
    steps:
      - uses: actions/checkout@master

      - name: Login GitHub Registry
        run: echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com -u owner --password-stdin

      - name: Pull image from GitHub Registry
        run: docker pull docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG}

      - name: Install trivy
        run: |
          sudo apt-get install --no-install-recommends apt-transport-https gnupg
          wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
          echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -cs) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
          sudo apt-get update
          sudo apt-get install --no-install-recommends trivy

      - name: Vulnerability Scan with Trivy
        run: |
          trivy -q --severity HIGH,CRITICAL \
            --exit-code 1 docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG}

      - name: Notify Result to Slack
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          job_name: ':trivy: *Trivy*'
          channel: '#general'
          url: ${{ secrets.SLACK_WEBHOOK }}

  dockle:
    name: Dockle
    runs-on: ubuntu-18.04
    needs: build
    steps:
      - uses: actions/checkout@master

      - name: Login GitHub Registry
        run: echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com -u owner --password-stdin
      - name: Pull image from GitHub Registry
        run: docker pull docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG}

      - name: Install dockle
        run: |
          VERSION=$(curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \
          grep '"tag_name":' | \
          sed -E 's/.*"v([^"]+)".*/\1/' \
          )
          curl -L -o dockle.deb https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.deb
          sudo dpkg -i dockle.deb
          rm dockle.deb

      - name: Check image with dockle
        run: dockle docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG}

      - name: Notify Result to Slack
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          job_name: ':docker: *Dockle*'
          channel: '#general'
          url: ${{ secrets.SLACK_WEBHOOK }}

  push:
    name: Push Docker Image to ECR
    runs-on: ubuntu-18.04
    needs: [trivy, dockle]
    steps:
      - uses: actions/checkout@master

      - uses: actions/setup-go@master
        with:
          go-version: '1.13'

      - name: Install ecs-cli
        run: go get github.com/aws/amazon-ecs-cli/ecs-cli

      - name: Login GitHub Registry
        run: echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com -u owner --password-stdin

      - name: Pull image from GitHub Registry
        run: |
          docker pull docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG}
         # Change docker image name
          docker tag docker.pkg.github.com/${{ github.repository }}/${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:${IMAGE_TAG}

      - name: Push image to ECR
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-1
        # GOPATH bug [reference](https://github.com/actions/setup-go/issues/14)
        run: $(go env GOPATH)/bin/ecs-cli push ${IMAGE_NAME}:${IMAGE_TAG}

      - name: Notify Result to Slack
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          job_name: ':rocket: *Push to ECR*'
          channel: '#general'
          url: ${{ secrets.SLACK_WEBHOOK }}
```

これで一回のDockerイメージビルドで後続のJobでもそのイメージを使いまわせるようになりました:clap:

# Reference
- [About GitHub Package Registry](https://help.github.com/en/github/managing-packages-with-github-package-registry/about-github-package-registry)
- [Configuring Docker for use with GitHub Package Registry](https://help.github.com/en/github/managing-packages-with-github-package-registry/configuring-docker-for-use-with-github-package-registry)