オールアバウトTech Blog

株式会社オールアバウトのエンジニアブログです。

インフラに詳しくないエンジニアが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
  • Superset
  • Metabase
    ※それぞれのBIツールのメリットデメリット等は比較記事がたくさんあるので、この記事では割愛させていただきます。

Redashのアーキテクチャ

f:id:allabout-techblog:20201015132315p:plain
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」へアクセスを試しました。

f:id:allabout-techblog:20201007122419p:plain
redash初期設定画面

できた!!ブラウザ上に上記のような初期設定画面が表示されたら、ローカル環境で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を利用するようにしました。ここまでの構成を図でまとめますと、下記のような図になります。

f:id:allabout-techblog:20201015132406p:plain
Redash on GKEアーキテクチャ

上記図には記載していませんが、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を構築してみようと思う方へ役に立てればと思います。

閲覧ありがとうございました!今後ともよろしくお願いいたします。

コロナ禍での情シスの対応とそこから見えたもの

はじめに

こんにちは!オールアバウトナビ、システム部の@k_shiotaです。

オールアバウトには業務全体に関わる部署、情報システムG(情シス)が存在します。
その情シスの皆さんに、「コロナの対応」について、社内で行っているLT会にて発表いただきました!

今回は情シスでのコロナ対応について発表をしてもらいましたので、このブログで紹介したいと思います。

目次

f:id:allabout-techblog:20200916155912p:plain
アジェンダ

ドタバタなコロナ対策

オールアバウト では早い段階でコロナへの対応を行っており2/27に全社広報があり、2/28から在宅勤務を行いましたが、
情シスはそれよりも前からすでにコロナ対策を始めていました。

コロナ対策準備は下記のことを想定して動いておりました。

f:id:allabout-techblog:20200916155947p:plain
ドタバタなコロナ対策_1

その中で在宅勤務が始まる前に下記のことをやっておきました。

f:id:allabout-techblog:20200916160036p:plain
ドタバタなコロナ対策_2

そして3/4より情シスも原則在宅勤務となりました。

情シス業務の変化

基本在宅勤務になって情シスの業務に変化がありました。 情シスは一緒のフロアにいたため、相談をしやすく都度対応してもらうことが多かったのですが 手順書を作成して困ることが少なくなり、困ったことはslackでオープンに相談することで、 他の方も同様な問題があった時に参考になったかと思います。

f:id:allabout-techblog:20200916160129p:plain
情シス業務の変化

slackへの投稿数は原則在宅が始まる前と後では増加しております。

f:id:allabout-techblog:20200916160211p:plain
情シス業務の変化_slackの投稿数

変化の中で見えてきたもの

固定電話が必要かどうかについて、以前からも話題に上がっていたのですが 今回の件でどうにかなるという事もわかり良い判断材料となりました。

f:id:allabout-techblog:20200916160256p:plain
変化から見えてきたもの

また、ゼロトラスト環境に移行することに待ったがない状態になりました。

f:id:allabout-techblog:20200916160319p:plain
変化から見えてきたもの_ゼロトラストセキュリティモデル

今後の計画

f:id:allabout-techblog:20200916160358p:plain
今後の計画

  • slackでの問い合わせに一つ一つ答えているため、定型化したものをボット化してその他の業務に当たれる時間をふやすこと
  • ゼロトラスト環境は社外からのアクセスが増えるため強化する必要があること
  • 固定電話は不要でスマホを全社員に配布すること など、在宅勤務環境をより良くしていくよう動いて行くとのことです。

最後に

f:id:allabout-techblog:20200925160036p:plain
最後に

会議の時間が超過する問題はどうしてもあるようでして、切実なお願いでした。 自分も今後気をつけていきたいと思います。。。

まとめ

弊社でもコロナ前から在宅勤務を導入しておりましたが、まだまだやりづらい点があったかと思います。
原則在宅勤務となり、在宅勤務の環境を整えることが急務となりました。

原則在宅勤務が始まったことで対応に追われて大変だったとのことですが、今は落ち着き更に働きやすい環境を整えているとのことです。
普段から頼っている情シスを更に信頼することになりました。

