LoginSignup
250
286

More than 3 years have passed since last update.

初心者でもできる! ECS × ECR × CircleCIでRailsアプリケーションをコンテナデプロイ

Last updated at Posted at 2020-02-11

前書き

  • ECSのクラスターは起動タイプにFargateEC2がありますが、今回は使い慣れているEC2を使います。
  • 本記事はAWSの環境構築は全てマネジメントコンソールで行ってます。 (Terraformで1発でやりたいような人は他の記事を見るか、一応GitHubにTerraformでコード化もしているのでそちらを参考に)
  • docker-composeは以下のような構成です
    • Nginxのコンテナ
    • Railsのコンテナ
    • MySQLのコンテナ

pumaとnginxをsocket通信させます。
※ 今回RailsはAPIモードで作成しています。
※ フロント部分がないためNginxはあまり意味がないかもしれませんが、練習のため

筆者のTwiiter

本記事で目指したいデプロイ構成

スクリーンショット 2020-02-11 10.08.44.png

(ECSのクラスターによって束ねられているEC2インスタンスや、VPC、RDS等は省略)

下準備

こちらからリポジトリをcloneしてください。
各Dockerfileと設定ファイルを用意しています。

$ git clone https://github.com/s14228so/ECS-Rails6.git
$ cd ECS-Rails6
$ docker-compose run app rails new . --force --database=mysql --api
$ vi config/database.yml
config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password #編集
  host: db #編集
# ...省略
production:
  <<: *default
  database: <%= ENV['DB_DATABASE'] %>
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>

本番環境用の環境変数は後ほどECSのタスク定義にて設定します。

Rails6から必要なそうなので追加します。(同じlocalhostなら問題ない気がするんですが、これがないとnginx(localhost:80)にアクセスするときにエラーが起きてしまいました。。わかる人いたら教えてください)

config/environments/development.rb
Rails.application.configure do
  config.hosts.clear #追加
$ cp docker/rails/puma.rb config/ (pumaの設定を上書き)
$ mkdir -p tmp/sockets  (socketファイルの置き場所を確保)
$ docker-compose up -d --build 
$ docker-compose run app rails db:create

http://localhost
にアクセスするとRailsのデフォルト画面が表示されるはずです。
localhost(:80)にアクセスするとnginxのwebサーバに(裏ではnginxがpumaにリクエストしている)
localhost:3000にアクセスするとpumaのアプリケーションサーバにリクエストを投げていることになります。

スクリーンショット 2020-02-05 8.11.50.png

scaffoldでjsonを返す簡易apiを作成します。

$  docker-compose run app rails g scaffold Post title:string
$  docker-compose run app rails db:migrate
seeds.rb
5.times.each do |i|
  Post.create(title: "test#{i + 1}")
end

$ docker-compose run app rails db:seed

下の画像のようにjsonが返ってきてればOKです!

スクリーンショット 2020-02-07 20.43.36.jpg

ECRへのpush

AWSのマネジメントコンソールを開きます。サービスタブをクリックしてECRと検索します。

RailsNginxのイメージ用のリポジトリをそれぞれ作成します。

リポジトリを作成をクリック
スクリーンショット 2020-02-05 9.23.52.png

まずはrailsのイメージから作成します。
リポジトリ名にはrails-apiを(任意のリポジトリ名で問題ありません)
スクリーンショット 2020-02-05 9.24.05.png

ECRヘのpushコマンドを表示します。
スクリーンショット 2020-02-05 9.28.59.png

プッシュコマンドの表示をクリックすると4つのコマンドが表示されるので一つずつターミナルで入力しましょう。

※ちなみに1つ目のコマンドでaws command not foundと言われる場合
aws-cliが入っていいないはずなので、でaws-cliをインストールしましょう。

$ curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"
$ unzip awscli-bundle.zip
$ sudo ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws

※2番目のbuild時に以下のように-fでDockerfileのコンテキストを変えることに注意してください。

$ docker build -t rails-api -f ./docker/rails/Dockerfile .

ECRヘのpushコマンドを4つ全て打ち終えたらECRのリポジトリにpushされていることを確認しましょう。

同様にnginxのDockerイメージも、ECRにリポジトリを作成してpushします。
先ほどと同じく2つ目のコマンドのみ以下のようコンテキストに注意が必要です。

