はじめに
http://qiita.com/kizashi1122/items/b54b0f1033ff5d7fbd66 に2014年に書いた記事は今でもまあまあ見られているので、更新してみようと思います。今回は前回の自分自身の記事をみて試したところ、ファイルのパスがわかりにくいとか作業順がアレだなとか思うところがあったので、そういった点も修正できればと思います。
また前回の記事では自分のローカル環境から(capistrano経由で)デプロイすることができるようになるというところで終わっていましたが、今回は master
を git push
すれば(master
にマージされたら)、自動的にデプロイされるところまでやってみたいと思います。
AWS のサービスあれこれ
AWS (Amazon Web Services) には
- CodeDeploy
- Elastic Beanstalk
- AWS CloudFormation
- AWS OpsWorks
など、微妙に似たようなサービスが多いです。
本当はこれらのサービスを組み合わせて環境構築したりデプロイするのがスジなんだろうとは思いますが、まとまった情報もなく、いくつか試したけどうまくいかなかったため、前回の記事の更新版ということになりますが、手で環境構築をするバージョンを以下に書いていきます。
目標
- Github 上にある Rails アプリを、
- (今のところ)無料のCI/CDサービスである Wercker ( https://app.wercker.com/ )を利用して、
- master に push されたら、自動テストが走るようにして、
- さらにテストが通ったらデプロイされるようにする
ところまでを書いてみたいと思います。Web(AP)サーバーは1台という前提です。
環境構築時はローカルにインストールした capistrano からデプロイのテストします。
ただ最終的には Wercker からデプロイできるようになるので、複数人で開発もしやすくなるはずです。
やはり、認証などが入ってくるアプリだと、SSL必須になると思うので、LetsEncrypt よりSSL証明書を取得していますが、詳細はここでは省略します。
前提条件
下記のミドルウェアはインストールされている前提とします。
- nginx (nginx-1.10.2-1.30.amzn1.x86_64)
- postgresql (postgresql95-9.5.4-1.71.amzn1.x86_64)
カッコ内は私が実施にインストールしたバージョンです。
別に今回の設定にpostgresqlが依存しているわけではないので、mysql が好きな人はそれでもいいし、RDS を使っている人は、database.yml
にそのホストを指定してもらえればいいと思います。
Ruby インストール
以下の作業は EC2 上での作業 となります。
Ruby がないと Rails が動きませんのでなにはともあれ Ruby をインストールします。
Ruby のインストールも前提条件にいれておいてもよかったのですが、手元にメモがあるので書いておきます。
色々やり方はあると思いますが、手慣れた rbenv を使います。
以下の作業は EC2 上での作業 となります。
$ git clone git://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 git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv versions
$ sudo yum install -y readline-devel
$ rbenv install 2.3.3
$ rbenv versions
$ rbenv global 2.3.3
$ rbenv rehash
$ gem install bundler
nginx 設定ファイル修正
設定したい内容としては、以下のとおりです。
- http にアクセスがあったら、https にリダイレクトする
- unicorn 連携 (rails 連携)
- SSL設定
nginx に関する設定なので、もちろん EC2 上での作業 となります。
Unicorn とは同一サーバ上で動かしているため、Unix Domain Socket で接続します。
まだ unicorn の設定もできてないのですが、先に設定ファイルを編集しておきます。
設定を反映する再起動はすべての設定が終わったらおこないます。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
server_tokens off; # 1
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
index index.html index.htm;
upstream hoge.example.com { # 2
server unix:/tmp/unicorn.sock; # 3
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name localhost;
return 301 https://$host$request_uri; # 4
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name _;
root /home/ec2-user/deploy/current/public; # 5
location @myapp {
proxy_set_header X-Forwarded-Proto https;
# needed to forward user's IP address to rails
proxy_http_version 1.1;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Queue-Start 't=${msec}000';
proxy_redirect off;
proxy_max_temp_file_size 0;
proxy_pass http://hoge.example.com; # 6
}
location ~ ^/(images|assets|javascripts|stylesheets)/ {
try_files $uri $uri/index.html /last_assets/$uri /last_assets/$uri.html @myapp;
expires 10y;
}
location / {
try_files $uri $uri/index.html $uri.html @myapp;
}
ssl_certificate "/etc/letsencrypt/live/hoge.example.com/fullchain.pem"; # 7
ssl_certificate_key "/etc/letsencrypt/live/hoge.example.com/privkey.pem"; # 7
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP;
ssl_prefer_server_ciphers on;
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
コメントにつけた番号について注意点をあげておきます。
番号 | 説明 |
---|---|
1 | セキュリティのためです。今回の設定とは無関係です。 |
2 | ちゃんとFQDNにしておかないとアプリでフルパスを取得するとき失敗してしまいました。 |
3 | socket ファイルの指定。unicorn の設定と合わせる必要あり。 |
4 | http にアクセスがあったら、https にリダイレクトする設定。 |
5 | コンパイル後の css, js が生成されるパスを指定しまる。後述の config/deploy.rb に書いているデプロイ先と関係しています。つまりアプリは /home/ec2-user/deploy/current に配備されてることになります。 |
6 | http:// より後ろは #2 と合わせておいてください。 |
7 | LetsEncrypt により生成された証明書と秘密鍵を指定します。 |
Capistrano 3 のインストール
以下は、これらは git@github.com:<github-account>/<certain-repository>.git
にある Rails アプリ上のファイルになります。
まずは ローカルでの作業 になります。
Gemfile
Gemfile に以下を追加しましょう。
gem 'capistrano'
gem 'capistrano-rails'
gem 'capistrano-bundler'
gem 'capistrano-rbenv'
gem 'capistrano3-unicorn'
gem 'unicorn'
前回の記事では、 capistrano3-unicorn
は使わないと書きましたが今回は使っています。
次に bundle install コマンドによって capistrano 関連の gem をインストールします。
インストールが成功したら bundle exec cap install
とコマンド実行します。
$ bundle install
$ bundle exec cap install
すると関連するフォルダやファイルが生成されます。
Rails.root
├── Capfile
├── config
│ ├── deploy
│ │ ├── production.rb
│ │ └── staging.rb
│ └── deploy.rb
└── lib
└── capistrano
└── tasks
Capfile
Capfile については # ADDED
のみ追加しています。
require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
require "capistrano/rbenv"
set :rbenv_type, :user # ADDED
set :rbenv_ruby, '2.3.3' # ADDED
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
require 'capistrano3/unicorn'
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
config/deploy.rb
次に、deploy.rb です。ここは重要です。
実際にデプロイする先の情報が必要になります。とはいってもgithub
のレポジトリurlやデプロイする先のディレクトリなどで、ホスト情報はまだでてきません。ホスト情報は後述する config/deploy/production.rb
に記述します。そりゃ環境によってホストは分かれるよねということです。
lock "3.8.0"
set :application, "<application-name>" # 1
set :repo_url, "git@github.com:<github-account>/<certain-repository>.git" # 2
set :deploy_to, "/home/ec2-user/deploy" # 3
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
set :keep_releases, 5
set :unicorn_config_path, -> { File.join(current_path, "config", "unicorn.rb") } #4
set :unicorn_rack_env, 'production'
after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
task :restart do
invoke 'unicorn:restart'
end
end
番号 | 説明 |
---|---|
1 | アプリ名です。 |
2 | デプロイ対象のアプリです。git のURLパスです。 |
3 | サーバ側のデプロイ先のディレクトリです。/var/www などのディレクトリを使っているサンプルが多かったのですが、パーミッションの問題もあるだろうと思い、ssh ユーザがアクセスできるように、ホームディレクトリ以下にしました。このディレクトリは先に mkdir しています。 |
4 | 後述の unicorn.rb の場所を明示的に指定します。デフォルトは config/unicorn/production.rb のようです。こちらのほうがよいという人は本設定はなくてよいかも。 |
デプロイするソースは master から取得する前提なので明示的に指定はしていません。たとえばウチは常に develop
ブランチをデプロイしたいのだという場合は
set :branch, 'develop'
と書けばよいかと思いますが、デプロイ時の cap
コマンドに外部から渡すことも可能かと思います。
config/deploy/production.rb
production に接続する情報を記入します。具体的には ssh で接続するため鍵情報も必要になります。
server "hoge.example.com", user: "ec2-user", roles: %w{app db web}
set :ssh_options, {
keys: [File.expand_path('~/.ssh/aws.pem')],
}
set :stage, :production
set :rails_env, 'production'
set :unicorn_rack_env, 'production'
ローカルの ~/.ssh/aws.pem
に hoge.example.com
のEC2サーバーにアクセスできる鍵を用意しておいてください。
この鍵ファイルのパスは、後から Wercker でデプロイする際も踏襲する必要があることに留意ください。
Unicorn
AP サーバーは慣れていることもあり unicorn を使います。前の記事と同じですね。
すでに、Gemfile に gem 'unicorn'
と書いて bundle install
しているのでインストールは終わっています。
あと、前回は、unicorn.cap
みたいなファイルを頑張って作ったような気がしますが、Capfile
に
require 'capistrano3/unicorn'
があれば、自分でタスクを作る必要はありません。なお、以前は *.cap
でしたが、今回は、
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
とあるように、自分で unicorn.cap
を作っても動かないので注意してください。拡張子が変わってますよという意味です。
config/unicorn.rb
とりあえず、こうしてます。このファイルの場所は config/deploy.rb
の unicorn_config_path
と一致させる必要があります。
worker_processes 3
app_path = "/home/ec2-user/deploy"
listen '/tmp/unicorn.sock' #1
pid "#{app_path}/shared/tmp/pids/unicorn.pid"
stderr_path File.expand_path('unicorn.err.log', File.dirname(__FILE__) + '/../log')
stdout_path File.expand_path('unicorn.log', File.dirname(__FILE__) + '/../log')
preload_app true
# use correct Gemfile on restarts
before_exec do |server|
ENV['BUNDLE_GEMFILE'] = "#{app_path}/current/Gemfile"
end
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
# Before forking, kill the master process that belongs to the .oldbin PID.
# This enables 0 downtime deploys.
old_pid = "#{server.config[:pid]}.oldbin"
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
end
after_fork do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
end
番号 | 説明 |
---|---|
1 | nginx.conf に記述する unicorn の socket ファイルのパスと合わせてください。 |
さてこれで設定ファイルはひととおり編集できました。
ローカルからデプロイ
あとは、git push
して、以下のデプロイコマンドを打ってください。
$ bundle exec cap production deploy
これで、ちゃんとEC2のサーバーを覗いたら、 /home/ec2-user/deploy/current
とか出来ていて、unicorn のプロセスが立ち上がっていたらバンザイ。
$ sudo /etc/init.d/nginx restart
して、外部からつながったら、さらにバンバンザイ。
wercker からデプロイ
事前条件として
- wercker には github アカウントでサインアップしておく。
- wercker の
Environment
タブより、キーペアを作っておく(+ Generate SSH Keys
リンクより)- ここで発行された公開鍵は予め手動で、EC2 サーバーの
ec2-user
の~/.ssh/authorized_keys
に追加しておく
- ここで発行された公開鍵は予め手動で、EC2 サーバーの
-
master
が push されたら deploy が走るように workflow を定義しておく
Git のリポジトリのルートに以下のような wercker.yml
を配置して、git push
する。
box: ruby:2.3
services:
- id: postgres
env:
POSTGRES_PASSWORD: test
build:
steps:
# A step that executes `bundle install` command
- bundle-install
# A step that prepares the database.yml using the database in services
- rails-database-yml:
service: postgresql-docker
# Add more steps here:
- script:
name: setup DB (schema load)
code: bundle exec rake db:schema:load RAILS_ENV=test
- script:
name: setup DB (seed sample)
code: bundle exec rake db:seed:sample RAILS_ENV=test
- script:
name: rspec
code: bundle exec rspec
deploy:
steps:
- script:
name: make .ssh directory
code: mkdir -p "$HOME/.ssh"
- create-file:
name: write ssh key
filename: $HOME/.ssh/aws.pem # ここが `config/deploy/production.rb` に定義しているパスと同じになること
overwrite: true
hide-from-log: true
content: $WERCKER_SSH_KEY_PRIVATE
- script:
name: set permissions for ssh key
code: chmod 0400 $HOME/.ssh/aws.pem # ここが `config/deploy/production.rb` に定義しているパスと同じになること
- script:
name: add ssh-agent
code: |
eval `ssh-agent`
ssh-add $HOME/.ssh/aws.pem # ここが `config/deploy/production.rb` に定義しているパスと同じになること
- bundle-install
- cap:
stage: $WERCKER_DEPLOYTARGET_NAME
たぶんこれで master
に push されるたびにデプロイが走るようになるはず。