LoginSignup
23
20

More than 3 years have passed since last update.

AWS Copilotを使ってRailsコンテナの本番環境を爆速で構築する

Last updated at Posted at 2020-08-06

やりたいこと

Amazon ECSで動くRails本番環境を手軽に作りたい。

やりたくないこと

→こういうときにはAWS Copilot!

AWS Copilot

AWSでコンテナ化されたアプリケーションの開発、リリースを容易に行うためのコマンドラインツールです。
コマンドを叩くとCloudFormationが動き、必要なリソースの作成やデプロイを行うことができる。CI/CDパイプラインもコマンド一つで作成できます。

※Fargate起動タイプのみサポートしています

Copilotを支える概念

スクリーンショット 2020-08-06 1.23.33.png
https://github.com/aws/copilot-cli/wiki/Environments

Service

ECS上で動くコンテナアプリケーションのことです。
主にECSのサービスやタスク定義と関連があります。

Environment

本番環境やステージング環境といったステージのことです。
Environmentが異なるとVPCレベルで異なります。
主に以下のリソースと関連があります。
- VPC, Subnet
- ECS Cluster
- ALB, Security Group, Internet Gateway
- Route53

Application

ServiceとEnvironmentを束ねたひとまとまりのことです。

Copilotを使ったRailsコンテナ環境の作り方

今回はNginxをかませず、直接ALBからRailsのコンテナにアクセスする仕組みを作ります。

手順概要

  1. ローカルで動くRailsコンテナアプリケーションを用意
  2. CopilotのインストールとAWS credentialsの設定
  3. Applicationの作成
  4. Environmentの作成
  5. RDSインスタンスの作成
  6. Serviceの作成

以下のコマンドでApplication、Environment、Serviceの作成を全て行ってくれますが、Serviceの作成の前にRails起動のためのデータベース(ここではRDSを利用)が必要なので、以下コマンドは使わずに一つ一つ手順を行っていきます。

$ copilot init

1. ローカルで動くRailsコンテナアプリケーションを用意

ローカルでdocker-compose upコマンドで起動するRailsアプリケーションを作成します。
Rails on DockerのQuickstartをalpine linuxでやってみるを参考にしつつ、APIモードで作成しました。

なお、プロジェクトフォルダ名にはアンダースコアは利用しないほうが良いです。後々、copilotを利用してCI/CDパイプラインを作成する際にCloudFormationの命名規則に引っかかりエラーになります。

利用したDockerfileとdocker-compose.ymlは以下の通りです。

Dockerfile
FROM ruby:2.7.1-alpine3.12

ENV ROOT="/myapp" \
    LANG=C.UTF-8 \
    TZ=Asia/Tokyo

WORKDIR ${ROOT}

RUN apk update && \
    apk upgrade && \
    apk add --no-cache \
        tzdata \
        nodejs \
        mysql-dev \
        mysql-client \
        vim && \
    apk add --virtual build-packs --no-cache \
        build-base \
        curl-dev \
        gcc \
        g++ \
        libc-dev \
        libxml2-dev \
        linux-headers \
        make

COPY Gemfile ${ROOT}
COPY Gemfile.lock ${ROOT}

RUN bundle install
RUN apk del build-packs

COPY . ${ROOT}

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
docker-compose.yml
version: '3'

services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: myapp
      MYSQL_USER: root
      MYSQL_ALLOW_EMPTY_PASSWORD: 1
      TZ: Asia/Tokyo
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    ports:
      - "3306:3306"
    volumes:
      - db_volume:/var/lib/mysql

  web:
    build: .
    command: ash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

volumes:
  db_volume:

取り急ぎ、コンテナ起動時にデータベースの作成とマイグレーションを行うために、entrypoint.shにコマンドを記述します。
マイグレーション用のECSタスクを作成して、適宜実行するのが良いかもしれません。

entrypoint.sh
#!/bin/sh
set -e

rm -f /myapp/tmp/pids/server.pid
bundle exec rails db:create
bundle exec rails db:migrate

exec "$@"

さらに.dockerignoreでmaster.keyをビルド対象から除外している場合は、ビルド対象として含めるようにコメントアウトします。
本来は、AWS Systems Managerのパラメータストアにmaster.keyの文字列を登録して、copilotから参照できるようにmanifest.yml(後述)のsecretsを登録すべきです。

.dockerignore
# Ignore master key for decrypting credentials and more.
# /config/master.key

