Google App Engine + GoでHello, World!

Google App Engine + Google Cloud SQLでAPIサーバーを構築したので記録を残します。（Qiita初投稿！）
今回はGoで書いたプログラムをGoogle App Script(以下GAE)にアップロードしHTTPレスポンスが返ってくるところまでを記載します。（要はHello, World!)


# 環境

* OS: macOS Catalina Version 10.15.7
* IDE: Visual Studio Code Version: 1.50.0
* Go(Local): version go1.15.5 (GAEは2020/11現在1.14までサポートしています。この記事では1.14でGAEを使用)

GCP CLIのバージョン：

``` shell-session:zsh
Google Cloud SDK 319.0.0
bq 2.0.62
core 2020.11.13
gsutil 4.55
```

# 実装！
## 1. GCPのプロジェクト作成
GCP CLIのインストールは[ここ](https://accounts.google.com/signin/oauth/consent?authuser=0&part=AJi8hANg3aGIZhSwojrPFfRuaJlPYC8RN7bWpw2dnoivZtktCDF52u0XJTk9fRcWUuS_CDnQ-r_qwrnZuKeDGEuODVS6MTmnrwyAvu15lQfthTfokkkgtBJFSp5kNZefdlvWYhMZoqSHMOUiExcPQojwBCRGXKxRF0dCnTLFsBV4cheblXpoW0Nx3SS4-_deBgzONXK2eTFMYkt10u5L4IsdUKTYWLzWftkyrVQi-B3lr5XzGh0ESi-A9X5VIEA2j0eBxoLnlCdVaGQ0sIEkYrl9wmZHtrJdxOy9z8u0bF1NBkg0NzhIfSj21ZGfwBRpSoy4i86gtM_8je-69tIyJr-zGEBlOWZjgeHyODxO-U0ZLZH3zNw8l6uZ1NgTfT_cFM7U_MdS8-KT_INkeHtp1M1MMYEmi_nImA&as=S-670366299%3A1605799100341449&pli=1&rapt=AEjHL4MqDjDUchxxRV_2MU24exQ_TLcq_lfqgAno9R4IfhuZd4hHy47ffiLyZHilGJHr_sCDNfPrThzAztrZKZqtDu6HX-_SFQ#)や[ここ](https://cloud.google.com/sdk/docs/install#mac)を見てください。

CLIダウンロードしたらアカウントの認証を行います。以下のコマンドを実行します。

``` shell-session:zsh
%gcloud auth login
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/...
```

するとブラウザが立ち上がり以下のような画面が表示され、アクセスの許可を求められます。Allowをクリックします。

![Google Cloud SDK認証画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62768/4de51c2a-efb9-d292-481a-b927035dc0c8.png)



Googleのリファレンスにリダイレクトすれば認証は成功です。ターミナルに戻ると以下のように表示されます。

``` shell-session:zsh
You are now logged in as [ ログインした際に使用したGmailアカウント].
Your current project is [None].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID
```

GCPはプロジェクトごとに各プロダクトのインスタンスを配置します。ログイン時、`$ gcloud config set project PROJECT_ID`とカレントプロジェクトが設定されていないよと怒られているので、新たにプロジェクトを作成し、カレントプロジェクトとして設定します。

この記事では新規プロジェクトのProject IDを`test-api-server`として作成します。以降では`test-api-server`は適宜設定したProject IDに読み替えてください。

``` shell-session:zsh
%gcloud projects create test-api-server
Create in progress for [https://cloudresourcemanager.googleapis.com/v1/projects/test-api-server].
Waiting for [operations/cp.6857715921976913154] to finish...done.              
Enabling service [cloudapis.googleapis.com] on project [test-api-server]...
Operation "operations/acf.d16dec56-cb67-494e-a03d-02c1911a6bb0" finished successfully.
```

プロジェクトのリストを表示し、`test-api-server`が作成されていることを確認します。NAMEはGUI(Webのコンソール画面)で表示される際に使っているようです。

``` shell-session:zsh
%gcloud projects list
PROJECT_ID          NAME               PROJECT_NUMBER
...
test-api-server     test-api-server    630796980740
...
```

カレントプロジェクトを`test-api-server`に設定します。`gcloud config set {キー} {値}`で設定値を変更します。 `gcloud config get-value {セクション}/{キー}`で設定値を確認できます。
全ての設定値は`gcloud config list`で確認できます。また`gcloud projects describe {Project ID}`でプロジェクトの詳細を確認できます。

ここでは`project`の値をProject ID`test-api-server`にセットします。セット後、設定が正しいか確認します。

```
%gcloud config set project test-api-server
Updated property [core/project].

%gcloud config get-value core/project
test-api-server

%gcloud projects describe test-api-server
createTime: '2020-11-19T15:45:58.946Z'
lifecycleState: ACTIVE
name: test-api-server
projectId: test-api-server
projectNumber: '{12桁の数字}'
```

以上でGCPのプロジェクトを作成できました。

## 2. GAEインスタンス作成

GCP CLIを使用する際は毎回アップデートするようにしておきましょう。GCPは1日10数回のペースで更新がかかっているので必須です。

``` shell-session:zsh
%gcloud components update 
All components are up to date.
```

GAEを配置するリージョンを選択します。今回は東京リージョン`asia-northease1`を選択します。ターゲットユーザーから物理的に遠いリージョンを選んでしまうと当然ですがGAEの応答が遅くなるので注意です。

数字の入力を求められるので2をタイプしエンターします。

``` shell-session:zsh 
%gcloud app create 
You are creating an app for project [test-api-server].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.

Please choose the region where you want your App Engine application 
located:

...
 [2] asia-northeast1
...
Please enter your numeric choice:  2
```

入力後、数秒から数分でGAEの作成が完了します。

``` shell-session:zsh
Creating App Engine application in project [test-api-server] and region [asia
-northeast1]....done.                                                          
Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.
```

とりあえず指示通りデプロイしてみます。当然ですがまだ何もデプロイするものがないので怒られます。

``` shell-session:zsh
%gcloud app deploy        
ERROR: An app.yaml (or appengine-web.xml) file is required to deploy this directory as an App Engine application. Create an app.yaml file using the directions at https://cloud.google.com/appengine/docs/flexible/python/configuring-your-app-with-app-yaml (App Engine flexible environment) or https://cloud.google.com/appengine/docs/standard/python/config/appref (App Engine standard environment) under the tab for your language.
ERROR: (gcloud.app.deploy) [{現在のわーキンディレクトリまでのパス}] could not be identified as a valid source directory or file.
```

デプロイに失敗しているのでもちろんインスタンスもありません。`gcloud app instances list`で確認します。

``` shell-session:zsh
%gcloud app instances list
Listed 0 items.
```

続いて中身のアプリを作っていきます。


## 2. GAEにアップロードするファイルの作成

GAEに最低限必要なファイルはGAEの設定を記述した[app.yaml](https://cloud.google.com/appengine/docs/standard/go/config/appref?hl=ja)と処理を記述したスクリプトファイル（ここでは`main.go`とします）の2つです。この記事では`app.yaml`と`main.go`のみ作成します。

他にも3つの構成ファイルがあります。ただし必須ではありません。

* [cron.yaml](https://cloud.google.com/appengine/docs/standard/go/scheduling-jobs-with-cron-yaml?hl=ja): 定期的に実行するタスクを設定できます。(e.g. 毎日0時にメールを送るなど)
* [dispatch.yaml](https://cloud.google.com/appengine/docs/standard/go/reference/dispatch-yaml?hl=ja): ルーティングを制御します。アクセスするエンドポイントによって実行する処理を変更できます。
* [index.yaml](https://cloud.google.com/appengine/docs/standard/go/configuring-datastore-indexes-with-index-yaml?hl=ja): App Engineから直接呼び出せるDatastore(Firestore, NoSQL)の構成ファイルだそうです。（使ったことない）

index.yamlにより使用できるDatastoreには上限があるようですが、通常の利用には十分なサイズだそうです。詳しくは「[インデックス  |  Cloud Datastore ドキュメント  |  Google Cloud](https://cloud.google.com/datastore/docs/concepts/indexes?hl=ja#datastore-datastore-unindexed-property-query-go)」を見てください。

### app.yaml

`app.yaml`に必須な要素は`runtime`のみです。ここでは利用可能なGoのバージョンのうち最新の1.14を使用します。他の要素を学ぶ際は[ここ](https://cloud.google.com/appengine/docs/standard/go/config/appref?hl=ja)が参考になります。

``` yaml:app.yaml
runtime: go114
```
### main.go

この記事ではとりあえずGoで書いた処理をGAEで動くことを確認したいため、最小限のコードでHello, World!が表示されるようにコードします。

以下のコードでは、
1. 最低限のパッケージをインポートし、
2. 即時関数でHello, World!と表示させる関数を定義、
3. httpにハンドラ関数として渡した後に
4. 8080ポートを開きます。

Vimなど、適当なエディタを開き、以下のファイルを作成して保存します。

``` go:main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	handler := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello, World!")
	}
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
```

上記の処理を実行し、HTTPレスポンスが返ってくるかを確認します。

`go run main.go &`で処理をバックグラウンドで実行した後、`curl`でHTTPレスポンスを確認します。
Hello, World!が返ってきたら無事GoでWeb Serverを立ち上げられています。確認が終わった後`kill`コマンドでバックグラウンドで実行していたGoを停止します。

``` shell-session:zsh
%go run main.go &
[1] 41608

%curl localhost:8080
Hello, World!%

%kill 41608
[1]  + terminated  go run main.go
```

これでデプロイの準備が整いました。

## 4. デプロイ

GAEへのデプロイは基本的に以下のワンライナーで行えます。

```
%gcloud app deploy
```

ですが、初回デプロイ時はGAEに必要な追加コンポーネントのダウンロードが必要なため以下のようなメッセージが表示されます。`Y`をタイプしインストールします。

``` shell-session:zsh
application.


Your current Cloud SDK version is: 319.0.0
Installing components from version: 319.0.0

┌──────────────────────────────────────────────┐
│     These components will be installed.      │
├──────────────────────────┬─────────┬─────────┤
│           Name           │ Version │   Size  │
├──────────────────────────┼─────────┼─────────┤
│ App Engine Go Extensions │  1.9.71 │         │
│ App Engine Go Extensions │  1.9.71 │ 4.8 MiB │
└──────────────────────────┴─────────┴─────────┘

For the latest full release notes, please visit:
  https://cloud.google.com/sdk/release_notes

Do you want to continue (Y/n)?  Y
...
```

必要なコンポーネントがダウンロードできると、続いてGAEへのアップロードが始まります。

```
descriptor:      [/{カレントディレクトリへのパス}/app.yaml]
source:          [/{カレントディレクトリへのパス}]
target project:  [test-api-server]
target service:  [default]
target version:  [20201120t025037]
target url:      [https://test-api-server.an.r.appspot.com]


Do you want to continue (Y/n)?  Y
Please enter 'y' or 'n':  y

Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 2 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...failed.
```

うーん失敗してしまいました、、、初回ビルド時はWebコンソールからCloud Build APIを有効化しないといけないようです。

```                                           
ERROR: (gcloud.app.deploy) Error Response: [7] Access Not Configured. Cloud Build has not been used in project test-api-server before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudbuild.googleapis.com/overview?project=test-api-server then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
```

https://console.developers.google.com/apis/api/cloudbuild.googleapis.com/overview?project=test-api-server にアクセスします。(URL中のtest-api-serverは適宜Project IDに書き換えてください)

下記の画面が表示されるのでEnableをクリックし有効化してください。GCPの請求先を求められるので請求先のアカウントを選択しクリックします。

![Cloud Build APIの画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62768/ef7b7baa-8d82-c439-9014-81fba4d8c3f0.png)



GCP上でビルドができるようになりました。これでGAEにコードをデプロイできるはず...
再度`gcloud app deploy`を実行します。

```
%gcloud app deploy
Services to deploy:

descriptor:      [/{カレントディレクトリへのパス}/app.yaml]
source:          [/{カレントディレクトリへのパス}]
target project:  [test-api-server]
target service:  [default]
target version:  [20201120t030334]
target url:      [https://test-api-server.an.r.appspot.com]


Do you want to continue (Y/n)?  Y

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 0 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...⠛      
```

```
Setting traffic split for service [default]...done.                            
Deployed service [default] to [https://test-api-server.an.r.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse
```

`gcloud app browse`を実行するとブラウザが立ち上がり、main.goによるHTTPレスポンスを確認できます。
また`gcloud app instances list`によりインスタンスが立ち上がっていることを確認できます。

``` shell-session:zsh
%gcloud app instances list
SERVICE  VERSION          ID   VM_STATUS  DEBUG_MODE
default  20201120t030334  XXX  N/A
```

ここまででGAE上でGoを実行できるようになりました。

次回以降ではmain.goの中身を更新してAPIサーバーのように作り替えていきたいと思います。



# 参考
- [gcloud  |  Cloud SDK のドキュメント  |  Google Cloud](https://cloud.google.com/sdk/gcloud/reference)
- [Google App Engine 上での Go の使用](https://cloud.google.com/appengine/docs/go?hl=ja)
- [Google App Engine Go 1.12+ スタンダード環境のドキュメント](https://cloud.google.com/appengine/docs/standard/go?hl=ja)
- [サーバーレスの理解とメリット・デメリット（2020年版）] (https://qiita.com/takanorig/items/3a3a0b43b5be5b4a124f)