Nginx,pm2を使ってNuxt.jsの本番環境を構築する

## はじめに

遅れてしまいましたが、
この記事はLinkbal Advent Calendar 2020の10日目の記事です。

Serverlessについての記事を書こうと思いましたが、準備に間に合わなかったので、このネタは次の記事で書かせていただきます。

本記事は`Nginx`,`pm2`を使って`Nuxt.js`のユニバーサルモードの本番環境を構築することについて紹介したいと思います。

## 環境
- Centos 7.x

## インストール
まず、以下の必要なものをインストールする

- Nginx

インストール

```bash
$ sudo yum install nginx
```

自動起動設定

```bash
$ sudo systemctl enable nginx
```

- Node.js（そして、自分の好みのパッケージマネージャーは`yarn`ですので、`yarn`もインストールする）

Node.jsのインストール

```bash
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
$ . ~/.nvm/nvm.sh
$ nvm
$ nvm install node
```

Yarnのインストール

```bash
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
$ sudo yum install yarn
```

- pm2: `Node.js`のプロセル状態、ログなどを管理するツールです。Nuxt.jsのユニバーサルモードでサーバーサイドレンダリングは`Node.js`で動かせるので、このツールを使うべきです。

```shell
$ npm install pm2@latest -g
```

## pm2の設定
プロジェクトのディレクトリ内にpm2設定用`ecosystem.config.js`ファイルを作成する

```js:ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'nuxt_app',
      cwd: '/path/to/project',
      exec_mode: 'cluster',
      instances: 2,
      script: './node_modules/.bin/nuxt',
      args: 'start -n ./tmp/nuxt_app.sock',
      env: {
	NODE_ENV: 'production'
      }
    }
  ]
}
```

- cwd: アプリケーションを動かせるディレクトリ
- exec_mode: `cluster`モードで動かせる
- instances: クラスターのインスタンス数
- script: アプリケーションを動かせるスクリプトのパス
- args: `script`の引数です。今回の紹介はTCPじゃなくて、`unix socket`で動かせます。理由は`unix socket`の方が効率が高いです。
- env: アプリケーションで使う環境変数。`NODE_ENV: 'production'`はNuxtの本番環境モードの設定のためです。

### pm2自動起動の設定
最後に、サーバーを再起動した際に、自動にプロセスを再起動するように設定します。

サーバーを再起動した場合、自動にプロセスを再起動する設定は以下の`pm2`コマンドで実現できます。

```shell
$ pm2 startup
$ pm2 save
```
ですが、`Unix Socket`で動かせる場合、うまくできなかったです。原因はおそらく`pm2`再起動の前に、既存のsocketファイルを削除しないといけないことです。

その代わりに、以下の簡単な起動スクリプトを作成して、サーバーの再起動の時に、pm2を再起動させます。

```bash:/etc/init.d/nuxt_client
#!/bin/bash
#
#
# description: PM2 next gen process manager for Node.js
# processname: pm2
#
### BEGIN INIT INFO
# Provides:          pm2
# Required-Start:    pm2
# Required-Stop:     
# Should-Start:      
# Should-Stop:
# Default-Start:     
# Default-Stop:
# Short-Description: PM2 init script
# Description: PM2 is the next gen process manager for Node.js
### END INIT INFO

# アプリケーション名
NAME=nuxt_app
# pm2のパス: `which pm2`コマンドでわかります。
PM2="/home/webuser/.nvm/versions/node/v15.4.0/bin/pm2"
# nodeのパス: `which node`コマンドでわかります。
NODE="/home/webuser/.nvm/versions/node/v15.4.0/bin/node"
# pm2を動かせるユーザー
USER=webuser
UNIX_SOCKET_PATH=/path/to/project/tmp/nuxt_app.sock
CONFIG_FILE_PATH=/path/to/project/ecosystem.config.js

start() {
    echo "Starting $NAME"
    su - $USER -c "$PM2 start $CONFIG_FILE_PATH --update-env"
    for i in {0..30}
      do
        echo "Waiting for creating socket file..."
        if [ -e $UNIX_SOCKET_PATH ]; then
            break
        fi
        sleep 1
    done
　　 # nginxのユーザーもアクセスできるように、権限を設定する
    chmod o+w $UNIX_SOCKET_PATH
}

stop() {
    su - $USER -c "$NODE $PM2 kill"
}

restart() {
    if [ -e $UNIX_SOCKET_PATH ]; then
       rm -rf $UNIX_SOCKET_PATH
    fi
    echo "Restarting $NAME"
    stop
    start
}

reload() {
    echo "Reloading $NAME"
    su - $USER -c "$PM2 reload all"
}

status() {
    echo "Status for $NAME:"
    $NODE $PM2 list
    RETVAL=$?
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status
        ;;
    restart)
        restart
        ;;
    *)
        echo "Usage: {start|stop|status|restart}"
        exit 1
        ;;
esac
exit $RETVAL
```

自動起動設定

```bash
$ sudo chkconfig --add nuxt_client
```


## Nginxの設定

Nginxの簡単な設定は以下のようです。

```conf:/etc/nginx/conf.d/nuxt_app.conf
upstream nuxt_client {
  server unix:/path/to/project/tmp/nuxt_app.sock fail_timeout=0;
}

server {
	listen 80;
	server_name example.com;

	index index.html;

	location / {
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $http_host;
		proxy_set_header X-NginX-Proxy true;
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_pass http://nuxt_client;
	}
}
```

## デプロイ

デプロイするには、`capistrano`を使います。

```rb:deploy.rb
# config valid for current version and patch releases of Capistrano
lock "~> 3.12.1"

set :application, "nuxt_app"
set :repo_url, "リポジトリのURL"
set :deploy_to, "/var/www/nuxt_app"
set :ssh_options, user: 'webuser'

set :branch, ENV['BRANCH'] || 'master'

# build app
after 'deploy:updated', 'init_app:build'
# restart pm2
after 'deploy:publishing', 'init_app:start'

namespace :init_app do
  task :build do
    on roles(:web) do
      execute "cd #{release_path}; yarn install"
      execute "cd #{release_path}; yarn nuxt build"
    end
  end
  task :start do
    on roles(:web) do
      sudo "/etc/init.d/nuxt_client restart"
    end
  end
end
```

以上です。


## リンク
- [pm2](https://pm2.keymetrics.io/docs/usage/quick-start/)
- https://nuxtjs.org/faq/deployment-pm2/