実践で学ぶRuby on rails 〜仮説千本ノック〜

プログラマーとして独立するため日々スキルアップに励んでいます。優れたプログラマは仮説を立てるのがうまい。そこを目指して仮説を立てては検証する日々です!!

gemのバージョン指定ってどんな時に必要なの?


はじめに

gemfileでインストールするgemを記述する際、バージョンを指定することがありますが、その結果何が起こるのか、どういう時にバージョンを指定すべきなのか、いまいちピンとこなかったので調べてみました。

# 5.2.3以上、6未満のものしかインストールできない。 '~>5.2.3', '<6.0.0'と意味は同じ。
gem 'rails', '~> 5.2.3'

# 0.4.4以上、.0.6.0未満のものしかインストールできない。
gem 'mysql2', '>= 0.4.4', '< 0.6.0'

# 1.3.0以上のものしかインストールできない(以上あればなんでもいい)
gem 'uglifier', '>= 1.3.0'

まとめ

Aというアプリがあって、このアプリが、0.4.4以上、.0.6.0未満のバージョンのmysql2に対応するよう設計していたとしましょう。 この場合、gemfileにバージョン指定せず、単にmysqlとだけ書くと、bundle install した際には、現在公開されている最新バージョンがインストールされてしまい、エラーが起きてしまう可能性があります。

このため、バージョンによってはエラーが生じる可能性のあるgemは、問題が起きないと確信がもてるバージョンしかインストールできないよう制限をかけているのです。

Gemfileで環境ごとにgemを指定する

はじめに

gemは、development環境だけで使いたいものもあれば、debelopmentはもちろんtestでも、productionでも全部の環境で使いたい場合もあります。 ここでは、環境ごとにgemを使い分けるための方法を紹介します。

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.1'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.3'
# Use mysql as the database for Active Record
gem 'mysql2', '0.5.3'
# Use Puma as the app server
gem 'puma', '~> 3.11'
# Use SCSS for stylesheets
gem 'sass-rails', '5.0.7'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'mini_racer', platforms: :ruby

# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of chromedriver to run system tests with Chrome
  gem 'chromedriver-helper'
end

group :production do
  gem 'unicorn', '5.4.1'
  gem 'mini_racer', platforms: :ruby
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

まとめ

gemfileの構成はgroup~endで囲まれているパート、それ以外、以上二種類のパートから構成されています。

developmentでのみ使いたいgemはgroup :development do ~ endで囲えばOKです。

では、全ての環境で使いたいgemはどこに書けばいいのかというと、group~endで囲っていない部分に書いてください。

group~endの上でも、下でもどっちに書いても構いません。

本番環境secret_key_base生成時のエラー

railsの本番環境にて、

Cokkieの暗号化に用いる文字列secret_key_baseを作成します。

 

作成コマンドを実行

