Windows開発機では functions-framework-pythonのローカルエミュレータを動かせない
Cloud Functionsをそれなりに利用するようになると、動かす関数の信頼性・安全性を向上させる、あるいは開発体験を向上させるためにユニットテストをしっかり書くようにする・CIを回すようにするといった段階に進むことが多々あると思います。 そこで活用したいローカルエミュレータなのですが、Python製ライブラリだとWindowsを使う開発者を対象としておらず、開発環境を整えることが不可能だったよ、という話です。
要約
- GCP公式がCloud Functionsのローカルエミュレータを配布している
- Python製エミュレータはWindows機では動かすことができない
- 回避策は「テスト対象を絞る」「別の言語でCloud Functionsで動かす関数を実装しなおす」
Cloud Functionsのローカル開発環境に利用するfunctions-frameworkシリーズ
Cloud Functionsを使ったサーバレスアーキテクチャを採用する際、ローカルで事前に検証・ユニットテストなどに活用するように、公式からCloud Functionsのエミュレータが配布されています。 今回はこのPythonで書かれたエミュレータ(functions-framework-python)はWindowsでは動かせられないよ、という趣旨の話です。 なおPythonの他にNode.js・Java・Rubyでも同様のライブラリが開発されています。
本題
Python製エミュレータはWindows機では動かすことができない
Pythonで書かれたエミュレータ(functions-framework-python)は、内部で Flask が使われています。
Flask とはPythonで書かれたウェブサーバで、他の言語でいうところの Sinatra(Ruby) express.js(Node.js) にあたるウェブフレームワークです(少し語弊ありそう)。
エミュレータでは Flask を動かすためのアプリケーションサーバに gunicorn というライブラリを利用しており、この gunicorn が曲者で、unix系OSでしか動かせないライブラリとして開発されていたのです。
なのでWindowsでは gunicorn を動かそうにも動かすことができず、連鎖して Flask が動かず、当然 functions-framework-python も動かないという構造になっていました。
なお、厳密にはpipインストールすらできずつまづく、というところでその先進めない状態になります。
# Windows機でpip installしようとすると... pipenv install --dev Installing dependencies from Pipfile.lock (12c19b)... ================================ 1/1 - 00:00:00 Ignoring gunicorn: markers 'platform_system != "Windows"' don't match your environment
Stackoverflowでも「そもそもgunicornはWindows用に作られていないよ」というやりとりが...w stackoverflow.com
回避策
さて、課題があっても解決しなければならない・解決するのがエンジニアリング。
採用したいかどうかは置いておいて、採れる回避策を検討しました。
1. テスト対象を絞る
そもそもローカルエミュレータを使いたい理由は主に以下の2点です。
- ローカルで動作確認をとりたい(HTTPトリガーの関数の場合)
- CI等で動作確認をとりたい
逆に言うと、ローカルで動作確認をとらないと決めたならエミュレータは不要です。特にPub/Sub経由のイベントトリガーの関数としてCloud Functionsを利用する場合、以下の理由でローカルでの動作確認がしづらかったりします。
- 最初に実行するmain関数の部分で返り値が不要だったりするので、動作確認がしづらい
- 同様の理由でユニットテストも書きづらい
- Pub/Sub側のエミュレータを用意する必要がありかなり環境構築がつらくなる
上記理由からmain関数から呼ぶ独自の関数やクラスの振る舞いを単体テストする、などの対応で納めることが可能な作りならば、「エミュレータは不要」と判断することもできます。
2. コンテナで隠蔽してあげる
Pythonを動かす環境自体をDockerコンテナで隔離してあげれば、WindowsだろうがMacだろうがコンテナの中は同じ環境として扱えるのでこの問題は解消されます。
しかし、開発者にDockerに関する知識・Python特有の仮想環境の知識が求められるようになるため、「関数を書くことだけに集中」という状態はより作りづらくなるなぁという課題も生まれます。
3. 別の言語でCloud Functionsで動かす関数を実装しなおす
いやどうしてもローカルエミュレータで動作確認したいんだ!ということでしたら、やはりWindowsでも環境構築できる言語で再実装・再構築し直す必要があります。 Windowsでも相性がよさそうなのはJavaや.NETなんですかね。試してないのでわからないです。
さいごに
Cloud Functionsに限らずサーバレス関数な技術は使い勝手がよくてとても便利なのですが、テストを書いたり、開発者が同じ開発環境を整える手筈を組もうとすると結構大変です。無邪気にアプリケーションでの利用をするのはなるべく避けるのも運用観点では重要な判断軸なのかなと思ったり。
CloudSQLのメンテナンスに対する運用を考える
CloudSQLを使っていると逃げきれない仕様の一つに定期メンテナンスがあります。 数ヶ月に一度、最大で90秒ほどDBインスタンスが落ちる仕様なため、プロダクション環境などでCloudSQLを利用していると、必然と90秒のメンテナンスによるサービスダウンが発生しかねません。
頑張って高可用クラスタ(冗長化)してたり、リードレプリカを作成しても、メンテナンス中はすべて停止してしまうため、サービスダウンは不可避です。
そんなわけでCloudSQLのこの仕様について頭を悩ませているインフラ運用者もきっと多いはず!
ということで2年くらいCloudSQLを運用してみえた運用方法の策定基準をまとめてみます。
前提
- サーキットブレイカーの導入など、アプリケーションレイヤでのDBインスタンスの停止(GCPが一番推奨している対策方法)に対する処方は抜きとする
- アプリケーションに手を入れず、CloudSQLを運用するインフラエンジニアだけでなんとかしなきゃいけないケースを想定
- 本番サービス運用を想定
- 特にSLAを重要視して作った運用方針案
3通りの考えられる運用方針
1. 自動メンテナンスのなすがままパターン
方針 :
CCPが自動で決めたメンテナンスタイミングに従って自動メンテナンス作業をしてもらう方針です。インフラエンジニアのメンテナンス作業はほぼいらないので楽といえば楽ですが、メンテナンス作業時間幅を設定していないと、不意に作業が開始されて期待しないサービスダウンが起きてしまいかねません。
【この方針を採用できるサービス例】
- いつサービスが落ちても重大な被害がない(SLAを設定していない場合など)
- 数ヶ月に一度の90秒程度のサービスダウンなら許容できる
- 日中は困るが、深夜帯ならサービスダウンが一瞬起きても許容できる場合も採用できる
【この方針をとるときに設定しておくべきこと】
- MUST
- 特になし
- 推奨
- GCPからメンテナンスについての事前通知を受け取る設定を行なう
- メンテナンス時間幅の設定を行なう
2. 事前に手動でメンテナンス作業を実施するパターン
方針 :
CloudSQLでは1週間前にメンテナンスするーという旨の通知をもらうことができます(設定してないと通知はこない)。通知を受け取ったら1週間以内にメンテナンス作業を手動で実施することで、意図しないサービスダウンを防ぐことができます。ただしサービスダウン自体は発生するため、あくまで「サービスダウンが起きる時間を固定し、ステークホルダにその旨を知らせることができる」という利点があるのみです。
なお事前通知を受けてから1週間後に自動メンテナンスが走りますが、通知を受けた際に「延期」することも可能です。最大でさらに1週間ほど実施タイミングを伸ばせるので、ステークホルダへの合意・共有に時間を要する組織であればこの設定もしておくとよいです。
【この方針を採用できるサービス例】
- SLAが定義されており、サービスをダウンさせる場合はステークホルダへの合意が必要なケース
- 調整にかけられる時間は最大で2週間なので、それ以上に時間が必要な重厚長大な組織の場合は当てはまりません
- 1〜2週間程度の間でメンテナンスのためにインフラチームの工数を確保できる柔軟なチームの場合
【この方針をとるときに設定しておくべきこと】
- MUST
- GCPからメンテナンスについての事前通知を受け取る設定を行なう
- 推奨
- メンテナンス作業の延期申請
- メンテナンス時間幅の設定を行なう
3. 繁忙期だけサービスダウンを拒否し、それ以外の期間では2.で運用するパターン
方針 :
CloudSQLではメンテナンス作業を拒否することができます。最大で拒否できる期間は90日で、メンテナンス作業時間が設定されていてもこの設定がある場合はメンテナンスが行なわれません。しかしあくまでサービス特性上、繁忙期は1秒でもサービスを落としたくないといったニーズへの対応であり、メンテ工数・メンテナンスによって生じるサービスダウンタイムを増やしたくないという理由で拒否をするは避けた方がよいと個人的には思っています。繁忙期以外の期間では、2.で提案した運用に切り替えます。
注意点としては、年に1度しかこの拒否設定はできないため、年に複数回のキャンペーンを行ない、そのキャンペーン中はサービスを落としたくない、といったニーズには答えられません。
こうなってくるとアプリケーションレイヤに手を入れなければなりませんし、マイクロサービス 前提のアーキテクチャになっていないと実現は難しいです。
【この方針を採用できるサービス例】
- 季節要因により負荷が高まるサービス
- キャンペーンを行なう予定のあるサービス
【この方針をとるときに設定しておくべきこと】
- MUST
- GCPからメンテナンスについての事前通知を受け取る設定を行なう
- メンテナンス拒否設定を行なう
- 推奨
- メンテナンス時間幅の設定を行なう
所感
すべて泥臭い運用方針でイケてる提案ではないですが、現在のCloudSQLの仕様上、しょうがないのかな?とも思っています。
せめて高可用クラスタやリードレプリカはローリングアップデート方式でメンテナンスしてくれよという気持ちでいますが、実現するでしょうか...
他、「うちはこうしてるよー」というアイデアや、「もっといい方法あるやん」というツッコミ、まってます。
複数コンテナを有するPodのHPA設定の挙動を深ぼる
HPAの設定ではDeployment単位で閾値を指定しているのに、扱うリソースはPodなので少しわかりづらいとおもったので、複数コンテナを有するPodに対してHPA設定をかけた場合の計算ロジック(詳細版)をまとめました。
HPAのreplicaset数算出方法を整理
公式を眺める
複数コンテナを持つPodだと、「Podが利用しているリソース」はどう計算されるでしょうか。
以下、メモリについて考えてみます。
公式を参考にすると、以下のような計算式になる。 
数式をみるとウッ頭痛が...!となる人向けに少し解説します。
どちらもシンプルです。
podUsage でやっていることは、Pod内のコンテナが要求しているメモリ量の合計値と、同じくPod内コンテナが使用しているメモリ量の合計値を割ることでPod単位の使用率を求めているだけです。
scalledReplicasets は理想のreplicaset数を意味します。
計算方法は、HPAリソースで指定した使用率(averageUtilization)を分母にし、replicaset分の podUsage の総和を分子とした式を切り上げ(ceil関数)する、といったものになります。
具体例で考える
僕は式を出されても理解できないタイプなので、実際のケースに当てはめて考えてみたいと思います。
まず、
- アプリケーションPod
- Javaアプリケーションが載っているコンテナ(要求したメモリ量は 512Mi・使用中のメモリ量は360Mi)
- cloudproxyコンテナ(要求したメモリ量は32Mi・使用中のメモリ量は12Mi)
を考えてみます。
このPodの合計要求メモリ量は 512 + 36= 548Mi、使用中の合計メモリ量は 360 + 12 = 372Miとなり、使用率に変換すると 372÷548 = 67%となります。( PodUsage )。
現在のreplicaset数は2とし、どのPodも同じメモリ使用率とします。
さらにHPAで設定した期待するメモリ使用率は 60%( averageUtilization )とします。
まず分子の計算をします。replicaset数分のPodのメモリ使用率を足していくと 134%となります。
最後に 134 ÷ 60 = 2.23。小数点切り上げをするので 3つのreplicasetが必要と算出されます。
本来はPodごとに使用率は変わってきますし、メモリ以外にもCPU使用率やカスタムメトリックスなどを使った複合的なスケール基準値を設定していると多少なり複雑になってくるはずです。
Pod内で複数コンテナを運用している場合のHPAの設定方法
要約すると「複数コンテナを有するPodに対してHPA設定をかけるなら、コンテナすべてにlimits設定をかけておきましょう。でなければ正常に機能しません」という些末な話になります。
課題
HPA設定を付与したリソースを確認しようと思いGKEのコンソール画面を覗くと、
HPA cannot read metrics value
どうやらHPAの設定がうまくいっていない様子。

