Set up production environment for Rails app using Puma and Nginx

Table of contents

Puma là web server nhỏ gọn đi liền trong Rails giúp developer có thể bắt đầu code một cách nhanh nhất. Tuy nhiên mang nó làm web server thực sự để chạy trên môi trường production thì chưa ổn. Bài này mình muốn memo lại chia sẻ với các bạn các bước cài đặt server để ứng dụng Rails chạy ổn định trên môi trường production.

Prerequisite

  • OS môi trường là Amazon linux
  • Database sử dụng là Postgres

System configuration

Puma hoạt động như application server cho ứng dụng Rails, còn Nginx sẽ hoạt động với vai trò là reverse proxy - nhận request và chuyển response giữa client và Puma. Puma và Nginx giao tiếp với nhau thông qua socket.

  • rbenv + Ruby 2.5
  • Rails 5 + Puma + Nginx

puma-nginx.png

Source Image : http://codeonhill.com

Ok, ssh vào server và bắt đầu cài đặt thôi! ↓↓↓


1. Chuẩn bị môi trường

# Cài đặt các package cơ bản
# ※ Riêng cái htop thì optional, mình thích dùng htop để xem trạng thái server nên có thói quen cài nó
$ sudo yum update
$ sudo yum install \
git make gcc-c++ patch \
openssl-devel \
libyaml-devel libffi-devel libicu-devel \
libxml2 libxslt libxml2-devel libxslt-devel \
zlib-devel readline-devel \
postgresql-libs postgresql-devel \
epel-release htop
$ sudo amazon-linux-extras install epel
$ sudo yum install nodejs npm --enablerepo=epel

2. Tạo user

Thói quen tốt là không nên dùng user root/ec2-user mặc định, do đó ta sẽ tạo một user mới để phục vụ quá trình cài đặt cũng như khởi chạy ứng dụng Rails.

# Tạo user deploy
$ sudo adduser deploy
$ sudo passwd deploy  # Cài đặt password cho user deploy
$ sudo visudo         # Gán quyền cho user deploy
-----------------------------
# vim khởi động lên, thêm quyền bằng cách thêm dòng sau
root    ALL=(ALL)       ALL
deploy  ALL=(ALL)       ALL # ← thêm dòng này
-----------------------------
$ sudo su - deploy    # Chuyển qua user deploy
# Cài đặt timezone
$ sudo ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
# Cài đặt alias ll, hữu dụng và dễ đọc file size hơn lệnh ls
$ echo 'alias ll="ls -lh --color=auto"' >> ~/.bash_profile
$ source ~/.bash_profile

3. Cài đặt rbenv và Ruby

Sử dụng rbenv để quản lý các version của ruby.

# Cài đặt rbenv
$ sudo su - deploy
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv rehash
# Cài đặt ruby 2.5
$ rbenv install -v 2.5.1
$ rbenv global 2.5.1
$ rbenv rehash
$ ruby -v

4. Chuẩn bị cho ứng dụng Rails

# Lấy source của ứng dụng về
$ sudo mkdir -p /var/www
$ sudo chown -R deploy:deploy /var/www
$ sudo chmod 775 /var/www/
$ cd /var/www
$ git clone abc@example.com:/great_app.git && cd great_app
# bundle install
$ gem install bundler -v 1.16.6 # check bundler's version in Gemfile.lock
$ bundle i
$ source ~/.bash_profile

# Phải generate mới credential cho Rail vì những file này bị ignore khỏi git repository
$ rm config/credentials.yml.enc
$ rm config/master.key
$ EDITOR=vim bin/rails credentials:edit

# Tạo file .env vì file này cơ bản cũng bị ignore khỏi git repository
$ touch .env
$ vi .env    # Cài đặt các biến môi trường bạn cần dùng

# Precompile
$ RAILS_ENV=production rails assets:precompile
# DB Migration
$ RAILS_ENV=production rails db:migrate

5. Cài đặt Puma chạy dạng service - chạy ở background và tự khởi chạy

# Đầu tiên cần tạo file cấu hình cho service puma
$ sudo touch /etc/systemd/system/puma.service
# Nội dung file puma.service tham khảo mình để ở dưới
$ sudo vi /etc/systemd/system/puma.service
$ sudo systemctl daemon-reload

# Cho phép chạy puma khi boot
$ sudo systemctl enable puma
# Khởi động puma
$ sudo systemctl restart puma

# Nếu cần khởi động puma bằng tay, chạy lệnh sau
$ RAILS_ENV=production /home/deploy/.rbenv/shims/puma -C /var/www/great_app/config/puma.rb

File puma.service tham khảo

[Unit]
Description=Puma HTTP Server
After=network.target

# Uncomment for socket activation (see below)
# Requires=puma.socket

