GAEでRailsのstaging環境を作った

# 概要

今関わっているプロジェクト(Rails)でGAE上にstaging環境を作ることになったが、
設定値周りでちょっと苦労したので構築にあたっての設定内容を残す。

# Staging環境

Rails詳しくないのでstaging環境の作り方を調べたところ、大きく

- Rails.envにstagingを追加(enviroments/staging.rb)
- Rails.envはproductionで、設定値を切り替える

の2通りがある模様だが、1個目のRails.env追加の方は色々問題もあって(理由は備考に)、今回は2個目を採用。

2個目の方式の場合、production/stagingといった環境がRails.envとは独立した概念になるため、envという単語がどちらを指してるのが分かりづらい。

ここではproduction/stagingなどで区別するenvをproject_envと呼ぶことにする。

# project_envの設定値管理

設定ファイルの管理方法自体は大きく3通りあった。

1. 設定ファイル
2. 環境変数
3. credentials (Rails5.2より導入)

色々検討した結果「1. 設定ファイル」の方式を採用することに(長くなったので検討は備考に)

今回はgemの[railsconfig/config](https://github.com/railsconfig/config)を使用する。

config/以下にsettings.ymlというファイルを用意するが、大元のsettings.yml以外にも

```
config/settings/development.yml
config/settings/production.yml
```

といった環境ごとのファイルが用意できる他、

```
config/settings.local.yml
config/settings/development.local.yml
config/settings/production.local.yml
```

などローカル(というかファイルが置いてあるプロジェクト)用の設定ファイルも読み込んでくれる。

ちなみに設定は以下の順序で、下の方が優先になるとのこと。

```
config/settings.yml
config/settings/#{environment}.yml
config/environments/#{environment}.yml

config/settings.local.yml
config/settings/#{environment}.local.yml
config/environments/#{environment}.local.yml
```

基本的には、

- 全体やenvごとに固定したい値は、localじゃないファイルに記載してcommit
- その環境(project_env)でのみ使いたい値は、localファイルに記載しつつ.gitignoreに追加してcommitはしない
- 秘密情報はlocalファイルに加える。env共通だったとしてもproject_envごとに管理する

といった方針で管理。

3番目の秘密情報については、運用上あまりenv共通になることはなかったのでproject_envごとで問題はなかった。


# デプロイ

GAEを使っているのでCloudBuildでデプロイを行う。

以下のようなcloudbuild.yamlを用意しつつ、CloudBuild側でトリガー設定を行う。

```
steps:
  - name: 'gcr.io/cloud-builders/gsutil'
    args: ['cp', 'gs://${_BUCKET}/config/*', './config/']
  - name: 'gcr.io/cloud-builders/gcloud'
    args: ['app', 'deploy', 'app-${_PROJECT_ENV}.yaml']
substitutions:
  _PROJECT_ENV: dev
```

## 1. gsutil

```
  - name: 'gcr.io/cloud-builders/gsutil'
    args: ['cp', 'gs://${_BUCKET}/config/*', './config/']
```

CloudBuildのトリガー設定を行うと、cloudbuild.yaml上では紐づけたbranchのコードがすでに展開された状態になっているので、その状態に対しての操作を書くことができる。

このため、GCSにsettings.local.ymlを配置しておきgsutilで配布することで、コミットしていないファイルもrails側に配布することができる。

今回は他にも配布したいファイルがあったため、それらまるごとrailsのconfig以下に配布するようにした。

配置したGCSのバケット名は、トリガーの代入変数の_BUCKETで設定しておく。

また、cloudbuildのサービスアカウントがgcsに触れる必要があるので、IAMでストレージのオブジェクト閲覧権限も設定しておく。

## 2. deploy

```
  - name: 'gcr.io/cloud-builders/gcloud'
    args: ['app', 'deploy', 'app-${_PROJECT_ENV}.yaml']
```

gsutilでrails環境ができたので、gcloud app deployコマンドを実行。

共通のapp.yaml一個で複数にデプロイしてもよいが、運用上はproject_envごとにapp.yamlを分けて作成した方が便利なことが多そう。
開発はしょぼいインスタンスで、本番はちゃんとしたスペックのもの、とかを切り分ける場合にはapp.yamlでの切り分けが必要になってくる。

上記のコマンドの場合、app-stg.yamlなどのファイルを用意しておき、トリガーの代入変数にも_PROJECT_ENVを設定しておく。

あとはトリガー設定がうまく行っていれば、branchをpushして

# まとめ

ということで、GAEでRailsのstaging環境を作成したときのメモでした。

基本的には

- settings.local.ymlを用意
- GCSに配置
- CloudBuildでgcsからファイルを持ってくる処理を追加

でうまくいく気がする。

あとは秘密情報に関してはバケット権限を考慮しつつ、どうしても回避が難しければCloud KMSを使っていく感じか。

いつも文章多めなので、図とかもっと増やしたい....

# 備考1: Rails.env追加の問題点

Railsでenvを追加してみると、自分でロジックを追加するのは簡単だが、Rails自体が他のenvを想定していないことが多い。

一例を上げるとsecret_key_base取得は以下のような実装になっていて、development/testとそれ以外で挙動が分かれていたりする。

```
def secret_key_base
  if Rails.env.test? || Rails.env.development?
    secrets.secret_key_base || Digest::MD5.hexdigest(self.class.name)
  else
    validate_secret_key_base(
      ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
    )
  end
end
```

database.yml等はenvごとに接続設定を管理するので、同じdevelopmentでもローカル用と共有用で項目を分けるため、localというenvを追加した。

上記のsecret_key_baseのようなRails本体で分岐しているところは流石にどうしようもなく、都度env対応をする羽目に。

stagingを作る目的は本番相当の環境での動作確認なので、ロジックが違う事態は避けたい。
ということでRails.envはいじらずに、別途project_envの概念を持ち込むことに。

# 備考2. 設定ファイルの管理方式検討

## 1. 設定ファイル

設定値をyml等で管理して、コード側でそれを呼び出す簡易的な変数管理

[railsconfig/config](https://github.com/railsconfig/config)というgemが有名のようで、

- Settings.hoge.fugaなど階層データを扱え、呼び出しもdotつなぎで楽
- settings.yml他、envごとのymlやsettings.local.ymlというローカルのみ有効な設定ファイルも持てる

など色々利点があった。

今回はconfigを採用し、project_envごとにsettings.local.ymlを用意してGCS等に配置し、コードと一緒に配ることで反映する方式を採用することに。

## 2. 環境変数

システム環境変数(ENV)を使う方式
gemとしては[dotenv](https://github.com/bkeepers/dotenv)が有名とのこと

ただ、設定を階層構造にしづらく(_でつなぐとかで無理やりやる？）、設定管理が大変になりそうなので断念。

## 3. credentials

rails5.2から導入された機能。
1や2の方式では、接続用の鍵情報やDBパスワードなどの秘密情報をどう管理するかが懸念だが、これは設定ファイルを暗号化することでコミットできるので、git上などで履歴管理ができる。

公式はproductionにしか使えないが、Rails.envごとに分けられる[rails-env-credentials](https://github.com/sinsoku/rails-env-credentials)というgemも登場していて、Rails.envごとに設定を分けられる。

ただ今回はproject_envが違う設定ファイルを用意したいので、Rails.envが同じ場合にファイル分けが別途必要になってしまう。

結局暗号化されたファイルをgit上で管理できる利点よりも、暗号鍵や設定ファイルの用意や編集の手間がかかる欠点が大きいと判断し断念。