使用Cloudformation在ECS上构建Redash,并进行代码管理
首先
关于Redash环境的构建,在官方的快速导航中介绍了各种方法,包括Docker镜像和针对AWS的AMI也提供了。因此,如果希望在EC2上简单运行,可以相对容易地进行构建。
然而,QuickNavi的配置是在单个EC2实例内执行各种Docker进程。这使得它在快速验证方面表现良好,但在生产环境中对可用性是令人担忧的。因此,本次我们将在ECS上以冗余配置来构建服务器、PostgreSQL、Redis等,并最终使用Cloudformation将这些配置进行编码。

2. Redash到底是什么?
由於已在其他網站詳細介紹,因此在這裡只做簡單介紹。
Redash 是一款开源工具,支持连接各种数据源,如 SQL/NoSQL/BigData/API,并可进行数据的整合和分析。它提供以下功能。
-
- クエリティエディタ
-
- ダッシュボード
-
- アラート
- API
换句话说,你可以对数据库发送查询或从诸如BigQuery、Athena等查询服务中提取数据,并将结果作为API提供,或者在仪表板上进行可视化。
查询编辑器
可以使用各种数据源的查询结果,并将不同的数据源整合在一起。(图表只是示例)

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

应用程序编程接口
尽管没有图表,但查询结果的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进程,并构建冗余配置。
关于配置,请参考下图。

作为构建流程,首先要确保每个组件都不成为单一故障点,将作为数据存储的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的说明如下。
任务容器定义: 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