SPDYも廃止されたのでnginxでhttp2使いたいけど、そもそもhttp2ってなに?っていうお話

こんにちは、高橋です。最近はnginxとrailsとswiftをぐるぐるしています。

nginxは非常にパワフルなwebサーバーで、アクセス数が増大した時した時でも非常に安定してトラフィックを捌いてくれています。

Ruby on Railsを使用する場合には、unicornやpumaなどのアプリケーションサーバーと併用して使うことが多いかと思いますが、どうしてもアプリケーションサーバーの方がボトルネックになってしまいます。nginxの処理能力をどれだけ極限まで高めても、結局はアプリケーションサーバーの方で処理しきれずに503が返ってきてしまうので、どうやってアプリケーションサーバーの方を安定的に処理能力をあげるか最近は悩むところです。。

 

最近ではAWSのEC2などでautoscalingもよく使われているので、1台あたりのトラフィックの処理能力はそれほどカリカリにまで高める必要はないのかもしれません。

 

ということで前置きはこんな所で、今回は(僕が勝手に)社内にもhttp2を取り入れたいなーと思い、色々調べてサーバー上で試験的に試してみたので、まとめてみました。

 

nginxでHTTP/2を使用する

Googleが提唱していたSPDYを取り込んだ形でhttp/2が標準化される流れとなり、これから本格的に広まって行きそうですが、nginxではバージョン1.9.5からhttp/2をサポートするようになりました。

HTTP/2とは

http/2は、http/1.1でプロトコルレベルでの問題となっていたデータ送受信における効率の悪さを克服する形で、http/1.1ではクライアントとサーバーがシングルシーケンスでデータをやりとりを行なっていたのが、http/2ではmultipleにシーケンスのやりとりを行うことができるようになりました。これによってデータ転送と処理の効率が上がりwebページを表示するまでのスピードが上がるというわけです。

http/1.1の場合 http/1.1ではブラウザのTCP同時接続は最大4~6に制限されています。 HTTP Head of Line Blockingによりブラウザのネットワークはこんな感じに、ひとつのファイルの読み込みが終わるまでは、それ以降のファイルの読み込みはブロックされます。

f:id:cluex-developers:20160928165756p:plain

   

http/2を使用した場合 同時に複数のファイルの読み込みを行うようになります。http/2ではブラウザの最大同時リクエスト数は100以上が可能とされていています。

f:id:cluex-developers:20160928165916p:plain

 

webブラウザのHTTP/2対応状況

2016年9月現在のブラウザの対応状況はこんな感じです。ChromeFirefoxなどのメジャーなブラウザではほぼほぼ対応していますが、IEsafariなどでは一部対応といった感じのようです。

Can I use... Support tables for HTML5, CSS3, etc f:id:cluex-developers:20160928203910p:plain

   

HTTP/1.1からHTTP/2への主な変更点

  • 根本となる部分はhttp/1.1と同じ
  • http/1.1のセマンティックスは変更しない
  • サーバーへのTCP接続数を1つに限定
  • TLSの機構を使用してプロトコルを自動選択
  • バイナリープロトコル
  • 多重化送信

などといったことが挙げられます。

HTTP/2を使用すると現在のTLSは一部機能が使用できない

よりセキュアな通信を確立するために、http/2は現在のTLSの一部機能を制限することで、セキュアなプロトコルとして現在は使用されています。今後http/2に対応した形の新しいTLS、もしくはそれに替わるものが出てくるかと思います。

制限される内容はこちら

  • プロトコル選択にはNPNではなくALPNを使用
  • サーバー認証を共有できる接続は接続共有が可能
  • TLSプロトコルは1.2以上のみが使用可能
  • TLS Compressionの禁止
  • Renegotiationの禁止
  • DH鍵は2048bit以上を使用
  • AEAD(認証付き暗号)のうち、GCMとCCM以外の暗号方式は利用できない

 

 

NPN (Next Protocol Negotiation) からALPN (Applicaiton-Layer Protocol Negotiation) へ

http/2に先行して、NPN (Next Protocol Negotiation) はTLS false start方式の次の規格としてgoogleが提唱し、SPDYを支える次期アプリケーション層プロトコルネゴシエーションとして2010年ごろに登場しました。

背景的にはwebサイトの表示速度にはネットワークの回線速度よりもRTTの影響が大きく関わっており、TCP接続のハンドシェイクを毎回行うところに非効率さがあったと判断したのかと思います。

これに関しては2011年にAkamaiが行なった調査を簡単にまとめた2012年の記事があるので、下記の引用画像を見ていただくとwebサイトの表示速度がRTT依存というのがよりわかりやすいかと思います。

f:id:cluex-developers:20160928235245p:plain 引用元: Latency: The New Web Performance Bottleneck - igvita.com

   

これまではNPNが使用されていましたが、今後の流れとしてはhttp/2での後押しもありALPNが一般的となりそうです。

   

NPNとALPNの簡単な違いは、サーバーとクライアントのどちらが使用するアプリケーションプロトコルの決定を行うかというところで、NPNではクライアント側によるプロトコルの選択が行われていましたが、ALPNではサーバー側がクライアントの使用できるアプリケーションプロトコルの中から使用するプロトコルを選択するという形になります。

 

ALPNを使用するにはOpenSSLのバージョン1.0.2以上が必要となりますが、サーバーのOSによっては最新のOpenSSLを入れてもALPNを使用できないこともあります。

   

CentOSでHTTP/2とALPNを使う

CentOSでhttp/2とALPNが使用できるのはCentOS 7 以上のバージョンとなります。

   

UbuntuでALPNを使う

Ubuntuでhttp/2とALPNを使用する場合は、今年リリースされた Ubuntu 16.04 LTS (Xenial) 以上のバージョンになります。

   

nginxでのhttp2を設定

現在、nginxでhttp/2を使うには、ngx_http_v2_moduleが必要となります。

Module ngx_http_v2_module

再ビルドなどに関しては他にも記事があるので割愛しますが、 サーバーのOSとOpenSSLのバージョンがhttp/2に対応していれば、nginxで使用するのは非常に簡単かと思います。

   

最後の最後で、もはやおまけみたいな感じになってしまいますが、listenディレクティブにhttp2と入れるだけで、基本的には完了です。

server {
  listen 443 ssl http2;

  # ~~~~~
}

付け加えるとしたら、SSLプロトコルの設定や、ssl_ciphersなどの設定などでしょうか。その辺りに関しましては、こちらの記事を流し読み程度にご参考にしていただけると嬉しいです。

