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

くりにっき

ドリコムのプリキュアの人です

オレオレRailsアプリを支えるインフラの作り方

Rails AdventCalendar

はじめに

これは Ruby on Rails Advent Calendar 2014 - Qiita の19日目です

18日目

@ さんの Rails でシングルじゃないテーブル継承 - Qiita でした

19日目:オレオレRailsアプリを支えるインフラの作り方

最近では Heroku などのPaaS*1 も普及してインフラのことを知らなくても簡単にアプリを公開することができるようになりました。

しかしトラブルシューティングやパフォーマンスチューニングなどを行うにはアプリケーションコードだけで完結することは少なく、全体像を把握する必要があります。Railsアプリケーションの裏でどんな構成で動いているかを知っておくかは重要なのでざっくりと紹介したいと思います。

書かないこと

  • Railsアプリを作る上でのノウハウ
  • 便利なgemや外部サービスの紹介 *2
  • 監視
  • デプロイ

Railsアプリのインフラ構成

モダンなRailsアプリに必要なミドルウェア

私はRailsの会社に入って約2年半になりますが、大小合わせて10個以上のRailsアプリをリリースしてきました。*3 Railsアプリを作る時にはだいたい同じようなミドルウェアをインストールしてるので一覧で挙げてみます。

データベース

Percona Serverの説明を社内wikiより引用

Perconaは機能・性能は本家より劣る部分は無く、 マルチコアCPU・同時アクセスI/Oといったハードを考慮した設計や、 効率的なバックアップ、その他細かい改善が施された、 一歩進んだMySQLサーバといったところです。

リリースも本家に1~2ヶ月程度の遅れで追随し、 マニュアルも整備されており、ご丁寧に各種OS用のパッケージも用意されています。

近年のハード環境においてDB運用を少しでも効率よくしたいならば 使うしか無いシロモノであります。

アプリケーションサーバ

Railsは基本的に複数リクエストを同時にさばけないので、アプリケーションサーバ側でworkerを複数たてるのが定番です。*5

詳しい比較

Thinは遅い? - lab.ursm.jp

Webサーバ

HTTPのリクエストを直接さばくサーバ。

画像やjsのような静的ファイルもアプリケーションサーバで処理させるとパフォーマンスが落ちるため、アプリケーションサーバの手前に置いてリバースプロキシするのがRailsに限らず大体定番。

辺り

KVS (Key Value Store)

Q「KVSって何よ?」 A「Hashのすごい版」

辺り

弊社でよくある構成

全体像(最大構成)

冗長化を考えたらだいたいこんな構成になるはず

f:id:sue445:20141219140944p:plain

VIPは普段はMasterについていて故障時はHAが発動して自動的にSlaveがMasterに昇格してspareサーバが*7Slaveになります

f:id:sue445:20141219141038p:plain

VIPつけてるのはハードウェア障害時にアプリ側でIP直す必要をなくすため。(IP直書きしていたらDBとかの故障時にアプリも道連れになるので)

用語の説明

  • webサーバ
    • ユーザのリクエストを受けてrailsアプリに送るためのサーバ
    • nginxとunicornを別々のサーバに置く会社もあるけど、弊社だと分かれてるのは見たことない
  • adminサーバ
    • 管理画面用。webサーバの台数が多くなると管理画面の修正だけでwebサーバ全台デプロイするのは大変なのでadminサーバが作られることが多い
  • jobサーバ
    • cronとか非同期処理とかを処理するサーバ
    • Railsアプリが動いているという点ではwebサーバと同じなんだけど、ユーザからのリクエストを受けないのでnginxは不要
    • resqueやsidekiqがRailsアプリを動かすのでunicornも不要
  • dbサーバ
    • データベース
    • 弊社だとmaster 1 : slave 1の構成
    • 用途に応じて垂直・水平分割
  • kvsサーバ
  • cacheサーバ
  • VIP
    • Virtual IP Addressの略。複数のサーバを同一IPでアクセスすることができる。
    • 例えば2台あるうちのDBサーバが片方死んだらアプリは動かなくなるが、VIPをつけていれば生きているサーバだけにアクセスがいくので片肺運転で生き延びる。(その間にサーバ復旧)
  • Master / Slave
    • 参照時のサーバを分散させることによる負荷対策
    • Masterに書き込んだらSlaveにレプリケーションで同じデータを書き込み整合性を保つ
    • サーバの負荷によってはレプリケーションが遅延することもある

全体像(最小構成)

全部入りとかオールインワンとか言うこともある。冗長化考えなければこれでも十分

f:id:sue445:20141202014142p:plain

各種ミドルウェアのインストール手順

前述の最小構成(全部入り)で環境構築してみます。 chefで楽をしてもいいんだけど、基礎を学ぶために全部手動インストールしてみます。

