バイセル Tech Blog

バイセル Tech Blogは株式会社BuySellTechnologiesのエンジニア達が知見・発見を共有する技術ブログです。

RedashをECS Fargateで構築する

テクノロジー戦略本部の村上です。弊社ではRedashを使用しているのですが、バージョン4だったため、Dockerfileは提供されていたものの現在のように公式でDockerによるデプロイ手順はサポートされていませんでした。今回管理しやすいように新たにRedashを建ててそちらに利用者を移すことにしたので、その建てた際の手順等をご紹介したいと思います。

f:id:nmu0:20210204182805p:plain
引用元: https://github.com/getredash/redash/blob/v8.0.0/client/app/assets/images/logo.png

デプロイの方針

今回は以下の方針で建てました。

  1. AWSのECS FargateにECS CLIでデプロイする
  2. RedashのコマンドをECSのタスクとして叩くことが出来るようにする
  3. terraformでECS以外のリソースを作成する

1については、ECSへのデプロイは基本的にはtask definitionとDockerイメージがあれば良いので色々な方法があります。 今回は、Redashが公式のdocker-composeファイルを公開していることもあり、ecs-cliを使うことにしました。 それによって何か変更があった場合に追従しやすいかなと考えました。

2については、初回のデータベースセットアップや、バージョンアップ時にmigrateコマンドを叩くことなどあるので、コマンドを叩けるようにしておく必要があるからです。 それに加え、今回はデータサイエンスグループから権限変更のコマンドを叩きたいという要望もありました。

3は文字通りですが、次にポイントだけ紹介させて頂きます。

terraformでのリソース作成

  1. ECSクラスターとCloudwatch logsのロググループ、Systems Manager Parameter Storeのパラメータを作成する
  2. RedisとAurora postgresを構築する
  3. VPC Peeringを使うために、VPC Peeringをする予定のVPCのIP rangeと被らないようにしておく
  4. private subnetにECSを配置
  5. ECSで必要なロールを作成する

1については、クラスターもロググループもecs-cliのコマンドやオプションで作成出来るのですが、terraformで管理出来るものは管理したいので作成しておきます。

2は本番稼働ということでマネージドサービスを使います。

3と4は私達の都合です。VPC Peeringを使うのは、データソースとして接続したいDBが別AWSプロジェクトのprivate subnetにあるのでそれに接続するためです。 重複したIPアドレスの範囲にしてしまうと、VPCから作成し直しになるので気をつけましょう。 また、データソースとするDBにIP制限を掛けているので、NATのIPアドレスを指定して出来るようにするためにprivate subnetにECSを配置しました。

5についてはECSに詳しくないとわかりづらいので、少し詳しく説明します。

ECSのロールについて

ECSで関連するロールには、task execution roleとtask roleがあります。

task execution roleは、その名の通りタスクを実行するにあたって必要な権限を割り当てるロールです。ですので例えばECRからイメージを取ってきて使用する、Parameter Storeのパラメータを復号して環境変数として設定する等は、タスク実行時の処理なのでそれらの権限が必要となります。最低限必要な権限はAmazonECSTaskExecutionRolePolicyとして定義されているので、ECRのための権限等必要なものを追加すれば良いです。

task roleはタスクとして動かすプログラム内でAWSのライブラリを使う場合に設定が必要ですが、今回はRedashをそのまま動かすだけなのでtask roleは設定しなくて大丈夫です。

task execution roleについては、今回は公式のDockerイメージを使うのでParameter Storeにさえアクセスして復号出来るように割り振っておけば良いです。 その際に定義するpolicy statementは、公式ドキュメントのRequired IAM permissions for private registry authenticationの項に書かれているのでこれを採用すれば良いです。

必要なリソースをterraformで定義すると以下のようになります。

data "aws_kms_alias" "redash" {
  name = "alias/redash"
}

resource "aws_ssm_parameter" "cookie_secret" {
  name   = "/redash/cookie_secret"
  key_id = data.aws_kms_alias.redash.arn
  type   = "SecureString"
  value  = data.aws_kms_secrets.redash.plaintext["cookie_secret"]
}
  
data "aws_iam_policy_document" "ecs_tasks" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
  }
}

data "aws_iam_policy_document" "ecs_tasks_ssm" {
  statement {
    effect = "Allow"
    actions = [
      "ssm:GetParameters",
      "kms:Decrypt",
    ]

    resources = [
      aws_ssm_parameter.cookie_secret.arn,
      // 他のparameter群は省略
      data.aws_kms_alias.redash.target_key_arn,
    ]
  }
}

