RailsアプリをAWS EC2にデプロイする方法(つまづきそうなポイント)

#前提(絶対にやってはいけないこと)
**「.env」ファイルをgithubにあげない。**

過去、一度でも上げている場合の対処手順

1, 「.gitignore」ファイルに「.env」を追記
2, ファイルを残したまま、管理対象から除外。

```
# ファイルを残したまま、管理対象から除外。  
 git rm --cached .env
```

3, コミットして、gitにpush

#EC2のインスタンス生成
- リージョンを**東京**に変更
- railsアプリをデプロイする上で、事前サポートが充実している「Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type」を選択
※AMIとは、OSやアプリケーションなど、インスタンスの作成に必要なソフトウェアのパッケージのようなもの。バージョンは作成時に変わると思う。

- 「インスタンスの詳細の設定」：特に何もなければいじらない
- 「ストレージの追加」：通常サイズは8、ボリュームタイプは汎用になっているため、特に何もなければ、このまま何もいじらない
- 「タグの追加」：必須ではないが、設定しておくと後が楽。
Ex),「キー」に「name」、「値」に「（アプリ名）-production」
- セキュリティグループ作成：
ブラウザなどからアクセスするためのhttpと、コンソールからEC2にログインするためのsshへの通信を許可する必要がある。
<img width="1018" alt="スクリーンショット 2019-04-19 11.37.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114933/9b5bc99b-92bc-ab7e-c20f-4edd6ffbcae4.png">

#キーペアの生成と固定IPの割り振り
EC2インスタンスに対しては、公開鍵認証方式によるSSH接続を前提としている（githubとローカル環境で行っている接続方式と、考え方はほぼ一緒）

- まずは、EC2へログインするための手段として仮のキーであるキーペアをAWSで作成し、ダウンロード(秘密鍵)し、インスタンスの作成をクリック。
※ローカルからEC2に接続できていないと何もできないので、仮のキーを使う。「キーペア名」はなんでも良い。Ex),[アプリ名]-key
<img width="675" alt="スクリーンショット 2019-04-19 11.49.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114933/217575e3-fd52-a23c-7b60-6c9fab465181.png">
- 固定IPの割り振り  
EC2ダッシュボードの「Elastic IP」をクリック。
「新しいアドレスの割り当て」「割り当て」をクリック。  
**固定IPアドレスはEC2インスタンスと関連付けなければ、課金対象になり料金が発生するため、すぐにEC2インスタンスと関連付ける。
※インスタンスを停止させる場合は[アドレスの解放]も実施しないと課金される。**  
  「アクション」の「アドレスの関連付け」で生成したインスタンスと関連付けをする。

- ダウンロードしたキーペアへの読み取り権限付与

```command:キーペアのファイルがあるディレクトリーでコマンド実行
$ chmod 400 [アプリ名]-key.pem 
```
```command:EC2とSSH接続する(ユーザ名は「ec2-user」 （デフォルトのユーザ名）)
$ ssh -i キー名 ユーザ名@パブリック（ElasticIP）IP

# Ex), ssh -i "sample-key.pem" ec2-user@51.168.225.224
```
「The authenticity of host '51.168.225.224 (51.168.225.224)' can't be established.」と表示されるが、コマンド要求時に「yes」と打てば、大丈夫。

#EC2インスタンス初期設定

```command:管理者権限の切り替え
$ sudo su -
```
```
$ yum update -y
```
 Amazon LinuxはRedHat系のOSなのでyumでパッケージ管理をする。

```command:タイムゾーンをJST(日本時間)に変更
$ ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

```command:インスタンス再起動毎にタイムゾーンがUTCに戻らないようにする
$ vi /etc/sysconfig/clock
```
```vim:タイムゾーンを変更し、保存
ZONE="Asia/Tokyo"
UTC=false
```
```command:言語設定を変更
vi /etc/sysconfig/i18n
```

```command:日本語に変更し、保存
LANG=ja_JP.UTF-8 
```

```command:Linuxを、使用する国の環境に最適化
vi /etc/cloud/cloud.cfg 
```

```command:設定を追記し、保存
repo_upgrade: none

