GhostをCloudFront+Nginxでキャッシュする
ども、takiponeです。
本ブログはオープンソースのブログエンジンGhostを利用しています。最近、Ghost バージョン1系へのアップグレードにあたりCDNとしてAmazon CloudFront、リバースプロキシとしてNginxでの構成に移行したのでご紹介します。
図で示すと以下になります。
GhostとNginxは、ScalewayパリリージョンのC1インスタンスでDockerで稼働させています。
CloudFrontを使う理由
日本からパリだとネットワーク的に遠いので、CloudFrontを噛ませて日本にあるエッジサーバーからコンテンツを配信するのが第一の目的です。ついでにCloudFrontによるフルHTTPS、HTTP/2、IPv6対応もお任せしています。無償でTLS証明書が取得できるACM(AWS Certificate Manager)が嬉しいですね。
CloudFlareにも同様の機能があり以前使っていた時期もあったのですが、TLS証明書が関係のないドメインとSANsでごっちゃにされるのが嫌だったのでCloudFrontに乗り換えた経緯があります。
なお、キャッシュ期間は/ghost
(Ghost-Admin)は全ヘッダを転送&キャッシュなし、デフォルトのビヘイビアはGhostのレスポンスにMax-Age:60
を付けて1分にしていますがもっと長くしても良いかなと思っています。
Nginxを使う理由
GhostをフルHTTPSでホストするためにはコンフィグのurl
にhttps://
スキーマをセットする必要があるのですが、Ghostでのプロトコルの判断ロジックがExpressに依存していてX-Forwarded-Proto
ヘッダにhttps
をセットする必要があります。CloudFrontはオリジンへのX-Forwarded-Proto
ヘッダ転送をサポートせず、独自のCloudFront-Forwarded-Proto
が代替のヘッダになっています。
以前はNginxを経由せずCloudFrontから直接Ghostを参照するために、Expressに以下のパッチを当てて対応していました。
diff -u -r old/node_modules/express/lib/request.js new/node_modules/express/lib/request.js
--- old/node_modules/express/lib/request.js 2016-02-07 13:04:41.000000000 +0900
+++ new/node_modules/express/lib/request.js 2016-02-07 13:08:58.000000000 +0900
@@ -315,7 +315,7 @@
// Note: X-Forwarded-Proto is normally only ever a
// single value, but this is to be safe.
- var header = this.get('X-Forwarded-Proto') || proto
+ var header = this.get('X-Forwarded-Proto') || this.get('CloudFront-Forwarded-Proto') || proto
var index = header.indexOf(',')
return index !== -1
Ghost 1系にアップデートするタイミングでこのパッチがエラーになってしまったので、Nginxでプロキシしてヘッダを置換することにしました。以下のイメージです。
利用しているnginx.confは以下の通りです。
nginx.conf
user nginx;
worker_processes 1;
error_log /dev/stderr warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
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 /dev/stdout main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
server_tokens off;
server {
listen 80;
server_name _;
location / {
proxy_pass http://ghost:2368;
}
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $http_cloudfront_forwarded_proto;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
}
}
Docker周り
nginx.conf
のproxy_pass
ディレクティブで指定しているホスト名ghost
は、DockerのUser-defined bridgesのDNS機能でGhostコンテナのIPアドレスに名前解決できることを想定しています。docker-compose.yaml
でUser-defined bridge internal
を定義してNginxとGhostの両コンテナが接続しています。
docker-compose.yaml
version: '2'
services:
nginx:
image: "arm32v7/nginx"
ports:
- "80:80"
volumes:
- "/opt/ghost/nginx.conf:/etc/nginx/nginx.conf"
networks:
- internal
ghost:
image: "arm32v7/ghost:1.19.2"
container_name: "ghost"
volumes:
- "/opt/ghost/content/:/var/lib/ghost/content/"
- "/opt/ghost/config.production.json:/var/lib/ghost/config.production.json"
networks:
- internal
networks:
internal:
なお、ghost
コンテナのイメージ名は年明けに修正された日本語IME対応のPRを適用するために、バージョン1.22のGhostコンテナを手元でビルドして利用しているため実際と異なるものです(tamurashingoさんに圧倒的感謝!)。Docker Hubのarm32v7/ghostイメージのビルドが最近Failしているのが気がかりです。。。
コスト
ScalewayのC1サーバーが2.99€/月、AWSはRoute 53の$0.5/月とCloudFrontの従量がだいたい$0.5/月くらいで概ね月額500円です。C1サーバーは2年半前から利用していますが、とても安定していて満足しています。
まとめ
Ghostをホストする例として、CDNのCloudFrontとヘッダ置換のためにリバースプロキシとしてNginxを組み合わせる例をご紹介しました。