Help us understand the problem. What is going on with this article?

LightsailにDockerizeされたNuxt SSR アプリケーションを自動デプロイする

More than 1 year has passed since last update.

普段はバックエンドと最近フロントエンドも始めたエンジニアです。その他にも前からインフラの技術に興味があり、NuxtアプリケーションをVPS環境(Lightsail)へ自動デプロイが行えるか試してみました。世の中には、NetlifyFirebase Hostingzeitがあり、VPSへのデプロイは少しニッチですが、なにかの参考になればと思います。
最終的なプロジェクト構成やCIの設定情報はGitHubにあります。

全体フロー

Untitled Diagram.png

  1. GitHubにpushを行う
  2. CircleCIが起動する
  3. 単体テストを実行する
  4. DockerHubにNuxtアプリケーションをPushする
  5. Lightsail環境にデプロイを行う
  6. Nuxtアプリケーションが自動で公開される

自動デプロイ環境の構築

はじめにデプロイしたいNuxtアプリケーションの準備を行います。

MacにNodeのインストール

Nodeのオフィシャルサイトより11.xをダウンロードしインストールを完了してください。ダウンロードしたファイルをダブルクリックし、指示に従うとインストールが完了します。

ターミナルを起動し、バージョンが表示されればNodeのインストールが完了しています。

node --version 
# v11.2.0

Nuxtのインストール

公式の設置手順に沿ってインストールを行います。

npx create-nuxt-app nuxt-sample-project

いくつか質問が来るので、以下は回答例です。実際は自分のプロジェクトやスキルに合わして回答して貰えればと思います。

? Project name nuxt-sample-project
? Project description My phenomenal Nuxt.js project
? Use a custom server framework none
? Use a custom UI framework none
? Choose rendering mode Universal
? Use axios module no
? Use eslint no
? Use prettier no
? Author name lnkusuin
? Choose a package manager npm

開発環境の起動

以下コマンドで開発用サーバを起動します

cd nuxt-sample-project
npm run dev

サーバが立ち上がると、http://localhost:3000でアクセスできるようになります。

b0e9a7f3191e780803dca720d64ce2d0.png

以下のページが表示されれば成功です。

1d398388d44bb34c8923ee308d716359.png

単体テストの用意

CircleCI上で単体テストを実行するために、簡単なテストを用意します。

まずはテストに必要なパッケージをインストールします。

npm install --save-dev jest babel-jest "babel-core@^7.0.0-bridge.0" @babel/core @vue/test-utils --save regenerator-runtime

npmスクリプトからテストが実行できるようにpackage.jsonscriptの箇所にtestを追加します。


"scripts": {
    ...
    "test": "NODE_PATH=. jest"
  },

Jest実行時にbabelが有効でないとシンタックスエラーでテストが落ちるため、package.jsonにbabelの設定を追加します。


  "babel": {
    "presets": [
      [
        "@babel/preset-env",
        {
          "targets": {
            "node": "current"
          }
        }
      ]
    ]
  }

Storeの単体テスト

store/index.jsにテスト対象ファイルを作成します。

index.js
export const state = () => ({
  counter: 0
})

export const mutations = {
  increment (state) {
    state.counter++
  }
}

__tests__/store/test.jsにテストコードを用意します。

import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import { state, mutations } from 'store'

const localVue = createLocalVue()
localVue.use(Vuex)

let store = new Vuex.Store({
  state, mutations
})

test('カウンターが動作していることを確認する', () => {
  store.commit('increment')
  expect(store.state.counter).toBe(1)

  store.commit('increment')
  expect(store.state.counter).toBe(2)
});

npm run testでテストを実行し、

f067596029e6e5d4ed88326b23638adf.png

テストが通ることを確認しテストの準備は終了

NuxtアプリケーションのDockerize

簡単なテストが通るNuxtアプリケーションが準備できたので、これをDockerizeし本番に展開できるようにします。
DockerHubnode:11-alpineがあります。このイメージをベースにして、Nuxtアプリケーション用のDockerfileを用意します。

