CircleCI? ?? ?? Docker ??? People should look at this first. ↑
Studying for studying doesn't mean much ... So move your hands first! So this time I will write an article in hands-on format. I hope that those who read it will be able to quickly build a development environment and concentrate on development quickly.
I reproduced it in my environment about 3 times so as not to suffer from errors as much as possible.
I think it will probably be a trilogy ... I'm sorry it's long.
First: This article 2nd: [[Part 2] Automatically deploy WEB service created with Rails + Nuxt + MySQL + Docker with ECS / ECR / CircleCI and make it terraform](https://qiita.com/Shogo1222/items/ dcbc1e50f6fc83f48b44) Third: [Part 2] Automatically deploy WEB service created with Rails + Nuxt + MySQL + Docker with ECS / ECR / CircleCI and make it terraform
--Hello World! Using Rails and Nuxt! --Launch Rails in API mode-Pull Rails data with Nuxt. --Create a user table in Rails and create a user in Nuxt. --Introduce rspec and run the test. --Try automatic testing in cooperation with CircleCI.
It's long, but let's do our best!
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 installed
Install Docker on Mac (Update: 2019/7/13)
--CircleCI account created
I've just started CircleCI, so I've summarized it in an easy-to-understand manner
--Created git account
[6.1 GitHub-Account Preparation and Setup](https://git-scm.com/book/ja/v2/GitHub-%E3%82%A2%E3%82%AB%E3%82%A6%E3% 83% B3% E3% 83% 88% E3% 81% AE% E6% BA% 96% E5% 82% 99% E3% 81% A8% E8% A8% AD% E5% AE% 9A)
I gave the finished product to git. qiita-sample-app
app //Any name is fine
├─docker-compose.yml
├─front
| ├─Dockerfile
└─back
├─Dockerfile
├─Gemfile
└─Gemfile.lock
mkdir app
cd app //Go to app
mkdir front //front creation
mkdir back //back creation
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
Edit Dockerfile in / back
back/Dockerfile
#Specifying the image
FROM ruby:2.6.3-alpine3.10
#Download required packages
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
#Move to working directory
WORKDIR ${HOME}
#Copy the necessary files from the host (files on your computer) to 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*
#Copy the necessary files from the host (files on your computer) to Docker
ADD . ${HOME}
#Open port 3000
EXPOSE 3000
#Execute command
CMD ["bundle", "exec", "rails", "s", "puma", "-b", "0.0.0.0", "-p", "3000", "-e", "development"]
Edit Dockerfile in / front
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/*
#I will remove it later(When using ECS)
#RUN yarn run build
#EXPOSE 3000
#CMD ["yarn", "dev"]
back/Gemfile
source 'https://rubygems.org'
gem 'rails', '~>6'
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.Useful when you fail to erase the rails server with pid
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
#Host computer port: Port in Docker
ports:
- 3000:3000
front:
build: front/
command: yarn run dev
volumes:
- ./front:/app:cached
ports:
#Host computer port: Port in Docker
- 8080:3000
depends_on:
- back
volumes:
public-data:
tmp-data:
log-data:
db-data:
The network inside Docker is different from the network on the host machine. For example, the rails process started by Docker is started by localhost (127.0.0.1): 3000 in the virtual environment (container). It is different from localhost on the host machine.
However, it can be solved by port mapping in docker-compose.yml.
If you specify host port 3000 and container port 3000 and bind to 0.0.0.0, you can access with localhost as if you started it on the host machine. This is called port forwarding.
You can access the process built in the virtual environment normally by specifying the binding address and performing port forwarding.
reference: The meaning of option -b of rails s -b 0.0.0.0
So what does 8080: 3000 mean? This port-maps container port 3000 to host port 8080 (≈ port forwarding). In other words, the nuxt process built inside the Docker container uses port 3000, but when browsing on the host machine, make it visible on 8080. (If Rails is also 3000, it will be duplicated in the host machine, so an error will occur)
./back/db.env
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_USER=username
MYSQL_PASSWORD=userpassword
./
docker-compose build
It will take some time. You don't have to do build many times, just run it when you change Gemfile or Dockerfile! (I did it many times at the beginning and lost time.)
This is the docker-compose command that came out for the first time, but from now on I will use this command a lot.
hello, Nuxt
Create a nuxt app under front Hello World with Nuxt!
The final directory structure looks like this.
...Abbreviation
front
├─Dockerfile
...Abbreviation
├─components //Where to put vue components
├─layouts //Where index calls by default
├─pages //Add here when adding a page
├─plugins //Where to put the plugin configuration file to be added with yarn etc.
├─nuxt.config.js //nuxt itself configuration file
├─package.json //Where to set the package dependency of yarn / npm
...Abbreviation
./
docker-compose build
docker-compose run front npx [email protected]
#If it is version3, an error will occur unless it is an empty directory, so the version is specified.
#The following selection screen will appear.
#I like the name of the app, so it's OK!It will be the title when opened on the browser
? Project name --> sample_app
#I like the description of the app, so it's OK!It becomes the subtitle when opened on the browser
? Project description --> sample_app
#I like the creator of the app, so it's OK!
? Author name --> me
#You can choose npm or yarn, but yarn seems to be faster, so select 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 **: means run the following command on your Docker machine ** front **: means run on a container with this name
When you want to type Rails command,
docker-compose run back rails db:create
By the way, if you want to go into a container and debug
docker-compose exec back sh
To
docker-compose up front
Launch the front image you just built. http://localhost:8080/
Access the above URL and when this screen is displayed, it is complete!
hello, Rails
Create a rails app under back Hello World on Rails! Yay!
...Abbreviation
front
├─Dockerfile
├─Gemfile
├─Gemfile.lock
...Abbreviation
├─app //Where controller and view are included
├─config //Where there is something that is read at startup
├─db //db table information etc.
├─environments //Environment variables for DB connection information
...Abbreviation
./
#Create in API mode.--If you remove the api, things necessary for screen drawing such as view will also be installed.
#Select MySQL as the database.
docker-compose run back rails new . -f -d mysql --api
docker-compose build
In this state, the contents of the Gemfile have already been rewritten from the initial state. Please check it.
yml:./back/config/database.yml
default: &default
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= ENV.fetch('MYSQL_USER') { 'root' } %> #add.
password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %> #add.
host: db #change.
Grant the authority to the user described in db.env.
./
touch ./back/db/grant_user.sql
sql:./back/db/grant_user.sql
GRANT ALL PRIVILEGES ON *.* TO 'username'@'%';
FLUSH PRIVILEGES;
./
#Start the container.
docker-compose up
#Run the above SQL in the db container.
docker-compose exec db mysql -u root -p -e"$(cat back/db/grant_user.sql)"
#Creating a DB.
docker-compose run back rails db:create
http://localhost:3000/ Access to! If you see the familiar screen below, you are successful.
From here, let's actually move on to implementation. As much as possible, I made it easy to add functions after that, so If you want to add another new function, please try it in the same way.
./
docker-compose run back rails g scaffold user name:string
#It creates various files like this.
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/Below migrate~create_users.It runs rb and creates a table in the DB.
#Once run, the same file will not be rerun.
# 2020~If the number part of is changed, it can be re-executed, but if the same table exists in the DB, an error will occur.
docker-compose run back rails db:migrate
Now you have a table called users in a DB called app_development. Easy!
http://localhost:3000/users/ Let's access to. Since it's in API mode, you should see an empty json on a blank screen.
Why does the screen look like this? that is, There is a secret in **./back/config/routes.rb **.
rb:./back/config/routes.rb
Rails.application.routes.draw do
resources :users
end
There is a description of: users in resources. It links a controller called ./back/app/users_controller.rb with a path like ** / users **. Therefore, it is easy to think that the same procedure is used when adding functions.
./
#With this command, you can check which route is currently registered.
docker-compose run back rails routes
I will omit the details, but I would appreciate it if you could check it in the tutorial etc.
Communication from nuxt to rails requires some preparation. Let's not rush.
A plugin called axios is used for API communication.
./
docker-compose run front yarn
docker-compose run front yarn add @nuxtjs/axios
Notice that the following has been added to ./front/package.json:
json:./front/package.json
...
"@nuxtjs/axios": "^5.12.2"
...
Modify nuxt.config.js, which is a configuration file that is loaded when nuxt is started for the first time.
javascript:nuxt.config.js
...
modules: [
'@nuxtjs/axios' //add to
],
...
Create a new file called axios.js under ./front/plugins/.
javascript:./front/plugins/axios.js
import axios from "axios"
export default axios.create({
baseURL: "http://localhost:3000"
})
./back/Gemfile
...
gem 'rack-cors' #add to
...
./
docker-compose build
Add settings to ./back/config/initializers/cors.rb
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
Let's actually create a new screen. Create a new users.vue under ./front/pages.
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() {
//Get users with axios
axios.get("/users").then(res => {
if (res.data) {
this.users = res.data
}
})
}
}
</script>
I got the Users data above ... but it shouldn't show anything because it doesn't contain anything. Now let's save the data to the DB with the API.
vue:./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() {
//Get users with axios
axios.get("/users").then(res => {
if (res.data) {
this.users = res.data
}
})
},
methods: {
//Register users with axios
createUser(){
axios.post("/users", {name: this.name})
.then(res => {
if (res.data) {
this.users.push(res.data)
}
})
}
}
}
</script>
You can now register the user. Did it! By the way, the screen of nuxt becomes white when the dark theme in nuxt.config.js is set to false.
Now, let's set up and add an automated test!
./back/Gemfile
...
gem 'rspec-rails' #add to
gem 'factory_bot_rails' #add to
...
./
docker-compose down
docker-compose build back
docker-compose up
docker-compose exec back rails generate rspec:install
#The following files will be added
Running via Spring preloader in process 18
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
First, create a test file.
./
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
rb:./back/spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
it 'Normal test' do
@user = User.new(
name: 'test'
)
expect(@user).to be_valid
end
end
rb:./back/spec/factories/users.rb
# frozen_string_literal: true
FactoryBot.define do
factory :user do
name { 'testuser' }
end
end
rb:./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 'Show user' do
@user = FactoryBot.create(:user)
get '/users/'
@json = JSON.parse(response.body)
#Judgment of availability of 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
Did it. The test is successful. thank you for your hard work.
Next, let CircleCI automatically test the test created above every time you push it with git!
Have you created a CircleCI account? If your CircleCI account is linked to your git account, as shown in the image You will see a SetUp button for your repository, press it!
Press Add Config to create and build a pull request for this repository.
Merge it into master and pull it locally with the git pull
command.
After pulling, .circleci / config.yml
was successfully generated locally.
CircleCI edits this config.yml to control it for automatic testing and automatic deployment.
Add the following to the test part of database.yml.
yml:./back/config/database.yml
test:
<<: *default
database: app_test
username: root #add to
password: rootpassword #add to
Now it's time to get CircleCI to run the automated tests. Overwrite config.yml with the following description and push it to git.
yml:./circleci/config.yml
version: 2.1
#Job to run
jobs:
#job to build
build:
machine:
image: circleci/classic:edge
steps:
- checkout
- run:
name: docker-compose build
command: docker-compose build
#job to test
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 to control the order
workflows:
build_and_test_and_deploy:
jobs:
- build
- test:
requires:
- build
Did it! It's a success!
-[x] Hello World! Using Rails and Nuxt! -[x] Start Rails in API mode-Pull Rails data with Nuxt. --[x] Create a user table with Rails and create a user with Nuxt. -[x] Install rspec and run the test. -[x] Try automatic testing in cooperation with CircleCI.
That's all for the purpose of this article. Thank you for your hard work.
Thank you for your hard work. I'm sorry it's been so long. I wrote this article with the hope that there would be an article that would create an environment that could be easily developed immediately. Since it is linked to CircleCI, I think that test-driven development can be done immediately in this environment. These are just examples, so I think there is a better structure and writing style. In that case, try implementing it yourself, and if you think "this is better!", I would be grateful if you could comment.
If I have time, I think I'll write these up to production deployment using ECS in the AWS environment. After that, I think that I will finish writing up to IaC with terraform.
Recommended Posts