TL;DR
Minikube環境でFlaggerによるカナリアリリースを試してみました。
この記事ではFlaggerでカナリアリリースする方法について説明します。
この記事で扱う設定ファイルやソースコードは下記のリポジトリに存在します。
Github: https://github.com/cacapouh/flagger-example/
最速でカナリアリリースを試したい場合は、上記のリポジトリをgit cloneして、
run.sh
を実行するとMinikube環境に諸々のワークロードがデプロイされます。
最終的に出来上がるもの
以下は、この記事で取り扱うFlaggerによるリリースの構成をKubeViewで可視化したものです。
simple-app
というサンプルアプリケーションを作成し、Deployment
オブジェクトとしてデプロイし、
それをFlaggerでカナリアリリースするようにします。
セットアップ
まず、Istio, Prometheus, Flaggerをインストールします。
以下のコマンドでは、istio-system
ネームスペースにIstioとFlaggerをインストールし、
monitoring
ネームスペースにPrometheusをインストールします。
# Istio
istioctl install --set profile=default --set components.egressGateways[0].name=istio-egressgateway --set components.egressGateways[0].enabled=true -y
kubectl label namespace default istio-injection=enabled
# Prometheus
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
kubectl create ns monitoring
helm upgrade -i prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false \
--set fullnameOverride=prometheus
kubectl wait --for=condition=Ready pod --all --all-namespaces --timeout=-1s
# Flagger
helm repo add flagger https://flagger.app
helm repo update
kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml
helm upgrade -i flagger flagger/flagger \
--namespace=istio-system \
--set meshProvider=istio \
--set metricsServer=http://prometheus-prometheus.monitoring.svc.cluster.local:9090
helm upgrade -i flagger-loadtester flagger/loadtester
kubectl apply -f metrics-template.yaml
サンプルアプリケーションの実装
次にサンプルアプリケーションを用意します。
以下はサンプルアプリケーションの実装です。
bottleでHTTPサーバーを立てています。
8080ポートの/
にリクエストが来た場合は、version: 1.0.0
という文字列を返すようにしています。
8000ポートの/metrics
にリクエストが来た場合、Prometheusがパースできる形式のメトリクス情報をレスポンスするようにしています。
from bottle import route, run, response
from prometheus_client import start_http_server, Summary, generate_latest
REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')
count = 0
@route('/')
@REQUEST_TIME.time()
def index():
global count
count += 1
print("Request: {}, version: 1.0.0".format(count)) # デバッグ用にPodにログを残す
return "version: 1.0.0"
@route('/metrics')
def metrics():
response.content_type = 'text/plain; version=0.0.4; charset=utf-8'
return generate_latest()
if __name__ == '__main__':
start_http_server(8000)
run(host='0.0.0.0', port=8080)
bottle
prometheus_client
上記のPython実装の4行目でREQUEST_TIME
を定義していますが、
これは後にカナリア分析するためのメトリクス情報として使用します。
参考までに、/metrics
エンドポイントのレスポンスの一部を以下に示します。
request_processing_seconds_count 1.0
request_processing_seconds_sum 0.0035420419881120324
また、この記事では、上記の実装のバージョンを1.0.0
とし、
カナリアリリースで1.0.1
にバージョンアップします。
以下は、バージョン1.0.1
のpythonの実装です。
レスポンスするバージョン文字列を1.0.1に変更しただけです。
from bottle import route, run, response
from prometheus_client import start_http_server, Summary, generate_latest
REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')
count = 0
@route('/')
@REQUEST_TIME.time()
def index():
global count
count += 1
print("Request: {}, version: 1.0.1".format(count))
return "version: 1.0.1"
@route('/metrics')
def metrics():
response.content_type = 'text/plain; version=0.0.4; charset=utf-8'
return generate_latest()
if __name__ == '__main__':
start_http_server(8000)
run(host='0.0.0.0', port=8080)
サンプルアプリケーションのデプロイ
以下はサンプルアプリケーションのDockerfileおよびDeploymentです。
Deploymentでは、8080ポートにsimple-app
, 8000ポートにprometheus
という名前を付けています。
FROM python:3.8.18-slim
WORKDIR /app
COPY requirements.txt /app
RUN pip install -r requirements.txt
COPY main.py /app
ENTRYPOINT python -u main.py
apiVersion: apps/v1
kind: Deployment
metadata:
name: simple-app
spec:
replicas: 2
selector:
matchLabels:
app: simple-app
template:
metadata:
labels:
app: simple-app
spec:
containers:
- name: simple-app
image: simple-app:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: simple-app
containerPort: 8080
- name: prometheus
containerPort: 8000
PodMonitor
PrometheusのPodMonitorマニフェストを用意し、マニフェスト適用します。
PodMonitorとは、Prometheus Operatorで利用可能なカスタムリソースで、
Podからメトリクスを取得するための設定を定義します。
以下はPodMonitorのマニフェストです。
- デプロイ対象のネームスペースは
monitoring
です - PrometheusがPodMonitorを検知できるように
labels.release: prometheus
としています -
spec.podMetricsEndpoints
でメトリクス取得元のエンドポイントを設定しています-
path
はPython実装の通り/metrics
です -
port
は、Deploymentリソース設定で定義したprometheus
です
(つまり8000ポートです)
-
- メトリクス取得対象のネームスペースは
namespaceSelector.matchName
のところで、default
に設定しています
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: simple-app-canary
namespace: monitoring
labels:
release: prometheus
spec:
podMetricsEndpoints:
- path: /metrics
port: "prometheus"
selector:
matchLabels:
app: simple-app
namespaceSelector:
matchNames:
- default
以下は取得したメトリクス情報をPrometheusのWeb UIで可視化した結果です。
MetricsTemplate
次にMetricTemplateの設定をします。
MetricTemplateとは、Flaggerで定義されたカスタムリソースの一種で、カナリア分析に使用するメトリクスを定義するものです。
以下のマニフェストはMetricTemplateの設定です。
-
spec
-
provider
- メトリクスデータの取得元を
type: prometheus
としています- 他にも
dagdog
,newrelic
,cloudwatch
等が設定可能です
- 他にも
-
address
でmonitoring
ネームスペースのPrometheusのアドレスを指定しています
- メトリクスデータの取得元を
-
query
- 上記のPrometheusのWeb UIで示した
request_processing_seconds_sum
とrequest_processing_seconds_count
を使って、平均処理時間をメトリクスとして定義しています
- 上記のPrometheusのWeb UIで示した
-
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
name: request-processing-seconds-avg
namespace: default
spec:
provider:
address: http://prometheus-prometheus.monitoring.svc.cluster.local:9090
type: prometheus
query: |
request_processing_seconds_sum / request_processing_seconds_count
Canary
Canaryとは、Flaggerのカスタムリソースの一種で、
カナリアリリースのための設定を行うものです。
以下はCanaryのマニフェスト設定です。
-
spec
-
targetRef
のところで、カナリアリリース対象をsimple-app
のDeploymentとしています -
service
でServiceリソースの定義をしています- このCanaryのマニフェストを適用することによって、Serviceオブジェクトも自動で生成されます
-
analysis
-
metrics
- ここではMetricsTemplateの参照と、カナリア分析の閾値を設定しています
- 平均処理時間が3秒以下の場合、カナリア分析が失敗するようにしています
-
webhook
- Flaggerのロードテスターサービスにロードテストコマンドを送っています
- 2並列で2分間、1000QPSでリクエストを送ります
- リクエスト送信先は
http://simple-app-canary.default:80/
ですが、このsimple-app-canary
サービスはFlaggerの方で、カナリアリリース時に自動生成されます
-
-
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: simple-app
namespace: default
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: simple-app
service:
port: 80
targetPort: 8080
analysis:
# schedule interval (default 60s)
interval: 20s
# max number of failed metric checks before rollback
threshold: 10000
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 10
metrics:
- name: request-processing-seconds-avg
templateRef:
name: request-processing-seconds-avg
namespace: default
thresholdRange:
max: 3
interval: 5s
webhooks:
- name: load-test
url: http://flagger-loadtester.default/
timeout: 5s
metadata:
cmd: "hey -z 2m -q 1000 -c 2 http://simple-app-canary.default:80/"
カナリアリリースの実行
以下のコマンドで、既存のDeploymentのイメージを書き換えることによって、カナリアリリースをキックします。
kubectl set image deployment/simple-app simple-app=simple-app:1.0.1
以下は、イメージを書き換えた後、カナリアリリースのイベント監視をしたものです。
新しいDockerのバージョンを検知し、新しいバージョンのPodに割り振るリクエストのweightを徐々に増やしていき、weight: 50
でのカナリア分析の結果が問題なければ、新しいバージョンに完全移行するようになっています。
Halt advancement no values found for custom metric
とのWarningが出ていますが、
これは、まだメトリクスがPrometheusで取れていないタイミングでカナリア分析されたものかと思われます。
$ kubectl get events --watch --field-selector involvedObject.kind=Canary
LAST SEEN TYPE REASON OBJECT MESSAGE
0s Normal Synced canary/simple-app New revision detected! Scaling up simple-app.default
0s Normal Synced canary/simple-app Starting canary analysis for simple-app.default
0s Normal Synced canary/simple-app Advance simple-app.default canary weight 10
0s Warning Synced canary/simple-app Halt advancement no values found for custom metric: request-processing-seconds-avg: no values found
0s Warning Synced canary/simple-app Halt advancement no values found for custom metric: request-processing-seconds-avg: no values found
0s Normal Synced canary/simple-app Advance simple-app.default canary weight 20
0s Normal Synced canary/simple-app Advance simple-app.default canary weight 30
0s Normal Synced canary/simple-app Advance simple-app.default canary weight 40
0s Normal Synced canary/simple-app Advance simple-app.default canary weight 50
0s Normal Synced canary/simple-app Copying simple-app.default template spec to simple-app-primary.default
0s Normal Synced canary/simple-app Routing all traffic to primary
0s Normal Synced canary/simple-app (combined from similar events): Promotion completed! Scaling down simple-app.default
おわりに
この記事では、Flaggerを使ったカナリアリリースの基本的なセットアップ手順及び諸概念について紹介しました。
登場するワークロードやオブジェクトの数が多く、挙動もやや複雑ですが、
カナリアリリースでより安全にリリースを実現できればと思います