前書き
- 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を選択
RDSを作成する際にVPCが複数のAZで構成されている必要があるので
パブリックとプライベートサブネットそれぞれを1cのAZにも追加します。
サブネットのタブをクリック => サブネットの作成
public-1aサブネットのIPv4のアドレス指定動作を変更します。
[パブリック IPv4 アドレスの自動割り当てを有効にする] チェックボックスをオンにし、[Save (保存)] を選択します。
ここで注意したいのは先ほど後から追加したpublic-1cですが、サブネット作成時にデフォルトでプライベートルートテーブル
が選択されます。
public-1aのようなインターネットゲートウェイ
が設定されているパブリックルートテーブル
にに修正します。
public-1cを選択した状態でアクション => ルートテーブルの編集をクリック
public-1aのルートテーブルのIDと同じルートテーブルのIDを選択して保存します。
RDSの作成
エンジンのオプションでMySQLを選択
テンプレート => 無料枠
=>VPCで先ほど作成したVPCを選択
データベースの作成をクリック!
ECSでタスクの作成
マネジメントコンソールからECSを検索
左タブのタスク定義を選択 => 新しいタスク定義の作成をクリック
起動タイプの互換性: EC2を選択して次へ
タスク定義名に任意の名前を入力します。
ネットワークモードにbridgeを選択
一番下の方までスクロールしていくとボリュームの追加という項目があります。
・ボリュームの追加を選択
ここでスコープをsharedに指定するThere is already a server bound to: <socket>
あとで怒られてしまいます。task
にするように注意してください
中間の方まで戻リます。
タスクサイズは両方512を設定します。
**コンテナの追加というボタンがあるのでクリック
ここでnginx*とrails*のコンテナをそれぞれ追加していきます。
まずはnginxのコンテナから。
コンテナ名はnginx
イメージにECRリポジトリに登録してあるnginxイメージのURIをコピペします。
ポートマッピングにはホストを0、コンテナポートを80に設定します。
マウントポイントで先ほど作成したsocketsボリュームをマウントします。
また、Cloud Watch Logsにログを吐き出したいのでログ設定にチェックします
他はとりあえず全て空欄でokです!
次にrailsのコンテナを作成します。
先ほどと同様にイメージにECRのrailsイメージのURIをコピペし、ボリュームマウントにsocketsを指定し(パスも同様に/app/tmp/sockets)、ログ設定にもチェックをいれましょう。
ポートマッピング: ホストを0、コンテナポートを3000
また、起動時のコマンドを追加します。作業ディレクトリは/appで。
bundle,exec,puma,-C,config/puma.rb
環境変数を追加します。
DB_HOST: RDSのエンドポイント
DB_DATABASE: RDSのDB識別子
RAILS_MASTER_KEY: ローカルのconfig/master.keyの値を貼り付けます。
右下の作成ボタンをクリック!
ロードバランサーの作成
マネジメントコンソールからEC2を検索 => 左タブのロードバランサーを選択
=> ロードバランサーの作成
名前を適当に入力してAZの設定を行います。
今回は上で作成したVPCを選択し、パブリックサブネットのみ(public-1a、public-1c)を追加します。
・セキュリティグループの設定
新しくセキュリティグループを作成します。
・手順 5: ターゲットの登録
ECSの設定で登録するのでここではターゲットは登録せずに「次の手順:確認」をクリック
作成!
ECSでクラスタの作成
ステップ1:クラスターテンプレートの選択
EC2 Linux + ネットワーキング
を選択
ステップ2:クラスターの設定
EC2 インスタンスタイプにはt2.micro
を指定(画像ではm3.mediumにしてますが、料金がかなり高いのでt2.microをお勧めします)
キーペア: EC2インスタンスに接続するキーペアを指定します。
セキュリティグループは新規作成
します。
後ほど、ここで新規作成したセキュリティグループを用いて、ECSによって起動するEC2インスタンス、RDS、ALBなどを全て繋ぎ合わせます。
作成をクリック!
クラスターを作成した時点でコンテナインスタンス(今回は2台)が立ち上がります。
サービスの作成
クラスター一覧で先ほど作成したクラスターを選択します。
サービスという項目に作成ボタンがあるのでそちらをクリックします
ステップ 1: サービスの設定
タスク定義に作成済みのタスクを指定
クラスターに作成済みのクラスターを指定
サービス名に任意の名前を入力
サービス対応はレプリカを、
タスクの数は2を設定します。
ECSで言うタスクはコンテナ群を意味します。
レプリカはクラスター全体で必要なコンテナ数を指定してそれを維持します。
デーモンでは、各コンテナインスタンスにコンテナのコピーを1つ配置し維持します。
他は変更せずに次のステップへ
ステップ 2: ネットワーク構成
ロードバランシングでApplication Load Balancerにチェックをいれます。
サービスの検出の統合の有効化のチェックを外します。
ステップ 3: Auto Scaling (オプション)
・今回はサービスの必要数を直接調整しないをチェックして次のステップへ
サービスの作成をクリック!
このサービスの作成で、これまで作成してきたクラスタとタスク、ALBを全て繋ぎ合わせることができました。
セキュリティグループの設定
ここが重要です!
今まで作成してきたコンポーネント間のトラフィックを許可するためにECSのクラスター作成時に一緒に新規作成したセキュリティグループのインバウンドを以下のように修正します。
また、EC2インスタンスからRDSにアクセスできるようにします。
[サービス]タブで、RDSを検索してクリック => 左側のメニューで、「データベース」をクリック。
次に、作成したデータベースを見つけてクリック => 下のタブでセキュリティグループをクリック
以下のようにインバウンドを編集します。
EC2インスタンスからポート3306 への MySQLトラフィックを許可します。
こうすることで、EC2インスタンスからDBインスタンスに接続できるようにします。
(要はこれしないとrailsコンテナ内でrails db:migrateとか, mysql -u root -pできなくなるってことです)
EC2インスタンスにSSHログイン
IPv4 パブリック IPをコピーしておきます。
ターミナルを開きます。
$ ssh -i [キーペアのpath] ec2-user@[パブリック IP]
$ docker ps
とするとコンテナが立ち上がっていることがわかります。
ちなみに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
[ALBのDNS名]/postsにアクセスして以下のようにjsonが返ってきていれば大成功です!!!
※基本的にコンテナのログは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}"