概要
現在、運営しているサークルの公式サイトを作成しているのですがデプロイ環境をEC2からECSに引っ越しました。その際に、色々ハマりながらデプロイしたので、方法を忘れないために書きます。
この方法を真似ればFargateへのデプロイが再現できてるので、ぜひ活用してください。
以下のようなインフラ構成を想定しております。
【前編】
- 1.VPCの設定
- 2.RDSの設定
- 3.ECRの設定
- 4.ECSの設定
【後編】
- 5.独自ドメインの設定(お名前ドットコム)
- 6.ACMの設定
- 7.ALBの設定
- 8.CircleCIの設定
追記
sample_public_subnetがtypoで写真内でsample_pubnet_subnetとなっていました。sample_public_subnetだと思って進めてください。
前提
Dockerの環境とAWSでIAMを管理者権限で作成していること前提で話を進めます。また、筆者は以下のような技術を使っています。
名前 | バージョン |
---|---|
リージョン | 東京 |
Docker | 20.10.5 |
Ruby | 2.6.5 |
Rails | 6.1.3 (バージョン5でも可) |
PostgreSQL | 9.6.2 |
注意点
今回のECSの構成をするに当たり、料金が発生します。
お金をかけたくない場合は、すぐに作ったネットワーク構成を削除してください。
1.VPCの設定
Amazon Virtual Private Cloud(VPC)は、AWSの提供する機能の1つで、プライベートな仮想のネットワークを構成できます。このネットワークの中で、仮想サーバ「EC2」など多くのAWSのサービスが起動します。このネットワーク内にEC2やRDSなどを起動させることでアプリケーションを構築することができます。そして、サブネットとはVPCによって作成されるcidrブロックを分割したネットワーク群のことです。サブネット内にEC2やRDSなどを設置します。
以下のような設計で構成します。
1.1 VPCを作成
AWSのサービスよりVPCを選択して、VPCの作成をクリックします。
その後、以下のように設定していきます。
タグ | 値 |
---|---|
名前 | sample_vpc |
CIDRブロック | 10.0.0.0/16 |
※テナンシーとは物理的にハードウェアを占領したいかで、必要がないためデフォルトを選択しています。
1.2 サブネットの作成
VPC内にサブネットを構成します。
サービスからサブネットを選択して、サブネットの作成を押します。
そして、以下のようにプライベートサブネット2つとパブリックサブネット2つの4つのサブネットを構成してください。(プライベートサブネットはVPC内部でのみ通信可能なサブネット(送信先が10.0.0.0/16)のことで、パブリックサブネットは外部のインターネットから通信可能なサブネットのことです。1.3のインターネットゲートウェイの作成と1.4のルートテーブルの作成の時にプライベートとパブリックの作成方法の違いを説明します。)
サブネットのCIDRブロックはVPCのCIDRブロックの16bit目(10.0)までは等しい必要があります。
それ以降は自由で構いません。
ECS用パブリックサブネット
タグ | 値 |
---|---|
名前 | sample_public_subnet1 |
AZ | ap-northeast-1a |
CIDRブロック | 10.0.0.0/24 |
タグ | 値 |
---|---|
名前 | sample_public_subnet2 |
AZ | ap-northeast-1c |
CIDRブロック | 10.0.1.0/24 |
RDS用サブネット
タグ | 値 |
---|---|
名前 | sample_private_subnet1 |
AZ | ap-northeast-1a |
CIDRブロック | 10.0.10.0/24 |
タグ | 値 |
---|---|
名前 | sample_private_subnet2 |
AZ | ap-northeast-1c |
CIDRブロック | 10.0.11.0/24 |
以下はsample_public_subnet1を作成しているときの画面です。
※VPC IDには作成したVPC(sample_vpc)を選択してください。
1.3 インターネットゲートウェイの作成
インターネットゲートウェイとはVPCとインターネットの通信を可能にするもので、これを設定することによって誰のパソコンからも自分のVPC内のアプリにアクセスできるようになります。
以下の公式リファレンスに詳しい内容が記述されています。
インターネットゲートウェイのサービスを選択して、インターネットゲートウェイの作成を選択します。
タグ | 値 |
---|---|
名前 | sample_igw |
作成が終わったら、作成したインターネットゲートウェイにチェックを入れてアクションからVPCにアタッチするを選択します。
以下でsample_vpcが表示されるので選択します。
1.4 ルートテーブルの作成
ルートテーブルとはサブネットのインスタンスがどこと通信するのかを明示的に定めるものです。ルートテーブルの作成により、サブネットがVPC内のどこと通信できるのか、また外部と通信できるのかを選択できます。ここで、インターネットゲートウェイの通信ルールを入れるか否かで先ほど作成したサブネットをプライベートサブネットにするかパブリックサブネットにするかを決められます。
ルートテーブルの作成を選択し、以下のように設定を行います。
タグ | 値 |
---|---|
名前 | sample_route |
VPC | sample_vpc |
ルートテーブルの作成が完了したら、作成したルートテーブルを選択してアクション→ルートの編集
を選択します。
VPCをインターネットから通信できるようにルールを編集するために全てのIPアドレスを選択する0.0.0.0
を送信先に設定し、ターゲットにはInternet Gateway→先程作成したターゲット
を選択します。
次に、サブネットとの関連付けを行います。
この操作をすることにより1.2で作成したECS用のサブネットをインターネットゲートウェイを介して外部と通信できるようにします。
作成したルートテーブルを選択してアクション→サブネットの関連付けの編集
を選択します。
そうしたらsample_public_subnet1
とsample_public_subnet2
の2つを選択して保存を押します。
1.5 セキュリティグループの作成
セキュリティグループとはファイアウォール機能であり、インスタンスへのアクセスを許可したり、トラフィックを制御することができます。
以下ではECSとRDSのセキュリティグループを設計していきます。
ECSのセキュリティグループ
基本的な詳細
タグ | 値 |
---|---|
名前 | sample_ecs_security |
説明 | 適当 |
VPC | sample_vpc |
インバウンドルール
インバウンドルールはセキュリティグループに関連付けられたインスタンスへのアクセスを制限する
ルールです。
※Fargateだとsshアクセスできないのでポート22番を解放する必要がありません。
タグ | 値 |
---|---|
タイプ | HTTP |
リソースタイプ | 任意の場所 |
アウトバウンドルール
アウトバウンドルールはセキュリティグループに関連付けられたインスタンスからどの送信先にトラフィックを送信できるか決める
ルールです。
変更なし
RDSのセキュリティグループの作成
基本的な詳細
タグ | 値 |
---|---|
名前 | sample_rds_security |
説明 | 適当 |
VPC | sample_vpc |
インバウンドルール
こちらはECSのセキュリティグループからのみアクセスできるように設定します。
※MySQLや他のデータベースを使用する人は、自分のデータベースにあったタイプを選択してください。
タグ | 値 |
---|---|
タイプ | PostgreSQL |
リソースタイプ | カスタム |
ソース | sample_ecs_security |
アウトバウンドルール
変更なし
2.RDSの設定
ここからRDS周りの設定に移ります。
Amazon Relational Database Service (RDS)は、AWSのデータベース機能です。こちらを使用することにより、Amazon Aurora、PostgreSQL、MySQL、MariaDB、Oracle データベース、SQL Serveといったデータベースを簡単にセットアップできます。
AWSのアカウントを作成してはじめの1年間は無料枠を使用することによりお金をかけず使用することができます。
2.1 RDS用サブネットグループの作成
RDSの作成には2つ以上のサブネットを束ねたサブネットグループを作成する必要があります。
1.2のサブネットの作成の際に、RDS用のサブネットのAZにap-northeast-1aとap-northeast-1c
の2つを選択しているため、マルチAZにできます。(マルチAZとは複数のAZを使用する構成のことで、1つのAZで障害が起きても他のAZで稼働を続けることができるため可用性が向上します。)
サービス検索よりRDSを検索し、サブネットグループのを選択します。すると、DBサブネットグループを作成
をクリックし以下のように設定します。
サブネットグループの詳細
タグ | 値 |
---|---|
名前 | sample_rds_subnet_group |
説明 | 適当 |
VPC | sample_vpc |
サブネットを追加
タグ | 値 |
---|---|
アベイラビリティゾーン | ap-northeast-1a, ap-northeast-1c |
サブネット | 1.2で作成したRDSのサブネットをそれぞれのAZで選択 |
2.2 RDSの作成
次にRDSを作成します。
RDSを検索後、データベースを選択します。すると、データベースの作成という項目があるのでクリックして以下のように設定を行います。
タグ | 値 |
---|---|
エンジンのオプション | PostgreSQL(自分の使っているDB) |
サブネット | バージョン(自分の使っているDBのバージョン(異なると動作しない可能性があるのでバージョンによる差異があるのか調べたほうがいいかも)) |
タグ | 値 |
---|---|
DBインスタンス識別子 | sample-db |
マスターユーザー名 | postgres |
マスターパスワード | 自分で決める |
DBインスタンスサイズ | db.t2.micro(無料枠) |
タグ | 値 |
---|---|
ストレージタイプ | 汎用(SSD) |
ストレージ割り当て | 20 |
ストレージの自動スケーリング | 有効 |
最大ストレージしきい値 | 1000 |
タグ | 値 |
---|---|
VPC | sample_vpc |
サブネットグループ | sample_rds_subnet_group |
パブリックアクセス可能 | なし |
VPCセキュリティグループ | 既存の選択 |
既存のVPCセキュリティグループ | sample_rds_security |
アベイラビリティゾーン | 指定なし |
エンドポイントはメモしておいてください。
3.ECRへイメージのプッシュ
Amazon Elastic Container Registry (ECR)はDockerイメージのレジストリサービスです。
ここからECRへ自分のDockerイメージのプッシュを行っていきます。
3.1 ECRのリポジトリの作成
まずはDockerイメージをプッシュするためのリポジトリを作成します。
このリポジトリでDockerイメージのバージョン管理を行います。少しGithubに似てますね笑
まずは、Rails用のリポジトリを作成します。
以下のように設定します。
タグ | 値 |
---|---|
可視性設定 | プライベート |
リポジトリ名 | rails-sample |
同様にして、nginxのリポジトリを作成してください。
タグ | 値 |
---|---|
可視性設定 | プライベート |
リポジトリ名 | nginx-sample |
3.2 Dockerfileの確認
(1) Rails
Dockerfileの中身
筆者のDockerfileの中身は以下のようになっています。
FROM ruby:2.6.5
# 以下の記述を追加
ENV RAILS_ENV=production
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get update && \
apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev sudo vim
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn && apt-get install imagemagick
RUN yarn add node-sass
WORKDIR /app
RUN mkdir -p tmp/pids
RUN mkdir -p tmp/sockets
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
COPY yarn.lock /app
COPY package.json /app
RUN bundle install
# ここでyarn installをしないとwebpackerを実行できない
RUN yarn install
COPY . /app
# RUN yarn add jquery popper.js bootstrap
# WARNING:webpack:installをするとwebpackの設定ファイルが初期化されてjqueryなどが使えなくなってしまう
# RUN rails webpacker:install
RUN NODE_ENV=production ./bin/webpack
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
# 以下の記述があることでnginxから見ることができる
VOLUME /app/public
VOLUME /app/tmp
CMD bash -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"
※webpackerを使っていますが、sprocketsを使っている場合はrake assets:precompile
で置き換えてください。
ポイントとしては、Dockerfile内でVOLUMEを設定することで、nginxコンテナからrailsコンテナのsockファイルが見えるようにしてください。
VOLUME /app/public
VOLUME /app/tmp
またentrypoint.shにdbのマイグレーションなどを行う設定をしてください。
※ 2回目以降のタスク実行時にはdb:createとdb:seedを行うとエラーになるのでコメントアウトしてください。
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid
# WARNING:createとseedはfargateの初回のみ実行
# WARNING:タスクを個々に作って実行の方がいいかも
bundle exec rails db:create
bundle exec rails db:migrate
bundle exec rails db:seed
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
(2) nginx
FROM nginx:1.16
RUN apt-get update && \
apt-get install -y apt-utils \
locales && \
echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
locale-gen ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
# 初期状態の設定ファイル
ADD ./nginx/nginx.conf /etc/nginx/nginx.conf
ADD ./nginx/default.conf /etc/nginx/conf.d/default.conf
3.3 ECRの設定
まずはRailsのイメージをプッシュします。
先程作成したsample-rails
というリポジトリを選択するとプッシュコマンドの表示からプッシュコマンドを確認します。
こういう感じでコマンドが出てくると思います。
# awsのログイン
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
# dockerイメージのビルド
docker build -t rails-sample . --no-cache
# dockerイメージのタグ付け
docker tag rails-sample:latest ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/rails-sample:latest
# dockerイメージをECRのリポジトリへプッシュ
docker push ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/rails-sample:latest
※ aws-cliが入っていない場合は以下のリンクよりaws-cliのインストールを行ってください。
※ dockerファイルがカレントディレクトリでDockerfileでない場合
は、以下のように-f
のオプションを付けることでDockerファイル名とパスを選択してください。
(例) nginxディレクトリにあるDockerfile.nginxというdockerファイルを選択したい場合
docker build -f ./nginx/Dockerfile.nginx -t sample-rails . --no-cache
同様にnginxのイメージについても同様の手順でECRのリポジトリへプッシュをしてください。
さらに、リポジトリへプッシュできたことが確認できたら、URIをそれぞれコピーして控えておいてください。(ECSのタスク定義で使います。)
4.ECSの作成
遂にECSの作成に入ります。
Amazon Elastic Container Service (ECS) はクラスターでコンテナをスケーラブルに実行、停止、管理できるサービスです。
簡単に言うと、クラスターと呼ばれる複数のコンテナを束ねることができる基盤の上でコンテナを実行し、サービスを構成するサービスと考えるとわかりやすいかも知れません。
4.1 タスクの作成
ECSのタスクとはコンテナを組み合わせやボリュームの設定などサービスを動作させるための設定を行う部分です。
docker-compose.ymlの役割に似ているかも知れません。
ECS→タスク定義→新しいタスク定義の作成
を選択し、タスクの作成を行いましょう。
起動タイプはFARGATEを選択してください。
タグ | 値 |
---|---|
タスク定義名 | sample |
タスクロール | なし |
タスク実行ロール | ecsTaskExecutionRole |
タスクロール
とはECS上のコンテナ中のアプリケーションのIAMロールのようなものでコンテナ中のアプリケーションが他のサービス(S3やElastic Searchなど)にアクセスする権限を設定することができます。
対してタスク実行ロール
とはタスク定義がAWSのサービスを利用するためのルールで、タスクのログをCloud Watchに出力したり、ECRのコンテナイメージを使用するために用いられます。
↓参考
タグ | 値 |
---|---|
タスクメモリ | 0.5GB |
タスクCPU | 0.25vCPU |
タスクメモリとタスクCPUについては料金を最小限にするために上の設定にしていますが、自分のサービスの規模に合わせて変更してください。
次にコンテナの追加を行います。
railsコンテナとnginxコンテナの2つを追加します。
(1) Railsコンテナ
タグ | 値 |
---|---|
名前 | rails |
イメージ | ECRの作成の際メモしたsample-railsのリポジトリURI |
ポートマッピング | 3000 |
環境変数 | docker-compose.ymlや.envに設定している環境変数(RAILS_MASTER_KEYがCircleCIデプロイをする際に必要になるのでmaster.keyの値を入れておいてください) |
ログ設定 | ON |
(2) Nginxコンテナ
ポイントはボリュームソースにRailsコンテナのコンテナ名(今回の場合はRailsコンテナに付けた名前のrails)を設定しています。
これにより、railsコンテナのsockファイルをnginxコンテナから見ることができるようになります。
タグ | 値 |
---|---|
名前 | nginx |
イメージ | ECRの作成の際メモしたsample-nginxのリポジトリURI |
ポートマッピング | 80 |
ボリュームソース | rails |
ログ設定 | ON |
残りはデフォルトで作成を押します。
以下のようにsample-taskのステータスがActiveになっていればタスク定義成功です。
4.2 クラスターの作成
次にクラスターの作成を行います。
ECS→クラスター→クラスターの作成
を選択する。
Fargateを使ってデプロイするのでネットワーキングのみ
を選択します。
選択したら次のステップへをクリックします。
クラスターの設定は以下のように行います。
そして作成をクリックします。
タグ | 値 |
---|---|
名前 | sample-cluster |
CloudWatch Container Insights | OFF |
4.3 サービスの作成
クラスターが作成できたらサービスを選択し、作成をクリックします。
サービス設定は以下のように行います。今回は料金を最小にするためにタスクの数を1にして行いたいと思います。
設定が終わったら次のステップ
をクリックします。
タグ | 値 |
---|---|
起動タイプ | FARGATE |
タスク定義 ファミリー | sample |
タスク定義 リビジョン | latest |
クラスター | sample-cluster |
サービス名 | sample-service |
タスクの数 | 1 (概要で説明した編成のようにALBによって負荷分散するためには2以上にする必要があります) |
ネットワーク構成は以下のように行います。
まだ独自ドメインを取っていないためALBを設定していないので今回はロードバランサー無しで次のステップへ進みます。
タグ | 値 |
---|---|
クラスターVPC | sample_vpc |
サブネット | sample_public_subnet1 |
セキュリティグループ | sample_ecs_security |
パブリック IP の自動割り当て | ENABLED |
Auto Scalingは今回は調整しない設定にします。
選択したら次のステップへ進みます。
最後に確認画面になりますので、内容を確認したらサービスの作成をクリックしてサービスを起動します。
タスクがRUNNINGになっているのを確認したら、railsのLogsを確認して以下の一番下のログのようにdatabaseが作成されているのを確認してください。
※ データベースとの連携ができない場合、以下のような原因が考えられます。
① ユーザー名かパスワードが誤っている
② RDSのエンドポイントが間違ってる
③ RDSのインバウンドルールにECSのセキュリティグループが設定されていない
①②の対処法はRDSサービスに一度アクセスしてユーザー名とエンドポイントがあっているか確認してください。パスワードは確認ができないため変更からパスワードの変更を行ってください。
③の場合はRDSのセキュリティグループのインバウンドルールにECSのセキュリティグループが設定されているか確認してください。また、インバウンドルールのタイプがPostgreSQLになっているか確認してください
どちらかわからない場合は、一度RDSのセキュリティグループのソースを任意の場所(0.0.0.0)
に変更してタスクを再度実行してください。(確認が終わったらセキュリティ上の問題からすぐにソースをもとに戻してください)
↓のようになっていれば③はOK
4.4 サービスの再作成
4.3でデータベースの作成が終わったので以下のようにentrypoint.sh
をdb:createとdb:seedをコメントアウトします。
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid
# WARNING:createとseedはfargateの初回のみ実行
# WARNING:タスクを個々に作って実行の方がいいかも
# bundle exec rails db:create
bundle exec rails db:migrate
# bundle exec rails db:seed
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
その後、ECRのリポジトリに移動しrails-sampleを選択します。
イメージタグがlatestのものを削除します。
削除が終わったら再度ECRにentrypoint.shを変更したコンテナをプッシュします。
キャッシュが残っているとコンテナの内容が変更されていない可能性があるので --no-cacheは必ず付けてbuildを行ってください。
次にタスク定義の変更を行います。
タスクsampleに移動して最新のリビジョンを選択して新しいリビジョンの作成
をクリックします。(筆者は一度データベースの生成に失敗しているためリビジョンが2になっていますが皆さんの場合は最新が1になっているはずです)
タスク内部に移動したら一度railsコンテナを削除して再度コンテナの追加より設定を行ってください。設定内容は変わらず以下のように行います。
タグ | 値 |
---|---|
名前 | rails |
イメージ | ECRの作成の際メモしたsample-railsのリポジトリURI |
ポートマッピング | 3000 |
環境変数 | docker-compose.ymlや.envに設定している環境変数(RAILS_MASTER_KEYがCircleCIデプロイをする際に必要になるのでmaster.keyの値を入れておいてください) |
ログ設定 | ON |
次にクラスターに移動します。
クラスターsample-clusterを選択し、サービス名sample-serviceを選択してください。
そうしたらリビジョンを最新に変更
してあとは同じままサービスを更新します。
サービスの更新が完了したらしばらく待って以下のようにタスクからタスク定義が最新になっている
のとステータスがRUNNINGになっている
のを確認します。
確認が終わったらタスク定義をクリックします。
パブリックIPを確認できるのでアクセスします。
以下のように自分のサービスがパブリックIPからアクセスできることが確認できるはずです。
仮にアクセスできない場合以下の可能性があります。
①タスク定義のLogsからnginxとrailsのコンテナでエラーが起きている
②タスク定義のサブネットIDをコピーしてVPC→サブネット→ルートテーブル
より送信先にインターネットゲートウェイが追加されているか確認する
↓②の確認例
次回、独自ドメインの設定〜CircleCIの自動デプロイの設定まで行います。
↓後編