もし、こんな環境で働いてみたいと思ったらぜひ一緒に働きましょう!
以下より採用情報がありますので、募集をお待ちしております!

corp.allabout.co.jp

Google Cloud Functionsを使ってバックアップデータからログの再収集を行う方法の紹介

こんにちは!オールアバウトでデータエンジニアをしている@ondaljhです。

皆さんはGoogle Cloud Functions(以降、Cloud Functions)を利用したことがありますか?自分も業務上では使ったことがなかったんですが、今回ある案件でCloud functionsを利用する機会があったので、私たちのチームでのCloud Functionsの利用シーンを紹介させていただきたいと思います。この記事を最後まで読んでいただいて、「こんな利用シーンもあるんだ~」程度で理解していただければ幸いです。

従いまして、本記事は下記のような方を想定して書かせていただきます。

  • Google Cloud Functionsに興味はあるが深くかかわったことがない方
  • Google Cloud Functionsの利用パターンを調べてる方

また、Cloud Functions以外に出てくるGoogle Cloud群についての詳細な説明は記事の最後の方に公式サイトへのリンクを貼りますので、興味ある方はぜひ見てみてください!

概要

冒頭で書いた「ある案件」とは、チームプロジェクトで「ログ収集基盤のサーバーレス化」というものがありまして、そのうちの1つである「バックアップデータからログの再収集」を意味します。

まず、ログ収集基盤のサーバーレス化の基本的なアーキテクチャをお見せします。

f:id:allabout-techblog:20200826165138p:plain
ログ収集基盤のログ転送フロー(通常)

図の通りにウェブから発生したログはGoogle Cloud Run(以降、Cloud Run)で受け入れて最終的にはTreasure DataとGoogle BigQuery(以降、BigQuery)まで流れていきます。
また、Cloud RunからGoogle Cloud Dataflow(以降、Dataflow)までのデータ転送にはネットワーク上の通信の高速化のためにProtocol Buffersを使ってデータ通信を行います。(Protocol Buffersは通信の高速化以外にもいくつかのメリットがありますが、本題とずれるので詳細については記事の最後の方に貼ってある公式サイトへのリンクをご確認ください。)
なお、Cloud Runが受け入れたログはバックアップとしてBigQueryのあるテーブルへ格納します。その際のフォーマットは、Cloud RunからGoogle Cloud Pub/sub(以降、Pubsub)へ転送する、Protocol Buffersにてシリアライズされたフォーマットのままにしてます。
このBigQueryへ格納するバックアップデータが「ログの再収集」の肝になりまして、PubsubやDataflowに障害が発生した場合、このバックアップデータからリカバリーが可能になります。

ここからが本題で、Cloud Functionsの導入に伴う下記の2点について紹介させていただきたいと思います。
- リカバリー処理(BigQueryからデータを取得して~)
- Cloud Functionsのソース管理及びデプロイ

通常時と比べてリカバリー時のイメージは下記の図のようになります。

f:id:allabout-techblog:20200826165207p:plain
ログ収集基盤のログ転送フロー(リカバリー)

それでは、リカバリー処理から見てまいりましょう!

リカバリー処理

リカバリー処理自体はシンプルで、BigQueryへ格納されているバックアップデータを取得して、通常処理のPubsubへ投げる、だけになります。

  • BigQueryへバックアップされたデータは通常処理でPubsubが扱うデータと同じフォーマットとなっている
  • PubsubとDataflowはオートスケールできるため、通常処理にリカバリー処理をかぶせても負荷的な影響はない

実際の処理負荷に対しては気になるところだと思いますが、その詳細につきましてはここでは割愛させていただき、別の機会でご紹介できればと思います。

Cloud Functions設定情報

今回のCloud Functionsの設定情報は下記のようになります。

  • トリガー : HTTP
  • ランタイム : Go 1.13

また、内部的にはGCP関連いくつかの環境変数とあわせて、リカバリー用の環境変数も設定してソースの中で参照することにしています。

必要なIAMロール

