OpsWorks with Rails 環境でDB Migration問題をなんとかする

はじめに

AWS OpsWorksではChef11を選択すると、Railsスタックをデフォルトで提供しており、エンジニアが自前でChefなど用意しなくてもHeroku感覚でAWSをPaaSとして利用することが出来ます。

  • Herokuほどフルマネージドは要件上出来ないが、インフラはある程度管理したい
  • Chef実行準備が面倒くさい
  • コードのDeploy機能とデプロイ記録とりたい
  • CloudWatch Logs統合 (ログPath設定を画面から出来る)
  • AWSビジネスサポートに入っていると、困っている時に技術的なことを直接チケットを投げて聞ける

こんなメリットたちがあります。正直最後のメリットが一番のような気もしますが・・・

DB Migration問題?

このサービスには致命的な問題があります。なんと複数インスタンスへのデプロイ時に、RailsのDB Migrationと、Unicornの再起動の同期が全く取れていません。 そのため、複数インスタンスを選択した状態で、DB Migrationを実行すると、Migration以外のノードはソース反映後すぐにUnicorn再起動がかかり、高確率で不完全な状態でデプロイされてしまいます。

OpsWorks上でのMigration実行の仕組み

OpsWorks経由で「Migrate YES」を選択してデプロイを行うと rake の db:migrate が実行されます。
https://github.com/aws/opsworks-cookbooks/blob/2209f35fc0e3fd7a75af2e3d0715d425c6a55783/deploy/attributes/deploy.rb#L79

複数ノードがある状態で、「Migrate YES」を選択してデプロイを行う場合、グループ内で任意のノード1個が選択され(基本的にはグループで一番最初のもの)、そのノードに対して内部的にchefの変数に default[:deploy][application][:migrate] = true が設定入り、その後、全ノードに同時にcookbookが実行されます。

デプロイ中は、基本的に全てのインスタンスはそれぞれ勝手にChefが実行されるだけです。そのため、これが原因でマイグレーション同期されない問題がおきています。小さなDBマイグレーションであれば問題なかったりしますが、ちょっとでも時間がかかるようになるとアウトです。規模によりますが、Railsのデプロイを1回実行するのに少なくとも3〜4分くらいかかりますので、ダウンタイム発生となります

不完全な状態でのUnicorn再起動を回避する方法 by AWS

AWSサポート等にもヒアリングした所、以下2点の方針が一般的との回答。

  1. Migration用のレイヤーを作っていただき、先にそちらでMigration込みで一旦ソースを反映して頂く
  2. Chefのデプロイフックがあるので、それを利用して何かしら処理を入れてもらう

うーむ、自前でやらねば・・・・ということで素直に上記を対応します。
(1)に関してはただの運用フロー寄りの問題なので、一旦今回は詳細を割愛します。

デプロイフック実装

OpsWorksはDocument Root配下に deploy ディレクトリ配下に before_symlink.rb (シンボリックリンク張替え直前) で以下のチェック入れます。

Chef::Log.info("Running deploy/before_symlink.rb")
current_release = release_path
env = node[:deploy][:app_rails][:rails_env]

execute "rake aws:verify_migration" do
  cwd current_release
  command "bundle exec rake aws:verify_migration"
  environment new_resource.environment.merge({ "RAILS_ENV" => env })
end

Rakeタスクを適当に作ります。

namespace :aws do
  task verify_migration: :environment do
    raise StandardError if 
  ActiveRecord::Migrator.needs_migration?
  end
end

これでとりあえず、不完全な状態でUnicornが再起動される問題だけ回避しました。before_symlinkの段階で、verify_migrationを実行し、マイグレーションが残っている状態の時は対象インスタンスの反映を途中中断させるようにします。「カラムの削除の時はどうするのか」みたいな懸念はありますが、一旦これだけでも入れておけば大きい所はカバーできそうです。

最後に

そもそもChefは諦めて、コンテナ運用(GKE + Spinnaker) に早く移行したほうがいいかもしれない

続きを読む

aws-sdk for Rubyを使ってCloudFrontのinvalidationをリクエストする

S3にjsonをあげて、取得するときはCloudFront経由にする構成を作っています。
jsonを更新/削除する際にCloudFrontのキャッシュを削除したかったのですが、余り記事が引っかからなかったので備忘までにメモしておきます。

前提

  • Ruby 2.3.0
  • aws-sdk 2.10.22

※ sdkのversionが1の場合は、Clientのクラスやapiのパラメータが異なりますので、こちらをご覧ください。

コード

Railsアプリケーションで削除するclassを作ったので、そのまま貼ります。

