AWSの無料利用枠内で複数サイトをサクサクと運営する方法

AWSの無料利用枠内で複数サイトをサクサクと運営する方法

記事一覧AWSの無料利用枠内で複数サイトをサクサクと運営する方法

過去に作ったレシピなので、最新の Amazon Linux だとどうかわからないですが、さくらのレンタルサーバの「スタンダートプラン」から、AWS に移行した時に、無料枠でどのくらいサイトが運営できるか試したことがあるので、それを残しておこうと思います。

さくらの時は、共有サーバで、かつ Web サーバが Apache でした。

AWS に移行した際には、Web サーバは nginx にして、FastCGI キャッシュを使いたいと思っていたのと、ページキャッシュしないところは、Redis を有効活用してデータベース(RDS)への接続を極力減らそうと思っていました。

さくらの時は、アクセス過多になると、共有サーバなので一定の閾値で 504 エラーになり、そこについては保証外ということで、上のプランや VPS など他のサービスを勧められました。

スタンダートプランはすごくコストパフォーマンスがいいので、私も 6 アカウントくらい契約して、複数サイトをそれぞれで運用していたのですが、さすがに自分でリソース管理できないと辛いなと思い始めたのがキッカケでした。

今では、さくらでもクラウドサービスは提供されていますが、とあるハンズオンセミナーで実際に体験してみたところ、日本人向けとしては使いやすそうなのですが、費用面で AWS の初年度無料枠を天秤に掛けると、どうしても AWS をまずは 1 年間使ってみようかなという気になってしまいます。

また、SSL のサーバ証明書は「Let's Encrypt」で無料発行できるので、これを利用します。

AWS でも ELB(ロードバランサ)を使えば、SSL のサーバ証明書は無料で発行可能なのですが、証明書の発行にドメインの WHOIS に設定しているメール認証が必要なので、ドメインの公開窓口にレジストラの代理公開サービスを利用している場合は難しいケースが多いです。

私はバリュードメインでドメインを発行することが多く、公開窓口は代理公開サービスを利用しているので、AWS の無料の証明書や、値段の安い RapidSSL などの証明書の購入は諦めました。

「Let's Encrypt」ならドメイン認証できるので、目的のドメインで Web サーバを立ち上げて公開していれば即時発行可能です。

サーバはAmazonLinuxにしました

サーバの OS は Linux というところは決まっていたのですが、ディストリビューションを CentOS にするか Amazon Linux にするかは迷っていました。

Amazon Linux だと、AWS じゃない環境になった時に、TIPS 的なノウハウが役に立たなくなるのと、ディレクトリ構成などに慣れるか心配だったからです。

しかし、RedHat 系のディストリビューションをベースに余分なものが入ってないものだとわかったので、迷わず Amazon Linux を採用することにしました。

とりあえず、EC2 のインスタンスや RDS、Redis のコンソールマネジメント上での設定はここでは省き、Amazon Linux で Web サーバを立ち上げる部分を残しておきたいと思います。

Webサーバの構築手順

初期ユーザーである ec2-user を使い ssh でサーバに接続し、root ユーザーになったと仮定した状態で進めます。

とりあえず、ssh のポートが 22 番のままだと IP 制限をしていない場合にセキュリティ的に緩いので、気休め程度に 10022 番などに変更しておきます。

実際には、固定 IP を持っているならそこからに限定しますし、別途ゲートウェイ的なサーバを用意して、直接 Web サーバへは入れないようにしておくと思いますが、あくまでも無料枠を活用するので、今回はそこを考えません。

$ vi /etc/ssh/sshd_config

Port 10022

$ service sshd restart

インストール済みのパッケージを最新にして、最低限の開発用コマンドをインストールしておきます。

$ yum -y update

$ yum -y groupinstall base "Development tools"

AWS の VPC の設定でセキュリティは対応するとして、サーバの OS 側では IP フィルタリングはしないので無効にします。

$ service iptables stop

$ service ip6tables stop

$ chkconfig iptables off

$ chkconfig ip6tables off

無料枠で利用できる EC2 のインスタンスタイプは t2.micro が最大スペックとなり、メモリは 1GB となっています。

これでは不安なので、スワップメモリ用として 2GB 別途確保します。

無料枠の EBS のストレージ容量が最大で 30GB なので、スワップにもう少し多く使ってもいいかもしれませんが。

スワップを作って適用したら、free コマンドでメモリの状態を確認しておきます。

$ fallocate -l 2038904k /swap.img && mkswap /swap.img && swapon /swap.img

