Backbone.js + Rails + AWSでWebサービスを開発したお話(その2:AWS/ミドルウェア)

Pocket

弊社では、新サービス「otacco – おたっこ|オトナになったオタク女子のための情報サイト」を先日ローンチいたしました。

otacco

前回の記事では主に弊社のRailsでの開発手法に関してご紹介させて頂きましたが、今回はotaccoのインフラを構成しているAWSおよびミドルウェア周りの環境について解説させて頂きたいと思います。

AWS

まずは構成図をご覧ください。

otacco7

専用のVPCを構築し、Multi-AZ構成で冗長化、サブネットは「フロント層/アプリケーション層/データ層」に分割し、セキュリティグループもこの単位で分けています。

Webサーバーおよび全文検索用サーバーはELBでそれぞれ冗長化および負荷分散しています。踏み台(bastion)サーバー以外へのSSHアクセスは許可していません。

Webサービス用のAWS環境としては標準的な構成だと思います。

ここら辺の構成に関しては、AmazonのAWS構成事例のページや、下記のDevelopers.ioさんの記事等、色々な情報を参考にさせて頂きました。
【AWS】VPC環境構築ノウハウ社内資料 2014年4月版

サーバー監視は別会社様に外注させて頂いておりますが、VPCごとに監視元サーバーを用意するとコスト面で問題があるため、今後VPCの数が増えていっても監視元サーバーの数を増やさないですむように、VPC peering機能を活用した監視方式を採用しております。

EC2

EC2のAMIは標準のAmazon Linux AMIを使用しております。

特殊な設定は行っておりませんが、Capistranoでのデプロイ時に、AWS SDKを使用してデプロイ先のサーバーを環境に応じて動的に識別出来るように、Webサーバーには「Project: production」のようなタグを設定しています。

S3/CloudFront

本番環境用の静的アセットファイル、記事等の画像ファイル、ログファイル等は全てS3にアップロードしています。

アセットファイルや画像ファイルに関しては、デプロイ時や更新時に古いファイルは自動的に削除されますが、ログファイルはどんどん蓄積されていってしまうため、専用のRakeタスクを作成し、それをwheneverでcronに登録して、古いログを定期的に削除するようにしています。

RDS

弊社ではMySQL5.6を使用しています。

文字コードにutf8mb4を採用する場合は、新しいParameter Gropを作成し、下記のように設定を変更してRDSをReboot必要があります。

character_set_client = utf8mb4
character_set_connection = utf8mb4
character_set_results = utf8mb4
character_set_server = utf8mb4
character_set_database = utf8mb4
innodb_file_per_table = 1
innodb_file_format = barracuda
innodb_large_prefix = 1
skip-character-set-client-handshake = 1

セッションストレージおよびキャッシュとして使用しているInnoDB memcached plugin用のOption Groupsの設定に関しては下記の記事が参考になると思われます。
Amazon RDS MySQL 5.6と新機能を試してみた

Route53

「otacco.com」というzone apexをELBのエンドポイントにマッピングするために使用しています。詳しくは下記の記事等をご参照ください。
Zone apexとCNAME

ミドルウェア

nginx

nginxに関しては、要件ごとに下記のような設定を行っております。

常時SSL対応

otaccoでは全てのアクセスをhttpsで行う(常時SSL対応)というルールを採用しておりますため、下記のような設定を行い、httpでアクセスされた場合はhttpsにリダイレクトするようにしています。
(ELB経由の場合は、HTTP_X_FORWARDED_PROTOというヘッダで判定を行います)

if ($http_x_forwarded_proto != "https") {
  rewrite ^ https://$host$request_uri? permanent;
}
ヘルスチェック

ELBからのヘルスチェックは下記のような設定で対応しています。

server {
  listen 80;
  location = /healthcheck {
    empty_gif;
    access_log off;
    break;
  }
}
IPフィルタリング

特定のURLへのアクセスを特定のIPアドレス(オフィスの固定IP等)に限定するために、nginxのgeoモジュールを使用しています。

geo $geo {
    proxy VPCのネットワークアドレス;
    オフィスのIPアドレス OK;
    default NG;
}

