Open1

ECSへのデプロイ(Rails7, Sidekiq, Elasticache)

FukuiFukui

AWS ECSへのデプロイ

とりあえずECSで動く状態で公開することを目指す。
まずはAWS環境を整える

VPC作成

  • VPC一括作成機能で以下を一気に作成
    • IGW作成とアタッチ
    • 2AZで以下を作成
      • パブリックサブネット
      • プライベートサブネット
    • NATGW作成
    • ルートテーブル作成
      • パブリックはIGWに、プライベートはNATGWにルーティング
    • ルートテーブルのサブネットへのアタッチ
    • EIP作成(あとでインスタンスにアタッチする)

SG作成

SG=FWみたいなもの。今回は「ユーザー>ALB(SSL化)>ECS(Webサーバーは建てないので直接3000ポートで待ち受け)>RDS(ポスグレ5432)orRedis(6379)」

  • ALB用
    • インバウンドルールで80,443を許可
  • ECS用
    • インバウンドルールでALBSGからの3000のみを許可
  • RDS用
    • インバウンドルールでECSSGからの5432のみを許可
  • Redis用
    • インバウンドルールでECSSGからの6379のみを許可

Route53

  • ホストゾーン作成
  • お名前ドットコムで取得したドメインのNSをRoute53のホストゾーンのものに変更

ACMで証明書取得

  • 取得したドメインで証明書を取得

ALBの作成

  • EC2のコンソールに移動しターゲットグループを作成(まだインスタンスを作成していないのでターゲットは一旦空でOK)
  • ALBを作成
    • マッピング先としてパブリックサブネットを選択
    • デフォルトのセキュリティグループに加え、先ほど作成したALB用のセキュリティグループを追加し許容する通信を制限
    • HTTPとHTTPSをリスニングする
  • HTTPはHTTPSにリダイレクトするよう設定変更

RDSの作成

  • 無料枠で作成(シングルAZ)
  • サブネットグループを作成しプライベートサブネットを対象にする
  • ※のちのちdatabase.ymlをRDSに合わせて書き換えること
  • password, hostはECSタスク作成時に環境変数を渡してあげる

Redisの作成

  • 「独自のクラスターを作成」
  • 今回は費用を抑えるため最小のインスタンスで作成
  • クラスターを無効, 通信暗号化を無効
    • こちらも本来はどちらも有効にすべきかもだが、後で作成したsidekiqコンテナからのキュー実行でコケたので、一旦単一ノードで尚且つ暗号化せず作成(※その代わりSGでしっかり通信は制御する)。本当ならsidekiqの方でクラスターモードでのredisとの通信の設定とかやりようはありそう…
  • Redis用SGを設定

ECSデプロイの下準備

  • 本番環境ではbin/devではなくrails sを使用したいのでDockerfileのCMDを修正
  • シェルスクリプトで環境変数を参照してsidekiq, rails s, bin/devのいづれかを実行するようにする
    • CMD ["sh", "-c", "if [ \"$SIDEKIQ_ENV\" = 'true' ]; then bundle exec sidekiq -C config/sidekiq.yml; elif [ \"$RAILS_ENV\" = 'production' ]; then rails s -b 0.0.0.0; else bin/dev; fi"]
  • その他、テスト,開発環境でのみ実行するgemが読み込まれるような箇所があれば読み込まれないように修正(じゃないとコンテナ起動時に「そんなモジュール無いよ」と怒られてコンテナが終了する)
# application.rb

if Rails.env.test?
  config.middleware.use RackSessionAccess::Middleware
end
  • entrypoint.shでコンテナ起動時のコマンドを定義。アセットのプリコンパイルとDB関連を行う
    • ※初回起動はDB作成やマイグレーションを行うが、二回目以降はエラーになるのでコメントアウトしてイメージをpushしなおす
if [ "$RAILS_ENV" = "production" ]; then
  bundle exec rails assets:precompile
  # bundle exec rails db:create
  # bundle exec rails db:migrate
  # bundle exec rails db:seed
fi

ECRへのイメージ登録

  • ECRでリポジトリを作成
  • イメージ登録のコマンドが出てくるのでローカルで実行
    • docker build -t beta-master-ecr .でイメージをビルド
    • docker tag beta-master-ecr:latest 908231638687.dkr.ecr.ap-northeast-1.amazonaws.com/beta-master-ecr:latestでイメージにタグを付ける
    • docker push xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/beta-master-ecr:latestでイメージをpush