$ free

下記は OS や EC2 のインスタンスを再起動した時に、スワップの領域が自動で割り当てられるように、起動スクリプトへの設定となります。

$ vi /etc/rc.local

SWAPFILENAME=/swap.img
MEMSIZE=`cat /proc/meminfo | grep MemTotal | awk '{print $2}'`

if [ $MEMSIZE -lt 2097152 ]; then
  SIZE=${((MEMSIZE * 2))}k
elif [ $MEMSIZE -lt 8388608 ]; then
  SIZE=${MEMSIZE}k
elif [ $MEMSIZE -lt 67108864 ]; then
  SIZE=${((MEMSIZE / 2))}k
else
  SIZE=4194304k
fi

fallocate -l $SIZE $SWAPFILENAME && mkswap $SWAPFILENAME && swapon $SWAPFILENAME

EC2 のサーバはタイムゾーンがデフォルトでは UTC になっているので、日本時間に変更しておきます。

仕事では海外の拠点とかが絡む場合もありますので、UTC のまま運用することもありますが、プライベートでは日本時間の JST にしちゃいます。

$ rm -f /etc/localtime

$ ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

$ service crond restart

$ vi /etc/sysconfig/clock

ZONE="Asia/Tokyo"
UTC=false

コマンドラインから mail コマンドでメールを送れるようにしておきます。

これはシェルなどでデータベースのバックアップを取ったり、その他、通知したいバッチを用意したいので設定しています。

$ yum install -y mailx

そして、ここまできて、ようやく nginx のインストールです。

合わせて、PHP もインストールします。今なら PHP7.1 を入れてもいいかもしれませんね。

$ yum install -y nginx

$ service nginx start

$ yum -y install php56 php56-devel php56-fpm php56-pdo php56-mysqlnd php56-mbstring

http(https)のアクセスは nginx が受け取り、バックエンドで動いている php-fpm に受け渡します。

いわゆるリバースプロキシですが、ここで php-fpm の 8080 番ポートなどに直接転送するのではなく、UNIX ドメインソケットを使っています。

どのくらい高速化できているのかのベンチマークは取っていないですが、何かの記事で高速化できるというのを見かけてそのまま鵜呑みにしちゃってます。

$ vi /etc/php-fpm-5.6.d/www.conf

user = nginx
group = nginx

listen = /var/run/php-fpm/php-fpm.sock

listen.owner = nginx
listen.group = nginx

;listen.acl_users = apache,nginx
$ vi /etc/php.ini

date.timezone = "Asia/Tokyo"

続いて nginx の設定です。

ログファイルの設定は ELB 前提で作っていましたが、Let's Encrypt の証明書を利用するので、 443 番ポートを直接サーバで待ち受けることにしています。

その他、データ転送量を減らすために通信内容を気休め程度に圧縮したりしておきます。

以下の設定は参考例です。

$ vi /etc/nginx/nginx.conf