locale: ja_JP.UTF-8 
```

```command:swap領域の作成
$ dd if=/dev/zero of=/swapfile bs=1M count=1024
$ mkswap /swapfile
$ swapon /swapfile
$ vi /etc/fstab 
```
```command:設定を追記し、保存
/swapfile swap swap defaults 0 0 
```
swap:サーバ動作中で、物理メモリ(RAM)の容量が満杯になった時点で必要になるのがswap領域

- 新規ユーザーの作成  
デフォルトユーザであるec2-userで開発していくのはセキュリティ的に危険。
新しくユーザを作成し、今後はそのユーザで開発していく必要がある。

```command:任意のユーザー名・グループ名を設定(app,wheelを例に設定)
$ useradd app -G wheel
$ visudo
```
```command:毎度パスワードを要求されないように設定（先頭にくっついている#を削除）
 %wheel ALL=(ALL)    NOPASSWD: ALL 
```
#EC2へのSSH接続
- 公開鍵をローカルからEC２のインスタンスへコピー

```command:EC2で「exit」を２回実行しログアウトする
$ exit
```
```command:公開鍵を表示
cat ~/.ssh/id_rsa.pub
```

- 公開鍵をEC2上に設置
- EC2上のsshディレクトリ配下にauthorized_keysファイルを作成
- そのファイルの中に、公開鍵を保存。

```command:先と同じコマンドでEC2に接続
$ ssh -i キー名 ユーザ名@パブリック（ElasticIP）IP

# Ex), ssh -i "sample-key.pem" ec2-user@51.168.225.224
```
```command:EC2で実行(公開鍵をセットする)
$ sudo su app
# ホームディレクトリー配下でないと権限が持てない
$ cd ~
#sshディレクトリを作成（既に存在している場合は省略）
$ mkdir .ssh
#自分のみに実行権を付与
$ chmod 700 .ssh
$ cd .ssh
$ touch authorized_keys
#自分のみに編集権限を付与
$ chmod 600 authorized_keys
#authorized_keysファイルを開いて、公開鍵をセットし、保存の後、ローカルに戻るためにexitする
$ vi authorized_keys
```

```command:ローカルで実行し、EC2へSSH接続をする
$ cd ~/.ssh 
$ pwd #ディレクトリーを確認
$ ssh -i "pwdで確認したpath/id_rsa" [ユーザー名]@パブリック（ElasticIP）IP
```
接続が成功しているか確かめる意味でも、「date」コマンドで時刻を確認。


- crondの時刻のズレ修正のためにcrondを再起動

```command:crondを再起動し、EC２のインスタンスも再起動
$ sudo service crond restart
$ sudo reboot
```

```command:EC2に接続後、下記コマンドでデフォルトのユーザー名「ec2-user」を削除
$ sudo su -
$ userdel ec2-user 
```

#EC2にgit/rbenv/ruby-build/ruby/bundlerをインストール
```command:EC2に接続して下記を実行し、gitがインストール
$ sudo su -
$ yum -y install git
$ git --version
```

```command:システムワイドな配置を意識し、ディレクトリー指定する
$ git clone https://github.com/rbenv/rbenv.git /usr/local/rbenv 
```
```command:rbenv利用のための環境変数を設定
vi /etc/profile.d/rbenv.sh
```
```command:パスを通す処理を記述し、保存
export RBENV_ROOT=/usr/local/rbenv
export PATH="$RBENV_ROOT/bin:$PATH"
eval "$(rbenv init -)" 
```
**パス(PATH)を通すとは?
→環境変数PATHにコマンド検索パスを追加する作業**
つまりは、/usr/local配下にあるrbenvにPATHを通し、システム上からPATH指定がなくてもコマンドが実行できるように設定している。

```command:正しく読み込ませるために、sourceコマンドを実行
$ source /etc/profile.d/rbenv.sh
　# バージョンが出力されるか、念のため確認
$ rbenv --version
```

```command:ruby-buildのインストール
git clone https://github.com/rbenv/ruby-build.git /usr/local/rbenv/plugins/ruby-build 
```
ruby-buildとは？
rbenv installコマンドを実行するためのrbenvのプラグイン

```command:インストールできたか、念のため確認
$ rbenv install -list
```

```command:Rubyをインストールするために必要なパッケージ群をインストール
$ yum -y groupinstall "Development Tools"

$ yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel mysql-devel readline-devel

$ yum -y install ImageMagick ImageMagick-devel
```
```command:gccのダウングレード
$ yum install gcc44
$ alternatives --set gcc /usr/bin/gcc44
```
AWSの環境内にはあるgcc(コンパイラツール)が、デフォルトのAWSでのバージョンがrbenvに非対応のため、ダウングレードする。

```command:rubyのインストール(ネット環境にもよるが5分くらいかかる)
$ rbenv install 2.6.2
$ rbenv versions #念のため、バージョンの確認
```
**デプロイするアプリのバージョンと合わせる**

```command:rootで設定(後に自身のユーザーでもバージョン指定のコマンドを実行する)
$ rbenv global 2.6.2
$ rbenv rehash
$ ruby -v #バージョンの確認
```
rbenv rehashコマンドの意味は？
それぞれのRuby環境にある各種コマンドが、ある決まった場所に登録され、実行可能にするため。

```command:自身のユーザーでrubyのバージョンを指定する
$ cd /usr/local/rbenv/
$ sudo chown app version
$ sudo chown app shims

$ rbenv global 2.6.2
$ rbenv rehash
$ ruby -v #バージョンの確認
```
```command:bundlerのインストール
$ sudo su - #rootユーザーに切り替え
$ rbenv exec gem install bundler
$ rbenv rehash

$ which bundler
　# /usr/local/rbenv/shims/bundlerが出力されれば成功
```
# データベース（PostgreSQL）の設定

```command:rootユーザー
$ yum -y install postgresql94 postgresql94-server postgresql94-devel postgresql94-contrib postgresql94-docs
```

```command:データベースの初期化
$ /etc/init.d/postgresql94 initdb
```
```command:データベースの起動
$ /etc/init.d/postgresql94 start
```

```command:listen_addressesのコメントを解除して、設を'localhost'から'*'に変更
$ vi /var/lib/pgsql94/data/postgresql.conf
```
<img width="604" alt="スクリーンショット 2019-04-19 15.31.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114933/00546191-3911-6e99-3a9b-e3e904157c34.png">

PostgreSQLの初期状態では、接続時に認証を必要としない設定になっているため、認証設定を編集する。

```command:下の画像のように、追記およびコメントアウトを実施
$ vi /var/lib/pgsql94/data/pg_hba.conf
```
<img width="540" alt="スクリーンショット 2019-04-19 15.38.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114933/bd58038b-066f-bf94-e3a3-02362e14f298.png">

```command:ファイルの編集を反映させるために再起動する
$ /etc/init.d/postgresql94 restart
```
- ユーザとデータベースを作成

```command:postgresというユーザー名で、PostgreSQLへ接続(DBを起動させた状態でコマンド実行する)
$ psql -U postgres
```

```command:管理者権限（SUPERUSER）を持った、”achieve”という名前のロールを作成
postgres=# CREATE ROLE "sample" WITH SUPERUSER LOGIN;
```
ROLEとは？
PostgreSQLではロールという概念を使ってデータベース上の権限管理をしている。

```command:”achieve_production”の名前でデータベースを作成
postgres=# CREATE DATABASE "sample_production";
```
ロールとデータベースを作成したら「\q」で接続解除
<img width="444" alt="スクリーンショット 2019-04-19 15.50.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114933/11b3b576-caf8-f227-6d48-e260d4b27f0c.png">

**ユーザー名、データベース名はconfig/database.ymlのファイルの記載内容と合わせる**

```command:ここでDBを再起動させる
$ /etc/init.d/postgresql94 restart
```

#Nginxの設定
```command:rootユーザーでNginxをインストール
$ yum -y install nginx
```
```command:EC2のOSが起動するのと同時に、Nginxも自動起動させる設定をし、起動させる。
$ chkconfig nginx on
$ /etc/init.d/nginx start
```

# デプロイツール（Capistrano）のインストール
Capistranoを使ったデプロイまでの流れ
１、Capistranoのコマンドを使ってEC2に対しデプロイ命令。
２、EC2からGithubに対し必要なコードを要求。
３、GithubからEC2へgit cloneが実行。
４、デプロイが実行。

まずは、EC2上に秘密鍵を、Github上に公開鍵を設置
（基本、命令元に秘密鍵。命令先に公開鍵を設置する）

```command:EC2でキーの作成
$ sudo su app 　　　　 # ユーザーの切り替え
$ cd 　　　　　　　　　　　# ホームディレクトリに移動
$ ssh-keygen -t rsa 　 　# 公開鍵秘密鍵のペアを作成
```

```command:公開鍵を表示
$ cat ~/.ssh/id_rsa.pub
```

```command:アプリ公開用ディレクトリを作成
$ cd /var
$ sudo mkdir www
$ sudo chown -R app:app www  #作成したwwwディレクトリの所有ユーザをユーザー自身に変更
$ cd /var/www
```

```ruby:gemfileを追加
  gem 'dotenv-rails' # 開発環境で環境変数を操作するのに必要
  gem 'unicorn'  
  gem 'mini_racer', platforms: :ruby # デプロイ時に必要

  group :development, :test do
    gem 'capistrano', '3.6.0' # capistranoのツール一式
    gem 'capistrano-bundler'
    gem 'capistrano-rails'
    gem 'capistrano-rbenv'
    gem 'capistrano3-unicorn'
    # 以下省略
  end