リカバリーではサービスアカウントを使いますが、そのサービスアカウントに必要なIAMロールは下記になります。各ロールに詳しい方も多いと思いますが、補足として簡単な説明も記載させていただきます。

  • bigquery.jobs.create : プロジェクト内でジョブ(クエリを含む)を実行する権限。
  • pubsub.topics.publish : 1つ以上のメッセージをトピックへ追加する権限。
  • cloudfunctions.functions.setIamPolicy : 未承認のデベロッパーによる該当Functionsへの権限変更を防ぐために、該当Functionsをデプロイするユーザーまたはサービスに与える権限。

ファイル構成

  • functions.go : メイン処理
  • go.mod : go言語の依存モジュール管理ファイル

処理の流れ

基本的な処理の流れは下記のようになります。

1.トリガーでCloud Functionsの実行対象関数が呼び出される
2.BigQueryからデータを取得し、Iteratorを回してPubsubへメッセージを送る関数へデータを渡す
3.Pubsubへメッセージを送る

ソース

バックアップデータからログの再収集を行うソースを全部載せると記事が長くなりますので、流れが把握できる範囲で紹介させていただきます(エラー処理もすべて省きます)。

functions.go

package p

import (
    ~省略~
    "cloud.google.com/go/bigquery"
    "cloud.google.com/go/pubsub"
    "google.golang.org/api/iterator"
)

// ①Cloud Functionsでエンドポイントとして使われる関数
func EndPoint(w http.ResponseWriter, r *http.Request) {
    // BigQueryからデータを取得し、1行ずつPubsubへメッセージを送る
    fetchBigQueryData()
}

// ②BigQueryからデータを取得し、Iteratorを回してPubsubへメッセージを送る関数へデータを渡す
func fetchBigQueryData() {
    ctx := context.Background()

    client, err := bigquery.NewClient(ctx, {GCP_ID})
    defer client.Close()

    // バックアップデータ取得用クエリ設定
    q := client.Query({SQL})

    // 実行のためのqueryをサービスに送信してIteratorを通じて結果を返す
    // itはIterator
    it, err := q.Read(ctx)

    for {
        var tc {BQTableColumns 構造体}

        // 引数に与えたtcにnextを格納する
        // Iteratorを返す
        // これ以上結果が存在しない場合には、iterator.Doneを返す
        // iterator.Doneが返ってきたら、forを抜ける
        err := it.Next(&tc)
        if err == iterator.Done {
            break
        }

        // pubsubへメッセージを送る
        sendPubsubMessage(&tc)
    }
}

// ③pubsubへメッセージを送る
func sendPubsubMessage(tc *{BQTableColumns 構造体}) {
    msg := &pubsub.Message{
        Data: tc.Message,
    }

    ctx := context.Background()
    if _, err := {Pubsubのtopicインスタンス}.Publish(ctx, msg).Get(ctx); err != nil {
        return
    }
}

go.mod

module DOMAIN/MODULE_NAME

go 1.13

require (
    cloud.google.com/go/bigquery v1.10.0
    cloud.google.com/go/pubsub v1.6.1
    google.golang.org/api v0.30.0
)

Cloud Functionsのソース管理及びデプロイ

Cloud Functionsはサーバーリソースを各自が管理しなくても良いメリットがあります。
その代わりにというのも不適切かもしれませんが、CloudFunctions上で動かせるソースの管理をどうするか、また、デプロイはどうするかを検討しないといけません。
自分にとっては初のCloudFunctionsだったので、一旦検証時に利用したソース管理とデプロイ手順を紹介させていただきます。

Cloud Functionsへのソース反映方法

Cloud Functionsへのソース反映はCloud FunctionsのウェブUIからも確認できるように、下記の4種類があります。

  • Inline Editor
  • ZIP Upload
  • ZIP from Cloud Storage
  • Cloud Source repository

Inline Editorの場合はウェブUIから直接ファイルを更新するパターンなので、ソース管理ができない問題があります。
ZIPを利用する方法も、ソース管理ツールからファイルをダウンロードして、ZIP圧縮して、等の遠回りをすればある程度のソース管理はできますが、手間が増えるし、ミスも増える可能性があります。
Cloud Source repositoryはGCP上でのソース管理サービスです。このサービスにはなんと!他のソース管理ツールへのミラーリング機能があったので、今回はCloud Source repositoryを使ってみることにしました。
※現時点でミラーリングできるのは「GitHub」と「Bitbucket」の2種類のみですので他のソース管理ツールをご利用中の場合にはご注意ください。