7c0813acab4b21bbc51844b083694842.png

Dockerfileでは、日本時刻の設定・ソースコードのコピー・npmのプロダクション用インストール・nuxtのbuild or startを行っています。

Dockerfile
FROM node:11-alpine
MAINTAINER lnkusuin

# 日本時刻の設定
RUN apk --update add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata && \
    rm -rf /var/cache/apk/*

# ポート解放
EXPOSE 3000

COPY app /home/node/app

WORKDIR /home/node/app

RUN rm -rf node_modules .nuxt package-lock.json

# NODEアプリケーションのプロジェクトパスを設定
ENV NODE_PATH .
# プロジェクトルート
ENV APP_ROOT /home/node/app

RUN npm install --production
RUN npm run build

CMD npm run start

DockerHubの準備

先程作成したDocker イメージファイルをDocker Hubに保存し、Circle CI上から保存、本番環境から取得できるようにします。
Docker Hubにログインし、右上のCreateから、Create Repositoryを選択し、namespacerepository nameを入力しCreateを選択します。

6c83a153a9583205463a55bcc1f546f6.png

Docker Hubでの作業は以上です。

Lightsailの用意

次はデプロイ先である本番環境の用意を行います。今回は安価で使えるVPSサーバのLigjtsailを使用します。AWS ConsoleよりLightsailにログインを行い、インスタンスの作成を選択します。

3a8eee73400c4a3e3d0aad08dee1534f.png

次にインスタンスロケーション・インスタンスイメージを選択します。

  • インスタンスロケーションを東京、ゾーンA
  • インスタンスイメージをLinux/UnixUbuntuを選択

912e2e3c23121246d44e507bc7620c77.png

インスタンス作成時に、CIからデプロイ可能にするためにdocker swarmを構成します。以下に用意している起動スクリプトを貼り付けます。

eec08637034d7de3db9b33c4f0d20d23 (1).png

起動スクリプト

run.sh
apt-get update
apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
apt-get update
apt-get install -y docker-ce
usermod -aG docker ubuntu

mkdir /app
mkdir /app/nginx
cat <<EOL > /app/nginx/default.conf
server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://app:3000;
        proxy_set_header Host \$host;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
    }
}
EOL

cat <<EOL > /app/docker-compose.yml
version: '3.1'
services:

  app:
    image: wakachan/nuxt-sample-project:release
    working_dir: /home/node/app
    deploy:
      replicas: 2

  nginx:
    image: nginx:1.15
    ports:
      - "80:80"
    volumes:
      - ./nginx:/etc/nginx/conf.d
    depends_on:
      - app
EOL

docker swarm init
docker stack deploy -c /app/docker-compose.yml sample

CircleCIからSSHでアクセスしデプロイ作業を可能にするために、SSHキーをダウンロードします。

7db4988e9866217a6ccb4cbee0ab9c44.png

インスタンスの作成を選択します。

3decf7a54810ad88a01b1aa1e72cf70c.png

IPアドレスをメモ

サーバが起動するとIPアドレスが割り振られます。CircleCI上からデプロイ先サーバを識別するために使用します。

e294ebbef57a22c3cdbc0f4500b93d6f.png

サーバの準備は完了です。

CircleCIの設定

ここまでに準備した、単体テスト・Dockerfile・Docker Hub・本番環境の各構成要素をCircleCIを使って自動デプロイを構成します。ますは、CircleCIにGitHubアカウントでログインします。

f21231a0263e9a792bab2d7cd317cfd7.png

ログインが完了するとダッシュボードが表示されます。

704bc1a9bcd89a315995ad99828f3020.png

Add Projectよりプロジェクト追加画面に移動し

5fbd919ff5469dcfc37bf0e22626438b.png

nuxt-sample-projectSet Up Projectを選択します。

46cbad0471c09c0400d28ca3ffe9f4fa.png

Operating SystemLinuxLanguageNodeを選択し、とりあえずstart buildを押します。まだ、nuxt-sample-projectにはCircleciの設定ファイルが存在しないため、ビルドは失敗します。とりあえずこれでGitHubとCircleCIの連携はこれで行えるようになり、次回Pushを行ったときに処理されるビルドスクリプトを用意します。

各種環境変数とSSHキーの設定

CircleCI上でNuxtアプリケーションのビルドを行った後、Docker Hubにイメージファイルをpushするためには先に、CircleCI上でDocker Hubへログインする必要があります。またCircleCI上からデプロイ先のサーバにSSH接続しデプロイ作業を行うためにデプロイ先のIPの登録も行います。これらの情報はCircleCIの環境変数設定で登録を行います。

Setting -> Followed Projects -> inkusu nuxt-project 横にある設定アイコンを選択します。

4f0179382fe934e02cd93da99ce984bb.png

Build Setting -> Environment Variables より環境変数の設定画面を開きます
Add Variableから、DOCKER_USERDOCKER_PASSSTATIC_IP_ADDRESSを登録します。

c117414cff4c2ad9ae8045711b32fee7.png

デプロイ先のサーバとSSHで接続してデプロイ処理を行うため鍵の登録をします。
PERMISSIONS -> SSH Permissions より行います。

8fcd25dc373700e406e6bbb22891c702.png

ここで発行されるFingerprint情報を使用してSSH接続を行います。

CIのコードを作成

ソースコードに戻り.circleci/config.ymlを作成し以下のコードを貼り付けます。

version: 2

defaults: &defaults
  working_directory: ~/project
  docker:
    - image: banian/node-headless-chrome

jobs:

  setup:
    <<: *defaults
    steps:
      - checkout

      # Restore cache
      - restore_cache:
          key: package-{{ checksum "nuxt-sample-project/package-lock.json" }}

      - run:
          name: Install Dependencies
          command: NODE_ENV=dev npm install
          working_directory: nuxt-sample-project

      # Keep cache
      - save_cache:
          key: package-{{ checksum "nuxt-sample-project/package-lock.json" }}
          paths:
            - "nuxt-sample-project/node_modules"

      # Persist files
      - persist_to_workspace:
          root: ~/project
          paths:
            - .

  check:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/project
      - run:
          name: unit test
          command: npm run test
          working_directory: nuxt-sample-project

  build:
    machine: true
    steps:
      - attach_workspace:
          at: ~/project
      - run:
          name: docker build
          command: docker build --no-cache -t wakachan/nuxt-sample-project:release -f Dockerfile .
      - run:
          name: docker login
          command: docker login --username=$DOCKER_USER --password=$DOCKER_PASS
      - run:
          name: docker push
          command: docker push wakachan/nuxt-sample-project:release

  deploy:
    machine: true
    steps:
      - add_ssh_keys:
          fingerprints:
            - "7e:8b:cc:15:65:0e:a2:fa:5c:f3:18:42:34:19:51:df"
      - run:
          name: delpoy
          command: >
            ssh ubuntu@$STATIC_IP_ADDRESS "cd /app/ & docker service update --image wakachan/nuxt-sample-project:release sample_app"

workflows:
  version: 2
  test-build-deploy:
    jobs:
      - setup

      - check:
          requires:
            - setup

      - build:
          requires:
            - check

      - deploy:
          requires:
            - build

デプロイ

これでNuxtアプリケーションを自動デプロイする準備が整いました。実際にコードをpushすることにより、Lightsail(VPS)環境に展開がされます。

デプロイの様子

実際のデプロイされる風景はCircleCIの管理画面から確認することができます。

b907730a07e7c982127009b1430ed45f.png

まとめ

すごくざっくりとですが、VPS環境にNuxt SSRアプリケーションを自動デプロイする構成を組むことができました。
また他にも

  • Circleci上でカバレッジ情報をcodecovにアップロード
  • Nightwatchを使用して、CI上でe2eテストの実行
  • sequelizeでのDBのマイグレーション

といったことも可能で、NuxtのCircle CIの設定情報が参考になります。

参考リソース

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away