CentOS7にnginx+puma+Postgresql+Let's Encrypt+Rails5.2環境をitamaeで作成する

この記事を書いたひと: @t4traw 2018年7月26日

先日公開したecdo、とりあえずherokuにデプロイしていたのですが、諸事情により国内VPSに引っ越しする必要がありました🚚

まあ引っ越しといってもユーザー登録受け付けてなかったし、dbの引っ越しが無いので単純にアプリケーションのデプロイ先を変えてDNS変えるだけでカンタンカンタン🙆……だったはずなのに、CentOS7とLet’s Encryptという新しい要素もあってなかなか大変でした。ので、その作業を備忘録として残しておきます。

CentOS 7

まず国内のVPSで良いのが無いかな?と探してみました。第1候補はやっぱりさくらのVPSだったのですが、さくらのVPSはスナップショット機能が2018年7月26日時点で無い(それに初期費用とかスケールアップ費用が高いよ……)ので却下。他にどこかないかなーと思って探したらConoHaのVPSが良さそうだったのでConoHaにしました。

ConoHaのVPSはサーバー追加時にRails構成など作れるみたいですが、それだと勉強にならないし、そもそも何が入っているか分からないので、真っ更な状態で始めます。

建てたら簡単にrootでsshしてみて繋げるかテストしておきました。ここからitamaeでプロビジョニングしていきます。

nginx

CentOS7環境で普通にyum install -y nginxすると1.12が入るのですが、これが動きません😭 具体的には

nginx: [emerg] module “/usr/lib64/nginx/modules/ngx_http_geoip_module.so” version 1010002 instead of 1011009 in /usr/share/nginx/modules/mod-http-geoip.conf:1

というエラーが発生します。

少し調べると1.10を使えと書いてあるのですが、yum list --showduplicates nginxしてみて1.12しか無い……。過去のrpmを探してyum installしてみても依存関係が解決しない。

えー……参ったな。と思ってふとnginxの最新版っていくつだ?と思って調べてみると、1.15が最新版のようす。

sudo vim /etc/yum.repos.d/nginx.repo

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=1

を書き込んで、yum remove -y nginxからのyum install -y nginxして動かしてみると……動いた❗😭

あとは不必要なdefaultのファイルとかrailsの実行ユーザーと揃えたりなどの作業をすれば、とりあえず🆗👌なはず。

レシピはこんな具合になってます。

# webサーバー系のポートを開ける
execute "Open web server ports" do
  user "root"
  cmd = [
    'firewall-cmd --add-port=80/tcp --permanent',
    'firewall-cmd --add-port=443/tcp --permanent'
  ]
  command cmd.join('&&')
end

execute 'firewall-cmd --reload'

service 'firewalld' do
  action [:restart]
end

# CentOS7で普通にyumでnginxを入れると1.12が入るが、
# これが動かないので、最新版を入れる必要有り
template "/etc/yum.repos.d/nginx.repo"

package 'nginx'

%w(default.conf ssl.conf virtual.conf).each do |conf|
  file "Delete #{conf}" do
    path "/etc/nginx/conf.d/#{conf}"
    action :delete
  end
end

# railsの実行ユーザーと合わせないといけないので変更する
file "Edit nginx.conf" do
  path "/etc/nginx/nginx.conf"
  action :edit
  block do |content|
    content.gsub!(/^.?user .+$/, "user root;")
  end
end

service 'nginx' do
  action [:enable, :start]
end

Let’s Encrypt

さてLet’s Encryptですが、itamaeで使えそうなプラグインを試してみるけど……自分の環境で動かない。ので、2つだけなのでもうシェルスクリプトでいきました。

Let’s Encryptの注意点としては申請するドメインにhttpでアクセスできる必要があります。なので先にnginxを起動させ、申請するドメインにアクセスしてnginxのデフォルト画面表示できる状態でないとダメみたいです。

package "git"
git "#{node.letsencrypt.cert_bot_path}" do
  repository "https://github.com/certbot/certbot"
end

execute "#{node.letsencrypt.cert_bot_path}/certbot-auto -n certonly --webroot -w #{node.letsencrypt.webroot} -d #{node.letsencrypt.domain} -m #{node.letsencrypt.mail_address} --agree-tos --server https://acme-v02.api.letsencrypt.org/directory"

execute %(echo "00 05 01 * * root #{node.letsencrypt.cert_bot_path}/certbot-auto renew --webroot -w #{node.letsencrypt.webroot} --post-hook 'systemctl reload nginx'" > /etc/cron.d/certbot-auto)