lib/clients/cloud_front.rb
module Clients
class CloudFront
  include Singleton

  @@instance = Aws::CloudFront::Client.new(
    region: ENV['AWS_DEFAULT_REGION'],
    access_key_id: ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
  )

  # CloudFrontのInvalidationを作成する
  # @see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFront/Client.html#create_invalidation-instance_method
  #
  # @param [Array[String]] paths キャッシュオブジェクトのパスの配列
  # @return [String] 結果のメッセージ
  def self.create_invalidation(paths=[])
    # 非同期のAPIをcall
    ret = @@instance.create_invalidation({
      distribution_id: ENV['AWS_CLOUD_FRONT_DISTRIBUTION_ID'],
      invalidation_batch: {
        paths: {
          quantity: paths.size,
          items: paths
        },
        # api callを同定するためのユニークな文字列
        # @see http://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateInvalidation.html#API_CreateInvalidation_RequestSyntax
        caller_reference: Time.now.to_s
      }
    })

    # invalidationの作成に成功したときは、
    # レスポンスのinvalidation.statusにInProgressが入る
    if ret.blank? or ret.invalidation.blank? or ret.invalidation.status != 'InProgress'
      Rails.logger.warn "Fail to delete cache of #{paths.join(', ')}"
      Rails.logger.warn "Response from CloudFront: #{ret.inspect}"
      return 'キャッシュの削除に失敗しました。'
    end

    'キャッシュの削除を開始しました。しばらく待って確認してください。'
  end

end
end

補足1: メソッドのレスポンスについて

invalidationのcallは元々非同期ですし、削除に失敗したからと言ってアプリケーションを止めたくなかったので、私の場合はメッセージを返してコントローラーでflashに入れるようにしています。場合によっては例外出してもいいかもしれません。

補足2: メソッドの引数について

引数経由で渡しているオブジェクトのpathですが、同じversionのS3クライアント(Aws::S3::Resource)やWeb consoleとは異なり、冒頭の’/’が必須になるのでご注意ください。

  • NG

    • v1/hoge.json
  • OK
    • /v1/hoge.json

続きを読む

新たにEC2インスタンスを立ち上げてデプロイするまでの順序

AWSのインスタンスを立ち上げるなんて毎日やるわけでもないので、やるたびに忘れてしまいますよね。ちなみに今のいままでほぼ完全に忘れていました。以前一回やったことがあったのですが、数ヶ月前だし、一回しかやったことないし。

そこで、備忘録的にメモしておこうかと。

登場人物

  • ruby on Rails
  • capistrano
  • AWS
  • itamae

手順をば

大まかには以下の通り。
1. インスタンスをつくる
2. itamaeを新たに作成したインスタンスに向けて実行する
3. capデプロイコマンドを実行する
4. EC2インスタンスのドメインを変更する(Route 53)

1. インスタンスをつくる

詳細なインスタンスの各種設定は他のQiita記事やドキュメントに譲ります。
が、新規でインスタンスをつくる際にいくつか注意すべき点があるのでそれらを特記しておきます。

pemファイルをダウンロードする

コレはあとでEC2インスタンスにssh接続するときに必要なので、消さないように作業ディレクトリにでも一時的に保管しておきます。が、itamaeなどで管理している場合は必要ないかもしれません。下記に記載の通り、GitHubで公開鍵を設定しておけば、それ経由でssh接続できるためです。

公開鍵を用意しておく

詳細は以下のリンクなどを参照してください。EC2インスタンスにパスなしでコマンド入力して入れるようにするために必要です。プロジェクトメンバー全員で共有するなら、シートか何かにまとめておくと良いかも。
4.3 Git サーバー – SSH 公開鍵の作成

2. 新たに作成したインスタンスに向けてitamaeを実行する

itamaeの作り方は他の記事に譲ります。
node/以下に環境ごとの設定ファイル(production.ymlstaging.ymlなど)があると思うので、下記コマンドを実行する時に-yオプションで指定します。

// --dry-runで問題なく通るかをまず確認する
$ bundle exec itamae ssh -u ec2-user -h your_ip_address -y node/staging.yml --dry-run sabaku.rb

// 問題ないことを確認したら実行する
$ bundle exec itamae ssh -u ec2-user -h your_ip_address -y node/staging.yml sabaku.rb

3. capのデプロイコマンドを実行する

インスタンスにもろもろ環境が出来たら、今度はGitHubの作業ブランチを指定してデプロイします。
作業ディレクトリ/deploy/以下に環境ごとの設定ファイル(下記コマンドを叩く場合はfugafuga.rbが必要)があるので、下記コマンド実行時に指定するのを忘れずに。

// この場合はfugafuga.rbを元にデプロイ
$ BRANCH=hogehoge bundle exec cap fugafuga deploy

4. EC2インスタンスのドメインを変更する(Route 53)

デプロイが終わったら、パブリックIPを直打ちしてサイトに行くことはできますが、なんだがいちいちIP書くのめんどくさい。
そこでAWSコンソールに行ってRoute 53というサービスを使います。
ここで、取得したドメインを登録しておけば、そのサブドメインなんかを気軽に登録することができるようになります。
下記に簡単な手順(といっても2点しかないですが)を記載しておきます。

Create Record Setボタンで新しいレコードセットを作成する

スクリーンショット 2017-08-18 14.36.42.png

レコードセットの設定を行う

Nameの部分の任意のサブドメイン名を入力します。TypeにはDNSレコードの種類を選択します。
Aliasの部分なんですが、Aliasを張らない場合はValueに直にインスタンスのパブリックIPを記入してしまうのが、一番手っ取り早くて簡単かと思います。
スクリーンショット 2017 08 18 14.36.55.png

よし、これで一件落着。
自分で設定したhoge.mydomain.comみたいなURLを叩けば、IP直打ちしたときみたいにサイトにアクセスできるようになります。
これで、次回インスタンスを立てることになっても、大体大丈夫でしょう。

