使用Cloudformation在ECS上构建Redash,并进行代码管理

首先

关于Redash环境的构建,在官方的快速导航中介绍了各种方法,包括Docker镜像和针对AWS的AMI也提供了。因此,如果希望在EC2上简单运行,可以相对容易地进行构建。

然而,QuickNavi的配置是在单个EC2实例内执行各种Docker进程。这使得它在快速验证方面表现良好,但在生产环境中对可用性是令人担忧的。因此,本次我们将在ECS上以冗余配置来构建服务器、PostgreSQL、Redis等,并最终使用Cloudformation将这些配置进行编码。

redash-on-ecs-EC2.png

2. Redash到底是什么?

由於已在其他網站詳細介紹,因此在這裡只做簡單介紹。

Redash 是一款开源工具,支持连接各种数据源,如 SQL/NoSQL/BigData/API,并可进行数据的整合和分析。它提供以下功能。

    • クエリティエディタ

 

    • ダッシュボード

 

    • アラート

 

    API

换句话说,你可以对数据库发送查询或从诸如BigQuery、Athena等查询服务中提取数据,并将结果作为API提供,或者在仪表板上进行可视化。

查询编辑器

可以使用各种数据源的查询结果,并将不同的数据源整合在一起。(图表只是示例)

redash-query-sample.png

仪表盘

您可以在仪表板上将在查询编辑器中定义的查询结果可视化。
此外,您可以通过计划程序设置在查询编辑器中定义的查询的执行,因此也可以实现数据的自动更新。

redash-dashboard.png

应用程序编程接口

尽管没有图表,但查询结果的API化也是可能的。
这非常方便,当想要将查询结果提供给其他系统时,通过使用Redash,即使没有时间去设置数据传输任务或构建API,也可以轻松提供连接数据的端点。

2.2 数据源列表 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

公式集成

3. 在ECS上建立

在QuickNavi的架构中,EC2上运行着一个单独的实例,其中包含了Server、Worker、PostgreSQL和Redis服务器,它们分别以Docker进程的形式启动。
由于这种架构,如果其中任何一个进程停止运行,服务就无法正常工作,更不用说当EC2宕机时,服务也会停止。

因此,本次我们将在ECS上部署各个Docker进程,并构建冗余配置。
关于配置,请参考下图。

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

作为构建流程,首先要确保每个组件都不成为单一故障点,将作为数据存储的PostgreSQL迁移到AWS的RDS上,并采用MultiAZ配置。
而作为缓存服务器的Redis,将迁移到ElaticCache,并且也采用MultiAZ配置,启用自动故障切换。

而且Redash由以下各个过程组成,在ECS上作为Service内的任务启动和管理。

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

 

    • Worker: 設定したクエリを実行するWorker

 

    Scheduler: 設定したクエリの実行Scheduler

3.1 建立程序

本章中,将总结实际的构建步骤。
在构建步骤的解释中,我们将总结Cloudformation的定义示例,但RDS和ElasticCache将被排除在代码化的对象之外。
这是为了避免在使用Cloudformation进行部署时发生意外的替换操作。
由于这方面的管理方式依赖于操作流程,请根据需要添加。

RDS,弹性缓存

首先,我们分别创建一个空的实例。
同时,确保ElasticCache的”Multi-AZ”和”Auto-failover”设置为启用。
另外,由于之后的create_db任务会对初始模式进行定义,在这一点上,为空状态没有问题。

使用角色来完成任务。

这是用于执行ECS任务的角色。
该角色使用了AWS管理的AmazonECSTaskExecutionRolePolicy和CloudWatchAgentServerPolicy策略,但请根据需要进行定制。

  #  -------------------- 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

阿尔巴尼亚

以下是与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 – 路由53

将先前创建的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 ]

任务

以下是有关ECS任务的定义。
这次我们使用ECS的Fargete,所以需要在RequiresCompatibilitiesFARGATE中指定。
ContainerDefinitions用于定义Redash的进程,包括服务器、调度器和工作者(详细信息见后文)。

  # -------------------- 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 

任务容器定义:服务器

以下是用于在任务中执行容器的服务器定义。

请在Docker Hub redash镜像中指定一个官方提供的版本,例如redash/redash:8.0.0。

由于是用于服务器容器,因此请将命令设置为”server”,并且务必将环境设定中的”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_SECRETDBの暗号化で使用REDASH_SECRET_KEYDBの暗号化で使用REDASH_DATABASE_URL先ほど作成したPostgreSQLに関する接続設定REDASH_REDIS_URL先ほど作成したRedisに関する接続設定REDASH_CSV_WRITER_ENCODINGCSVエクスポート時のエンコーディング設定です。

任务容器定义: 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'

任务容器定义: 调度器

以下是用于在任务上运行的容器调度器的定义。

        # 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 ------------------------
  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任务进行初始化

一旦完成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

bannerAds