GKEでRe;dashを作成する

はじめに

GKEからGoogle Cloud SQLに接続するにはいくつかのパターンがあります。

接続方法 備考
IPアドレス + SSL証明書 証明書を用意する必要がある
IPアドレスのみ セキュリティ観点から選択肢としてなし
Cloud SQL Proxy JavaとGoのライブラリが存在する。
公式ドキュメントにはPythonの例があるが cloud_sql_proxy デーモンを使用する方法のみ記載されているので cloud_sql_proxy を使用しなければならい。
※第1世代では使用できない。
Cloud SQL Proxy Docker Image サイドカー or service-Replicaset(or DaemonSet)として起動させることが可能。
※第1世代では使用できない。

今回、Re;dashをGKEで起動させるのでセキュリティと実装のスマートさからCloud SQL Proxy Docker Imageを選択します。 また、Worker数(コンテナ数)とするため、サイドカーとしてではなくService-ReplicaSetで起動します。

また、Re;dash管理で使用するRedis及びPostgreSQLはGKEでPersistent Volume + StatefulSetを使用します。 理由としてはGKE関係者とお話させて頂いた際に、社内ではGKEにDB(RDBMS)を構築することがよくあるそうです。

Docker Containerの作成

今回作成するコンテナは以下の通りです。

  • Nginx
  • Redash Server
  • Redash worker
  • Postgresql
  • Redis

1. Nginx Docker File

Dockerfile-nginx Nginxの設定でログの出力先にそれぞれ /dev/stdout, /dev/stderr を指定するのも良いと思います。

FROM nginx:1.15.5-alpine-perl
COPY nginx.conf /etc/nginx/nginx.conf
RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log

nginx.conf httpsを有効にした場合にhealthcheckがリダイレクトしないよう設定を それ用に設定を行います。 また、redashへのproxyはK8sのServiceを使用して通信を行うのでSevice Nameを redash とし、 Serviceに対してproxyを行う様に設定します。

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
  worker_connections  1024;
}


http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

  access_log  /var/log/nginx/access.log  main;

  sendfile        on;

  keepalive_timeout  65;

  upstream redash {
    server redash-server:5000;
  }

  server {
    listen   80 default;

    gzip on;
    gzip_types *;
    gzip_proxied any;

    location = /healthcheck {
        return 200;
    }

    set $redirect 0;

    if ($http_x_forwarded_proto = http) {
        set $recirect 1;
    }

    if ($http_x_health_check = on) {
        set $recirect 0;
    }

    if ($recirect = 1) {
        return 301 https://$host$request_uri;
    }

    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;
    }
  }
}

2. Redash Server/Worker

dockerhubのredashイメージを使用します。

3. Postgresql

dockerhubのpostgresqlイメージを使用します。

4. Redis

dockerhubのredisイメージを使用します。

Name Spaceの作成

kubectl create namespace redash

k8sクラスタは作成済みの想定していますが、terraformを使用する場合 以下の様にtfファイルを作成すれば用意できます。

ingress用のstatic ipを取得も合わせて行っています。

### GKE
resource "google_container_cluster" "gke" {
  name               = "${var.cluster_name}"
  zone               = "${var.zone}"
  network            = "${var.network}"
  initial_node_count = "${var.initial_node_count}"
  description        = "${var.description}"

  min_master_version = "${var.min_master_version}"
  node_version       = "${var.node_version}"

  node_config {
    machine_type = "${var.machine_type}"
    disk_size_gb = "${var.disk_size}"

    oauth_scopes = [
      "https://www.googleapis.com/auth/compute",
      "https://www.googleapis.com/auth/devstorage.read_only",
      "https://www.googleapis.com/auth/logging.write",
      "https://www.googleapis.com/auth/trace.append",
      "https://www.googleapis.com/auth/monitoring",
      "https://www.googleapis.com/auth/devstorage.read_only",
      "https://www.googleapis.com/auth/compute",
      "https://www.googleapis.com/auth/cloud-platform",
      "https://www.googleapis.com/auth/servicecontrol",
      "https://www.googleapis.com/auth/cloud-platform",
      "https://www.googleapis.com/auth/cloud-platform.read-only",
      "https://www.googleapis.com/auth/service.management",
      "https://www.googleapis.com/auth/service.management.readonly",
    ]
  }
}