続きを読む

Rails5アプリケーションのAWSによるネットワーク構築 Nginx+Puma+Capistranoな環境とAWS構築(VPC EC2 RDS CloudFlont Route53 etc)

はじめに

数ヶ月前に書いたRails5のAWS構築をもう少し項目を増やしてから公開しようと思っていたのですが、なかなか出来ないのでもう公開します。(ELBとかCloudFlontとかもっとちゃんと書けそう、、)
項目の追加や修正は適宜入れると思います。

Railsは5.0を使っています。(記事を書いたときの最新)

4系は以前書きました↓

構築する環境

  • VPC
  • EC2
    • Nginx + Puma
    • Capistrano
  • (S3)
  • ELB
  • RDS
  • CloudFlont
  • Route53
  • (CloudWatch)

VPC、EC2のインスタンスの作成

AWSのデザインが多少変更はありますが、以下を参考に作成出来ます。

AWS VPCによるネットワーク構築とEC2によるサーバー構築

これに追加でElastic IPの設定もしておきました。

EC2内の環境構築

作成したEC2にログインして環境を構築します。
ec2-userでまずログインしましょう。

ユーザーの作成

デフォルトではec2-userなので新たなユーザーを追加し、そのユーザーでsshログイン出来るように設定します。

$ sudo adduser shizuma #ユーザーの追加 「shizuma」の部分は好きなユーザー名にします。以後「shizuma」の部分は各自のユーザー名になります。
$ sudo passwd shizuma
#ここで、新規ユーザーのパスワードを設定します。
$ sudo visudo
-----------------------------
#vimが起動するので新規ユーザーにroot権限を与える。
root    ALL=(ALL)       ALL
shizuma ALL=(ALL)       ALL #この行を追加
-----------------------------
$ sudo su - shizuma #ユーザー切り替え
#先ほど設定したパスワード

ここでローカルに一旦戻り、鍵を作成します。

$ cd .ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():first_aws_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ vi config
-----------------------------
# 以下を追記
Host first_aws
  Hostname 54.64.22.197 #自分の設定に合わせて
  Port 22
  User shizuma #先ほどのユーザー名
  IdentityFile ~/.ssh/first_aws_rsa #秘密鍵の設定
-----------------------------

次に、サーバーに戻り作成した 公開鍵 をサーバーに設定します。

$ mkdir .ssh
$ chmod 700 .ssh
$ cd .ssh
$ vi authorized_keys
-----------------------------
#localの「first_aws_rsa.pub」の中身のコピペ。(localで $ cat first_aws_rsa.pubとかすると良い)
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@shizuma-no-MacBook-Air.local
-----------------------------
$ chmod 600 authorized_keys
$ exit
$ exit

これで設定が完了したので、以降作成したユーザーでアクセスするようにします。

基本ライブラリとrubyの環境構築

$ 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 
mysql mysql-server mysql-devel 
ImageMagick ImageMagick-devel 
epel-release
$ sudo yum install nodejs npm --enablerepo=epel
$ 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
$ rbenv install -v 2.3.1
$ rbenv global 2.3.1
$ rbenv rehash
$ ruby -v

gitの設定

最低限の設定だけしておきます。

$ vi .gitconfig
.gitignore
[user]
  name = your_name #自分の名前
  email = hoge@hoge.com #自分のメアド

[alias] #これはお好きに
  a = add
  b = branch
  ch = checkout
  st = status

[color] #色付け
  ui = true

[url "github:"] #pull、pushのための設定
    InsteadOf = https://github.com/
    InsteadOf = git@github.com:

アプリケーションフォルダの設置

/var/www/rails にアプリケーションフォルダを設置します。

$ cd /
$ sudo mkdir -p /var/www/rails
$ sudo chown -R shizuma var/www

GitHubの接続とアプリケーションのclone

$ cd ~/.ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():aws_git_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ chmod 744 config
$ vi config
-----------------------------
# 以下を追記
Host github github.com
  Hostname github.com
  User git
  Port 22
  IdentityFile ~/.ssh/aws_git_rsa #秘密鍵の設定
-----------------------------
$ cat aws_git_rsa.pub
-----------------------------
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@ip-10-0-1-10
-----------------------------

ここで、これをコピペしてgithubに公開鍵を登録する。
githubへの鍵の登録がよくわからない方は以下の記事を参考に。
gitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

そして、git clone する。

$ cd /var/www/rails
$ git clone https://github.com/kuboshizuma/cheerfull # 自分のアプリケーション

RDSの設定

EC2の環境構築に一区切りついたのでRDSの設定を行います。

DBサブネットの登録

RDSで使うサブネットを登録します。
この設定には異なるアベイラビリティゾーンにあるサブネットが最低1つずつ計2つ必要になります。
また、Railsアプリケーションをおいたサブネットはゲートウェイに繋がったpublicなサブネットなので、privateなサブネットを異なるアベイラビリティゾーンに一つずつ作成します。

スクリーンショット_2017-03-04_11_56_01.png

パラメータグループの設定

パラメータグループを設定します。
mysqlを使用していると設定なので、ここではmysqlを選択します。バージョンは適宜選択。
「chara…」で検索すると出て来るcharasetをutf-8に変更。

