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


Hakusan Mafiaアドベントカレンダー5日目を余語 [<a href="http://qiita.com/yogoken" target="_blank">Qiita</a>|<a href="https://facebook.com/yogoken" target="_blank">facebook</a>|<a href="https://github.com/yogoken" target="_blank">github</a>] が担当します！

※以下に、GCP/インフラ 初心者の考察であるため、的を外している点ありましたら
突っ込みを頂けますと幸いですm(_ _)m

# **`GCPでサーバーを立てる`**
<img src="https://qiita-image-store.s3.amazonaws.com/0/117667/246dce55-1b7c-5c46-afb7-26366e38df02.png">

<img src="https://i.gyazo.com/bbbcb3d65118965e78d1ab882013ea4d.png" width=50%>

適当なものを返してくれるapiを事前に作っていて、それをインスタンスに乗せました。
<img src="https://i.gyazo.com/5ddd56992db72440134ffffae3cfc75f.png" width=30%>

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

**`過去の記事`**
**<a href="https://qiita.com/yogoken/items/2f80c1dd4b98c459028d" target="_blank">本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【SSL証明書編】</a>**
**本番環境を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インスタンス作成`**
<a href="https://gyazo.com/0581708139112c11db672d5cdcfb7ea6"><img src="https://i.gyazo.com/0581708139112c11db672d5cdcfb7ea6.png" alt="https://gyazo.com/0581708139112c11db672d5cdcfb7ea6" width="446"/></a>

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

<a href="https://gyazo.com/5cc259ec583d4635073f1876cbbd28e6"><img src="https://i.gyazo.com/5cc259ec583d4635073f1876cbbd28e6.png" alt="https://gyazo.com/5cc259ec583d4635073f1876cbbd28e6" width="494"/></a>

##**`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 コマンドを表示」という箇所でコマンドをコピーして、ローカルで接続して見てください。

```shell-session: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
```

↓

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

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


```shell-session: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 新規ユーザー作成`**

```shell-session: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関連含む)`**

```shell-session: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`**

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

※ node.jsのバージョンなどを指定したい際は、nvmで管理するのが良いかと ↓参考
https://qiita.com/ozawan/items/86ca7551d59005128892

#### **`4.2.3 rbenv/ruby-build`**

```shell-session: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`**

```shell-session: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| ~ ]$ source ~/.bash_profile
[user_name| ~ ]$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]
```


###**`4.3 Gitとインスタンスの紐ずけ`**

**ssh接続 → git clone**


```shell-session: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]$ chmod 600 config
[user_name@vm_name .ssh]$ ssh -T github
Hi xxxx! You've successfully authenticated, but.....

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

###**`4.4 Nginxを積む`**

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

Nginxの設定は最小限です。

```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;
    }
}
```

```shell-session:server
[user_name|~]$ cd /var/lib
[user_name|lib]$ sudo chmod -R 775 nginx #パーミッション調整
```

###**`4.5 Unicornの導入/設定`**

```shell-session: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
```

```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を積む`**
```shell-session: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
```

※ 参考
https://qiita.com/tiwu_official/items/5ff3fa38611de058704a
https://qiita.com/_am_/items/bd9b8828ee9d958a0ea5


###**`4.7 CSS/JSをコンパイル`**

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

###**`4.8 Unicorn起動`**

```shell-session: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再起動`**

```shell-session: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を返してくれるサーバー構築例↓
<img src="https://i.gyazo.com/5ddd56992db72440134ffffae3cfc75f.png" width=30%>

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

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

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

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

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

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

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