server {
    ...
    location ~ ^/hoge/ {
      if ($geo = 'NG') {
        return 403;
      }
      ...
    }
    ...
}

unicorn

unicornの設定に関してはネット上に色々な情報が散見されますが、弊社では下記のような設定に落ち着きました。

# -*- coding: utf-8 -*-
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 1)
timeout 15
preload_app true

listen "/tmp/hoge.sock"
pid "/tmp/hoge.pid"

current_path = ENV["RAILS_ENV"] == "development" ? "/hoge" : "/hoge/current"
working_directory current_path

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = File.expand_path("Gemfile", current_path)
end

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_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

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

before_execBUNDLE_GEMFILE環境変数を設定する箇所は非常に重要で、これが記載されていないと、RailsのGemfileを更新した場合にunicornのreloadが失敗する場合がありますのでご注意ください。この問題に関しては下記の記事で解説されています。
capistrano + unicornではまった。

WEB_CONCURRENCY環境変数に関しては、弊社では/etc/environmentに設定する方式を採用しました。

# /etc/environment
WEB_CONCURRENCY=任意の値

fluentd

nginxとRailsのログを集約し、S3にアップしています。設定が重複しないようにfluent-plugin-forestプラグインを使用しています。

<match hogelog.**>
  type forest
  subtype copy
  remove_prefix hogelog
  <template>
    <store>
      type s3
      aws_key_id "hoge"
      aws_sec_key "secret"
      path ${tag}
      buffer_path /var/log/td-agent/buffer/${tag}.s3
      s3_bucket "hogelog"
      s3_region "ap-northeast-1"
      s3_object_key_format %{path}.%{time_slice}_%{index}.%{file_extension}
      time_slice_format %Y%m%d
      time_slice_wait 10m
    </store>
  </template>
</match>

NewRelic

パフォーマンス監視用にNewRelicを導入しています。ほぼ標準設定のまま使用しています。

ElasticSearch

全文検索用にElasticSearchを導入しています。AWS環境ではマルチキャストが使用できませんので、ノードのディスカバリ用に、下記のような設定を行う必要があります。
AWS Cloud Plugin for ElasticSearchを使う

RailsとElastiSearchの連携、特に「Modelに親子関係がある場合」の連携方法に関しては下記の記事がとても参考になりました。
既存のRailsアプリの検索にElasticSearchを導入してみる

Wercker

CIツールにはWerckerを採用しました。rubyおよびmysqlのdockerを使用する場合の設定ファイルは下記のようになります。ご参考までに。
(otaccoではDBのマイグレーションにridgepoleを使用しています。メッセージの通知先はChatworkです)

box: ruby
services:
  - id: mysql:5.6
    env:
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
      MYSQL_DATABASE: $MYSQL_DATABASE
      MYSQL_USER: $MYSQL_USER
      MYSQL_PASSWORD: $MYSQL_PASSWORD
build:
    steps:
        - bundle-install
        - rails-database-yml:
            service: mysql-docker
        - script:
            name: ridgepole
            code: (ridgepoleコマンド。省略)
        - script:
            name: rspec
            code: bundle exec rspec
    after-steps:
        - heathrow/chatwork-notify:
            (省略)
deploy:
    steps:
        - script:
            name: make .ssh directory
            code: mkdir -p "$HOME/.ssh"
        (以下、秘密鍵の生成用コード。省略)
        - script: 
            name: execute deploy
            code: (capistranoを起動するコード。省略)
    after-steps:
        - heathrow/chatwork-notify:
            (省略)

Capistrano

デプロイにはCapistranoを使用しています。かなり設定が多くて書ききれないので省略させて頂きますが、アセットのプリコンパイルに関しては下記の記事を参考にさせて頂きました。
Capistrano3 で asset_sync をローカル環境で実行してからデプロイする

次回は、シングルページアプリケーションを開発する際にハマりやすい、SEOとOGP関係の設定方法に関して紹介させて頂きたいと思います。


「世界NO1のotakuマーケティングカンパニー」を目指す株式会社アリスマティックでは、インフラ・サーバーサイド・ネイティブ・フロントエンドなど、各職種でエンジニアを絶賛募集中です!

採用ページ

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です