[Part 2] Automatically test and deploy WEB services created with Rails + Nuxt + MySQL + Docker with ECS / ECR / CircleCI to make them terraform

In writing this article

Creating a portfolio from a state where I had no knowledge of AWS, personally deploying to AWS and automatic deployment of CircleCI were the most difficult, so a memorandum ・ I will create a portfolio from now on ・ For those who will use AWS from now on I thought it would be nice if there was an article.

An overview of the portfolio I created [AWS? Docker?] Summarize the necessary knowledge in your portfolio in an easy-to-understand manner [terraform? CircleCI?]

The predecessor of this article is [Part 1] WEB service created with Rails + Nuxt + MySQL + Docker is automatically tested and deployed with ECS / ECR / CircleCI and converted to terraform

I would appreciate it if you could take a look.

First: [Part 1] Automatically deploy WEB service created with Rails + Nuxt + MySQL + Docker with ECS / ECR / CircleCI and make it terraform Second: This article Third: [Part 2] Automatically deploy WEB service created with Rails + Nuxt + MySQL + Docker with ECS / ECR / CircleCI and make it terraform

Goals for this article

--You can make basic network settings such as VPC, IGW, subnet, route table, and security group. --You can build a WEB service that allows SSL communication at all times using Route53, ACM, and ALB. --Can be deployed in a production environment using ECS / ECR. --You can use CircleCI to push to master and deploy automatically at the same time.

Premise

--You have already obtained an AWS account, IAM account, and key pair (you should be able to do it right away) --aws-cli installed --The region used is Tokyo (ap-northeast-1) --Implementation is proceeding up to the previous article.
[Part 1] WEB service created with Rails + Nuxt + MySQL + Docker is automatically tested and deployed with ECS / ECR / CircleCI and converted to terraform

Finished product

qiita-sample-app

Note

What you can do with this article is usually ** paid ** beyond the AWS free tier. Also, because you get a domain with your name.com, it also costs ** money. ** ** Please read only those who think that it is a study fee and can divide it. For the time being, I would like to publish a loosely coupled web service using ECS and ECR! For those who say.

Network construction

Let's be enthusiastic!

VPC creation

VPC.png

Trivia

CIDR block is an abbreviation for Classless Inter-Domain Routing. One of the notation methods of IP address. Simply put, it's the range of VPCs.

By the way, here it is a display like / 16. In CIDR notation, it is written as / 16, / 24, 32.

/ 16 can specify up to the third octet. For example, the 0.0 of 10.20.0.0 You can specify this part.

/24 can specify up to the 4th octet. For example, the last 0 in 10.20.0.0 You can specify this part.

/ 32 can specify a specific IP address.

When creating a VPC, you have to secure an IP in the subnet or AZ inside, and the change does not work, so In many cases, specify / 16 to secure many available IPs.

Subnet creation

Here we use AZ ** ap-northeast-1a and ap-northeast-1c **. The reason for using the two AZs is that we will use ALB later.

Create the following subnet from Subnet> Create Subnet. サブネット.png

name CIDR block Region/AZ
qiita-sample-front-subnet-1a 10.20.1.0/24 ap-northeast-1a
qiita-sample-front-subnet-1c 10.20.2.0/24 ap-northeast-1c
qiita-sample-back-subnet-1a 10.20.3.0/24 ap-northeast-1a
qiita-sample-back-subnet-1c 10.20.4.0/24 ap-northeast-1c
qiita-sample-rds-subnet-1a 10.20.5.0/24 ap-northeast-1a
qiita-sample-rds-subnet-1c 10.20.6.0/24 ap-northeast-1c

Creating a route table

Create the following route table from Route table> Create route table.

ルートテーブル.png

name
qiita-sample-front-route-table
qiita-sample-back-route-table
qiita-sample-rds-route-table

Creating an IGW

Create the following Internet gateway from Internet Gateway> Create Internet Gateway.

igw.png

Associate VPC and IGW.

vpctoigw.png

name
qiita-sample-igw

Association of subnets and route tables

--Select the Edit tab of the route table and associate the IGW created earlier with 0.0.0.0 to the route table other than RDS. --Select the Subnet Association tab and associate as shown in the table below.