[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rake secret

 

成功時には、secret_key_baseの文字列が表示されるのだが、 

 エラーが発生

 

cound not find a Javascript runtime

See https://github.com/rails/execjs for a list of available runtimes.

エラー文によると、問題は、Javascriptを実行するために必要なもの(runtime)が見つからないため、githubをみて、必要なruntimeを確認せよとのこと。

githubを確認してみる。

ExecJsとは、gemの一種で、数種類runtimeをサポートしているとのこと。

その中から適切なものをインストールしようと考えたところ、説明文を読んでも、ネットで調べてもどれが、自分の環境にあったruntimeなのか、判別ができない。(うちの一つtherubyracerの後継がmini_racerのようです)

さらに解決法を検索したところ、
mini_racerというruntimeをインストールしてエラー解消した事例を発見。

そこで、ローカルで、gemfileの本番環境の部分にmini_racerを追加し、bundleし、pushした。その後、EC2において、先ほどpushした内容をgit pullしたところエラー解決した。

どうやら、mini_racerとは、javascriptのコードを実行するためのJavascriptエンジンであるV8と一種であるとのこと。

mini_racerがruntimeなのかは明記されてないが、少なくとも、javascriptのコードを実行するという役割は共通している。

ECサイト作成〜はじめに〜

RailsECサイトの開発するにあたり、

まずは、サイトはどう使う(操作する)ことを想定しているのか整理します。

この作業を通じて、何(DB、コントローラ、アクション、ページ)を作る必要があるのか、明確にしていきましょう。

 

 

〇基本的な機能

・ユーザー登録

・商品の出品

・商品の購入

 

〇追加機能(購入を促す機能)

・カテゴリ別一覧表示

・ブランド別一覧表示

・入力したキーワード該当商品の一覧表示(商品名等)

・選択した価格帯商品の一覧表示

・ユーザー評価機能(高い評価を受けているユーザーの商品は買いやすい)

 

 

〇各機能の詳細

続いて、サイトをどのように使うのか、詳細に表現することで、具体的に何をどう作れば良いのか、明らかにしていきます。

 

どのページにおいて、どんなデータを、どのタイミングで、どう処理し、処理後、どのページに移動するのか意識して、箇条書きにしてみましょう。

 

①どんなデータを扱うのか

作るべきテーブルが明らかになります。

作るべきコントローラーが明らかになります。

  

②データを「どう処理する」のか

使うべきアクションが明らかになります。

保存、保存したものの表示(←アクションではない)、更新、削除、いずれかになります。

 

③どのページにおいて・どのページに移動するか

作るべきページが明らかになります。

 

④どのタイミングで(「」内)

データの処理、ページ移動をどのタイミングで行われるよう設定するのかが明らかになります。 

〜の場合、についても、タイミングとして分類できる。

 

列挙した機能を見るとわかるように、

アプリ作成作業は、①〜④の要素を作成(設定)する作業によって構成される。

 

想定する機能

・ユーザー登録

▫️ユーザー新規登録ページにて、ユーザーに関する情報をフォームに入力、「登録ボタンを押すと」DBに保存され登録完了ページが表示される。

▫️一度DBに保存したユーザーは、ログイン画面にて、登録ずみのユーザーに関する情報の一部を入力し、「ログインボタンボタンを押すと」、ログインし、「その後」、トップページへ移動する。

▫️トップページにて、「ログアウトのリンクを押すと」、ログアウトし、「その後」、トップページへ移動する。

▫️トップページにおいて、ログイン中は、ログアウトのリンク、ログアウト中は、新規登録のリンクとログインのリンクが表示される。

 

 

▫️商品の出品

▫️出品ページにて、商品に関する情報をフォームに入力、「出品ボタンを押すと」、DBに保存され出品完了ページに移動する。

 

・商品情報の概要の表示

▫️一度DBに保存した商品に関する情報は、トップページでは、一部情報のみ表示できるようにする。

 

・商品詳細情報の表示

▫️トップページに表示される、「商品概要をクリック」すると、一度DBに保存した商品に関する情報を表示する詳細ページに移動する。

▫️ユーザーがログインしており、さらに、ログインユーザが出品者である場合に、詳細ページにおいて、編集リンクは表示される。

▫️ユーザーがログインしており、さらに、ログインユーザが出品者である場合に、詳細ページにおいて、削除リンクは表示される。

 

・商品情報の更新

▫️詳細ページに表示される「編集リンクを押すと」、編集画面に移動し、「更新ボタンを押すと」、商品に関する情報更新され、更新完了ページに移動する。

 

・商品情報の削除

▫️詳細ページに表示される「削除リンクを押すと」、削除しますか?ページが表示され、「削除実行リンクを押すと」、DBから商品に関する情報削除される。 

 

・商品の購入

▫️クレジットカード情報を登録ずみの場合、詳細ページに表示される、「購入リンクを押すと」、本当に購入しますか?ページに移動し、購入リンクを押すと、購入確定画面が表示される。

クレジットカードに関する情報を未登録の場合、登録画面へ移動し、クレジットカードに関する情報をフォームへ入力し、登録ボタンを押すと、(保存保留状態で)本当に購入しますか?ページに移動し、購入リンクを押すと、情報が保存され、購入確定画面が表示される。 

AWSを使ったデプロイ〜S3にファイルをアップロードする〜

S3にバケット(保存先)を用意する

データが保存される場所であるバケットを作成します。

名前とリージョンを決める必要がありますが、

名前は、バケットにアクセスするためのURLとして使われるため、一意である必要があります。

リージョンは、バケットが存在しているサーバーの場所となりますので、EC2と同じ考え方で選択して下さい。

なお、誰がS3にアクセスできるのか、バケットポリシーで設定しますが、まずは、自分で作成したIAMユーザーからのみアクセス許可して、ARNを確認し、バケットポリシーに下記の通り記述しましょう。

{
    "Version": "2012-10-17",
    "Id": "Policy1544152951996",
    "Statement": [
        {
            "Sid": "Stmt1544152948221",
            "Effect": "Allow",
            "Principal": {
                "AWS": "IAMユーザーのARN"
            },
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::バケットの名前"
        }
    ]
}

〇Sid ポリシーを識別するID

〇Version ポリシーの記法を指定する。

〇Effect ここで設定しているポリシーが許可を与えるポリシー(ALLOW)なのか、許可しない(Deny)なのか設定

〇Action *によってS3に関する全ての権限を許可している

〇Resource  S3のうち、どのリソースに関する権限を許可するのか指定。(今回で言えば、一つのS3についてのみ権限を認める)

 

これで、保存先となるS3のバケットは準備できました。

この後の手順は次のようになります。

▫️ローカルのアプリの画像保存先を、アプリ内のpublicフォルダから、S3へ変更し、ローカルからS3へアップロードできるようにする。

 

▫️次に環境変数の設定を変えた上、アプリを本番環境にデプロイすることで、本番環境においても、保存先がS3となります。

 

なぜ、環境変数の設定を変える必要があるかというと、

S3を使用するには、環境変数が必要。そして環境変数は、OSが提供する変数のため、ローカル環境では、自分のPCのOS(.bash_profileファイル)、本番環境では、EC2インスタンスのOS(environmentファイル)がそれぞれ提供する。

そのため、本番環境でS3へ保存するなら、EC2インスタンス内environmentファイルにてで環境変数を定義する必要がある。

 

画像保存先をS3へ変更する

▫️ネット上(S3)にファイルをアップロードできるようにするためのgem「fog-aws」をインストール

開発環境でも本番でも使うので、Gemfileの一番下に記述して、bundleする。

group :development do
  # Access an IRB console on exception pages or by using <%= console %> in views
  gem 'web-console', '~> 2.0'

  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
end

gem 'carrierwave'
gem 'fog-aws'

 

▫️インストールしたfog-awsをアップロード時に使えるようにimage_uploaderを編集する。

class ImageUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick
  process resize_to_fit: [800, 800]

  storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

 

上の設定では、アップロード時にfogを使うよう指定しただけで、結局fogを使って「どこに」アップロードするのかの指定していない。

 

▫️ここで保存先を指定する。

config/initializers直下にcarrierwave.rbを作成して設定する。

idとkeyの値は環境変数に代入している値を呼び出します。

require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

CarrierWave.configure do |config|
  config.storage = :fog
  config.fog_provider = 'fog/aws'
  config.fog_credentials = {
    provider: 'AWS',
    aws_access_key_id: Rails.application.secrets.aws_access_key_id,
    aws_secret_access_key: Rails.application.secrets.aws_secret_access_key,
    region: 'ap-northeast-1'
  }

  config.fog_directory  = 'バケット名'
  config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/バケット名'
end

環境変数

大元のidとkeyの値は、ローカルにて環境変数に代入していますが、その環境変数をさらにsecrets.yamlにて、別の変数に代入し、上ではその変数を呼び出すことで、大元の値を取得している。

 

.gitignoreによってsecrets.yamlをアップロード対象から外す

すでに、この段階で、ローカルの開発環境でアップロードした画像はS3にアップロードされるはずです。

 

確認するには、ローカル環境で画像を登録してみましょう。

S3のコンソールでバケットの中身を確認し、成功していればuploadsというディレクトリが生成されており、その中に画像が保存されています。

 

S3への保存はできましたが、ここでセキュリティ対策を1つ講じましょう。

 

secrets.ymlには、パスワード系の記述がされるため、gitignoreに記述することによりgitの管理対象から外しましょう(pushしてもリモートリポジトリにアップロードされなくなる)

config/secrets.yml

 

ただし、gitignoreに記載する前に、すでにsecrets.ymlは一度gitの管理対象となっていますので、gitignoreに記述した後でいいので、対象か外すコマンドを実行しましょう。

$ cd ~/projects/アプリ名
# 単にrmだと削除だが、--cachedオプションにより、ファイルは残し、
 gitの管理対象から外すことができる。 $ git rm --cached config/secrets.yml

 

 

本番環境でも保存先がS3となるよう、環境変数を設定する。

ec2インスタンスにログインして、environmentファイルを編集する。(ローカルの.bash_pfofileで行ったのと同じ趣旨)

$ ssh -i [pem鍵の名前].pem ec2-user@[作成したEC2インスタンスと紐付けたElastic IP]
(ダウンロードした鍵を用いて、ec2-userとしてログイン)
$ sudo vim /etc/environment
# iを押してインサートモードに移行し、下記を追記する。既存の記述は消去しない。
AWS_ACCESS_KEY_ID='ここにCSVファイルのAccess key IDの値をコピー'
AWS_SECRET_ACCESS_KEY='ここにCSVファイルのにSecret access keyの値をコピー'
# 編集が終わったらescapeキーを押してから:wqと入力して保存して終了

 

environmentに加えた変更を反映させるため、一旦ログアウトして改めてログインしましょう。

正しく環境変数が設定されているかをenv | grepコマンドで確認しましょう。

# 編集した環境変数を適用するために一旦ログアウトします。
$ exit
$ ssh -i [pem鍵の名前].pem ec2-user@[作成したEC2インスタンスと紐付けたElastic IP]
# 環境変数が適用されているか確認しましょう。
$ env | grep AWS_SECRET_ACCESS_KEY
$ env | grep AWS_ACCESS_KEY_ID

 

さて、環境変数は大元の設定場所(.bash_profileやenvironment)から、secrets.yml、carrierwave.rbへと読み込まれていきます。

secrets.yamlには、本番環境における環境変数の設定を行っていませんので、次のように記述しましょう。

# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!

# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rails secret` to generate a secure secret key.

# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.

development:
  secret_key_base: cb2965bfebd75267542611a74ab612b9754f98・・・・・
  aws_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
  aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>

test:
  secret_key_base: 7362cb8e960adf75f110e17bb4cd1f2d4edc3d・・・・・

# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  aws_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
  aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>

 

これでうまく動きそうですが、実はそうではありません。

 

secrets.ymlの設定をしても、このファイルが現状、gitignoreに記述したことにより、Github経由でサーバーにアップロードされない状況にあるため、機能しないのです。

 

ポイントなのは、ローカル環境にあるsecrets.yamlを機能させるには、何らかの方法でサーバー環境と連携をさせたいが、

直接的にファイルをネット上にアップするにはリスクがあるので、gitignoreに記述して、リモートリポジトリには上がらないようにしつつ、capistranoのdeploy.rbにコードを書くことで、secretsの保存場所は変えずに、シンボリックリンクでサーバ上からローカルのsecrets.ymlを呼び出せるよう、deploy.rbを変更する必要があります。(deploy.rbへの変更はgithub経由でサーバにあげたいので、pushします)

 

deploy.rbを次のように編集しましょう

set :linked_files, %w{ config/secrets.yml }

# 元々記述されていた after 「'deploy:publishing', 'deploy:restart'」以下を削除して、次のように書き換え

after 'deploy:publishing', 'deploy:restart'

#deployした時、restartさせる。 namespace :deploy do task :restart do invoke 'unicorn:restart' end
#サーバーにshared/configディレクトリがないなら作成し、ローカルにあるsecrets.yamlをshared/condigフォルダにアップロードするという、uploadタスクの定義。 desc 'upload secrets.yml' task :upload do on roles(:app) do |host| if test "[ ! -d #{shared_path}/config ]" execute "mkdir -p #{shared_path}/config" end upload!('config/secrets.yml', "#{shared_path}/config/secrets.yml") end end before :starting, 'deploy:upload' after :finishing, 'deploy:cleanup' end

〇linked_filesはシンボリックリンクの設定。

大元のsecrets.yamlはローカルにあるが、サーバーのshared/config/secrets.ymlに、ローカルのconfig_secrets.ymlへのシンボリックリンクを貼って連携させる。

 

ここまでで、いつくかローカルで設定ファイルを編集しているのでpushした上で、自動デプロイを実行しましょう。

$ bundle exec cap production deploy

 

 

 

 

 

 

 

 

 

 

 

 

 

 

AWSを使ったデプロイ〜S3を使用するための準備〜

S3を利用する際のセキュリティ対策

従量制サービスであるS3は悪意のあるユーザーに操作されることで高額請求される可能性があり、パスワードを漏洩しない、したとしてもパスワードだけでは操作できないようにする等の工夫が必要です。

 

今回は、下記の三つの対策について学びます。

 

▫️ログイン時の2段階認証

AWSへログインする際、(ルートにしてもIAMにしても)パスワードに加えて、Authyのパスワード入力も求める様にしましょう。

Authyのパスワードは刻一刻と変化するので、漏洩する可能性は低いです。

 

・携帯などへのAuthyのインストール

AWSへの2段階認証設定

AWSの多要素認証(MFA)の設定画面からQRコードを表示し、それを携帯のAUthyで読み取ることによって、AWSログイン時に、Authyのワンタイムパスワードの入力が必須となる。

 

▫️IAMユーザーの利用

ルートユーザーは権限が強く、認証情報が漏れて悪用された際のリスクが高いので、権限を限定したIAMユーザーで普段の作業を行う様にしましょう(漏れた時のリスクが抑えられる)

AWSではIAMというサービスで、IAMユーザーを作成できます。

 

こちらも、作成したのちに、Authyによる2段階認証を設定しましょう。

 

▫️リモートリポジトリ(Github)へのパスワードのアップを防ぐ

ソースコードと一緒に、誤ってパスワードをGithubにアップしてしまうと、GIthubには誰でもアクセスできるので、悪用される恐れがあります。

そこで、pushする際に、コードを精査し、パスワードらしきものがあったら、処理を中断してくれるgit-secretesを導入しましょう。

 

git-secretsを、ローカル環境で、Homebrewを経由してインストール

$ cd ~/
$ brew install git-secrets

 

続いて、アプリのディレクトリに移動して、git-secretsを有効化する。

$ cd chatspace
$ git secrets --install

 

続いて、アプリのディレクトリにいる状態で、どの様なコードのコミットを防ぐのか、設定しましょう。

 

このコマンドにより、aws関連の重要情報をチェックする様に登録(register)できます

$ git secrets --register-aws --global

 

現在のgit-secretsの設定状況を確認して見ましょう。

$ git secrets --list

secrets.providers git secrets --aws-provider
secrets.patterns [A-Z0-9]{20}
secrets.patterns ("|')?(AWS|aws|Aws)?_?(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)("|')?\s*(:|=>|=)\s*("|')?[A-Za-z0-9/\+=]{40}("|')?
secrets.patterns ("|')?(AWS|aws|Aws)?_?(ACCOUNT|account|Account)_?(ID|id|Id)?("|')?\s*(:|=>|=)\s*("|')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}("|')?
secrets.allowed AKIAIOSFODNN7EXAMPLE
secrets.allowed wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

 

これで、一つのアプリのリポジトリについての設定は終わりましたが、アプリを作るたびに設定をするのは煩雑なので、今後作られるリポジトリに自動的に有効化するコマンドを二つ実行しておきましょう。

$ git secrets --install ~/.git-templates/git-secrets
$ git config --global init.templatedir '~/.git-templates/git-secrets'

 

なお、Githubdesktopによるpushの際にgit-secretsを効かせたいなら、別途コマンドを実行する必要があります。

 

大前提として、githubDesktopがアプリケーションフォルダに格納されている必要があるので、違う場合は移動しておきましょう。

 

git-secetsをどこかのフォルダにコピーしてルーティングっぽいのですが、詳細は不明。

$ sudo cp /usr/local/bin/git-secrets /Applications/GitHub\ Desktop.app/Contents/Resources/app/git/bin/git-secrets

 なお、「No such file or directory」エラーが出た場合GIthubdesktopのバージョンが古いことが原因のため、次のコマンドを実行します。

$ sudo cp /usr/local/bin/git-secrets /Applications/GitHub\ Desktop.app/Contents/Resources/git/bin/git-secrets

 

 

 

 

 

 

 

 

AWSを使ったデプロイ〜Capistranoによる自動デプロイ〜

デプロイするには、以下のような作業が必要で、様々なコマンドを実行する必要がありますが、一度Capistranoによる自動デプロイの設定を行えば、ローカルにおいて、コマンドを一つ実行すればすべての作業が自動で実行できます。

▫️ローカルからリモートリポジトリへのpush

▫️(SSH接続した上で)EC2において実行するリモートリポジトリからのpull

▫️EC2におけるアセットコンパイル

▫️EC2におけるunicorn再起動

 

 

Capistranoをインストール

ローカルにて、Gemfileの開発環境にCapistrano関連のgem群をインストール

group :development, :test do
  gem 'capistrano'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano3-unicorn'
end

 

bundle installしたら、引き続き、ローカルにて、このコマンドを実行するとCapstrano関連のファイルが生成される。

bundle exec cap install

 

ライブラリのうちどれを読み混むのかCapfileで設定

Capsitranoは、いくつかのライブラリを読み込んで動作し、

どのライブラリを読み込むかはCapfileで設定します。

require "capistrano/setup"
require "capistrano/deploy"
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

 ※Dir.glob(tasks/*.rake)=tasksディレクトリにある.rakeファイルを、eachで人ずつimportする的な意味。ファイルがたくさんあっても、順番にすべて処理してくれる。

 

デプロイに関する設定を行う

先ほどのcap install小窓では、デプロイに関するCapstranoの設定を記述するファイル、production.rbとstaging.rbの二つが生成されている。

 

本番環境固有の設定を行うにあたっては、production.rbに次のように記述してください。

 

なお、production.rbはconig/environment/production.rbとconfig/deploy/production.rbの二つがあるが、自動デプロイの設定は後者に記述する。

server '<用意したElastic IP>', user: 'ec2-user', roles: %w{app db web}

server = サーバーホスト名(Elastic IPのこと)

user = AWSサーバのログインユーザ名(今回はec2-user)

role = 複数サーバーがあるときに、一括で処理したい場合に役に立つ。例えば、サーバが10個あって、5個はrole: app、残りの5個別はrole:webとしていた場合、appに所属する5個に一括である処理を実行することなどができる。 一種のグルーピング。

 

production環境staging環境いずれにも共通の設定はconfig/deploy.rbに記述する。

# capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する
lock '〇.〇〇.〇'# capstranoのバージョンを記述

# Capistranoのログの表示に利用する
set :application, '<自身のアプリケーション名>'

# どのリポジトリからアプリをpullするかを指定する
set :repo_url,  'git@github.com:<Githubのユーザー名>/<レポジトリ名>.git'

# releasesに保存される様々なバージョンのソース、いずれからも共通で参照するディレクトリを指定
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')

set :rbenv_type, :user
set :rbenv_ruby, '<このアプリで使用しているrubyのバージョン>' #カリキュラム通りに進めた場合、2.5.1か2.3.1です

# どの公開鍵を利用してデプロイするか
set :ssh_options, auth_methods: ['publickey'],
                  keys: ['<ローカルPCのEC2インスタンスSSH鍵(pem)へのパス(例:~/.ssh/key_pem.pem)>'] 

# プロセス番号を記載したファイルの場所
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }

# Unicornの設定ファイルの場所
set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" }
# 自動デプロイ実行時に生成されるreleasesディレクトリ内にためておくソースの世代数(多すぎると容量消費すうるので注意) set :keep_releases, 5 # デプロイ処理が終わった後、Unicornを再起動するための記述 after 'deploy:publishing', 'deploy:restart' namespace :deploy do task :restart do invoke 'unicorn:restart' end end

 ▫️Capistranoのバージョン確認

gemfile.lockを確認。

▫️set: 名前, 値の記法

変数のようなもので、一度定義すれば、ファイル内で、名前を書けば、値を取り出すことができる。

▫️task :〇〇〇 do

capistranoでデプロイ実行時に行われるタスクはcapfileでrequireしたライブラリだが、追加でおこないたいタスクをここで記述している。

 

 

自動デプロイによるディレクトリ構造の変化

Capistranoによる自動デプロイを実行すると、アプリケーションの本番環境のディレクトリ構造が変化します。

 

重要なのは、アプリケーション名のディレクトリの下にappディレクトリなどと同じ階層に並ぶ次の三つのディレクトリです。

 

▫️releases

バックアップデータが保存されます。

デプロイされたアプリは、keep_releasesで指定した世代分、バージョンが保存されます。

 

▫️current

現在デプロイさえているアプリを格納します。

と言っても、実は、releases内の最新のものにシンボリックリンクが貼られているのが実態です。

 

▫️shared

どの世代のアプリからも共通で参照されるディレクトリが格納されています。(わざわざ世代ごとに紐つける必要がない)

log、public、tmp、vendorディレクトリなど。

 

 

自動デプロイによるディレクトリ構造の変化に伴う設定の修正

自動デプロイ前は、アプリとして動くのは、

アプリ名ディレクト

でしたが、

 

自動デプロイ後は、

アプリ名ディレクトリ--app

                                            --current←1階層不覚にあるこれが動く

 

また、sharedなど、新規で作成されるディレクトもあるのでunicornについての設定を記述するunicorn.rbにあるパス の修正が必要となる。

 

自動デプロイ実行前のパス設定

#unicorn.rbから見たアプリケーショんディレクトリのパス
app_path
= File.expand_path('../../', __FILE__) worker_processes 1 working_directory app_path pid "#{app_path}/tmp/pids/unicorn.pid" listen "#{app_path}/tmp/sockets/unicorn.sock" stderr_path "#{app_path}/log/unicorn.stderr.log" stdout_path "#{app_path}/log/unicorn.stdout.log"

 

 実行後のディレクトリ構造に合わせたパス設定

../が一つ増えている
app_path = File.expand_path('../../../', __FILE__)

worker_processes 1
# 動かすディレクトリがアプリ名のディレクトリ→currentへ変更
working_directory "#{app_path}/current"

# tmp、pidsなどがsharedの中に格納されるため、パス変更
listen "#{app_path}/shared/tmp/sockets/unicorn.sock"
pid "#{app_path}/shared/tmp/pids/unicorn.pid"
stderr_path "#{app_path}/shared/log/unicorn.stderr.log"
stdout_path "#{app_path}/shared/log/unicorn.stdout.log"

 

 

自動デプロイによるディレクトリ構造の変化に伴うNginxの設定

これまで/var/www/アプリケーション名ディレクトリを参照していたが、アプリケーション名ディレクトリの中にできるcurrent、sharedディレクトリを参照することになるので、設定ファイルrails.confを変更。

 

EC2にてrails.confをvimで編集

$ sudo vim /etc/nginx/conf.d/rails.conf

 

自動デプロイ前

upstream app_server {
  server unix:/var/www/<アプリケーション名>/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name <Elastic IPを記入>;

# クライアントからアップロードされてくるファイルの容量の上限を2ギガに設定。デフォルトは1メガなので大きめにしておく
  client_max_body_size 2g;

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

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }

  error_page 500 502 503 504 /500.html;
}

 

自動デプロイ後

# upstreamとはブラウザを下流、サーバーを上流とした場合の上流。
 webサーバーのNginxからみて上流にあるappサーバーunicornに関する設定
upstream app_server { # sharedの中を参照するよう変更 server unix:/var/www/<アプリケーション名>/shared/tmp/sockets/unicorn.sock; } server { listen 80; server_name <Elastic IPを記入>; # クライアントからアップロードされてくるファイルの容量の上限を2ギガに設定。デフォルトは1メガなので大きめにしておく client_max_body_size 2g; # currentの中を参照するよう変更 root /var/www/<アプリケーション名>/current/public; location ^~ /assets/ { gzip_static on; expires max; add_header Cache-Control public; # currentの中を参照するよう変更 root /var/www/<アプリケーション名>/current/public; } try_files $uri/index.html $uri @unicorn; location @unicorn { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://app_server; } error_page 500 502 503 504 /500.html; }

 

設定を反映させるため、再読み込み、再起動を行う。

[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx reload
[ec2-user@ip-172-31-25-189 ~]$ sudo service nginx restart

自動デプロイ前の確認事項

ここまでで、準備は整いました。

最後に、下記の3点について確認しましょう。

 

▫️MySQLを起動する

起動していないと、自動デプロイは行えません。再起動しておきましょう。

[ec2-user@ip-172-31-25-189 ~]$ sudo service mysqld restart 

 

▫️unicornを停止する

unicornは自動デプロイの中で動かすので、事前に止めておきましょう。(masterプロセスのkillにより停止)

 

プロセスのpidの確認

[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn

ec2-user 17877  0.4 18.1 588472 182840 ?       Sl   01:55   0:02 unicorn_rails master -c config/unicorn.rb -E production -D
ec2-user 17881  0.0 17.3 589088 175164 ?       Sl   01:55   0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D
ec2-user 17911  0.0  0.2 110532  2180 pts/0    S+   02:05   0:00 grep --color=auto unicorn

 

killの実行

[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのPID(上記では17877)>
...

▫️Githubのリモートリポジトリが最新の状態か確認

ローカルリポジトリからpushしてない変更があれば自動デプロイ前にしておきましょう。

 

でないと、EC2がGithubのリモートリポジトリからコードをpullした場合、最新のものが反映できません。

 

 

自動デプロイ前の準備

ローカルのターミナルのアプリディレクトリに置いて、本番環境の自動デプロイを実行しましょう。

# アプリケーションのディレクトリで実行する
$ bundle exec cap production deploy

 

エラーが出た際には次の2点をチェックしましょう

 

▫️再度同じコマンドを実行

初めての自動デプロイは負荷がかかるので、設定に問題がなくてもエラーが生じることがあります。

 

▫️設定に誤りがないか

設定はしたけど、設定を反映させるコマンドを実行し忘れることがあるので、チェックしましょう。

 

なお、今後アプリを更新する際には、

▫️ローカルで行った変更をリモートリポジトリへpush

▫️ローカルのアプリのディレクトリにおいて自動デプロイコマンド

bundle exec cap production deployを実行する