✔ Rails ☓ AWS 環境構築 タスクリスト φ(・・*)

Railsプロジェクトで環境構築をするときに何が必要だったか忘れちゃうので、メモをまとめてみました。
途中、まとめるのに力尽きたところや抜けてたところは、思い立ったら追記していこうかなと思いますが、まずはざっくり^^;

## □ itamae レシピ用意
▼itamaeレシピのファイル構造例

```
  ├── Gemfile           # `gem "itamae"`
  ├── Gemfile.lock
  ├── README.md
  ├── Vagrantfile
  ├── cookbooks         # ここにレシピを置く 命名、分け方自由
  │   ├── dir_set       # Rails pjt を置くディレクトリを用意 / 権限設定 など
  │   ├── etc_packages  # 基本的なpackage群をinstall
  │   ├── iptables
  │   ├── mysql
  │   ├── nginx
  │   ├── ntp
  │   ├── redis
  │   ├── security
  │   ├── time_set      # 日本時間にする設定
  │   ├── unicorn
  │   ├── yum
  │   └── zabbix
  ├── node
  │   └── rails        # ここで設定したものをレシピの中で`node[:hoge]`と呼び出して使う
  │       ├── batch_pro.yaml
  │       ├── batch_stg.yaml
  │       ├── development.yaml
  │       ├── production.yaml
  │       └── staging.yaml
  ├── project
  │   └── rails_pjt   # 決まりはないが、ローカルで扱いやすいようにここにRailsのコードを置くようにしている
  ├── rails.rb        # レシピ実行 をここにまとめて書く ex.`include_recipe "cookbooks/mysql/default.rb"`
  ├── tmp
  └── vendor
      └── bundler

```


▼レシピの実行 (Vagrantの仮想サーバーやAWSのインスタンスに向けて)

```
$ bundle exec itamae ssh --user <ユーザー名> --host <IP> -y node/rails/<反映したい環境>.yaml rails.rb
```

※itamaeレシピの書き方参考：