user nginx;
worker_processes  auto;
worker_rlimit_nofile 100000;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  2048;
    multi_accept on;
    use epoll;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    client_max_body_size 5m;

    # For ELB
    set_real_ip_from   172.31.0.0/16;
    real_ip_header     X-Forwarded-For;

    log_format  main  '$remote_addr - $remote_user [$time_iso8601] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$gzip_ratio" $request_time';

    access_log  /var/log/nginx/access.log  main;

    server_tokens off;
    sendfile        on;

    keepalive_timeout 10;
    client_header_timeout 10;
    client_body_timeout 10;
    client_body_buffer_size 64k;
    reset_timedout_connection on;
    send_timeout 10;

    gzip              on;
    gzip_http_version 1.0;
    gzip_types        text/plain
                      text/xml
                      text/css
                      application/xml
                      application/xhtml+xml
                      application/javascript
                      application/x-javascript
                      application/json
                      application/x-httpd-php;
    gzip_disable      "MSIE [1-6]\.";
    gzip_disable      "Mozilla/4";
    gzip_comp_level   1;
    gzip_proxied      any;
    gzip_vary         on;
    gzip_buffers      4 8k;
    gzip_min_length   1100;

    include /etc/nginx/conf.d/*.conf;
}

サイト単位の設定は、FastCGI キャッシュや静的ファイルのクライアントキャッシュの設定などもろもろあります。

とりあえず、SSL の証明書が発行された後のことを考えて、最初から SSL の設定込みというか、http では受けたくないので、http の場合は https にリダイレクトする前提で設定を一部コメントアウトして用意しておきます。

あまり変更のないサイトと仮定して、30 日間キャッシュする設定としています。

この辺は必要に応じて適宜置き換えます。SSL の証明書が発行されたらコメントしている部分を開放します。

ドキュメントルートを仮に /usr/share/nginx/site1/app/current/docroot としたので、ここにとりあえず index.html を置いておけば閲覧できるようになるハズです。

$ vi /etc/nginx/conf.d/site1.conf

fastcgi_cache_path /var/run/site1-cache levels=1:2 keys_zone=site1:512m max_size=512m inactive=30d;
fastcgi_cache_valid 200 30d;

#server {
#    listen 80;
#    server_name [ドメイン名];
#    return 301 https://$host$request_uri;
#}

server {
    #listen       443;
    #ssl          on;
    server_name  [ドメイン名];

    #ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    #ssl_certificate      /etc/letsencrypt/live/[ドメイン名]/fullchain.pem;
    #ssl_certificate_key  /etc/letsencrypt/live/[ドメイン名]/privkey.pem;

    charset UTF-8;
    access_log  /var/log/nginx/access_site1.log  main;

    location = /ok.html {
        empty_gif;
        access_log off;
        break;
    }

    location / {
        root   /usr/share/nginx/site1/app/current/docroot;
        index  index.php ok.html;
        if (-f $request_filename) {
            break;
        }

        if (!-e $request_filename) {
            rewrite ^(.*)$ /index.php?$1 last;
        }
    }

    location ~ .*\.(html?|jpe?g|gif|png|css|js|ico|swf|inc)$ {
        root   /usr/share/nginx/site1/app/current/docroot;
        expires 10d;
        access_log off;
    }

    fastcgi_cache_methods GET;
    fastcgi_cache_min_uses 1;
    add_header Fastcgi-Cache $upstream_cache_status;

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    location ~ \.php$ {
        root   /usr/share/nginx/site1/app/current/docroot;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param  SCRIPT_NAME     $fastcgi_script_name;
        fastcgi_param  PATH_INFO       $fastcgi_script_name;
        fastcgi_param  ENVIRONMENT production;
        include        fastcgi_params;

        # cache
        set $nocache 1;
        if ($request_uri !~ "^/api/") {
            set $nocache 0;
        }

        fastcgi_no_cache      $nocache;
        fastcgi_cache_bypass  $nocache;
        fastcgi_cache_key "$request_uri";
        fastcgi_cache  site1;
        fastcgi_cache_valid 200 30d;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    location ~ /\.ht {
        deny  all;
    }

}

一旦、Web サーバは 80 番ポートで待ち受け、サイトが動く状態にしておきます。

もちろん、上記の nginx の設定でしたサイトのドメインは、DNS サーバでこの EC2 インスタンスに向けるようになっている必要があります。

例えば、バリュードメインでドメイン取得していたら、AWS の Route53 のサービスでゾーンを作成して、そこに割り当てられたネームサーバを、バリュードメイン側のドメインのネームサーバに設定しておきます。

AWS の場合は 4 つのネームサーバのアドレスが発行されるので、4 つともバリュードメイン側に設定します。

$ service php-fpm start

$ service nginx restart

$ chkconfig php-fpm on

$ chkconfig nginx on

SSL証明書の発行と設定

http でサイトが確認できたら、早速「Let's Encrypt」で証明書の発行です。

まずは、「Let's Encrypt」で証明書発行をするツールのインストールです。

$ curl https://dl.eff.org/certbot-auto -o /usr/bin/certbot-auto

$ chmod 700 /usr/bin/certbot-auto

$ unset PYTHON_INSTALL_LAYOUT

サイトのドメインと、証明書発行や運用中の連絡先としてメールアドレスを設定しておきます。

$ certbot-auto certonly --debug --webroot -w /usr/share/nginx/site1/app/current/docroot -d [ドメイン名] --email [メールアドレス]

※yum のインストールを許可して進める

「Let's Encrypt」の証明書の有効期限は 3 ヶ月後と短いスパンでの更新となります。

残り 1 ヶ月を切ると証明書の更新が可能になるので、毎日、更新確認をしておくと安心なので、以下のコマンドを cron に設定しておきます。

$ crontab -e

5 3 * * * /usr/bin/certbot-auto renew --force-renew && service nginx reload

では、この状態で、先ほどの nginx の site1 用の設定でコメントアウトしていた部分を外します。一部抜粋しますが、以下のシャープから始まる行のシャープを削除します。。

$ vi /etc/nginx/conf.d/site1.conf

fastcgi_cache_path /var/run/site1-cache levels=1:2 keys_zone=site1:512m max_size=512m inactive=30d;
fastcgi_cache_valid 200 30d;

#server {
#    listen 80;
#    server_name [ドメイン名];
#    return 301 https://$host$request_uri;
#}

server {
    #listen       443;
    #ssl          on;
    server_name  [ドメイン名];

    #ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    #ssl_certificate      /etc/letsencrypt/live/[ドメイン名]/fullchain.pem;
    #ssl_certificate_key  /etc/letsencrypt/live/[ドメイン名]/privkey.pem;

nginx を再起動して、https でアクセスできるようになっていれば成功です。

その他の運用設定

次に、AWS の ElastiCache の Redis のサービスを利用したいので、Redis クライアントをインストールしておきます。

これで redis-cli コマンドで Redis の操作が可能となり、キャッシュの管理もしやすくなります。

$ cd /usr/local/src

$ rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

$ yum -y --enablerepo=remi install redis

PHP からも Redis が使えるように phpredis のインストールしておきます。

$ git clone https://github.com/phpredis/phpredis.git

$ cd phpredis

$ phpize

$ ./configure

$ make

$ make install

php の設定をして、php-fpm と nginx を再起動します。

php- m のコマンドで phpredis が表示されれば OK です。

$ vi /etc/php-5.6.d/50-redis.ini

$ extension = redis.so

$ service php-fpm restart

$ service nginx restart

次に MySQL クライアントもインストールします。

これでコンソールから mysql コマンドで RDS にも接続できます。

$ yum -y install mysql

あとはお好みで必要なコマンドやツールをインストールします。

私用の備忘録なので、メモ代わりに必要なものを書き残しておきます。

### jhead
$ cd /usr/local/src

$ wget http://www.sentex.net/~mwandel/jhead/jhead-3.00.tar.gz

$ tar zxvf jhead-3.00.tar.gz

$ cd jhead-3.00

$ make

$ make install
### nkf
$ cd /usr/local/src

$ wget http://mirror.centos.org/centos/6/os/x86_64/Packages/nkf-2.0.8b-6.2.el6.x86_64.rpm

$ rpm -ivh nkf-2.0.8b-6.2.el6.x86_64.rpm
### image magic
$ yum -y install sharutils ImageMagick ImageMagick-devel

プログラムやアプリのソールコードは BitBucket にあるとして、Jenkins を使ってデプロイできるようにしておきます。

要は CI サーバも、この 1 台の中に含めて管理しちゃいます。

### jenkins

$ yum install java-1.8.0-openjdk-devel

# 1.8の番号を入力
$ alternatives --config java

$ cd /usr/local/src

$ wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo

$ rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key

$ yum -y install jenkins

$ vi /etc/sysconfig/jenkins

JENKINS_JAVA_OPTIONS="-Djava.awt.headless=true -Dhudson.util.ProcessTree.disable=true -Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Tokyo"

JENKINS_ARGS="--httpPort=${JENKINS_PORT}"

$ vi /etc/nginx/conf.d/jenkins.conf

(省略)

$ service jenkins start

$ chkconfig jenkins on
$ visudo

Defaults:jenkins !requiretty
jenkins ALL=(ALL) NOPASSWD:ALL

$ vi /etc/passwd

jenkins:x:496:495:Jenkins Continuous Integration Server:/var/lib/jenkins:/bin/bash

$ su - jenkins

$ ssh-keygen -trsa -C "jenkins"

# bitbucket上でデプロイ鍵を設定する

$ ssh -i /var/lib/jenkins/.ssh/id_rsa -T git@bitbucket.org

まとめ

今回は、site1 だけの設定を例にしてみましたが、site2 や site3 を増やす場合も、アプリのソースコードを持ってきて nginx の設定をすれば完了です。

同じく、SSL の証明書発行をすれば、この 1 台のサーバで複数の SSL 対応のサイトも稼働できます。

これは、nginx の SNI(Server Name Indication)の機能を利用して、マルチドメインの HTTPS の対応をしています。

昔は、1 つの SSL に対して(443番ポートで待ち受ける場合)、グローバル IP アドレスが 1 つ必要で、無理だった記憶があるのですが、いつの間にか SNI とかの機能ができていましたね。

これで、一時期 10 サイト以上運用しても、Route53 に対するドメインの設定と、データ転送量だけの料金の支払いがあるだけでしたので、実質、共有サーバのようにリソースの制限は自分のサーバ次第となり、コスト面でも月に 1000 円以内で十分収まります。

まあ、Route53 にドメインを 1 つ設定するのに 50 セントの料金が必要なので、10 サイトと仮定するとその時点で 500 円くらいになるのですよね。

さくらのスタンダートプランは確かドメイン 20 個まで設定が可能だったので、その部分ではコスト的には負けますが、アクセスが増えても、この EC2 のスペック内であれば対処できるので、その自由度は高いです。

ズバリ、CPU に対しては、nginx のキャッシュが一番貢献度が高いと思います。

ネックとなる DB アクセスも、最悪は Redis でカバーできますし、php の実行と RDS への接続コストはあまりありません。

もちろん、動的なコンテンツで、リアルタイムに最新の情報をやり取りしないといけないようなサービスの場合は、キャッシュでの効率化が難しい部分もありますが、不特定多数の人に同じ内容を閲覧してもらうサービスにはマッチしているパターンだと思います。

参考カテゴリ

オススメ記事

最新の投稿やよく見られているのオススメ記事一覧です。

標準出力と標準エラー出力をパイプに渡す

よく、標準出力先をファイルにリダイレクトすることはありますが、意外に標準エラー出力は使う機会がありません。しかし、やはりツールやパッケージ製品になると標準エラー出力も活用されているようです。でも、...

Linux(FreeBSD)

>>記事を確認する

主要無料ブログ5つに対する自動投稿PHPプログラム

アメブロへ楽天APIなどの情報を自動で投稿する の反響が良かったので、どうせならアメブロ以外の無料ブログの XML-RPC を使った自動投稿も紹介したいと思います。これには FC2BlogManager.php というライブ...

PHP

>>記事を確認する

snmpでデバイスの情報を取得

snmpでデバイスの情報を取得するコマンドです。これを元に、ディスクIOなどのMIB値を探します。 $ snmpwalk -v 2c -c {SNMPGROUPNAME} localhost 1.3.6.1.4.1.2021.13.15.1.1.2 (結果の一部) UCD-DIS...

Linux(FreeBSD)

>>記事を確認する

awkで指定したカラム以降をprint表示する

前回、awk で最後のカラム以外を表示してみましたが、今回は指定したカラム以降のカラムを全部表示したいと思います。 例えば、7 個の文字列がカンマで区切られているファイルがあるとしたら、3 番目のカラム...

awk / シェル

>>記事を確認する

CentOSでRedisサーバを使う

冗長化した Web サーバなどで、セッションの管理や、セッションや特定のユーザーに紐付く一時データを管理するのに memcache を使う場面は多いと思います。 その memcache の中でも主流なのが memcached です...

Linux(FreeBSD) / CentOS / Redis

>>記事を確認する

新しいぐるなびAPIで飲食店の店舗情報取得

過去に作成した「ぐるなびAPI」のプログラムや、ぐるなびの Web Service が新しくなったこともあり、API からの情報取得プログラムを書き換えてみました。 以前の記事は下記になります。 ・ ぐるなびAPIで...

WebAPI

>>記事を確認する

NginxのFastCGIキャッシュで白い画面がキャッシュされる

以前から、トップページにアクセスすると、レスポンスステータスは 200 で返ってくるのに、画面に何も表示されない現象が稀に見受けられたので調査してみました。 さすがに機会損失にも繋がるということで、...

Nginx

>>記事を確認する

Laravel5.4の認証ユーザーのパスワードハッシュについて

Laravel で用意されている認証モジュールを利用する際、ユーザーモデル(User.php)経由で登録されるパスワードのハッシュ方法について調べてみました。 Laravel 上ではパスワード文字列を bcrypt() のヘルパー...

PHP / Laravel

>>記事を確認する

Laravel5.4のコントローラコンストラクタでAuth::user()が取得できない

Laravel5.4 で認証を通したアクセスに対して、コントローラのコンストラクタでユーザモデルの値を取得しようと思ったら、なぜか Auth::user() の値が取得できなくて悩みました。 public function __construc...

PHP / PHPフレームワーク / Laravel

>>記事を確認する

NginxのHSTS(HTTP Strict-Transport-Security)の設定

「Let's Encrypt」のおかげで、全サイト SSL 化していますが、これまで nginx の設定では、http のアクセスがあった場合に https に 301 リダイレクトさせていました。 この場合、Googlebot に http のアク...

セキュリティ / Nginx / SSL

>>記事を確認する