LoginSignup
4
2

More than 1 year has passed since last update.

RedashをECS上で構築して, Cloudformationでコード管理する

Posted at

1. はじめに

Redashの環境構築については、公式のQuickNaviに様々な方法が紹介されており、Docker imageや、AWSの向けにはAMIも提供されてます。
そのためシンプルにEC2上で運用するのであれば、比較的容易に構築が可能です。

ただしQuickNaviの構成は、 Dockerプロセス各種が単一のEC2インスタンス内で実行されます。
そのためサクッと検証するには良いのですが、 本番運用において可用性の点で心許ないです。
そこで今回は、 ServerやPostgreSQL、 Redis等をECS上に冗長化構成で構築、
最終的にはその構成をCloudforamtionを使用してコード化を行います。

QuickNavi構成
redash-on-ecs-EC2.png

2. そもそもRedashとは?

既に他のサイトで詳しく紹介されていますので、ここでは簡単に紹介します。

RedashとはSQL/NoSQL/BigData/APIなどの様々なデータソースに対しての接続をサポートしており、各種データの統合、分析を行うことができるOSSツールです。
そして機能については以下を提供しています。

  • クエリティエディタ
  • ダッシュボード
  • アラート
  • API

つまり、簡単に言えばデータベースに対してクエリーを投げたり、BigQuery、Athenaなどのクエリ系サービスからデータを抽出して、
その結果をAPIとして提供したり、ダッシュボードで可視化できますよーというものです。

クエリティエディタ

様々なデータソースに対してのクエリ結果を使用し、異なるデータソースを統合することが可能です。
(図はサンプルです)

redash-query-sample.png

ダッシュボード

クエリエディタで定義したクエリーの結果をダッシュボードで可視化できます。
またクエリエディタで定義したクエリーの実行はスケジューラ設定が可能なので、データの自動更新も可能です。

redash-dashboard.png

API

図はないですが、クエリ結果のAPI化も可能です。
これが結構便利で、クエリで抽出した結果を他のシステムに提供したい場合に、 データ転送用のjobを仕込んだり、APIを構築したりの工数が取れないケースで、Redashを使えば簡単にデータへの接続エンドポイントを提供することが可能になります。

2.2 データソース一覧

利用可能なデータソース一覧については一部ですが以下に記載しています.
以下のようにSQL/NoSQL/BigDataなど様々なのデータストアへ接続をサポートしています。
また全てを確認したい方は下記公式のインテグレーションリンクから確認可能です。

  • Amazon Athena
  • Amazon Aurora
  • Amazon DynamoDB
  • Amazon Redshift
  • Cassandra
  • Elasticsearch
  • Google BigQuery
  • Hive
  • Impala
  • Microsoft SQL Server
  • MongoDB
  • PostgreSQL
  • Snowflake
  • TreasureData

公式integration

3. ECS上で構築

QuickNavi構成ではEC2の単一インスタンス上に、ServerやWorker、PostgreSQL、RedisサーバがそれぞれDockerプロセスとして立ち上がっています。
これではそれぞれのプロセスが一つ停止しただけでサービスとして機能しなくなってしまい、言うまでもなくEC2がダウンした際にもサービスは停止してしまいます。

そこで今回は各DockerプロセスをECS上に展開し冗長化構成にしてみましょう。
構成について以下の図に表します。

redash-on-ecs-ECS (1).png

構築の流れとしては、まずそれぞれが単一障害点とならないように、
データストアとして使用しているPostgreSQLをAWSのRDSへ移行し、MultiAZ構成に
そしてキャッシュサーバとして使用しているRedisについては、ElaticCacheへ移行し、こちらもMultiAZ構成かつ自動フェイルオーバをenabledにします。

そしてRedashは以下の各プロセスで構成されており、これらをECS上のTaskとしてSerivice内で起動、管理します。

  • Server: 可視化ダッシュボードやクエリを設定するWEBアプリケーション
  • Worker: 設定したクエリを実行するWorker
  • Scheduler: 設定したクエリの実行Scheduler

3.1 構築手順

本章では実際の構築手順をまとめていきます。
また構築手順の解説では、Cloudformationの定義例もまとめていきますが、RDSとElasticCacheはコード化対象から除外しています。
これはCloudforamtionによるdeploy処理の際に、意図しないreplace処理が走ることを避けるためです。
この辺りの管理方法は運用フローに依存するため、必要であれば追加してもらえればと思います。

RDS,ElasticCache

こちらはまず空のインスタンスをそれぞれ作成します。
ElasticCacheの「マルチAZ」「自動フェイルオーバー」はenabledにしておきましょう。
また、後述のcreate_dbタスクによって初期スキーマの定義を行うため、この時点では空の状態で問題ありません。

Task用Role