[Service]
# Foreground process (do not use --daemon in ExecStart or config.rb)
Type=simple

# Preferably configure a non-privileged user
# User=

# The path to the your application code root directory.
# Also replace the "<YOUR_APP_PATH>" place holders below with this path.
# Example /home/username/myapp
WorkingDirectory=/var/www/great_app

# Helpful for debugging socket activation, etc.
# Environment=PUMA_DEBUG=1

# SystemD will not run puma even if it is in your path. You must specify
# an absolute URL to puma. For example /usr/local/bin/puma
# Alternatively, create a binstub with `bundle binstubs puma --path ./sbin` in the WorkingDirectory
Environment=RAILS_ENV=production
ExecStart=/home/deploy/.rbenv/shims/puma -C /var/www/great_app/config/puma.rb

Restart=always

[Install]
WantedBy=multi-user.target

6. Cài đặt Nginx

# cài nginx
$ sudo yum -y install nginx
# Nội dung file greatapp.nginx.conf tham khảo mình để ở dưới
$ sudo vi /etc/nginx/conf.d/greatapp.nginx.conf

# Cho phép chạy nginx khi boot
$ sudo systemctl enable nginx
# Khởi động nginx
$ sudo systemctl restart nginx
$ sudo chown -R nginx:nginx /var/lib/nginx/ # 1回権限エラーで困った為、このフォルダーの権限を変更しておく

# Thay đổi quyền cho log folders
$ sudo chown -R deploy:deploy /var/log/nginx
$ sudo chown -R deploy:deploy /var/log/nginx/*
$ sudo chown -R deploy:deploy /var/www/great_app/log/*

File greatapp.nginx.conf tham khảo

# log directory
error_log  /var/www/great_app/log/nginx.error.log;
access_log /var/www/great_app/log/nginx.access.log;

upstream app_server {
    # for UNIX domain socket setups
    server unix:///var/www/great_app/puma/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name great-app.com
                *.great-app.com;

    # path for static files
    root /var/www/great_app/public;

    # page cache loading
    try_files $uri/index.html $uri @app_server;

    location / {
        # If requested files exists serve them
        try_files $uri $uri @app;
    }

    location @app {
        # When nginx should return maintenance page?
        # - when tmp/maintenance.txt file exists
        if (-f $document_root/../tmp/maintenance.txt) {
            set $maint_mode 1;
        }
        if ($remote_addr = 127.0.0.1) {
            set $maint_mode 0;
        }
        if ($maint_mode) {
            return 503;
        }

        # HTTP headers
        proxy_pass http://app_server;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    # Maintenance page
    error_page 503 /maintenance.html;
    location /maintenance.html {
        if (!-f $request_filename) {
            rewrite ^(.*)$ /maintenance.html break;
        }
    }

    # Error pages
    error_page 500 502 504 /500.html;
    location = /500.html {
        root /var/www/great_app/public;
    }
    error_page 404 /404.html;
    location = /404.html {
        root /var/www/great_app/public;
    }

    client_max_body_size 4G;
    keepalive_timeout 10;
}

7. Cài đặt log rotation

Log của nginx, puma, rails theo thời gian sẽ tăng dần do đó cài đặt để rotation log là thói quen chúng ta nên làm.

# Nội dung file greatapp.logrotate.conf tham khảo mình để ở dưới
$ sudo vi /etc/logrotate.d/greatapp.logrotate.conf
$ mkdir /var/log/nginx/old/
$ mkdir /var/www/switch_bike/src/log/old/

File greatapp.logrotate.conf tham khảo

# Nginx log
/var/log/nginx/*.log
{
    missingok
    weekly       # rotate hàng tuần
    rotate 12    # keep đến 12 bản log gần đây nhất
    dateext
    dateformat -%Y%m%d
}

# Nginx logs
/var/log/nginx/*.log
{
    daily                     # rotate hàng ngày
    rotate 60                 # keep đến 12 bản log gần đây nhất
    olddir /var/log/nginx/old # log rotated để trong thư mục old/
    copytruncate              # chỉ truncate file log chứ ko move/tạo mới file log khi rotate
                              # nếu log luôn bị append (ví dụ log của nginx chẳng hạn) thì nếu không có option này
                              # sau khi rotate log, nginx sẽ không ghi được log vào file log mới nữa đó! cần chú ý!
    nocompress
    dateext
    dateformat -%Y%m%d
    missingok
    su deploy deploy
}

# Application logs
/var/www/switch_bike/src/log/*.log
{
    daily
    rotate 60
    olddir /var/www/switch_bike/src/log/old
    copytruncate
    nocompress
    dateext
    dateformat -%Y%m%d
    missingok
    su deploy deploy
}

Share on:
comments powered by Disqus