本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【GCP環境構築編】

Hakusan Mafiaアドベントカレンダー5日目を余語 [Qiita|facebook|github] が担当します!

GCPでサーバーを立てる

適当なものを返してくれるapiを事前に作っていて、それをインスタンスに乗せました。

誰でもGCPで本番環境を作理、デプロイできるようになるかと思います。

過去の記事
本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【SSL証明書編】
本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【GCP環境構築編】 ←今これ
本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【パフォーマンス改善編】

はじめに

以前はAWSでインフラ環境を整えるのがベストプラクティスかと思っていました。というのも、EC2は直感的に触って起動できるし、S3・RDS・ DynamoDMなどの記事も相当Webに溢れていたので簡単でした。ただ、プレスリリースを売ったりメディアに出したりする案件があった際にわざわざAmazonへ申請を出さなければならないといけないらしいということを聞いて少し疑問に感じた時もありました。

そんな時に、AWSと同等のものを用意してくれている。且つ、自動スケーリングが凄い(詳しくいうと、ロードバランサがGoogle検索と同じらしい)という記事を見た時に、少々衝撃を覚えました。

今回の記事では、Google Cloud Platform(GCP)を利用して本番環境を構築する方法を記述します。
基本的には、公式ドキュメント通りにやると全てうまくいくのですが、自分のターミナルから色々いじりたい人用に書いてます。

10分でGCP環境構築

1. ログイン、プロジェクト作成

https://cloud.google.com/?hl=ja の右上でログインし、その後「コンソール」へ入る
プロジェクト作成から、「プロジェクト名」を入力して、準備完了

2. GCEを選択し、VMインスタンス作成

https://gyazo.com/0581708139112c11db672d5cdcfb7ea6

名前は適当で、
- ゾーンを「asia-northeast1-a」
- ブートディスクに今回は、「CentOS」
- ファイアウォールの設定は二つともチェックを入れましょう。

https://gyazo.com/5cc259ec583d4635073f1876cbbd28e6

3. Terminalにてgcloudログイン

今回は、ミドルウェア、とりわけWebサーバーをNginx、ApサーバーをUnicornで実装します。
OSは先ほどCentOSを利用すると選択したので、webにあるDebianでの記事と比較しながらやると勉強になると思います。
(RoRアプリケーションを想定しています。)

ミドルウェアの設定は次の通りです。

ミドルウェア 項目
nginx conn./worker 1024
unicorn worker processes/cpu 2 or 3

インスタンス作成後に、そのVMインスタンスの「接続」から「glcloud コマンドを表示」という箇所でコマンドをコピーして、ローカルで接続して見てください。

local
$ gcloud compute --project "xxxxx-yyy-1111111" ssh --zone "asia-northeast1-a" "xxx"
Last login: Sun Dec  3 08:13:59 2017 from softbankxxxx.bbtec.net

server
[xxxx@yyyy ~]$ #こんな感じでログインできる

先ほどGoogle Compute Engineで作成したVMインスタンスにログインできる。
(ここで弾かれたら、permission関連なのでgithubのSSH and GPG keysという箇所に公開鍵を貼り、ログインしてください。)

local
# gcloudがないと言われたら、下記でインストール&ログイン
$ curl https://sdk.cloud.google.com | bash
$ gcloud init

