Open19

GKEでRailsを動かしてみる

en30en30

GKEについて軽く学んでみるの続き。

  • 気になっている要素ごとにスレッドを作る
    • 読んだ記事や経過をつなげる
    • スレッド最上部に現段階での結論を書いて更新していく

とやってみる (かもしれない)。

en30en30

デプロイ

  • 基本Cloud Buildを使って、ビルドとデプロイは分離する
    • ロールバックも簡単にできるように

ビルド

  • docker build
  • assetsをCloud Storageにアップロード
  • k8sのマニフェスト生成
  • マニフェストをCloud Storageにアップロード
  • デプロイをトリガーする

デプロイ

  • Cloud Storageからマニフェストをダウンロード
  • db:migrate を実行するJobのマニフェストをapply
  • db:migrate の完了/失敗を待つ
  • マニフェストのapply

参考

en30en30

マイグレーションについて

  • なるべくデプロイ前後どちらのschemaでも動くように書く
    • 無理な場合は場合はあきらめてダウンタイム
  • KubernetesのJobでdb:migrate したあとにデプロイする
    • Jobではサイドカーに cloud_sql_proxy を使うけど、 db:migrate が終了したら cloud_sql_proxy も終了するように、ちょっとしたハックが必要
    • Cloud Buildではmigration用Jobの完了/失敗を kubectl wait で待つ
      • https://stackoverflow.com/a/60286538/2468823
      • 時間がかかるmigrationの場合Cloud Buildのビルド時間が無駄に長くなってしまうので、migrationの完了は待たずにmigration用のJob完了時にデプロイを開始するように分割する改善が必要そう
  • ロールバックするには db:rollback ではなく、より宣言的な db:migrate VERSION=XXX を使う。
    • 新しいバージョンのコードがないとできないので、ここで使うコンテナのimageは Deployment と違いlatestを使う。db/migrate 下にファイルが追加されるような場合には問題ないけど、既存のファイルを更新するような変更が入ると問題になりうる。ただ、これはmigration自体の性質なので…
en30en30

static assetsの扱い

IngressのバックエンドをCloud Storageにするのは今のところできなさそう?

Cloud Storageの前にHTTP(S) Load Balancingを置くなら、今はIngressとは別にTerraformで管理した方が良さそうか。

en30en30

Cloud CDN + Cloud Storageと圧縮

GKEというかGCPの話。

Cloud CDN自体に圧縮の機能はないので、Cloud Storageで設定する必要がある。

Cloud CDN does not compress or decompress responses itself, but it can serve responses generated by your origin server that are compressed by using encodings such as gzip and DEFLATE.
Troubleshooting  |  Cloud CDN  |  Google Cloud

Cloud Storageは、metadataとしてContent-Encoding: gzipがついているときは以下のように配信する。

  • リクエストにAccept-Encoding: gzipがついている場合はそのまま配信
  • ついていない場合はdecompressしてContent-Encodingを除いて配信

Vary: Accept-Encodingはちゃんと勝手につく。
Transcoding of gzip-compressed files  |  Cloud Storage  |  Google Cloud

Cloud Storageにアップロードする際には以上のことを考慮して工夫しないといけない。

  1. 生成されたassetsのうち、gzipされているものを.gzを取り除いてgsutil -h "Content-Encoding: gzip" -h "Cache-Control: public, immutable, max-age=31536000" cp
  2. 関連するassetsを削除
  3. 残りのassetsをgsutil -h "Cache-Control: public, immutable, max-age=31536000" cp

ちょっと不便なところ

他のCDNを使うか、自分で管理するものを増やしたくはないけど間にnginxみたいなものを挟んだ方が便利そうだな…
もしくはpumaが入っているpodにnginxも入れて、Cloud Storageを使わない方がだいぶシンプルになるかも。

en30en30

Secretsの管理

Secret Managerへのリクエスト数によるコストが問題にならない限り以下で済ます。シンプルなので。

  • コンテナのイメージ内にberglasを入れる
    • 必要なものにはWorkload Identityでroles/secretmanager.secretAccessorを渡す
  • ConfigMapで環境変数を渡す
    • Secret相当はsm://*

コストの問題を解決したくなった時に読む記事メモ

en30en30

logging

  • デフォルトでSTDOUTSTDERRがCloud Loggingに送られる
    • FluentDがDaemonSetで入っている
      • ConfigMapで設定をいじれる
    • 30日間保存される
  • Cloud LoggingからはCloud StorageやBigQueryにエクスポートできる

構造化ログ周りは記事にまとめた
https://zenn.dev/en30/articles/b48f48ff4710d9

en30en30

RailsでCloud Loggingに適した構造化ログを出力する

requestSize, responseSize

The size of the HTTP request message in bytes, including the request headers and the request body.

で、rackのレベルだと簡単に取れなさそうでどうしたものか。

en30en30

モニタリング

Prometheus→Cloud Monitoring