スクリーンショット_2017-03-04_12_00_57.png

設定が完了した後に、パラメータの編集を押してパラメーター変更。

スクリーンショット_2017-03-04_12_04_29.png

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

VPCでセキュリティグループを作成します。

スクリーンショット_2017-03-04_12_08_19.png

インバウンドルールにMySQLを設定し、MySQLのアクセスのみ許可します。
「送信元」は0.0.0.0/0で設定。特定のRailsアプリケーションサーバーがあるサブネットに限定するほうがよさそう。

スクリーンショット 2017-03-04 12.55.42.png

RDSインスタンスの作成

エンジンの選択

スクリーンショット_2017-03-04_12_12_05.png

本番稼働用?

スクリーンショット_2017-03-04_12_12_45.png

DB詳細設定の指定

無料枠はt2.micro

スクリーンショット_2017-03-04_12_17_35.png

詳細設定の設定

各種作成したもので設定。

スクリーンショット_2017-03-04_12_19_44.png

スクリーンショット_2017-03-04_12_19_53.png

接続確認

hostは各自の作成したRDSインスタンスのエンドポイントをみる。

$ mysql -h hogepoge.ap-northeast-1.rds.amazonaws.com -u shizuma -P 3306 -p

接続出来たら完了!

絵文字の扱い

絵文字も登録出来るようにする場合。
以下を参考に utfmb4を採用する。

ActiveRecordをutf8mb4で動かす

MAMPでは /Applications/MAMP/conf/my.cnfmy.cnf を設置した。

Railsアプリケーションの起動のための準備

puma setting

以下を追記する。

cofig/puma.rb
# add setting for production
_proj_path = "#{File.expand_path("../..", __FILE__)}"
_proj_name = File.basename(_proj_path)
_home = ENV.fetch("HOME") { "/home/#{ENV['PUMA_USER']}" }
pidfile "#{_home}/run/#{_proj_name}.pid"
bind "unix://#{_home}/run/#{_proj_name}.sock"
directory _proj_path
# add end

ENV['PUMA_USER'] にサーバーのユーザー名が入るように環境変数を設定。

database setting

以下のように編集。

config/database.yml
production:
  <<: *default
  database: cheerfull
  username: <%= ENV['DATABASE_USER_NAME_PRODUCTION'] %>
  password: <%= ENV['DATABASE_PASSWORD_PRODUCTION'] %>
  host: <%= ENV['DATABASE_HOST_PRODUCTION'] %>

それぞれ該当の環境変数を設定。

rake secret

以下のsecret keyの箇所に値が入るように環境変数を設定。
rake secret とコマンドを打つと出て来る文字列を設定する。

config/secrets.yml
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
$ gem install bundler
$ bundle install
$ rake db:create RAILS_ENV=production
$ rake db:migrate RAILS_ENV=production

PumaとNginxの起動

Pumaの起動

アプリケーションディレクトリにて以下を実行。

$ RAILS_SERVE_STATIC_FILES=true RAILS_ENV=production puma -w 4

「Ctr+c」でプロセス消さなければ生き残る。
プロセス消す必要があれば以下のようにする。

$ ps aux | grep puma # プロセスIDを探す
$ kill -9 (ID)

Nginx の起動

「cheerfull」の部分は自分のアプリケーションディレクトリ名に変更する。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/log/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/home/shizuma/run/cheerfull.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }

ユーザーをnginxから自分のユーザー名に変更しておく。

/etc/nginx/nginx.conf
#user nginx;
user shizuma;

nginxを起動します。

$ sudo service nginx restart

これで、IPアドレスでアクセスするとアプリケーションが表示されるようになりました。

Capistranoの設定

諸々動くことが確認出来たのでデプロイが出来るように設定します。
デプロイのためにCapistranoの設定をします。
慣れてきたら、いきなりCapistranoの設定をしていけばいいと思います。

socketの場所もアプリケーションディレクトリに変更するのでnginxの設定もそれに合わせて変更します。

「cheerfull」の部分は自分のアプリケーション名に変更する。

deploy.rb
require 'dotenv'
Dotenv.load

lock "3.7.1"

set :application, "cheerfull"
set :repo_url, "git@github.com:your_gitname/cheerfull.git"
set :branch, 'master'
set :deploy_to, '/var/www/rails/protospacce'
set :linked_files, fetch(:linked_files, []).push('.env')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
set :keep_releases, 5
set :rbenv_ruby, '2.3.1'

# puma setting
set :puma_threads,    [4, 16]
set :puma_workers,    0
set :pty,             true
set :use_sudo,        false
set :stage,           :production
set :deploy_via,      :remote_cache
set :deploy_to,       "/var/www/rails/#{fetch(:application)}"
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state,      "#{shared_path}/tmp/pids/puma.state"
set :puma_pid,        "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{shared_path}/log/puma.error.log"
set :puma_error_log,  "#{shared_path}/log/puma.access.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true

namespace :deploy do
  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
end

実際にdeployする前に、共有ファイルは用意しておきましょう。この場合、.env

