👋

Hosted RedashにサヨナラしてECS(Fargate)上にRedash(v10)を構築する

2021/12/16に公開

はじめに

この記事は、FOLIO Advent Calendar 2021の16日目の記事です。

今年、Hosted Redashのサービス終了がアナウンスされました。Hosted Redashを導入されている皆様は移行対応に追われたことかと思います。
FOLIOでもHosted Redashを利用していましたが、9月にAWS ECS(Fargate)による自前運用に切り替えました。
Hosted Redashのサービス終了後の発信となってしまいコミュニティの役にたてず心苦しいのですが、一つの事例としてやったことの記録を発信できればと思い、この記事を書いてみました。
トピックとしては色々参考にさせていただいた事例はあったものの、事例の内容をterraformに落とし込むのに苦労したので、そのあたりに焦点をあてて書いています。参考になれば幸いです。

やったこと

  • Redashは移行時(2021年9月)は当初最新であったv10(beta)版を利用し、その後v10正式版のリリースに伴いv10に移行しました。
  • 自前運用にあたって、ECSを利用した運用に切り替え、また各種ミドルウェア含め構築に必要なAWSリソースはIaC(terraform)化しました。
    • ミドルウェアのサイズに関しては最低限に近いスペックで定義しているものが多いですが、これでも一応は動きます。ご所属の組織の規模などに応じてご調整ください
  • Hosted Redash内部のデータについては、移行スクリプトを書きデータ移行を行いました。
  • その他、諸々の周辺リソースや設定も揃えていきました。
  • さらに、自前運用環境ができた時点で、関係者への移行のよびかけなど、業務レベルでの移行作業も行いました。

以下、各項目についての詳細です。

ミドルウェア構築

terraformによる構築サンプルを一緒に載せています。

RDS

以下全てについて言えることですが、最低限のスペックで、パラメータも最低限のものしか入れていないので、各位ご都合に合わせて変更ください。

rds.tf
module "db" {
  # see: https://github.com/terraform-aws-modules/terraform-aws-rds
  source  = "terraform-aws-modules/rds/aws"
  version = "~> 3.0"

  identifier = "<your-redash-rds>"

  engine = "postgres"
  # redash公式は9系を利用しているが、さすがに古すぎるので無視して一旦12で構築する
  engine_version              = "12.6"
  family                      = "postgres12"
  major_engine_version        = "12"
  instance_class              = "db.t3.micro"
  allocated_storage           = 5
  storage_encrypted           = true
  allow_major_version_upgrade = true
  auto_minor_version_upgrade  = true
  apply_immediately           = true

  username = "root"
  password = random_password.rds_root_password.result
  port     = 5432

  vpc_security_group_ids = ["<your-security-group_id>"]

  maintenance_window = "sat:19:00-sat:19:30"
  backup_window      = "18:30-19:00"
  backup_retention_period = 7

  # Enhanced Monitoring - see example for details on how to create the role
  # by yourself, in case you don't want to create it automatically
  monitoring_interval    = "30"
  monitoring_role_name   = "your-redash-rds-MonitoringRole"
  create_monitoring_role = true

  kms_key_id = aws_kms_key.db_encryption_key.arn


  enabled_cloudwatch_logs_exports = ["postgresql"]

  # DB subnet group
  subnet_ids = ["<your-subnet-a>", "<your-subnet-c>"]

  # Database Deletion Protection
  deletion_protection = true

  parameters = [
    {
      name  = "autovacuum"
      value = 1
    },
    {
      name  = "client_encoding"
      value = "utf8"
    }
  ]
}

resource "random_password" "rds_root_password" {
  length           = 30
  override_special = "!$%&*()-_=+[]{}<>:"
}

resource "aws_kms_key" "db_encryption_key" {
  description         = "kms key for database encryption"
  enable_key_rotation = true
}

resource "aws_kms_alias" "db_encryption_key" {
  name          = "alias/kms-redash-db-encryption-key"
  target_key_id = aws_kms_key.db_encryption_key.key_id
}

インスタンスが作成されたら、任意の踏み台などから作成したroot userでRDSに接続し、redash用ユーザーを作成しましょう。

login_rds.sh
psql -h <your-host>.<your-region>.rds.amazonaws.com -U root -p 5432

