capistrano3を使ってRailsアプリをさくらVPSにデプロイする
前回の記事の続き。 capistrano3を使って、VPS上にRailsアプリをデプロイする。
↓の記事を参考にした。 Rails5+Puma+Nginxな環境をCapistrano3でEC2にデプロイする(前編)
1. Gemの追加
デプロイに使うGemを設定する。 Gemfileに下記追加。
# Use Puma as the app server gem 'puma', '~> 3.7' group :development do gem 'capistrano', '3.7.0' gem 'capistrano-rails' gem 'capistrano-bundler' gem 'capistrano-rbenv' gem 'capistrano-rbenv-vars' end
$ bundle install
2. capistranoの設定を追加
下記コマンドを実行することで、設定ファイルが自動で作成される。
$ bundle exec cap install mkdir -p config/deploy create config/deploy.rb create config/deploy/staging.rb create config/deploy/production.rb mkdir -p lib/capistrano/tasks create Capfile
まず、Capfileを以下のように編集。
require "capistrano/setup" require "capistrano/deploy" require "capistrano/scm/git" install_plugin Capistrano::SCM::Git Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
次に、config/deploy.rbを編集。
lock "3.7.0" set :application, "アプリ名" set :repo_url, "git@github.com:ユーザー名/アプリ名.git" namespace :deploy do desc "Make sure local git is in sync with remote." task :confirm do on roles(:app) do puts "This stage is '#{fetch(:stage)}'. Deploying branch is '#{fetch(:branch)}'." puts 'Are you sure? [y/n]' ask :answer, 'n' if fetch(:answer) != 'y' puts 'deploy stopped' exit end end end desc 'Initial Deploy' task :initial do on roles(:app) do invoke 'deploy' end end before :starting, :confirm end
最後に、config/deploy/production.rbを編集。
server "IPアドレス", user: "deploy", roles: %w{app db web} set :ssh_options, { keys: %w(~/.ssh/id_rsa), forward_agent: true, auth_methods: %w(publickey) }
2. デプロイ用ユーザーの作成
$ sudo su - $ useradd deploy $ passwd deploy $ sudo visudo ## Allow root to run any commands anywhere root ALL=(ALL) ALL deploy ALL=(ALL) ALL ← この1行を追加
SSH接続のための鍵の設定をする。
$ su - deploy => 先程作成したdeployユーザーに切り替え $ mkdir ~/.ssh $ chmod 700 ~/.ssh $ vi ~/.ssh/authorized_keys => ローカルで作成した公開鍵を貼り付ける $ chmod 600 ~/.ssh/authorized_keys
3. アプリケーションのデプロイ先のディレクトリを作成
$ pwd /home/deploy $ sudo mkdir /var/www && cd /var/www $ sudo mkdir アプリ名 $ sudo chown deploy:deploy アプリ名
4. GitHubの設定
サーバーからGitHubに接続するために、公開鍵を登録する。
$ ssh-keygen -t rsa -v => まずは公開鍵と秘密鍵のペアを作成
作成した公開鍵を下記のページからGitHubに登録。
https://github.com/settings/ssh/new
SSHで接続確認。
$ ssh -T git@github.com Hi ユーザー名! You've successfully authenticated, but GitHub does not provide shell access. => このメッセージが表示されればOK.
5. 試しデプロイ実行
ここまで出来たら、一旦デプロイを実行してみる。
$ bundle exec cap production deploy => deploy:log_revision まで実行出来たらOK
6. 必要なパッケージのインストール
yum で必要なパッケージをインストールする。
$ pwd /home/deploy $ sudo yum update $ sudo yum install git $ sudo yum install gcc $ sudo yum install gcc-c++ $ sudo yum install openssl-devel $ sudo yum install readline-devel $ sudo yum install mysql-devel $ sudo yum -y install nodejs
ruby-build のインストール
$ git clone git://github.com/sstephenson/ruby-build.git $ sudo ruby-build/install.sh
rbenv のインストール
$ 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 $ exec $SHELL -l $ rbenv --version rbenv 1.1.1
rbenv-varsのインストール
$ git clone https://github.com/rbenv/rbenv-vars.git $(rbenv root)/plugins/rbenv-vars
ruby2.4.1のインストール
$ rbenv install 2.4.1 $ rbenv global 2.4.1 $ rbenv rehash $ ruby -v ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]
bundler のインストール
$ gem install bundler
rubyracer のインストール
$ gem install therubyracer
Rails 5.1.6 のインストール
$ gem install rails -v 5.1.6 $ rails -v Rails 5.1.6
puma のインストール
$ gem install puma
MySQL をインストール
参考記事:MySQL8をCentOS7にインスール(開発環境用)
$ rpm -qa | grep maria mariadb-libs-5.5.60-1.el7_5.x86_64 => mariadb が入っていないか確認 $ sudo yum remove mariadb-libs => mariadbのパッケージをアンインストール $ sudo yum -y localinstall https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm $ sudo yum -y install mysql mysql-devel mysql-server mysql-utilities $ sudo systemctl start mysqld
7. nginxの設定を修正
/etc/nginx/nginx.conf
を下記のように編集する。
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; 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 puma { server unix:///var/www/アプリ名/shared/tmp/sockets/puma.sock; } server { listen 80 default_server; listen [::]:80 default_server; server_name localhost; root /var/www/アプリ名/current/public; include /etc/nginx/default.d/*.conf; location / { try_files $uri $uri/index.html $uri.html @webapp; } location @webapp { proxy_read_timeout 300; proxy_connect_timeout 300; proxy_redirect off; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://puma; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } }
8. デプロイ設定の修正
本番環境で、下記のディレクトリが /var/www/アプリ名/shared/
の下に配置されるように設定を編集する。
ローカルに戻り以下の設定を編集する。
config/puma.rb
に下記追加。
app_dir = File.expand_path("../..", __FILE__) bind "unix://#{app_dir}/tmp/sockets/puma.sock" pidfile "#{app_dir}/tmp/pids/puma.pid" state_path "#{app_dir}/tmp/pids/puma.state" stdout_redirect "#{app_dir}/log/puma.stdout.log", "#{app_dir}/log/puma.stderr.log", true
config/deploy.rb
に下記追加
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"
デプロイを実行
$ bundle exec cap production deploy
9. SECRET_KEY_BASE の設定
以下のコマンドで本番環境の SECRET_KEY_BASE を環境変数に設定する。
$ export SECRET_KEY_BASE=`bundle exec rake secret`
10. MySQL の初期設定
Rootユーザーの初期パスワードを変更する。
参考:(MySQL8をCentOS7にインスール(開発環境用))https://d-ebi.hatenablog.com/entry/2018/11/26/210000
$ sudo grep 'temporary password' /var/log/mysqld.log 2018-11-26T01:55:34.881029Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: Rw.gaqQMp2>R => 初期パスワードを確認する $ mysql_secure_installation Enter password for user root: => 初期パスワードを入力して、パスワード設定などを行う。
アプリケーションで使用する(config/database.ymlに記載されている)本番用のDBとユーザーの作成。
$ mysql -u root -p mysql> create database DB名; Query OK, 1 row affected (0.10 sec) mysql> create user ユーザー名@localhost identified by パスワード; Query OK, 0 rows affected (0.01 sec) mysql> grant all on DB名.* to ユーザー名@localhost; Query OK, 0 rows affected (0.03 sec)
11. MySQLのSocketファイルの設定
MySQLのSocketファイルの出力先パスを調べて config/database.yml
に設定する。
$ mysqladmin -u root version -p .. UNIX socket /var/lib/mysql/mysql.sock ... => このパスを `config/database.yml` に追記する。
12. asset compile
$ bundle exec rake assets:precompile RAILS_ENV=production
12. Pumaの再起動
サーバーに接続して、Pumaを再起動する。
$ cd /var/www/アプリ名/current $ bundle exec puma -t 5:5 -e production -C config/puma.rb => Pumaを起動
起動に成功すれば、socketファイルやpidファイルが生成される。
どんなプログラマになりたいか考えたのである
どんなプログラマになりたいか考えてみたのだ。 一言で言えば。「金を作れるプログラマ」だなぁ。 せせりさんとかマジリスペクト。 技術的にはまずは、RubyとRailsを極めるしかないっしょ。 でも極めるってかなりぼやっとした目標。漂う達成不可能感。 どうすりゃいいの。 じゃあもっと具体的な目標にすればええんでないの? ということで、とりあえず、今年中にWebサービスを最低3個作る。ってのはどうでしょう。 いいんじゃないでしょうか。すごく具体的。 あと、個人的には数学も勉強したい。数学検定一級合格!とかいい目標でない? あとTOEIC900点!とかね。かっこいいじゃない。 でもその前になりよりもプログラマとして一人前になりたいのですね。アタシ。 インタプリタとかOSとか作ってみたいじゃない、プログラマなら。
さくらVPSの初期設定〜nginxの起動まで
ドットインストールの「(さくらのVPS入門)https://dotinstall.com/lessons/basic_sakura_vps」をやったので簡単に内容をメモしておく。 OSはCentOS7。
1. VPSに接続する
$ ssh root@[IPアドレス] Are you sure you want to continue connecting (yes/no)? yes root@IPアドレス's password: => 仮登録完了メールに記載されているパスワードを入力 $ yum update => インストール済みのパッケージをアップデート
2. 作業用ユーザーの設定
$ useradd admin => adminという作業用ユーザーを追加 $ passwd admin => 任意のpasswordを設定
次に作成した作業用ユーザーで sudo コマンドを実行できるようにする。
$ usermod -G wheel admin => wheel グループに作業用ユーザーを追加する。wheelというのは、sudoを実行できるユーザーグループの名前。大物とか実行者という意味らしい。 $ visudo => sudo権限を変更するためのコマンド。sudoersというファイルが開く。
以下の行を探して、2行目の「#」を消すことでこの一文を有効化する。 これで wheel グループのユーザーが sudo を実行できるようになる。
## Allows people in group wheel to run all commands # %wheel ALL=(ALL) ALL
3. 鍵認証の設定
パスワード認証だとセキュリティの強度が低いので、公開鍵を使った認証に切り替える。
$ pwd /home/admin $ mkdir ~/.ssh $ chmod 700 ~/.ssh => 所有者のみ操作可能とする。
次に、SSHの接続元(ローカル)側で接続に使う鍵のペアを作成する。
$ ssh-keygen -t rsa -v Generating public/private rsa key pair. Enter file in which to save the key (/Users/***/.ssh/id_rsa): => 保存するファイル名を指定。基本はデフォルトでよい。 $ chmod 600 ~/.ssh/id_rsa.pub => 作成された公開鍵のファイルのパーミッションを変更。 $ scp ~/.ssh/id_rsa.pub admin@IPアドレス:~/.ssh/authorized_keys => 公開鍵をVPSに転送する。
鍵のアップロードが完了したら、以下のコマンドで秘密鍵を使ってローカルからVPSに接続ができる。
$ ssh -i ~/.ssh/id_rsa admin@IPアドレス # デフォルトで ~/.ssh/id_rsa を見に行くようになっているので、下記のように省略してもOK。 $ ssh admin@IPアドレス
4. SSHの設定
以上で鍵を使ってSSH接続するところまで完了。だが、パスワードログインやルートログインが有効になっていて、このままではセキュリティ上良くないので、それらを無効化する
VPSに接続して、以下のコマンドを実行。
$ sudo -s => ここからは、ルート権限で実効するコマンドが多いので、ルート権限に変更する。 # cp /etc/ssh/sshd_config /etc/ssh/sshd_config.org => SSHの設定ファイルを編集する前にバックアップをとっておく # vim /etc/ssh/sshd_config => vimで設定ファイルを開く
vimで、/etc/ssh/sshd_config
を開いて以下の設定を編集する。
編集前:PasswordAuthentication yes 編集後:PasswordAuthentication no
編集前:# PermitRootLogin yes 編集後:PermitRootLogin no
下記コマンドを実行して編集したSSHの設定を読み込む。
# systemctl restart sshd
5. ファイアウォールの設定
CentOS6までは、iptablesというコマンドだったけど、7からはfirewalldというのに変わったらしい。 firewalldについては↓の記事がわかりやすかった。 ネコでもわかる!さくらのVPS講座 ~第七回「ファイアウォール”firewalld”について理解しよう」
あとで、Webサーバーを立ち上げるので、あらかじめHTTP通信を許可しておく。
# firewall-cmd --add-service=http --zone=public --permanent # firewall-cmd --add-service=https --zone=public --permanent # systemctl restart firewalld
6. nginxのインストール
$ yum install nginx => nginx をインストール $ systemctl start nginx => 起動 $ systemctl enable nginx => OS起動時に自動で起動させる
割り振られたIPアドレスにアクセスしたらnginxのトップページが表示されるはず。
なぜ、ああたの仕事は終わらないのか? 中島聡
著者はマイクロソフト本社で働いていたこともある日本人プログラマの中島さん。
中島さんが培ってきた仕事術を紹介している。
・(仕事が終わらない理由は)大きくまとめると、次の3点に集約されます。(p.48)
① 安請け合いをしてしまう
② ギリギリまでやらない
③ 計画の見積もりをしない
・言葉で説明するのが難しいときは形にして見せてしまうのが一番いい(p.123)
・どんな仕事でも、企画をアイデアのままではなく形にした人がその企画の推進者になることができます。
私のような日本から渡米したばかりのいちエンジニアがマイクロソフトで活躍できたのは、
まさに限られた時間を濃密に使いこなし、プロトタイプを先に作ったからなのです。(p.134)
・まずは「締切は絶対に守るもの」と考える(p.152)
① 「まずはどのくらいかかるかやってみるので、スケジュールの割り出しのために2日ください」と
答えて仕事に取り掛かる(見積もりをするための調査期間をもらう)。
② その2日をロケットスタート期間として使い、2日で「ほぼ完成」まで持っていく
③ 万が一、その2日で「ほぼ完成」まで持っていけなかった場合、これを「危機的な状況」と認識して
スケジュールの見直しを交渉する
・期間が10日だとしたら、最初の2日は「ロケットスタート」の期間。ここで、「ほぼ完成」に持っていく。残り8日は「流し」の期間。(p.165)
→ ロケットスタート期間のまま全て終わらせないこと。
・いつも全力を出していると、真の実力を発揮できなくなる(p.164)
Bundlerとは
Bundlerとは
Bundlerとはアプリケーションで利用するgemパッケージを定義し、依存関係を解決するための仕組み。
Bundlerはアプリケーションごとにgemパッケージの依存関係を閉じ込めることで、特定のアプリケーションだけで利用するgemパッケージを簡単に管理することができる。
Gemfile
Bundlerはアプリケーションで利用するgemパッケージをGemfileというファイルに列挙する。Gemfileは「bundle init」コマンドを実行することで生成される。
Gemfileの中身は以下のようになっている。
# frozen_string_literal: true # A sample Gemfile source "https://rubygems.org" # gem "rails"
sourceがgemパッケージの取得先、デフォルトではrubygems.orgが指定されているが、ローカルネット内に独自に立てているリポジトリサーバーに向けることも出来る。
インストール
Gemfileに使用するgemパッケージを記載して「bundle install」コマンドを実行するとgemパッケージがインストールされる。またインストール対象のパッケージ内で使われているgemパッケージも芋づる式にインストールされる。
「bundle install」を実行すると、Gemfileと同じディレクトリにGemfile.lockファイルが作成される。このファイルには、Gemfilleに記述されたgemパッケージの依存関係を解決した結果が書き出される。
Gemfile.lock内にはインストールされたgemパッケージの具体的なバージョンも記載される。
Gemfile.lockも更新したい場合は、「bundle update」コマンドを実効する。
「bundle install」でインストールしたコマンドの実行
「bundle install」でインストールしたGemは「bundle exec コマンド名」で実行する。
$ bundle exec [コマンド]
開発用、テスト用
Gemfileは開発環境の場合とテスト環境の場合にインストールするものを設定可能。
source "http://rubygems.org" gem 'sinatra' # 開発用のGem group :development do gem 'awesome_print' end # テスト用のGem group :test do gem 'rspec' end
特定の環境のGemだけインストールしない場合は、--withoutオプションを使う。
# developmentとtest以外のGemをインストールする % bundle install --without development test
binstubsオプション
「bundle install」でインストールしたコマンドは、通常「bundle exec コマンド名」でなければ実行できない。
しかし、「bundle install」時に「--binstubs」オプションを指定すれば、直下にbinディレクトリが作成されその中にGemのコマンドが配置されるので、コマンド名だけでコマンドが実行可能になる。
RSpecのまとめ
概要
BDDを行うためのテストフレームワーク。
準備
gemのインストール。
$ gem install rspec
初期化
$ rspec -init
これを実行すると spec というディレクトリが直下に出来て、その中に spec_helper.rb ファイルが出来る。rspecのテストファイルを作成する際は必ずこれを require する必要がある。
チュートリアル
(1) テスト対象を決める
今回は、/lib/dog.rb を対象とする。
(2) 失敗するテストを書く
下記のテストファイルを作る。
spec/lib/dog_spec.rb
require "spec_helper" require "dog" describe Dog do it "is named 'Pochi'" do dog = Dog.new expect(dog.name).to eq 'Pochi' end end
またテスト対象である Dog クラスもファイルだけ作成しておく。
(でないとテスト実行時にエラーがとなってしまうので)
テストを実行してみます。実行結果は・・
$ rspec spec/lib/dog_spec.rb F Failures: 1) Dog is named 'Pochi' Failure/Error: expect(dog.name).to eq 'Pochi' NoMethodError: undefined method `name' for #<Dog:0x007fcc4f97a8d8> # ./spec/lib/dog_spec.rb:7:in `block (2 levels) in <top (required)>' Finished in 0.00061 seconds (files took 0.55266 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/lib/dog_spec.rb:5 # Dog is named 'Pochi'
当然、dogの中身がないので失敗します。
テストが成功するように、dog.rbを修正します。
class Dog attr_accessor :name def initialize(name="Pochi") @name = name end end
再度テストを実行してみる。
$ rspec spec/lib/dog_spec.rb . Finished in 0.0074 seconds (files took 0.53777 seconds to load) 1 example, 0 failures
今度は無事成功したのでOK! このテスト作成→失敗→修正→成功までの一連のサイクルがBDD(Behavior Driven Development)の基本的な流れになる。
その他のテスト例
describe User do describe '#greet' do before do @params = { name: 'たろう' } end context '12歳以下の場合' do before do @params.merge!(age: 12) end it 'ひらがなで答えること' do user = User.new(@params) expect(user.greet).to eq 'ぼくはたろうだよ。' end end context '13歳以上の場合' do before do @params.merge!(age: 13) end it '漢字で答えること' do user = User.new(@params) expect(user.greet).to eq '僕はたろうです。' end end end end
describe '#greet' doは、Userクラスのgreetメソッドをテストするということ。
また、beforeは、テスト実行前に必要な共通処理を記載する。
contextは、直訳すると「文脈」や「状況」という意味。つまり、なにかしら条件が異なる場合はcontextを使ってテストを分割する。
インスタンス変数の代わりにletを使う
インスタンス変数の代わりに以下のようにletを使うこともできる。
require 'spec_helper' require 'user' describe User do describe '#greet' do let(:params) {{name: 'たろう'}} context '12歳以下の場合' do before do params.merge!(age: 12) end it 'ひらがなで答えること' do user = User.new(params) expect(user.greet).to eq 'ぼくはたろうだよ。' end end context '13以上の場合' do before do params.merge!(age: 13) end it '漢字で答えること' do user = User.new(params) expect(user.greet).to eq '僕はたろうです。' end end end end
let(:params) {{name: 'たろう'}}がそう。{}が二重になってちょっとわかりにくいが、外側がRubyのブロックの{}で、内側がハッシュの{}だ。
let は遅延評価されるので、それが使われる時に初めて初期化される。つまり、上のテストコードを以下のように書き直すことが出来てしまう。
require 'spec_helper' require 'user' describe User do describe '#greet' do let(:user) { User.new(params) } let(:params) {{name: 'たろう', age: age}} context '12歳以下の場合' do let(:age) {12} it 'ひらがなで答えること' do expect(user.greet).to eq 'ぼくはたろうだよ。' end end context '13以上の場合' do let(:age) {13} it '漢字で答えること' do expect(user.greet).to eq '僕はたろうです。' end end end end
age は params ハッシュの定義時に使われているが、各context内ではじめて実際の値が代入されている。