GKEでRailsを動かしてみる
デプロイ
- 基本Cloud Buildを使って、ビルドとデプロイは分離する
- ロールバックも簡単にできるように
ビルド
- docker build
- assetsをCloud Storageにアップロード
- k8sのマニフェスト生成
- マニフェストをCloud Storageにアップロード
- デプロイをトリガーする
デプロイ
- Cloud Storageからマニフェストをダウンロード
-
db:migrate
を実行するJobのマニフェストをapply -
db:migrate
の完了/失敗を待つ - マニフェストのapply
参考
マイグレーションについて
- なるべくデプロイ前後どちらの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完了時にデプロイを開始するように分割する改善が必要そう
- Jobではサイドカーに
- ロールバックするには
db:rollback
ではなく、より宣言的なdb:migrate VERSION=XXX
を使う。- 新しいバージョンのコードがないとできないので、ここで使うコンテナのimageは
Deployment
と違いlatestを使う。db/migrate
下にファイルが追加されるような場合には問題ないけど、既存のファイルを更新するような変更が入ると問題になりうる。ただ、これはmigration自体の性質なので…
- 新しいバージョンのコードがないとできないので、ここで使うコンテナのimageは
static assetsの扱い
IngressのバックエンドをCloud Storageにするのは今のところできなさそう?
- Kubernetes 1.19には必要そうなものが入ったけれど、ingress-gceには入っていない?
- 入っているとしても1.19はRapid channel
Cloud Storageの前にHTTP(S) Load Balancingを置くなら、今はIngressとは別にTerraformで管理した方が良さそうか。
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にアップロードする際には以上のことを考慮して工夫しないといけない。
- 生成されたassetsのうち、gzipされているものを
.gz
を取り除いてgsutil -h "Content-Encoding: gzip" -h "Cache-Control: public, immutable, max-age=31536000" cp
- 関連するassetsを削除
- 残りのassetsを
gsutil -h "Cache-Control: public, immutable, max-age=31536000" cp
ちょっと不便なところ
- nginxの
gzip_static
のように.gz
があればそれをそのまま配信といったことはできない - Brotliは使えない
他のCDNを使うか、自分で管理するものを増やしたくはないけど間にnginxみたいなものを挟んだ方が便利そうだな…
もしくはpumaが入っているpodにnginxも入れて、Cloud Storageを使わない方がだいぶシンプルになるかも。
認可
Workload Identityを使う。
Node用に作ったサービスアカウントに必要な最低限のroleは https://cloud.google.com/kubernetes-engine/docs/how-to/hardening-your-cluster#permissions を参照。
Secretsの管理
Secret Managerへのリクエスト数によるコストが問題にならない限り以下で済ます。シンプルなので。
- コンテナのイメージ内にberglasを入れる
- 必要なものにはWorkload Identityで
roles/secretmanager.secretAccessor
を渡す
- 必要なものにはWorkload Identityで
-
ConfigMap
で環境変数を渡す- Secret相当は
sm://*
で
- Secret相当は
コストの問題を解決したくなった時に読む記事メモ
logging
- デフォルトで
STDOUT
、STDERR
がCloud Loggingに送られる- FluentDがDaemonSetで入っている
- ConfigMapで設定をいじれる
- 30日間保存される
- FluentDがDaemonSetで入っている
- Cloud LoggingからはCloud StorageやBigQueryにエクスポートできる
構造化ログ周りは記事にまとめた
RailsでCloud Loggingに適した構造化ログを出力する
- gemとしては
google-cloud-logging
や、それを含むstackdriver
があるが、これは直接Cloud Loggingに送るもので、STDOUTに出力するために使うものではなさそう -
GCPで理想の構造化ログを出力する方法
- 構造化ログ自体の話。アクセスログとアプリケーションログ。Cloud Loggingでいい感じにするにはなど、非常に参考になる
- どのような JSONを出力するべきかはConfiguring the agent | Cloud Logging | Google Cloud
requestSize
, responseSize
は
The size of the HTTP request message in bytes, including the request headers and the request body.
で、rackのレベルだと簡単に取れなさそうでどうしたものか。
モニタリング
Prometheus→Cloud Monitoring
- Stackdriver/stackdriver-prometheus-sidecar: A sidecar for the Prometheus server that can send metrics to Stackdriver.を使う
- それなりにお金がかかるので注意
-
$0.2580/MiB: 150–100,000 MiB
$0.1510/MiB: 100,000–250,000 MiB
$0.0610/MiB: >250,000 MiB
-
- Rubyの場合discourse/prometheus_exporterを使うとRuby, Rack, Sidekiq周りの情報が簡単にとれて便利
- rails server, sidekiqが動いているコンテナのサイドカーとして
bundle exec prometheus_exporter
するものを入れる
- rails server, sidekiqが動いているコンテナのサイドカーとして
参考になる記事
スケーリングについて
概要
- 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 )]
desirecReplicas
とcurrentReplicas
の比が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"
参考
独自ドメインとHTTPS
Kubernetes外でやる必要があること
- global static IP addressの予約
- terraformの
google_compute_global_address
リソースで
- terraformの
- 予約した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:
# ...
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的なインターフェースができたという感じっぽい。
Ingressでhttpの場合にhttpsへリダイレクトする設定は割と最近できたっぽい。
https周りの説明と設定例
クラスタ自体、GKE外のリソースの管理
Terraformを使う。GKEのモジュールは使わなくてもいいような気がするが?
メモ
- google_container_cluster | Resources | hashicorp/google | Terraform Registry
- google_container_node_pool | Resources | hashicorp/google | Terraform Registry
- terraform-google-modules/terraform-google-kubernetes-engine: A Terraform module for configuring GKE clusters.
- Using GKE with Terraform | Guides | hashicorp/google | Terraform Registry
- Provision a GKE Cluster (Google Cloud) | Terraform - HashiCorp Learn
驚いたところ
KubernetesとDockerのcommandは違う
- Kubernetesでの
command
はDockerでのENTRYPOINT
- Kubernetesでの
args
がDockerでのCMD
Define a Command and Arguments for a Container | Kubernetes
また、Dockerfile
の ENTRYPOINT
がshell formだと args
だけ書いても渡らない。
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