Share
Embed
突然のメモリ使用率上昇へ対応! k8sカスタムコントローラー開発事例
MIXI × ココナラのSRE改革大作戦 〜改善のその先へ〜 の資料です
https://mixi.connpass.com/event/352623/
syossan27
April 30, 2025
Transcript
32 slides
-
©MIXI 自己紹介 名前:しょっさん X(旧: Twitter)/ mixi2:@syossan27 所属:MIXI 活動: • SRE
Kaigi • SRE Magazine • ゆるSRE勉強会 -
©MIXI 何が起こったのか? 起因は一部のGraphQLクエリの修正 • 修正したGraphQLクエリでは非常に大きいレスポンスを返すように なってしまっていた • 結果、メモリ使用量が急速に肥大化 • メモリがサチることでレイテンシの悪化につながった
• k8sを利用していたため、HPAでメモリ使用率がある一定を超えたら スケールするようにしていたが、スケール数も上限に達していた -
©MIXI 前提:k8sカスタムコントローラーとは? • k8sでは様々なリソースを管理するコントローラーが動いています ◦ Node ◦ Deployment ◦ ReplicaSet
◦ CronJob ◦ etc… ※ つくって学ぶkubebuilder - カスタムコントローラーの基礎 : https://zoetrope.github.io/kubebuilder-training/introduction/basics.html -
©MIXI 前提:k8sカスタムコントローラーとは? • このコントローラーを自作することができる! • コントローラーを自作することで、独自のロジックをk8s内に持ち込むこと ができます • また、独自リソース(CR)を定義し、それを利用するためにカスタムコン トローラーを作成することもできます
• 有名なカスタムコントローラーだと ◦ cert-manager ◦ external-secrets ◦ などなど • とはいえ、きちんとk8sの内部処理を理解していかないといけないので、ち とハードルは高め -
©MIXI カスタムコントローラーを作ってみよう やりたいこと:指定したメモリ使用率に達したらPodをevictする • evict(立ち退き)とは、Podに対して終了を要請すること • 当初調べた時に、 zapier/preoomkiller-controller というカスタムコン トローラーもあったが、こちらはメモリ使用量を指定するタイプで取
り回しが悪かった • やりたいことはほぼpreoomkiller-controllerが実現していたので、こ ちらを参考にする形で実装する -
©MIXI カスタムコントローラーを作ってみよう client-goを使い、k8sクライアントを作成 • kubernetes/client-go を利用して、 k8sクライアントを作成 • シグナルハンドラー、k8s APIやmetrics
API へ接続する設定を渡して作成します • 最後に、コントローラー構造体にそれらを 入れ、監視ループを走らせます config, err := clientcmd.BuildConfigFromFlags(masterUrl, kubeconfig) if err != nil { log.Fatal(err) } clientset, err := kubernetes.NewForConfig(config) if err != nil { log.Fatal(err) } metricsClientset, err := metrics.NewForConfig(config) if err != nil { log.Fatal(err) } ctx := SetupSignalHandler() controller := NewController(ctx, clientset, metricsClientset, time.Duration(interval)*time.Second) controller.Run() -
©MIXI カスタムコントローラーを作ってみよう 監視ループを回し、対象Podを検出 • 対象となるannotationが付与されたPodを 検出します • annotationはdeploymentに以下のように設 定する ◦
pre-oom-killer.v1alpha1.k8s.io/target-container-name: "app" • ここで検出したPodで更に処理を行う podList, err := c.clientset.CoreV1().Pods("").List( c.context, metav1.ListOptions{ LabelSelector: PodLabelSelector, }, ) for _, pod := range podList.Items { podName, podNamespace := pod.ObjectMeta.Name, pod.ObjectMeta.Namespace podTargetContainer, ok := pod.ObjectMeta.Annotations[TargetContainerName] if !ok { log.WithFields(log.Fields{ "pod": podName, "namespace": podNamespace, }).Errorf("PodTargetContainerNameFetchError: %s", err) continue } // 後続処理は省略 } -
©MIXI カスタムコントローラーを作ってみよう - メモリ使用率チェック メモリ使用率をチェックし、超過でevict • 対象となるPodで設定されているevictにす るメモリ使用率を取得する • メモリの最大値・使用量を取得する
podMemoryUsageThreshold, err := resource.ParseQuantity(pod.ObjectMeta.Annotations[MemoryUsa geThresholdAnnotation]) // メモリの最大値を取得 var containerLimitsMemory *resource.Quantity for _, container := range pod.Spec.Containers { containerLimitsMemory = container.Resources.Limits.Memory() } // メモリの使用量を取得 containerMemoryUsage := &resource.Quantity{} podMetrics, err := c.metricsClientset.MetricsV1beta1().PodMetricses(podNamespac e).Get(c.context, podName, metav1.GetOptions{}) for _, containerMetrics := range podMetrics.Containers { containerMemoryUsage = containerMetrics.Usage.Memory() } -
©MIXI カスタムコントローラーを作ってみよう - メモリ使用率チェック メモリ使用率をチェックし、超過でevict • 対象となるPodで設定されているevictにす るメモリ使用率を取得する • メモリの最大値・使用量を取得する
• 最後に、メモリ使用率を取得し、超過した 場合にPodをevictさせる // メモリ使用率を算出 containerMemoryUsagePercentage := (float64(containerMemoryUsage.Value()) / float64(containerLimitsMemory.Value())) * 100 if containerMemoryUsagePercentage > float64(podMemoryUsageThreshold.Value()) { _, err := evictPod(c.context, c.clientset, podName, podNamespace, "v1", false) } -
©MIXI カスタムコントローラーを作ってみよう - メモリ使用率チェック メモリ使用率をチェックし、超過でevict • 対象となるPodで設定されているevictにす るメモリ使用率を取得する • メモリの最大値・使用量を取得する
• メモリ使用率を取得し、超過した場合にPod をevictさせる • 最後に作成したクライアントを通して evictする eviction := &policy.Eviction{ TypeMeta: metav1.TypeMeta{ APIVersion: policyGroupVersion, Kind: EvictionKind, }, ObjectMeta: metav1.ObjectMeta{ Name: podName, Namespace: podNamespace, }, DeleteOptions: deleteOptions, } err := client.PolicyV1beta1().Evictions(eviction.Namespace).Evict(ctx, eviction) -
©MIXI カスタムコントローラーを作ってみよう - 終了処理 シグナルをハンドリングし、終了させる • SIGTERMとSIGINTをハンドリングし、 キャッチしたら終了させる • シグナルハンドラを多重で呼び出さないよ
う、onlyOneSignalHandlerを作成 var onlyOneSignalHandler = make(chan struct{}) var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} func SetupSignalHandler() context.Context { close(onlyOneSignalHandler) c := make(chan os.Signal, 2) ctx, cancel := context.WithCancel(context.Background()) signal.Notify(c, shutdownSignals...) go func() { <-c cancel() <-c os.Exit(1) }() return ctx } -
©MIXI 非常にお世話になった文献 ※ 実践入門 Kubernetes カスタムコントローラーへの道 : https://amzn.asia/d/8oIrthm • カスタムコントローラーについて、日本語
で体系的にまとまった文献が少ない中、内 部構造の説明から非常に分かりやすく書か れていました • ただ、本書内で使われているコマンド等は 使えなくなっている可能性が大なので、適 宜最新の内容に変えつつ読み進めていくの がよし