RedashをECS上で構築して, Cloudformationでコード管理する
1. はじめに
Redashの環境構築については、公式のQuickNaviに様々な方法が紹介されており、Docker imageや、AWSの向けにはAMIも提供されてます。
そのためシンプルにEC2上で運用するのであれば、比較的容易に構築が可能です。
ただしQuickNaviの構成は、 Dockerプロセス各種が単一のEC2インスタンス内で実行されます。
そのためサクッと検証するには良いのですが、 本番運用において可用性の点で心許ないです。
そこで今回は、 ServerやPostgreSQL、 Redis等をECS上に冗長化構成で構築、
最終的にはその構成をCloudforamtionを使用してコード化を行います。
QuickNavi構成
2. そもそもRedashとは?
既に他のサイトで詳しく紹介されていますので、ここでは簡単に紹介します。
RedashとはSQL/NoSQL/BigData/APIなどの様々なデータソースに対しての接続をサポートしており、各種データの統合、分析を行うことができるOSSツールです。
そして機能については以下を提供しています。
- クエリティエディタ
- ダッシュボード
- アラート
- API
つまり、簡単に言えばデータベースに対してクエリーを投げたり、BigQuery、Athenaなどのクエリ系サービスからデータを抽出して、
その結果をAPIとして提供したり、ダッシュボードで可視化できますよーというものです。
クエリティエディタ
様々なデータソースに対してのクエリ結果を使用し、異なるデータソースを統合することが可能です。
(図はサンプルです)
ダッシュボード
クエリエディタで定義したクエリーの結果をダッシュボードで可視化できます。
またクエリエディタで定義したクエリーの実行はスケジューラ設定が可能なので、データの自動更新も可能です。
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上に展開し冗長化構成にしてみましょう。
構成について以下の図に表します。
構築の流れとしては、まずそれぞれが単一障害点とならないように、
データストアとして使用している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管理の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
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を取得しています。
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を使用するため RequiresCompatibilities
FARGATEに。
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に関する定義ファイル載せておきます。
Discussion