というのも、他のPodに付与しているHPA設定も、今回エラーを発見したものと同様の設定をかけていたので条件は同じはず…とずっと思っていたからです。
原因と対処
そこでGCPサポートに相談したところ、実は今回エラーになっていたHPA設定の対象となっていたPodはサイドカーを持っており、そのサイドカーのlimits設定がなされていなかったからでした。下記がサイドカーのlimits制限がされてない状態のときのmanifestです。
apiVersion: apps/v1
kind: Deployment
metadata:
name: hoge
namespace: hoge
spec:
replicas: 3
selector:
matchLabels:
app: hoge
tier: backend
template:
metadata:
labels:
app: hoge
tier: backend
spec:
containers:
- env:
- name: SPRING_PROFILES_ACTIVE
value: prd
image: 省略
imagePullPolicy: Always
livenessProbe:
httpGet:
path: /private/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
name: hoge
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /private/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
limits:
cpu: 500m
memory: 768Mi
requests:
cpu: 100m
memory: 512Mi
# サイドカー
- name: cloudsql-proxy
command: 省略
image: gcr.io/cloudsql-docker/gce-proxy:1.17
name: cloudsql-proxy
volumeMounts:
- mountPath: /secrets/cloudsql
name: cloudsql-instance-credentials
readOnly: true
volumes:
- name: cloudsql-instance-credentials
secret:
secretName: cloudsql-instance-credentials
メインコンテナの hoge の設定があれば大丈夫だと思っていたのが勘違いのもとでした。
Limitsを付与して実行するとうまくメトリクスを取得できていたので、課題は解決しました。
...
- name: cloudsql-proxy
command: 省略
image: gcr.io/cloudsql-docker/gce-proxy:1.17
resources:
limits:
memory: 64Mi
requests:
memory: 32Mi
...

