WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

サイトを Amazon S3 に移行しました

遅ればせながら、新年明けましておめでとうございます。今年もよろしくお願いいたします。

2008 年に移行してからずっと XREA で動いていたこのブログですが、さすがにこのご時世にレンタルサーバーはないだろということで、年末年始の休みを利用して Amazon S3 に移行してみました。 DNS は以前から Route 53 に移行済みなので、サイトのほぼ全体で 99.999999999% の堅牢性が確保できたはず。技術の無駄使いとはこのことですね(笑)

Amazon S3 で静的 Web サイトをホスティングする方法については日本語でも多くの解説がありますが、今回は以下の点で少し工夫してみました。

  • ネイキッドドメインでのホスティング
  • 低冗長化ストレージを使って料金節約、ファイル喪失時はメール通知
  • Ruby スクリプトでファイル転送

そこで、本日はこれらの点も含めて Amazon S3 で Web サイトをホスティングする方法をご紹介します。

AWS アカウントの取得

Amazon S3 の機能を利用するには Amazon Web Services (AWS) アカウントが必要です。私はすでに作ってあったのですが、持っていない場合は AWS のトップページでアカウントを登録できます(登録だけなら無料です)。 1 年くらい前の情報なので少し変わっているかもしれませんが、以下の記事で手順を解説しているので参考にしてください。

Amazon EC2 と Route 53 のはじめかた その 1 : サインアップ

バケットを作成する

AWS アカウントが用意できたら、サイトのドメイン名と同じ名前で S3 のバケットを作成します。うちのサイトであれば、「webos-goodies.jp」という名前のバケットを作るわけですね。実際の作業は、 Web ブラウザで S3 上の各種ファイル操作ができる Management Console を使うのが手軽です。

https://console.aws.amazon.com/s3/

上記のページを表示し、右上の「Create Bucket」をクリックすることでバケットが作成できます。 Bucket Name にはバケット名(サイトのドメイン名)を入力してください。

Region は S3 をホストするサーバーの場所です。速度(レイテンシの低さ)は当然 Tokyo が有利ですが、価格は以下のとおり少々割高です。

リージョンストレージ (RRS) 料金データ転送料金
US Standard$0.076/GB$0.120/GB
Tokyo$0.080/GB$0.201/GB

※ 主な値の抜粋です。正式な価格体系は公式サイトを参照してください。

ちなみに、このブログはひとまず US Standard に置いています。ブログのホスティングだけなら微々たる差でしょうが、 EC2 も組み合わせるとかやりだすとけっこうな値段差になるので、ちょっと考え中。

静的 Web サイトのホスティングを有効にする

バケットを作成したら、それを Web サイトとして公開する設定を行います。 Management Console での手順は以下のとおりです。

  • 右上のボタンで「Properties」を選ぶ。
  • バケットを選択する(バケット名をクリックすると中を開いてしまうので、アイコンのあたりをクリックするのが良い)。
  • 右に表示されたプロパティの中から「Static Website Hosting」の項目を展開する。
  • 「Enable website hosting」を選択する。
  • 「Index Document」に index.html (URL の末尾がスラッシュのときに表示するファイル)を入力する。
  • Save ボタンをクリック。

ドメインが www 付きであれば、この設定だけでサイトが公開されます。しかし、このブログのような Naked Domain (サブドメインのないドメイン名)の場合、追加のパーミッション設定が必要です。パケットのプロパティで「Permissions」を展開し、「Add bucket policy」ボタンをクリックすると、ポリシーの XML を入力するダイアログが開くので・・・

以下のコードを入力し(「webos-goodies.jp」の部分は自分のドメイン名に置き換えてください)、 Save ボタンをクリックしてください。

