前書き
- ECSのクラスターは起動タイプにFargateとEC2がありますが、今回は使い慣れている
EC2
を使います。 - 本記事はAWSの環境構築は全て
マネジメントコンソール
で行ってます。
(Terraformで1発でやりたいような人は他の記事を見るか、一応GitHubにTerraformでコード化もしているのでそちらを参考に) - docker-composeは以下のような構成です
- Nginxのコンテナ
- Railsのコンテナ
- MySQLのコンテナ
pumaとnginxをsocket通信させます。
※ 今回RailsはAPIモード
で作成しています。
※ フロント部分がないためNginxはあまり意味がないかもしれませんが、練習のため
本記事で目指したいデプロイ構成
(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
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)にアクセスするときにエラーが起きてしまいました。。わかる人いたら教えてください)
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のアプリケーションサーバにリクエストを投げていることになります。
scaffoldでjsonを返す簡易apiを作成します。
$ docker-compose run app rails g scaffold Post title:string
$ docker-compose run app rails db:migrate
5.times.each do |i|
Post.create(title: "test#{i + 1}")
end
$ docker-compose run app rails db:seed
下の画像のようにjsonが返ってきてればOKです!
ECRへのpush
AWSのマネジメントコンソールを開きます。サービスタブをクリックしてECR
と検索します。
RailsとNginxのイメージ用のリポジトリをそれぞれ作成します。
まずはrailsのイメージから作成します。
リポジトリ名にはrails-apiを(任意のリポジトリ名で問題ありません)
プッシュコマンドの表示をクリックすると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**を選択
以下のように編集して作成します。
<img width="1249" alt="スクリーンショット 2020-02-07 4.25.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/303c0509-e270-fd1c-bb8b-0f194838fc7c.png">
RDSを作成する際にVPCが複数のAZで構成されている必要があるので
パブリックとプライベートサブネットそれぞれを1cのAZにも追加します。
**サブネット**のタブをクリック => **サブネットの作成**
<img width="1274" alt="スクリーンショット 2020-02-07 4.29.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/861067c1-d531-7360-53a1-de6bd5c5d5df.png">
プライベートなサブネットも同様に作成します。
<img width="1274" alt="スクリーンショット 2020-02-07 4.29.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/df78b4ba-450d-ab29-3b47-a46cfbcc6583.png">
public-1aサブネットのIPv4のアドレス指定動作を変更します。
<img width="583" alt="スクリーンショット 2020-02-07 4.34.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/825339cc-b4ea-d3ad-be0f-d742e9473666.png">
<img width="702" alt="スクリーンショット 2020-02-07 4.34.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/bc7d62cf-8b1f-ba7b-f218-aa63f0f3e0fa.png">
[パブリック IPv4 アドレスの自動割り当てを有効にする] チェックボックスをオンにし、[Save (保存)] を選択します。
ここで注意したいのは先ほど後から追加したpublic-1cですが、サブネット作成時にデフォルトで`プライベートルートテーブル`が選択されます。
public-1aのような`インターネットゲートウェイ`が設定されている`パブリックルートテーブル`にに修正します。
<img width="889" alt="スクリーンショット 2020-02-07 4.39.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/2bf131ca-8721-6ee6-7444-23e3a9e1f6e1.png">
public-1cを選択した状態で**アクション** => **ルートテーブルの編集**をクリック
<img width="683" alt="スクリーンショット 2020-02-07 4.38.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/157ff5d6-8fbf-7b41-6001-bca4bd5e4e29.png">
public-1aのルートテーブルのIDと同じルートテーブルのIDを選択して保存します。
<img width="1273" alt="スクリーンショット 2020-02-07 4.42.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/afc55868-4d85-be32-da9d-eb80203dae74.png">
# RDSの作成
エンジンのオプションでMySQLを選択
テンプレート => 無料枠
<img width="764" alt="スクリーンショット 2020-02-07 0.32.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/bd92e72e-a510-7e31-ee1f-83cf3dfac146.png">
=>VPCで先ほど作成したVPCを選択
<img width="786" alt="スクリーンショット 2020-02-07 5.33.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/d8640303-51fe-c21c-619c-2fe2bde88dc4.png">
データベースの作成をクリック!
# ECSでタスクの作成
マネジメントコンソールから**ECS**を検索
左タブの**タスク定義**を選択 => **新しいタスク定義の作成**をクリック
起動タイプの互換性: **EC2**を選択して次へ
<img width="1295" alt="スクリーンショット 2020-02-07 5.42.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/e9299076-71df-b612-41ce-f9fd0dbe2588.png">
タスク定義名に任意の名前を入力します。
ネットワークモードに**bridge**を選択
<img width="935" alt="スクリーンショット 2020-02-07 0.35.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/79c8ed57-3f09-f27c-73b2-2ed59991136f.png">
一番下の方までスクロールしていくと*ボリュームの追加*という項目があります。
・ボリュームの追加を選択
<img width="791" alt="スクリーンショット 2020-02-07 21.45.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/8322058f-f5f5-bda7-82c4-8cb38554151f.png">
ここでスコープをsharedに指定する`There is already a server bound to: <socket>`あとで怒られてしまいます。`task`にするように注意してください
中間の方まで戻リます。
タスクサイズは両方**512**を設定します。
**コンテナの追加**というボタンがあるのでクリック
ここで**nginx**と**rails**のコンテナをそれぞれ追加していきます。
まずはnginxのコンテナから。
コンテナ名はnginx
イメージにECRリポジトリに登録してあるnginxイメージのURIをコピペします。
ポートマッピングにはホストを0、コンテナポートを80に設定します。
<img width="834" alt="スクリーンショット 2020-02-07 5.49.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/d5d47f94-2cc9-25f2-93a2-2462b19cbcb4.png">
マウントポイントで先ほど作成したsocketsボリュームをマウントします。
また、Cloud Watch Logsにログを吐き出したいのでログ設定にチェックします
<img width="838" alt="スクリーンショット 2020-02-07 5.51.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/d3d004ec-c9e4-cea5-ce45-a0b5aa82f507.png">
他はとりあえず全て空欄でokです!
次にrailsのコンテナを作成します。
先ほどと同様にイメージにECRのrailsイメージのURIをコピペし、ボリュームマウントにsocketsを指定し(パスも同様に/app/tmp/sockets)、ログ設定にもチェックをいれましょう。
ポートマッピング: ホストを0、コンテナポートを3000
また、起動時のコマンドを追加します。作業ディレクトリは/appで。
<img width="764" alt="スクリーンショット 2020-02-07 6.09.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/0cc52bc8-5a6b-bc18-ca92-e53151bd6e6a.png">
bundle,exec,puma,-C,config/puma.rb
環境変数を追加します。
<img width="811" alt="environmet.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/1bc331d5-4e77-584b-ec0a-4c80e9ca0d90.png">
DB_HOST: RDSのエンドポイント
DB_DATABASE: RDSのDB識別子
RAILS_MASTER_KEY: ローカルのconfig/master.keyの値を貼り付けます。
右下の**作成**ボタンをクリック!
# ロードバランサーの作成
マネジメントコンソールからEC2を検索 => 左タブのロードバランサーを選択
=> ロードバランサーの作成
ALBを選択して作成をクリック
<img width="1263" alt="スクリーンショット 2020-02-07 6.20.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/e288eef3-8001-2cc7-d422-5f5401127d14.png">
名前を適当に入力してAZの設定を行います。
今回は上で作成したVPCを選択し、パブリックサブネットのみ(public-1a、public-1c)を追加します。
<img width="1014" alt="スクリーンショット 2020-02-07 6.23.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/bf0c1819-7630-05ed-26f3-43a8523917fb.png">
・セキュリティグループの設定
新しくセキュリティグループを作成します。
<img width="1275" alt="スクリーンショット 2020-02-07 6.27.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/1102eab2-ab18-00f6-233a-c07d9f4b9397.png">
・ルーティングの設定
ターゲットグループの作成をします
<img width="1260" alt="スクリーンショット 2020-02-07 7.46.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/d989f021-cd86-6921-9176-ce4860be8ff1.png">
・手順 5: ターゲットの登録
ECSの設定で登録するのでここではターゲットは登録せずに「次の手順:確認」をクリック
作成!
# ECSでクラスタの作成
<img width="986" alt="スクリーンショット 2020-02-07 6.40.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/3f803375-d68f-c0c3-9928-5c194f203783.png">
ステップ1:クラスターテンプレートの選択
`EC2 Linux + ネットワーキング`を選択
ステップ2:クラスターの設定
EC2 インスタンスタイプには`t2.micro`を指定(画像ではm3.mediumにしてますが、料金がかなり高いのでt2.microをお勧めします)
キーペア: EC2インスタンスに接続するキーペアを指定します。
ネットワーキングの項目で今回作成したVPCを選択します。
<img width="986" alt="スクリーンショット 2020-02-07 6.40.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/a8136bf1-6e6d-2463-7bfd-e6c497ba2b97.png">
`セキュリティグループは新規作成`します。
後ほど、ここで新規作成したセキュリティグループを用いて、ECSによって起動するEC2インスタンス、RDS、ALBなどを全て繋ぎ合わせます。
作成をクリック!
作成が完了したら以下のような画面になります。
<img width="1173" alt="スクリーンショット 2020-02-07 6.47.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/933d4da0-edf1-2329-fd94-8901f042bec5.png">
クラスターを作成した時点でコンテナインスタンス(今回は2台)が立ち上がります。
# サービスの作成
クラスター一覧で先ほど作成したクラスターを選択します。
サービスという項目に作成ボタンがあるのでそちらをクリックします
<img width="872" alt="スクリーンショット 2020-02-07 18.59.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/8cd70024-27d9-56f0-2821-bb744bdb4a09.png">
ステップ 1: サービスの設定
タスク定義に作成済みのタスクを指定
クラスターに作成済みのクラスターを指定
サービス名に任意の名前を入力
サービス対応はレプリカを、
タスクの数は2を設定します。
ECSで言う**タスク**は**コンテナ群**を意味します。
レプリカはクラスター全体で必要なコンテナ数を指定してそれを維持します。
デーモンでは、各コンテナインスタンスにコンテナのコピーを1つ配置し維持します。
<img width="804" alt="スクリーンショット 2020-02-07 6.55.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/5968de33-4e26-1c89-26bd-e32102b9f6da.png">
他は変更せずに**次のステップ**へ
ステップ 2: ネットワーク構成
ロードバランシングで**Application Load Balancer**にチェックをいれます。
<img width="918" alt="スクリーンショット 2020-02-07 7.00.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/538cd956-c283-02a9-a1e9-bbf818e06729.png">
<img width="913" alt="スクリーンショット 2020-02-07 7.01.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/92356df6-a9bc-6ba5-dc1c-e64816c92b73.png">
サービスの検出の統合の有効化のチェックを外します。
ステップ 3: Auto Scaling (オプション)
・今回はサービスの必要数を直接調整しないをチェックして次のステップへ
サービスの作成をクリック!
このサービスの作成で、これまで作成してきたクラスタとタスク、ALBを全て繋ぎ合わせることができました。
## セキュリティグループの設定
ここが重要です!
今まで作成してきたコンポーネント間のトラフィックを許可するためにECSのクラスター作成時に一緒に新規作成したセキュリティグループのインバウンドを以下のように修正します。
<img width="1124" alt="スクリーンショット 2020-02-11 11.23.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/fefad064-24c7-2559-082d-76a90f74f826.png">
また、EC2インスタンスからRDSにアクセスできるようにします。
[サービス]タブで、RDSを検索してクリック => 左側のメニューで、「データベース」をクリック。
次に、作成したデータベースを見つけてクリック => 下のタブでセキュリティグループをクリック
<img width="955" alt="スクリーンショット 2020-02-07 5.36.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/05612099-5dae-1262-aa54-d93b46043620.png">
以下のようにインバウンドを編集します。
EC2インスタンスからポート3306 への MySQLトラフィックを許可します。
こうすることで、EC2インスタンスからDBインスタンスに接続できるようにします。
(要はこれしないとrailsコンテナ内でrails db:migrateとか, mysql -u root -pできなくなるってことです)
<img width="1123" alt="スクリーンショット 2020-02-11 11.28.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/aec59435-b5cf-67d7-a2f6-a0eb36afea93.png">
## EC2インスタンスにSSHログイン
<img width="1040" alt="スクリーンショット 2020-02-07 7.14.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/7ed8f174-c9ad-ecc4-abcd-f2b07a6a9ce5.png">
IPv4 パブリック IPをコピーしておきます。
ターミナルを開きます。
$ ssh -i [キーペアのpath] ec2-user@[パブリック IP]
<img width="605" alt="スクリーンショット 2020-02-07 7.16.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/b1a16a61-302e-099e-6a78-069ac9c6d1e7.png">
$ docker ps
とするとコンテナが立ち上がっていることがわかります。
<img width="1438" alt="スクリーンショット 2020-02-07 7.18.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/0f7e367e-767e-3bbb-1af7-1ae24943b29a.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
<img width="946" alt="スクリーンショット 2020-02-07 7.24.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/7d9b53d4-3a14-81dd-45cd-22266f112c83.png">
[**ALBのDNS名]/posts**にアクセスして以下のようにjsonが返ってきていれば大成功です!!!
<img width="763" alt="スクリーンショット 2020-02-07 7.27.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251768/46daa1a2-58ec-ee2f-c149-83da18dcfc8f.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`で設定しておく必要があります。
```yml
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}"