インフラに詳しくないエンジニアがGKE上でRedash環境を構築した話
はじめに
こんにちは!オールアバウトのデータエンジニア@ondaljhです。
データエンジニアをやっているものの恥ずかしいながらインフラにはそこまで詳しくない自分が、GKE上でRedash環境を構築したのでその経緯とやったことを紹介します。この記事を通して、GKEの概要とRedashをGKEで構築する1つの知見が皆様の役に立てば幸いです。
従いまして、この記事は下記のような方を想定して書かせていただきます。
- GKEに興味はあるがインフラには詳しくないエンジニア
- GKE上でRedash環境を構築してみたいエンジニア
※この後はRedashの導入背景やdocker-composeを使ってのローカル環境構築の話をさせていただきます。ともかくGKE上でのRedash構築の話をお読みたい方は「GKEへのチャンレジ」へ進んでいただければと思います。
なぜRedashなのか
まず、なぜRedashを導入することになったのかの話を少しだけさせていただきます。
新しい軸のデータ要望
オールアバウトはPrimeAdというコンテンツマーケティングプラットフォームサービスを提供しています。PrimeAdの計測データはすでにAllAboutReportingという、自社制作の広告集計計測サービスで確認できるようになっています。
ある日、ある部署からAllAboutReportingではまだ提供していない軸でのデータが欲しいとの要望がありました。普段ならアドホック的なデータ抽出は順次対応していましたが、今回の要望はAllAboutReportingのように、日毎のデータが常に確認できてほしいとのことでした。話をシンプルにするために、この時点での状況をまとめると下記のようになります。
- 新しい軸でのデータ要望があった。
- 日毎のデータが常に確認できる必要がある。
- この軸のデータをAllAboutReportingへ追加する前にプロトタイプとして試してみた方が良いと判断した。
この状況を踏まえて、新しい軸のデータはBIツールにて提供することにしました。
BIツール選定
BIツールといっても、世の中色んな種類のBIツールがあり、どれを使えばいいのかは皆さんにとっても悩みどころだと思います。
幸い、チーム内ではすでにGoogleのデータポータルも使っていたので、今回もデータポータルで良いのでは?と思いましたが、データポータルには下記の問題がありました。
データポータルのデータソースとしてMySQLを利用する場合、データポータルのドロップダウンリストで利用するカラムの値が日本語である場合、最大20個しか使えず、それ以上の項目は「その他」として丸められる。
※ここでのMySQLはCloudSQLではなく、GCE等で自前で構築したMySQLになります。弊社の集計データとCloudSQLは相性悪く、やむを得ずGCEでMySQLをインストールして使っている状況です。
今回の要望としてはドロップダウンリストは必須で、そこで使うデータは日本語で数百~数千件に至ることはわかっていたので、今回、データポータルは除外せざるを得ないことになりました。
その故、無料で使えるBIツールを改めて調べてみることにしました。その結果上がってきた候補は下記3つで、それぞれのメリットデメリットはあうものの大差はなかったので、前から使ってみたかったRedashを試してみることになりましたー(;・∀・)
Redashのアーキテクチャー
自分が認識しているRedashのアーキテクチャーは上記の図のようなものです。
これをGKE上にどうやって構築するか、を試す前に、自分のローカル環境でとりあえず構築してみることにしました。
ローカル環境での試し
Redashもdocker-composeもあんまり知らなかったので、とりあえずローカル環境での構築は下記サイトを参考にしました。
[ データ可視化ツール]RedashをDocker上で構築してRedshiftへ接続する
上記サイトにも手順がありますが、ここからは実際に自分が行った際の手順になりますので、ご参考になれば幸いです。
- Redashリポジトリをクローンする
- プロダクション用docker-compose.ymlを取得する
- redash.envファイルを作成する
- docker-composeを使ってアプリケーション全体を起動する
- localhost:8080へアクセスする
Redashリポジトリをクローンする
git clone https://github.com/getredash/redash.git
クローンしてくるといくつかのディレクトリも生成されていることが確認できます。すべてのディレクトリ構成はリポジトリ元を参考していただくこととして、以降の説明に出てくるディレクトリ構成だけ簡単に載せますと下記のようになります。
/redash # 作業ディレクトリルート /redash/setup # 設定ファイル等が格納されてる
また、ここで取得したdocker-compose.ymlファイルは開発専用のものであるため、プロダクション用を取得する必要があります。
プロダクション用docker-compose.ymlを取得する
get clone https://github.com/getredash/setup.git
- 「/setup/data」ディレクトリにdocker-compose.ymlファイルがあることを確認する
docker-compose.yml
version: "2" x-redash-service: &redash-service image: redash/redash:8.0.0.b32245 depends_on: - postgres - redis env_file: 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 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 redis: image: redis:5.0-alpine restart: always postgres: image: postgres:9.6-alpine env_file: redash.env volumes: - /opt/redash/postgres-data:/var/lib/postgresql/data restart: always nginx: image: redash/nginx:latest ports: - "80:80" depends_on: - server links: - server:redash restart: always
ご覧の通り、docker-compose.ymlにはredashのserverはもちろん、nginxやredis等、アーキテクチャー図に登場する複数のサービスが定義されています。
redash.envファイルを作成する
ビルド時に利用する設定情報をredash.envファイルへまとめておきます。中身は下記サイトを参考にしました。必要に応じて各情報を書き換えていただければと思います。
redash-hands-on
REDASH_HOST=http://localhost PYTHONUNBUFFERED=0 REDASH_LOG_LEVEL=INFO REDASH_REDIS_URL=redis://redis:6379/0 REDASH_DATABASE_URL=postgresql://postgres@postgres/postgres REDASH_COOKIE_SECRET=redash-hands-on REDASH_SECRET_KEY=redash-hands-on POSTGRES_PASSWORD= POSTGRES_HOST_AUTH_METHOD=trust
docker-composeを使ってアプリケーション全体を起動する
まずは下記コマンドを使ってredashで利用するデータベースを作成します。
docker-compose -f docker-compose.yml run --rm server create_db
次は、下記コマンドを使ってアプリケーションを起動します。
docker-compose -f docker-compose.yml up -d
エラーメッセージなどが出なければとりあえず環境構築はできたことになります。
localhost:8080へアクセスする
自分はredash.envファイルでREDASH_HOSTを「http://localhost」にしたので、ブラウザを開いて「http://localhost:8080」へアクセスを試しました。
できた!!ブラウザ上に上記のような初期設定画面が表示されたら、ローカル環境でRedashが構築できたことになります。
GKEへのチャレンジ
ローカル環境ではdocker-composeを使って簡単にredash環境構築ができました。が、問題はこれからです。ご存じの方も多いと思いますが、docker-composeコマンドはあくまでもdockerコマンドであり、gcloudやkubectlのようなGCP向けのコマンドではないです。つまり、ローカル環境を構築した手順のままではGKEへの構築ができない!ということになります。ローカル環境構築はredashのアーキテクチャーを理解する勉強になったので、ここからはGKE上でredashの各サービスを起動するために行ったことを紹介させていただきますー
インフラ構成
まずは詳細説明に入る前に、ローカル環境とGCP上のアーキテクチャーを比較することで全体図を把握しましょうー
ローカル環境のマニフェスト(docker-compose.yml)を見るとわかるように、Redashを構築するには複数のインフラサービスが必要になります。ここでは、ローカル環境で登場するサービスがGCP上ではどう変わるのかを表でまとめてみました。
ローカル環境 | GCP |
---|---|
(redash)server | GKEのDeployment redash-server |
(redash)scheduler scheduled_worker adhoc_worker |
GKEのDeployment redash-worker |
nginx | GKEのDeployment redash-nginx |
redis | Memorystore for redis |
postgres | CloudSQL for PostgreSQL |
上記表のように、redashのserverとworker群、そしてnginxはすべてGKEのDeploymentとして構成することにしました。redisとpostgresはGKEのDeploymentではなく、GCP上のそれぞれの専用サービスを利用する構成にしました。また、ローカル環境では不要でしたが、本番運用ではメール送信も必要になりますので、redashからのメール送信はSendGridを利用するようにしました。ここまでの構成を図でまとめますと、下記のような図になります。
上記図には記載していませんが、KubernetesのPodへのアクセスにはServiceが必要なので、svc-redash-nginxとsvc-redash-serverを構築しています。Serviceを構築している理由としては、podのIPはエフェメラルでpodを構築する度にIPが変わります。そのため、Serviceを構築することでIPが変わっても指定したpodにアクセスができるようになります。詳しくは公式サイトに説明があるので、ご参考ください。
nginxのカスタマイズ
docker-compose用のnginxは「redash/nginx:latest」イメージを使っていて、その中の設定ファイル(default.conf)は下記のようになっています。
変更前
upstream redash { server redash:5000; } server { listen 80 default; gzip on; gzip_types *; gzip_proxied any; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_pass http://redash; } }
upstream項目で指定している「server redash:5000;」が、nginxサーバーへアクセスが発生したらredashサーバーの5000ポートへつないでくれる設定になります。GKE上でnginxを構成する際には、この値をredash-serverのServiceに変更する必要があります。また、後述しますが、ステージング環境と本番環境で同じnginxイメージを使う想定なので、default.confのserver値を変数化し、ステージング環境と本番環境のそれぞれのマニフェストから値を設定できるようにしました。カスタマイズした設定ファイルは下記のようになります。
変更後
upstream redash { server ${UPSTREAM_SERVER}:${UPSTREAM_PORT}; } server { listen 80 default; gzip on; gzip_types *; gzip_proxied any; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; proxy_pass http://redash; } }
ちなみに、カスタマイズした設定ファイルは、変数の置換が必要になるためファイル名も「default.conf.template」にし、実際のデプロイの際に変数が置換された状態の「default.conf」ファイルを生成するようにしています。こちらの内容は後述するマニフェストにて紹介させていただきます。
このようにカスタマイズしたnginxイメージはGCR(Google Container Registry)のある場所へ格納することでマニフェストから参照できるようにしました。
マニフェスト
前述したとおり、GKEのDeploymentへのアクセスはServiceを利用します。そして、DeploymentもServiceもGKEのクラスタに属されています。今回のGKE構成を表で表すと下記のようになります。
GKE要素 | サービス名 |
---|---|
Cluster | redash-cluster |
Deployment | redash-server redash-worker redash-nginx |
Service | svc-redash-server svc-redash-nginx |
redash-workerの場合、内部でceleryプロセスが動いていて、redisとPostgreSQLと経路を書く方できればredash-serverやそれ以外との経路は不要なので、専用のServiceを持つ必要がないです。従ってServiceは2つだけになります。また、redashで利用するためのMemorystore for redisとCloudSQL for PostgreSQLはそれぞれのサービスで新規作成するだけで簡単に構築できました。これらの構成を行うために5つのマニフェストファイルを作成したので、それぞれ記載させていただきます。 ※弊社ではCircleCIを使ってのデプロイを行っているため、マニフェストファイルの中にプロジェクト毎のCircleCI環境変数を使っています。
deployment-redash-server
apiVersion: apps/v1 kind: Deployment metadata: name: redash-server labels: app: redash-server spec: selector: matchLabels: app: redash-server template: metadata: labels: app: redash-server spec: containers: - name: redash-server-container image: redash/redash:8.0.0.b32245 command: - /bin/sh - -c - /app/bin/docker-entrypoint create_db; /app/bin/docker-entrypoint server resources: limits: memory: "1Gi" requests: memory: "512Mi" env: # redashで利用する環境変数 - name: DB_USER value: ${DB_USER} - name: DB_PASS value: ${DB_PASS} - name: DB_NAME value: ${DB_NAME} - name: TZ value: "Asia/Tokyo" - name: PYTHONUNBUFFERED value: "0" - name: REDASH_LOG_LEVEL value: "INFO" - name: REDASH_WEB_WORKERS value: "4" - name: REDASH_REDIS_URL value: "redis://${REDIS_IP}:6379/0" # ここで設定した環境変数を利用するため{}ではなく()となる - name: REDASH_DATABASE_URL value: "postgresql://$(DB_USER):$(DB_PASS)@127.0.0.1/$(DB_NAME)" - name: REDASH_HOST value: "https://${REDASH_HOST}" - name: REDASH_MAIL_SERVER value: "${REDASH_MAIL_SERVER}" - name: REDASH_MAIL_PORT value: "${REDASH_MAIL_PORT}" - name: REDASH_MAIL_USERNAME value: "${REDASH_MAIL_USERNAME}" - name: REDASH_MAIL_PASSWORD value: "${REDASH_MAIL_PASSWORD}" - name: REDASH_MAIL_DEFAULT_SENDER value: "redash@example.com" ports: # このポート番号でsvc-redash-serverとつながる - containerPort: 5000 # CloudSQL for PostgreSQLとつなげるためのcloud-sql-proxy設定 - name: redash-cloud-sql-proxy image: gcr.io/cloudsql-docker/gce-proxy:1.17 command: ["/cloud_sql_proxy", "--dir=/cloudsql", "-instances=${GCP_PROJECT_ID}:${CLOUDSQL_REGION}:db-${CLOUDSQL_INSTANCE}=tcp:5432"] volumeMounts: - name: ssl-certs mountPath: /etc/ssl/certs volumes: - name: ssl-certs hostPath: path: /etc/ssl/certs
deployment-redash-worker
apiVersion: apps/v1 kind: Deployment metadata: name: redash-worker labels: app: redash-worker spec: selector: matchLabels: app: redash-worker template: metadata: labels: app: redash-worker spec: containers: - name: redash-worker-container image: redash/redash:8.0.0.b32245 resources: limits: memory: "1Gi" requests: memory: "512Mi" env: # redashで利用する環境変数 - name: DB_USER value: ${DB_USER} - name: DB_PASS value: ${DB_PASS} - name: DB_NAME value: ${DB_NAME} - name: TZ value: "Asia/Tokyo" - name: PYTHONUNBUFFERED value: "0" - name: REDASH_LOG_LEVEL value: "INFO" - name: REDASH_REDIS_URL value: "redis://${REDIS_IP}:6379/0" # ここで設定した環境変数を利用するため{}ではなく()となる - name: REDASH_DATABASE_URL value: "postgresql://$(DB_USER):$(DB_PASS)@127.0.0.1/$(DB_NAME)" - name: WORKERS_COUNT value: "2" - name: QUEUES value: "queries,scheduled_queries,celery" - name: REDASH_HOST value: "https://${REDASH_HOST}" - name: REDASH_MAIL_SERVER value: "${REDASH_MAIL_SERVER}" - name: REDASH_MAIL_PORT value: "${REDASH_MAIL_PORT}" - name: REDASH_MAIL_USERNAME value: "${REDASH_MAIL_USERNAME}" - name: REDASH_MAIL_PASSWORD value: "${REDASH_MAIL_PASSWORD}" - name: REDASH_MAIL_DEFAULT_SENDER value: "redash@example.com" args: ["scheduler"] # CloudSQL for PostgreSQLとつなげるためのcloud-sql-proxy設定 - name: stg-dmg-redash-cloud-sql-proxy image: gcr.io/cloudsql-docker/gce-proxy:1.17 command: ["/cloud_sql_proxy", "--dir=/cloudsql", "-instances=${GCP_PROJECT_ID}:${CLOUDSQL_REGION}:db-stg-${CLOUDSQL_INSTANCE}=tcp:5432"] volumeMounts: - name: ssl-certs mountPath: /etc/ssl/certs volumes: - name: ssl-certs hostPath: path: /etc/ssl/certs
deployment-redash-nginx
apiVersion: apps/v1 kind: Deployment metadata: name: redash-nginx labels: app: redash-nginx spec: replicas: 1 selector: matchLabels: app: redash-nginx template: metadata: labels: app: redash-nginx spec: containers: - name: redash-nginx-container # カスタマイズ済みのnginxイメージを格納しているGCR情報 image: ${GCR_PATH}/${IMAGE_NGINX_NAME}:${IMAGE_NGINX_TAG} resources: limits: memory: "256Mi" requests: memory: "256Mi" env: # nginx設定ファイルで利用する変数設定 - name: UPSTREAM_SERVER value: "svc-${REDASH_SERVER}" - name: UPSTREAM_PORT value: "5000" # envsubstコマンドを使って変数を置換する command: ["/bin/sh", "-c", "envsubst '$$${ENV_UPSTREAM_SERVER}$$${ENV_UPSTREAM_PORT}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && /usr/sbin/nginx -g 'daemon off;'"] ports: - containerPort: 80
カスタマイズ済みのnginxイメージを利用していますが、設定ファイルで利用する変数設定でちょっと苦労したので少しだけ補足させていただきます。
default.conf.templateファイルでは${UPSTREAM_SERVER}と${UPSTREAM_PORT}を記載しており、envsubstコマンドを使って該当変数をマニフェストの値に置換し、その結果をdefault.confファイルへ書き出すのがゴールになります。普通のenvsubstコマンドで書くと下記のような感じになります。
envsubst '$$UPSTREAM_SERVER$$UPSTREAM_PORT' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
※envsubstコマンドについては、Docker上のNginxのconfに環境変数(env)を渡すたったひとつの全く優れてない方法(修正:+優れている方法)を参照にしました。
envsubstコマンドも不慣れだったものの、マニフェストファイルで書いてkubectlコマンドで実行するだけならシンプルですが、CircleCI経由になると余計に変数設定-置換が怪しくなり、いくつかの試行錯誤でやっと上記マニフェストのような書き方にたどり着きました(後から見ると当たり前のように見えるものが、何かにはまっちゃうと見えなくなりますよね;;;)。ここで、恥ずかしながら試行錯誤の内容も書かせていただきます。
envsubst記載の試行錯誤
ゴール : envsubst '$$UPSTREAM_SERVER$$UPSTREAM_PORT'
kubectlコマンド用マニフェスト : envsubst '$$UPSTREAM_SERVER$$UPSTREAM_PORT'
CircleCI用マニフェストの試行錯誤
※Inputはマニフェストへの記載内容、OutputはCircleCIデプロイ時の置換後の内容になります。
Case# | Input | Output | 備考 |
---|---|---|---|
1 | envsubst '$$UPSTREAM_SERVER$$UPSTREAM_PORT' | envsubst '$$' | |
2 | envsubst '${$UPSTREAM_SERVER}${$UPSTREAM_PORT}' | envsubst '${}${}' | |
3 | envsubst '$UPSTREAM_SERVER$UPSTREAM_PORT' | envsubst '' | |
4 | Input : envsubst '\$\$UPSTREAM_SERVER\$\$UPSTREAM_PORT' | found unknown escape character | |
5 | envsubst '$$$UPSTREAM_SERVER$$$UPSTREAM_PORT' | envsubst '$$$$' | |
6 | envsubst $$UPSTREAM_SERVER$$UPSTREAM_PORT |
envsubst $$ |
|
7 | envsubst '$${ENV_UPSTREAM_SERVER}$${ENV_UPSTREAM_PORT} | found unknown escape character | CircleCIの環境変数利用 ENV_UPSTREAM_SERVER : \$UPSTREAM_SERVER ENV_UPSTREAM_PORT : \$UPSTREAM_PORT |
8 | envsubst '$$${ENV_UPSTREAM_SERVER}$$${ENV_UPSTREAM_PORT} | envsubst '$$UPSTREAM_SERVER$$UPSTREAM_PORT' | CircleCIの環境変数利用 ENV_UPSTREAM_SERVER : UPSTREAM_SERVER ENV_UPSTREAM_PORT : UPSTREAM_PORT |
svc-redash-server
apiVersion: v1 kind: Service metadata: name: svc-redash-server labels: app: svc-redash-server spec: type: ClusterIP ports: - port: 5000 targetPort: 5000 protocol: TCP selector: app: redash-server
svc-redash-nginx
apiVersion: v1 kind: Service metadata: name: svc-redash-nginx labels: app: svc-redash-nginx spec: type: NodePort ports: - port: 80 # このnodePort番号にてロードバランサとつながる nodePort: 30419 selector: app: redash-nginx
kubectlコマンド
マニフェストも何回かの試行錯誤の結果、完成できました。試行錯誤の際に役に立ったいくつかのkubectlコマンドを簡単に紹介させていただきます。
podのリストを取得する
kubectl get pods
podの詳細情報を確認する
kubectl decribe pods POD-NAME
podのログを確認する
kubectl logs POD-NAME kubectl logs POD-NAME -c CONTAINER-NAME # 1つのpodに複数のコンテナがある場合
deployment作成に失敗したり、エラーが発生したり等の場合には上記コマンドでどのような状況・エラーなのかの確認ができるので結構役に立ちました。
podに対してポートフォワードする
kubectl port-forward pod/POD_NAME LOCALHOSTのPORTNUMBER:PODのPORTNUMBER
上記コマンド実行後、ブラウザに「localhost:LOCALHOSTのPORTNUMBER」と書くと、ブラウザから該当podへアクセスすることが可能です。
Serviceに対してポートフォワードする
kubectl port-forward svc/SERVICE_NAME LOCALHOSTのPORTNUMBER:SERVICEのPORTNUMBER
podにブラウザからアクセスできることと同じく、Serviceにもブラウザからアクセスすることができます。これで、podへのアクセスに問題があるかどうか、Serviceへのアクセスに問題があるかどうかの確認ができるので便利ですねー
CircleCI設定
前述の通り、弊社ではCircleCIを使ってデプロイを行っています。今回作成したマニフェストを使ってデプロイするCiecleCI設定内容の一部を記載させていただきます。
version: 2.1 commands: deploy_to_kubernetes: steps: - run: name: Deploy to Kubernetes command: | envsubst < yaml_files/${YAML_FILES_DIRECTORY}/deployment-${ENVIRONMENT}-${REDASH_NGINX}.yaml > ./patched_deployment_nginx.yaml envsubst < yaml_files/${YAML_FILES_DIRECTORY}/deployment-${ENVIRONMENT}-${REDASH_SERVER}.yaml > ./patched_deployment_server.yaml envsubst < yaml_files/${YAML_FILES_DIRECTORY}/deployment-${ENVIRONMENT}-${REDASH_WORKER}.yaml > ./patched_deployment_worker.yaml envsubst < yaml_files/${YAML_FILES_DIRECTORY}/svc-${ENVIRONMENT}-${REDASH_NGINX}.yaml > ./patched_svc_nginx.yaml envsubst < yaml_files/${YAML_FILES_DIRECTORY}/svc-${ENVIRONMENT}-${REDASH_SERVER}.yaml > ./patched_svc_server.yaml kubectl apply -f ./patched_deployment_nginx.yaml kubectl apply -f ./patched_deployment_server.yaml kubectl apply -f ./patched_deployment_worker.yaml kubectl apply -f ./patched_svc_nginx.yaml kubectl apply -f ./patched_svc_server.yaml kubectl rollout status deployment ${ENVIRONMENT}-${REDASH_NGINX} kubectl rollout status deployment ${ENVIRONMENT}-${REDASH_SERVER} kubectl rollout status deployment ${ENVIRONMENT}-${REDASH_WORKER}
後はCircleCIを起動して無事デプロイできることも確認でき、実際にRedashも起動していることを確認できました。
おわりに
ここまでがGKE上にRedashを構築するためにやったことになります。インフラにはそこまで詳しくない自分がredashのアーキテクチャーを理解し、試行錯誤しながらマニフェストを作成してGKE構成までできたので、今回の経験を活かしていきたいと思います。
余談ですが、今回構築した環境でRedashのダッシュボードを作成し、要望があった部署へ提供することで実際に使ってもらっていて嬉しいですー
長文になりましたが少しでもGKEの構成を理解いただき、GKE上でRedashを構築してみようと思う方へ役に立てればと思います。
閲覧ありがとうございました!今後ともよろしくお願いいたします。