Closed13

Rails on Fargate(ESC)入門・構築メモ書き

ピン留めされたアイテム
hiracky16hiracky16

そもそも目的とか ECS にした理由

  • 脳死で EC2 上で Rails アプリを動かしてサービスを運用し続けているので他の選択肢ってどうなん?ってところからスタート
  • Docker は散々さわっているけれどクラウド上のオーケストレーションツール自体は使ったことなかったので体験するという試み

ECS と EKS どっち使う問題

  • AWS という縛りで考えると 2 つのうちどちらを使うかとなった
  • いろいろな記事を読んだ結果難易度的には ECS < EKS という結果に思えた
    • k8s を理解するのに時間を要しそうなイメージ
    • ただデファクトになりつつあるのでいつか概念だけでも理解したいところ…
  • 時間の成約があり、クラウド上でコンテナを使った開発が体験するのが目的だったため ECS を採用

参考
https://dev.classmethod.jp/articles/ecs-and-eks/
https://speakerdeck.com/hamadakoji/mi-eruzi-yang-nipeng-gerukontenahuan-jing-che-di-bi-jiao-ecs-fargate-ekshahe-gade-yi-debu-de-yi-ka?slide=7

hiracky16hiracky16

Dockerfile の ENTRYPOINT と CMD の関係

  • 改めて Dockernize していて気になったところ

  • Dockerfile に ENTRYPOINT を用意する理由

    • 用意すると特定のプロセスが実行されるコンテナとして利用できる
    • ENTRYPOINT がない場合に CMD が実行される(コンテナ起動時のデフォルトのプロセスとなり得る)
    • また ENTRYPOINT で実行されるコマンドの追加引数として CMD が使用されることがある
    • Rails でいうと CMD に rails s を書くことで ENTRYPOINT で CMD が実行されてプロセスが実行される
    • DockerfileのCMDとENTRYPOINTを改めて解説する
  • ここらへん個人的に良し悪しがわからない…

hiracky16hiracky16

AWS に必要なリソースたちを Terraform を使って作成

  • あまり時間掛けなたくなかったので下の記事をほぼコピペ
    • VPC やら subnet などのリソースを作ることができる
  • できれば ECS 周りのリソースも一緒に作りたかった…
    • Rails Fargate Terraform と検索してもあまりいい記事が見つからない
    • wordpress ecs Terraform などで調べると以下のような記事が出てきてすごく参考になった
  • Fargate の作成は頑張る

https://nakamurake-site.com/archives/i-tried-running-wprdpress-with-ecs/
https://www.inomaso.com/post/2020/11/terraform-wordpress-ec2-1/
https://github.com/ibuchh/terraform-ecs-wordpress

hiracky16hiracky16

Aurora Serverless

  • 初めて触ったのでメモ
  • Serverless との名の通り使ってないとき最小限のリソースで負荷に応じてスケールしてくれるのでお得
  • Aurora Serverless に限らずだが RDS のサブネットを別に作る必要があり VPC 内の AZ が違うサブネットを最低 2 つ紐付けなければ行けなかった
  • RDS のユーザーやパスワード、クラスタへのエンドポイントは SSM のパラメータストアに保存
hiracky16hiracky16

改めて Terraform について

  • AWS や GCP、Azure にも対応している Cloud Formation みたいなもの
  • yaml 形式で記述していく
  • Dry Run ができるので既存の環境との差分が明確 ← これ重要
  • 今回用意する AWS 上のリソースはすべて準備できそう
  • version によって記法が結構違うのでネットの記事を参考にする際は注意が必要
    • ※ 今回は 0.13 を使用(あまり意識せずに最新バージョンを入れたため)
    • variable による変数の参照して埋め込むやり方が古い書き方だと ${var.xxx} だが var.xxx で記述可能
    • tags はブロックではなく変数として扱われるため tags = { xxx } みたいな書き方しなければならない
    • AWS 系のリソース名は大体「小文字でハイフンつなぎ」にしなければエラーになる