またこの段階で、ALBのヘルスチェックに引っかかってコンテナの停止→起動を繰り返さないためにも、ヘルスチェック応答用のgemであるokcomputerを導入しました。
ルートアクセスに対して応答させるために以下のような記述をします。

config/initializers/okcomputer.rb
OkComputer.mount_at = '/'

ルートアクセスして以下のような表示が返れば準備完了です。

スクリーンショット 2020-08-05 12.56.09.png

2. CopilotのインストールとAWS credentialsの設定

Copilotのインストール

$ brew install aws/tap/copilot-cli

AWS credentialsの設定(設定の仕方は設定の基本を参照)
リソースを作成したいAWSアカウント情報をdefaultのprofileに設定しておきます。

~/.aws/credentials
[default]
aws_access_key_id=XXXXXXXXXXXXXXXXXXX
aws_secret_access_key=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

3. Applicationの作成

copilotコマンドの多くは対話型で操作が進みます。

Applicationが既に存在する場合は、既存のApplicationを利用するか確認されます。
今回は「copilot-demo」という名前のApplicationを新規に作成します。

$ copilot app init


  Would you like to use one of your existing applications? (Y/n)
  > n

  What would you like to name your application? [? for help] 
  > copilot-demo

しばらく経過すると以下のログが表示され、CloudFormationでRoleが作成されたことがわかります。
ローカルではApplication名が書かれた「copilot/.workspace」というファイルが作成されます。

  Use existing application: No
  Application name: copilot-demo
  ✔ Created the infrastructure to manage services under application copilot-demo.

  ✔ The directory copilot will hold service manifests for application copilot-demo.

  Recommended follow-up actions:
  - Run `copilot init` to add a new service to your application.

スクリーンショット 2020-08-06 3.10.17.png

4. Environmentの作成

次にEnvironmentを作成します。
今回は「production」という名前で作成しました。

$ copilot env init


  What is your environment's name? [? for help]
  > production

  Which named profile should we use to create production?  [Use arrows to move, type to filter, ? for more help]
  > default

しばらく経過すると以下のログが表示され、ネットワーク周りのリソース、ECSクラスタ、ALBが作成されたことがわかります。
もしApplication作成時にRoute53に指定しているドメインを指定すれば、Route53のレコードも作成されます。

CloudFormationで「EnvironmentSecurityGroup」というセキュリティグループが作成されます、後ほどRDSセキュリティグループのインバウンドグループのソースとするためにIDを保存しておきます。

  What is your environment's name? production
  Which named profile should we use to create production? default
  ✔ Created the infrastructure for the production environment.
  - Virtual private cloud on 2 availability zones to hold your services     [Complete]
  - Virtual private cloud on 2 availability zones to hold your services     [Complete]
    - Internet gateway to connect the network to the internet               [Complete]
    - Public subnets for internet facing services                           [Complete]
    - Private subnets for services that can't be reached from the internet  [Complete]
    - Routing tables for services to talk with each other                   [Complete]
  - ECS Cluster to hold your services                                       [Complete]
  - Application load balancer to distribute traffic                         [Complete]
  ✔ Linked account XXXXXXXXXXX and region ap-northeast-1 to application copilot-demo.

  ✔ Created environment production in region ap-northeast-1 under application copilot-demo.

5. RDSインスタンスの作成

Service作成時にデータベースに接続できないというエラーを回避するために、このタイミングでRDSインスタンスを作成します。
Aurora Serverlessを利用していますが、何を利用しても問題ありません。

スクリーンショット 2020-08-05 11.37.35.png
スクリーンショット 2020-08-05 11.39.03.png
スクリーンショット 2020-08-05 11.39.22.png

作成後は、Environment作成時に作成された「EnvironmentSecurityGroup」を、RDSセキュリティーグループのインバウンドルールのソースに設定します。
スクリーンショット 2020-08-05 13.01.28.png

また、作成したRDSインスタンスの情報をdatabase.ymlに反映させます。

config/database.yml
production:
  <<: *default
  database: myapp_production
  host: copilot-demo-database.cluster-csfienv6hggj.ap-northeast-1.rds.amazonaws.com
  username: admin
  password: 5ohWzjXPr7w7u8OZgyBw

6. Serviceの作成

最後にServiceを作成します。