もちろん本当は頑張ればこのプロセスもIaC化できるのですが、今回は断念し手動実行しました。

create_redash_role.sql
CREATE USER redash WITH PASSWORD '<your-awesome-strong-password>' CREATEDB;
CREATE DATABASE redash;
GRANT ALL ON ALL TABLES IN SCHEMA public TO redash;

redis

特に特別なことはしておらず、最低限のスペックで構築しています。

redis.tf
resource "aws_elasticache_cluster" "redis" {
  cluster_id      = "redash-redis"
  engine          = "redis"
  node_type       = "cache.t2.micro"
  num_cache_nodes = 1
  # 公式がredis3系なのでそれに合わせる
  # see: https://github.com/getredash/redash/blob/0f41f2572060600301a31ac3654dff09395f5ab4/docker-compose.yml#L48
  parameter_group_name = "default.redis3.2"
  engine_version       = "3.2.10"
  port                 = 6379
  maintenance_window = "sat:19:00-sat:20:00"
  subnet_group_name  = aws_elasticache_subnet_group.redis.name
  security_group_ids = ["<your-security-group-id>"]
}


resource "aws_elasticache_subnet_group" "redis" {
  name        = "redash-redis-subnet"
  description = "redis subnet group for redash redis"
  subnet_ids  = ["<your-subnet-a>", "<your-subnet-c>"]
}

rdsとredisにアクセスする際のURLは、Redash本体を構築する際に使うので、パラメータストアに登録して起きましょう。

ECS

全体の構成は参考記事に倣い構築しています。
クラスターは一つだけ作成し、サービスは2つ、タスク定義は最初に一度だけ手動でcreate_db コマンドを動かすためのものも含めて3つ定義しました。

クラスター定義のサンプル

ecs_cluster.tf
resource "aws_ecs_cluster" "redash_ecs_cluster" {
  name = "redash-ecs-cluster"
  setting {
    name  = "containerInsights"
    value = "enabled"
  }
  capacity_providers = [
    "FARGATE",
    "FARGATE_SPOT"
  ]
}

サービス定義のサンプル

Redash server用のサービスとredash worker用のサービスに用意し、workerを負荷に応じてスケールさせたいようなケースに対応するためサービスを分割しています。ただし、worker用のserviceは、後述のALBからアクセスさせる設定以外、中身はほぼ一緒なため、ここではserver用のサービスのみ例示しておきます。
なお、Redashはwebserverやworkerの間でコンテナ間通信を必要としないため、サービス分割にあたってはサービスディスカバリなどの設定は不要です。
また、コメントにも記載していますが、Redashのコンテナが立ち上がるまでに時間がかかるケースがあるため、ヘルスチェックまでの時間を600秒と長くしています。

ecs_service.tf
# ECSサービス
resource "aws_ecs_service" "redash_server_service" {
  name            = "redash-server"
  cluster         = aws_ecs_cluster.redash_ecs_cluster.id
  # 後述
  task_definition = aws_ecs_task_definition.redash_server.arn

  desired_count = 1
  deployment_maximum_percent = 200
  deployment_minimum_healthy_percent = 100
  # デフォルトの設定のままだと立ち上がる前にヘルスチェックが走ってしまうので、ヘルスチェックを始めるまで10分待つ
  # see: https://discuss.redash.io/t/aws-fargate-redash-server-alb/6152
  health_check_grace_period_seconds = 600

  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }

  network_configuration {
    subnets          = ["<your-subnet-a>", "<your-subnet-c>"]
    security_groups  = ["<your-security-group-id>"]
    assign_public_ip = false
  }
  capacity_provider_strategy {
    capacity_provider = "FARGATE"
    base = 2
    weight = 1
  }

  capacity_provider_strategy {
    capacity_provider = "FARGATE_SPOT"
    base = 0
    weight = 0
  }

  enable_execute_command = true

  # ALBからアクセスさせるための設定
  load_balancer {
    target_group_arn = "<your-alb-target-group-arn>"
    container_name   = "redash_server"
    container_port   = 5000
  }

タスク定義のサンプル

サービス定義と紐付けるタスク定義のサンプルです。CPUやメモリは最低限付与していますが、環境によってスケールさせると良いかと思います。