```
gemを追加しbundle installする。
※必要に応じてbundle updateする

- ローカルでCapfileを作成、編集する

```command:Capfileの作成
$ bundle exec cap install
```

```ruby:/Capfileに追記
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'
```

デプロイ先のサーバ情報、アクセスキー情報を設定


```command:/config/deploy/production.rb　サーバー情報の設定
server 'EC2のIPアドレス', user: 'app', roles: %w{app db web}
set :ssh_options, keys: 'ローカルの秘密鍵へのpath'
```
ローカルの秘密鍵のディレクトリーpathは、だいたい
/Users/ユーザー名/.ssh/id_rsa　にある。


```ruby:/config/deploy.rb　アクセス情報の設定  
# config valid only for current version of Capistrano
lock '3.6.0'

# デプロイするアプリケーション名
set :application, 'sample'

# cloneするgitのレポジトリ（xxxxxxxx：ユーザ名、yyyyyyyy：アプリケーション名）
set :repo_url, 'https://github.com/xxxxxxxxx/yyyyyyyyy'

# deployするブランチ。デフォルトはmasterなのでなくても可。
set :branch, ENV['BRANCH'] || 'master'

# deploy先のディレクトリ。
set :deploy_to, '/var/www/sample'

# シンボリックリンクをはるフォルダ・ファイル
set :linked_files, %w{.env config/secrets.yml}
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets public/uploads}

# 保持するバージョンの個数(※後述)
set :keep_releases, 5

# Rubyのバージョン
set :rbenv_ruby, '2.5.1'
set :rbenv_type, :system

#出力するログのレベル。エラーログを詳細に見たい場合は :debug に設定する。
#本番環境用のものであれば、 :info程度が普通。ただし挙動をしっかり確認したいのであれば :debug に設定する。
set :log_level, :info

namespace :deploy do
  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end

  desc 'Create database'
  task :db_create do
    on roles(:db) do |host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end

  desc 'Run seed'
  task :seed do
    on roles(:app) do
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:seed'
        end
      end
    end
  end

  after :publishing, :restart

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
    end
  end