ミラーリング設定

弊社ではBitbucketを使ってソース管理を行っているので、下記公式サイトの説明に従ってリポジトリミラーリング設定を行いました。
Bitbucket リポジトリのミラーリング

デプロイ

まずは、Bitbucketの該当リポジトリへ今回のソースをcommitしました。
そのあと、Cloud Source repositoryの該当リポジトリミラーリングが正常にできたことを確認したので、gcloudコマンドを使ってCloudFunctionsへデプロイを試してみました。

gcloudコマンドを使ってのデプロイは下記公式ドキュメントに詳細が載っているので参考しながらコマンドを書きました。

ソース コントロールからのデプロイ

gcloud functions deploy CLOUD_FUNCTION_NAME \
  --region REGION_ID \
  --source https://source.developers.google.com/projects/PROJECT_ID/repos/REPOSITORY_ID/moveable-aliases/master/paths/SOURCE \
  --runtime go113 \
  --service-account=SERVICE_ACCOUNT@appspot.gserviceaccount.com \
  --trigger-http \
  --set-env-vars HOGEHOHE=AAA,FUGAFUGA=BBB… \
  --entry_point ENTRY_POINT \

基本的には公式ドキュメントに従って書けば問題なくデプロイできることを確認しました。
また、「--runtime」オプションは該当Functionを最初に作るときに指定すれば良くて、そのあとからは該当オプションをしないことで同じFunctionへの更新になります。

おわりに

Cloud Runと比べてCloud Functionsはコンテナイメージのメンテナンスが不要というメリットがあります。
通常のログ収集処理はCloud Runで行うことにしましたが、めったに発生しないであろうログの再収集処理はCloud Functionsで行うことで、メンテナンスコストが節約できると思います。
また、今回は「HTTPトリガー」を試してみましたが、Cloud Functionsには「HTTPトリガー」以外にも「Cloud Pub/Subトリガー」や「Cloud Storageトリガー」もあるので、今後それらも試してみたいと思います。
デプロイについても、今回はgcloudコマンドでの手動デプロイを試してみましたが、近いうちにCircleCIからの完全自動デプロイも試してみたいと思ってます。

今後ともよろしくお願いいたします。

参考サイト

公式サイト

ブログ等

GoでBigQueryクライアントを実装してBigQueryからデータを取得する

Cloud RunからCloud SQLやMemorystoreへの繋ぎ方

こんにちは!@y_hideshi です。

今回はCloud RunからCloud SQLやMemorystoreにつなぐ方法について紹介します。

  • Cloud SQLへの接続方法
  • Memorystoreへの接続方法

Cloud SQLへの接続方法

検証のため、phpMyAdminからCloud SQLにつなぐシンプルな構成のシステムを作ります。

f:id:allabout-techblog:20200629200338p:plain
Cloud Run - Cloud SQL

続きを読む

データ活用を推進しようとしているお話

はじめに

初めまして、オールアバウトの @akidukin(@Akidukin14) です。 この度上手く出来たら noteで公開しようと思っていた内容を 「自社のtechblogで書いてよー」と低めに脅された依頼されたので、気持ちよく執筆させていただくことにしました。

初めてTechBlogを執筆させていただきますので、簡単に私がどんな業務を担当しているのかを説明させて下さい。

興味が無い人は ## 何をしようとしているのかの説明 に飛んでください。(データサイエンティストとして仕事している人なんだなーって認識を持っていただけたら十分です。)

: @akidukin14 https://twitter.com/Akidukin14
出身 : 福岡
現所属 : 株式会社オールアバウト 開発部 マーケティング開発 データサイエンティスト

主な業務 :
【ちょっと前まで】

  • 広告配信に利用している機械学習の改善
  • 営業向け 資料の拡充
  • 広告配信のレポーティングの拡充 ... 等

