generated at
Gyazo と GKE
2022-05-17 (Tue) 19:55 スタート予定 (15m)

準備
30分ぐらい前にたたかうマヌカを舐める


Gyazo のメンテや開発をやってます
おもにバックエンド関連 (Rails, golang, etc...)やたまに DevOps

今日は DevOps のお話
Gyazo と GKE
deploy
branch staging

Gyazo と GKE

Gyazo は GKE 上で動いています
GKE とは GCP (Google Cloud Platform) で提供されている Kubernetes の managed service
GCP とか Kubernetes (k8s) が何かはなんとなくわかっている前提で話をします
GKE cluster は production (gyazo.com) と staging で分けています
staging cluster では branch staging と呼ばれているものが動いている
他にもイントラ系 kibana, grafana などが動いている GKE cluster もある...

なぜ GKE (Kubernetes)?
Gyazo の初期のころは素朴にレンタルサーバー的なホスティングのマシン上で動いていた
直近では GCE の instance template から instance group 作成
docker for mac が登場して開発者のローカル環境は docker-compose に
rbenv や nvm が必要無いので楽
deploy 先も コンテナ化 or Full managed な PaaS に移行して楽をしたい
ruby 以外にもフロントエンドのビルドには node.js のツールが必要
コンテナにすると最終 image は node.js なしで済む
サーバーホスト管理したくない...
アップロードや画像配信のアプリケーションは分かれていてやや複雑
メインは Rails だけど画像バイナリを扱うところは golang
heroku みたいなシンプルな PasS では難しい
アプリケーションコードをほぼそのまま移行するにはその複雑さを再現できる Kubernetes がちょうどよかった
k8s cluster 内 network でお互いにアクセス
最近は Serverless VPC Access で Cloud Run とかから GCP のネットワークに接続とかできるらしいけど...
pod に複数のコンテナ (nginx, ruby server, fluentd, etc...)
一つのコンテナに突っ込むのは辛そう...

Gyazo の GKE cluster はどんな感じに運用されているのか
Cloudbuild で deploy してます
production 環境への入り口を制限したい
GCP 外部の CI から deploy するためには service account key などが必要になる
各CIに self-hosted runner とかあるけど...
Cloudbuild の step としては
普通に docker image をビルドして
kubectl apply で service, deployment などのリソースを更新
deploy で工夫してるところだと...
manifest.yaml
kind: Deployment spec: template: metadata: annotations: kubernetes.io/change-cause: "${date} TAG=${IMAGE_TAG}"
slack で rollback 方法を通知
manifest yaml の管理は kustomize を一部使ってますが...
変数展開は envsubst とか使う必要あるし
構成変更するのに yaml のパッチみたいなの書くのですが冗長だし自由度が低い...
単純なところは bash の heredoc 使ったほうが簡単
シンプルな yaml, json 汎用管理ツールみたいなのに切り替えたい...

heroku review apps とか一般的には development 環境と呼ばれたりしているものを k8s 向けに自前で実装
https://my-branch--g.gyazo.example/ みたいなサブドメインでアクセスすると my-branch のデプロイ先にアクセスできる

branch staging はどういうしくみなのか?
URL ルーティング
scale to zero

branch staging へのリクエスト
Google HTTP Load balancer の backend service として staging-proxy という nginx コンテナの service を指定
nginx.conf で --g で終わるサブドメインは upstream .default.svc.cluster.local に流れるように...
e.g. my-branch--g.gyazo.example -> my-branch--g.default.svc.cluster.local
そうすると cluster network の service my-branch--g にリクエストが流れる...

branch staging の scale to zero
production ではふつうに HPA (Horizaontal Pod Autoscaler)
HPA は pod を 0 にできない
どんどんブランチができると GKE cluster の CPU/memory resources を圧迫
手動で kubectl delete deployment my-branch--g とかキビシイ
HPA は 0 から復活することもできない
osiris というのを最初は使っていたけど...
ブランチの数が増えたときに挙動のデバッグが大変であきらめた
今見たら僕の helm3 対応 commit が最後で archive になってる...
すごいwakix
knative serving (Cloud Run はこれで動いている) でも scale to zero できるみたいだけど...
1 container/ 1 pod という制限があったり (このへん変わるみたいだけど...)
これ書いているときに https://keda.sh/ というのも発見してしまった...
やりたいことは環境によって微妙に違うはず...
k8s API 自体は REST なので難しくない
試しに ruby で実装してみるか... -> branch-manager.rb

branch-manager.rb がやってること
上記 staging-proxy の nginx.conf で --g の svc に upstream するときに branch-manager にもリクエストを mirror
該当する service の deployment の pod が 0 だったら kubectl scale deployment/my-branch--g --replicas=1 相当の API を叩く
各 service の最終アクセス日時を記録しておいて、一定時間アクセスが無い service は scale --replicas=0
基本はコレだけ


branch-manager.rb の実装
100行ぐらいの ruby script
Gemfile
gem "sinatra" gem 'rest-client' gem 'rufus-scheduler' gem 'tzinfo-data' # Internally required by rufus-scheduler gem 'dalli'
rbac で適切な権限を与えていれば以下のようなリクエストヘッダ設定で API アクセスできる
ruby
def resource_client if ENV['KUBECTL_PROXY'] Thread.current[:resource_client] ||= RestClient::Resource.new( ENV['KUBECTL_PROXY'], headers: { 'Content-Type' => 'application/strategic-merge-patch+json' } ) else token = File.read('/var/run/secrets/kubernetes.io/serviceaccount/token') Thread.current[:resource_client] ||= RestClient::Resource.new( 'https://kubernetes.default.svc', ssl_ca_file: '/var/run/secrets/kubernetes.io/serviceaccount/ca.crt', headers: { 'Authorization' => "Bearer #{token}", 'Content-Type' => 'application/strategic-merge-patch+json' } ) end end
たとえばこんな感じ
ruby
res = resource_client["/api/v1/namespaces/default/pods"].get(params: {labelSelector: 'branch-manager'}) json = JSON.parse(res.body)
API を調べるには kubectl -v8
ローカルでデバッグするときは
sh
kubectl --cluster=gyazo-staging proxy
out
Starting to serve on 127.0.0.1:8001

おわり
こんな感じに Nota では DevOps 系のタスクもひっそりとやってます
インフラ/セキュリティエンジニア募集してます
最初は helpfeel の DevOps を助けて欲しいけど、長期的にはサービス共通基盤みたいなのができるとかっこいいと思ってる