end
```
config直下にunicornディレクトリ作成し、直下にproduction.rbを作成

```command:/config/unicorn/production.rb
$worker  = 2
$timeout = 30
#自分のアプリケーション名（currentがつくことに注意）
$app_dir = "/var/www/アプリケーション名/current"
$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

# 上記で設定したものが適応されるよう定義
worker_processes  $worker
working_directory $app_dir
stderr_path $std_log
stdout_path $std_log
timeout $timeout
listen  $listen
pid $pid

preload_app true

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_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end
```
もし、background-imageを設定していたらのurlをimage-urlに書き換える

- Nginxの設定ファイルを新規作成

```command:EC2側で実行
$ sudo vi /etc/nginx/conf.d/アプリケーション名.conf
```
```command:アプリ名、IPアドレスを変更して保存
upstream unicorn {
    server unix:/var/www/アプリケーション名/shared/tmp/sockets/unicorn.sock;
}

server {
        listen       80;
        server_name  EC2のIPアドレス;


        root /var/www/アプリケーション名/shared/public;

            access_log /var/log/nginx/アプリケーション名_access.log;
            error_log /var/log/nginx/アプリケーション名_error.log;

            location ~ ^/assets/ {
            }

            location / {
                    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_pass http://unicorn;
            }
    }
```
```command:Nginxを再起動
$ sudo nginx -s reload
```

```command:ローカルで実行し、デプロイ前のチェック
$ bundle exec cap production deploy:check
```
多分、gem追加してね、ってエラーがでる。

```ruby:Gemfileに以下を追加
gem 'bcrypt', '~> 3.1.7'

gem 'ed25519'
gem 'bcrypt_pbkdf'
```

※.envファイルに関するエラーは後で、実装するのでここでは無視。

- 環境変数を登録

API情報など、公開すべきではない情報をコードと分離させるために、EC2上にsecrets.ymlというファイルを設定

```command:ローカルで実行　まずは乱数を生成
$ bundle exec rake secret
```
```command:EC2で自身のユーザーで実行
$ cd /var/www/アプリケーション名/shared/config
$ vi secrets.yml
```

```command:/var/www/achieve/shared/config/secrets.ymlに追記
production:
  secret_key_base: 生成した乱数をコピペ
```

```command:EC2上で実行　shared/.envファイルの作成と環境変数の設定
 $ cd /var/www/アプリケーション名/shared # /shared/config/ ではないので注意
$ vi .env
```
.envファイルに環境変数を記載する

```command:ローカルで実行しデプロイ
$ bundle exec cap production deploy
```
ここで多分、エラーがでる。

#追加で実装したこと

```command:EC2上の自身のユーザーで実行　sqlite3の依存関係を解決するパッケージをインストール
$ sudo yum install sqlite-devel
```
nodeをインストール
https://qiita.com/paranishian/items/bddaed7c3aacedb11967

画像が表示されない問題(解決法3 public/images 以下に画像を置くを使う)
https://qiita.com/wadako111/items/03bc00d914e62243a511

```css:画像の設定例
 background: image-url("space.jpg") repeat;
```
ドメインをNginxに設定変更する
https://qiita.com/ryuchan00/items/3e172fbfd1044e093c52

SSL証明書の設定方法
https://labs.mobingi.com/aws-certificate-manager-and-freenom/
https://recipe.kc-cloud.jp/archives/11084
https://qiita.com/iwaseasahi/items/1687426add4124899fe3

解除方法
https://dev.classmethod.jp/cloud/aws/mesoko-r53-cdn/

S3
https://cre8cre8.com/aws/https-s3-and-cloudfront.htm#hl2

Nginxで画像アップロードサイズを調整

```command:以下を追記(サイズは任意で設定)
client_max_body_size 20M;
```
<img width="497" alt="スクリーンショット 2019-05-29 10.30.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114933/9f3b59f2-a6e8-22b1-3f52-e54600e7cb0d.png">

```command:seedデータ生成のコマンド
bundle exec rails db:seed RAILS_ENV=production
```