ここでpumaのセッティングをしたので、config/puma.rb はもう不要になります。
また、dotenvを使用する場合は変更を読み込むために config/deploy/templates/puma.rb.erb を用意します。以下を用意。

config/deploy/templates/puma.rb.erb
#!/usr/bin/env puma

directory '<%= current_path %>'
rackup "<%=fetch(:puma_rackup)%>"
environment '<%= fetch(:puma_env) %>'
<% if fetch(:puma_tag) %>
  tag '<%= fetch(:puma_tag)%>'
<% end %>
pidfile "<%=fetch(:puma_pid)%>"
state_path "<%=fetch(:puma_state)%>"
stdout_redirect '<%=fetch(:puma_access_log)%>', '<%=fetch(:puma_error_log)%>', true


threads <%=fetch(:puma_threads).join(',')%>

<%= puma_bind %>
<% if fetch(:puma_control_app) %>
activate_control_app "<%= fetch(:puma_default_control_app) %>"
<% end %>
workers <%= puma_workers %>
<% if fetch(:puma_worker_timeout) %>
worker_timeout <%= fetch(:puma_worker_timeout).to_i %>
<% end %>

<% if puma_preload_app? %>
preload_app!
<% else %>
prune_bundler
<% end %>

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = "<%= fetch(:bundle_gemfile, "#{current_path}/Gemfile") %>"
  ENV.update Dotenv::Environment.new('.env')
end

<% if puma_preload_app? and fetch(:puma_init_active_record) %>
before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end
<% end %>
$ bundle exec production cap puma:config 

これでこの設定を本番に反映。

あとはデプロイコマンドを打つ。

$ bundle exec cap production deploy

デプロイdone!

nginxのlogの位置やsocketの位置を変更します。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/shared/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/shared/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/var/www/rails/cheerfull/shared/tmp/sockets/cheerfull-puma.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/current/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/current/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }
$ sudo service nginx restart

これで、デプロイ環境が整いました。
以前起動したpumaのプロセスはきっておきましょう。

複数サーバーへのデプロイ

multiconfig を使用します。

Gemfile
gem 'capistrano-multiconfig', '~> 3.1', require: false

Capfileのcapistrano/setupから変更。

Capfile
# require "capistrano/setup"
require 'capistrano/multiconfig'

config/deploy/production.rb から config/deploy/app1/production.rb に変更する。
以下のようにdeploy出来るようになる。

$ bundle exec cap app1:production deploy

違うサーバーにデプロイするときは同じように config/deploy/app2/production.rb を準備すればオーケー。

$ bundle exec cap app2:production deploy

これで違うサーバーにデプロイ出来る。

これまでと同様にec2サーバーをもう一度用意して2箇所にデプロイ出来るようにする。

ロードバランサー ELB

ロードバランサーをhttpsで運用したいときadmin@hoge.comのようなアドレスへのメールを受信可能のしないといけない。

以下でとりあえずzoneファイルのインポートまでする。
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)

その後にこの記事にあるようにお名前.com側の設定をする。
お名前.com + Route53で独自ドメインのメールをGmailに転送する

メールの転送が出来ていることを確認する。
そして、ELBの指示に従って設定。
下記参考。
【初心者向け】ELBにSSL証明書をインストールする

そこまでしたら
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)
の続きでELBとつなげる。

注意点は、ターゲットグループにトラフィックをルーティングするときのプロトコルをHTTPにすること。これでELBへはHTTPSでアクセス出来、それをアプリケーションではHTTPで扱うことが出来る。

また、以下のように nginxのサーバーネームにドメイン名を追加する必要がある。

server_name hogepoge.com 00.000.00.00 sakamichi-app-elb-000000000.ap-northeast-1.elb.amazonaws.com

CloudFront

Amazon CloudFront + ACM 独自ドメインで HTTPS (SSL) 配信設定メモ

CloudWatchとか

必要の応じて加筆。

続きを読む

【超初心者向け】AWSでたてたnginxサーバーにドメインを設定する

はじめに

(下準備編)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで

上記の記事を読んで、Railsアプリケーションサーバーを立てました。
しかし、URLがElastic IPのIPアドレスになったままです。
独自ドメインでアプリをリリースしたいと思い、そこまでの手順をまとめました。
基本的に上記の記事の補足なので、AWSでデプロイしたい!とお考えの方は、
(デプロイ編②)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで
までお読みください。

ドメイン取得

ドメイン名を登録する

AWS公式リファレンスにドメインの取得方法が書いてあります。
「ステップ 2: ドメイン名を登録する」をご覧ください。

まずは、Route 53 コンソールに入って、「Domain Registration」をクリックします。

次に、希望のドメイン名を入力します。
.io.jpなど人気ドメインは高いです。
他の人に使用されていなければ、ドメインを取得することができます。

次に個人情報入力の画面が出てきます。
電話番号が国際電話番号だったり、住所を欧米式だったりして、見慣れない人もいるかもしれませんが
特に問題はないでしょう。

最後に「Completed Purchase」をクリックして、購入完了です。
個人情報に入力したメールアドレスにメールがきているので、確認してリンクをクリックして有効化しましょう。

DNS設定

ドメイン名を登録する

リファレンスの「Step 3: DNS を設定する」に進みます。
今回取得したドメインは「静的IPアドレス」です。