{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "AddPerm",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::webos-goodies.jp/*"
    }
  ]
}

これで Web サイトが公開設定されました。

ファイル喪失時のメール通知を有効にする

低冗長化ストレージ (Reduced Redundancy Storage、RRS) は、価格が安い代わりに年間 1 万分の一の確立でファイルが失われます。高いんだか低いんだか、微妙な数値ですよね。文字通り万が一のファイル喪失があった場合に備えて、メールによる通知を設定しておきましょう。

メール通知は Amazon SNS を通じて行われるので、まず Amazon SNS で通知のためのトピックを作成し、自分のメールアドレスで購読します。 Amazon SNS にも Management Console があるので、そちらで設定するのが簡単です。

https://console.aws.amazon.com/sns/

手順は以下の通りです。

  • Amazon SNS の Management Console を開き、左上の「Create New Topic」ボタンをクリック。
  • ダイアログが表示されるので、「Topic Name」に適当な名前を入力(使える文字は英数字とハイフン、アンダースコアのみ)。 Display Name は空欄で OK 。
  • ダイアログの「Create Topic」ボタンをクリック。
  • サイドバーに作成したトピック名が表示されるので、それをクリック。
  • 「Create New Subscription」ボタンをクリック。
  • 「Protocol」で Email を選択し、 Endpoint に通知を受けるメールアドレスを入力。
  • Subscribe ボタンをクリック。
  • 指定したメールアドレス宛にメールが届くので、そのリンクをクリックして購読を承認。

これで Amazon SNS 側の準備は完了です。次は S3 側の設定ですが、その前にトピックを識別するための「Topic ARN」という文字列を確認しておきます。下図(Management Console でトピックを選択した画面)の赤で囲った文字列です。長いので、クリップボードにコピーしておくと良いでしょう。

あとは、 S3 の Management Console に移動して以下の操作をすれば OK です。

  • バケットのプロパティを表示。
  • 「Notifications」の項目を展開。
  • 「Enabled」のチェックボックスにチェックを入れる。
  • 「Amazon SNS Topic」に先ほどの Topic ARN をペーストする。
  • 「Save」ボタンをクリック。

これで、ファイル喪失時にメールが送信されます。設定後にテストメールが送信されるはずなので、確認しておきましょう。

コンテンツを転送し、動作確認

S3 のバケットに、 Web サイトのコンテンツを転送します。 S3 へのファイル転送ができるツールは多くありますが、低冗長化ストレージを使うなら、それに対応したものを使う必要があります。 Mac では Cyberduck あたりでしょうか。

もっとも、 AWS は各言語用に使いやすいクライアントライブラリを用意しているので、自分用の転送ツールを作るのも難しくありません。私も Ruby で簡単なコマンドを作って使っています。記事末にソースコードを掲載しているので、参考にしてください。

コンテンツを転送したら、きちんとブラウザで表示できるかどうか確認しておきましょう。 S3 の Management Console でバケットのプロパティを表示し、 Static Website Hosting を展開した時に「Endpoint」として表示されるリンクをクリックしてみてください。正しく設定されていれば、サイトのトップページが表示されるはずです。

DNS (Amazon Route 53) の設定を変更する

既存 Web サイトを S3 に移行する上で、おそらくこれが最大の障害となるものです。 S3 で Web サイトをホストするためには、 DNS サーバーを Amazon Route 53 に移行しなくてはいけません。私は既に移行済みだったので良かったのですが、もし他の DNS サーバーを使っている場合は、以下のページなどを参考にして移行しましょう。

DNSもクラウド化! Amazon Route 53を導入してみたよ。 » aquadrops *

Route 53 への移行ができていれば、あとの設定は簡単です。 Route 53 の Management Console で Web サイトのドメインの A レコードを選択し、 Alias を Yes に設定します。 Aliast Target は、補完候補の「S3 Website Endpoints」の中から対象のドメインを選択してください。

これで Web サイトの S3 への移行が完了です。 DNS のキャッシュが切れれば、 S3 側のコンテンツが表示されるようになります。

S3 へのアップロードツール

Ruby の AWS SDK を使うと、以下の簡単な Ruby スクリプトでファイルを S3 に転送できます。

require "aws-sdk"

client = AWS::S3.new(:access_key_id => "Access Key ID",
                     :secret_access_key => "Secret Access Key" )
bucket = client.buckets['bucket_name']
object = bucket.objects['path/to/target_file']
object.write(Pathname.new('path/to/local_file'),
             :reduced_redundancy => true)

なので、下手に GUI ツールを使うよりも、自分のニーズに合わせてスクリプトを組んでしまったほうが効率的です。私も自前の CMS ツールに組み込んだほか、簡単なアップロードコマンドも作ってみました。以下がそのソースです。

#! /usr/bin/ruby

require 'rubygems'
require 'yaml'
require 'optparse'
require 'aws-sdk'
require 'mime/types'

class RRSUpload

  attr_reader   :s3_client
  attr_accessor :time_compare
  attr_accessor :verbose

  def initialize(config)
    @s3_client    = AWS::S3.new(config)
    @time_compare = false
    @verbose      = false
  end

  def upload_dir(local_path, s3_path, opts={})
    Dir.chdir(local_path) do
      Dir.glob('**/*') do |path|
        if File.file?(path)
          upload_file(path, File.join(s3_path, path))
        end
      end
    end
  end

  def upload_file(local_path, s3_path, opts={})
    bucket_name, key = split_s3_path(s3_path)
    object = s3_client.buckets[bucket_name].objects[key]
    if !@time_compare || modified?(local_path, object)
      print "#{local_path} => #{bucket_name}:#{key}\n" if @verbose
      opts = {
        :reduced_redundancy => true,
        :content_type => get_content_type(local_path)
      }.merge(opts)
      object.write(Pathname.new(local_path), opts)
    end
  end

  private

  def split_s3_path(path)
    rv = path.gsub(/\A\/+|\/+\z/u, path).split('/', 2)
    raise "A file must be in any bucket." if rv.size <= 1
    rv
  end

  def get_content_type(path)
    types = MIME::Types.type_for(path)
    if types.empty?
      'application/octet-stream'
    elsif types[0].extensions.include?('js')
      'text/javascript'
    else
      types[0].to_s
    end
  end

  def modified?(local_path, object)
    begin
      File.mtime(local_path) > object.last_modified
    rescue
      true
    end
  end

end


$options = {
  :conf_path    => File.join(File.dirname(__FILE__), 'rrs_upload.yml'),
  :time_compare => false,
  :verbose      => false,
  :help         => false
}

OptionParser.new("Usage: rrs_upload.rb [options] <local_path> <s3_path>", 20) do |opt|
  abort = false
  opt.on('-h', '--help', 'Show this message.') { $options[:help] = true }
  opt.on('-c', '--config PATH',
         'Load configurations from PATH.') {|path| $options[:conf_path] = path }
  opt.on('-t', 'Skip older files.') { $options[:time_compare] = true }
  opt.on('-v', 'Verbose mode.') { $options[:verbose] = true }
  opt.parse!(ARGV)

  if $options[:help] || ARGV.size != 2
    $stderr << opt.help
    exit(1)
  end
end

if $options[:verbose]
  $stderr << "Configuration file: #{$options[:conf_path]}\n"
  $stderr << "If local file is older than s3: #{$options[:time_compare] ? 'skipped' : 'uploaded' }\n"
end

conf = YAML.load_file($options[:conf_path])
rrs_upload = RRSUpload.new(:access_key_id     => conf['access_key_id'],
                           :secret_access_key => conf['secret_access_key'])
rrs_upload.time_compare = $options[:time_compare]
rrs_upload.verbose      = $options[:verbose]

if File.directory?(ARGV[0])
  rrs_upload.upload_dir(ARGV[0], ARGV[1])
elsif File.file?(ARGV[0])
  rrs_upload.upload_file(ARGV[0], ARGV[1])
else
  $stderr << "#{ARGV[0]} is not a file nor a directory."
end

実行には aws-sdk と mime-types の gem が必要なので、以下のコマンドでインストールしておいてください。

gem install aws-sdk
gem install mime-types

そして、スクリプトと同じディレクトリに rrs_upload.yml として以下の内容を保存してください。 Access Key ID と Secret Access Key は AWS のサイトの右上の「アカウント/コンソール」のメニューで「セキュリティ証明書」を選ぶと確認できます。

access_key_id: <Access Key ID>
secret_access_key: <Secret Access Key>

あとは、以下の書式でファイルを転送できます。

rrs_upload.rb [オプション] <ローカルパス> <バケット名>/<オブジェクト名>

ローカルパスがディレクトリであれば、その下のすべてのファイルが転送されます。例えば ~/site/webos-goodies.jp/articles 以下のファイルを S3 の webos-goodies.jp バケットの articles 以下に転送するなら、以下のコマンドを実行します。

rrs_upload.rb ~/site/webos-goodies.jp/articles webos-goodies.jp/articles

このとき、 S3 側のパスの「articles」は省略できないことに注意してください。省略すると webos-goodies.jp 直下に articles 内のファイルがコピーされてしまいます。 S3 自体にはディレクトリという概念がないので、転送先がディレクトリかどうかは容易には判断できないためです。

オプションには以下の 2 つが指定できます。

オプション機能
-c PATHrrs_upload.yml の代わりに PATH で指定したファイルを読む
-tS3 側のファイルとタイムスタンプを比較し、新しいものだけ転送する
-v転送時にファイル名を表示

このコマンド自体は最低限の機能しかありませんが、自前のアップロードツールを作る際のテンプレートなどとして活用していただければ幸いです。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles