XserverでCI/CD化して自動デプロイを実現する方法

Xserverへのアプリケーションの継続的デプロイを、GitHub Actionsのワークフローを活用して実現する方法を解説します。

この記事では、ReactフロントエンドとNest.JSバックエンドを含むプロジェクトの自動デプロイ方法を詳しく説明します。

GitHub Actionsの設定

まず、GitHub Actionsの設定を行います。

リポジトリの「Settings」タブから「Secrets and variables」→「Actions」を選択します。

以下の情報をActions secretsとして設定します。

※ 変数名は任意です。

  • XSERVER_HOST: Xserverのホスト名
  • XSERVER_PORT: SSHポート番号
  • XSERVER_USER: Xserverのユーザー名
  • XSERVER_SSH_KEY: SSH秘密鍵
  • XSERVER_SSH_PASSPHRASE: SSH鍵のパスフレーズ(必要な場合)

ワークフローの設定

次に、以下ファイルを作成し、デプロイワークフローを設定します。

  • .github/workflows/deploy.yaml

以下に、ワークフローの主要な部分を順番に説明します。

1. ワークフローのトリガー設定

何をトリガーにワークフローを実行するか、任意に設定してください。

on:
  pull_request:
    types: [ closed ]
    branches: [ main ]
  workflow_dispatch:

jobs:
  deploy:
    if: |
      (
        github.event_name == 'pull_request' && 
        github.event.pull_request.merged == true && 
        startsWith(github.event.pull_request.head.ref, 'release/')
      ) || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest

上記例では、mainブランチへのreleaseブランチのプルリクエストがマージされた時、または手動で実行された時にトリガーされます。

2. 環境のセットアップ

Node.jsの設定とYarnのキャッシュを設定します。

steps:
- uses: actions/checkout@v3

- name: Use Node.js
  uses: actions/setup-node@v3
  with:
    node-version: '16.20.2'

- name: Get yarn cache directory path
  id: yarn-cache-dir-path
  run: echo "::set-output name=dir::$(yarn cache dir)"

- uses: actions/cache@v3
  id: yarn-cache
  with:
    path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-yarn-

Xserver環境では、Node.jsが実行できる最新バージョンは16.20.2となるため、以下のバージョンを設定しています。

node-version: '16.20.2'

3. 環境変数の取得

Actions secretsの設定値を使用して、XserverへSSH接続します。

env.productionを取得して、環境変数を参照できるようにします。

環境変数を使用していない場合、省略してください。

- name: Get .env file from production server
  env:
    HOST: ${{ secrets.XSERVER_HOST }}
    USER: ${{ secrets.XSERVER_USER }}
    PORT: ${{ secrets.XSERVER_PORT }}
    KEY: ${{ secrets.XSERVER_SSH_KEY }}
    PASSPHRASE: ${{ secrets.XSERVER_SSH_PASSPHRASE }}
  run: |
    echo "$KEY" > deploy_key
    chmod 600 deploy_key
    mkdir -p ~/.ssh
    ssh-keyscan -p $PORT $HOST >> ~/.ssh/known_hosts
    eval $(ssh-agent -s)
    echo "$PASSPHRASE" | ssh-add deploy_key
    scp -P $PORT $USER@$HOST:/home/~ 各自のディレクトリ ~/.env.production ./.env.production

- name: Set environment variables from .env.production
  run: |
    export $(grep -v '^#' .env.production | xargs)
    echo "REACT_APP_ENV=$REACT_APP_ENV" >> $GITHUB_ENV
    echo "REACT_APP_API_HOST=$REACT_APP_API_HOST" >> $GITHUB_ENV
    echo "REACT_APP_API_PORT=$REACT_APP_API_PORT" >> $GITHUB_ENV
    ...各自の環境変数
  shell: bash

※ 「~ 各自のディレクトリ ~」箇所を書き換えてください。

4. フロントエンドのビルド

フロントエンドのディレクトリに移動し、依存関係をインストールしてビルドを実行します。

- name: Build Frontend
  working-directory: ./frontend
  env:
    REACT_APP_ENV: ${{ env.REACT_APP_ENV }}
    REACT_APP_API_HOST: ${{ env.REACT_APP_API_HOST }}
    REACT_APP_API_PORT: ${{ env.REACT_APP_API_PORT }}
    ...各自の環境変数
  run: |
    yarn install --frozen-lockfile
    yarn build