ルートテーブルの編集.png

Subnet Route table
qiita-sample-front-subnet-1a qiita-sample-front-route-table
qiita-sample-front-subnet-1c qiita-sample-front-route-table
qiita-sample-back-subnet-1a qiita-sample-back-route-table
qiita-sample-back-subnet-1c qiita-sample-back-route-table
qiita-sample-rds-subnet-1a qiita-sample-rds-route-table
qiita-sample-rds-subnet-1c qiita-sample-rds-route-table

Creating a security group

Create security groups in the table below

Security group name type protocol Port range Source
qiita-sample-front-sg HTTP TCP 80 0.0.0.0/0
Same as above SSH TCP 22 Your own IP
Same as above HTTPS TCP 443 0.0.0.0/0
Same as above custom TCP 3000 0.0.0.0/0
qiita-sample-back-sg custom TCP 3000 0.0.0.0/0
Same as above HTTPS TCP 443 0.0.0.0/0
qiita-sample-rds-sg MYSQL/Aurora TCP 3306 qiita-sample-back-sg

This is the end of the network.

Creating Route53 (charged from here)

Obtaining a domain

Get the domain from Name.com. Please get the domain of your choice (preferably 1 yen) for those who are referring to this article.

For information on how to get a domain with your name.com, please refer to [Get a domain] Get a domain with your name.com and aws.

** * Turn off the automatic domain update setting! (It costs money every year) **

This time, in my environment, I got the following domain.

Domain name role
qiita-sample.work front
qiita-sample-api.work back

Creating a host zone

Create a host zone from Route53.

Route53 ホストゾーン.png

From the host zone details, copy the ** value / traffic routing destination ** of the NS record.

Register the value of NS record in the name server of your name.com

Route53 NS設定.png

It will take some time to reflect. (It may take up to 1 day)

dig qiita-sample.work(Acquired domain) +short NS

Is typed from the terminal, and if the NS record registered successfully is displayed, it is reflected.

Issue a certificate to your domain using ACM

Request a certificate from ACM. Select Request Public Certificate to request a certificate.

Domain name
qiita-sample.work
*.qiita-sample.work

Fill in the above domain name and select DNS validation. Similarly, do the same for qiita-sample-api.work.

ACM request.png

Then, you will be taken to the verification screen, where you will ** create a record with Route53 **. (Create records in all of the above domains.) After a while, the verification will be pending and the status will be published. If the verification is pending for a long time, it is possible that the NS server settings are delayed. Let's check with the dig command.

Create RDS

It takes some time to set up Route53 and domain name server, so let's create RDS quickly here.

Creating a subnet group

RDS サブネットグループ.png

Create / configure RDS

Please refer to the following settings and create RDS from RDS> Create Database.

Configuration Remarks
Engine type MySQL
version 5.7.30 This time 5.7
template development of/test Free tier is fine, but this time for studying
DB instance identifier qiita-sample-rds Anything is OK
Master username ** Anything is OK
Master password ** Anything is OK ・ Do not let it leak
DB instance class information db.t2.micro I will keep it the cheapest
Storage type General purpose SSD
Storage allocation 20 20 is the smallest
Virtual Private Cloud (VPC) The VPC I made earlier
Subnet group 先ほど作ったSubnet group
Existing VPC security group The security group you created earlier
Database authentication Password authentication
First database name production
DB parameter group default This time, set it to default. (You can make it yourself!)
Option group default This time, set it to default. (You can make it yourself!)

After a while, it will be available, so RDS creation is complete.

Modify database.yml and credentials.yml.enc

yml:./back/config/database.yml


production:
  <<:         *default
  host:       <%= Rails.application.credentials.rds[:host] %>
  database:   <%= Rails.application.credentials.rds[:database] %>
  username:   <%= Rails.application.credentials.rds[:username] %>
  password:   <%= Rails.application.credentials.rds[:password] %>

./


docker-compose run -e EDITOR="vi" back rails credentials:edit 

Then, the vim screen will appear, so enter the following settings.

rds:
  host: {end point}
  database: {database name (created early)}
  username: {Set user name}
  password: {Password set}

Creating a load balancer

Let's create a load balancer from EC2> Create load balancer.