今回はメモリに対してオートスケール設定を付与するケースだったので、メモリだけlimits制限をかけています。
DeepL opener PROのおかげで勉強効率が上がった話
先日、DeepL opener PROというchrome拡張機能が公開されたので、さっそく使ってみました。
この記事はこの拡張プラグインを使ってから1週間経った時点での感想記事になります。
DeepL opener PROとは何か
個人で契約したDeepL APIのAPIキーを入力すれば使える拡張プラグインで、選択した範囲だけ即時翻訳してくれるツールです。
これまで類似したプラグインは出ていましたが、DeepLの翻訳ページに飛ばすものが多く、体験があまりよくありませんでした。
これはそのページで即座に翻訳結果がみれるのが嬉しいところです。
嬉しかったこと
総合するとリーチできるドキュメントが増えたにつながります。細かく分解すると以下のとおり。
1. 英文を読むときの心理的コストが減った
最近ではOSSのドキュメントも有名なものだと日本語翻訳されたものが多いですが、それでもまだ英文だけのやつも多いです。構文や使われている単語は大学受験で英語をしっかりやってきたらならそこまで難しくないものですが、いかんせん疲れる。気合を入れて読むことが多い自分にとってはDeepL opener PROはかなりのサポーター君になっていて、「英文読むの疲れたら翻訳しちゃえばいいや」と思える前提をつくれたのは大きく、気兼ねなく英文記事を読む体制が作れたので、地味に嬉しいです。
2. Oreilly Safaribooksを読む速度が上がった
おそらくOreilly側が対策をしているのだろうけども、選択範囲を指定して一部翻訳するといった機能は使えないです。
ただ、ページ全体翻訳機能を使うことはでき、その場合は開いている章単位での翻訳になります。一度の翻訳で150〜200円程度かかりますが、章ごとに読むタイプにとっては1冊を読み切るためにちょうど都合がよく、わりと便利だったりしています。
インフラ系の書籍はバンバン出てるので、積読が増えていましたが、これでインプット速度をあげることができたので今後はもっと読んでいこうと思っています。
つらいこと
同じ箇所を翻訳すると再度課金対象なの?
はい。翻訳したページをリロードすると元に戻り、同じ場所をもう一度翻訳すると翻訳した文字数が加算されます。
なので、大量の文章(あるいはページ)を翻訳した場合はリロードには気をつけてください。
最低でも3130円かかる
基本料金が630円かかります。
さらに100万字ごとに2500円かかります。
なので、3130円は最低でも支払う必要があります。
例えばさきほどのOreillyの章単位での翻訳で200円かかった場合、残り2300円分の翻訳字数が残ることになります。無邪気に翻訳しまくっているとすぐに1万円くらい使っちゃいそうですね。
一応、DeepL API側の機能として、文字数制限をかけることができます。
100万字を超えたら使えなくする、通知を飛ばすといったことが可能なので、超過しそうになったら気付けるのでありがたいです。
kubevalでmanifestのフォーマットをチェック
YAMLベースのドキュメントのコードレビューって、フォーマットのズレや期待していない値かどうかの確認で不備を見逃してしまい、あとあとリリース時の kubectl apply -f manifest.yaml などでうまく実行できずに焦ることが経験上、多いです。
今回はコードレビューをする前に機械的にmanifestをバリデーションしてくれるツールである kubeval について、Macへの導入を試します。
kubevalをインストール
kubeval とはkubectlを使わずにmanifestファイルをバリデーション(規格にあった構成かどうかをチェック)するツールです。Linuxをはじめ、MacやWindows用にもパッケージが配布されており、ローカル開発環境などで利用されることが多いツールです。
今回はMacOSにインストールしていきます。
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-darwin-amd64.tar.gz tar xf kubeval-darwin-amd64.tar.gz sudo cp kubeval /usr/local/bin
インストールが済んだら
kubeval --version Version: 0.15.0 Commit: df50ea7fd4fd202458002a40a6a39ffbb3125bad Date: 2020-04-14T09:32:50Z
と、バージョンを確認し、うまく起動できたことを確認。
kubevalを使ってみる
試しにサンプルコード(serviceとdeployment)のバリデーションをしてみましょう。 検査対象はこちら。
# manifest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: adservice
spec:
selector:
matchLabels:
app: adservice
template:
metadata:
labels:
app: adservice
spec:
serviceAccountName: default
terminationGracePeriodSeconds: 5
containers:
- name: server
image: adservice
ports:
- containerPort: 9555
env:
- name: PORT
value: "9555"
# - name: DISABLE_STATS
# value: "1"
# - name: DISABLE_TRACING
# value: "1"
#- name: JAEGER_SERVICE_ADDR
# value: "jaeger-collector:14268"
resources:
requests:
cpu: 200m
memory: 180Mi
limits:
cpu: 300m
memory: 300Mi
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:9555"]
livenessProbe:
initialDelaySeconds: 20
periodSeconds: 15
exec:
command: ["/bin/grpc_health_probe", "-addr=:9555"]
---
apiVersion: v1
kind: Service
metadata:
name: adservice
spec:
type: ClusterIP
selector:
app: adservice
ports:
- name: grpc
port: 9555
targetPort: 9555
kubeval manifest.yaml PASS - adservice.yaml contains a valid Deployment (adservice) PASS - adservice.yaml contains a valid Service (adservice)
どちらのkubernetesリソースも PASS となっています。これは問題なしという意味の PASS です。問題がある場合は WARN ERR が表示されます。
試しに以下の間違いを混入させて実行してみます。
# manifest.yaml
...
apiVersion: v1
kind: Service ## インデントズレ
metadata:
name: adservice
spec:
type: ClusterIP
selector:
app: adservice
ports:
- name: grpc
port: 9555
targetPort: 9555
実行してみます。
kubeval manifest.yaml ERR - Failed to decode YAML from adservice.yaml: error converting YAML to JSON: yaml: line 2: mapping values are not allowed in this context
2行目が何かおかしいぞ、とエラーを出してくれています。
kubeval でコードをリポジトリにあげるまえにローカルで確認しておくとmanifestの品質を一定まで維持できるので、使っていきたいやつです。
ServiceとPodだけでほぼダウンタイムゼロのリリースを実現する
ラベルを使ってPodを世代管理しておくと、Serviceを更新するだけでリリース(トラフィックルーティングを切り替える)を行なうことができて便利です。
要約
- 実装
- メリット
- アプリケーションのデプロイとサービス公開の作業を分離できる
- Service(L4レイヤの設定)を切り替えるだけなので影響範囲・切り替えコストを最小化できる
- Deploymentの再applyによるダウンタイムを極力ゼロにできる
- デメリット
- 一時的にクラスタのリソースを過分に利用してしまう
- 古いバージョンのお掃除が大変
詳細
全体像
扱うKubernetesリソースが2つ(ServiceとDeployment)だけなので最初にmanifestの全体像を貼ります。
この設定では hoge-0.0.1 と hoge-0.0.2という2つのDeploymentが用意されており、現在Serviceは hoge-0.0.1に紐づけられているPod( label['version']: v0.0.1) に向けられています。
apiVersion: v1
kind: Service
metadata:
name: hoge
labels:
app: hoge
spec:
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
selector:
app: hoge
version: 0.0.1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hoge-0.0.1
labels:
app: hoge
version: 0.0.1
spec:
replicas: 10
selector:
matchLabels:
app: hoge
template:
metadata:
labels:
app: hoge
version: 0.0.1
spec:
containers:
- name: hoge
image: tacumaigei/hoge:0.0.1
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hoge-0.0.2
labels:
app: hoge
version: 0.0.2
spec:
replicas: 10
selector:
matchLabels:
app: hoge
template:
metadata:
labels:
app: hoge
version: 0.0.2
spec:
containers:
- name: hoge
image: tacumaigei/hoge:0.0.2
ports:
- containerPort: 8080
現在のPodの状況はこう(以降、すべて namespaceはdefaultとします)
kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE hoge-0.0.1 10/10 10 10 8h hoge-0.0.2 10/10 10 10 8h kubectl get pods NAME READY STATUS RESTARTS AGE hoge-0.0.1-5d45d5845d-7lqpw 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-85lxj 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-98cnl 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-b96ff 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-jx7tc 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-knjng 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-mzwrq 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-t2h84 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-wxpd6 1/1 Running 1 8h hoge-0.0.1-5d45d5845d-zgnq6 1/1 Running 1 8h hoge-0.0.2-f579c9474-49bx5 1/1 Running 1 8h hoge-0.0.2-f579c9474-7lkfz 1/1 Running 1 8h hoge-0.0.2-f579c9474-9bxd7 1/1 Running 1 8h hoge-0.0.2-f579c9474-b7qmg 1/1 Running 1 8h hoge-0.0.2-f579c9474-ctn6v 1/1 Running 1 8h hoge-0.0.2-f579c9474-jc4x6 1/1 Running 1 8h hoge-0.0.2-f579c9474-jkfrd 1/1 Running 1 8h hoge-0.0.2-f579c9474-lrj42 1/1 Running 1 8h hoge-0.0.2-f579c9474-p2427 1/1 Running 1 8h hoge-0.0.2-f579c9474-rzv6q 1/1 Running 1 8h kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hoge ClusterIP 10.96.222.54 <none> 80/TCP 8h kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10h
利用しているアプリケーション(dockerfile)は適宜ご自身のアプリケーションと思ってください。この例で利用しているアプリケーションは / にリクエストすると HOGE: Hello World! が返ってくるだけの簡易アプリケーションです(リポジトリ))。
ここで重要なことが2つあります。
- Deploymentの名前はバージョンごとに設定すること(今回は
hoge-0.0.1hoge-0.0.2としています) - ServiceのSelectorはPodに対して行われる
1.の設定は複数アプリケーションをバージョン違いで共存させるために必要です。Deploymentに紐づいているPodのlabelだけバージョンを上げてDeployment名を同じままにしておくとDeploymentを更新してしまうことになるため注意です。
2.の設定は理解に注意が必要です。ServiceはあくまでPodを認知する機構であり、Deploymentを認知するわけではないということです。Selectorの設定で Deploymentのラベルであるapp: hoge-0.0.1を設定してもこの名前のPodを探しにいってしまい、見つかりません。 なのでPodのラベルを使って、適切にPodを認知させてください。
Serviceを hoge-v0.0.2に切り替え
ここが本題。
現在 ラベル app:hoge version: 0.0.1 と設定されているPodに向けられているServiceの設定を version: 0.0.2 へ変更します。
apiVersion: v1
kind: Service
metadata:
name: hoge
labels:
app: hoge
spec:
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
selector:
app: hoge
version: 0.0.2 # 変更箇所
kubectl apply -f service.yaml
これでDeployment hoge-0.0.2 に切り替わりました。
めちゃくちゃ楽ですね。
アプリケーションのdockerimageが作成されたらdeploymentファイルをクラスタにapply、という一連の処理を自動化しておけば、リリース時にはServiceの切り替え作業だけにすることができ、かつダウンタイムがほぼゼロに収まる運用を実現できます。
検証結果
念のため、ローカルでkindを立ち上げて検証しました。
同じnamespace内にcurlコマンドを叩くためのPodを用意し、Service hoge にリクエストを流し続けている間にcurlを叩き続けた結果がこちらです。
# hogeと同じnamespace内に立てたPodの中
$ for i in {1...100000}; do curl hoge:8080/; done
HOGE: Hello,World!HOGE: Hello,World!HOGE: Hello,World!HOGE: Hello,World!HOGE: Hello,World!HOGE: Hello,World!HOGE: Hello,World!HOGE 2!!!!!!!!: Hello,World!HOGE 2!!!!!!!!: Hello,World!HOGE 2!!!!!!!!: Hello,World!HOGE 2!!!!!!!!: Hello,World!HOGE 2!!!!!!!!: Hello,World!HOGE 2!!!!!!!!:
検証した限りはリクエストの欠損なく切り替えられていることが確認できました。 これはクラスタの負荷状況やリクエスト数によっても変わるところなのでダウンタイムゼロ!と断言することはできない部分ですが、一番コスト低く新しいPodをサービス公開する手順として覚えておくとよさそうです。