cluex-developers.hateblo.jp

     

googleはもちろんのこと、facebooktwitterといった名だたるwebサービスで既にproductionへ導入されているhttp/2で、 徐々に様々なwebサービスで導入され始めていますが、本格的に普及するまではもうしばらく時間がかかりそうな気もします。

また進展があったら、記事にしたいと思います!

Cluexではエンジニアの方を探しています!

cluex.co.jp

AWS Lambdaを使用し、CloudfrontのInvalidationを走らせる

こんにちは。エンジニアの志村です。
先日assets on S3についての実装を行いました。   その際の記事は下記になります。

cluex-developers.hateblo.jp

今回はasset_syncにより、S3にassetファイルがアップロードされた段階でCloudfrontのInvalidationをLambdaを使用して走らせるという処理を実装したいと思います!

Invalidationとは

Cloudfrontのキャッシュを明示的に消す機能です。

dev.classmethod.jp

クラスメソッドさんの上記の記事が非常に分かりやすいかと思います。

キャッシュを使用するのに重要なポイントは適切なキャッシュ期間を設けることです。それを行わないと古いファイルがいつまで経っても配信され続けてしまいます。
特にassets on S3を使用する場合には、明示的にファイルをInvalidationしないといつまで経っても最新のassetsファイルがユーザーに配信されなくなります。
このような状況を防ぐために、S3にファイルが転送された段階で、assetsのキャッシュを消去する必要があります。

Lambdaの実装

ではLambdaの実装を始めていきます。

AWSコンソールからLambdaを選択します

「Create Lambda Function」を選択します

f:id:cluex-developers:20160916104010p:plain

今回はBlueprintは使用しないので「Skip」を選択します

f:id:cluex-developers:20160916104022p:plain

S3を選択します。

f:id:cluex-developers:20160916104040p:plain

項目 説明
Bucket assets用のBucketを選択
Event Type S3にassetsファイルがCreateされた段階でInvalidationを走らせたいので「Object Created(All)」を選択
Enable Trigger チェックを入れると、設定したイベントが走るようになります(production等で設定する場合は注意!)

※Prefix, Suffixは必要であれば入力して下さい。

LambdaFunctionを作成します

f:id:cluex-developers:20160916104141p:plain

項目 説明
Name 適当な名前を入力
Description 分かりやすい説明を入力
Runtime 今回はPythonにします

コードは下記のコードが非常に分かりやすかったのでそれを使用します。

www.cloudberrylab.com

※DistributionIDは自身のCloudfrontディストリビューションのIDを入力して下さい

from __future__ import print_function

import boto3
import time

def lambda_handler(event, context):
    for items in event["Records"]:
        path = "/" + items["s3"]["object"]["key"]
    print(path)
    client = boto3.client('cloudfront')
    invalidation = client.create_invalidation(DistributionId='ディストリビューションID',
        InvalidationBatch={
            'Paths': {
                'Quantity': 1,
                'Items': [path]
        },
        'CallerReference': str(time.time())
    })

因みにですが、boto3はPython用のAWS SDKです。 Lambda FunctionをPythonで書く際には必ずと言って良いほど良く使用します。 ドキュメントも充実しているので非常に使い勝手が良いと思います。

Boto 3 Documentation — Boto 3 Docs 1.4.0 documentation

ロールやその他の設定をします

「Lambda function handler and role」のRole→「Create a custom role」を選択します。 f:id:cluex-developers:20160916104432p:plain

そうすると下記のような画面が現れるので、下記のCloudfrontのInvalidationを許可するロールを作成します。 f:id:cluex-developers:20160916104616p:plain

下記のようなポリシードキュメントになります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
        "Effect": "Allow",
        "Action": [
            "cloudfront:CreateInvalidation"
        ],
        "Resource": [
            "*"
        ]
    }
  ]
}

最後に「Create function」を押下すれば完成です!

使い方

asset_syncでデプロイすると勝手にInvalidationが走ってくれます。 下記の画面で確認が出来ます。

AWSコンソールからCloudfrontを選択します

該当するディストリビューションのIDを選択します

f:id:cluex-developers:20160916104912p:plain

「Invalidations」タブを選択すると、Invalidationsの状態が閲覧出来ます。ここでデプロイ直後にStatusがProgressとなっていればトリガーがきちんと走っています。

f:id:cluex-developers:20160916105140p:plain

以上になります。 キャッシュの扱いって本当難しいなーと感じますが、この様に自動化してしまえば特に意識することも少なくなって、よりCloudfrontが使いやすくなりますね。

Rackの基礎

こんにちは!! エンジニアとしてインターンしている村田です。

今回はズバリRackについて書いていきたいと思います。

Rackに関しては日本語の資料もあまり多くないため、特にRailsの初心者の方はよく分からないまま見過ごしていることも多いかもしれません。

私も初心者なので探すのに苦労しました。。

そこで自分もまだまだではありますが、これを読んで自分と同じような初心者の方々がRackのぼんやりとしたイメージをつかんでいただけるように書いていきたいと思います。

それでは早速ですがやっていきましょう!

Rackが誕生した背景

まず何故Rackが必要なのか? ここがわからないと、話が頭に入ってこないですよね。

これはPSGIというPythonのインターフェースの仕様に影響を受けました。

以前のPythonフレームワーク界は、様々フレームワークが開発されたものの、そのフレームワークが特定のWebサーバーに依存した状態でした。 ただし、これではサーバーの乗り換え時の負担が大きかったりして、開発者も頭を悩ませることが多かったようです。 そこで、ある特定のルールを定めて(インターフェース)、そのルールのもとフレームワークとWebサーバーの開発を進めることにしました。

これがPSGIの正体です。

RubyフレームワークRailsSinatraを筆頭に多様性に富みますし、WebサーバーもWebrickをはじめとして様々な独自のデプロイ方法を持っています。

すると、当然PSGIの代わりとなるシステムが必要になりまして、そうやって検討されていった結果現在のRackが誕生していくことになります。

Rackってなに??

さていよいよ本題にいきましょう。

Rackとは、rackupというコマンドを打つことで指定したファイルを参照しながらWebサーバーを立ち上げられるようにしているライブラリ(gem)です。 もう少し機能自体の説明をするならば、HTTPの送受信処理を担当するモジュールです。

指定したファイル(デフォルトではconfig.ru。ちなみにruはruckupの略。)には独自のRuby DSLで様々なミドルウェアやアプリケーションを参照するように書かれています。 これをもとにWebサーバーを立ち上げていくことになります。