container_definition.tf
# 初回のみ手動で一度だけ実行するコマンドなのでサービスには紐付け不要
resource "aws_ecs_task_definition" "create_db" {
  family                   = "redash_create_db"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  task_role_arn            = "<your-task-role-arn>"
  execution_role_arn       = "<your-task-execusion-role-arn>"
  cpu                      = 1024
  memory                   = 2048
  container_definitions    = file("container_definition/create_db.json")
}

# redash server用のサービスに紐付けるコンテナ定義
resource "aws_ecs_task_definition" "redash_server" {
  family                   = "redash_server"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  task_role_arn            = "<your-task-role-arn>"
  execution_role_arn       = "<your-task-execusion-role-arn>"
  cpu                      = 1024
  memory                   = 2048
  container_definitions    = file("container_definition/redash_server.json")
}

# redash worker用のサービスに紐付けるコンテナ定義
resource "aws_ecs_task_definition" "redash_worker" {
  family                   = "redash_worker"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  task_role_arn            = "<your-task-role-arn>"
  execution_role_arn       = "<your-task-execusion-role-arn>"
  cpu                      = 1024
  memory                   = 2048
  container_definitions    = file("container_definition/redash_worker.json")
}

続いて、タスク定義のcontainer_definitionのサンプルも置いておきます。
namecommand は適宜置換してください。workerなら worker, schedulerなら scheduler, create_dbコマンドを実行したい場合は create_db に置換して利用しましょう。
あとは、他人が作ったクエリを編集出来るようにしたり(参考記事)、不要なquery_runnerを削ったり(参考記事)しています。

container_definition/redash_worker.json
[
    {
        "name": "redash_server",
        "image": "redash/redash:10.0.0.b50363",
        "cpu": 0,
        "stopTimeout": 60,
        "memoryReservation": null,
        "environment": [
            {
                "name": "REDASH_HOST",
                "value": "<your-host>"
            },
	    {
                "name": "PYTHONUNBUFFERED",
                "value": "0"
            },
	    {
                "name": "REDASH_LOG_LEVEL",
                "value": "INFO"
            },
	    {
                "name": "REDASH_FEATURE_SHOW_PERMISSIONS_CONTROL",
                "value": "true"
            },
	    {   
                "name": "REDASH_DISABLED_QUERY_RUNNERS",
                "value": "redash.query_runner.graphite,redash.query_runner.mongodb,redash.query_runner.couchbase,redash.query_runner.url"
            }
        ],
        "secrets": [
            {
                "name": "REDASH_DATABASE_URL",
                "valueFrom": "<your-redah-rds-url>"
            },
            {
                "name": "REDASH_REDIS_URL",
                "valueFrom": "<your-redah-redis-url>"
            }
        ],
        "command": [
            "server"
        ],
        "logConfiguration": {
            "logDriver": "awslogs",
            "secretOptions": null,
            "options": {
                "awslogs-group": "<your-log-group>",
                "awslogs-region": "<your-region>",
                "awslogs-stream-prefix": "<your-prefix>"
            }
        },
        "entryPoint": null,
        "portMappings": [
            {
                "hostPort": 5000,
                "protocol": "tcp",
                "containerPort": 5000
            }
        ],
        "dockerLabels": null
    }
]

ALB+Route53+ACM

作成したECS上で動作するRedash環境を、https経由でアクセス可能にするために、ALB+Route53+ACMのスタックを準備します。
Route53でドメインを取得し、ACMで証明書を作成、そこにALBのリスナーを追加し、ECSの項で追加したターゲットグループを追加しています。
これで、ヘルスチェックが通れば無事ブラウザからredashにアクセスできるようになります。
ヘルスチェックは /ping で実施しましょう。

alb.tf
resource "aws_lb" "redash" {
  name               = "redash-lb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [<"your-alb-sg-id">]
  subnets            = var.subnet_ids
}

resource "aws_lb_target_group" "redash" {
  name        = "redash-target"
  port        = 5000
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = <your-vpc-id>
  health_check {
    # health check用の /ping でhealth checkを行う
    # see: https://discuss.redash.io/t/aws-fargate-redash-server-alb/6152/6
    path     = "/ping"
    interval = 300
    timeout  = 120
  }
}