service 'nginx' do
  action [:restart]
end
node.yml
letsencrypt:
  domain: YOUR_DOMAIN
  mail_address: YOUR_MAIL_ADDRESS
  cert_bot_path: /usr/local/certbot
  webroot: /usr/share/nginx/html

Rails向けのnginx設定

無事Let’s Encryptで証明書が取得できたら、Rails用のnginxコンフィグファイルを作成し、適用します。レシピとconfはこんな具合になりました。

directory YOUR_APP_PATH do
  owner node[:user][:name]
  group node[:user][:name]
end

template "/etc/nginx/conf.d/app.conf" do
  source "./templates/app.conf.erb"
  variables app_path: YOUR_APP_PATH
  not_if "test -e /etc/nginx/conf.d/app.conf"
end

execute "openssl dhparam 2048 -out /etc/nginx/ssl/dhparam.pem"

execute 'npm install -g yarn'

file "/home/#{node['user']['name']}/.bashrc" do
  action :edit
  not_if "test $RAILS_MASTER_KEY"
  block do |content|
    content << %(export RAILS_MASTER_KEY="#{ENV["RAILS_MASTER_KEY"]}"\n)
  end
end

nodeの所は各自で置き換えてくださいませorz

app.conf.erb
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

upstream <%= node.app.name %> {
  server unix://<%= @app_path %>/shared/tmp/sockets/puma.sock;
}

server {
  listen      80;
  listen [::]:80;
  return 301 https://$host$request_uri;
}

server{
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name <%= node.app.domain %>;
  root <%= @app_path %>/current/public;
  
  gzip on;
  gzip_types text/css application/javascript application/json application/font-woff application/font-tff image/gif image/png image/jpeg;
  gzip_min_length 1000;
  gzip_vary on;
  gzip_proxied any;
  
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_certificate /etc/letsencrypt/live/<%= node.app.domain %>/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/<%= node.app.domain %>/privkey.pem;
  ssl_prefer_server_ciphers on;
  ssl_ciphers ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!aNULL!eNull:!EXPORT:!DES:!3DES:!MD5:!DSS;
  ssl_dhparam /etc/nginx/ssl/dhparam.pem;
  add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains;';
  client_max_body_size 64M;

  ssl_stapling on;
  ssl_stapling_verify on;
  ssl_trusted_certificate /etc/letsencrypt/live/<%= node.app.domain %>/fullchain.pem;

  resolver 8.8.8.8;
  
  location / {
    gzip_static on;
    proxy_pass http://<%= node.app.name %>;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  location ~* ^/assets|iamges|favicon|packs/ {
    gzip_static on;
    # Per RFC2616 - 1 year maximum expiry
    expires 1y;
    add_header Cache-Control public;

    # Some browsers still send conditional-GET requests if there's a
    # Last-Modified header or an ETag header even if they haven't
    # reached the expiry date sent in the Expires header.
    add_header Last-Modified "";
    add_header ETag "";
    break;
  }
}

いくつか注意点

ssl_dhparam /etc/nginx/ssl/dhparam.pem;

ssl_dhparam /etc/nginx/ssl/dhparam.pem;という記述をしたので、opensslで生成してあげる必要があります。

execute "openssl dhparam 2048 -out /etc/nginx/ssl/dhparam.pem"

RAILS_MASTER_KEYを登録

今回はRails5.2なので、シークレット系は基本的に全てcredentials.yml.encで管理するようにしました。なので、環境変数RAILS_MASTER_KEYを設定しないといけないのですが、以下みたいな書き方が一番安全かつ分かりやすかったです。

file "/home/#{node['user']['name']}/.bashrc" do
  action :edit
  not_if "test $RAILS_MASTER_KEY"
  block do |content|
    content << %(export RAILS_MASTER_KEY="#{ENV["RAILS_MASTER_KEY"]}"\n)
  end
end

VueJS(というかwebpack)用にlocationにpacksを追加

今回はVue.js(Webpack)を使っているので、public/にpacksが生成されます。なので、locationにpacksを追加してあげました。

SSLテストの結果はA+!! 次はcapistranoでデプロイを書きます

とりあえずサーバーを作る時、辛かった部分は書いてみました。いちおうSSL Server TestではA+の結果がでたので、多分大丈夫かなと。

SSL Server Test: ecdoapp.com (Powered by Qualys SSL Labs)

次はcapistranoでデプロイする時の記事を書きたいと思います。