5. バックエンドのビルド

バックエンドのディレクトリに移動し、同様にビルドを実行します。

- name: Build Backend
  working-directory: ./backend
  run: |
    yarn install --frozen-lockfile
    yarn build

6. Xserverへのデプロイ

ビルドしたフロントエンドとバックエンドのファイルをXserverにデプロイし、バックエンドアプリケーションをPM2で起動または再起動します。

また、データベースのマイグレーションも実行しています。

- name: Deploy to Xserver
  env:
    HOST: ${{ secrets.XSERVER_HOST }}
    USER: ${{ secrets.XSERVER_USER }}
    PORT: ${{ secrets.XSERVER_PORT }}
    KEY: ${{ secrets.XSERVER_SSH_KEY }}
    PASSPHRASE: ${{ secrets.XSERVER_SSH_PASSPHRASE }}
  run: |
    # SSHの設定
    echo "$KEY" > deploy_key
    chmod 600 deploy_key
    mkdir -p ~/.ssh
    ssh-keyscan -p $PORT $HOST >> ~/.ssh/known_hosts
    
    eval $(ssh-agent -s)
    echo "$PASSPHRASE" | ssh-add deploy_key

    # バックエンドディレクトリの作成
    ssh -p $PORT $USER@$HOST "mkdir -p /home/~ 各自のディレクトリ ~/backend"
    
    # フロントエンドのデプロイ
    rsync -avz -e "ssh -p $PORT" 
      ./frontend/build/ $USER@$HOST:/home/~ 各自のディレクトリ ~/public_html/~ 各自のディレクトリ ~/
    
    # バックエンドのデプロイ
    rsync -avz -e "ssh -p $PORT" 
      ./backend/dist/ $USER@$HOST:/home/~ 各自のディレクトリ ~/backend/dist/
    rsync -avz -e "ssh -p $PORT" 
      ./backend/package.json $USER@$HOST:/home/~ 各自のディレクトリ ~/backend/
    rsync -avz -e "ssh -p $PORT" 
      ./backend/yarn.lock $USER@$HOST:/home/~ 各自のディレクトリ ~/backend/
    rsync -avz -e "ssh -p $PORT" 
      ./backend/ecosystem.config.js $USER@$HOST:/home/~ 各自のディレクトリ ~/backend/
    
    # PM2でバックエンドを起動または再起動
    ssh -p $PORT $USER@$HOST "cd /home/~ 各自のディレクトリ ~/backend && 
      if pm2 list | grep -q 'アプリ名'; then 
        pm2 reload ecosystem.config.js --only アプリ名; 
      else 
        pm2 start ecosystem.config.js --only アプリ名; 
      fi"

    # バックエンドの依存関係をインストール
    ssh -p $PORT $USER@$HOST "cd /home/~ 各自のディレクトリ ~/backend && 
      yarn install --frozen-lockfile"

    # データベースマイグレーションの実行
    ssh -p $PORT $USER@$HOST "cd /home/~ 各自のディレクトリ ~/backend && 
      NODE_ENV=production npx typeorm migration:run -d dist/src/typeorm.config.js"

ecosystem.config.jsは、PM2の設定ファイルです。

以下のように設定して、main.jsで起動するサーバーを永続化します。

module.exports = {
  apps: [
    {
      name: 'アプリ名',
      script: 'dist/src/main.js',
      env: {
        NODE_ENV: 'production'
      }
    }
  ]
};

※ 「~ 各自のディレクトリ ~」、「アプリ名」箇所を書き換えてください。

7. クリーンアップ

最後に、SSHエージェントを終了し、一時的に作成したデプロイキーを削除します。

- name: Cleanup
  if: always()
  run: |
    eval $(ssh-agent -k)
    rm -f deploy_key

注意点

  • セキュリティ上の理由から、本番環境の.envファイルをGitHubリポジトリに直接保存することは避け、Xserverから取得しています。
  • PM2を使用してバックエンドアプリケーションを管理しています。
    • PM2についてはこちらを参考にしてください。

以上の設定により、GitHubのmainブランチへのマージ時に自動的にXserverへデプロイが行われるCI/CDパイプラインが構築されます。