hiracky16hiracky16

実際に Fargate を立ち上げる(AWS console からやる方法)

  • 参考記事に載ってた手順で立ち上げてみる
  • 以下記事中にはなかったが個人的に躓いた点まとめ

そもそもタスクってなに??

  • 以下僕の理解
  • タスクとはコンテナ立ち上げた際に実行されるコマンド(=プロセス)のこと
    • 例えば Rails だったら rails s
    • また一回限りしか実行しないタスク(rails db:create)などもタスクとして立ち上げて実行しなければならない
      • これがサーバー立てて Rails 運用するのとの大きな違いに思える
  • タスク定義とはそれらタスクを起動に使うイメージやパラメータを予め指定すること
  • サービスはクラスタ内で常時起動しておくタスク(Rails アプリ)を何個起動するかや ELB との紐付け周りを指定するもの
  • クラスタはサービスや起動しているタスクを総合して束ねているもの
    • クラスタではコンテナの起動タイプ(EC2 or Fargate)を指定できる
    • rails db:migrate するだけのタスクはクラスタ内で起動することができる

タスク定義

  • タスクには 1 イメージ 1 コンテナ起動という制約はなく複数のイメージを含めることが可能
  • Rails と Nginx のイメージをどうやって起動するか書いていく

Docker Volum の共有

  • puma + Nginx ソケット通信する際に puma.sock を Nginx から見れるようにしなければならない
  • Fargate の場合は「ボリュームソース」という項目で Rails イメージを指定しておけば見れた

Role の設定がいろいろ抜けていた

  • ECS の role に ECR へのアクセスが含まれていなかった
  • SSM パラメータストアから値を読み出すところ
    • ECS のタスク定義時に ValueForm で環境変数と SSM のパラメータ名を保存すると勝手に読み込んでくれる
      • このとき SSM へのアクセス権を role にアタッチしておかないとできない

[WIP] アプリケーション log の見方

  • 最低限 nginx のアクセスログは cloudwatch に出力されるがアプリケーションのログが見れない…

参考
https://qiita.com/saongtx7/items/f36909587014d746db73#サービスの作成
https://qiita.com/ShoutaWATANABE/items/401efdc09191b2f20990#タスク定義

hiracky16hiracky16

Terraform で ECS 周り作っていく

  • 個人的に一旦コンソール上で理想の環境を作ってから Terraform に書き起こして行くことをおすすめします
    • 何を用意すればいいかわかる
    • タスク定義は json で記述していくがコンソール上でコピーできる
      • 一回作るとコンソールからコピーできる
      • やり方は添付画像参照
    • 以下リソース頃に記述していく

ECS クラスタ

resource "aws_ecs_cluster" "ecs-cluster-1" {
  name               = "${var.pj_name}-ecs-cluster"
  capacity_providers = ["FARGATE", "FARGATE_SPOT"]
  default_capacity_provider_strategy {
    base              = 0
    capacity_provider = "FARGATE"
    weight            = 1
  }
  default_capacity_provider_strategy {
    base              = 0
    capacity_provider = "FARGATE_SPOT"
    weight            = 1
  }
}

タスク定義

  • 4 つのタスクを定義
    • Rails アプリ起動
    • db の作成
    • migrate 実行
    • seed 実行
  • タスク定義自体は json ファイルにして外から読んでくる
    • file("task_definitions/service.json") がその部分
  • aws_iam_role は別途作成
    • SSM や ECR へのアクセスをアタッチしておくこと!
resource "aws_ecs_task_definition" "task-definition-1" {
  family                   = "${var.pj_name}-task-definition"
  container_definitions    = file("task_definitions/service.json")
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.role-1.arn
}

resource "aws_ecs_task_definition" "task-definition-2" {
  family                   = "${var.pj_name}-db-create"
  container_definitions    = file("task_definitions/db_create.json")
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.role-1.arn
}