- [Chef脱落者が、Itamaeで快適インフラ生活する話 - Qiita](http://qiita.com/zaru/items/8ae6182e544aac6f6d79)
- [zaru/itamae-rails](https://github.com/zaru/itamae-rails)
- [Itamaeが構成管理を仕込みます！ ～新進気鋭の国産・構成管理ツール～：連載｜gihyo.jp](http://gihyo.jp/admin/serial/01/itamae)

## □ 環境変数設定ファイル用意

- 便利なgem をセット

```rb
# Gemfile
gem 'config'                              # 定数管理
gem 'dotenv-rails'                        # 環境変数管理
gem 'dotenv-deployment'                   # Production, Stating環境での環境変数設定
```

- `.env`に`export RAILS_ENV="development"`など定数を設定

- settings ファイル作成

```
$ bundle exec rails g config:install
```
`config/settings/development.yml`とかができるので、環境ごとの設定を書く
例えば、ymlに`site_url: https://hoge.com`と書くと
→ `Settings.site_url #=> "https://hoge.com"` と呼び出せる


## □ capistrano 設定 


```rb
# Gemfile
gem 'aws-sdk-v1'                          # AWS SDK for Ruby v1 ※前のpjtコードを参考にしたのでv2に寄せきれてなくていったん両方いれましたm(_ _)m
gem 'aws-sdk-core'                        # AWS SDK for Ruby v2

group :development, :test do
  gem 'capistrano-sidekiq', github: 'seuros/capistrano-sidekiq'
end

group :development do
  gem 'capistrano'
  gem 'capistrano-rails'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano3-unicorn'
end

group :production, :staging do
  gem 'unicorn'
end
```


```
$ bundle exec cap install
# => Capfile、config/deploy.rb 等が作成される
```

```rb
# Capfile

# Load DSL and set up stages
require 'capistrano/setup'

# Include default deployment tasks
require 'capistrano/deploy'
require 'capistrano/rbenv'

set :rbenv_type, :user
set :rbenv_ruby, '2.2.0'

require 'capistrano/bundler'
require 'capistrano/rails'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano/sidekiq'
require 'capistrano3/unicorn'
require 'whenever/capistrano'
require 'dotenv'
Dotenv.load

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
```

```rb
# config/deploy.rb

# config valid only for current version of Capistrano
lock '3.4.0'

set :application, 'app_name'
set :repo_url, 'git@github.com:hoge/app_name.git'
set :deploy_to, "/var/www/project/#{fetch :application}"
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" }
set :whenever_roles, ->{ :batch }

after 'deploy:publishing', 'deploy:restart'

namespace :deploy do
  # dotenvファイルはgit管理できないので、S3に置いてdeploy時に取得してそれを使うように
  # FileUpdate & ln -s :.env --------
  desc '.env file from s3'
  task :update_dotenv do
    on roles( %w(web batch)) do
      within release_path do
        with rails_env: fetch(:rails_env) do
          execute :bundle, :exec, :rake, "dotenv:update"
        end
      end
    end
  end

  desc 'link dotenv'
  task :link_dotenv do
    on roles( %w(web batch) ) do
      execute "ln -s /home/ec2-user/.env #{release_path}/.env"
      execute "source /home/ec2-user/.env"
    end
  end

  # Restart Unicorn
  desc 'Restart application'
  task :restart do
    # lib/capustrano/tasks/unicorn.rake 内処理を実行
    invoke 'unicorn:restart'
  end

  # FileUpdate & ln -s :.env
  after :updated,         'deploy:update_dotenv'
  after :update_dotenv,   'deploy:link_dotenv'
end
```

▼環境別のdeploy設定

```rb
# config/deploy/staging.rb
require 'aws-sdk-v1'
set :branch, "develop"
set :rails_env, "staging"
set :unicorn_config_path, "#{current_path}/config/unicorn.conf.rb"
set :default_env, {
  RAILS_ENV: "staging",
  FOG_DIRECTORY: "app_name-stg",
  FOG_REGION: ENV["FOG_REGION"],
}
set :pty, false 
set :sidekiq_role, :batch

AWS.config(access_key_id: ENV["AWS_ACCESS_KEY_ID"],
           secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"],
           region: ENV["FOG_REGION"],
           ec2_endpoint: "※ ec2_endpoint を記載",
           elb_endpoint: "※ elb_endpoint を記載")

elb = AWS::ELB.new
lb = elb.load_balancers["app_name-stg-elb"]
web_roles = %w{web app}
lb.instances.map(&:public_ip_address).each_with_index do |ip, i|
  web_roles << "db" if 0 == i
  server ip, user: 'ec2-user', roles: web_roles
end

ec2 = AWS::EC2.new.instances.with_tag "Name", "app_name-stg-batch"
server ec2.first.public_ip_address, user: 'ec2-user', roles: %w{batch cron}

set :ssh_options, {
  keys: [File.expand_path('~/.ssh/keyname.pem')],
  forward_agent: false,
  auth_methods: %w(publickey)
}

```

▼dotenvファイルはgit管理できないので、S3に置いてdeploy時に取得してそれを使うように

```rb
#lib/tasks/dotenv.rake
namespace :dotenv do
  desc "update dotenv file"
  task :update => :environment do
    S3_DOTENV_PATH = "dotenv"
    LOCAL_DOTENV_PATH = "#{ENV['HOME']}/.env"

    s3 = AWS::S3.new
    bucket = s3.buckets[ENV['FOG_DIRECTORY']]

    File.write LOCAL_DOTENV_PATH, bucket.objects[S3_DOTENV_PATH].read
    FileUtils.chmod 0600, LOCAL_DOTENV_PATH
  end
end
```

参考：[AWS RailsアプリケーションのCapistranoによるデプロイ - Qiita](http://qiita.com/shizuma/items/c23518a1934c738ec3b0)
## □ unicorn 設定

```rb
# config/unicorn.conf.rb

rails_env = ENV["RAILS_ENV"] || "production"
workers_procs = {production: 3, staging: 2}
workers_procs.default = 2
worker_processes workers_procs[rails_env.to_sym]

app_directory = "/var/www/project/app_name/current"
working_directory app_directory # available in 0.94.0+

listen "#{app_directory}/tmp/sockets/unicorn.sock", backlog: 128

timeout 30

pid "#{app_directory}/tmp/pids/unicorn.pid"

stderr_path "#{app_directory}/log/unicorn_#{rails_env}_error.log"
stdout_path "#{app_directory}/log/unicorn_#{rails_env}.log"

preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
  GC.copy_on_write_friendly = true

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "#{app_directory}/Gemfile"
end

before_fork do |server, worker|
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
  sleep 1
end

```

## □ AWS S3 用意

[Amazon S3 (スケーラブルなクラウドストレージサービス ) | AWS](https://aws.amazon.com/jp/s3/)

今回利用するbucketを環境ごとに作成

## □ CDN サービス設定 (アカマイなど)

[CDN サービス|クラウドコンピューティングサービス| Akamai](https://www.akamai.com/jp/ja/index.jsp)

## □ asset sync 設定

```rb
# Gemfile
gem 'asset_sync'
```

```rb
# config/initializers/asset_sync.rb
AssetSync.configure do |config|
  config.fog_provider = 'AWS'
  config.fog_directory = ENV['FOG_DIRECTORY']
  config.fog_region = ENV['FOG_REGION']
  config.aws_iam_roles = true
  config.manifest = true
end

```

```rb
# config/environments/production.rb
  config.action_controller.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/assets.example.com'  # CDN サービスを使ってりる場合そこで設定したhost
```

参考：[Railsアプリケーションを公開するならAssets on Cloudパターンを使おう - 働かないプログラマのメモ帳](http://d.hatena.ne.jp/lettas0726/20130320/1363773153)


## □ AWS EC2 インスタンス 用意 (上記 itamae で作成)

[Amazon EC2 (クラウドサーバー・ホスティング) | AWS](https://aws.amazon.com/jp/ec2/)

- □ web 用
- □ batch 用

参考：[AWS VPCによるネットワーク構築とEC2によるサーバー構築 - Qiita](http://qiita.com/shizuma/items/396ad15ff574717dfa1d)

## □ AWS ELB 作成 / インスタンス紐付け

[Elastic Load Balancing（クラウドネットワークのロードバランサー） | AWS](https://aws.amazon.com/jp/elasticloadbalancing/)

## □ AWS RDS 用意

[Amazon RDS（クラウドでのリレーショナルデータベースサービス） | AWS](https://aws.amazon.com/jp/rds/)

## □ サーバーに入ってitamaeで設定したものの反映を確認
- □ chkconfig でonになっているか
- □ BASIC認証の設定ができているか確認 (`$ htpasswd /etc/nginx/conf.d/.htpasswd`)
- □ DB 接続確認 / database設定 (mysql 接続確認)

etc.

## □ ドメイン/DNS設定 (AWS Route53)

[Amazon Route 53（ドメインネームサーバー、DNS サービス） | AWS](https://aws.amazon.com/jp/route53/)

ELBのaliasと紐付ける

## □ AWS SES 設定

[Amazon Simple Email Service（クラウドベースのメールサービス） | AWS](https://aws.amazon.com/jp/ses/)

```rb
# Gemfile
gem 'aws-ses', require: 'aws/ses'

# config/initializers/aws_ses.rb
AppName::Application.configure do
  config.action_mailer.smtp_settings = {
      :address => Settings.smtp.address,
      :port => 587,
      :user_name => ENV["SMTP_USERNAME"] || Settings.smtp.user_name,
      :password => ENV["SMTP_PASSWORD"]  || Settings.smtp.password,
      :authentication => :login
  }
end
```

## □ AWS Elasticcache 用意

[Amazon ElastiCache （キャッシュ管理・操作サービス） | AWS](https://aws.amazon.com/jp/elasticache/)

▼用意したら設定ファイルで読み込めるように

```rb
# dotenv ※.env
  export REDIS_HOST="AWS Elasticcacheで設定したhost"

# config/initializers/session_store.rb
  Rails.application.config.session_store :redis_store, servers: "redis://#{Settings.redis.host}:6379/0/session", key: '_r_cw_session', expire_after: 1.month
```


## □ Git管理サービス(GitHubなど)に デプロイキー 設定

`ssh-keygen -t rsa` で作成したpublic key を設定

## □ デプロイ実行

```
$ git push upstream deploy_brach
$ bundle exec cap staging deploy
```

## □ secret_key_base の 設定

```
$ bundle exec rake secret
```

で作成したkeyをdotenvに設定して`config/secret.yml`で呼べるように

## □ サーバー監視設定

[Zabbix](http://www.zabbix.com/jp/)など


## □ エラー通知設定

- エラー通知サービスsentry設定

[Sentry](https://getsentry.com/welcome/)：Track exceptions with modern error logging for JavaScript, Python, Ruby, Java, and Node.jp


```rb
# Gemfile
gem 'sentry-raven'

# config/initializers/sentry.rb
Raven.configure do |config|
  config.dsn = '※ sentry で設定したもの'
  config.environments = ['production']
end
```

- Chatに通知

  - slackやChatworkなどに重要なものは通知できるように
  - sidekiqなど利用 

## □ CI 対応

[CircleCI](https://circleci.com/) など