Route 53 コンソールの [Hosted Zones] 部分を開きます。
そこに先ほど取得したドメイン名があるはずです。
これをクリックします。

[Create Record Set] ボタンをクリックします。ウィンドウの右側の [Name] テキストボックスに「www」と入力します。ステップ 1 で作成した Elastic IP アドレスを [Value] ボックスに入力してから、[Create] をクリックします。

ここまででDNS設定は完了です。
ほぼリファレンス通りですが、nginx側の設定が完了していません。
この時点では、リファレンス通りに固定ドメインに接続しようとしても応答がありません。

nginxの設定

confファイルのserver_nameで、nginxのサーバーの名前をドメインと同じにしてあげましょう

[user|~]$ cd /etc/nginx/conf.d/
[user|conf.d]$ sudo vi xxx.conf #xxxは自分のアプリケーション名でファイル名変更
xxx.conf
  server_name www.hello-ruby.net;(#アプリの固定ドメインに変更してください)

変更点は上記のみ
あとはnginxの再起動を行いましょう

[user|~]$ sudo service nginx restart

これでお待ちかね、ブラウザのURLバーに取得したドメインを入力しましょう。
繋がりましたかね?
知識がないと、ここまでいくのが大変ですよね!

続きを読む

capistranoで自動デプロイ後、AWSにThe page you were looking for doesn’t exist.と表示される。

デプロイでハマったのでメモです。

The page you were looking for doesn’t exist.

スクリーンショット 2017-07-18 19.11.02.png

結論からいいますと、capiを使った後はbundle exec rails s -e productionの実行は不要のようですね。自動的にサーバーが立ち上がるみたいです。

設定もきちんとしたし、何度もメンターに確認しました。
capistrano実行後のログをちゃんと読んでも、間違いはなかったです。
それでもうまくいかないで、あれこれ試していたら結論は以上でした。
不要なコマンドを実行していたと。

ポートが使用中かどうか確認する。

ポートが使用中かどうかは、下記コマンドで確認できます。

#terminal
lsof -i:3000

PIDっていうのが表示されるので、以下のコマンドを実行すると殺戮できるようです。

スクリーンショット 2017-07-18 19.29.29.png

#terminal
kill 1917

続きを読む

カテゴリー 未分類 | タグ

AWS EC2のUbuntuサーバーにFirefoxをインストールして、Selenium(Ruby)を動かすための準備

AWS EC2のUbuntuサーバーにFirefoxをインストールして、Selenium(Ruby)を動かすための準備を行ったときに、割りと大変だったので、そのメモを公開します。

アプリケーション自体はRuby on Railsなので、その準備も入ってます。
Amazon Linuxも試したのですが、firefoxのインストールとか大変でUbuntuを選択しました。
2017/7/18時点。

UbuntuにGUIとfirefox等をインストール

sudo apt-get update
sudo apt-get install lxde #GUI のインストール
sudo dpkg-divert --local --rename --add /sbin/initctl
sudo ln -s /bin/true /sbin/initctl
sudo start lxdm #GUI の起動

sudo apt-get install firefox
export DISPLAY=:1

sudo apt-get install vnc4server
vncserver
rm ~/.vnc/xstartup
ln -s /etc/X11/Xsession ~/.vnc/xstartup
vncserver -kill :1 #プロセス 1 (先ほど立ちあげたプロセス) を一旦 kill
vncserver #もう一回起動。設定が反映される。

参考:http://tushuhei.hatenadiary.jp/entry/20131009/1381302187

Ruby(Rails, MySQL)を動かすためのライブラリをインストール

sudo apt-get -y install git
sudo apt-get -y install ruby2.3
sudo apt -y install ruby-bundler
sudo apt-get -y install ruby2.3-dev nodejs libcurl4-openssl-dev apache2-dev libapr1-dev libaprutil1-dev libxml2 libxslt-dev build-essential patch libssl-dev mysql-server libmysqlclient-dev

参考:http://qiita.com/sasurai_usagi3/items/0fb2603669e7ac083395

FirefoxをSeleniumで動かすためにgeckodriverをインストールする

wget https://github.com/mozilla/geckodriver/releases/download/v0.16.1/geckodriver-v0.16.1-linux64.tar.gz
sudo sh -c 'tar -x geckodriver -zf geckodriver-v0.16.1-linux64.tar.gz -O > /usr/bin/geckodriver'
sudo chmod +x /usr/bin/geckodriver
rm geckodriver-v0.16.1-linux64.tar.gz

参考: https://askubuntu.com/questions/870530/how-to-install-geckodriver-in-ubuntu

トラブルシューティング

  • vncserverがいくつも立ち上がっていると上手くいかない。
  • ブラウザを処理完了後に閉じるようにしないと、メモリが足りなくなる。

続きを読む

Rails + CircleCI 1.0 + AWS の設定手順 ビルド開始~デプロイ完了まで

はじめに

RailsでCircleCI環境を整えるまでの手順をまとめました。

CircleCIはバージョン2.0がありますが、
Dockerがないといけない?ようなので、バージョン1.0で準備しました。

circleCIとは

簡単に言うと、
githubのpushを検知して、自動でビルドからテスト、デプロイまで自動化してくれるツール。
(デフォルトだと全部ブランチ対象)

1コンテナは無料なので、時間が掛かってもよいのであれば無料枠で十分。

https://circleci.com/

設定ファイル

必要なファイルは以下3つ

① circle.yml
② config/database.yml.ci
③ script/deploy-staging.sh

① circle.yml

circleCIを動かくための設定ファイル。
Railsのアプリケーションディレクトリ直下にファイルを作ります。(gemfileとかと同じ場所)

② config/database.yml.ci

テストするためのDB設定ファイル。Rspecを実行するために必要。

③ script/deploy-staging.sh

テストが終わったらステージング環境へデプロイするためのスクリプトファイル。

設定ファイルの中身

circle.yml
machine:
  timezone:
    Asia/Tokyo
  ruby:
    version:
      2.3.0
dependencies:
  pre:
    - sudo pip install awscli
  override:
    - bundle install:
database:
  pre:
    - mv config/database.yml.ci config/database.yml
  override:
    - bundle exec rake db:create db:schema:load RAILS_ENV=test
    - bundle exec rake db:migrate RAILS_ENV=test
test:
  override:
    - bundle exec rspec spec/
deployment:
  staging:
    branch: master
    commands:
      - sh script/deploy-staging.sh:
          timeout: 1500

ポイント
AWSにデプロイするのにawscliを使用するために、
「sudo pip install awscli」を記述する。(デフォルトは古いらしい)
DB名は「test」じゃないと動かない。

config/database.yml.ci
test:
  adapter: postgresql
  encoding: unicode
  database: test
  pool: 5
script/deploy-staging.sh
#!/bin/sh

export AWS_DEFAULT_REGION="ap-northeast-1"

MYSECURITYGROUP="*"
MYIP=`curl -s ifconfig.me`

aws ec2 authorize-security-group-ingress --group-id $MYSECURITYGROUP --protocol tcp --port 22 --cidr $MYIP/32

bundle exec cap production deploy

aws ec2 revoke-security-group-ingress --group-id $MYSECURITYGROUP --protocol tcp --port 22 --cidr $MYIP/32

ポイント
MYSECURITYGROUPは、AWSのセキュリティーグループIDを指定する。
LFの改行コードで作成する。CRLFだと動かない。

その他設定

CircleCI側にAWSの秘密鍵を登録する。
AWS上で「cat ~/.ssh/id_rsa」で表示されるもの。

CircleCI側にAWSのアクセスキーとシークレットキーを設定

AWSの秘密鍵をAWSの認証キーとして登録。
AWS上で「cat id_rsa >> ~/.ssh/authorized_key」

Port 22をセキュリティグループで空けておく。

続きを読む

【Elastic Beanstalk】1つの環境のみで完全なBlue-Greenデプロイを実現する方法【PHP】

目的

  • /var/www/html からアプリケーションのソースを読み取る
  • /var/app/current -> /var/www/html にシンボリックリンクを貼って現在のバージョンを動かしている
  • /var/app/ondeck で次のバージョンのビルドを行う
  • 最終的に /var/app/ondeck/var/app/current を上書きしたい

Elastic Beanstalk において,なんとか1つの環境だけで Blue-Green デプロイができないかな,と試行錯誤した記録です。

手順

前提

  • /var/app/ondeck で既にビルドが終わっている。
  • cpおよびmvがGNU版である。

コマンド

ln -sf /var/app/ondeck /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
sleep 5 && rm -rf /var/app/current
cp -al /var/app/ondeck /var/app/current
ln -sf /var/app/current /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
sleep 5 && rm -rf /var/app/ondeck

ポイント

  • ディレクトリのシンボリックリンクを上書きできるln -snfアトミックな操作ではないので,ln -sfmv -Tfを組み合わせる必要がある。

  • 新しいパスにコピーするときには,ハードリンクを再帰的に作成するcp -alを使うと,アプリケーションによるディスク使用領域が一時的に2倍に膨れ上がる無駄も避けられる。
  • 読もうとするファイルが既に削除されていることを防ぐために,1リクエストの処理にかかりそうな時間ぶんあるいはその数倍だけ,rmの前にスリープを入れておくと安全。ここでは5秒と見なす。
  • オートローディングのベースになる__DIR____FILE__シンボリックリンク解決後のパスになるため,古いバージョンから新しいバージョンを部分的に読み込んでしまう不整合は発生しない。直接 /var/www/html/src/foo/bar/Klass.php などと,シンボリックリンクを含む絶対パスをハードコーディングしている場合はアウト。

検証

macOS上で実験しているので,mvの代わりにgmvcpの代わりにgcpを使っています。

確認用シェルスクリプト
#!/bin/sh

# 準備
mkdir -p /tmp/example/current /tmp/example/ondeck
echo aaaa > /tmp/example/current/data.txt
echo bbbb > /tmp/example/ondeck/data.txt
ln -s /tmp/example/current /tmp/example/html

# 監視を開始
perl -e 'while (1) { open(my $file, "/tmp/example/html/data.txt") or die("ERROR!"); print <$file> }' &
sleep 1

# 置き換えを開始
ln -sf /tmp/example/ondeck /tmp/example/new_html && gmv -Tf /tmp/example/new_html /tmp/example/html
sleep 5 && rm -rf /tmp/example/current
gcp -al /tmp/example/ondeck /tmp/example/current
ln -sf /tmp/example/current /tmp/example/new_html && gmv -Tf /tmp/example/new_html /tmp/example/html
sleep 5 && rm -rf /tmp/example/ondeck

# 後始末
kill %%
rm -rf /tmp/example

ゼロダウンタイムになっているかどうかを確認します。

スクリーンショット 2017-07-12 14.19.23.png

できました!

Elastic Beanstalk への適用

/opt/elasticbeanstalk/hooks/appdeploy/enact/01_flip.shに以下の変更を加えます。

【変更前】 /opt/elasticbeanstalk/hooks/appdeploy/enact/01_flip.sh
#!/usr/bin/env bash
#==============================================================================
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use
# this file except in compliance with the License. A copy of the License is
# located at
#
#       https://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or
# implied. See the License for the specific language governing permissions
# and limitations under the License.
#==============================================================================

set -xe

EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config  container -k app_staging_dir)
EB_APP_DEPLOY_DIR=$(/opt/elasticbeanstalk/bin/get-config  container -k app_deploy_dir)