resource "aws_iam_policy" "ecs_tasks_ssm" {
  name   = "ecs_tasks_ssm"
  path   = "/"
  policy = data.aws_iam_policy_document.ecs_tasks_ssm.json
}

resource "aws_iam_role" "ecs_task_execution" {
  name               = "ecs-task-execution-role"
  assume_role_policy = data.aws_iam_policy_document.ecs_tasks.json
}

resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
  role       = aws_iam_role.ecs_task_execution.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role_policy_attachment" "ecs_tasks_ssm" {
  role       = aws_iam_role.ecs_task_execution.name
  policy_arn = aws_iam_policy.ecs_tasks_ssm.arn
}

docker-composeの準備

Dockerでのインストール方法は、以下の公式ドキュメントに説明があります。

https://redash.io/help/open-source/setup#-Docker

そこで紹介されているように、以下の公式docker-composeファイルが用意されています。

https://github.com/getredash/setup/blob/master/data/docker-compose.yml

同じリポジトリのsetup.shcreate_config関数を見るとわかりますが、いくつかの環境変数をenvファイルに書き込んでいます。 ですので秘匿情報以外の変数は同様にenvに書き込んでおきましょう。

PYTHONUNBUFFERED=0
REDASH_LOG_LEVEL=INFO
REDASH_REDIS_URL=<REDIS_URL>

ここで言う秘匿情報は、pwgenコマンドを使うことで生成している変数のことです。 これらは自身で値を作成して、terraformでParameter Storeに格納しておきましょう。例えばREDASH_COOKIE_SECRETは、terraformのコードで出てきたaws_ssm_parameter.cookie_secretに該当します。

docker-composeの修正

修正点は、

  1. Redash自体で使用されるPostgresとRedisはマネージドサービスを使うので、docker-composeから削除
  2. 弊社の利用負荷がそこまで高くないのもあり、ワーカーのコンテナをまとめる
    • こちらはそのままでも構わないです
  3. ログの出力先をCloudwatch logsに設定

この3つで、docker-compose.ymlは以下となります。

version: "2"
x-redash-service: &redash-service
  image: redash/redash:<TAGを指定>
  env_file: env
  restart: always
services:
  server:
    <<: *redash-service
    command: server
    ports:
      - "5000:5000"
    environment:
      REDASH_WEB_WORKERS: 4
    logging:
      driver: awslogs
      options:
        awslogs-group: /ecs/redash
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: server
  scheduler:
    <<: *redash-service
    command: scheduler
    environment:
      QUEUES: "celery,scheduled_queries,schemas,queries"
      WORKERS_COUNT: 2
    logging:
      driver: awslogs
      options:
        awslogs-group: /ecs/redash
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: scheduler

commandがworkerのものが無くても良いかという疑問があるかもしれません。 以下のv8.0.0のスクリプトを見てみますと、--beatオプションが付いているかが基本的な違いとなっています。

https://github.com/getredash/redash/blob/v8.0.0/bin/docker-entrypoint

このオプションはcelery beatをcelery workerに同居させるものなので、最低限schedulerを起動すればworkerが無くても大丈夫です。ただし、QUEUESにworkerで指定されていたキューの種類も全て指定する必要があります。名前からおおよそキューの役割がわかるのですが、celeryだけはわからないです。これは、以下のIssueを見るとschemaをrefreshするためのキューのようです。schemaのリフレッシュを定期的に行うためにcelery beatが使われているようです。

https://github.com/getredash/redash/pull/3534

ただ、ひとつ注意点としましては、バージョン9からはceleryではなくRQ(Redis Queue)が使われるので変更があるかもしれません。

ログについては、Dockerの公式のAmazon CloudWatch Logs logging driverに設定できる内容が書かれているので、terraformで作成したロググループをこちらで指定します。

ECSへのデプロイ

ECS CLIの準備は、

  1. Installing the Amazon ECS CLI
  2. Configuring the Amazon ECS CLI

この2つに従えば大丈夫です。そしてデプロイは

Tutorial: Creating a cluster with a Fargate task using the Amazon ECS CLI

に基本的に従います。docker-compose.ymlとは別に、ECS独自の設定を記載したecs-params.ymlが必要となります。

version: 1
task_definition:
  task_execution_role: terraformで作成したtask execution role名を指定
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 2.0GB
    cpu_limit: 1024
  services:
    server:
      secrets:
        - value_from: /redash/cookie_secret
          name: REDASH_COOKIE_SECRET
        # 他のsecretsも同様に設定
    scheduler:
      secrets:
        - # ここにはserverと同じ値を指定
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - private subnetsを指定
      security_groups:
        - 内部からしかアクセスしないので、VPCのデフォルトセキュリティグループを指定
      assign_public_ip: DISABLED # private subnetなので