$ docker build -t nginx -f ./docker/nginx/Dockerfile .

VPCの作成

まずはEIPの設定から

VPCから左側のElastic IPタブをクリック
新しいアドレスの割り当てをクリック => Amazon プール**を選択 => **割り当て

さて、VPCを作成していきましょう。

ちなみに今回作成するサブネットとCIDRの構成はこんな感じ

ap-northeast-1a ap-northeast-1c
Public 10.0.0.0/24 10.0.2.0/24
Private 10.0.1.0/24 10.0.3.0/24

左上のVPCダッシュボードをクリック => VPCウィザードの起動 => パブリックとプライベート サブネットを持つ VPCを選択

以下のように編集して作成します。
スクリーンショット 2020-02-07 4.25.12.png

RDSを作成する際にVPCが複数のAZで構成されている必要があるので
パブリックとプライベートサブネットそれぞれを1cのAZにも追加します。

サブネットのタブをクリック => サブネットの作成

スクリーンショット 2020-02-07 4.29.54.png

プライベートなサブネットも同様に作成します。
スクリーンショット 2020-02-07 4.29.13.png

public-1aサブネットのIPv4のアドレス指定動作を変更します。

スクリーンショット 2020-02-07 4.34.47.png

スクリーンショット 2020-02-07 4.34.55.png
[パブリック IPv4 アドレスの自動割り当てを有効にする] チェックボックスをオンにし、[Save (保存)] を選択します。

ここで注意したいのは先ほど後から追加したpublic-1cですが、サブネット作成時にデフォルトでプライベートルートテーブルが選択されます。
public-1aのようなインターネットゲートウェイが設定されているパブリックルートテーブルにに修正します。

スクリーンショット 2020-02-07 4.39.23.png

public-1cを選択した状態でアクション => ルートテーブルの編集をクリック

スクリーンショット 2020-02-07 4.38.16.png

public-1aのルートテーブルのIDと同じルートテーブルのIDを選択して保存します。
スクリーンショット 2020-02-07 4.42.34.png

RDSの作成

エンジンのオプションでMySQLを選択
テンプレート => 無料枠

スクリーンショット 2020-02-07 0.32.21.png

=>VPCで先ほど作成したVPCを選択

スクリーンショット 2020-02-07 5.33.33.png

データベースの作成をクリック!

ECSでタスクの作成

マネジメントコンソールからECSを検索

左タブのタスク定義を選択 => 新しいタスク定義の作成をクリック

起動タイプの互換性: EC2を選択して次へ

スクリーンショット 2020-02-07 5.42.16.png

タスク定義名に任意の名前を入力します。
ネットワークモードにbridgeを選択

スクリーンショット 2020-02-07 0.35.47.png

一番下の方までスクロールしていくとボリュームの追加という項目があります。
・ボリュームの追加を選択
スクリーンショット 2020-02-07 21.45.14.png

ここでスコープをsharedに指定するThere is already a server bound to: <socket>あとで怒られてしまいます。taskにするように注意してください

中間の方まで戻リます。
タスクサイズは両方512を設定します。
**コンテナの追加というボタンがあるのでクリック
ここで
nginx*rails*のコンテナをそれぞれ追加していきます。

まずはnginxのコンテナから。

コンテナ名はnginx
イメージにECRリポジトリに登録してあるnginxイメージのURIをコピペします。
ポートマッピングにはホストを0、コンテナポートを80に設定します。

スクリーンショット 2020-02-07 5.49.35.png

マウントポイントで先ほど作成したsocketsボリュームをマウントします。
また、Cloud Watch Logsにログを吐き出したいのでログ設定にチェックします

スクリーンショット 2020-02-07 5.51.42.png

他はとりあえず全て空欄でokです!

次にrailsのコンテナを作成します。

先ほどと同様にイメージにECRのrailsイメージのURIをコピペし、ボリュームマウントにsocketsを指定し(パスも同様に/app/tmp/sockets)、ログ設定にもチェックをいれましょう。

ポートマッピング: ホストを0、コンテナポートを3000

また、起動時のコマンドを追加します。作業ディレクトリは/appで。
スクリーンショット 2020-02-07 6.09.38.png

bundle,exec,puma,-C,config/puma.rb

環境変数を追加します。

environmet.png