ビジュアル的に説明をするとこんな感じになっています。

f:id:cluex-developers:20160916102713p:plain

このようにミドルウェアを介して、RailsなどのRackアプリケーションに対してWebサーバーから来たHTTPをパースしたりしています。

試しにbundle exec rake middlewareと打ってみましょう。 例えばこのような感じになって出てくると思います。

use Raven::Rack
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fd239f96d58>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use BetterErrors::Middleware
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CacheStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Remotipart::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Warden::Manager
use ExceptionNotification::Rack
use Bullet::Rack
use OmniAuth::Strategies::Facebook
run Rails::Application.routes

ここに書いてあるuseの対象がRackミドルウェアです。 そして最後に書いてあるrunの対象はRackアプリケーション(Rails)になります。 ちなみにuserunはRack DSLなのですが、今回は詳しく触れませんので興味がある方は調べてみてください。

ruckupの正体

こちらをご覧いただければお分かり頂けると思いますが、実はrackupコマンドがやっているのはRack::Server.startをしているだけなんですよね。

と言われてもRack::Serverとかよく知らないって方がこの記事を読んでくださっていると思いますので、説明します。

Railsで開発をしているとrails s、省略しないで書くと、rails serverというコマンドを打つことがあると思います。 このrails serverというコマンドも実は省略形でして、正確にはRails::Server.startというコマンドを打っています。

何か先ほどとても似ているコマンドを見ましたよね? そうRack::Server.startです。

もちろんこの二つは関係がないわけではなく、Rails::ServerはRack::Serverのサブクラスになっているのです。

継承する中で、ポートを3000番にするなど様々な違いを作っているようですが基本的な動きの部分は引き継いでいることがわかっていただけたと思います。

Rackミドルウェアの話

Rackミドルウェアはもちろん自分で追加することもできますが、そのためには簡単なルールを幾つか覚えなくてはなりません。

1つのHashオブジェクトを引数に取る「call」メソッドが実装されていること callメソッドは「ステータスコード」、「ヘッダーを表現したHash」、「eachに反応するオブジェクト」の3つの要素を持った配列を返すこと

上にある2つがそのルールになります。 また慣習的にcallメソッドの引数にはenvを用いることも覚えておくと得をするかもしれません。

詳しく話を始めるとDSLの説明やその他いろいろな話を書かないといけないので割愛しますが、Railsのようなアプリケーションも最後はcallで呼ばれるので

lamda do |env|
[200, {"Content-Type" => "text/html"}, ["Hello, world!"]]
end

簡単に言ってしまえばこのような形で動いていることになります。 これは先ほどの条件を満たしていますよね。

最後に

私の説明では物足りなかった部分も多かったと思います。 でもこれをきっかけにしてRackのイメージを掴めた方が一人でもいらっしゃればとても嬉しいです。

私もわからないことだらけなのでもっと勉強していこうと思いますが、最後にこの記事を作る上で参考にさせていただいた記事を載せておこうと思います。

RailsGuides

Rackとは何か

Rack解説

パーフェクトRubyonRails

それでは失礼いたします。

vertical-alignの使い方と別の実装方法について

こんにちは、エンジニアの井戸田です。

先日 vertical-align ではまり、検索してみると予測変換で vertical-aling 効かない と出てくるので、結構皆さんもはまっているんだなと感じました。 なので今回は僕もはまったcssのプロパティである、 vertical-align の値・使い方や、垂直方向の調整の別の実装方法を紹介したいと思います。

コードについて、htmlはslim、cssはscssを使用しているので注意してください。

vertical-alignの説明

vertical-align は行内のテキスト、画像の垂直方向の揃え位置を指定するプロパティです。 vertical-align は適用できるのが、 inline要素テーブルセル です。なので divタグpタグ などの ブロック要素 に対して vertical-align を指定してもうまくいきません。
divタグpタグvertical-align を指定したい時は diplay: inline や, display: table-cell , diplay: inline-block を指定し、要素自体を変更する必要があります。

説明
baseline 適当した要素のベースラインを親要素のベースラインに合わせる(初期値)
top 適用した要素の上端を、行ボックスの上端に合わせる
middle 適用した要素の中心を、親要素の中心に揃える
bottom 適用した要素の下端を、行ボックスの下端に合わせる
text-top 適用した要素の上端を、親要素の文字の上端に合わせる (テーブルセルへの指定はできない)
text-bottom 適用した要素の上端を、親要素の文字の下端に合わせる (テーブルセルへの指定はできない)
super 上付き文字 (テーブルセルへの指定はできない) 
sub 下付き文字 (テーブルセルへの指定はできない)
pxなどの単位 px, emなどの単位を使用しての指定(ベースラインが揃った状態を0、正の値は上方向、負の値はした方向になる)
  • 初期値が baseline なのですが、画像などは baseline を持たないため、下端が親要素のベースラインに揃えられます。
  • middleとは、フォントの場合 0.5ex 。小文字の「x」の半分高さです。

toptext-topbottomtext-bottombaselinetext-bottom の違いについて、勘違いしやすいので下記にまとめておきます。

top と text-topの違い

.verticalAlign.verticalAlign-top
  span.example
    | Example
    span.parent
      | parent
      span.child
        | top

.verticalAlign.verticalAlign-textTop
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-top
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;

  &-top {
    .child {
      vertical-align: top;
    }
  }

  &-textTop {
    .child {
      vertical-align: text-top;
    }
  }
}

.example {
  background-color: #fd7c79;
  font-size: 5rem;
}

.parent {
  background-color: #33cfff;
  font-size: 2.5rem;
}

.child {
  background-color: #24ff18;
  font-size: 1rem;
}

f:id:cluex-developers:20160909205157p:plain

上が top で 下が text-top になります。
text-top は親要素の文字の上端(一番高い場所)を基準とします。なので上端を基準として配置することが可能です。
top は適用した要素の上端を、行ボックスの上端に合わせるため上記のような表示になります。

bottom と text-bottomの違い

.verticalAlign.verticalAlign-bottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | bottom

.verticalAlign.verticalAlign-textBottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-bottom
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;

  &-bottom {
    .child {
      vertical-align: bottom;
    }
  }

  &-textTop {
    .child {
      vertical-align: text-bottom;
    }
  }
}

// 省略 以下はtopとtext-topと指定の仕方は同じです。

f:id:cluex-developers:20160909205407p:plain