検証環境:Debian Wheezy(CentOSは普段使ってないのでよく分かりません(´・ω・`))

最初にやること

$ sudo apt-get update

MySQL

インストール

$ apt-cache search mysql-server
mysql-server - MySQL データベースサーバ (最新版に依存するメタパッケージ)
mysql-server-5.5 - MySQL データベースサーババイナリおよびシステムデータベースの設定

どっちか入れればok

$ apt-cache show mysql-server | grep Version
Version: 5.5.40-0+wheezy1
Version: 5.5.38-0+wheezy1

$ apt-cache show mysql-server-5.5 | grep Version
Version: 5.5.40-0+wheezy1
Version: 5.5.38-0+wheezy1

と思ったけど、どっちも5.5系だw(5.6系はまだパッケージになってないだけ?)

おもむろにインストール

sudo apt-get install mysql-server

途中でrootのパスワード聞かれるので入れる

f:id:sue445:20141203005001p:plain

設定ファイルはこいつ

sueyoshi_go@sue445-advent-debian-01:/$ ls -l /etc/mysql/my.cnf
-rw-r--r-- 1 root root 3504 12月  3 00:51 /etc/mysql/my.cnf

デフォルトで使ってもいいんだけど必要に応じてこの辺をいじっておく

データの保存場所。ボリュームのでかいところに変えた方がいい

datadir         = /var/lib/mysql

スロークエリを出すかどうか。アンコメントしたら2秒以上かかったクエリがログに出る

# Here you can see queries with especially long duration
#log_slow_queries       = /var/log/mysql/mysql-slow.log
#long_query_time = 2
#log-queries-not-using-indexes

ついでなのでJenkinsでmysqlを設定する時の注意点

Jenkinsでのビルドの場合常にテストデータの作成と削除を繰り返すのでbinlogの保存期間(日数)は1日でいいと思います。(0だと無限に保存されてしまうので最小期間は1日)

expire_logs_days        = 10
max_binlog_size         = 100M

デーモンの起動方法

$ sudo /etc/init.d/mysql
Usage: /etc/init.d/mysql start|stop|restart|reload|force-reload|status

他のミドルにも言えますが /etc/init.d/ 辺りを探せば起動スクリプトが見つかります

設定を反映させるためにはrestartかreload

sudo /etc/init.d/mysql restart

nginx

$ apt-cache search nginx
nginx - 小さくて強力で拡張性のあるウェブ/プロキシサーバ

$ apt-cache show nginx | grep Version
Version: 1.2.1-2.2+wheezy3

$ sudo apt-get install nginx

設定ファイルをいじる

$ ls -l /etc/nginx/sites-available/
合計 4
-rw-r--r-- 1 root root 2765  9月 18 22:18 default

/etc/nginx/sites-available/default を適当な名前でコピーして使う。

$ sudo cp /etc/nginx/sites-available/{default,example}

$ sudo vi /etc/nginx/sites-available/example

最低限の設定ファイルはこんな感じ

upstream myapp.example.com {
   server 127.0.0.1:8080;
}

server {
    listen   80 default;
    server_name myapp.example.com;

    access_log  /var/log/nginx/myapp_access.log combined ;
    error_log   /var/log/nginx/myapp_error.log warn;

    location / {
        root   /var/www/myapp/current/public;

        if (-f $request_filename) {
            access_log off;
            rewrite_log off;
            expires 1h;
            break;
        }

        if (!-f $request_filename) {
            proxy_pass myapp.example.com;
            break;
        }
    }

    location ~ /\.git {
        deny  all;
    }
}

設定を反映させるためにはrestartかreload

sudo /etc/init.d/nginx restart

redis

インストール

$ apt-cache search redis-server
redis-server - ネットワークインターフェースを備えた永続的キーバリューデータベース

$ sudo apt-get install redis-server

設定ファイル

$ ls -l /etc/redis/redis.conf
-rw-r--r-- 1 root root 21162  6月  9  2012 /etc/redis/redis.conf

memcached

インストール

$ apt-cache search memcached

memcached - 高性能のメモリオブジェクトキャッシングシステム

$ sudo apt-get install memcached

ブコメレス

id:sonots

Master/Slaveに同じVIP貼ってるように見えるけど、ホントは別だよね?

id:ndication

vipってことはMyISAM?でも、レプリカの方向は一方向…まさか、mysql-proxy挟んで更新クエリ抱けマスタに飛ばしてる?!勉強不足でわからない

これは勘違いだったので図を直しました。(普段はMasterにVIPがついていて故障時はSlaveに移動。参照分散は行ってないです)

あと、InnoDB使ってます

id:sora_h

手で入れてんの…

サービスはChefで構築してますが、後半のインストールコマンドは「chefで楽をしてもいいんだけど、基礎を学ぶために全部手動インストールしてみます。」のため敢えて手動で入れてます。

まとめ

Heroku 使うのが一番楽

20日目

@ さんのrails new 手順書 - Qiita

*1:Platform as a service

*2:unicornはミドル的な立ち位置なので書きます

*3:自分の業務上社内ツール系を作ることが多いので超レアケースですw

*4:sqlite3で本番運用はないので除外。Oracle?知らない子ですね

*5:以前は config.threadsafe! なるものがあったけど、Rails 4.1で削除されている https://github.com/rails/rails/blob/v4.1.8/guides/source/4_1_release_notes.md#removals

*6:webrickで本番は(ry

*7:うちの場合は各サービスごとではなくデータセンターごとに数台スペアサーバがある