【爆速】Go × Docker で構築したアプリを GitHub Actions を使って GCP Cloud Run にデプロイする方法

# はじめに
最近は Go でのバックエンド開発やインフラ周りも少しずつ触る機会が増えてきたので備忘録としてまとめてみました。

## この記事で書くこと
- Go と Docker で作ったアプリを docker-compose で起動する方法
- 上記で作ったアプリを Github Actions 経由で Cloud Run にデプロイする方法

## この記事で書かないこと
- Go言語の基礎
- Docker / docker-compose の基礎
- Github Actions の基礎

## 事前準備
以下の項目は事前に設定しておいてください。
すでに登録済みの方はスルーでOKです。

### GCPアカウントの作成
- Google Cloud の[公式サイト](https://cloud.google.com/)にアクセス
- ページ右上の [無料で始める] ボタンをクリック
- Google アカウントでログインしてください。もし Google アカウントをお持ちでない場合は、[アカウントを作成] ボタンをクリックして新しい Google アカウントを作成
- アカウント作成フォームに必要事項を入力（必要な情報については、Google の指示に従ってください）
- 必要な情報を入力したら、Google Cloud Platform にアクセスするための支払い情報を入力（クレジットカード情報を入力するか、銀行
- 口座情報を使用して支払いを行うことができます。ただし、無料枠を超えた場合にのみ、料金が発生することに注意してください。）

### プロジェクトの作成
- アカウントを作成した後、Google Cloud Console にアクセスして、[プロジェクトを作成] ボタンをクリック
- プロジェクト名を入力し、[作成] ボタンをクリック。プロジェクトIDが自動的に生成されます。
- Cloud Run APIを有効にします。左側のメニューから[APIとサービス] > [ダッシュボード]をクリックします。[Cloud Run API] を検索し、APIを有効にする。

### gcloud コマンドを使えるように
`gcloudコマンド`は、Google Cloud Platformの様々なリソースを管理するために使用されるコマンドラインツールです。gcloudコマンドを使用することで、仮想マシン、データストレージ、データベース、アプリケーションサービスなどのGoogle Cloud Platformのリソースを作成、管理、監視することができます。

- [こちら](https://cloud.google.com/sdk/docs/install)から gcloud コマンドをインストール
- gcloudコマンドを使用してログイン。gcloudコマンドを使用して、Google Cloud Platformにログインすることができます。以下のコマンドを使用してログインしてください。

```bash
gcloud auth login
```
- 作成したプロジェクト一覧を以下のコマンドで確認。プロジェクトが作成されていたら表示されるはず。
```bash
gcloud projects list 
```

# 手順 1. Go と Docker の環境構築
早速 Docker コンテナ上で Go言語を使ってアプリを作成していきましょう。ここでは
- docker-compose でコンテナを起動
- ルートパス（"/"）にリクエストが来たときに HTTP レスポンスとして "Hello, World!!" という文字列を返す
までやっていきます。

## プロジェクトの作成
適当なディレクトリでプロジェクトを作成し、以下のようなディレクトリ構成を作成します。go.mod と go.sum は `go mod init` した時に自動生成されるので手動で作る必要はありません。

```bash
project
├── Dockerfile
├── README.md
├── backend
│   ├── cmd
│   │   └── main.go
│   ├── go.mod
│   └── go.sum
└── docker-compose.yml
```
## Goプロジェクトの作成
backend ディレクトリに移動し、以下のコマンドを実行してGoプロジェクトを作成します。ここでgo.mod と go.sumが生成されます。

```bash
cd backend
go mod init example.com/myproject
```
main.go を作成します。
```
mkdir cmd
cd cmd
touch main.go
```
main.go ファイルにアプリケーションのコードを追加します。
```go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, World!!")
	})

	http.ListenAndServe(":8080", nil)
}
```

## Dockerfile の作成
プロジェクトのルートディレクトリに Dockerfile を作成します。
```Dockerfile
FROM golang:1.18-alpine

WORKDIR /app

COPY backend/ .

RUN go build -o main ./cmd/main.go

EXPOSE $PORT

CMD ["/app/main"]
``` 

- `FROM golang:1.18-alpine`: golang:1.18-alpine イメージをベースにして新しいイメージを作成します。これは、Go 1.18 がインストールされた軽量な Alpine Linux ベースのイメージです。
- `WORKDIR /app`: 作業ディレクトリを /app に設定します。以降のコマンドはこのディレクトリで実行されます。
- `COPY backend/ .`: backend/ ディレクトリの内容を、現在の作業ディレクトリ（/app）にコピーします。
- `RUN go build -o main ./cmd/main.go`: go build コマンドを実行し、./cmd/main.go をコンパイルして実行可能なバイナリ main を作成します。
- `EXPOSE $PORT`: 環境変数 $PORT で指定されたポート番号を公開します。これにより、コンテナ外からアプリケーションにアクセスできるようになります。
- `CMD ["/app/main"]`: コンテナが実行されると、/app/main バイナリが実行されます。これにより、アプリケーションが起動します。


## docker-compose.yml の作成
```yml
version: '3'

services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '8080:8080'

```

- **version: '3'**: この `docker-compose.yml` ファイルが使用する Docker Compose のバージョンを `3` に設定します。
- **services**: このセクションでは、Docker Compose で定義するサービス（コンテナ）をリストします。
  - **backend**: サービス名として `backend` を定義します。このサービスは、以下の設定に従ってコンテナを作成および実行します。
    - **build**: このセクションでは、Docker イメージのビルド方法を指定します。
      - **context: .**: イメージのビルドコンテキストとして、現在のディレクトリを指定します。
      - **dockerfile: Dockerfile**: `Dockerfile` という名前のファイルを使用してイメージをビルドします。
    - **ports**: ホストとコンテナ間で公開するポートのマッピングを指定します。
      - **- '8080:8080'**: ホストのポート `8080` をコンテナのポート `8080` にマッピングします。これにより、ホストからコンテナ内のアプリケーションにアクセスできるようになります。

## イメージの作成とアプリケーションの起動
```
docker-compose up --build 
```
http://localhost:8080/ にアクセスすると Hello World が表示されているはず。ここまでできたら Github への push も忘れずにしましょう。


![スクリーンショット 2023-04-02 17.35.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/37d03003-dd42-047b-be62-43f944c6378c.png)

# 手順 2. コンテナを Cloud Run にデプロイ

docker-compose で起動できることが確認できたらまずは手動で cloud run へデプロイしてみましょう。

## Dockerイメージをビルド/プッシュする
手順１で作成した Dockerfile を下記コマンドでビルドします。
```
docker build -t gcr.io/[PROJECT_ID]/[IMAGE_NAME]:[TAG] .
```
`gcr.io/[PROJECT_ID]/[IMAGE_NAME]:[TAG]`は Google Container Registry（GCR）を使ってイメージを管理する場合のリポジトリ名の形式です。gcr.io は GCR のホスト名、[PROJECT_ID] は GCP プロジェクトの ID、[IMAGE_NAME] はイメージ名、[TAG] はタグ名です。

次にビルドしたコンテナイメージを、Google Container Registry（GCR）などのコンテナレジストリにアップロードします。
```bash
docker push gcr.io/[PROJECT_ID]/[IMAGE_NAME]:[TAG]
```

## Cloud Run にデプロイする

下記コマンドを実行すると Clou Run にデプロイできます。
```bash
gcloud run deploy [SERVICE-NAME] \
  --image gcr.io/[PROJECT-ID]/[IMAGE-NAME]:[TAG] \
  --platform managed \
  --allow-unauthenticated \
  --region [REGION]
```

# 手順 3. GitHub Actions で自動デプロイ


Cloud Run へのデプロイを  Github Actions 経由で自動デプロイできるようにしていきます。

## deploy.yml の作成

プロジェクトのルートに `.github/workflows/deploy.yml`を作成してください。

```yml
name: Deploy to Google Cloud Run

on:
  push:
    branches:
      - main

# 下記は適宜書き換えてください
env:
  GCP_PROJECT_ID: プロジェクトの名前
  GCP_REGION: リージョン
  IMAGE_NAME: gcr.io/プロジェクトの名前/イメージの名前

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v1

      - name: Authenticate with Google Cloud
        uses: google-github-actions/auth@v0.4.0
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Configure Docker
        run: gcloud auth configure-docker

      - name: Build and push Docker image
        run: |
          TAG=$(echo $GITHUB_SHA | head -c7)
          docker build -t ${IMAGE_NAME}:${TAG} .
          docker push ${IMAGE_NAME}:${TAG}

      - name: Deploy to Cloud Run
        run: |
          TAG=$(echo $GITHUB_SHA | head -c7)
          gcloud run deploy web-push-demo \
            --image ${IMAGE_NAME}:${TAG} \
            --platform managed \
            --region ${GCP_REGION} \
            --allow-unauthenticated
```

- **name**: ワークフローの名前です。この例では、`Deploy to Google Cloud Run` としています。
- **on**: ワークフローのトリガーとなるイベントを指定します。この例では、`main` ブランチへの `push` イベントがトリガーとなります。
- **env**: ワークフロー全体で使用する環境変数を定義します。この例では、`GCP_PROJECT_ID`、`GCP_REGION`、`IMAGE_NAME` の 3 つの環境変数が設定されています。
- **jobs**: ワークフロー内で実行されるジョブを定義します。この例では、`deploy` という名前のジョブが 1 つあります。
- **runs-on**: ジョブが実行される GitHub Actions のランナーを指定します。この例では、`ubuntu-latest` を使用しています。
- **steps**: ジョブの中で実行されるステップを定義します。この例では、以下のステップがあります。
   - **Checkout code**: `actions/checkout@v2` アクションを使用して、リポジトリのコードをチェックアウトします。
   - **Set up Cloud SDK**: `google-github-actions/setup-gcloud@v1` アクションを使用して、Google Cloud SDK をセットアップします。
   - **Authenticate with Google Cloud**: `google-github-actions/auth@v0.4.0` アクションを使用して、Google Cloud への認証を行います。`GCP_SA_KEY` シークレットを使用して、サービスアカウントの認証情報を提供します。
   - **Configure Docker**: `gcloud auth configure-docker` コマンドを実行して、Docker クライアントが Google Container Registry への認証を行えるように設定します。
   - **Build and push Docker image**: Docker イメージをビルドし、Google Container Registry にプッシュします。このステップでは、`GITHUB_SHA`（コミットの SHA）を使用してタグを生成し、イメージをビルドしてプッシュします。
   - **Deploy to Cloud Run**: Cloud Run にデプロイを行います。先ほどビルドされた Docker イメージを使用して、Cloud Run サービスをデプロイします。環境変数 `GCP_REGION` および `IMAGE_NAME` が使用されます

## GCPコンソールからサービスアカウントの作成

- 先ほど作成したGCPのプロジェクトへ移動し、左側のナビゲーション メニューから、IAM と管理 をクリックし、「サービス アカウント」を選択します。
- 「サービスアカウント」ページが表示されます。「+サービスアカウントの作成」ボタンをクリックします。
- 「サービスアカウント名」フィールドに、わかりやすい名前を入力します。オプションで「サービスアカウントの説明」も入力できます。 「作成」ボタンをクリックして次に進みます。
- 「サービスアカウントの権限を付与する」画面で、このサービスアカウントに必要なロールを追加します（他のサイトでは「Cloud Run サービスエージェント」「Cloud Run 管理者」「ストレージ管理者」だけ追加しておけばOKと書いてたのですが、自分は「storageオブジェクト」の権限も付与しないと上手くいきませんでした）

![スクリーンショット 2023-04-02 14.35.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/4e470308-09bf-ae80-9980-340cf5c745ba.png)


- オプションで、ユーザーアカウントやサービスアカウントを追加して、このサービスアカウントのリソースへのアクセスを制御することができます。設定が完了したら、「完了」をクリックします。

### GitHub リポジトリに秘密鍵を設定

`deploy.yml` では、秘密鍵は `${{ secrets.GCP_SA_KEY }}` として参照されています。この値は、GitHub リポジトリの[シークレット](https://docs.github.com/en/actions/security-guides/encrypted-secrets)から取得されます。シークレットは、リポジトリ設定の「Secrets」セクションに保存されており、GitHub Actions のワークフローでのみアクセス可能です。

#### GCPコンソールからの操作

- 先ほどGCPコンソール上から作成したサービスアカウントの行にある「編集」アイコン（鉛筆）をクリックします。
- 「キー」タブに移動し、「キーの追加」ボタンをクリックし、「JSON」を選択して、「作成」をクリックします。
- サービスアカウントキーの JSON ファイルがダウンロードされます。このファイルには、サービスアカウントの認証情報が含まれています

![スクリーンショット 2023-04-02 15.20.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/322de0f5-eb95-d2a0-6009-53c9071dac76.png)
自動でJSONファイルがダウンロードされます

#### Github リポジトリからの操作

- ソースコードを管理してる Github リポジトリを開き、Settings > Secrets And Variables を開き、New repository secret をクリックしてください

![スクリーンショット 2023-04-02 15.19.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/4b479278-f182-da0e-bf4c-772c80d57f93.png)

- Name を GCP_SA_KEY、Secret に先ほどダウンロードしたJSON形式の秘密鍵を貼り付けてください

 
![スクリーンショット 2023-04-02 15.34.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/abac3b4f-cbe6-4b06-9afd-53b25375f126.png)


ここまで設定できると main ブランチにマージされることで Cloud Run に自動デプロイされるようになっているはずです。


![スクリーンショット 2023-04-02 15.41.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/cc903591-5ba9-08d0-3587-6853d5d56a29.png)
詳細画面
![スクリーンショット 2023-04-02 15.43.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/e561b335-6fb4-ed06-47b4-05d39e351972.png)

デプロイされたURLにアクセスすると「Hello World」が表示されます。
![スクリーンショット 2023-04-02 15.51.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1741616/e81133a3-eebe-332c-7393-e8a90b332408.png)


# さいごに

備忘録として Go と Docker で使ったアプリを Github Actions を使って Cloud Run へデプロイする方法をまとめてみました。もし不明点やおかしな点があればご指摘ください。