こんにちは、SRE部の谷口(case-k)です。
本記事では、EC2 Image Builderを使いRedashの運用改善を行った事例をご紹介します。運用しているRedashについてご紹介し、その後、Redashの運用課題に対してEC2 Image Builderでどのように解決したかTipsも踏まえご紹介します。
余談ですが全国どこでも働けるようになったので沖縄に住めています(感謝!)
https://press-tech.zozo.com/entry/20210118_zozotechpress-tech.zozo.com
目次
- 目次
- 運用しているRedashの紹介
- Redashの運用課題
- EC2 Image Builderによる課題解決
- EC2 Image Builderの紹介
- EC2 Image Builderの利点・欠点
- EC2 Image Builderが担う範囲の検討
- まとめ
- さいごに
運用しているRedashの紹介
まず運用しているRedashの役割やインフラ構成、クエリ実行の流れ、起動時の処理についてご紹介します。
役割
ZOZOテクノロジーズでは配信基盤をインハウス化して自社で開発しています。メルマガやLINEなど複数のチャンネルに対して配信しています。
Redashの主な役割としては「分析」と「監視」です。
分析では配信施策の状況のモニタリングや施策の効果検証をしています。また、Redashではクエリ実行結果に基づいた監視も可能なので配信状況などの異常検知やデータ連携遅延などの他サービスの監視も行っています。
インフラ構成
ZOZOテクノロジーズで運用しているRedashは、公式に提供されているRedash AMIをベースにしています。Redash AMIからインスタンスを起動すると、Web UIをホスティングするRedashサーバー、クエリの実行を担うRedashワーカーが立ち上がり、Redashを利用できるようになリます。
クエリの数が増えてもRedashワーカーがオートスケールできるよう、ALB配下にはRedash AMIから起動したEC2インスタンスを配置しています。可用性を高めるため、フルマネージドでマルチAZ構成可能なAWSのAurora(PostgreSQL)とElastiCache(Redis)を参照するようにしています。
Redashは社内ツールであり、他サービスの監視ツールとしても使われています。そのため、冗長構成により可用性の高い運用を行っています。
なお、CloudForamtionについてはこちらにまとめました。
クエリ実行の流れ
クエリ実行の流れを説明します。
まず、実行するクエリはWeb UIをホスティングしているRedashサーバーからElastiCache(Redis)に保存されます。
クエリの実行はRedashワーカーで行われるため、RedashワーカーはElastiCache(Redis)に保存されたクエリを取得し、BigQueryなどデータソースに対してクエリを実行します。
そして、クエリの実行結果はAurora(PostgreSQL)に書き込まれます。書き込まれたクエリの実行結果はRedashサーバーより読み出されWeb UIで確認できます。なお、クエリの実行は分散タスクキューのCeleryによって非同期に行われます。
クエリ実行の流れは以下の記事が参考になりました。
EC2インスタンス起動時の処理
EC2インスタンスの起動時にはミドルウェアの接続先を変更するための処理をしています。
Redash AMIは起動時にDocker Composeでコンテナイメージをビルドします。コンテナイメージをビルドするとRedashサーバーやワーカー、ミドルウェアであるPostgreSQL、Redisコンテナが立ち上がります。その際に立ち上がるRedashサーバーやワーカーが参照するミドルウェアの接続先はの環境設定ファイルに定義されています。Redash AMIをそのまま使うと、Docker Composeで立ち上げたPostgreSQL、Redisの接続先は環境設定ファイルに定義されたものが使われます。なお、Redash AMIの起動時のビルド処理はユーザーデータには定義されておりません。
今回行いたいことはインフラ構成にて説明したような冗長構成です。EC2インスタンス2台の冗長構成にするため、ミドルウェアをEC2インスタンスの外で管理する必要があります。AWSのAurora(PostgreSQL)とElastiCache(Redis)を参照するようEC2インスタンスの起動時に環境設定ファイルを書き換えてコンテナイメージをビルドする必要があります。
そのため、ユーザーデータでAWSのCLIを使えるようライブラリをインストールし、CLIでAWSシークレットマネージャー管理下の秘密情報を取得します。秘密情報としてAuroraやElastiCacheのユーザー情報やデータソースの復号化に必要な「REDASH_COOKIE_SECRET」や「REDASH_SECRET_KEY」を管理しています。そして、取得した秘密情報とCloudFormationで作ったリソースに基づいて環境設定ファイルを生成し、コンテナイメージをビルドします。
Redash AMIには起動時にコンテナイメージのビルド処理が組み込まれています。この処理はユーザーデータで定義した処理より前に実行されるため、Redash AMIの古い環境設定ファイルに基づいてコンテナイメージをビルドします。古いコンテナイメージだとミドルウェアの接続先が正しくないため、ユーザーデータでは、Redash AMIで作られたコンテナを落としてから、新しい環境設定ファイルに基づいてビルドしています。
UserData: Fn::Base64: !Sub | #!/bin/bash -e rm /opt/redash/env curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" sudo rm /var/lib/dpkg/lock* sudo dpkg --configure -a sudo apt update sudo apt install python -y sudo python get-pip.py sudo pip install awscli sudo apt install jq -y RedashUsername=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/RDS/User | jq -r .SecretString) RedashPassword=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/RDS/Password | jq -r .SecretString) cat <<EOF > /opt/redash/env PYTHONUNBUFFERED=0 REDASH_LOG_LEVEL=INFO POSTGRES_PASSWORD=${!RedashPassword} REDASH_COOKIE_SECRET=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/CookieSecret | jq -r .SecretString) REDASH_SECRET_KEY=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/SecretKey | jq -r .SecretString) REDASH_REDIS_URL=redis://${ElasticacheClusterForRedash.RedisEndpoint.Address}:${ElasticacheClusterForRedash.RedisEndpoint.Port}/0 REDASH_DATABASE_URL=postgresql://${!RedashUsername}:${!RedashPassword}@${RDSDBClusterForRedash.Endpoint.Address}:${RDSDBClusterForRedash.Endpoint.Port}/${!RedashUsername} REDASH_FEATURE_EXTENDED_ALERT_OPTIONS=true EOF sudo docker-compose -f /opt/redash/docker-compose.yml down sudo docker-compose -f /opt/redash/docker-compose.yml up -d --build
Redashの運用課題
EC2インスタンス起動時にRedash AMI側の古い環境設定ファイルに基づいたビルドと、ユーザーデータで定義した新しい環境設定ファイルに基づいたビルドをしています。2つのビルドが同時に実行されることでRedashワーカーが正常に動かず、クエリ実行結果が返ってこない事象が確認できました。そのため、Redashワーカーを正常に動かすために、EC2インスタンス初回起動時のみ手動でEC2インスタンスの再起動する運用をしていました。せっかくクエリの個数に応じてオートスケールできる仕組みにしたのに活用できずにいました。また、データ分析の他に配信状況やデータ連携の遅延などの監視にも使われているため、監視ツールとしての役割に不安がありました。
EC2 Image Builderによる課題解決
前述のRedashの運用課題はコンテナイメージのビルド処理を制御すれば改善できるため、事前にAMIを作ることで解決できます。
手動でカスタムAMIを作る場合、Redashのバージョンアップやその他リソースの変更の度にカスタムAMIを作る必要があります。加えて、運用における属人化を防ぐ意味でも全てCloudFormationで管理したい思いがありました。
そのため、CloudFormationで管理可能で、カスタムAMIの手動運用が不要なEC2 Image Builderを使うことにしました。
EC2 Image Builderの紹介
EC2 Image BuilderとはカスタムAMIの作成を自動化するサービスです。
CloudFormationによる表現も可能で、一連のAMI作成をコード管理できます。CloudFormation管理にすることで、CloudFormationで作られたリソースに基づいたAMIの生成が可能となり属人的な運用の回避にも繋がります。
ここではCloudFormationを使った各リソースのTipsをご紹介します。
各リソースのTips
EC2 Image BuilderでカスタムAMIを作るときには4つの要素があります。
- コンポーネント
- レシピ
- インフラストラクチャ
- イメージパイプライン
各リソースについてCloudFormationを使いながらご紹介します。なお、「イメージパイプライン」はRedashの運用改善では使っていません。
ここで紹介する、EC2 Image BuilderによるAMI生成の全体図は次の通りです。
事前準備
事前準備としてソースイメージと、EC2 Image Builderに必要なIAMを定義します。
注意点はソースイメージに指定できるものはAWSが指定するマネージドなAMIもしくは、SSMがインストールされたカスタムAMIに限られている点です。そのため、公式に提供されているRedashのAMIをソースイメージに指定できなかったので、Ubuntuのイメージに必要なモジュールをインストールしました。
そして、EC2 Image Builderで必要なIAM権限は「EC2InstanceProfileForImageBuilder」と「AmazonSSMManagedInstanceCore」です。
また、EC2 Image Builderの内部処理としてSSMを呼び出しています。エラーについてもSSMのオートメーションページに出力されます。後述しますがSSMに出力されるエラーはデバッグが容易ではないので注意が必要です。
EC2ImageBuilderForRedash: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder' - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: ImageBuilderInstanceProfile Roles: - !Ref EC2ImageBuilderForRedash
コンポーネント
コンポーネントはカスタムAMI作成に必要な手順を定義したリソースです。
カスタムAMIに必要なモジュールを定義した手順に沿ってインストールします。更新したコンポーネントを反映したい場合はCloudFormation反映時に「Version: 1.0.0」の部分のバージョン番号を変更して適用します。
Component: Type: AWS::ImageBuilder::Component Properties Data: | name: InstallApache description: InstallApache schemaVersion: 1.0 phases: - name: build steps: - name: UpdateOS action: UpdateOS - name: RedashDir action: ExecuteBash inputs: commands: - mkdir /opt/redash - name: docker-install action: ExecuteBash inputs: commands: - sudo apt-get update - sudo apt-get install -y \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - sudo apt-key fingerprint 0EBFCD88 - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io - name: docker-compose-install action: ExecuteBash inputs: commands: - sudo apt-get update - sudo curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose - sudo chmod +x /usr/local/bin/docker-compose - sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose - name: aws-cli-install action: ExecuteBash inputs: commands: - curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" - sudo rm /var/lib/dpkg/lock* - sudo dpkg --configure -a - sudo apt update - sudo apt install python -y - sudo python get-pip.py - sudo pip install awscli - sudo apt install jq -y Name: redash-ami-component Platform: Linux # update version when fix the component Version: 1.0.0
レシピ
レシピはソースとなるイメージとコンポーネントを紐付けるリソースです。
先ほど述べましたが、ソースイメージとして指定できるのはAWSが指定するマネージドなAMI、もしくはSSMがインストールされたカスタムAMIです。
ここで定義したレシピに基づいてAMIが生成されます。なお、コンポーネントを変えた場合はレシピのバージョンも更新して反映します。
Recipe: Type: AWS::ImageBuilder::ImageRecipe Properties: Components: - ComponentArn: !Ref Component Name: redash-ami-recipe # parentImage only accept aws managed image or custom ami installed ssm. so can not use redash ami ParentImage: arn:aws:imagebuilder:ap-northeast-1:aws:image/ubuntu-server-18-lts-x86/2020.9.23 Version: 1.0.0
インフラストラクチャ
インフラストラクチャはイメージのビルドからテストまでの実行環境を定義するリソースです。
「TerminateInstanceOnFailure」を「false」に設定すると、処理の失敗時にインスタンスを終了せずに済みます。そのため、SSMのエラー内容が不十分な際に活用できます。
なお、AMIをビルドする環境はインターネットへ接続できる環境である必要があるので、サブネットを定義する際には注意が必要です。
InfrastructureConfiguration: Type: AWS::ImageBuilder::InfrastructureConfiguration Properties: InstanceProfileName: !Ref InstanceProfile InstanceTypes: [] Name: redash-ami-infrastructure-configuration SecurityGroupIds: [] TerminateInstanceOnFailure: True
カスタムAMIの生成
生成するRedashのイメージは次の通りです。
まず、レシピとイメージを定義しRedashのカスタムAMIを自動生成します。すると、ビルド用のインスタンスが起動、終了した後にテスト用のインスタンスが起動します。
所用時間として30〜60分ほどかかるので時間を短縮したい場合は「ImageTestsEnabled」を「false」に設定する手段もあります。
RedashAmiImage: Type: AWS::ImageBuilder::Image Properties: ImageRecipeArn: !Ref Recipe InfrastructureConfigurationArn: !Ref InfrastructureConfiguration ImageTestsConfiguration: ImageTestsEnabled: true TimeoutMinutes: 60
イメージパイプライン
イメージパイプラインはカスタムAMIの生成をスケジューリング実行するためのリソースです。
今回は使いませんでしたが、先ほど紹介したカスタムAMIの生成処理を定期実行する際に利用できます。他にもRedashの定期的なバージョンアップなどを自動化する際に活用できます。
Type: AWS::ImageBuilder::ImagePipeline Properties: Description: String DistributionConfigurationArn: String EnhancedImageMetadataEnabled: Boolean ImageRecipeArn: String ImageTestsConfiguration: ImageTestsConfiguration InfrastructureConfigurationArn: String Name: String Schedule: Schedule Status: String Tags: Key : Value
EC2 Image Builderの利点・欠点
EC2 Image Builderを実際に利用し、そこから得られた利点と欠点を紹介します。
利点
カスタムAMIの手動運用が不要になる
カスタムAMIの手動運用が不要になったのは喜ばしい効果です。
特に頻繁にバージョンアップが必要なケースでは有益です。Redashもそうですが、バージョンアップ関連の処理に利用範囲を拡げていきたいです。
リソースをコードで管理できる
カスタムAMIなどリソースがコード管理されてないと属人的な運用になってしまうので、CloudFormationを使いコードで管理できるのもメリットです。Terraformでもサポートされています。
EC2インスタンスの起動時間を短縮できる
事前に必要なモジュールがインストール済みのAMIを使えるので、EC2インスタンスの起動が早くなります。
EC2インスタンスのユーザーデータでインストールするにはモジュールが多すぎる場合に有効活用できます。例えばEC2インスタンスで稼働させているDigdagのワーカーなどにも活用できます。
欠点
エラーログの調査が大変
エラーログを調査する際に、SSMのエラーログだけでは具体的にどこで落ちたのかわかりにくいです。
原因を特定するためにはインフラストラクチャで「TerminateInstanceOnFailure」を「false」に設定し、EC2インスタンス内からログの調査を実施する必要があります。
AMI生成までの時間が長い
上述の通り、ビルドからデプロイまで長い場合だと60分ほど時間がかかります。これは、再掲の内容ですが、ビルド用のインスタンスが起動、終了した後にテスト用のインスタンスが起動するためです。
合わせて欠点の1つ目に記載したエラーログ調査の難解さもあり、必然的に開発ライフサイクルが長くなります。
EC2 Image Builderが担う範囲の検討
前章の利点・欠点であげたように、RedashのカスタムAMIの手動運用が不要になり、運用課題を解決できました。
一方で、エラーログの調査方法とAMI生成までの時間に関しては懸念が残ります。失敗時のログの調査とAMI生成までの時間を考慮すると、リソースを変更する度にEC2 Image Builderの更新が必要になる運用は避けたいです。
そのため、EC2 Image Builderではライブラリのインストールのみ実施することにしました。環境設定ファイルやdocker-compose.ymlの生成、ビルドはEC2インスタンスのユーザーデータで行っています。
今後、Redashのバージョンアップを自動化する際には、EC2 Image Builderで動的に生成すべきですが、現時点の運用ではこのような役割分担にしました。
UserData: Fn::Base64: !Sub | #!/bin/bash -e RedashUsername=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/RDS/User | jq -r .SecretString) RedashPassword=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/RDS/Password | jq -r .SecretString) cat <<EOF > /opt/redash/env PYTHONUNBUFFERED=0 REDASH_LOG_LEVEL=INFO POSTGRES_PASSWORD=${!RedashPassword} REDASH_COOKIE_SECRET=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/CookieSecret | jq -r .SecretString) REDASH_SECRET_KEY=$(aws secretsmanager get-secret-value --region ${AWS::Region} --secret-id Redash/SecretKey | jq -r .SecretString) REDASH_REDIS_URL=redis://${ElasticacheClusterForRedash.RedisEndpoint.Address}:${ElasticacheClusterForRedash.RedisEndpoint.Port}/0 REDASH_DATABASE_URL=postgresql://${!RedashUsername}:${!RedashPassword}@${RDSDBClusterForRedash.Endpoint.Address}:${RDSDBClusterForRedash.Endpoint.Port}/${!RedashUsername} REDASH_FEATURE_EXTENDED_ALERT_OPTIONS=true EOF cat <<EOF > /opt/redash/docker-compose.yml version: "2" x-redash-service: &redash-service image: redash/redash:8.0.0.b32245 env_file: /opt/redash/env restart: always services: server: <<: *redash-service command: server ports: - "5000:5000" environment: REDASH_WEB_WORKERS: 4 scheduler: <<: *redash-service command: scheduler environment: QUEUES: "celery" WORKERS_COUNT: 1 logging: driver: awslogs options: awslogs-region: ap-northeast-1 awslogs-group: redash_scheduler_logs awslogs-stream: redash_scheduler scheduled_worker: <<: *redash-service command: worker environment: QUEUES: "scheduled_queries,schemas" WORKERS_COUNT: 1 adhoc_worker: <<: *redash-service command: worker environment: QUEUES: "queries" WORKERS_COUNT: 2 nginx: image: redash/nginx:latest ports: - "80:80" depends_on: - server links: - server:redash restart: always EOF sudo docker-compose -f /opt/redash/docker-compose.yml up -d --build
まとめ
Redashは監視の役割も担っていたので、可用性の低い状態で他のサービスの監視をすることに不安がありました。その不安を払拭するためにも、EC2 Image Builderを導入したことで初回起動時に発生していたRedashの運用課題を解決でき、監視ツールとして可用性を高められました。
また、分析ツールとしてもクエリの実行数に応じてオートスケールが可能になりました。加えて、CloudFormationを使いコードとして管理できるので、カスタムAMIの運用負荷だけではなく属人的な運用を防ぐ意味でも役立ちそうです。
実際に試してみることでEC2 Image Builderの仕様も理解できました。同時に運用まで経験することでデバッグのやりにくさや、ビルドからデプロイまでの所要時間の課題感にも気づけました。
そこから、バージョンアップなど定期的に更新が必要な場合に相性が良い仕組みだという知見も得られました。
さいごに
ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください!