changed 5 years ago
Linked with GitHub
tags: AWS

AWS & Nginx & Unicorn & Ruby on Rails

【注意】

  • AWSのサービスを利用するにはクレジットカード登録が必要です。
  • AWS は従量課金制です。
  • 今回はRDSは使用しません。
  • 本メモではアプリ名を「ec_site」としています。

【開発環境】

  • macOS(Catalina)
  • ruby : 2.6.3
  • rails : 5.2.4

全体の流れ

0. サンプルアプリの作成

1. VPCの作成

2. サブネットの作成

3. インターネットゲートウェイの作成

4. ルートテーブルの作成

5. セキュリティーグループの作成

6. EC2インスタンスの作成

7. Elastic IPの作成、紐付け

8. SSH通信によるインスタンスへのログイン

9. EC2インスタンスの環境構築

10. gitとの連携、アプリのクローン

11. master.keyの登録

12. MySQLの設定

13. Nginxの設定

14. Unicornの設定

0. サンプルアプリの作成

ステップ 1rails new の実行。

$ rails _5.2.4_ new ec_site
$ cd ec_site

ステップ 2HomesController の作成

$ rails g controller homes top

ステップ 3config/routes.rb の編集

root 'homes#top'

ステップ 4app/views/layouts/application.html.erb の編集

<nav>
  <div class="hamburger">
    <div class="line"></div>
    <div class="line"></div>
    <div class="line"></div>
  </div>
  <div class="top-link">
    <%= link_to "トップ", root_path %>
  </div>
  <ul class="nav-links">
    <li><%= link_to "商品一覧", products_path %></li>
    <li><%= link_to "新規登録" %></li>
    <li><%= link_to "ログイン" %></li>
  </ul>
</nav>

ステップ 5app/assets/application.css の編集

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  z-index: 0;
}

nav {
  height: 10vh;
  background: #5b78c7;
  display: flex;
}

.top-link {
  height: 100%;
  width: 25%;
}

.top-link a {
  line-height: 10vh;
  font-size: 16px;
  color: white;
  text-decoration: none;
  margin-left: 25%;
}

.nav-links {
  display: flex;
  list-style: none;
  width: 50%;
  height: 100%;
  justify-content: space-around;
  align-items: center;
  margin-left: auto;
}

.nav-links li a {
  color: white;
  text-decoration: none;
  font-size: 16px;
}

.nav-links li a:first-child {
  justify-content: right;
}

@media screen and (max-width: 768px) {
  .line {
    width: 30px;
    height: 3px;
    background: white;
    margin: 5px;
  }

  nav {
    position: relative;
  }

  .hamburger {
    position: absolute;
    cursor: pointer;
    right: 5%;
    top: 50%;
    transform: translate(-5%, -50%);
    z-index: 2;
  }

  .nav-links {
    position: fixed;
    background: #5b78c7;
    height: 100vh;
    width: 100%;
    flex-direction: column;
    clip-path: circle(100px at 90% -10%);
    -webkit-clip-path: circle(100px at 90% -10%);
    transition: all 1s ease-out;
    pointer-events: none;
    z-index: 1;
  }

  .nav-links.open {
    clip-path: circle(1000px at 90% -10%);
    -webkit-clip-path: circle(1000px at 90% -10%);
    pointer-events: all;
  }

  .landing {
    flex-direction: column;
  }

  .nav-links li a {
    font-size: 25px;
  }

  .nav-links li:nth-child(1) {
    transition: all 0.5s ease 0.2s;
  }

  .nav-links li:nth-child(2) {
    transition: all 0.5s ease 0.4s;
  }

  .nav-links li:nth-child(3) {
    transition: all 0.5s ease 0.6s;
  }

  li.fade {
    opacity: 1;
  }
}

ステップ 6app/javascripts/application.js の編集

document.addEventListener('turbolinks:load', () => {
  const hamburger = document.querySelector(".hamburger");
  const navLinks = document.querySelector(".nav-links");
  const links = document.querySelectorAll(".nav-links li");
  hamburger.addEventListener("click", () => {
    navLinks.classList.toggle("open");
    links.forEach(link => {
      link.classList.toggle("fade");
    });
  });
});

ステップ 7app/views/homes/top.html.erb の編集

<main>
  <section class="presentation">
    <div class="introduction">
      <div class="intro-text">
        <h1>Welcome to our shop!</h1>
        <p>
          the premire purveyor of the finest shoes in the world
        </p>
      </div>
      <div class="cta">
        <button class="cta-select">ログイン</button>
        <button class="cta-add">新規登録</button>
      </div>
    </div>
    <div class="cover">
      <%= image_tag "shoe.png" %>
    </div>
  </section>