Serviceの種類として以下の2種類があります。今回は「Load Balanced Web Service」を選択し、名前は「api」としました。

  • Load Balanced Web Service: インターネットから接続がある場合に選択
  • Backend Service: インターネットから接続がない場合に選択

※上記の選択によって作成されるリソースに差が出る(インターネットゲートウェイ等)

$ copilot svc init


  Which service type best represents your service's architecture?  [Use arrows to move, type to filter, ? for more help]
  > Load Balanced Web Service
    Backend Service

  What do you want to name this Load Balanced Web Service? [? for help]
  > api

  Which Dockerfile would you like to use for api?  [Use arrows to move, type to filter, ? for more help]
  > ./Dockerfile

しばらく経過すると以下のログが表示され、CloudFormationでECSサービスやECSタスク定義が作成されます。
ローカルでは「copilot/api/manifest.yml」というファイルが作成されます。

  Service type: Load Balanced Web Service
  Service name: api
  Dockerfile: ./Dockerfile
  ✔ Wrote the manifest for service api at copilot/api/manifest.yml
  Your manifest contains configurations like your container size and port (:3000).

  ✔ Created ECR repositories for service api.

  Recommended follow-up actions:
  - Update your manifest copilot/api/manifest.yml to change the defaults.
  - Run `copilot svc deploy --name api --env test` to deploy your service to a test environment.
manifest.yml

Serviceの定義をするファイルです。
Dockerイメージ、コンテナの接続ポート、アクセスを許すURLパス、ECSタスクのCPU/メモリ、ECSサービス内のタスク数、環境変数、シークレット(参照できるAWS Systems Managerのパラメータ)を指定できます。

本番環境としてRailsコンテナを起動するためにRAILS_ENVを環境変数で渡します。
通常は「RAILS_ENV = development」ですが、CopilotのEnvironmentがproductionの時は「RAILS_ENV = production」という記述です。

manifest.yml
name: api
type: Load Balanced Web Service

image:
  build: ./Dockerfile
  port: 3000

http:
  path: '/'

cpu: 256
memory: 512
count: 1

variables:
  RAILS_ENV: development

environments:
  production:
    variables:
      RAILS_ENV: production

最後に、以下コマンドを叩き、定義したServiceをデプロイします。
以下が実行されます。
- ローカルでDockerイメージのビルド
- DockerイメージをECRへのプッシュ
- ECSタスク更新
- ECSサービス更新

$ copilot deploy
  ...
  Successfully tagged XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/copilot-demo/api:77673f1
  Login Succeeded
  The push refers to repository [XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/copilot-demo/api]
  cee202204539: Pushed 
  2bf6e538dc23: Pushed 
  788fae7f9c70: Pushed 
  0ee2627f68ac: Pushed 
  246acc754b43: Pushed 
  68a27f30bbbc: Pushed 
  bb97d43854fb: Pushed 
  9099c9ee41ff: Pushed 
  3d0400229c5c: Pushed 
  93a2bfafa84f: Pushed 
  54f362ba164c: Pushed 
  c4b1ff92c516: Pushed 
  446d8e2016ac: Pushed 
  50644c29ef5a: Pushed 
  77673f1: digest: sha256:40d94e7257d0657eb2a69450caa3b9f81f33c258719f57a0cafcd857aae2e123 size: 3245


  ✔ Deployed api, you can access it at http://copil-Publi-1R5AI7MW2C3Q3-974847059.ap-northeast-1.elb.amazonaws.com.

実行ログ行末に記載のあるURLにアクセスすると正常に表示されており、ログを見ると本番環境として動作していることが確認できました。

画面

スクリーンショット 2020-08-06 4.11.27.png

CloudWatchログ

  Database 'myapp_production' already exists
  => Booting Puma
  => Rails 6.0.3.2 application starting in production
  => Run `rails server --help` for more startup options
  Puma starting in single mode...
  * Version 4.3.5 (ruby 2.7.1-p83), codename: Mysterious Traveller
  * Min threads: 5, max threads: 5
  * Environment: production
  * Listening on tcp://0.0.0.0:3000
  Use Ctrl-C to stop

まとめ

今回作成したコードは以下です。
https://github.com/ssshun/rails-copilot

AWS Copilotを利用して、AWSコンソールを利用した操作を極力減らし、手軽にRailsコンテナを本番稼働させることができました。
CI/CDパイプラインの作成はハマりどころが多かったので別途記載します。

23
20
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
23
20