resource "google_compute_global_address" "redash-ingress-ip" {
  name  = "redash-ingress"
}

Deploymentの作成

1. nginx deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redash-nginx
  namespace: redash
  labels:
    app: redash-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redash-nginx
  template:
    metadata:
      labels:
        app: redash-nginx
    spec:
      containers:
      - name: redash-nginx
        image: gcr.io/${$PROJECT_ID}/redash-nginx:${REVISION_ID}
        resources:
          limits:
            memory: "256Mi"
          requests:
            memory: "256Mi"
        ports:
        - containerPort: 80
          name: redash-nginx
        readinessProbe:
          httpGet:
            path: /healthcheck
            port: 80

2. redash server deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redash-server
  namespace: redash
  labels:
    app: redash-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redash-server
  template:
    metadata:
      labels:
        app: redash-server
    spec:
      containers:
      - name: redash-server
        image: redash/redash:5.0.2.b5486
        resources:
          limits:
            memory: "512Mi"
          requests:
            memory: "512Mi"
        command:
        - /bin/sh
        - -c
        - /app/bin/docker-entrypoint create_db; /app/bin/docker-entrypoint server
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: redash-postgresql
              key: user
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: redash-postgresql
              key: password
        - name: PYTHONUNBUFFERED
          value: "0"
        - name: REDASH_LOG_LEVEL
          value: "INFO"
        - name: REDASH_WEB_WORKERS
          value: "4"
        - name: REDASH_REDIS_URL
          value: "redis://redash-redis:6379/0"
        - name: REDASH_DATABASE_URL
          value: "postgresql://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@redash-postgresql/redash-user"
        ports:
        - containerPort: 5000
          name: redash-server

パスワードについてはk8sのsecretに設定しておきます。

kubectl create secret generic redash-postgresql --from-literal=user=redash-user --from-literal=password=<password>

3. redash worker deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redash-worker
  namespace: redash
  labels:
    app: redash-worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redash-worker
  template:
    metadata:
      labels:
        app: redash-worker
    spec:
      containers:
      - name: redash-worher
        image: redash/redash:5.0.2.b5486
        resources:
          limits:
            memory: "256Mi"
          requests:
            memory: "256Mi"
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: redash-postgresql
              key: user
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: redash-postgresql
              key: password
        - name: PYTHONUNBUFFERED
          value: "0"
        - name: REDASH_LOG_LEVEL
          value: "INFO"
        - name: REDASH_REDIS_URL
          value: "redis://redash-redis:6379/0"
        - name: REDASH_DATABASE_URL
          value: "postgresql://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@redash-postgresql/redash-user"
        - name: WORKERS_COUNT
          value: "2"
        - name: QUEUES
          value:  "queries,scheduled_queries,celery"
        args: ["scheduler"]

4. redis deployment

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redash-redis
  namespace: redash
  labels:
    app: redash-redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redash-redis
  serviceName: redash-redis
  template:
    metadata:
      labels:
        app: redash-redis
    spec:
      containers:
      - name: redash-redis
        image: redis:5.0.0-alpine
        resources:
          limits:
            memory: "256Mi"
          requests:
            memory: "256Mi"
        ports:
        - name: redash-redis
          containerPort: 6379
        volumeMounts:
        - name: redash-redis-persistent-storage
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redash-redis-persistent-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi

redisについてはPersistent Volumeを用いてコンテナが停止してもデータを永続して サービスを提供し続けられる状態にしておきます。 これによりRedisの不具合等があった場合においてもデータロストをあまり気にせずに バージョンアップ等が可能になります。