Load balancer settings

front ロードバランサー の設定.png

back ロードバランサー の設定back.png Register the VPC and subnet you created earlier in the Availability Zone.

Configuring security settings

ロードバランサー  セキュリティ設定の構成.png

Select the certificate you created earlier. If you haven't been authenticated yet, wait a minute.

Security group settings

Please attach the security group you created earlier.

Routing settings

Create a new target group. This time in front, to start in ** host mode ** in EC2 generated by ECS Set the target to the instance and accept the port at 3000. In back, the target is ip because it starts in ** awsvpc mode ** in the container in the instance of ECS. This refers to the container's private IP.

front ロードバランサー ルーティング.png

back ロードバランサー ルーティングback.png

Target registration

As we will see later, don't worry, the target will be selected automatically when the ECS service starts. Let's create a load balancer as it is!

Add A record to Route53

From Route53> Host Zone, add A record and set as follows. Process front and back in the same way.

ALB Route53.png

Create repository in ECR / Push image

Let's change the orientation of the front API for production.

js:./front/plugins/axios.js


import axios from "axios"

export default axios.create({
  //baseURL: "http://localhost:3000"
  baseURL: "https://www.qiita-sample-api.work"
})

** * Let's use aws cli! ** ** Install AWS CLI version 1 on macOS Install AWS CLI

Create a repository from Amazon ECR. Please fill in the repository name and create it as it is. If you select a repository and press the push command, the command to push to the repository will be displayed, so create a repository for both front and back and execute it in the local environment.

When you execute these commands, an image will be created based on the Dockerfile in the directory. It will be in the same state as when docker-compose build is done in the local environment. Using the image created here and pushed to ECR, load it on ECS and start it.

Please note that ** front and back, please check which repository you want to push to, move to the directory where the Dockerfile is located, and then execute. ** ** ecr画面.png

ECS creation / setting

Overall picture

In ECS Cluster> Services> Tasks It has become the particle size. A cluster is a logical framework, a box. The service is like auto scaling + ALB that starts on the cluster. Automatically manage the number of tasks and configure networking settings. Think of a task as a unit that runs a Docker image.

The service creates tasks based on the task definition. Forcibly, the taiyaki mold is a service, and the taiyaki itself is a task. The cluster is a stall of a Taiyaki restaurant. building_taiyaki.png

Creating an ECS cluster

ECS作成1.png

This time I chose EC2 Linux + networking

ECS作成2.png

For networking, select the VPC / subnet (front) you created earlier. Also, select Enabled for Auto assign public IP. Let's set the key pair so that you can connect with ssh.

Amazon EC2 Key Pairs and Linux Instances

After creating the cluster, if the EC2 instance is started as shown below, it is successful. Good thing If EC2 instances are not tied, security groups and route tables are often the problem. ECS作成3.png

Creating a task definition

Select Create new task definition from ECS

Select EC2 ECS タスク定義2.png

front

Configuration Remarks
Task definition name qiita-sample-front Anything is OK
Task role Roles that have permission to run ECS
Network mode host
Task execution role Roles that have permission to run ECS
Task memory(MiB) 700 Fit within the size of the instance
Task CPU(unit) 256 Fit within the size of the instance

ECS タスク定義1.png

back

Configuration Remarks
Task definition name qiita-sample-back Anything is OK
Task role Roles that have permission to run ECS
Network mode aws-vpc
Task execution role Roles that have permission to run ECS
Task memory(MiB) 400 Fit within the size of the instance
Task CPU(unit) 128 Fit within the size of the instance

ECS タスク定義3.png

Creating a service

Create a service from the service tag of the cluster.

front

Configuration Remarks
Launch type EC2
Task definition What I created earlier
Service name qiita-front Anything is OK
Service type REPLICA
Number of tasks 1 1~Anything above is OK

For load balancing, select Application Load Balancer and select the ALB and target created earlier.

back

Configuration Remarks
Launch type EC2
Task definition What I created earlier
Service name qiita-back Anything is OK
Service type REPLICA
Number of tasks 1 1~Anything above is OK

In the network configuration, select the VPC and back subnet security group you created earlier. For load balancing, select Application Load Balancer and select the ALB and target created earlier.