こちらはECS Task実行用Roleです。
ポリシーはAWS管理のAmazonECSTaskExecutionRolePolicyCloudWatchAgentServerPolicyを使用していますが、必要の応じてカスタマイズしてください。

  #  -------------------- Task ExecutionRole  --------------------
  TascExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub ${ProjectName}-task-execution-role
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
        - !Sub arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole

ALB

ECSTaskと紐付けるALBの定義です。
ALBのSchemeは公開するためinternet-facingにしていますが、非公開の場合はinternalにしてください。
ALBTargetGroupでは、予め作成しておいたVPCのIDを定義、
ALBListenerでは、予めACMで作成しておいたCertificateのARNを定義します。

  # -------------------- ALB --------------------
  ALB: #
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${ProjectName}-alb
      Scheme: internet-facing # 公開したくない場合はinternalで
      SecurityGroups:
        - !FindInMap [ !Ref Env, Redash, ALBSecurityGroupId ]
      Subnets: !FindInMap [ !Ref Env, Redash, SubnetIds ]

  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${ProjectName}-tg
      VpcId: !FindInMap [ Common, Redash, VpcId ]
      Protocol: HTTP
      Port: 5000
      TargetType: ip
      HealthCheckPath: /ping
  # ACMは手動作成したものを使用しています.
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DependsOn:
      - ALBTargetGroup
      - ALB
    Properties:
      LoadBalancerArn: !Ref ALB
      Protocol: HTTPS
      Port: 443
      Certificates:
        - CertificateArn: !FindInMap [ Common, Redash, ACMArn ]
      SslPolicy: ELBSecurityPolicy-2016-08
      DefaultActions:
        - TargetGroupArn: !Ref ALBTargetGroup
          Type: forward

Route53

先ほど作成したALBと、予め作成しておいたホストゾーンを紐付けます。
!GetAtt ALB.CanonicalHostedZoneID!GetAtt ALB.DNSNameでは作成したALBのHostedZoneIdやDNSNameを取得しています。

※ALB ReturnValues


  RedashRoute53:
    Type: AWS::Route53::RecordSet
    DependsOn:
      - ALBListener
    Properties:
      Type: A
      AliasTarget:
        HostedZoneId: !GetAtt ALB.CanonicalHostedZoneID
        EvaluateTargetHealth: true
        DNSName: !GetAtt ALB.DNSName
      HostedZoneId: !FindInMap [ Common, Redash, HostZoneId ] #Replacement
      Name: !Sub #Replacement
        - ${SubDomain}.${HostZoneDomain}
        - SubDomain: !FindInMap [ !Ref Env, Redash, SubDomain ]
          HostZoneDomain: !FindInMap [ Common, Redash, HostZoneDomain ]

Task

以下ECS Task定義です。
今回はECSのFargeteを使用するため RequiresCompatibilitiesFARGATEに。
ContainerDefinitionsはRedashのプロセスである, server, scheduler, workerについて定義します(詳細は後述)

  # -------------------- All Task定義 --------------------
  ALLTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      NetworkMode: awsvpc
      Cpu: 4096
      Memory: 16384
      Family: !Sub ${ProjectName}-all-task
      RequiresCompatibilities:
        - FARGATE
      ExecutionRoleArn: !GetAtt TascExecutionRole.Arn
      ContainerDefinitions:
        # 省略.詳細は後述
        # server 
        # scheduler 
        # worker 

Task ContainerDefinitions: Server

以下はTask上で実行するContainerのServer用定義です。

指定Imageはredash/redash:8.0.0のように公式で提供されているImageのversionを指定してください。
Docker Hub redash images