DB_HOST: RDSのエンドポイント
DB_DATABASE: RDSのDB識別子
RAILS_MASTER_KEY: ローカルのconfig/master.keyの値を貼り付けます。

右下の作成ボタンをクリック!

ロードバランサーの作成

マネジメントコンソールからEC2を検索 => 左タブのロードバランサーを選択
=> ロードバランサーの作成

ALBを選択して作成をクリック
スクリーンショット 2020-02-07 6.20.30.png

名前を適当に入力してAZの設定を行います。
今回は上で作成したVPCを選択し、パブリックサブネットのみ(public-1a、public-1c)を追加します。
スクリーンショット 2020-02-07 6.23.45.png

・セキュリティグループの設定
新しくセキュリティグループを作成します。

スクリーンショット 2020-02-07 6.27.47.png

・ルーティングの設定
ターゲットグループの作成をします
スクリーンショット 2020-02-07 7.46.07.png

・手順 5: ターゲットの登録
ECSの設定で登録するのでここではターゲットは登録せずに「次の手順:確認」をクリック

作成!

ECSでクラスタの作成

スクリーンショット 2020-02-07 6.40.31.png

ステップ1:クラスターテンプレートの選択
EC2 Linux + ネットワーキングを選択

ステップ2:クラスターの設定
EC2 インスタンスタイプにはt2.microを指定(画像ではm3.mediumにしてますが、料金がかなり高いのでt2.microをお勧めします)
キーペア: EC2インスタンスに接続するキーペアを指定します。

ネットワーキングの項目で今回作成したVPCを選択します。
スクリーンショット 2020-02-07 6.40.31.png

セキュリティグループは新規作成します。
後ほど、ここで新規作成したセキュリティグループを用いて、ECSによって起動するEC2インスタンス、RDS、ALBなどを全て繋ぎ合わせます。

作成をクリック!

作成が完了したら以下のような画面になります。
スクリーンショット 2020-02-07 6.47.18.png

クラスターを作成した時点でコンテナインスタンス(今回は2台)が立ち上がります。

サービスの作成

クラスター一覧で先ほど作成したクラスターを選択します。
サービスという項目に作成ボタンがあるのでそちらをクリックします

スクリーンショット 2020-02-07 18.59.12.png

ステップ 1: サービスの設定

タスク定義に作成済みのタスクを指定
クラスターに作成済みのクラスターを指定
サービス名に任意の名前を入力

サービス対応はレプリカを、
タスクの数は2を設定します。

ECSで言うタスクコンテナ群を意味します。

レプリカはクラスター全体で必要なコンテナ数を指定してそれを維持します。
デーモンでは、各コンテナインスタンスにコンテナのコピーを1つ配置し維持します。

スクリーンショット 2020-02-07 6.55.50.png

他は変更せずに次のステップ

ステップ 2: ネットワーク構成
ロードバランシングでApplication Load Balancerにチェックをいれます。

スクリーンショット 2020-02-07 7.00.31.png
スクリーンショット 2020-02-07 7.01.20.png

サービスの検出の統合の有効化のチェックを外します。

ステップ 3: Auto Scaling (オプション)
・今回はサービスの必要数を直接調整しないをチェックして次のステップへ

サービスの作成をクリック!

このサービスの作成で、これまで作成してきたクラスタとタスク、ALBを全て繋ぎ合わせることができました。

セキュリティグループの設定

ここが重要です!
今まで作成してきたコンポーネント間のトラフィックを許可するためにECSのクラスター作成時に一緒に新規作成したセキュリティグループのインバウンドを以下のように修正します。

スクリーンショット 2020-02-11 11.23.16.png

また、EC2インスタンスからRDSにアクセスできるようにします。
[サービス]タブで、RDSを検索してクリック => 左側のメニューで、「データベース」をクリック。
次に、作成したデータベースを見つけてクリック => 下のタブでセキュリティグループをクリック

スクリーンショット 2020-02-07 5.36.13.png

以下のようにインバウンドを編集します。
EC2インスタンスからポート3306 への MySQLトラフィックを許可します。
こうすることで、EC2インスタンスからDBインスタンスに接続できるようにします。
(要はこれしないとrailsコンテナ内でrails db:migrateとか, mysql -u root -pできなくなるってことです)

スクリーンショット 2020-02-11 11.28.21.png

EC2インスタンスにSSHログイン

スクリーンショット 2020-02-07 7.14.45.png