Start ECS service

When created as above, one task should be added automatically. If it looks like the following, it's OK! The container should have been started inside EC2 from the image safely. ECS タスク.png

db:migrate You can create a task definition and execute the task, but this time I will ssh connect to the EC2 instance, then go inside the docker container and try rails db: migrate. Originally, I had to create a DB (db: create), but this time I set RDS as production in the initial DB at the time of creation, and it is not necessary because it has already been created.

[Pem file DL](https://qiita.com/Shogo1222/items/dcbc1e50f6fc83f48b44#ecs%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF% E3% 83% BC% E3% 81% AE% E4% BD% 9C% E6% 88% 90)

ssh -i {pem file path} ec2-user@{Public IP of the instance}


   __|  __|  __|
   _|  (   \__ \   Amazon Linux 2 (ECS Optimized)
 ____|\___|____/

For documentation, visit http://aws.amazon.com/documentation/ecs

In an EC2 instance


docker ps

Will display the currently running container. Specify the CONTAINER ID of the container running rails and type the following command to put it inside the Docker container.

In an EC2 instance


docker exec -it {CONTAINER ID} sh

Here, let's specify the environment and db: migrate!

Inside the Docker container


rails db:migrate RAILS_ENV=production

Start confirmation

Thank you for your hard work!

ttps: // www. {your own front domain} / users It is OK if you can access and register the user as well as local, as shown in the screen below. ttps: // www. {Back domain set by myself} / users It is OK if you access and the json of the registered user is returned.

qiita-sample画像.png

Automatic deployment on CircleCI

After pushing to master at the last, CircleCI, let's update the task definition automatically, update the ECS service, and make it possible to update the task.

Overall flow

Push from local to github
→ Start CircleCI from github
→ Run docker build on CircleCI
→ Push the image to ECR on CircleCI
→ Update ECS service ...

It will be.

Trivia

The difference from building locally is the presence or absence of environment variables.

The build run on CircleCI is ** referencing the source code in the git repository ** and is also ** running in a virtual machine **. If the environment variable required to execute Rails does not exist in the git repository, the environment variable cannot be referenced and it will fail. However, CircleCI allows you to set environment variables on the virtual machine.

Setting environment variables

Project Settings>Environment Variables

circleci environments.png

AWS_ACCESS_KEY_ID //AWS access key
AWS_SECRET_ACCESS_KEY //AWS secret access key
AWS_ACCOUNT_ID //See URL below
AWS_ECR_ACCOUNT_URL //ECR URL example:{Account ID}.dkr.ecr.{region}.amazonaws.com
AWS_REGION //region

RAILS_MASTER_KEY // ./back/config/Master below.key value
AWS_RESOURCE_NAME_PREFIX //The prefix of the service I created (qiita this time-sample)
CLUSTER_NAME //The name of the Cluster created earlier
REPO_NAME_FRONT //front ECR repository name
REPO_NAME_BACK //back ECR repository name
FAMILY_NAME_FRONT //The name of the task definition of front
FAMILY_NAME_BACK //name of back task definition
SERVICE_NAME_FRONT //The name of the front ECS service created earlier
SERVICE_NAME_BACK //The name of the front ECS service created earlier

Confirm AWS Account ID

config.yml settings

yml:./front/.circleci/config.yml


version:                                  2.1
orbs:
  aws-ecr:                                circleci/[email protected]
  aws-ecs:                                circleci/[email protected]

#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
      - aws-ecr/build-and-push-image:
            name:                         'build-and-push-back'
            account-url:                  AWS_ECR_ACCOUNT_URL
            region:                       AWS_REGION
            repo:                         ${REPO_NAME_BACK}
            tag:                          "${CIRCLE_SHA1}"
            path:                         './back'
            dockerfile:                   back/Dockerfile.pro
            extra-build-args:             '--build-arg RAILS_MASTER_KEY=$RAILS_MASTER_KEY'
            requires:
              - test
            filters:
              branches:
                only:
                  - master
      - aws-ecr/build-and-push-image:
            name:                         'build-and-push-front'
            account-url:                  AWS_ECR_ACCOUNT_URL
            region:                       AWS_REGION
            repo:                         ${REPO_NAME_FRONT}
            tag:                          "${CIRCLE_SHA1}"
            path:                         './front'
            dockerfile:                   front/Dockerfile.pro
            requires:
              - test
            filters:
              branches:
                only:
                  - master
      - aws-ecs/deploy-service-update:
            family:                       ${FAMILY_NAME_BACK}
            service-name:                 ${SERVICE_NAME_BACK}
            cluster-name:                 ${CLUSTER_NAME}
            #"container="Please note that is the container name set in Task Definition.
            container-image-name-updates: 'container=${AWS_RESOURCE_NAME_PREFIX}-back,image-and-tag=${AWS_ECR_ACCOUNT_URL}/${REPO_NAME_BACK}:${CIRCLE_SHA1}'
            requires:
              - build-and-push-back
            filters:
              branches:
                only:
                  - master
      - aws-ecs/deploy-service-update:
            family:                       ${FAMILY_NAME_FRONT}
            service-name:                 ${SERVICE_NAME_FRONT}
            cluster-name:                 ${CLUSTER_NAME}
        #"container="Please note that is the container name set in Task Definition.
            container-image-name-updates: 'container=${AWS_RESOURCE_NAME_PREFIX}-front,image-and-tag=${AWS_ECR_ACCOUNT_URL}/${REPO_NAME_FRONT}:${CIRCLE_SHA1}'
            requires:
              - build-and-push-front
            filters:
              branches:
                only:
                  - master

Create a production Dockerfile

dockerfile:./back.Dockerfile.pro


#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", "production"]

dockerfile:./front/Dockerfile.pro


FROM node:12.5.0-alpine

ENV HOME="/app" \
    LANG=C.UTF-8 \
    TZ=Asia/Tokyo

ENV HOST 0.0.0.0

WORKDIR ${HOME}
COPY package.json .
COPY . .

RUN apk update && \
    apk upgrade && \
    npm install -g n && \
    yarn install &&\
    rm -rf /var/cache/apk/*

RUN yarn run build
EXPOSE 3000
CMD ["yarn", "start"]

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
    environment: #add to
      RAILS_MASTER_KEY: ${RAILS_MASTER_KEY}
    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:

Deploy

Thank you for your hard work! As shown below, if all the processes are successful, the deployment is completed successfully. circleci success deploy.png

reference

Those who want to learn VPC, AWS network, etc. AWS course learned with video VPC [section1] Region, Availability Zone, Subnet

Those who want to know more about Docker's network and ECS Docker course to catch up from now on! Aim for container master with AWS ECS and Fargate! ~ First series ~

It's a really convenient time to learn from videos ... I repeated it about 30 times.

Finally

Thank you for your hard work. Now you can deploy Nuxt and Rails to production using ECS / ECR / CircleCI. This is just an example, and I think there are other good configurations. However, once you build and debug it, you'll know how to do it yourself. (I still have more Maybe there are typos or mistakes in this article. In such a case, I would appreciate it if you could gently throw Masakari.

Next time I wish I could write up to terraform.

Recommended Posts

[Part 2] Automatically test and deploy WEB services created with Rails + Nuxt + MySQL + Docker with ECS / ECR / CircleCI to make them terraform
[Part 1] WEB service created with Rails + Nuxt + MySQL + Docker is automatically tested and deployed with ECS / ECR / CircleCI and converted to terraform.
[Part 1] How to deploy Docker containers and static files with CircleCI + ECS + ECR + CloudFront
[AWS] How to automatically deploy a Web application created with Rails 6 to ECR / ECS using CircleCI ① Preparation [Container deployment]
Deploy to heroku with Docker (Rails 6, MySQL)
[For super beginners (more screenshots)] Automatically deploy to AWS ECR / ECS with Ruby2.6 x Rails6 x CircleCi [Hands-on format]
Introduce Docker to the development environment and test environment of existing Rails and MySQL applications
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (5)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (6)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (3)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (2)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (1)
[Rails AWS Docker] Build an existing Ruby on Rails + MySQL application with Docker and deploy it on AWS (4)
Run Rubocop and RSpec on CircleCI and deploy to ECS
How to check CircleCI code and automatically deploy to Heroku