普段はバックエンドと最近フロントエンドも始めたエンジニアです。その他にも前からインフラの技術に興味があり、NuxtアプリケーションをVPS環境(Lightsail)へ自動デプロイが行えるか試してみました。世の中には、Netlify・Firebase Hosting・zeitがあり、VPSへのデプロイは少しニッチですが、なにかの参考になればと思います。
最終的なプロジェクト構成やCIの設定情報はGitHubにあります。
全体フロー
- GitHubにpushを行う
- CircleCIが起動する
- 単体テストを実行する
- DockerHubにNuxtアプリケーションをPushする
- Lightsail環境にデプロイを行う
- 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でアクセスできるようになります。
以下のページが表示されれば成功です。
単体テストの用意
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.json
のscript
の箇所にtest
を追加します。
"scripts": {
...
"test": "NODE_PATH=. jest"
},
Jest実行時にbabelが有効でないとシンタックスエラーでテストが落ちるため、package.json
にbabelの設定を追加します。
"babel": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
Storeの単体テスト
store/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
でテストを実行し、
テストが通ることを確認しテストの準備は終了
NuxtアプリケーションのDockerize
簡単なテストが通るNuxtアプリケーションが準備できたので、これをDockerizeし本番に展開できるようにします。
DockerHubにnode:11-alpine
があります。このイメージをベースにして、Nuxtアプリケーション用のDockerfileを用意します。
Dockerfileでは、日本時刻の設定・ソースコードのコピー・npmのプロダクション用インストール・nuxtのbuild or startを行っています。
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
を選択し、namespace
とrepository name
を入力しCreate
を選択します。
Docker Hubでの作業は以上です。
Lightsailの用意
次はデプロイ先である本番環境の用意を行います。今回は安価で使えるVPSサーバのLigjtsailを使用します。AWS ConsoleよりLightsailにログインを行い、インスタンスの作成を選択します。
次にインスタンスロケーション・インスタンスイメージを選択します。
- インスタンスロケーションを
東京、ゾーンA
- インスタンスイメージを
Linux/Unix
のUbuntu
を選択
インスタンス作成時に、CIからデプロイ可能にするためにdocker swarm
を構成します。以下に用意している起動スクリプトを貼り付けます。
起動スクリプト
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キーをダウンロードします。
インスタンスの作成を選択します。
IPアドレスをメモ
サーバが起動するとIPアドレスが割り振られます。CircleCI上からデプロイ先サーバを識別するために使用します。
サーバの準備は完了です。
CircleCIの設定
ここまでに準備した、単体テスト・Dockerfile・Docker Hub・本番環境の各構成要素をCircleCIを使って自動デプロイを構成します。ますは、CircleCIにGitHubアカウントでログインします。
ログインが完了するとダッシュボードが表示されます。
Add Project
よりプロジェクト追加画面に移動し
nuxt-sample-project
のSet Up Project
を選択します。
Operating System
にLinux
をLanguage
にNode
を選択し、とりあえず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 横にある設定アイコンを選択します。
Build Setting -> Environment Variables より環境変数の設定画面を開きます
Add Variableから、DOCKER_USER
・DOCKER_PASS
・STATIC_IP_ADDRESS
を登録します。
デプロイ先のサーバとSSHで接続してデプロイ処理を行うため鍵の登録をします。
PERMISSIONS -> SSH Permissions より行います。
ここで発行される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の管理画面から確認することができます。
まとめ
すごくざっくりとですが、VPS環境にNuxt SSRアプリケーションを自動デプロイする構成を組むことができました。
また他にも
- Circleci上でカバレッジ情報をcodecovにアップロード
- Nightwatchを使用して、CI上でe2eテストの実行
- sequelizeでのDBのマイグレーション
といったことも可能で、NuxtのCircle CIの設定情報が参考になります。