またserver用コンテナのため、Commandはserverを、またEnvironment設定のREDASH_WEB_WORKERSは必ず1をセットしてください。

      ContainerDefinitions:
        # server
        - Image: !FindInMap [ Common, Redash, DockerImage ]
          Name: !Sub ${ProjectName}-server-container
          EntryPoint:
            - /app/bin/docker-entrypoint
          WorkingDirectory: /app
          Command:
            - 'server'
          Environment:
            - Name: 'PYTHONUNBUFFERED'
              Value: '0'
            - Name: 'REDASH_ALLOW_SCRIPTS_IN_USER_INPUT'
              Value: 'true'
            - Name: 'REDASH_COOKIE_SECRET'
              Value: !FindInMap [ !Ref Env, Redash, CookieSecret ]
            - Name: 'REDASH_SECRET_KEY'
              Value: !FindInMap [ !Ref Env, Redash, SecretKey ]
            - Name: 'REDASH_DATABASE_URL'
              Value: !Sub
                - postgresql://${RDSUser}:${RDSPass}@${RDSEndpoint}/postgres
                - RDSUser: !FindInMap [ !Ref Env, RDS, User ]
                  RDSPass: !FindInMap [ !Ref Env, RDS, Pass ]
                  RDSEndpoint: !FindInMap [ !Ref Env, RDS, Endpoint ]
            - Name: 'REDASH_REDIS_URL'
              Value: !Sub
                - redis://${ElasticCacheEndpoint}:${ElasticCachePort}/0
                - ElasticCacheEndpoint: !FindInMap [ !Ref Env, Redis, Endpoint ]
                  ElasticCachePort: !FindInMap [ !Ref Env, Redis, Port ]
            - Name: 'REDASH_HOST'
              Value: ''
            - Name: 'REDASH_DATE_FORMAT'
              Value: 'YY/MM/DD'
            - Name: 'REDASH_LOG_LEVEL'
              Value: !If [ IsPrd, 'INFO', 'DEBUG' ]
            - Name: 'REDASH_WEB_WORKERS'
              Value: '1'
            - Name: 'REDASH_CSV_WRITER_ENCODING'
              Value: 'cp932'
          PortMappings:
            - ContainerPort: 5000
              HostPort: 5000
              Protocol: 'tcp'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-create-group: true
              awslogs-group: !Sub /ecs/${ProjectName}
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: 'server'

その他environmentの解説は以下です。

項目名 内容
REDASH_COOKIE_SECRET DBの暗号化で使用
REDASH_SECRET_KEY DBの暗号化で使用
REDASH_DATABASE_URL 先ほど作成したPostgreSQLに関する接続設定
REDASH_REDIS_URL 先ほど作成したRedisに関する接続設定
REDASH_CSV_WRITER_ENCODING CSVエクスポート時のエンコーディング設定です。

Task ContainerDefinitions: Worker

以下はTask上で実行するContainerのWorker用定義です。

        # worker
        - Image: !FindInMap [ Common, Redash, DockerImage ]
          Name: !Sub ${ProjectName}-worker-container
          EntryPoint:
            - /app/bin/docker-entrypoint
          WorkingDirectory: /app
          Command:
            - 'worker'
          Environment:
            - Name: 'PYTHONUNBUFFERED'
              Value: '0'
            - Name: 'REDASH_ALLOW_SCRIPTS_IN_USER_INPUT'
              Value: 'true'
            - Name: 'REDASH_COOKIE_SECRET'
              Value: !FindInMap [ !Ref Env, Redash, CookieSecret ]
            - Name: 'REDASH_SECRET_KEY'
              Value: !FindInMap [ !Ref Env, Redash, SecretKey ]
            - Name: 'QUEUES'
              Value: 'queries,scheduled_queries,schemas'
            - Name: 'REDASH_DATABASE_URL'
              Value: !Sub
                - postgresql://${RDSUser}:${RDSPass}@${RDSEndpoint}/postgres
                - RDSUser: !FindInMap [ !Ref Env, RDS, User ]
                  RDSPass: !FindInMap [ !Ref Env, RDS, Pass ]
                  RDSEndpoint: !FindInMap [ !Ref Env, RDS, Endpoint ]
            - Name: 'REDASH_REDIS_URL'
              Value: !Sub
                - redis://${ElasticCacheEndpoint}:${ElasticCachePort}/0
                - ElasticCacheEndpoint: !FindInMap [ !Ref Env, Redis, Endpoint ]
                  ElasticCachePort: !FindInMap [ !Ref Env, Redis, Port ]
            - Name: 'REDASH_HOST'
              Value: ''
            - Name: 'REDASH_DATE_FORMAT'
              Value: 'YY/MM/DD'
            - Name: 'REDASH_LOG_LEVEL'
              Value: !If [ IsPrd, 'INFO', 'DEBUG' ]
            - Name: 'WORKERS_COUNT'
              Value: '3'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-create-group: true
              awslogs-group: !Sub /ecs/${ProjectName}
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: 'worker'

Task ContainerDefinitions: Scheduler