IPv4 パブリック IPをコピーしておきます。

ターミナルを開きます。

$ ssh -i [キーペアのpath] ec2-user@[パブリック IP]

スクリーンショット 2020-02-07 7.16.35.png

$ docker ps

とするとコンテナが立ち上がっていることがわかります。
スクリーンショット 2020-02-07 7.18.19.png
ちなみにdocker imagesとするとECRに登録してあるイメージが出てきます。

Railsのコンテナに入ってデータベースを作成しましょう。

$ docker exec -it [RailsコンテナのID] /bin/bash
$ echo $DB_DATABASE

とするとタスク定義の際に設定した環境変数の値が出力されているはずです。
環境変数でRAILS_ENVにproductionが設定されているのでRAILS_ENV=productionとかしなくでも大丈夫です。

$ rails db:create
$ rails db:migrate
$ rails db:seed

スクリーンショット 2020-02-07 7.24.35.png

[ALBのDNS名]/postsにアクセスして以下のようにjsonが返ってきていれば大成功です!!!

スクリーンショット 2020-02-07 7.27.24.png

※基本的にコンテナのログはCloud Watch Logsに吐き出されているのでそちらを見ればデバッグできると思います。

CircleCI での自動デプロイ化

記事が長くなってきたので、config.ymlの説明は省略します。

公式を参考に!
ECR: https://circleci.com/orbs/registry/orb/circleci/aws-ecr
ECS: https://circleci.com/orbs/registry/orb/circleci/aws-ecs?version=0.0.15

AWS_ECR_ACCOUNT_URLとか大文字になっているものは環境変数なのでCircleCIのプロジェクト設定の
Environment Variablesで設定しておく必要があります。


version: 2.1
orbs:
  aws-ecr: circleci/aws-ecr@6.7.0
  aws-ecs: circleci/aws-ecs@1.1.0
workflows:
  # Nginxのデプロイ
  nginx-deploy:
    jobs:
      - aws-ecr/build-and-push-image:
          account-url: AWS_ECR_ACCOUNT_URL
          region: AWS_REGION
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          create-repo: true
          dockerfile: ./docker/nginx/Dockerfile
          repo: nginx
          tag: "${CIRCLE_SHA1}"
          filters:
            branches:
              only: master
      - aws-ecs/deploy-service-update:
          requires:
            - aws-ecr/build-and-push-image
          family: 'nginx-rails-app' # ECSのタスク定義名
          cluster-name: '${ECS_ARN}'  #ECSのクラスターのARN
          service-name: 'test'  #サービス名
          container-image-name-updates: "container=nginx,tag=${CIRCLE_SHA1}"
  # Railsのデプロイ
  rails-deploy:
    jobs:
      - aws-ecr/build-and-push-image:
          account-url: AWS_ECR_ACCOUNT_URL
          region: AWS_REGION
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          create-repo: true
          dockerfile: ./docker/rails/Dockerfile
          repo: rails
          tag: "${CIRCLE_SHA1}"
          filters:
            branches:
              only: master
      - aws-ecs/deploy-service-update:
          requires:
            - aws-ecr/build-and-push-image
          family: 'nginx-rails-app' # ECSのタスク定義名
          cluster-name: '${ECS_ARN}' #ECSのクラスターのARN
          service-name: 'test' #サービス名
          container-image-name-updates: "container=rails,tag=${CIRCLE_SHA1}"


#AWS_ECR_ACCOUNT_URL => ${アカウントID}.dkr.ecr.${リージョン}.amazonaws.com

ちなみにECSのクラスターのARNはcliで確認できます。

$ aws ecs describe-clusters --cluster
{
    "clusters": [
        {
            "status": "ACTIVE",
            "statistics": [],
            "tags": [],
            "clusterName": "Rails-Nginx",
            "settings": [
                {
                    "name": "containerInsights",
                    "value": "disabled"
                }
            ],
            "registeredContainerInstancesCount": 0,
            "pendingTasksCount": 0,
            "runningTasksCount": 0,
            "activeServicesCount": 0,
            "clusterArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxx:cluster/Rails-Nginx"
        }
    ],
    "failures": []
}

一応ARNの規則的には以下のようになります。
arn:aws:ecs:${リージョン名}:${アカウントID}:cluster/${アカウントID}"

250
286
7

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
250
286