上が bottom で下が text-bottom になります。
text-bottom は親要素の文字の下端(ディセンター文字の下端)を基準として配置することが可能です。
また bottom は適用した要素の下端を、行ボックスの下端に合わせるため上記のような表示になります。

baseline と text-bottom

次は baselinebottom-text に対してどのような違いがあるのか、説明します。

.verticalAlign.verticalAlign-baseline
  span.example
    | Example
    span.parent
      | parent
      span.child
        | baseline

.verticalAlign.verticalAlign-textBottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-bottom
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;

  &-baseline {
    .child {
      vertical-align: baseline;
    }
  }

  &-textBottom {
    .child {
      vertical-align: text-bottom;
    }
  }
}

// 省略 以下はtopとtext-topと指定の仕方は同じです。

f:id:cluex-developers:20160909205017p:plain

上記を見てみるとどのような違いがあるのか迷うと思うのですが、親要素( .parent )を対して line-height を入れてみると違いが良く分かります。

.verticalAlign.verticalAlign-baseline
  span.example
    | Example
    span.parent
      | parent
      span.child
        | baseline

.verticalAlign.verticalAlign-textBottom
  span.example
    | Example
    span.parent
      | parent
      span.child
        | text-bottom
.verticalAlign {
  margin: 0 0 8px 0;
  background-color: #f93;
  
  &-baseline {
    .child {
      vertical-align: baseline;
    }
  }
  
  &-textBottom {
    .child {
      vertical-align: text-bottom;
    }
  }
}

.example {
  background-color: #fd7c79;
  font-size: 5rem;
}

.parent {
  background-color: #33cfff;
  font-size: 2.5rem;
  line-height: 2; // ← 追加
}

.child {
  background-color: #24ff18;
  font-size: 1rem;
}

f:id:cluex-developers:20160909205503p:plain

.parent に対して line-height: 2; を指定すると、 baseline は親のフォントのベースライン(基準線)に合わせるので、ズレは生じません。ただ text-bottom は親の要素の文字の下端に合わせるので、ズレが生じてしまいます。
上記の例の様に親の要素で line-height (や height などの縦幅調整をするプロパティ)を指定している時は注意が必要です。

(実際に実装する時には 初期値が baseline なので、 vertical-aling: baseline; の指定は必要ありません。)

ブロック要素への対応

テーブルセルとdisplay: table-cellの使い方

テーブルセルとは tableタグ で使用する tdタグ , thタグ のことです。こちらの要素に対しても vertical-align を使用すことができます。但し text-top , text-bottom , super , sub は指定することができません。 display: table-cell での実装方法は下記の様になります。下記の例では .block を垂直方向に中央揃えにしています。

.tableCell
  .block
    | block
.tableCell {
  display: table-cell;
  height: 100px;
  background-color: #f93;
  vertical-align: middle;
}

f:id:cluex-developers:20160909205532p:plain

block要素 に対して vertical-align を使用する場合には、垂直方向の調整をしたいタグの親要素に対して display: table-cell , vertical-align: ◯◯ を指定することで、使用することができます。 ただ表に関係ない要素に display: table-cell を指定するのはバットな気がしますね。

display: inline-block

display: table-cell を使用しないで display: inline-block を使用する方法もあります。inline-block要素 とは inline要素block要素 を兼ね備えた要素です。下記の例では2つの inline-block要素 を垂直方向に中央揃えにしています。

.block
  .inlineBlock1
    | inlineBlock1
  .inlineBlock2
    | inlineBlock2
.inlineBlock1 {
  display: inline-block;
  height: 8rem;
  background-color: #fd7c79;
  font-size: 2rem;
  vertical-align: middle;
}

.inlineBlock2 {
  display: inline-block;
  background-color: #33cfff;
  font-size: 2rem;
  vertical-align: middle;
}

f:id:cluex-developers:20160909205049p:plain

block要素 に対して display: inline-blockvertical-align: ◯◯ を指定することで、使用することができます。 inline-block要素inline要素 で異なるのが、 inline-block要素 の場合は横幅・縦幅が指定が可能なので、その分 inline要素 より使い易いかも知れません。

block inline inline-block の違いについては、また別の機会に詳しく話したいと思います!

vertical-alingを使用しないで垂直方向を調整する方法として

  • line-height
    line-height を使用して、親要素のheightに調整する。なんとなくバットな調整方法だと思います・・・。
  • position
    親のblock要素に対して position: relative 、子要素に対して postion: absolute を指定する。
    ただ垂直方向の中央揃えにしたいと思っていても、 postion:absolute は指定した要素の本来の位置からの相対位置なので top: 50% という指定をしていても、その要素の上端が真ん中にきてしまうので、 top: 50% ではなく調整する必要があります。

上記2つを挙げましたが、それぞれ調整が難しいと思います。そこで僕個人がおすすめしたいのが FlexBox を使用する方法です。

FlexBoxを使用し垂直方向を調整する

FlexBox とは「Flexible Box Layout Module(フレキシブルボックスレイアウトモジュール)」のことで、要素をFlexコンテナというボックス内にフレキシブルで簡単に整列する方法です。

FlexBoxに対応しているブラウザは下記のようになります。

f:id:cluex-developers:20160909210303p:plain

参考) Can I use... Support tables for HTML5, CSS3, etc

  • IE11から正式対応。
  • safariには -webkit- のベンダープレフィックス必要のようです。

垂直方向の調整をするために align-items 又は align-self というプロパティを使用します。

align-items と align-self

  • align-items
    flexコンテナに対して指定することで、コンテナ内のすべてflexアイテム(子要素)に対して、同じ位置に調整します。
  • align-self
    flexアイテムに対して指定することで、個別に位置を調整します。
説明
baseline flexアイテム内の文字のベースラインを揃える。 最初のflexアイテムのbaselineに揃うようです。
flex-start flexアイテムの上端を揃える( flex-directioncolumncolumn-reverse の場合は右側を揃える)
flex-end flexアイテムの下端を揃える( flex-directioncolumncolumn-reverse の場合は右側を揃える)
center 適用した要素の中心を、親要素の中心に揃える( flex-directioncolumncolumn-reverse の場合は横幅の中央を揃える)
stretch レックスアイテムの高さを揃える( flex-directioncolumncolumn-reverse の場合は横幅を揃える)

下記の例では、 align-items: center を使用して実装してます。

.flexBox
  .flexItem1
    | flexItem1
  .flexItem2
    | flexItem2
  span.flexItem3
    | flexItem3