</main>

ステップ 8app/assets/stylesheets/homes.scss の編集

.presentation {
  display: flex;
  width: 90%;
  margin: auto;
  min-height: 80vh;
  align-items: center;
}

.introduction {
  flex: 1;
}

.intro-text h1 {
  font-size: 44px;
  font-weight: 500;
  background: linear-gradient(to right, #494964, #6f6f89);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.intro-text p {
  margin-top: 5px;
  font-size: 22px;
  color: #585772;
}

.cta {
  padding: 50px 0px 0px 0px;
}

.cta-select {
  border: 2px solid #c36cbb;
  background: transparent;
  color: #c36cbb;
  width: 150px;
  height: 50px;
  cursor: pointer;
  font-size: 16px;
}

.cta-add {
  background: #c36cbb;
  width: 150px;
  height: 50px;
  cursor: pointer;
  font-size: 16px;
  border: none;
  color: white;
  margin: 30px 0px 0px 30px;
}

.cover {
  flex: 1;
  display: flex;
  justify-content: center;
  height: 60vh;
}

.cover img {
  height: 100%;
  animation: drop 1.5s ease;
}

.laptop-select {
  width: 15%;
  display: flex;
  justify-content: space-around;
  position: absolute;
  right: 20%;
}

@keyframes drop {
  0% {
    opacity: 0;
    transform: translateY(-80px);
  }
  100% {
    opacity: 1;
    transform: translateY(0px);
  }
}

@media screen and (max-width: 1024px) {
  .presentation {
    flex-direction: column;
  }
  .introduction {
    margin-top: 5vh;
    text-align: center;
  }
  .intro-text h1 {
    font-size: 30px;
  }
  .intro-text p {
    font-size: 18px;
  }
  .cta {
    padding: 10px 0px 0px 0px;
  }
  .cover img {
    height: 80%;
  }
}

ステップ 9app/assets/iamges 配下にshoe.png を配置。

ステップ 10: scaffold を使ってMVCを一気に作成

$ rails g scaffold product title image_id
$ rails db:migrate

app/assets/stylesheets/scaffolds.scss を削除

ステップ 11: refile の gem を追加

gem "refile", require: "refile/rails", github: 'manfe/refile'
gem "refile-mini_magick"
$ bundle

ステップ 12app/models/product.rb を編集

class Product < ApplicationRecord
  attachment :image
end

ステップ 13app/controllers/products_controller.rb を編集

def product_params
  params.require(:product).permit(:title, :image)
end

ステップ 14app/views/products/_form.html.erb を編集

<div class="field">
  <%= form.label :image_id %>
  <%= form.attachment_field :image %>
</div>

ステップ 15app/views/products/index.html.erb を編集

<tr>
  <td><%= product.title %></td>
  <td><%= attachment_image_tag product, :image, :fill, 100, 100, format: 'jpeg' %></td>
  <td><%= link_to 'Show', product %></td>
  <td><%= link_to 'Edit', edit_product_path(product) %></td>
  <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>

ステップ 16app/views/products/show.html.erb を編集

<p>
  <strong>Image:</strong>
  <%= attachment_image_tag @product, :image, :fill, 300, 300, format: 'jpeg' %>
</p>

ステップ 17: 「RuntimeError」を解消する

Refile.secret_key = 'xxxxxxxxxxx'config/initializers/application_controller_renderer.rbに貼り付ける。

$ rails s

ステップ 18: アプリが完成したらコミットして、GitHubにプッシュ。

GitHubのリンク(アプリ名は「ec_site」)

1. VPCの作成

キー
名前タグ VPCの識別子(VPC_for_ec_site)
IPv4 CIDR ブロック 10.0.0.0/16
IPv6 CIDR ブロック IPv6 CIDR ブロックなし
テナンシー デフォルト

2. サブネットの作成

今回はEC2インスタンスを配置するためサブネットを作成する必要がある。

キー
名前タグ サブネットの識別子(Subnet_for_ec_site)
VPC 先ほど作ったVPC
アベイラビリティーゾーン 指定なし
IPv4 CIDR ブロック 10.0.0.0/24

3. インターネットゲートウェイの作成

キー
名前タグ インターネットゲートウェイの識別子(Gateway_for_ec_site)

アクション→VPCにアタッチを選択

4. ルートテーブルの作成

キー
名前タグ ルートテーブルの識別子
VPC 先ほど作成したVPC

「作成したルートテーブル」を選択→「ルート」を選択→「ルートの編集」を選択

キー
送信先 0.0.0.0/0
ターゲット 先ほど作成したインターネットゲートウェイ

「作成したルートテーブル」を選択→「サブネット」を選択→「サブネットの関連付け」を選択

5. セキュリティグループの作成

キー
セキュリティグループ名 セキュリティグループの識別子(SecurityGroup_for_ec_site)
説明 This is a security group for ec_site
VPC 先ほど作成したVPC
タイプ プロトコル ポート範囲 ソース
SSH TCP 22 0.0.0.0/0
HTTP TCP 80 0.0.0.0/0

6. EC2インスタンスの作成

ステップ 1: Amazon マシンイメージ(AMI)

「Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type」(上から2番目)を選択。

ステップ 2: インスタンスタイプの選択

「t2.micro」(無料利用枠の対象)を選択。

ステップ 3: インスタンスの詳細の設定

キー
インスタンス数 1
購入のオプション チェックなし
ネットワーク 先ほど作成したVPC
サブネット 先ほど作成したサブネット(自動選択)
自動割り当てパブリックIP 有効化
配置グループ チェックなし
キャパシティーの予約 開く
ドメイン結合ディレクトリ ディレクトリなし
IAM ロール なし
シャットダウン動作 停止
停止 - 休止動作 チェックなし
削除保護の有効化 チェックなし
モニタリング チェックなし
テナンシー 共有-共有ハードウェアインスタンスの実行
Elastic Inference チェックなし
Credit specification チェックなし

ステップ 4: ストレージの追加

キー
サイズ(Gib) 8
ボリュームタイプ 汎用 SSD(gp2)
終了時に削除 チェックあり

ステップ 5: タグの追加

キー
Name インスタンスの識別子(ec_site-instance)

ステップ 6: セキュリティグループの設定

キー
セキュリティグループの割り当て 既存のセキュリティグループを選択する
セキュリティグループID 先ほど作成したセキュリティグループのID

ステップ 7: キーペアのダウンロード

「新しいキーペアの作成」を選択

キーペア名は「ec_site」

ダウンロード実行。

7. Elastic IPの作成、紐付け

  1. 「Elastic IP アドレスの割り当て」をクリック。
  2. 「割り当て」をクリック。
  3. 先ほどのIPアドレスにチェックを入れて選択し、「アクション」メニューの「Elastic IP アドレスの関連付け」をクリック。
  4. 先ほど作成したインスタンスを選択。

8. SSH通信によるインスタンスへのログイン

EC2を作成した際にダウンロードした公開鍵を.sshフォルダに移動。

$ mv Downloads/ec_site.pem .ssh/

EC2にログイン。

$ cd .ssh
$ ssh -i ec_site.pem ec2-user@52.23.9.110
The authenticity of host '52.23.9.110 (52.23.9.110)' can't be established.
ECDSA key fingerprint is SHA256:ItuuWAj8wINtxAf0vEB7ywXb32olRZWugMcBJjizS0Q.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '52.23.9.110' (ECDSA) to the list of known hosts.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/muses/.ssh/ec_site.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/Users/muses/.ssh/ec_site.pem": bad permissions
ec2-user@52.23.9.110: Permission denied (publickey).

上記のメッセージが出力されるはず。権限の変更を行う。

$ sudo chmod 600 ~/.ssh/ec_site.pem

上記コマンドにより

-rw-r--r--@ 1 muses staff 1704 Sep 5 01:38 ec_site.pem

-rw-------@ 1 muses staff 1704 Sep 5 01:38 ec_site.pem

に変更される。

chmod コマンドに関してはこちらの記事を参照するといいと思います。

もう一度ログイン実行。

$ ssh -i ec_site.pem ec2-user@52.23.9.110
Last login: Fri Sep 25 09:05:21 2020 from kd124212047254.ppp-bb.dion.ne.jp

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
No packages needed for security; 5 packages available
Run "sudo yum update" to apply all updates.

上記が表示されていればログイン成功。

9. EC2インスタンスの環境構築

必要なパッケージのインストール

$ sudo yum update
$ sudo yum -y install git make gcc-c++ patch openssl-devel libyaml-devel libffi-devel libicu-devel libxml2 libxslt libxml2-devel libxslt-devel zlib-devel readline-devel mysql mysql-server mysql-devel ImageMagick ImageMagick-devel epel-release sqlite sqlite-devel

rbenvのインストール

$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
$ sudo ~/.rbenv/plugins/ruby-build/install.sh
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

rubyのインストール(バージョンは 2.6.3

$ rbenv install 2.6.3
$ rbenv global 2.6.3
$ rbenv rehash

node.js のインストール

$ curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
$ sudo yum install -y nodejs

rails のインストール(バージョンは 5.2.4

$ gem install rails -v 5.2.4

ローカルでBundlerのバージョンを確認

$ bundler -v

EC2内でbundlerをインストール。

gem install bundler -v

10. gitとの連携、アプリのクローン

GitHubとの連携

$ cd ~/.ssh
$ ssh-keygen -t rsa
$ cat id_rsa.pub
$ ssh -T git@github.com

クローン

アプリのためのディレクトリを作成する。

$ cd /
$ sudo chown ec2-user var
$ cd var
$ sudo mkdir www
$ sudo chown ec2-user www
$ cd www
$ sudo mkdir rails
$ sudo chown ec2-user rails

権限の変更。これを行わないと、GitHubからのクローンができない。

アプリをクローンするディレクトリに移動

$ cd rails
$ git clone git@github.com:Farstep/ec_site.git
$ cd ec_site
$ bundle

11. master.keyの登録

ローカル環境においてマスターキーを確認。

$ vi config/master.key

master.key をコピーしておく。

EC2にログインしたあと

$ vi config/master.key

を実行し、master.key を登録。

credentials.ymlの設定

$ EDITOR=vim bin/rails credentials:edit
db:
  database: ec_site
  username: root
  password: xxxxxx
  socket: /var/lib/mysql/mysql.sock

passwordの値は任意。

12. MySQLの設定

MySQLを起動させる。

$ sudo service mysqld start

パスワードの設定。(先ほど設定したpasswordを使用。)

$ sudo /usr/libexec/mysql55/mysqladmin -u root password 'xxxxxx'

MySQLへ接続確認。先ほど設定したパスワードを入力してmysqlに接続できればOK。

$ mysql -u root -p

control+d で抜ける。

config/database.yml の編集。

$ vi config/database.yml
production:
  <<: *default
  database: <%= Rails.application.credentials.db[:database] %>
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  socket: <%= Rails.application.credentials.db[:socket] %>

13. nginxの設定

$ sudo yum -y install nginx
$ sudo vi /etc/nginx/conf.d/ec_site.conf
error_log  /var/www/rails/ec_site/log/nginx.error.log;
access_log /var/www/rails/ec_site/log/nginx.access.log;

upstream unicorn_server {
    server unix:/var/www/rails/ec_site/tmp/sockets/.unicorn.sock fail_timeout=0;
}

server {
    listen 80;
    client_max_body_size 4G;
    server_name 52.23.9.110;

    keepalive_timeout 5;

    # Location of our static files
    root /var/www/rails/ec_site/public;

    location ~ ^/assets/ {
        root /var/www/rails/ec_site/public;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://unicorn_server;
            break;
        }
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/rails/ec_site/public;
    }
}

「ec_site」の部分は自分のアプリの名前に変更。

権限の変更。

$ cd /etc

現在

drwxr-xr-x  4 root root    4096 Sep 17 04:01 nginx
$ sudo chown -R ec2-user nginx

drwxr-xr-x  4 ec2-user root    4096 Sep 17 04:01 nginx

14. Unicornの設定

Gemfileに以下を記述。

group :production, :staging do
  gem 'unicorn'
end

インストール実行。

$ bundle install
$ vi config/unicorn.conf.rb
$worker  = 2
$timeout = 30
$app_dir = "/var/www/rails/ec_site"
$listen  = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir
$pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
$std_log = File.expand_path 'log/unicorn.log', $app_dir
# set config
worker_processes  $worker
working_directory $app_dir
stderr_path $std_log
stdout_path $std_log
timeout $timeout
listen  $listen
pid $pid
# loading booster
preload_app true
# before starting processes
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end
# after finishing processes
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

「ec_site」の部分は自分のアプリの名前に変更。

マイグレーションの実行。

$ bundle exec rake db:migrate RAILS_ENV=production

assets コンパイル実行。

$ bundle exec rake assets:precompile RAILS_ENV=production
Uglifier::Error: Unexpected token: punc ()). To use ES6 syntax, harmony mode must be enabled with Uglifier.new(:harmony => true).

というエラーが吐かれる。

$ vi config/environments/production.rb
config.assets.js_compressor = Uglifier.new(harmony: true)

EC2の再起動

Nginxの起動。

$ sudo service nginx start

Unicornの起動

$ bundle exec unicorn_rails -c /var/www/rails/ec_site/config/unicorn.conf.rb -D -E production

Unicornの起動の確認

$ ps -ef | grep unicorn | grep -v grep

デバック

Nginxのエラー

$ sudo tail -f /var/log/nginx/error.log

Unicornのエラー

$ sudo tail -f log/unicorn.log

Railsのエラー

$ sudo tail -f log/production.log

参考

Select a repo