resource "aws_ecs_task_definition" "task-definition-3" {
  family                   = "${var.pj_name}-db-migrate"
  container_definitions    = file("task_definitions/db_migrate.json")
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.role-1.arn
}

resource "aws_ecs_task_definition" "task-definition-4" {
  family                   = "${var.pj_name}-db-seed"
  container_definitions    = file("task_definitions/db_seed.json")
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  execution_role_arn       = aws_iam_role.role-1.arn
}

サービス & ネットワーク周り

  • LB は別途作成済み
resource "aws_ecs_service" "ecs-service-1" {
  name                               = var.pj_name
  cluster                            = aws_ecs_cluster.ecs-cluster-1.id
  task_definition                    = aws_ecs_task_definition.task-definition-1.arn
  desired_count                      = 1
  platform_version                   = "1.4.0"
  depends_on                         = [aws_lb.aws-lb-1, aws_rds_cluster.rds-cluster-1]
  deployment_minimum_healthy_percent = 100
  deployment_maximum_percent         = 200

  load_balancer {
    target_group_arn = aws_lb_target_group.lb-tg-1.arn
    container_name   = "xxx"
    container_port   = 80
  }

  network_configuration {
    subnets          = [aws_subnet.public-subnet-1.id, aws_subnet.public-subnet-2.id]
    security_groups  = [aws_security_group.sg-1.id]
    assign_public_ip = true
  }
}
hiracky16hiracky16

Rails の mater key の運用

  • デフォルトで gitigonre に追加されているため push はしない
  • System Manager のパラメータストアに登録した
  • 例のごとく ECS からは VolumeForm で読む
hiracky16hiracky16

terraform で RDS 作るときの注意点

  • terraform のあるバージョンから起きるらしい
  • terraform apply するたびにインスタンスが replace (=作り変え)になっていた
  • terraform で rds 作ると AZ がデフォルトで 3 つ指定されるらしい
  • 英語でニューアンス伝わりづらかったけど以下に記されていた
  • 実際には aws_rds_clusteravailability_zones を書かないことで回避
hiracky16hiracky16

aws cli でタスク実行

  • 毎回タスクを実行するためだけに AWS console 立ち上げるの辛いので cli で実行できないか調べてみたらあった
# タスク一覧
$ aws ecs list-task-definitions

# サービスの詳細
$ aws ecs describe-services --cluster my-ecs-cluster --services my-service

# タスクの実行
$ aws ecs run-task --cluster my-ecs-cluster --task-definition task-def-1 --network-configuration {describe-services で得られた awsvpcConfiguration を文字列表記したもの}

# タスクの停止
$ aws ecs stop-task --cluster my-ecs-cluster --task {task_id}
  • run-task を実行するまでにいろいろなパラメータが必要で非常に面倒…
  • 以下の jq で簡略化してみる
    • sh task.sh db-migrate:2 みたいな感じで実行
  • これに関してはうまいやり方が知りたい…
echo "${1} の実行"

output=`aws ecs describe-services --cluster my-ecs-cluster --services my-service | jq ".services[0].networkConfiguration"`

subnets=`echo $output | jq -r '.awsvpcConfiguration.subnets|join(",")'`
echo $subnets
securityGroups=`echo $output | jq -r '.awsvpcConfiguration.securityGroups|join(",")'`
echo $securityGroups
assignPublicIp=`echo $output | jq -r '.awsvpcConfiguration.assignPublicIp'`

aws ecs run-task \
  --cluster my-ecs-cluster \
  --task-definition $1 \
  --network-configuration "awsvpcConfiguration={subnets=[${subnets}],securityGroups=[${securityGroups}],assignPublicIp=${assignPublicIp}}"
hiracky16hiracky16

Rails の production.log を CloudWatch で見る

  1. RAILS_LOG_TO_STDOUT を環境変数に設定(値は何でも良い)
  2. puma.rb に stdout_redirect があったら削除する(3 時間くらいハマった)
  3. grape logger を導入
このスクラップは2021/01/10にクローズされました