resource "aws_lb_listener" "forward" {
  load_balancer_arn = aws_lb.redash.arn
  port              = "443"
  protocol          = "HTTPS"
  certificate_arn   = aws_acm_certificate.redash.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.redash.arn
  }
}

resource "aws_acm_certificate" "redash" {
  domain_name = "redash.exapmle.com"
  subject_alternative_names = [
    "*.redash.exapmle.com"
  ]
  validation_method = "DNS"
  tags = {
    Name = "<your-redash-dns-name>"
  }
}

Redash内部のデータの移行

自前ホスティング版redashの移行であれば、旧環境のRDSのデータをdumpして新環境のDBにINSERTするなどが考えられるのですが、Hosted RedashはRDSに直接アクセスできないため、移行スクリプトを書く必要があります。
王道なのは、redash-migrateを使う方法ですが、今回の私のケースに限っては、Hosted Redash以前に利用していた自前Redash(ちなみに当時はEC2運用していました…)からHosted Redashへデータを移行するためのスクリプトがすでに手元にあったので、それを回収して使いました。が、ゼロベースで行うの出ればredash-migrateを使うのが丸いかと思います。

その他、こまごました周辺リソースの作成・準備

  • Redashbot
    • こちらも公式のものを利用していたのですが、yamitzkyさんのbot に乗り換えさせていただきました。構築方法はこちらの記事が参考になります。
    • 弊社の場合は、AWS App runner上にデプロイし運用しています。個人的にこのとき初めてAWS App runnerを利用したのですが、とても手軽に扱え、ちょっとした単一コンテナアプリケーションのデプロイにはピッタリかと思いました。
  • AzureADによるSAML認証設定
    • FOLIOでは、IDaaSとしてAzureADを利用しているため、AzureADによるSAML認証でセキュリティを担保しています。具体的な設定方法は割愛しますが、FOLIOでもリモートワークが当たり前になっているのでVPNレスでアクセスできるのは嬉しいですね。
  • Datadogによる監視
    • エラーログが出ていないか、workerやschedulerといった各コンテナのCPU/メモリ使用量が高止まりしていないかなど最低限の監視を仕込んでいます。

業務レベルの移行

さて、必要なリソースの準備が終わり、自前環境のredashが利用可能になったところで、次にはじまるのが業務レベルの移行です。Redashは社内の各チームに色々な用途で利用されていたため、移行にあたっては下記のようなプロセスを取り、各フェーズに発生した問い合わせに対応しながら移行していきました。

  1. 旧環境(Hosted Redash), 新環境(ECS自前運用)どちらの環境にもアクセス可能な並行稼動期間を設けつつ、新環境への移行を促す。具体的には下記のようなことを行いました。
    1. 旧環境と新環境のRedashURL対応表をconfluence上に作成。
    2. 旧環境にアクセスしている人をログイン履歴から確認し適宜ヒアリング。
    3. slackやConfluence上で旧環境のURLが貼られている場所を探して新環境のURLに置換する。
  2. 上記が完了しある程度移行が完了した段階で、Hosted RedashへのSAMLログイン権限(エンタープライズアプリケーションの割り当て)を一般ユーザーから剥がし、SAMLログインできるメンバーを管理者に絞る。
    1. こうすることで、一般ユーザーが意図せず旧環境を利用していた場合にも新環境の移行を促すことができ、問い合わせが来た場合、必要に応じて管理者が旧環境にもアクセスし問い合わせ対応することができました。

まとめ

無事にHosted RedashにRedash(v10)をECS上で構築し、Hosted Redashに別れを告げることができました。
FOLIOでは2020年のはじめに自前運用(EC2)RedashからHosted Redashに移行し、そして今年9月にHosted Redashの撤退、自前運用(ECS)への移行がはじまった形で、まさかこの短期間で前回含め二度もRedashの移行を行うとは思わなかったというのが正直な所感ですが、色々知識の整理にもなり良い経験になりました。すでにHosted Redashのサービスは終了しているため、この記事が直接役に経つ方は少ないかもしれませんが、これから新規にRedashを立ち上げる方の参考になれば幸いです。

Discussion