ECSへのデプロイ

ECSの概要

以下のような構成になっている。

  • タスク(コンテナの起動の定義)
    • どのイメージを利用するか
    • 起動させるマシンのCPU,メモリ数
    • コンテナ起動時に渡す環境変数
  • サービス(タスクの集合体)
    • タスクをいくつ実行するかの定義
    • どのネットワークに配置するか
    • コンテナに適用するSG
    • ALBで負荷分散させるか
  • クラスター(サービスの集合体)
  • ※なんか、タスクとサービスで合わせてcompose.ymlでやっているような定義を行っているようなイメージ

クラスターの作成

  • コンソールから良さげな名前でクラスターを作成する

コンテナに接続できるようIAMロールを編集しておく

  • 後述するが、コンテナにはIAMロールを付与することができる。他のAWSサービスへのアクセスなどをする必要があれば定義しておく
  • また、起動中のコンテナにCLIから接続するためにもIAMロールのアタッチが必要になる
  • 接続に必要なポリシーを作成する
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel"
            ],
            "Resource": "*"
        }
    ]
}
  • ポリシーをIAMロールにアタッチする(今回はAWS管理のecsTaskExecutionRole にアタッチ)

タスクの作成

今回はrails用サービス×2で負荷分散&sidekiqサービス×1で実行したいので、それぞれのタスクを作成する

  • タスク名, CPU, メモリをよしなに設定(今回は最小にする)
  • コンテナに使用するイメージを選択(ECRに登録したイメージのURIを確認し設定する)
  • 公開するポートを設定
    • railsコンテナならTCP,3000
  • 環境変数の設定
    • BETA_MASTER_DATABASE_HOST =作成したRDSのエンドポイント
    • BETA_MASTER_DATABASE_PASSWORD
    • RAILS_ENV =production
    • RAILS_LOG_TO_STDOUT =1
    • RAILS_MASTER_KEY =masterkeyの値
    • RAILS_SERVE_STATIC_FILES =1
    • REDIS_URL =作成したredisのエンドポイントを用いる(redis://xxx:6379)
    • SIDEKIQ_ENV =sidekiqタスクのときだけtrueを設定

サービス作成

railsサービスとsidekiqサービスを定義する

  • タスクを選択する
  • 実行数を選択する
    • 2にすればタスクで定義したコンテナが2つぶん起動する
  • ネットワークを選択(VPC,サブネット,SG)
    • ここで選択したサブネットによしなに起動される
  • ロードバランサーの選択
    • 作成済のロードバランサーを選択する
    • ロードバランサーのリスナーを作成させられるが、あとで編集するので一旦適当に設定しておく
  • ここまででデプロイが開始するので、ログを確認しながら無事起動するか確認する

ALBの設定

  • サービスを作成すると自動的にターゲットグループが作成される
  • ALBのリスナールールを以下のように設定
    • HTTPS:ACMで作成した証明書を利用し、自動的に作成されたターゲットグループにリクエストを飛ばす
    • HTTP:HTTPSにリクエストをリダイレクトさせる

Route53にレコード作成

  • 作成したALBのエイリアスに向けてAレコードを登録
    ここまででサービスにアクセスできるようになっているはず。

サービスを更新する場合

  • タスクはリビジョンというバージョンのようなもので管理されている
  • 何らかの更新がある場合はイメージをプッシュした後にコンソールからリビジョンを新たに作成する
  • サービスのコンソールから更新を行う(「新しいデプロイの強制」にチェックを入れ、新しいリビジョンのタスクを選択すると更新できる)

ECSへの接続

  • 起動中のコンテナにCLIから接続する方法がある
  • まずコンテナへの接続ができるようCLIからサービスをアップデートする
    • aws ecs update-service --cluster [クラスター名] --service [サービス名] --enable-execute-command
  • サービスを更新する(上記参照)
  • aws ecs execute-command --cluster [クラスター名] --task [タスク名] --container [コンテナ名] --interactive --command "bash"でコンテナに接続
  • もしできない場合、enable-execute-commandが有効になっているか再度確認
    • aws ecs describe-services --cluster (クラスター名) --services (サービス名) | grep enableExecuteCommand
    • aws ecs describe-services --cluster (クラスター名) --tasks (タスクID) | grep enableExecuteCommand