読者です 読者をやめる 読者になる 読者になる

#17 うわさのdatabase.ymlをどうしているか カヤックCW編

こんばんは。クライアントワーク(受託開発)チームのnobu_ohtaです。

この記事は tech.kayac.com Advent Calendar 2014 17日目です。

この記事では、弊社クライアントワーク(受託開発)チームで production 環境で Rails の database.ymlsecrets.yml をどう運用しているかを紹介したいと思います。

この話題最近ちょくちょく見かけますが、@mirakuiさんがやっているPodcastの Admins Bar #3: Fluentd, Rails, ActiveRecord でも取り上げられています。

なぜ機密情報をハードコードしないほうがいいか

Rails 4.1からsecrets.ymlやdatabase.ymlで機密情報は直書きせずに環境変数から読む設定ファイルが生成されるようになりました。

アプリのリポジトリには機密情報は直接コミットしないで別管理にしよう、という感じですね。APIのkeyだったり本番DBのパスワードなどを直接コミットしてると各開発者の開発環境に生でその情報がある状態で漏洩のリスクが上がります。また、社外のパートナーさんも開発している場合などは同じリポジトリで開発するけど、機密情報は見せられないみたいなケースもよくあるかと思います。

アプリ自体のリポジトリから機密情報を分離したとして、どこかにはその情報を書く必要があってじゃあどうやって管理するのがいいんだっていう話になるわけです。

環境変数から読んだり、Pitdotenvなどを利用したり、capistranoなどでdeployする際に機密情報が入ったファイルを作成してそれを配るなどいろいろな運用があると思います。

The Twelve-Factor App でも

The Twelve-Factor AppのConfigの章 でも以下のように設定はハードコーディングせずに環境変数に格納するのがよい、とされています。

Twelve-Factor Appは設定を 環境変数 に格納する。 環境変数は、コードを変更することなくデプロイごとに簡単に変更できる。設定ファイルとは異なり、誤ってリポジトリにチェックインされる可能性はほとんどない。また、独自形式の設定ファイルやJava System Propertiesなど他の設定の仕組みとは異なり、環境変数は言語やOSに依存しない標準である。

kayac cwチームではどうしてるの

弊社クライアントワークチームでは案件ごとにメンバーが異なり、自社サービスなどと比べると短いスパンでいくつもの案件に関わり、案件によっては他社のパートナーさんと協力して開発を行ったりしています。 なので、リポジトリごとの権限管理やリポジトリに含める・含めない内容などは非常に重要です。

Railsのdatabase.yml(の本番password)やsecrets.ymlに関して、現在は下記のような感じで運用しています。

  • database.yml や secrets.yml では ENV["HOGE"] と環境変数で読み込む形で記述してリポジトリにコミット
  • 実際の値自体は Chef で encrypted data bag を使って暗号化した状態で保持して、そこから /etc/environment を作る recipe を用意
  • bundle exec時に /etc/environment を読み込む wrapper を用意

encrypted data bag

弊社クライアントワークチームでは、サーバのprovisioningにChefを利用しています。

今回はこちらを利用して、共通鍵で暗号化したものを保存します。こうすることで、本番データベースのパスワードやなにかのAPIのkeyなどをハードコードすることなく保存できます。

Chefで公開したくないJSONデータを暗号化するためにDataBagsを利用してみた記録 でとてもわかりやすく説明されているので、ここでは簡単に手順だけ触れます。

$ bundle init
$ vi Gemfile
$ cat Gemfile
source "https://rubygems.org"

gem 'knife-solo'
gem 'knife-solo_data_bag'
$ bundle install --path=vendor/bundle
# 暗号化用の鍵を用意
$ openssl rand -base64 512 | tr -d '\r\n' > data_bag_secret

# data bag作成/編集/確認
$ bundle exec knife solo data bag create env data --secret-file ./data_bag_secret
$ bundle exec knife solo data bag edit env data --secret-file ./data_bag_secret
$ bundle exec knife solo data bag show env data --secret-file ./data_bag_secret

data bagの中身は、envをkeyにして以下のように作成したものとします。

{
  "id": "data",
  "env": {
    "DATABASE_PASSWORD": "**********",
    "SECRET_KEY_BASE": "xxxxxxxxxxx"
  }
}

これを利用して/etc/environmentを作成するrecipeは以下のような感じです。

env_data = Chef::EncryptedDataBagItem.load('env', 'data')

env_data['env'].each do |key, value|
  ruby_block key do
    var_line = %{export #{key}="#{value}"}

    block do
      file = Chef::Util::FileEdit.new('/etc/environment')
      file.search_file_replace_line(key, var_line)
      file.insert_line_if_no_match(key, var_line)
      file.write_file
    end

    not_if "grep '#{var_line}' /etc/environment"
  end
end

wrapper

以下のような wrapper をはさんで God などからunicornを起動するようにしています。

#!/bin/sh
CURRENT_DIR=$(dirname $0)
cd $CURRENT_DIR
export RAILS_ENV=production
source /etc/environment
source /etc/profile.d/rbenv.sh

exec "bundle" "exec" "--" "$@"

こうすることでdaemon化する際にも環境変数がちゃんと読み込まれるようにしています。

dotenv

現在は、1サーバ1アプリなケースがほとんどなのでサーバごとに共通で使えるように/etc/environmentに環境変数を設定するという方針で運用していますが、記事を書いているうちにdotenvを使って .env を生成する形で設定する環境変数がアプリローカルになるように運用するのもありな気がしてきました。

終わりに

どうやるかなーと検討していったんこのような形に落ち着いたのをAdvent Calendar駆動でまとめてみました。ご参考になれば幸いです。 また、各社どう運用しているかとても気になっております。

受託開発か自社アプリかや、案件の規模などによっていろいろな運用があると思うのですが、いろいろな運用が公開されてそれぞれにあった運用が見つかるといいですね。

明日は

Go版 ikachan, go-ikusanの作者の@mix3さんです。