if [ -d $EB_APP_DEPLOY_DIR ]; then
  mv $EB_APP_DEPLOY_DIR $EB_APP_DEPLOY_DIR.old
fi

mv $EB_APP_STAGING_DIR $EB_APP_DEPLOY_DIR

nohup rm -rf $EB_APP_DEPLOY_DIR.old >/dev/null 2>&1 &
【変更後】 /opt/elasticbeanstalk/hooks/appdeploy/enact/01_flip.sh
#!/usr/bin/env bash
#==============================================================================
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use
# this file except in compliance with the License. A copy of the License is
# located at
#
#       https://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or
# implied. See the License for the specific language governing permissions
# and limitations under the License.
#==============================================================================

set -xe

EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config  container -k app_staging_dir)
EB_APP_DEPLOY_DIR=$(/opt/elasticbeanstalk/bin/get-config  container -k app_deploy_dir)

ln -sf $EB_APP_STAGING_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
if [ -d $EB_APP_DEPLOY_DIR ]; then
  sleep 5 && mv $EB_APP_DEPLOY_DIR $EB_APP_DEPLOY_DIR.old
fi

cp -al $EB_APP_STAGING_DIR $EB_APP_DEPLOY_DIR

ln -sf $EB_APP_DEPLOY_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
sleep 5 && mv $EB_APP_STAGING_DIR $EB_APP_STAGING_DIR.old