【現在】

  • 株式会社オールアバウト全体にデータ活用やっていきましょうよー と話しまわっている(非公認)
  • 出てきた活用案をプロジェクト化して
    • PMしたり
    • 分析で実際に手を動かしたり
    • ダッシュボード作ってみたり
    • 機械学習のPoC回してみたり
    • 一般で利用できるようにツール化したり ... 等

今回は 【現在】主に対応している物についてご紹介しようと思います。

何をしようとしているのかの説明

タイトルの通り「株式会社オールアバウトの各部署のデータ活用を推進しよう」としています。

きっとこの記事を読んで頂いている方は
「会社としてデータ活用を推進していく共通意思があって進めているんだ...!!」
と思われるかもしれませんが、凄く雑に言うと 個人的に 暇だから やってみたいので進めています。

オールアバウトグループという単位でどんなサービスを運営しているかを簡単に説明します。

  • 株式会社オールアバウト : メディア運営 / タイアップ広告配信の運営
  • 株式会社オールアバウトライフマーケティング : ECサイト運営
  • 株式会社オールアバウトナビ : SNSを利用したエンタメ事業の運営
  • 日テレライフマーケティング株式会社 : ECとTV放送を連動させた事業の運営
  • 株式会社オールアバウトライフワークス : 講師育成や教室運営サービスの運営

様々なサービスの運営をしており、データアナリストやデータサイエンティストといったデータを触る方々からすると垂涎物な環境なのかなーって思います。
(違う業種違う情報 のデータが比較的豊富に取得できる点が)

今回、データ活用を推進しようとしているのは メディア運営 / タイアップ広告配信運営 の事業領域になります。

※...データ活用 : データソースの整理や掃除、から、実際にデータ分析 ~ 機能の提供 を対象のイメージとしております。

現在のデータ活用フェーズについて

そもそも、現在 株式会社オールアバウト ではデータの活用が全く進んでいないのかというと、そういうわけではありません。
メディア運営に於いてはGoogle Analyticsが社内にかなり浸透し各自でDataportal等を使っていたり、タイアップ広告配信ではTreasureDataにデータがたまっており、タイアップごとの効果測定等にもデータを利用してたりします。
ECサイトを運営しているオールアバウトライフマーケティングではRe:dashを利用して定常的に数値を追う環境もあったりします。

しかし、データの活用度合いが個人の知見に任せされていたり、情報の共有があまりされていなかったり、形骸的にデータを見るだけになっていたりしていました。
いくつかの業務の中では定量的ではなく定性的な判断をすることが常になっている業務、人の知見を元に判断をしている業務等がある。。。
というのが現在の 株式会社オールアバウト のデータ活用のフェーズになります。
イメージは伝わりますかね...??

現在のフェーズにおいて、実際に作業にあたっている方やその上司の方々は大なり小なり課題感は抱えており、今までその部分に介入する+出来る人的リソースが無かった というのが背景になります。

「あれ??これデータ活用進めたら、強力に売り上げ貢献とか利益貢献出来るんじゃね??」

f:id:allabout-techblog:20200528104258p:plain
イメージ

どう進めているのか

実際にどのようにデータ活用を推進しているのかという点について、説明させてください。
プロジェクトが動き始めたのが4月からでした。

基本的には

  1. 各グループの上長や担当者にデータを利用してやってみたい事をヒアリング
  2. データ環境の情報整理
  3. 優先順位付け、プロジェクト化
  4. 分析案や仮説のブレスト
  5. タスク化
  6. 分析作業及び報告

という流れで進めています。

元々各GMや担当者はデータに対しての理解度が高かったのですが、データを使って何が出来るか分からないという壁がありました。

そこで、テーマを「ドラえもん」に設定し、「あんなこっと良いな、出来たら良いな、不思議なデータで叶えてあっげっるー」という理想ドリブンでそれぞれやってみたい事をヒアリングしていきました。
この時、ooを解決したい!!みたいな形ではなく、ooがxxみたいになればいいのになー。という形でふんわりした内容を上げてもらって、それを詳細に詰めていくという導入から始めました。