以下はTask上で実行するContainerのScheduler用定義です。

        # scheduler
        - Image: !FindInMap [ Common, Redash, DockerImage ]
          Name: !Sub ${ProjectName}-scheduler-container
          EntryPoint:
            - /app/bin/docker-entrypoint
          WorkingDirectory: /app
          Command:
            - 'scheduler'
          Environment:
            - Name: 'PYTHONUNBUFFERED'
              Value: '0'
            - Name: 'REDASH_ALLOW_SCRIPTS_IN_USER_INPUT'
              Value: 'true'
            - Name: 'REDASH_COOKIE_SECRET'
              Value: !FindInMap [ !Ref Env, Redash, CookieSecret ]
            - Name: 'REDASH_SECRET_KEY'
              Value: !FindInMap [ !Ref Env, Redash, SecretKey ]
            - Name: 'QUEUES'
              Value: 'celery'
            - Name: 'REDASH_DATABASE_URL'
              Value: !Sub
                - postgresql://${RDSUser}:${RDSPass}@${RDSEndpoint}/postgres
                - RDSUser: !FindInMap [ !Ref Env, RDS, User ]
                  RDSPass: !FindInMap [ !Ref Env, RDS, Pass ]
                  RDSEndpoint: !FindInMap [ !Ref Env, RDS, Endpoint ]
            - Name: 'REDASH_REDIS_URL'
              Value: !Sub
                - redis://${ElasticCacheEndpoint}:${ElasticCachePort}/0
                - ElasticCacheEndpoint: !FindInMap [ !Ref Env, Redis, Endpoint ]
                  ElasticCachePort: !FindInMap [ !Ref Env, Redis, Port ]
            - Name: 'REDASH_HOST'
              Value: ''
            - Name: 'REDASH_DATE_FORMAT'
              Value: 'YY/MM/DD'
            - Name: 'REDASH_LOG_LEVEL'
              Value: !If [ IsPrd, 'INFO', 'DEBUG' ]
            - Name: 'WORKERS_COUNT'
              Value: '1'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-create-group: true
              awslogs-group: !Sub /ecs/${ProjectName}
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: 'scheduler'

ECS Service

  # ------------------------ Service ------------------------
  ECSService:
    Type: AWS::ECS::Service
    DependsOn:
      - ALLTaskDefinition
      - ALBListener
    Properties:
      Cluster: !Sub ${ProjectName}
      ServiceName: !Sub ${ProjectName}-all-task-service
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
        - ContainerName: !Sub ${ProjectName}-server-container
          ContainerPort: 5000
          TargetGroupArn: !Ref ALBTargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            - !FindInMap [ !Ref Env, Redash, AllTaskSecurityGroupId ]
          Subnets: !FindInMap [ !Ref Env, Redash, SubnetIds ]
          AssignPublicIp: ENABLED
      TaskDefinition: !Ref ALLTaskDefinition
      PropagateTags: TASK_DEFINITION
      EnableECSManagedTags: true

3.2 create_dbタスクによるInitialize

3.1で全体の環境を構築したら、最後にPostgreSQL,Redisの初期設定を行います。
初期設定に関する処理についても、ECSのタスクを使用して実行します。
一度初期設定を行えば、今後使用することはないのでCfn化する必要はありませんが、
以下にタスクに関する定義を載せておきます。

Commandはcreate_dbを指定してください。

  CreateDBTaskDefinition:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      Family: !Sub ${ProjectName}-create_db
      RequiresCompatibilities:
        - 'FARGATE'
      NetworkMode: 'awsvpc'
      ExecutionRoleArn: !Sub ${ExecuteRoleArn}
      Cpu: 4096
      Memory: 16384
      ContainerDefinitions:
        - Image: !FindInMap [ Common, Redash, DockerImage ]
          Name: !Sub ${ProjectName}-create_db-container
          Command:
            - 'create_db'
          Environment:
            - Name: 'PYTHONUNBUFFERED'
              Value: '0'
            - Name: 'REDASH_ALLOW_SCRIPTS_IN_USER_INPUT'
              Value: 'true'
            - Name: 'REDASH_DATABASE_URL'
              Value: !Sub
                - postgresql://${User}:${Pass}@${Endpoint}/${DB}
                - User: !FindInMap [ !Ref Env, RDS, User ]
                  Pass: !FindInMap [ !Ref Env, RDS, Pass ]
                  Endpoint: !FindInMap [ !Ref Env, RDS, Endpoint ]
                  DB: !FindInMap [ !Ref Env, RDS, DB ]
            - Name: 'REDASH_REDIS_URL'
              Value: !Sub
                - redis://${Endpoint}:${Port}/0
                - Endpoint: !FindInMap [ !Ref Env, Redis, Endpoint ]
                  Port: !FindInMap [ !Ref Env, Redis, Port ]
            - Name: 'REDASH_HOST'
              Value: ''
            - Name: 'REDASH_DATE_FORMAT'
              Value: 'YY/MM/DD'
            - Name: 'REDASH_LOG_LEVEL'
              Value: 'INFO'
            - Name: 'REDASH_PASSWORD_LOGIN_ENABLED'
              Value: false

ここまでで、構築は完了です。

最後に

いかがだったでしょうか?
RedashはDockerで提供されているため、ECS上での構築も比較的容易だったのではないでしょうか?

Redashを使用すれば、システムの開発やコーディング不要で、データの抽出や提供が可能になります。
ぜひ使ってみてください。

また、以下に今回使用したCloudformationに関する定義ファイル載せておきます。
https://github.com/kohei789/redash-on-ecs-example

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2