この記事を書くにあたって
【AWS?Docker?】ポートフォリオで必要な知識を自分なりに分かりやすくまとめる【terraform?CircleCI?】
CircleCI???Docker???って人はまずはこれを見てください。↑
勉強のための勉強はあんまり意味ない・・・。だからまず手を動かしとこ〜!
ということで今回はハンズオン形式で記事を書いていきます。
読んでくださる方がサクッと開発環境構築してさっさと開発に専念出来ると嬉しいです。
なるべくエラーで苦しまないよう、3回くらい自分の環境で再現しました。
恐らく三部作になるかと思います・・・長くて申し訳ないのですが。
第一弾:本記事
第二弾:【中編】Rails+Nuxt+MySQL+Dockerで作ったWEBサービスをECS・ECR・CircleCIで自動本番デプロイしてterraform化する
第三弾:【後編】Rails+Nuxt+MySQL+Dockerで作ったWEBサービスをECS・ECR・CircleCIで自動本番デプロイしてterraform化する
この記事で目指すゴール
- RailsとNuxtを使ってHello World!
- RailsをAPIモードで起動・NuxtでRailsのデータを引っ張ってくる。
- Railsでユーザーテーブルを作り、Nuxtでユーザーを作成する。
- rspecを導入してテストを実行してみる。
- CircleCIと連携して自動テストしてみる。
長いですが頑張りましょう!
使用環境
MacOS Catalina 10.15.5
Rails 6.0.3.2
@vue/cli 4.4.4
@nuxt/cli v2.14.4
Vuetify
Docker(forMac) version 19.03.8
docker-compose version 1.25.5
前提
- Dockerをインストール済み
DockerをMacにインストールする(更新: 2019/7/13) - CircleCIアカウント作成済み
いまさらだけどCircleCIに入門したので分かりやすくまとめてみた - gitアカウント作成済み
6.1 GitHub - アカウントの準備と設定
#完成品
gitに完成品あげときました。
qiita-sample-app
#Dockerで環境開発構築
初期設定
今回のゴール
app // 任意の名前で良い
├─docker-compose.yml
├─front
| ├─Dockerfile
└─back
├─Dockerfile
├─Gemfile
└─Gemfile.lock
作業ディレクトリ作成
mkdir app
cd app // appへ移動
mkdir front // front作成
mkdir back // back作成
touch ./back/Dockerfile
touch ./back/Gemfile
touch ./back/Gemfile.lock
mkdir ./back/environments
touch ./back/environments/db.env
touch ./front/Dockerfile
touch docker-compose.yml
Dockerfileの記述
/backのDockerfileを編集
# イメージの指定
FROM ruby:2.6.3-alpine3.10
# 必要パッケージのダウンロード
ENV RUNTIME_PACKAGES="linux-headers libxml2-dev make gcc libc-dev nodejs tzdata mysql-dev mysql-client yarn" \
DEV_PACKAGES="build-base curl-dev" \
HOME="/app" \
LANG=C.UTF-8 \
TZ=Asia/Tokyo
# 作業ディレクトリに移動
WORKDIR ${HOME}
# ホスト(自分のパソコンにあるファイル)から必要ファイルをDocker上にコピー
ADD Gemfile ${HOME}/Gemfile
ADD Gemfile.lock ${HOME}/Gemfile.lock
RUN apk update && \
apk upgrade && \
apk add --update --no-cache ${RUNTIME_PACKAGES} && \
apk add --update --virtual build-dependencies --no-cache ${DEV_PACKAGES} && \
bundle install -j4 && \
apk del build-dependencies && \
rm -rf /usr/local/bundle/cache/* \
/usr/local/share/.cache/* \
/var/cache/* \
/tmp/* \
/usr/lib/mysqld* \
/usr/bin/mysql*
# ホスト(自分のパソコンにあるファイル)から必要ファイルをDocker上にコピー
ADD . ${HOME}
# ポート3000番をあける
EXPOSE 3000
# コマンドを実行
CMD ["bundle", "exec", "rails", "s", "puma", "-b", "0.0.0.0", "-p", "3000", "-e", "development"]
/frontのDockerfileを編集
FROM node:12.5.0-alpine
ENV HOME="/app" \
LANG=C.UTF-8 \
TZ=Asia/Tokyo
ENV HOST 0.0.0.0
WORKDIR ${HOME}
RUN apk update && \
apk upgrade && \
npm install -g n && \
yarn install &&\
rm -rf /var/cache/apk/*
# あとから外します(ECS使うとき)
#RUN yarn run build
#EXPOSE 3000
#CMD ["yarn", "dev"]
Gemfileの記述
source 'https://rubygems.org'
gem 'rails', '~>6'
docker-compose.ymlの記述
version: "3"
services:
db:
image: mysql:5.7
env_file:
- ./back/environments/db.env
restart: always
volumes:
- db-data:/var/lib/mysql:cached
back:
build: back/
# rm -f tmp/pids/server.pidしとくとrailsのサーバ消し損ねたときに便利
command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
env_file:
- ./back/environments/db.env
volumes:
- ./back:/app:cached
depends_on:
- db
# ホストコンピュータのポート:Docker内のポート
ports:
- 3000:3000
front:
build: front/
command: yarn run dev
volumes:
- ./front:/app:cached
ports:
# ホストコンピュータのポート:Docker内のポート
- 8080:3000
depends_on:
- back
volumes:
public-data:
tmp-data:
log-data:
db-data:
豆知識
Docker内のネットワークは、ホストマシンのネットワークと異なっています。
例えばDockerで起動したrailsのプロセスは仮想環境(コンテナ)内のlocalhost(127.0.0.1):3000で起動されます。ホストマシンのlocalhostとは異なっています。
ですが、docker-compose.yml内にててポートマッピングしてあげると解決できます。
ホストポート3000、コンテナポート3000という指定をしてあげて、0.0.0.0にバインディングすると、まるでホストマシンで起動したかのようにlocalhostでアクセスする事が出来ます。
これをポートフォワーディングといいます。
仮想環境で構築したプロセスに対し、バインディングアドレスを指定して、ポートフォワーディングをすることで、正常にアクセス出来ます。
参考:
rails s -b 0.0.0.0 のオプション-bの意味
では、8080:3000は、どういうことでしょう。
これは、ホストポート8080に対し、コンテナポート3000をポートマッピング(≒ポートフォワーディング)しています。
つまり、Dockerコンテナ内で構築されたnuxtプロセスは3000番ポートを利用していますが、ホストマシンで閲覧する際は8080で見れるようにしよ〜ってことです。(Railsも3000だとホストマシン内でダブっちゃうからエラーになるよ)
db.envを設定しよう
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_USER=username
MYSQL_PASSWORD=userpassword
DockerComposeでbuildしてみよう
docker-compose build
ちょっと時間かかります。
buildは何度もやる必要はなく、GemfileだったりDockerfile等変えたときに実行しよう!
(自分は初めの時何度もやって時間ロスしまくりました。)
初めて出てきたdocker-composeコマンドですが、これからはこのコマンドめっちゃ使います。
hello, Nuxt
今回のゴール
front以下にnuxtアプリを作る
NuxtでHello World!
最終的にこんな感じのディレクトリ構成になります。
...略
front
├─Dockerfile
...略
├─components // vueのコンポーネントを置くところ
├─layouts // indexでデフォルトで呼び出されるところ
├─pages // ページ追加するときはここに追加する
├─plugins // yarnなどで追加するpluginの設定ファイルをおくところ
├─nuxt.config.js // nuxt自体の設定ファイル
├─package.json // yarn・npmのパッケージ依存関係を設定するところ
...略
nuxtのアプリを作成しよう
docker-compose build
docker-compose run front npx create-nuxt-app@v2.15.0
# version3だと、空ディレクトリじゃないとエラーとなるためバージョン指定してます。
# 以下のような選択画面が出ます。
# アプリの名前、好きなのでOK!ブラウザ上で開いた時のタイトルになる
? Project name --> sample_app
# アプリの説明、好きなのでOK!ブラウザ上で開いた時のサブタイトルになる
? Project description --> sample_app
# アプリの作成者、好きなのでOK!
? Author name --> me
# npmとyarnが選べるけど、yarnの方が早くて良いらしいのでyarn選択
? Choose the package manager --> Yarn
? Choose UI framework --> Vuetify.js
? Choose custom server framework --> None
? Choose Nuxt.js modules --> Axios
? Choose linting tools --> -
? Choose test framework --> None
? Choose rendering mode --> SPA
豆知識
docker-compose run : Dockerマシン上で次のコマンドを実行してねという意味
front : この名前のコンテナ上で実行してねという意味
Railsコマンドとか打ちたいときは、
docker-compose run back rails db:create
ちなみにコンテナに入ってデバッグしたいときは、
docker-compose exec back sh
みたいにする。
いざ、Hello nuxt
docker-compose up front
先ほどbuildしたfrontイメージを起動します。
http://localhost:8080/
hello, Rails
今回のゴール
back以下にrailsアプリを作る
RailsでHello World!Yay!
...略
front
├─Dockerfile
├─Gemfile
├─Gemfile.lock
...略
├─app // controllerやviewなどが入っているところ
├─config // 起動時に読み込まれたりするものが入っているところ
├─db // dbテーブル情報など
├─environments // DB接続情報の環境変数
...略
Railsアプリの作成
# APIモードで作成します。 --api外すとview等、画面描画に必要なものもインストールされます。
# データベースはMySQLを選択。
docker-compose run back rails new . -f -d mysql --api
docker-compose build
この状態で、既にGemfileの中身が初期状態から書き換わっています。確認してみてね。
DB設定
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= ENV.fetch('MYSQL_USER') { 'root' } %> #追加します。
password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %> #追加します。
host: db #変更します。
db.envで記載したuserに、権限を付与します。
touch ./back/db/grant_user.sql
GRANT ALL PRIVILEGES ON *.* TO 'username'@'%';
FLUSH PRIVILEGES;
# コンテナ起動します。
docker-compose up
# dbコンテナで、上記のSQLを流します。
docker-compose exec db mysql -u root -p -e"$(cat back/db/grant_user.sql)"
# DBの作成。
docker-compose run back rails db:create
いざ、Hello Rails
http://localhost:3000/
にアクセス!以下のお馴染みの画面が出ていたら成功です。
Nuxt->Rails間で、APIを使ってユーザーを取得
ここからは実際に実装に移ってみます。
なるべく、その後も機能追加しやすいようにしているので、
新しく別の機能を追加する際は同じような方法で試してみてください。
DBにUsersテーブルを作ろう
docker-compose run back rails g scaffold user name:string
# こんな感じで色々なファイルを作成してくれます。
Running via Spring preloader in process 20
invoke active_record
create db/migrate/20200902105643_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
invoke resource_route
route resources :users
invoke scaffold_controller
create app/controllers/users_controller.rb
invoke test_unit
create test/controllers/users_controller_test.rb
# db/migrate以下の~create_users.rbを実行し、DBにテーブルを作成してくれます。
# 一度実行すると、同じファイルは再実行されません。
# 2020~の数字の部分が、変更されると、再実行可能となりますが、DBに同テーブルがある場合エラーになります。
docker-compose run back rails db:migrate
これでapp_developmentというDBに、usersというテーブルが生まれました。簡単!
Usersのデータを取得しよう
http://localhost:3000/users/
へアクセスしてみましょう。APIモードなので、真っ白の画面に空のjsonが返ってきているはずです。
なぜこのような画面になるのでしょうか?
それは、
./back/config/routes.rbの中に秘密があります。
Rails.application.routes.draw do
resources :users
end
resoursesに :usersという記述があります。
これは、./back/app/users_controller.rbというコントローラと、/usersのようなパスを紐づけています。
なので、機能追加の際は同様の手順で行うと考えれば簡単です。
# このコマンドで、現在どのrouteが登録されているのか確認出来ます。
docker-compose run back rails routes
詳細は省きますが、チュートリアルなどで確認していただけると幸いです。
NuxtからUsersのデータを参照してみよう
###下準備
nuxtからrailsへの疎通は、いくつか下準備が必要です。
焦らずいきましょう。
####@nuxtjs/axiosのインストール
API通信には、axiosというpluginを利用します。
docker-compose run front yarn
docker-compose run front yarn add @nuxtjs/axios
./front/package.jsonに以下が追加されたのを確認して下さい。
...
"@nuxtjs/axios": "^5.12.2"
...
####./front/nuxt.config.jsの修正
nuxtを初回起動時に読み込まれる設定ファイルであるnuxt.config.jsを修正します。
...
modules: [
'@nuxtjs/axios' // 追加
],
...
####./front/plugins/axios.jsの追加
./front/plugins/以下にaxios.jsというファイルを新しく作成して下さい。
import axios from "axios"
export default axios.create({
baseURL: "http://localhost:3000"
})
####./back/Gemfileにrack-corsを追加
...
gem 'rack-cors' # 追加
...
docker-compose build
./back/config/initializers/cors.rbに設定を追加
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: %i[get post put patch delete options head]
end
end
docker-compose up
###画面開発
実際に画面を新しく作ってみましょう。
./front/pages以下にusers.vueを新しく作成して下さい。
<template>
<v-container>
<v-row align="center" justify="center">
<v-col cols="12">
<h1>Hello, Qiita! </h1>
</v-col>
</v-row>
<v-card
class="mx-auto"
max-width="300"
tile
>
<v-list rounded>
<v-subheader>USERS</v-subheader>
<v-list-item-group color="primary">
<v-list-item
v-for="user in users"
:key="users.id"
@click=""
>
<v-list-item-content>
<v-list-item-title v-text="user.name"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-container>
</template>
<script>
import axios from "~/plugins/axios"
export default {
data() {
return {
users: []
}
},
created() {
// ユーザーをaxiosで取得
axios.get("/users").then(res => {
if (res.data) {
this.users = res.data
}
})
}
}
</script>
NuxtからUsersのデータを追加しよう
上記でUsersのデータは取得できました・・・が、何も入っていないので、何も表示されないはずです。
今度はAPIでデータをDBへ保存してみましょう。
<template>
<v-container>
<v-row align="center" justify="center">
<v-col cols="12">
<v-text-field
label="Username"
v-model="name"
prepend-icon=""
type="text"
/>
<v-btn color="primary" @click="createUser">ADD USER</v-btn>
</v-col>
<v-col cols="12">
<h1>Hello, Qiita! </h1>
</v-col>
</v-row>
<v-card
class="mx-auto"
max-width="300"
tile
>
<v-list rounded>
<v-subheader>USERS</v-subheader>
<v-list-item-group color="primary">
<v-list-item
v-for="user in users"
:key="users.id"
@click=""
>
<v-list-item-content>
<v-list-item-title v-text="user.name"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-container>
</template>
<script>
import axios from "~/plugins/axios"
export default {
data() {
return {
name: "",
users: []
}
},
created() {
// ユーザーをaxiosで取得
axios.get("/users").then(res => {
if (res.data) {
this.users = res.data
}
})
},
methods: {
// ユーザーをaxiosで登録
createUser(){
axios.post("/users", {name: this.name})
.then(res => {
if (res.data) {
this.users.push(res.data)
}
})
}
}
}
</script>
これでユーザーを登録出来るようになりました。やりました!
ちなみにnuxtの画面は、nuxt.config.js内のdarkテーマをfalseにすると白くなります。
#自動テスト
さぁ、次は自動テストの設定・追加をしてみましょう!
####./back/Gemfileにrspec-railsとfactory_bot_railsを追加
...
gem 'rspec-rails' # 追加
gem 'factory_bot_rails' # 追加
...
docker-compose down
docker-compose build back
docker-compose up
docker-compose exec back rails generate rspec:install
# 以下のファイルが追加されます
Running via Spring preloader in process 18
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
####Usersのテストを書いてみよう
まずはテストファイルを作成します。
mkdir ./back/spec/models
mkdir ./back/spec/requests
mkdir ./back/spec/factories
touch ./back/spec/models/user_spec.rb
touch ./back/spec/requests/user_spec.rb
touch ./back/spec/factories/users.rb
require 'rails_helper'
RSpec.describe User, type: :model do
it '正常テスト' do
@user = User.new(
name: 'test'
)
expect(@user).to be_valid
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :user do
name { 'testuser' }
end
end
require 'rails_helper'
RSpec.describe User, type: :request do
# frozen_string_literal: true
require 'rails_helper'
describe 'User' do
before(:each) do
@status_code_ok = 200
end
it 'ユーザーを表示' do
@user = FactoryBot.create(:user)
get '/users/'
@json = JSON.parse(response.body)
# responseの可否判定
expect(response.status).to eq(@status_code_ok)
end
end
end
docker-compose run back bundle exec rspec
..
Finished in 0.13418 seconds (files took 2.18 seconds to load)
2 examples, 0 failures
やりました。テスト成功です。お疲れ様です。
CircleCIで自動テスト
お次は上記で作成したテストをgitでpushする度にCircleCIに自動テストさせましょう!
gitリポジトリにCircleCIアカウントを連携
CircleCIアカウントは作成しましたか?
お持ちのgitアカウントに、CircleCIアカウントが連携されていると、画像のように
リポジトリに対してSetUpボタンが表示されるので、それを押しましょう!
Add Configを押すと、このリポジトリに対してプルリクが作成され、ビルドされます。
masterにマージしてあげて、git pull
コマンドでローカルにpullします。
pullすると、無事、.circleci/config.yml
がローカルに生成されました。
CircleCIでは、このconfig.ymlを編集して自動テストや、自動デプロイが出来るように制御します。
CircleCIに自動テストを実行させる。
database.ymlのtestの部分に以下を加筆してください。
test:
<<: *default
database: app_test
username: root #追加
password: rootpassword #追加
さあ、いよいよCircleCIに自動テストを実行させます。
config.ymlを以下の記述で上書きして、gitにpushしてください。
version: 2.1
# 実行するjob
jobs:
# buildするjob
build:
machine:
image: circleci/classic:edge
steps:
- checkout
- run:
name: docker-compose build
command: docker-compose build
# testするjob
test:
machine:
image: circleci/classic:edge
steps:
- checkout
- run:
name: docker-compose up -d
command: docker-compose up -d
- run: sleep 30
- run:
name: docker-compose run back rails db:create RAILS_ENV=test
command: docker-compose run back rails db:create RAILS_ENV=test
- run:
name: docker-compose run back rails db:migrate RAILS_ENV=test
command: docker-compose run back rails db:migrate RAILS_ENV=test
- run:
name: docker-compose run back bundle exec rspec spec
command: docker-compose run back bundle exec rspec spec
- run:
name: docker-compose down
command: docker-compose down
# 順番を制御するworkflow
workflows:
build_and_test_and_deploy:
jobs:
- build
- test:
requires:
- build
- RailsとNuxtを使ってHello World!
- RailsをAPIモードで起動・NuxtでRailsのデータを引っ張ってくる。
- Railsでユーザーテーブルを作り、Nuxtでユーザーを作成する。
- rspecを導入してテストを実行してみる。
- CircleCIと連携して自動テストしてみる。
以上でこの記事の目的は果たしました。お疲れ様でした。
#最後に
お疲れ様でした。大変長くなってしまって申し訳ありません。
簡単に、すぐ開発出来る環境が出来る記事があったらなぁ〜という思いからこの記事を書きました。
CircleCIまで連携させているので、この環境ですぐにテスト駆動開発出来るなと思います。
これらは一例のため、より良い構成や書き方があると思います。
その際は自分なりに実装してみて、もし「こっちの方が良い!」と思ったら是非コメントいただけると嬉しいです。
時間があったら次はこれらをAWS環境にECSを使って本番デプロイまでを書こうかなと思います。
その次はterraformでIaCまでを書いて終わりになるかなと思います。