やりたい事いっぱい出た。
中には「知見に基づいてやっている作業の自動化が出来たら凄く嬉しい。」等も出てきて狙い通りでした。

そして、出てきたものに対して、優先順位をつけ PO(プロジェクトオーナー) を付け、プロジェクト化を行いました。

その後、どういった分析をすれば実現できるのか、や、どういったデータを見れば示唆が出そうなのかのブレストを行い、タスクへ落とし込み、分析の作業に移ります。

データ活用するにあたって、壁になる事の一つに担当者と作業者間の期待値コントロールがあると思います。
正直最初は「理想ドリブンでプロジェクト立ち上げちゃったけど、期待値コントロールどうしよう...」と悩んでいました。
期待値が凄く実現性と離れた時は、ミーティング外での啓蒙活動やミーティング自体の開催頻度を上げて細かくOUTPUTを出しつつ期待値や結果を下げていく方向へ持っていこうと考えてたりしました。
が、そんな心配が具現することなく、思ってたより大丈夫だった(今後出て来るかもしれませんが...)

こうしたい未来のお話

まだ走り出したばかりで100年後も変わらず、こう思っているかと言われると自信は無いですが、、、

将来的には グループ会社全体でデータ活用なら @akidukin (または付随するその組織)に相談してみよう、となるのが理想です。
後はニーズをくみ取りながら、効果的な機能の提供を自発的に出来るようになるともっと嬉しい。企画 ~ 提供を一手に担える

受けた相談を元に実際にデータを見てみたり、分析してみたり、結果を元に改善案や何かしらの機能提供まで出来るようになったら良いです。みんな幸せHappy

上記のような理想に対してはまだまだ溝があり(始めたばかりなので当たり前ですが)、その溝を埋めるためにこんなロードマップを考えています。

  1. こちらからデータ活用についてのニーズのヒアリングを行い、プロジェクト化 <- イマココ
  2. プロジェクトに対して対応、及び改善案の提案や実施
  3. 良いor悪い影響の社内共有
  4. メディア運営 / タイアップ広告配信運営 以外の事業領域へデータ活用についてのニーズのヒアリング、プロジェクト化
  5. プロジェクトに対して対応、及び改善案の提案や実施
  6. 良いor悪い影響の社内共有
  7. ... ぐーるぐる回す
  8. ニーズの策定 + 企画立案 + 企画試行

まずは ご用聞き -> 誠実に対応 -> 正しく結果を共有 のサイクルを繰り返してデータを使う事の効果を知ってもらう (と同時にそれぞれの事業部で何をしているのかをより深く知る) その後、生まれそうなニーズや生まれたニーズを元に改善できそうな部分を考えていく。 という大まかな流れになるかなーと妄想しています。

きっとその間で、

  • 一緒に分析を進めてくれる人達のレベルアップとか
  • 一緒に実装を進めてくれる人達のレベルアップとか
  • どうやって膨大になっていくプロジェクトを適宜割り振っていくかとか
  • そうなるともっと人が必要になるよなーとか
  • 分析するマシンリソースがひっ迫してくるだろうから対応考えないと行けないよなーとか
  • 複雑になっていくデータリレーションをどう管理していけばいいのかなーとか
  • etc...

多分今考えている 5倍 ぐらいは出て来るとは思いますが、来る未来を想像しながらとりあえず目先の事を頑張る。

終わりに

改めて書き出してみるとあまり固まっていない事が判明して困惑しています。

今後進めていけるのであれば、より影響範囲は広めていきたいなー、色々なデータを触ってみたいなーとか思っています。が、
グループ会社を横断するって事を考えると、良く分からない大人の事情も出て来るだろうし、、、、
じゃあ提案をして改善していきましょう、ってなったときのステークホルダーとの折衝とか、、、、

まあ未来を悲観してもしょうがないので、今は目先の事をちゃんとこなしていく事にしています。

その辺り、もし同様の経験をされた方がいらっしゃったらお話お聞きしてみたい...

拙い文章でしたが、ここまで読んでいただいてありがとうございました。
随時、経過とか得られた知見とかアウトプットしていきたいなーと思っているので、よろしくお願いします!