CPUとメモリの値の組み合わせは、Invalid CPU or memory value specifiedを見るとわかるように既定値が決まっているのでその中から選びます。

デプロイコマンドは以下となります。 ただ、先にデータベース作成が必要となるのでそこはコマンド実行について次に話すのでそこで説明します。

ecs-cli compose --project-name <ECSクラスタ名> \
                --file docker-compose.yml \
                --ecs-params ecs-params.yml \
                service up \
                 --target-groups "targetGroupArn=<terraformで作成したALBのターゲットグループのARN>,containerName=server,containerPort=5000"

Redashコマンド実行環境作成

Redashのコマンドを実行する環境として、task definitionまでは事前に作成しておいて、コマンドを叩く際にaws ecs run-taskで実行するという方法をとりました。 task definitionを作成するために、docker-compose.ymlは1コンテナだけ走らせるためにRedash本体のものと別に作成して、ecs-params.ymlには追記しています。 docker-compose.ymlは以下となります。ほぼRedash本体と同じですが、commandにはcreate_dbを指定しています。

version: "2"
x-redash-service: &redash-service
  image: redash/redash:<TAGを指定>
  env_file: env
services:
  redash-task-runner:
    <<: *redash-service
    command: create_db
    ports:
      - "5000:5000"
    environment:
      REDASH_WEB_WORKERS: 1
    logging:
      driver: awslogs
      options:
        awslogs-group: redash
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: task-runner

そしてecs-params.ymlには以下を追記しています。

  services:
    redash-task-runner:
      secrets:
        - value_from: /redash/cookie_secret
          name: REDASH_COOKIE_SECRET
        - 他のsecretsを設定

上の2ファイルを元に、タスク実行用のtask definitionを作成します。

ecs-cli compose --project-name redash-task-runner \
                --file docker-compose-redash-task-runner.yml \
                --ecs-params ecs-params.yml \
                create

よって初回のデータベースセットアップ時には、

aws ecs run-task --cluster <クラスタ名> \
                 --task-definition redash-task-runner \
                 --count 1 \
                 --launch-type FARGATE \
                 --network-configuration 'awsvpcConfiguration={subnets=[private subnetを指定],securityGroups=[デフォルトセキュリティグループを指定],assignPublicIp=DISABLED}'

のようにcount 1でタスクを実行すれば良いです。

また、他のコマンドを叩きたいときは、containerOverridesで設定を上書きする必要があります。基本的にmanageコマンドを使うはずなので、そのためにrun-taskをラップした以下のようなシェルスクリプトを用意しました。

CMD=$*
echo "command: manage $(IFS=' '; echo $*)"

task_arn=$(aws ecs run-task --cluster redash --task-definition redash-task-runner --count 1 --launch-type FARGATE --overrides "{\"containerOverrides\": [{\"name\":\"redash-task-runner\",\"command\": [\"manage\", \"$CMD\"]}]}" --network-configuration 'awsvpcConfiguration={subnets=[省略],securityGroups=[省略],assignPublicIp=DISABLED}' --output json --query 'tasks[0].taskArn' | sed 's/"//g')
task_id=$(echo $task_arn | cut -f 3 -d/)

echo "Task information:"
echo $task_arn
echo $task_id
echo "Task output:"

aws ecs wait tasks-stopped --cluster redash --tasks $task_arn
ecs-cli logs --task-id $task_id --region ap-northeast-1 --cluster redash

少し実行に時間がかかりますが、シェルスクリプトの引数として例えばgroups listを与えると、ユーザーグループの一覧が確認できます。

メール送信設定

最後にメール送信の設定です。パスワードリセットやアカウント作成時の通知のために、メール設定は必要となります。 そのために、公式のMail Configurationを参考に、 envに以下を追加します。私達はsendgridを使用しているのでその例となっていますが、そうではない方は適宜変更してください。

REDASH_MAIL_SERVER=smtp.sendgrid.net
REDASH_MAIL_PORT=587
REDASH_MAIL_USE_TLS=true
REDASH_MAIL_USERNAME=apikey
REDASH_MAIL_DEFAULT_SENDER=<SENDER_EMAIL_ADDRESS>
REDASH_HOST=<REDASH_HOST>

まとめ

試行錯誤する際に、tjinjinさんのRedashをFargateで立ち上げるが大変に参考となりました。 私の勉強不足で間違っているところもあるかもしれませんが、本記事もまた同様にRedashを建てる際の参考になれば幸いです。