.flexBox {
  display: flex;
  align-items: center;
}

.flexItem1 {
  background-color: #fd7c79;
  height: 200px;
}

.flexItem2 {
  background-color: #33cfff;
  height: 80px;
}

.flexItem3 {
  background-color: #24ff18;
  height: 160px;
}

f:id:cluex-developers:20160909205040p:plain

.flexBox に対して display: flex , align-items: center を指定することで、flexコンテナ内の全てのflexアイテムが垂直方向に中央揃えになります。 このように簡単に垂直方向を調整することができるので、ブラウザの対応に気をつけなければいけませんが、 FlexBox はとても便利です。

もし vertical-align での調整が難しい場合には、 FlexBoxの導入をぜひ検討してみてください。弊社が運用しているmamanokoでも flex を多用してます。

余談

株式会社Cluexではエンジニアの方を探しています!

cluex.co.jp

GeocoderとGeokitを使用して、現在地周辺検索を行う機能を実装してみた

こんにちは、エンジニアの神山です。最近大豆製品ばっかり食べています。

今回はWebサービスでよく見かける、現在地周辺検索を行う機能の実装について書きました。

例えば現在地から半径2km内にあるレストランを検索したり、現在地より最寄りの駅を探したりできる機能です。

今回使用したGemは、 geocodergeokit-rails です。

まずこの2つのGemにフォーカスして話を進めていきます。

GeocoderとGeokit

これらのGemは住所から緯度経度を割り出したり、緯度経度を用いた検索ロジックを提供しております。

具体的には以下の様な事ができます。

  • 住所から緯度経度を割り出す
  • 緯度経度から住所を割り出す
  • IPアドレスから緯度経度を割り出す
  • 指定したオブジェクトまたは緯度経度を中心に、指定した距離内や範囲(ex: 2km~5km)にある施設を検索する
  • 2点間の距離を算出する
  • あるオブジェクトに対して指定したオブジェクトが位置する方角を示す
  • 指定したオブジェクトまたは緯度経度を中心に、長方形状に指定した範囲内ある検索する

住所から緯度経度を割り出すとは、「東京都中央区◯◯」という文字列を判別し、北緯35度、東経139度というように緯度経度に直すことです。

GeocoderとGeokitの比較

Geocoderの利点

Geocoderを使うメリットは、住所から緯度経度を割り出す機能が優秀という点です。

GeocoderはGeokitより多くのAPIを網羅しております。

GeokitはGoogle・Yahoo・Bing・FCC・MapQuest・Mapbox・OpenCageの7つに対して、 Geocoderは上のFCCを除いたものに加えて、Google Maps API for Work・Google Maps API for Work・Nominatim・Yandex・Geocoder.ca・Geocoder.us・Ovi/Nokia・Here/NokiaESRI・Mapzen・Pelias・Data Science Toolkit・Geocodio・SmartyStreets・OKF Geocoder・Geoportail.lu・PostcodeAnywhere Uk・LatLon.ioの25個網羅しています。

使用できるAPIの数が多いほうが、最適なものを選べるので使い勝手がいいですね。

Geokitの利点

一方、Geokitを使うメリットは、オブジェクトを絞るメソッドを使用する際メソッドチェーンが使えるという点です。

Geocoderだと可能な場合と不可能な場合があるようです。

例えば、Geokitのメソッドであるwithinを使うとき、

prefecure_id = 1
shop_type = restaurant
latitude = 33.33
longitude = 140.44

Shop.within(2, [latitude, longitude]).tap do |scoped|
  scoped.where!(prefecure_id: prefecure_id) if prefecure_id
  scoped.where!(shop_type: shop_type) if shop_type
end

のようなことが出来ます。 例えば絞込を行う等、メソッドチェーンを使用したい場面を想定するとGeokitのほうが使い勝手が良いですね。

上記の理由により今回は、

  • Geocoder: 緯度経度の割り出しを行う
  • Geokit: 現在地周辺検索を行う

の様な使い分けをしました。

ちなみにgeocodergeokitの人気を比較しているサイトがあったので載せておきます。

ruby.libhunt.com

Geocoderの使い方

住所から緯度経度を割り出すことを geocoding といいます。 geocoding を行いたいカラムがあるモデルに以下のように記述します。

geocoded_by :address
after_validation :geocode, if: :address_changed?

geocoded_bygeocoding を行いたいカラムを指定します。 addressというカラムに住所(ex: 東京都港区〇〇)が入っており、その住所を基に検索を行いたいような場面です。

after_validationgeocoding するタイミングを指定します。 上のように書くと、addressカラムに変更があった際に、自動でgeocodingされるようになります。

Geokitの使い方

緯度経度を使って出来ることを、GitHubより抜粋しました。

# 指定した緯度経度から半径5km内のオブジェクトを検索
Shop.within(5, origin: [33.33, 140.33]) 

# 指定した緯度経度から半径5km外のオブジェクトを検索
Shop.beyond(5, origin: [33.33, 140.33])  

# 指定した緯度経度から2km~5km内のオブジェクトを検索
Shop.in_range(2..5, origin: [33.33, 140.33]) 

# 緯度が30.33~35.55内でかつ、経度が135.55度~145.55度内にあるオブジェクトを検索
Shop.in_bound([[30.33, 135.55], [35.55, 145.55]]) 

# 指定した緯度経度から一番近いオブジェクトを検索
Shop.closest(origin: [33.33, 140.33])

様々な検索方法がありますね。 因みにGeocoderでも同じようなこと検索は可能です。

現在地の検索機能

では、現在地より半径2km以内にある店を検索する機能を実装していきます。 「現在地から探す」ボタンを押した時に、上記の条件で検索が行われるような実装です。

「現在地から探す」ボタン押下後の挙動としては、 Javascriptを用いて現在地の緯度経度情報を取得→Geokitのメソッドを用いて緯度経度付近にある施設を検索して、Viewに表示させる という流れになります。

Model

Shopモデルのaddressカラムをgeocodingします。

  • models/shop.rb
geocoded_by :address
after_validation :geocode, if: :address_changed?

