珍しく(初めて?)AWSネタです。
仕事でAWSのVPCを構築しているのですが、画像を取り扱うサーバが欲しいとの要望がありました。
画像を表示するだけならCloudFront+S3だけで強力な画像(CDN)サーバを構築することができるのですが、今回は画像サイズもリアルタイムで変換して表示してほしいとのことでした。
幸いにも社内で以前、nginxのhttp_image_filter_moduleを使って画像変換している実例(実サーバ)があるのでそれを参考に構築してみました。
構成図
簡単な図です。
今回やりたかったこと
- ユーザが独自ドメインとマッピングされたCloudFrontにアクセスすると、CloudFrontに画像がキャッシュされていればその画像を返します
- もし画像がキャッシュされていなければELB・EC2インスタンス経由でS3にある画像を取得します
- EC2インスタンスに
http_image_filter_module
が組み込まれたnginxがインストールされており、画像サイズをQueryString(width
,height
など)で指定すると、S3に保存している画像をリサイズして返します
なぜ、ELBを設置しているかと、以下の2つの理由があります。
- 画像変換サーバ(EC2インスタンス)の負荷対策
- CloudFrontに画像がキャッシュされていれば頻繁にアクセスされず負荷が上がることがないと思いますが、最低限の負荷対策はしておきたいです。ELB配下に置いておくことでAutoScalingにも対応できます。
- 画像変換サーバ(EC2インスタンス)をPrivateSubnetに置きたかった
環境
- aws-cli 1.4.0
- ELB、EC2インスタンスの作成などに使いました。AWS Command Line Toolsより使いやすいです
- nginx 1.6.1
前提条件
VPC内にELBを作成・設定する
$ aws elb create-load-balancer --load-balancer-name elb-image-filter \
--listeners Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80 \
--subnets subnet-xxxxxxxxx \
--security-groups sg-xxxxxxxxx \
--output text --query 'DNSName'
elb-image-filter
という名前で作成しました。SubnetはPublicSubnetを指定します。
次にELBのHealthCheckを設定します。
$ aws elb configure-health-check --load-balancer-name elb-image-filter \
--health-check Target=TCP:80,Interval=30,UnhealthyThreshold=2,HealthyThreshold=2,Timeout=5
Interval
やHealthyThreshold
などはお好みで設定してください。
VPC内にEC2インスタンスを作成する
$ aws ec2 run-instances --image-id ami-xxxxxxxx \
--key-name xxxxxxxx \
--security-group-ids sg-xxxxxxxx \
--instance-type t2.micro \
--subnet-id subnet-xxxxxxxx \
--count 1 \
--output text --query 'Instances[].InstanceId'
作成したEC2インスタンスにNameタグを付けます。
$ aws ec2 create-tags --resources i-xxxxxxxx --tags Key=Name,Value=vpc-image-filter
先ほど作成したEC2インスタンス(i-xxxxxxxx
)にvpc-image-filter
というNameタグを付けました。
ELB配下にEC2インスタンスを追加します。
$ aws elb register-instances-with-load-balancer --load-balancer-name elb-image-filter --instances i-xxxxxxxx
この時点ではまだnginxをインストールしていない(80ポートは開いていない)ため、ELBのHealthCheckに失敗(OutOfService)しています。
EC2インスタンスにnginxをインストール・設定する
インストール方法は、nginx(1.4.7)をソースからインストールするを参照してほしいのですが、今回インストールするときの相違点を以下に挙げます。
- nginxのバージョンについて
- 何でも良いのですが、現時点(2014/08/18)でStableである1.6.1でインストールします
- パッケージのインストールについて
http_image_filter_module
をインストールするには、gd
が必要なので予めインストールしておきます。
$ sudo yum install gd gd-devel
- configureのオプションについて
http_image_filter_module
をインストールするには、configureのオプションに--with-http_image_filter_module
を付ける必要があります
$ ./configure --with-http_image_filter_module
nginxをインストールしたら起動します。
$ sudo service nginx start
ELBのHealthCheckがInService
になるのを待ちます。先ほど設定したHealthCheckだと約1分程度でInService
になるはずです。
$ aws elb describe-instance-health --load-balancer-name vpc-image-filter --output text --query 'InstanceStates[]'
N/A i-xxxxxxxx N/A InService
もしずっと待ってもOutOfService
になっていたら、VPCのネットワーク設定(NetworkACL,SecurityGroup)やnginx,ELBの設定を見直してください。
InService
になったら、ブラウザからELBのDNSName(http://elb-image-filter-xxxxxxxx.ap-northeast-1.elb.amazonaws.com/)を確認して、nginxのページ(Welcome to nginx!)が表示されることを確認してください。
S3にバケット作成&画像アップロードする
S3のバケット作成と画像アップロードはManagementConsoleから行いました。
バケット名はimage-filer
、画像ファイル名はtest.jpg
にしました。
画像をアップロードしてもそのままではブラウザから確認できないため、パーミッションを変更します。
アップグレードした画像に対して、下図のようにパーミッションを設定します。Grantee
にEveryone
を選択し、Open/Download
にチェックを入れてください。
https://s3-ap-northeast-1.amazonaws.com/image-filter/test.jpgか、https://image-filter.s3-ap-northeast-1.amazonaws.com/test.jpg にアクセスして画像が表示されればOKです。
S3にある画像のサイズを変換させて表示させる
ここでようやく、nginxのhttp_image_filter_module
を使った設定を行います。
/etc/nginx/nginx.conf
に記述していますが、別ファイルに記述してInclude
しても良いです。
設定内容は、簡単!リアルタイム画像変換をNginxだけで行う方法 | cloudrop を参考にしました。
user nginx nginx;
worker_processes 2;
error_log /var/log/nginx/error.log debug;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
set_real_ip_from 10.0.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
merge_slashes on;
gzip_static off;
server_tokens off;
server {
listen 80;
server_name localhost;
root /etc/nginx/html;
access_log /var/log/nginx/image-filter-access.log main;
error_log /var/log/nginx/image-filter-error_log;
resolver 8.8.8.8 valid=30s;
image_filter_buffer 5M;
location ~ /(.*\.jpg|png|gif)$ {
set $s3host s3-ap-northeast-1.amazonaws.com/image-filter;
set $file $1;
set $width 150;
set $height 150;
set $quality 100;
if ($query_string !~ .*=.*) {
rewrite ^ /s3_original last;
}
if ($arg_width ~ (\d*)) {
set $width $1;
}
if ($arg_height ~ (\d*)) {
set $height $1;
}
if ($arg_quality = (100|[1-9][0-9]|[1-9])) {
set $quality $1;
}
if ($arg_type = "resize") {
rewrite ^ /s3_resize last;
}
rewrite ^ /s3_crop last;
}
location /s3_original {
internal;
proxy_pass https://$s3host/$file;
}
location /s3_resize {
internal;
proxy_pass https://$s3host/$file;
image_filter resize $width $height;
image_filter_jpeg_quality $quality;
error_page 415 = @empty;
}
location /s3_crop {
internal;
proxy_pass https://$s3host/$file;
image_filter crop $width $height;
image_filter_jpeg_quality $quality;
error_page 415 = @empty;
}
location @empty {
empty_gif;
}
}
}
複雑そうな設定に見えますが、画像サイズなどのパラメータにしたがって、nginx -> S3へのリバースプロキシを設定しているだけです。
nginxを再起動(ORリロード)します
$ sudo service nginx reload
http://elb-image-filter-xxxxxxxx.ap-northeast-1.elb.amazonaws.com/test.jpg?width=300&height=500&type=resize などheight
, weight
のパラメータ値を設定し、ブラウザからアクセスしてリサイズされた画像を表示されればOKです。
CloudFrontの設定を行う
CloudFrontのCDN作成は、ManagementConsoleで、Create Distributions
からCloudFrontを作成します。
下図のように、Origin Domain Name
には、ELBのDNSNameを選択してください。OriginID
は自動的に入力されるので変更しなくても良いです。
Default Cache Behavior Settings
では、Forward Query Strings
をYesに設定するだけであとはデフォルト設定で良いです。
Distribution Settings
にある、Alternate Domain Names(CNAMEs)
は後で設定します。
設定するとCloudFront Distributions
の一覧に追加されますのでStatus
がDeployed
になるまで待ちます。
Deployed
になったら、ブラウザで、http://xxxxxxxxxxxxxx.cloudfront.net/test.jpg?width=300&height=500&type=resize にアクセスして画像が表示されればOKです。
※ xxxxxxxxxxxxx.cloudfront.net
は作成されたCloudFrontのDomanNameです。
画像変換サーバのnginxのアクセスログを確認すると、初回アクセスのみアクセスされ、同じリクエストをするとCloudFront側でキャッシュされるため画像変換サーバにアクセスが来なくなることが分かります。異なる画像サイズを指定する度に画像変換サーバにアクセスされますが、CloudFront側にもリサイズされた画像がキャッシュされます。
独自ドメインの設定
ここまでで画像変換サーバの構築は終わっているのですが、URLがhttp://xxxxxxxxxxxxxx.cloudfront.net〜
のようになっていますので、独自ドメインで画像を表示できるようにします。
ドメインをimg.example.com
として設定します。
独自ドメインの設定には、CloudFrontとRoute53の設定が必要です。また、必要に応じてnginxの設定も変更します。
CloudFrontの設定
下図のようにAlternate Domain Names(CNAMEs)
に独自ドメインを設定します。
設定完了後、CloudFront Distributions
にてCNAMEa
の列にドメインが表示されればOKです。
Route53の設定
下図のようにALIASレコードを設定します。
- Name: 独自ドメイン(サブドメイン)
- Type: Aレコード
- Alias: Yes
- Alias Target: CloudFrontで割り当てられたドメイン(xxxxxxxxxxxxxx.cloudfront.net)
CNAMEレコードでも良いのですが、ALIASレコードの方が余計なリクエストをしなくて済むみたいです。
参考:Amazon Route 53のALIASレコード利用のススメ
設定が完了したら、dig
コマンドでDNSレコードを確認してみます。
$ dig img.example.com
; <<>> DiG 9.8.3-P1 <<>> img.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30409
;; flags: qr rd ra; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;img.example.com. IN A
;; ANSWER SECTION:
img.example.com. 59 IN A 54.230.xxx.xxx
img.example.com. 59 IN A 54.230.xxx.xxx
img.example.com. 59 IN A 54.230.xxx.xxx
img.example.com. 59 IN A 54.230.xxx.xxx
img.example.com. 59 IN A 54.230.xxx.xxx
img.example.com. 59 IN A 54.230.xxx.xxx
img.example.com. 59 IN A 54.230.xxx.xxx
img.example.com. 59 IN A 54.230.xxx.xxx
;; Query time: 112 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Aug 14 19:15:53 2014
;; MSG SIZE rcvd: 178
複数IPアドレスが返ってくるようになるはずです。
nginxの設定
ServerName
を変更します。localhsot
(デフォルトサーバ)でも良いのですが、複数のVirtualHostを設定したい場合はServerName
をELBのDNS名に変更します。
また、ELBのDNS名が長すぎてnginx: [emerg] could not build the server_names_hash, you should increase server_names_hash_bucket_size: 64
というエラーが出るため、server_names_hash_bucket_size 128;
というパラメータを設定しています。
下記がnginxの設定ファイルの最終版です。
user nginx nginx;
worker_processes 2;
error_log /var/log/nginx/error.log debug;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
set_real_ip_from 10.0.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
merge_slashes on;
gzip_static off;
server_tokens off;
server_names_hash_bucket_size 128;
server {
listen 80;
server_name elb-image-filter-xxxxxxxx.ap-northeast-1.elb.amazonaws.com;
root /etc/nginx/html;
access_log /var/log/nginx/image-filter-access.log main;
error_log /var/log/nginx/image-filter-error_log;
resolver 8.8.8.8 valid=30s;
image_filter_buffer 5M;
location ~ /(.*\.jpg|png|gif)$ {
set $s3host s3-ap-northeast-1.amazonaws.com/image-filter;
set $file $1;
set $width 150;
set $height 150;
set $quality 100;
if ($query_string !~ .*=.*) {
rewrite ^ /s3_original last;
}
if ($arg_width ~ (\d*)) {
set $width $1;
}
if ($arg_height ~ (\d*)) {
set $height $1;
}
if ($arg_quality = (100|[1-9][0-9]|[1-9])) {
set $quality $1;
}
if ($arg_type = "resize") {
rewrite ^ /s3_resize last;
}
rewrite ^ /s3_crop last;
}
location /s3_original {
internal;
proxy_pass https://$s3host/$file;
}
location /s3_resize {
internal;
proxy_pass https://$s3host/$file;
image_filter resize $width $height;
image_filter_jpeg_quality $quality;
error_page 415 = @empty;
}
location /s3_crop {
internal;
proxy_pass https://$s3host/$file;
image_filter crop $width $height;
image_filter_jpeg_quality $quality;
error_page 415 = @empty;
}
location @empty {
empty_gif;
}
}
}
nginxを再起動(ORリロード)します。
$ sudo service nginx reload
全て設定完了後、ブラウザから http://img.example.com/test.jpg?width=500&height=650&type=resize にアクセスして画像が表示されることを確認してください。
もし画像が表示されない場合は
キャッシュを削除すると表示されることがあります。
- PCのDNSキャッシュ
Mac OS Xの場合
$ sudo killall -HUP mDNSResponder
- ブラウザのキャッシュ
- スーパーリロードする
最後に
CloudFrontと組み合わせた画像変換サーバを構築してみました。