nohup rm -rf $EB_APP_STAGING_DIR.old $EB_APP_DEPLOY_DIR.old >/dev/null 2>&1 &

オートスケールで反映させるためには, .ebextensions 配下にこの定義を書く必要があります。

.ebextensions/01-customize-flip.config
files:
  "/opt/elasticbeanstalk/hooks/appdeploy/enact/01_flip.sh":
    owner: root
    group: root
    mode: "000755"
    content: |
      #!/usr/bin/env bash
      #==============================================================================
      # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
      #
      # Licensed under the Amazon Software License (the "License"). You may not use
      # this file except in compliance with the License. A copy of the License is
      # located at
      #
      #       https://aws.amazon.com/asl/
      #
      # or in the "license" file accompanying this file. This file is distributed on
      # an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or
      # implied. See the License for the specific language governing permissions
      # and limitations under the License.
      #==============================================================================

      set -xe

      EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config  container -k app_staging_dir)
      EB_APP_DEPLOY_DIR=$(/opt/elasticbeanstalk/bin/get-config  container -k app_deploy_dir)

      ln -sf $EB_APP_STAGING_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
      if [ -d $EB_APP_DEPLOY_DIR ]; then
        sleep 5 && mv $EB_APP_DEPLOY_DIR $EB_APP_DEPLOY_DIR.old
      fi

      cp -al $EB_APP_STAGING_DIR $EB_APP_DEPLOY_DIR

      ln -sf $EB_APP_DEPLOY_DIR /var/www/new_html && mv -Tf /var/www/new_html /var/www/html
      sleep 5 && mv $EB_APP_STAGING_DIR $EB_APP_STAGING_DIR.old

      nohup rm -rf $EB_APP_STAGING_DIR.old $EB_APP_DEPLOY_DIR.old >/dev/null 2>&1 &

これをリポジトリに含めた上で,デプロイ実行中に ll /var/www/html を連打して確認してみました。

スクリーンショット 2017-07-12 15.13.34.png

うまくいきました!

注意事項

PHPのようにソースコードを設置するだけでデプロイが完了するアプリケーションに関してはゼロダウンタイムですが,Railsのようにアプリケーションサーバとして永続させるタイプのものの場合,スタートアップにかかる時間が存在し,古いファイルパスの情報を保持し続けるため,依然として

  • ロードバランサのスワップ
  • CNAMEスワップ

といった処理は必要です。また,データベースのスキーマ変更が発生する場合もこの方法では不可能です。

…とはいえども,スキーマ変更の伴わないPHPアプリケーションの変更に関しては,非常に簡単に対応できるのは美味しいところだと思います。また1つPHPの長所が増えましたね(笑

続きを読む