4. postgresql deployment

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redash-postgresql
  namespace: redash
  labels:
    app: redash
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redash-postgresql
  serviceName: redash-postgresql
  template:
    metadata:
      labels:
        app: redash-postgresql
    spec:
      containers:
      - name: redash-postgres
        image: postgres:11.0-alpine
        resources:
          limits:
            memory: "512Mi"
          requests:
            memory: "512Mi"
        env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: redash-postgresql
              key: user
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: redash-postgresql
              key: password
        ports:
        - containerPort: 5432
          name: postgres
        volumeMounts:
        - name: redash-postgresql-persistent-storage
          mountPath: /var/lib/postgresql/db-data
  volumeClaimTemplates:
  - metadata:
      name: redash-postgresql-persistent-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 20Gi

Redis同様Postgresqlのストレージ領域をPersistent Volumeに保存しておきます。 バックアップについては現時点で機能提供が見つけられない状態でしたが バックアップ用のjobコンテナを作成し、定期実行しておけば然程問題ではないと言えます。

※少しめんどくさいですが・・・

Deploymentのまとめ

manifestファイルが増えるとSpinnakerを使用する事を検討した方がいい!
しかし、小さく始めるのであればSpinnakerは起動するだけでもリソースをある程度大きく消費するので 既に使用しているCI/CDツールとSkaffoldを使用する事をお勧めしまします。

メモリやストレージ容量の設定はミニマムに開始する為の設定を行ったので必要であればチューニングを行って下さい。

Serviceの作成

Ingress -> Nginx と接続させるため、 Nginxはクラスタ外からアクセスできる NodePortを使用し、 クラスタ内からアクセスできるNginx -> Redash, Redash -> Redis, Redash -> PostgresqlはClusterIPを使用します。 具体的な書き方としては今回以下の様な設定をしました。

1. Nginx service

apiVersion: v1
kind: Service
metadata:
  name: redash-nginx
  namespace: redash
  labels:
    app: redash-nginx
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: redash-nginx

2. redash server service

apiVersion: v1
kind: Service
metadata:
  name: redash-server
  namespace: redash
  labels:
    app: redash-server
spec:
  type: ClusterIP
  ports:
  - port: 5000
    targetPort: 5000
    protocol: TCP
  selector:
    app: redash-server

3. redis service

apiVersion: v1
kind: Service
metadata:
  labels:
    app: reddash-redis
  name: redash-redis
spec:
  type: ClusterIP
  ports:
  - port: 6379
    targetPort: 6379
    protocol: TCP
    name: redash-redis
  selector:
    app: redash-redis

4. postgresql service

apiVersion: v1
kind: Service
metadata:
  labels:
    app: redash-postgresql
  name: redash-postgresql
spec:
  type: ClusterIP
  ports:
  - port: 5432
    targetPort: 5432
    protocol: TCP
    name: redash-postgresql
  selector:
    app: redash-postgresql

Ingressの作成

今回、HTTPSでのアクセスを想定しているのでTLSの設定も実施しています。 証明書については提供事業者によって手順は異なるとは思いますが 作成済みと仮定し、GKEへの登録は公式ドキュメントの手順通りに進めれば 問題ないと思います。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.allow-http: "false"
    kubernetes.io/ingress.global-static-ip-name: redash-ingress
  generation: 1
  name: redash
  namespace: redash
spec:
  backend:
    serviceName: redash-nginx
    servicePort: 80
  tls:
  - secretName: hogehoge-com

まとめ

Redashは基本的に非機能要件であるのでService提供用のGKE Clusterに作成するのはどうなのかと思いますが 非機能要件をまとめたGKE Clusterを作成するパターンもありだなと感じました。

一方で管理するGKE Clusterを増やすのも面倒なので 1つのGKE Clusterに専用nodeを作成し、node affinity 機能を使用した パターンも実際に使用してみたので次回はそのデザインパターンについての記事を書いてみようと思います。

また、K8sでPersistent Volumeを使用した場合のバックアップ方法についても次回以降で解説をいたいと思います。