address_changed?というメソッドはActiveRecord::Dirtyの機能です。{column_name}_changed?で該当するカラムが変更された時にtrue`を返します。 詳しくは下記を御覧ください。

qiita.com

Controller

Geokitのwithinメソッドを用いることにより、現在地から2km以内の店が@placesに渡されます。

  • shops_controller
def search
  latitude = params[:latitude]
  longitude = params[:longitude]

  @places = Shop.all.within(2, origin: [latitude, longitude])
end

View

  • shops/index
    「現在地から探す」ボタン部分になります。
.currentLocation
  = button_tag ‘現在地から探す’, type: ‘button’

Javascript

現在地の取得には、HTML5Geolocation APIを使用します。 以下の様なコードになります。

class @CurrentLocations
  @getCurrentLocation: ->
    $('.currentLocation').on 'click', ->
      if navigator.geolocation
        navigator.geolocation.getCurrentPosition(successGetPosition, failGetPosition, options)
      else
        message = 'ご使用中のブラウザは現在地検索に対応されておりません。'
        Alert.set('warning', message)

  successGetPosition = (position) ->
    window.location.href = "/shops/searches?latitude=#{position.coords.latitude}&longitude=#{position.coords.longitude}"

  options =
    enableHighAccuracy: true

  failGetPosition = (error) ->
    switch error.code
      when 1
        message = '位置情報の提供を許可してください。'
      when 2
        message = '位置情報の取得に失敗しました。'
    Alert.set('warning', message)

では上から見ていきます。

  @getCurrentLocation: ->
    $('.currentLocation').on 'click', ->
      if navigator.geolocation
        navigator.geolocation.getCurrentPosition(successGetPosition, failGetPosition, options)
      else
        message = 'ご使用中のブラウザは現在地検索に対応されておりません。'
        Alert.set('warning', message)

@getCurrentLocation が現在地を取得する関数です。

navigator はブラウザの情報を取得するオブジェクトです。 そして.geolocationでブラウザが Geolocation API に対応しているか調べています。 対応していないブラウザもあるので注意して下さい。(IE8.0など)

ブラウザが Geolocation API に対応している時、 geoCurrentPosition で現在地の取得を試みます。 geoCurrentPosition の第一引数は現在地の取得に成功した時に実行され、第二引数は失敗した時に実行されます。 第三引数は現在地の取得の際のオプション設定をしております。

ちなみにブラウザが Geolocation API に対応していてもユーザーが位置情報の提供を許可していない状況も考えられます。 そのような場合、 geoCurrentPosition が呼ばれると自動で許可するか否かのポップアップが出るようになっております。これまた便利ですね。

ブラウザが Geolocation API に未対応である場合は、 else の部分が実行されます。 ここでは「対応していません」というアラートを出すようにしています。

  successGetPosition = (position) ->
    window.location.href = "/places/searches?latitude=#{position.coords.latitude}&longitude=#{position.coords.longitude}"

successGetPosition は現在地の取得に成功した時の関数です。 positionオブジェクトに現在地の情報が入っており、position.coordsで様々な情報を取り出せます。

例えば、 position.coords.latitude で緯度を取得できたり、 position.coords.logitude で経度を取得できます。

そして現在地の取得に成功した時に、現在地周辺の店の一覧ページに移動します。 またControllerに緯度経度の値を渡しております。

  options =
    enableHighAccuracy: true

options はその名のとおり、現在地取得の際のオプション設定です。 enableHighAccuracyGPSなどを使った高精度な現在地の取得を可能にします。 他にもタイムアウト設定やキャッシュの設定なども可能です。

  failGetPosition = (error) ->
    switch error.code
      when 1
        message = '位置情報の提供を許可してください。'
      when 2
        message = '位置情報の取得に失敗しました。'
    Alert.set('warning', message)

failGetPosition は現在地の取得に失敗した時に実行される関数です。 error というオブジェクトに失敗の原因が入っております。 今回は、エラーの原因ごとに別々のアラートを出したいので、 error.code ごとに条件分岐をしております。

error.code = 1 はユーザーが位置情報の許可をしていない時を指しております。
error.code = 2 は現在地の取得に失敗した時を指しております。

Geolocation API の詳しい情報は以下のリンク先にも書かれているので参考にしてみてください。

HTML5 Geolocation

以上が現在地周辺の施設を検索する機能の解説でした。 非常に便利なGemですね。

改良点等アドバイスいただけたら大変幸いです。

参考

今のうちに見直しておきたいnginx設定集 【セキュリティ編】

こんにちは、Cluexの高橋です。

先日EC2サーバーのOSを新しいものにしまして、その時にサーバーのテストを簡単に行ってくれるQualys SSL Labsで移行前のサーバーをテストしてみたところ、C判定が出てしまったので、nginxの設定も全体的に見直してみました。

     

イメージはこんな感じです f:id:cluex-developers:20160813190017p:plain    

テストしたのはこちら「Qualys SSL Labs」 www.ssllabs.com    

ということで今回はnginxの設定に関してセキュリティ関連でやっておくべき設定集をまとめました。      

nginxのバージョン情報を隠す

nginxのバーション情報を隠蔽します。

server {
  # ~~
  server_tokens off;
  # ~~
}

   

SSL通信に使用するプロトコルを指定する

SSLv2やSSLv3はダウングレード攻撃やPOODLEなどへの脆弱性があるのでTLSのみ許可します。

server {
  # ~~
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  # ~~
}

   

暗号化スイートの設定

SSLの暗号化にする暗号化スイートを明示的に指定し、クライアントサイドで指定した暗号化をしないようにすることで、通信をよりセキュアにすることができます。 ちなみにssl_ciphersの設定方法としては、「:」をデリミタとして使用し、「!」をプレフィックスでつけることで除外対象となります。  

詳細な暗号化スイートの設定内容はこちらのサイトを参考にさせていただきました。

Strong SSL Security on nginx - Raymii.org    

おすすめの設定がこちら。

server {
  # ~~
  ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
  # ~~
}

 

IE6/WinXP などにも対応したいと言う方にはこちらの設定がおすすめとのことですが、SSL3.0などを使用できない設定にしているので今回は特にIE6を気にする必要はないかと思います。

server {
  # ~~

  ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

  # ~~
}

   

サーバーの暗号化設定の優先

先ほどのサーバー側で指定した暗号化スイートを優先して使用するようにします。

server {
  # ~~
  ssl_prefer_server_ciphers on;
  # ~~
}

   

Http Strinct Transport Security(HSTS)ヘッダの追加

サーバーからStrict Transport Securityヘッダを返すことで常にHTTPSで通信を行うようにし、中間者攻撃を防ぎます。これを使用するには Nginxのngx_http_headers_moduleが必要になります。

add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains;';

developer.mozilla.org

   

ちなみにpreloadオプションを使用したい場合は、こちらでドメインを登録しておくと宜しいかと思います。

HSTS Preload List Submission

こちらに登録するには下記の要件を満たす必要があります。

Submission Requirements

If a site sends the preload directive in an HSTS header, it is considered be requesting inclusion in the preload list and may be submitted via the form on this site.

In order to be accepted to the HSTS preload list through this form, your site must satisfy the following set of requirements:

  1. Serve a valid certificate.

  2. Redirect from HTTP to HTTPS on the same host.

  3. Serve all subdomains over HTTPS.

 ・In particular, you must support HTTPS for the www subdomain if a DNS record for that subdomain exists.

  1. Serve an HSTS header on the base domain for HTTPS requests:

 ・The max-age must be at least eighteen weeks (10886400 seconds).

 ・The includeSubDomains directive must be specified.

 ・The preload directive must be specified.

If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS header (rather than the page it redirects to).      

preloadを指定する場合はこんな感じです。

add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains;preload';

       

DH鍵交換時のKeyの強化

TLSプロトコルで採用されているDiffie-Hellman鍵交換は512bitもしくは1024bit以下のDHパラメータにおいてLogjam攻撃を受ける可能性があり、これを防ぐにはDHパラメータに2048bitのものを使用することが望ましいとされています。

   

Diffie-Hellman鍵交換による暗号化時にnginxのデフォルトでは1024ビットの鍵を使用しますが、Google ChromeMozilla Firefoxなどの主要なブラウザでは「最低でも」1024ビット以上のものを使用するよう推奨しており、できれば2048bitのものを独自生成して使用するのが好ましいかと思います。

  一般的なwebサイトでは2048bitもしくは4096bitでpemを作っておくといいようです。

   

まずはpemファイルの作成

$ cd /etc/ssl/certs
$ openssl dhparam -out dhparam.pem 4096

こちらがnginxのconfファイルに追加する内容。

server {
  # ~~
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  # ~~
}

     

OCSPステープリング(OCSP Stapling)の設定

OCSP(Online Certificate Status Protocol)はSSL/TLS通信の時に証明書が失効していないかを確認するために使用されています。 通常はSSL通信を行う際はクライアント側がこのOSCPを使用してサーバー証明書の正当性を確認する必要がありますが、サーバー側で事前にこの処理を行うことでクライアント側で行う必要がなくなるのでHTTPS通信の開始を高速化できます。

 

具体的にはOCSPステープリングによってクライアント側ではなくサーバー側でOCSPによるサーバー署名書の確認を行い、その結果をクライアントへのレスポンスと一緒に送るようにすることで、これを実現しています。

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/cert/trustchain.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s

ssl_staplingを使用するにはDNSリゾルバを resolverディレクティブにて指定する必要があります。googleのパブリックDNSサーバーが使用されることが多いようです。    

X-Frame-Options

レスポンスヘッダにX-Frame-Optionsを設定することでブラウザがiframeなどの内部に自身のサイトを表示を許可するかを設定できます。DENYもしくはSAMEORIGINを指定することで自分のサイトのコンテンツが他のサイトに埋め込まれないように設定でき、クリックジャッキング攻撃を防ぐことができます。

add_header X-Frame-Options SAMEORIGIN;

 

X-XSS-Protection

XSSに対するフィルタ機能を有効化

add_header X-XSS-Protection "1; mode=block";

 

X-Content-Type-Options

Content-Typeに一致しない処理は行わないようにさせることでXSSなどを防ぎます。

add_header X-Content-Type-Options nosniff;

これが設定されていない場合、例えば Content-type="text/plain" のファイルでも、その中にjavascriptのソースがあるとブラウザによって実行される可能性があります。

     

再度テストした結果は・・・

f:id:cluex-developers:20160813185943p:plain    

当時の設定では上記の幾つかはまだ設定していないものもあるのでA+の評価をとることができなかったので、今回あげたものも含めてさらにサーバー周りを強化していきたいなと思います。

     

ちなみにMozilla Foundationが公開しているwebサーバーのConfig Generatorがあるので、そちらで基本的な設定を確認できるので、気になる方はお使いになってみるいいかと思います。

Generate Mozilla Security Recommended Web Server Configuration Files

       

余談)Cluexではエンジニアの方を探しています!

 

See more at cluex.co.jp

assets on S3の導入

こんにちは。エンジニアの志村です。 最近暑くて参りますね…。アイスばっかり食べてます。

さて今回ですが、Assets on S3を導入しましたのでその際のメモです。
結構この形でassetsを配信しているサービスは多いですよね。
今回は、Cloudfront+S3 / asset_sync+capistranoという定番のパターンで実装しております。

assets on S3とは

デプロイ時にassetsファイルをS3に配置し、CDN経由で配信する方法です。
通常であればnginxやApache等のWebサーバーを介して静的ファイルは配信されています。
assets on S3はassetsファイルをS3に配置し、CloudfrontやAkamai等のCDN経由で配信します。

Webサーバー負担軽減やCDNを噛ませることによる高速化を目的として用いられることが多いかと思います。

仕組み

下記のような流れになります。

  1. デプロイ時にrake assets:precompileを走らせる。その際にasset_syncを使用し、S3にassetsファイルをアップロードする。
  2. manifestファイルをEC2にアップロードする。
  3. rake assets:precompile時に作成されるassetファイルを削除する(public/assetsに格納されている)

こんな感じでしょうか。 他にもCloudfrontやS3の設定があるので、下記に記していきます。

Rails側の設定

ではまずasset_syncを導入し、bundle installをします。

asset_syncの導入

gem 'asset_sync'

次にasset_syncのconfigファイルを生成します。

$ rails g asset_sync:install --provider=AWS

今回はAWSを使用するのでproviderオプションにはAWSを設定します。 そうするとasset_sync.rbという設定ファイルが生成されます。

  • config/initializers/asset_sync.rb
AssetSync.configure do |config|
  # fogを使用するサービス
  config.fog_provider = 'AWS'
  # S3の存在するリージョン
  config.fog_region = 'ap-northeast-1'
  # S3のバケット名
  config.fog_directory = 'assets'
  # IAMのアクセスキー
  config.aws_access_key_id = 'xxxx'
  # IAMのシークレットアクセスキー
  config.aws_secret_access_key = 'xxxx'
  # 既にS3上に存在しているファイルの扱い keep, delete, ignoreから選択
  config.existing_remote_files = 'keep'
  # gzip圧縮をするかどうか
  config.gzip_compression = true
end if defined?(AssetSync)

上記で使用してるIAMに関しては、次の章のS3の部分で説明します。 これでasset_sync自体の設定は完了しました!

assetsのURLをCloudfrontにする

assetsファイルはCloudfrontより配信されます。 通常の設定では、http://domain/assets/配下のファイルが配信されますが、これをhttps://Cloudfrontのエンドポイント/assetsに変更し、Cloudfrontから配信するように設定します。

  • config/environments/production.rb
〜中略〜

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  config.action_controller.asset_host = '//xxxxx.cloudfront.net'

〜中略〜

これでassetsファイルがCloudfrontより配信されるようになりました!

次はcapistranoの設定を行います

capistranoの設定

流れとして、

  1. cap 〜 deployコマンドを叩いた時に、ローカルにてrake assets:precompileを走らせる(asset_syncが自動的にS3にアップロードしてくれる)
  2. manifestファイルをサーバのpublic/assetsディレクトリに転送する
  3. ローカルで生成されたpublic/assetsディレクトリを消去する

この中で特に大切なのは2.かと思います。
railsは、productionにデプロイされる際にprecompileを行います。
その際にdigestを付与してファイル名-digest.scssのようなファイル名に変更します。
このdigestはprecompile時にランダムに設定されるのでrails側で管理が必要になります。
そのファイル名を管理するのがmanifestファイルです。これが存在しないと、railsはassetのパスを正しく指定出来ません。

ではcapistranoの設定に移っていきます。 こちらのコードを参考にさせて頂きました。

qiita.com

  • config/deploy.rb
〜中略〜

namespace :deploy do
  # rake db:assets:precompileを実行する
  task :asset_sync do
    run_locally do
      Bundler.with_clean_env do
        execute :rake, 'assets:precompile'
      end
    end
  end

  # manifestファイルをアップロードする
  task :upload_manifest do
    on roles(:app) do |host|
      if test "[ ! -d #{release_path}/public/assets ]"
        execute "mkdir -p #{release_path}/public/assets"
      end
      file_path = Dir::glob('public/assets/.sprockets-manifest*').first
      upload!(file_path, "#{release_path}/public/assets")
    end
  end

  # ローカルに残されたprecompile後のassetsファイルを削除する
  task :assets_cleanup do
    run_locally do
      Bundler.with_clean_env do
        execute :rake, 'assets:clobber'
      end
    end
  end

  task :restart do
    invoke 'unicorn:restart'
  end
end

before 'deploy:starting', 'deploy:asset_sync'
after 'deploy:publishing', 'deploy:upload_manifest'
after 'deploy:publishing', 'deploy:assets_cleanup'
after 'deploy:publishing', 'deploy:restart'

〜中略〜

上記のようになります。
rakeタスクに関しては新しくファイルに切り分けても良いかと思いますが、そんなにデプロイスクリプトが多いわけでも無かったので僕はそのままdeploy.rbに書いてしまっています。
release_pathの部分はそれぞれの環境に合わせたpathを指定すればOKです。
developmentではsprocketsを使用しているのでそれをそのままuploadしております。

AWS側の設定

S3の設定

S3はバケットを作成し、CORSの設定を行います。 CORSの設定をしないと、Font Awesomeが正しく表示されません。 詳しくは

dev.classmethod.jp

を御覧ください。

  1. AWSコンソールの「S3」を選択

  2. 「バケットの作成」を選択

  3. バケット名、リージョンを設定

  4. 作成したバケットを選択し、「プロパティ」→「アクセス許可」→「CORS設定の編集」を選択 f:id:cluex-developers:20160810172755p:plain

  5. ここに下記のコードを貼り付け、「保存」をします 下記のコードを使用させて頂きました。

sora33.hatenadiary.com

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Cloudfrontの設定

基本設定

assets用のディストリビューションを作成します。

  1. AWSコンソールの「Cloudfront」を選択

  2. 「Web」を選択 f:id:cluex-developers:20160810152149p:plain

  3. Origin Settingsを入力する

項目 設定 説明
Origin Domain Name 先ほど作成したS3バケット名 オブジェクトの取得先
Origin Path 空のまま S3のルートディレクトリの設定
Origin-ID 分かりやすい名前 オリジンを区別する一意のID
Restrict Bucket Access Yes CloudfrontのURLからのみS3のオブジェクトにアクセス出来る
Comment 分かりやすい名前 identityを区別するIDのようなもの
Grant Read Permissions on Bucket Yes バケット内のオブジェクトの読み取り許可
Origin Custom Headers 空のまま リクエストをオリジンに転送する際にカスタムヘッダーを含めるかどうか

f:id:cluex-developers:20160810152400p:plain

※ Default Cache Behavior Settings, Distribution Settingsに関しては環境に応じて入力して下さい。今回は特にいじらずに行きます。

「Create Distributions」を選択するとCloudfrontのDistributionが作成されます。

Behaviorの設定

さて、Behaviorの設定を行っていきましょう。 Behaviorは振り分けのルールです。
例えば、

  • https://xxxx.cloudfront.net/assetsにリクエストが来た場合にはassetsバケットに転送
  • https://xxxx.cloudfront.net/imagesにリクエストが来た場合にはimagesバケットに転送

のようなことが可能になります。

  1. 「Behavior」タブの「Create Behavior」を選択 f:id:cluex-developers:20160810155354p:plain

  2. Settingsを入力

項目 設定 説明
Path Pattern /assets/* 振り分けパターン。今回は/assets配下のオブジェクト全てをS3から配信したいので/assets/*とする
Origin 先ほど設定したOrigin-ID どのオリジンからオブジェクトを取得するかを選択
Viewer Protocol Policy 環境によって選択 アクセスする際のプロトコルの選択
Allowed HTTP Methods GET, HEAD オリジンに転送するHTTPメソッド。今回は取得のみなのでGET, HEADを選択
Forward Headers Whitelist -> Origin どのヘッダを転送するか 今回CORSの設定を行っているので、OriginをAddする
Grant Read Permissions on Bucket Yes バケット内のオブジェクトの読み取り許可
Object Caching Use Origin Cache Headers MaximumTTL等を変更しないのであればUse Origin Cache Headersのままで良い

f:id:cluex-developers:20160810173203p:plain

「Create」を選択すると、このBehaviorが有効になります。

設定の反映までは10分〜20分ぐらいかかるので気長に待ちましょう!

以上です。 asset_syncのおかげでそこまで多くの手順を踏むこと無く、assets on S3の導入が出来ました。 次回はLambdaを使用してInvalidationを走らせる処理について書きたいと思います。