参考になる記事

en30en30

スケーリングについて

概要

  • Podに関する理想的な状態はyamlで指定する
    • kind: VerticalPodAutoscaler: スケールアップ。あるPodに対する理想的なCPU、Memoryの量。
    • kind: HorizontalPodAutoscaler: スケールアウト。理想的なPod数。
  • Podの理想的な状態を実現するために必要なNodeのスケールは
    • GKE Standardだと、node poolを作りCluster Autoscalerを有効にするとnode pool内のnode数調整を自動でやってくれる
    • GKE Autopilotだとnode poolの管理もなしで自動でやってくれる

Horizontal Pod autoscaling

  • スケールの基準
    • Actual resource usage: Podが使っているCPU、メモリ使用量
    • Custom metrics: Kubernetes objectによるmetrics
    • External metrics: cluster外のサービスによるmetrics
  • 複数のmetricsを使う場合は最大値でscaleされる

理想的なPod数の計算

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

desirecReplicascurrentReplicasの比が1.0±torelanceに入っている場合には無視。

currentReplicas currentMetricValue desiredMetricValue desiredReplicas
4 200m 100m ceil(4 * 200/100) = 8
4 50m 100m ceil(4 * 50 / 100) = 2

参考: Horizontal Pod Autoscaler | Kubernetes

Custom / External Metricsでのオートスケール

Custom Metrics Adapterを入れる

Autoscaling Deployments with Cloud Monitoring metrics通り。

Cloud Buildでデプロイする最初のstepに以下を入れることにした。

  # ref: https://cloud.google.com/kubernetes-engine/docs/tutorials/autoscaling-metrics
  - id: custom-metrics-adapter
    name: "gcr.io/cloud-builders/gcloud"
    entrypoint: /bin/bash
    args:
      - "-c"
      - |
        curl -o custom_metrics_adapter.yaml https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml \
        && test "e9b05a7726f8508379f780bf6257bb4b6815026fd5a007d2c4841d20b887b71c" = "$$(openssl sha256 custom_metrics_adapter.yaml | sed 's/^.* //')" \
        && gcloud container clusters get-credentials ${_CLUSTER} --zone ${_ZONE} \
        && kubectl apply -f custom_metrics_adapter.yaml

HorizontalPodAutoscaler

PrometheusからCloud Monitoringにいれたものの場合external.googleapis.com/prometheus/*にある。
Custom Metrics Adapterが入っていれば以下で値が見れる。

$ kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/exte
rnal.googleapis.com|prometheus|<METRIC_NAME>" | jq "."  

マニフェストでは以下のように使う。

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: worker
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: worker
  minReplicas: 1
  maxReplicas: 10
  metrics:
    - type: External
      external:
        metric:
          name: external.googleapis.com|prometheus|<METRIC_NAME>
        target:
          type: AverageValue
          averageValue: "10"

参考

en30en30

独自ドメインとHTTPS

Kubernetes外でやる必要があること

  • global static IP addressの予約
    • terraformのgoogle_compute_global_addressリソースで
  • 予約したIPアドレスを指すAレコードを登録

マニフェスト

以下でやっていること

  • Ingressを作って予約したstatic ipを指定
  • httpsのマネージド証明書を使う
  • httpの場合にhttpsにリダイレクト
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
  name: ingress-certificate
spec:
  domains:
    - $DOMAIN

---

apiVersion: networking.gke.io/v1beta1
kind: FrontendConfig
metadata:
  name: ingress-config
spec:
  redirectToHttps:
    enabled: true

---

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: web-ingress
  annotations:
    kubernetes.io/ingress.class: "gce"
    kubernetes.io/ingress.global-static-ip-name: $INGRESS_GLOBAL_STATIC_IP_NAME
    networking.gke.io/managed-certificates: ingress-certificate
    networking.gke.io/v1beta1.FrontendConfig: ingress-config

spec:
  backend:
    # ...
en30en30

https://cloud.google.com/blog/products/networking/introducing-cloud-domains
Cloud Domainsというサービスができていたの知らなかった。

Since Cloud Domains uses Google Domains — Google’s internet domain name registration service — as the registrar, customers can access a wide range of registrar features through Google Domains management console.

Google Domainsに対する、GCP的なインターフェースができたという感じっぽい。

en30en30

Probe

  • Startup Probes: 初期化時のチェック。失敗したらRestart Policyに基づいて処理。
  • Readiness Probes: 常にチェックして、失敗したらトラフィックを流さない。
  • Liveness Probles: Startup Probeの後、常にチェックして、失敗したらRestart Policyに基づいて処理 。

全部使うのが良さそうで、liveness probeの失敗判定はreadiness probesの失敗判定よりもゆるくしておくと良さそう。
Configure Liveness, Readiness and Startup Probes | Kubernetes

SidekiqのProbe

https://moneyforward.com/engineers_blog/2021/02/19/kubernetes-sidekiq-healthcheck/