# gcloud auth loginしてと言われたが、できない場合
$ gcloud auth list
ACTIVE  ACCOUNT
        xxx@gmail.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`

$ gcloud config set account xxx@gmail.com

4. 権限管理・ミドルウェアのインストール

4.1 新規ユーザー作成

server

[user_name| ~ ]$ sudo adduser [新規ユーザー名]
[user_name| ~ ]$ sudo passwd [新規ユーザー名]
[user_name| ~ ]$ sudo visudo
----------------------------
root ALL=(ALL) ALL
[新規ユーザー名] ALL=(ALL) ALL # この行を追加
# ↑後々 wheelか何かで管理できるとなお良い
----------------------------
[user_name| ~ ]$ sudo su - [新規ユーザー名]

4.2 VMインスタンス内の環境構築

yum -> node.js -> rbenv/ruby-build -> rubyの順

4.2.1 yum (mysql関連含む)

server
[user_name| ~ ]$ sudo yum install \
                 git make gcc-c++ patch \
                 libyaml-devel libffi-devel libicu-devel \
                 zlib-devel readline-devel mysql-server mysql-devel  -y

4.2.2 node.js

server
[user_name| ~ ]$ sudo curl -sL https://rpm.nodesource.com/setup_6.x | sudo bash -
[user_name| ~ ]$ sudo yum install -y nodejs

4.2.3 rbenv/ruby-build

server
[user_name| ~ ]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
[user_name| ~ ]$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
[user_name| ~ ]$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
[user_name| ~ ]$ source ~/.bash_profile
[user_name| ~ ]$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
[user_name| ~ ]$ rbenv rehash

4.2.4 ruby

server
# centOSを選択した場合
[user_name| ~ ]$ sudo yum -y install bzip2

[user_name| ~ ]$ rbenv install -v 2.4.1
[user_name| ~ ]$ rbenv global 2.4.1
[user_name| ~ ]$ rbenv rehash
[user_name| ~ ]$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]

4.3 Gitとインスタンスの紐ずけ

ssh接続 → git clone

server
[user_name@vm_name]$ vim .gitconfig
-----------------------------------
[user]
  name = <githubに登録したユーザー名>
  email = <githubに登録したメールアドレス>

[color]
  ui = true

[url "github:"]
    InsteadOf = https://github.com/
    InsteadOf = git@github.com:
-----------------------------------

[user_name@vm_name]$ mkdir .ssh
[user_name@vm_name]$ cd .ssh
[user_name@vm_name .ssh]$ ssh-keygen -t rsa
# 作成されたid_rsa.pubをgithubのSSH and GPG keysの箇所で登録する
[user_name@vm_name .ssh]$ vim config 
-----------------------------------
Host github
  Hostname github.com
  User git
  IdentityFile ~/.ssh/id_rsa
-----------------------------------
[user_name@vm_name .ssh]$ ssh -T github

[user_name@vm_name www]$ pwd
/var/www
[user_name@vm_name www]$ git clone git@github.com:~~~~

4.4 Nginxを積む

server
[ユーザー名|~]$ sudo yum install nginx
[ユーザー名|~]$ cd /etc/nginx/conf.d/
[ユーザー名|~]$ sudo vi <your_app_name>.conf (小文字でもok)
# default.conf/ssl.confなどと分けるとなお良い。(細分化)

Nginxの設定は最小限です。

/etc/nginx/conf.d/your_app_name.conf
upstream unicorn {
    server  unix:/var/www/<your_app_name>/tmp/sockets/unicorn.sock;
}

server {
    listen       80;
    server_name  <ip address and/or domain name>;

    access_log  /var/log/nginx/access.log;
    error_log   /var/log/nginx/error.log;

    root /var/www/<your_app_name>/public;

    client_max_body_size 100m;
    error_page  404              /404.html;
    error_page  500 502 503 504  /500.html;
    try_files   $uri/index.html $uri @unicorn;

    location @unicorn {
        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;
    }
}
server
[user_name|~]$ cd /var/lib
[user_name|lib]$ sudo chmod -R 775 nginx #パーミッション調整

4.5 Unicornの導入/設定

server
[user_name@vm_name app_name]$  vi Gemfile
---------------------
group :production do
    gem 'unicorn'
end
---------------------
# gem: command not found と言われたら再度bash_profileを通す
[user_name@vm_name app_name]$ source ~/.bash_profile

[user_name@vm_name app_name]$ gem install bundler
[user_name@vm_name app_name]$ bundle install
[user_name@vm_name app_name]$ vim config/unicorn.conf.rb
/var/www/app_name/config/unicorn.conf.rb
$worker = 2
$timeout = 30
$app_dir = '/var/www/<App_name>'
$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) && 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) && ActiveRecord::Base.establish_connection
end

4.6 Mysqlを積む

server
#4.6.1 元々CentOSに入ってるmariadbを削除する
[user_name@vm_name app_name]$ rpm -qa | grep maria
mariadb-libs-5.5.50-1.el7_2.x86_64
[user_name@vm_name app_name]$ sudo yum remove mariadb-libs
[user_name@vm_name app_name]$ rm -rf /var/lib/mysql/

#4.6.2 MySQL5.7インストール
[user_name@vm_name app_name]$ sudo yum localinstall http://dev.mysql.com/get/mysql57-community-release-el7-7.noarch.rpm
[user_name@vm_name app_name]$ yum install mysql mysql-devel mysql-server mysql-utilities
[user_name@vm_name app_name]$ rpm -qa | grep mysql
[user_name@vm_name app_name]$ mysqld --version

#4.6.3 初期化
[user_name@vm_name app_name]$ mysqld --user=mysql --initialize

#4.6.3 自動起動、再起動
[user_name@vm_name app_name]$ sudo systemctl enable mysqld.service
[user_name@vm_name app_name]$ sudo systemctl restart mysqld.service

#4.6.5 一旦mysqlのpwを無効化(今後しっかりmysql側で設定する必要あり。)
my.cnf の [mysqld]ブロックに skip-grant-tables を追記して、mysqldを再起動(<-sudo systemctl restart mysqld.service)
# my.cnfの場所を調べる
[user_name@vm_name app_name]$ mysql --help | grep my.cnf

#4.6.6 エラー対応
------------------------------------------------------------------------------------------------
[問題1]
Your password has expired. To log in you must change it using a client that supports expired passwords.

[解決策1]
my.cnf の [mysqld]ブロックに skip-grant-tables を追記して、mysqldを再起動(<-sudo systemctl restart mysqld.service)
------------------------------------------------------------------------------------------------
[問題2]
LoadError: libmysqlclient.so.18: cannot open shared object file

[解決策2]
mysqlをgem経由で入れnative extensionsを反映させる必要あり

[user_name@vm_name app_name]$ bundle exec gem uninstall mysql2
[user_name@vm_name app_name]$ bundle install
------------------------------------------------------------------------------------------------

#4.6.7 db作成
[user_name@vm_name app_name]$ bundle exec rake db:create RAILS_ENV=production
[user_name@vm_name app_name]$ bundle exec rake db:migrate RAILS_ENV=production

4.7 CSS/JSをコンパイル

server
[user_name@vm_name app_name]$ bundle exec rake assets:precompile RAILS_ENV=production

4.8 Unicorn起動

server
[user_name@vm_name app_name]$ bundle exec unicorn_rails -c /var/www/<your_app_name>/config/unicorn.conf.rb -D -E production
# 起動しているかチェック (何も表示されなければunicornが起動していない)
[user_name@vm_name app_name]$ ps -ef | grep unicorn | grep -v grep

4.9 Nginx再起動

server
# 自動起動設定
[user_name@vm_name app_name]$ sudo systemctl enable nginx
# 起動
[user_name@vm_name app_name]$ sudo systemctl start nginx
[user_name@vm_name app_name]$ sudo service nginx reload

# ここで13 permission deniedでerror.logが埋まり、「bad gateway」が表示され続ける場合があります。
# この際にベストではないですが、SELinuxをdisabledにした際に解決しました。(それ以外の方法があれば教えていただきたいです🙇)
# 参考リンク → https://qiita.com/hanaita0102/items/5d3675e4dc1530b255ba

これで本番環境が構築できたかと思います。

適当なapiを返してくれるサーバー構築例↓

エラーが出た際は、下記を参考にデバッグをすれば問題ないので、各自調べてください。

nginx   なら /var/log/nginx/error.log
unicornなら  /log/unicorn.log

AWSやVPSで普段環境構築してる人からしたら、ほとんど難しいところはないかと思います。
今後

GKE・k8sによるオートスケールの設定
Dockerによる環境開発改善・デプロイ改善
Cloud Storageによる外部ファイルサーバ利用

などをする際に、便利さを身にしみて感じるのではと思います。
上記の方法以外にGCEのVMインスタンスを立てて、サーバー起動(railsなどで利用できる状態)にするまでの方法があるかと思います。
自分自身、インフラ経験数ヶ月なので、もし他の方法や考え方/運用方法などがあれば教えていただきたいです。🙇

では最後の投稿を楽しみにしていてください。

本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【パフォーマンス改善編】