環境変数管理をdotenvからconfigへのスムーズな移行手順

# 環境変数管理をdotenvからconfigへのスムーズな移行手順

フリーランスでお仕事しているamyroiです。
最近はRubyOnRailsを使っていきたい企業の開発基盤や開発整備などを担当しています。

そんな中、プロジェクトの環境変数管理で使われているDotenvの廃止を2社経験するという珍しい体験をしたので、
今回はその手順、ノウハウを書いていこうと思います。


# どうして廃止するの :question: 
* プロジェクトのリポジトリに公開したくない秘密情報をdotenvで管理してhost毎に管理する為によく使われるdotenvですが、host側の管理となると会社の規模によってはインフラ担当者に依頼が発生し、いちエンジニアが管理できない。
* dotenvファイルを暗号化する機構を別で持つ事もあり、一つの環境変数を変更するのにインフラから作業が切り離せない。
* host毎の環境変数管理を管理する便利なgemがちゃんとあるので[config](https://github.com/railsconfig/config)に変更しようよ。

# 実現できた事:v:
* 環境変数をconfigに変更しいちエンジニア側で管理を完結できるようになった。
* itamaeなどの構成管理が不要になる。
* 暗号化復号化の機構もリポジトリ内で完結
* 暗号化用のkeyをAWSのKMSを使い、エンジニア毎・ホスト毎のkeyを一度作るだけでOK。
* 暗号化復号化処理を自動化させることによりプロジェクトに参加したばかりのエンジニアでも意識する必要がない。

いいことづくめですね ::sparkles:

# 何を使うの? :gem: 
実際に対応したRails,Rubyのバージョンと実現するために使ったgemを紹介します。
新たに追加したのはconfig, yaml_vault, AWS KMSです。

* Rails5.0
* Ruby2.3.3
* [config](https://github.com/railsconfig/config)
* [yaml_vault](https://github.com/joker1007/yaml_vault)
* [AWS KMS](https://aws.amazon.com/jp/kms/)
* [Rspec3](https://github.com/rspec/rspec-rails)
* [capistrano](http://capistranorb.com/)
* bin/setup

# 始める前に:walking:
* 事前調査と設計をしてから手をつけるのが得策です。
* 事前準備(調査)編と導入編に分けました。
* 2回目となるconfigへの移行作業の規模が大きく、なかなか大掛かりとなりました。調査と設計をしっかりしていくのがポイントとなります。


# 事前準備(調査)編 :gear:

## dotenvで管理している機構を確認する
まずはdotenvがリリース環境で展開されどのような構成で動作しているのかを確認します。

1. dotenvの各enviromentsでの管理の仕方を確認
* インフラ側の管理の仕組みを確認 
* 暗号化の仕組みを確認
* 各enviroment環境で復号化される機構の確認
* dotenvで管理している環境変数の種類の確認

> 実例 :pencil: 
> dotenvの各ホストへの配布にitamae,暗号化にyaml_vaultを利用していました。こちらは全てインフラ側で行われていました。
> またdotenvとは別に独自環境変数のclass定義がありました。

## enviromentsの目的を明確にする。
プロジェクトで使われているproduction, staging, development, testのenviromentsの意味を明確にします。

> 実例 :pencil: 
>developmentとlocal環境、ローカルでのtestとcircle ciでのテストで使っている環境変数に相違がある場合がありました。
>みなさんはそんな事しないと思いますが何があるか分かりませんね。ここは前もって整理しておきましょう。

## 使っている環境変数を確認
1. 各enviromentsで使われいてる環境変数を復号化して一覧にします。
* dotenv以外の独自環境変数classがないかチェックし移行対象にします。
* 必要のない環境変数の洗い出しをします。

> 実例 :pencil: 
>dotenv以外に独自にclassからYAML.loadさせてアプリ固有の環境変数を作っていました。
>そして一部の変数はdotenvから呼び出すというスパゲッティ。

> :pencil: 
>既に使われていない環境変数が残っている事、独自定義の環境変数もどきと重複のチェックも行います。

## 環境変数の呼び出し元の種類を確認
呼び出し元が統一されていたら楽ですが、そうもいきません。

* シンゴルクオーテーションとダブルコーテーションの違い
* exists?を使っているところ

```ruby
# 3種類ありました。
ENV['GOOGLE_CLIENT_ID']
ENV["GOOGLE_CLIENT_ID"]
ENV.exists?('GOOGLE_CLIENT_ID')
```

## プロジェクト内でRails.env.{enviroment}?で制御している箇所を洗い出す
環境変数をつかっているのにRails.env.{enviroment}?でif分制御している箇所があったりします。
これは [enviromentsの目的を明確にする]でローカルとdevelopmentで違う値を使いたいときなどにハードコーディングしている場合がありますのでリファクタしていくことにしましょう。

# 導入(設計)編 :pencil2:
まだ実装しません。暗号化の仕組みを選定し、設計をしていきます。

## 暗号化の仕組みを選定する

### 候補 :scales:
#### sekrets(https://github.com/ahoward/sekrets)
Rails5.1から導入される秘密情報の暗号化の参考にされているsekretsが有力候補でしたが、ファイル事暗号化してしまう為、環境変数名がリポジトリ上で確認できません。

#### yaml_vault(https://github.com/joker1007/yaml_vault)
jokerさんのyaml_vault、vaultキー配下のkeyのvalueだけ暗号化してくれます。
key管理にKMSが使える。

### 決定:pencil: 
本来ならsekretsにするところですが、key名までも暗号化されてしまうと変数名すらわからない状態になってしまう為、yaml_vaultを採用しました。より運用しやすくするためにrakeタスクを作成することにしました。

## 暗号化・復号化の仕組みを考える
* リポジトリには暗号化されたファイルをcommit
* 復号化されたファイルは.gitignore対象にしコミットしない
* 復号化のキーはAWS KMSで管理

### 復号化するタイミングを決める
#### 環境構築時

`bin/setup` 内で復号化

#### capistranoデプロイ時
deployコマンドに仕込みます。

#### code deploy デプロイ時
deployタスクに仕込みます。

# 導入(実装)編 :pick:
やっと実装していきます。

## config導入
* https://github.com/railsconfig/config
いつもどおりgemで導入します。

### 構成
暗号化対象と対象外のファイル、配置構成を下記としました。

```ruby
config/secret.yml # cookie,sessionkey等。暗号化対象外
config/settings.yml #  enviroments共通の設定ファイル。暗号化対象外
config/settings/ # 復号化されたenviroment毎の設定ファイル(gitignore対象)
|
| - development.yml
| - staging.yml
| - test.yml
| - production.yml
|
config/encrypted/ # 暗号化されたenviroment毎の設定ファイル
|
| - development.yml
| - stging.yml
| - test.yml
| - production.yml
```

## configファイルに環境変数を移動する。
整理した環境変数をconfigファイルに移動していきます。
命名規則も綺麗にしていきましょう。

* サンプル :pencil: 

```ruby
ENV['GOOGLE_CLIENT_ID']

=>

Settings.google.client_id
```
## dotenvとconfigの変数の対応表を作ります。
主にRspecでの単体テスト用に作っていきます。
~~懐かしい~~jpmobileの絵文字の[conversion_table](https://github.com/jpmobile/jpmobile/blob/94a9a7b4f03f93c9703f3329203a3145c683a3a9/lib/jpmobile/emoticon/conversion_table.rb)を思い出して作りました。

このconversion_tableがあれば動作テストを減らすこともできますし、もし移行によるバグが発生しても変更された変数名が他のエンジニアでも容易にチェックできます。

```ruby
SETTINGS_TABLE_FOR_ENV =
{ 
  GOOGLE_CLIENT_ID: 'google.client_id'
}
```
> :pencil: 
> 削除対象の環境変数は対応表にはいれません。
> その代わりSpecに検証対象外のkeyとして残しておきます。

## yaml_vaultのrakeタスクを作成する。
さてここでyaml_vaultのrakeタスクを作ります。
### 理由
* yaml_vaultの[ISSUE](https://github.com/joker1007/yaml_vault/issues/3)にも上がっていますが、現時点(2017/08/04)でyaml_vaultは暗号化対象yamlのvaultキー配下をデフォルトで暗号化します。
* configファイルにvaultキーを作ると`Settings.vault.google.client_id` となりかっこ悪い
* オプションで暗号化対象のkeyを渡すとvaultキー以外でも暗号化してくれますが、全て指定しないと他が暗号化されないまま出力されてしまう。
* エンジニアの日々の運用をしやすいようにSettingsファイル配下のkey一覧を取得して、keyを指定せずに暗号化復号化できるようにrakeタスクを作りました。

:pencil: 
> 実例をお見せしたいですが、ここは公開していないコードになる為割愛します。 :bow: 

実行タスクは下記のようにしました。 :point_up: 

#### 暗号化
* config/encrypted/{enviroment}.ymlからconfig/settings/{enviroment}.ymlを作成
* config/settings/{enviroment}.ymlが存在する状態で実行

```ruby
# 全enviromentsを暗号化
$ bundle exec rake yaml_vault:encrypt
# enviroment指定
$ bundle exec rake yaml_vault:encrypt['development']
```

###  復号化
* config/encrypted/{enviroment}.ymlから config/settings/{enviroment}.ymlを作成

```ruby
# 全enviromentsを復号化
$ bundle exec rake yaml_vault:decrypt

# enviroment指定
$ bundle exec rake yaml_vault:decrypt['development']
```


## 暗号化復号化をbin/setup, デプロイタスクに組み込む
環境開発時、検証環境・本番サーバーへのデプロイ時のタスクにそれぞれ上記で作ったrakeタスクを組み込んでいきます。

* capistranoサンプル :memo

```ruby
namespace :config do
  task :decrypted do
    on roles(:app) do
      execute :rake, 'yaml_vault:decrypt["staging"]'
    end
  end
end

after 'deploy:updated', 'config:decrypted'
```

## dotenvからconfig移行のテストを書く
これが一番大事ですね。
rakeタスクのspecはもちろんですが、dotenvとconfigの変数の対応表を使い、specを作っていきます。

* 各enviroments毎にループさせ、Dotenv.loadをした値と、復号化したconfigファイルの値が一致しているのかのテストを書いていきます。

>:pencil: 
> ここで値チェックは終わりです。あと少し！

## dotenvの呼び出し元をconfigの変数名に置換
環境変数の削除の準備ができましたのでconfigの変数名に置換していきます。
ここは対応表を使って置換していくのがいいでしょう。

## コードのリファクタ
* 独自環境変数用のクラス、if分制御している箇所をリファクタしていきます。

## 動作テスト
* 各enviroments用のテスト環境を作り、動作テストを行います。

## dotenv削除
1. gem dotenv削除
* itamae側のdotenv配置ロジックを削除
* リポジトリ内にサンプルのdotenv等があればそちらも削除

## リリース!
* 移行作業を行います。
* 変更点が多いので監視をしましょう。

## 後処理
* 動作テストしてリリースしたら後処理を行います。
* リリース後ホスト内にあるdotenvファイル群を削除していきます。

## 移行完了
お疲れ様でした！


# 最後に(感想など)
* KMSの設定やテスト環境の構築などは省略しました。
* dotenvをリリース環境で使っているプロジェクトもまだまだあるんだなというのが正直な感想。
* 元々インフラチームが管理していた構成は移行作業にもインフラとの密な連携が必要となるのでやるなら早めにした方がよいよ。
* 調査・設計・実装・テスト・デプロイも全て担当したのでやった感あり。 
* これから移行して行こうと思っている方々の参考になるかな。
* 最後まで読んでいただきありがとうございます :smile: