Google App EngineにGit管理ファイルだけデプロイする


GAEへのデプロイはCloud SDKを使って`gcloud app deploy`というコマンドで行うことができますが，基本的にカレントディレクトリ以下のすべてのファイルがアップロードされてしまうので，デプロイ用の設定ファイル`app.yaml`で除外ファイルを指定できるようになっています．（[GCPのドキュメント](https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml)）

ただ，除外ファイルを指定する場合，意図しないファイル（開発用の認証ファイル，お試しコード混入など）がある場合に，それがアップロードされてしまう危険があったので，Git管理のファイル（とビルド済みコードと`app.yaml`）だけがデプロイされるように，デプロイ用のスクリプトを作って対処しました．

本当はCIツールなどでちゃんと`git pull`してからデプロイするのが良いのでしょうが，小規模の検証システムを手元で開発してデプロイしたい場合に必要だったので作りました．

herokuのように`git push`でデプロイできるオプションがあると便利なんですけどね．

## 環境

* Google App Engine (Node) スタンダード環境
* Google Cloud SDK 207.0.0
* Node v8.11

## 作ったサンプルプロジェクト

* [koa](https://github.com/koajs/koa)で動作するWebアプリケーション
* 環境変数の`HELLO_NAME`を読んで`Hello <HELLO_NAMEの値>`とだけ返す
* babelでビルドした`lib/app.js`を起動する
* ソースのリポジトリは[こちら](https://github.com/ottijp/gae-deploy-sample)

## ビルドスクリプト

リポジトリの`gae-deploy.sh`がそうです．

```bash:gae-deploy.sh
#!/bin/bash -eu

# デプロイ対象のGitブランチ
GIT_BRANCH=${1:-master}
# app.yamlのパス
APP_CONFIG_FILE=${2:-app.yaml}
# gcloudコマンドのオプション
GCLOUD_OPTS=${@:3}

echo "GIT_BRANCH=${GIT_BRANCH}"
echo "APP_CONFIG_FILE=${APP_CONFIG_FILE}"
echo "GCLOUD_OPTS=${GCLOUD_OPTS}"

read -p "デプロイしてもよろしいですか？(y/N): " confirm
if [[ $confirm == [yY] ]]; then
  TEMP_DIR=`mktemp -d`
  echo "TEMP_DIR=$TEMP_DIR"

  # Gitブランチのエクスポート
  git archive --format=tar $GIT_BRANCH | tar -C $TEMP_DIR -xf -

  # app.yamlのコピー
  cp $APP_CONFIG_FILE $TEMP_DIR

  # ビルド
  cd $TEMP_DIR
  npm install
  npm run build

  # デプロイ
  gcloud app deploy $APP_CONFIG_FILE $GCLOUD_OPTS

  rm -r $TEMP_DIR
fi
```

## デプロイ方法

`npm run deploy <Gitブランチ> <app.yamlのパス>`

例えば

`npm run deploy master app.production.yaml`

`app.produnction.yaml`の内容が次のようなとき，Webサーバにアクセスすると`Hello production-me`と表示されます．

```yaml:app.production.yaml
runtime: nodejs8
env_variables:
  HELLO_NAME: production-me
```

## デプロイ時のコンソール

```bash:console
$ npm run deploy master app.production.yaml

> gae-deploy-sample@1.0.0 deploy /Users/xxxxx/src/github.com/ottijp/gae-deploy-sample
> ./gae-deploy.sh "master" "app.production.yaml"

GIT_BRANCH=master
APP_CONFIG_FILE=app.production.yaml
GCLOUD_OPTS=
デプロイしてもよろしいですか？(y/N): y
TEMP_DIR=/var/folders/jh/8g_d52792dld3_ck2yphd2280000gn/T/tmp.xrls1RGL

> fsevents@1.2.4 install /private/var/folders/jh/8g_d52792dld3_ck2yphd2280000gn/T/tmp.xrls1RGL/node_modules/fsevents
> node install

[fsevents] Success: "/private/var/folders/jh/8g_d52792dld3_ck2yphd2280000gn/T/tmp.xrls1RGL/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
Pass --update-binary to reinstall or --build-from-source to recompile
npm WARN gae-deploy-sample@1.0.0 No description

added 273 packages in 4.634s

> gae-deploy-sample@1.0.0 build /private/var/folders/jh/8g_d52792dld3_ck2yphd2280000gn/T/tmp.xrls1RGL
> babel src --out-dir lib

src/app.js -> lib/app.js
Services to deploy:

descriptor:      [/private/var/folders/jh/8g_d52792dld3_ck2yphd2280000gn/T/tmp.xrls1RGL/app.production.yaml]
source:          [/private/var/folders/jh/8g_d52792dld3_ck2yphd2280000gn/T/tmp.xrls1RGL]
target project:  [xxxxx]
target service:  [default]
target version:  [20180709t221520]
target url:      [https://xxxxx.appspot.com]


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

Beginning deployment of service [default]...
Created .gcloudignore file. See `gcloud topic gcloudignore` for details.
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 8 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://xxxxx.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
```

## 参考

* [gitでsvn export的なことをやる - Qiita](https://qiita.com/scalper/items/1905b47209989dda5648)
* [shell - Process all arguments except the first one (in a bash script) - Stack Overflow](https://stackoverflow.com/questions/9057387/process-all-arguments-except-the-first-one-in-a-bash-script)