LoginSignup
40
50

More than 3 years have passed since last update.

【前編】Rails+Nuxt+MySQL+Dockerで作ったWEBサービスをECS・ECR・CircleCIで自動テスト・デプロイしてterraform化する

Last updated at Posted at 2020-09-03

この記事を書くにあたって

【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

前提

完成品

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を編集

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を編集

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の記述

back/Gemfile
source 'https://rubygems.org'
gem 'rails', '~>6'

docker-compose.ymlの記述

./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を設定しよう

/back/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/

上記URLにアクセスして、こんな画面が表示されたら完成!
nuxt helloworld.png

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設定

./back/config/database.yml
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
./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/
にアクセス!以下のお馴染みの画面が出ていたら成功です。

rails helloworld.png

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の中に秘密があります。

./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に以下が追加されたのを確認して下さい。

./front/package.json
...
    "@nuxtjs/axios": "^5.12.2"
...

./front/nuxt.config.jsの修正

nuxtを初回起動時に読み込まれる設定ファイルであるnuxt.config.jsを修正します。

nuxt.config.js
...
  modules: [
    '@nuxtjs/axios' // 追加
  ],
...

./front/plugins/axios.jsの追加

./front/plugins/以下にaxios.jsというファイルを新しく作成して下さい。

./front/plugins/axios.js
import axios from "axios"

export default axios.create({
  baseURL: "http://localhost:3000"
})

./back/Gemfileにrack-corsを追加

./back/Gemfile
...
gem 'rack-cors' # 追加
...
./
docker-compose build

./back/config/initializers/cors.rbに設定を追加

./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を新しく作成して下さい。

./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へ保存してみましょう。

./front/pages/users.vue
<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 add user.png

これでユーザーを登録出来るようになりました。やりました!
ちなみにnuxtの画面は、nuxt.config.js内のdarkテーマをfalseにすると白くなります。

自動テスト

さぁ、次は自動テストの設定・追加をしてみましょう!

./back/Gemfileにrspec-railsとfactory_bot_railsを追加

./back/Gemfile
...
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
./back/spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it '正常テスト' do
    @user = User.new(
      name: 'test'
    )
    expect(@user).to be_valid
  end
end
./back/spec/factories/users.rb
# frozen_string_literal: true

FactoryBot.define do
  factory :user do
    name { 'testuser' }
  end
end
./back/spec/requests/user_spec.rb
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ボタンが表示されるので、それを押しましょう!

CircleCI 連携.png
CircleCI 連携2.png

Add Configを押すと、このリポジトリに対してプルリクが作成され、ビルドされます。

CircleCI プルリク.png

masterにマージしてあげて、git pullコマンドでローカルにpullします。
pullすると、無事、.circleci/config.ymlがローカルに生成されました。
CircleCIでは、このconfig.ymlを編集して自動テストや、自動デプロイが出来るように制御します。

CircleCIに自動テストを実行させる。

database.ymlのtestの部分に以下を加筆してください。

./back/config/database.yml
test:
  <<:         *default
  database:   app_test
  username:   root #追加
  password:   rootpassword #追加

さあ、いよいよCircleCIに自動テストを実行させます。
config.ymlを以下の記述で上書きして、gitにpushしてください。

./circleci/config.yml
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

やりました!成功です!
CircleCI success.png

  • RailsとNuxtを使ってHello World!
  • RailsをAPIモードで起動・NuxtでRailsのデータを引っ張ってくる。
  • Railsでユーザーテーブルを作り、Nuxtでユーザーを作成する。
  • rspecを導入してテストを実行してみる。
  • CircleCIと連携して自動テストしてみる。

以上でこの記事の目的は果たしました。お疲れ様でした。

最後に

お疲れ様でした。大変長くなってしまって申し訳ありません。
簡単に、すぐ開発出来る環境が出来る記事があったらなぁ〜という思いからこの記事を書きました。
CircleCIまで連携させているので、この環境ですぐにテスト駆動開発出来るなと思います。
これらは一例のため、より良い構成や書き方があると思います。
その際は自分なりに実装してみて、もし「こっちの方が良い!」と思ったら是非コメントいただけると嬉しいです。

時間があったら次はこれらをAWS環境にECSを使って本番デプロイまでを書こうかなと思います。
その次はterraformでIaCまでを書いて終わりになるかなと思います。

40
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
50