Chatwork Creator's Note

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

ビジネスチャット「Chatwork」のエンジニアとデザイナーのブログです。

読者になる

2023年度版!Chatwork流Kubernetesの運用方法

こんにちは。SRE部の桝谷@hnchn87です。
この記事は、 Chatworkのカレンダー | Advent Calendar 2023 - Qiita の 9日目です。

2023年12月6日に行われた「耐障害性向上・パフォーマンス改善・運用負荷軽減をどう実現する? 事業を支えるSREのノウハウを共有」の中で「2023年度版!Chatwork流Kubernetesの運用方法」というタイトルでお話しをさせていただきました。
https://enechange-meetup.connpass.com/event/301585/
当日は時間の関係上、詳しくお話しできなかったので、発表資料を見ていただきつつ、補足情報をこちらに記載していこうと思います。

発表資料は以下を参照ください。

balloon✖️ClusterAutoScaler

balloon Podのmanifestは以下のような感じです。(簡略版)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: balloon
  namespace: default
spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: node.chatwork.io/role
                    operator: In
                    values:
                      - worker
                  - key: topology.kubernetes.io/zone
                    operator: In
                    values:
                      - ap-northeast-1a
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app.kubernetes.io/instance
                    operator: In
                    values:
                      - balloon
              topologyKey: kubernetes.io/hostname
      containers:
        - image: 'registry.k8s.io/pause:3.9'
          imagePullPolicy: IfNotPresent
          name: balloon-pause
          resources:
            limits:
              cpu: 100m
              memory: 512Mi
            requests:
              cpu: 100m
              memory: 512Mi
      priorityClassName: low
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: default
      serviceAccountName: default
      terminationGracePeriodSeconds: 30
      tolerations:
        - effect: NoSchedule
          key: node.chatwork.io/role
          operator: Equal
          value: worker

registry.k8s.io/pauseというイメージを使用して、待機しておくだけのballoon Podを起動しています。
上記のmanifestは適当な値にしていますが、実際は「Chatwork」AppのPodの2.5倍のCPUリソースを確保して起動しています。

priorityClassName: "low"と指定しているのですが、弊社では独自にpriorityのlawを以下のように定義しています。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: law
value: -5
globalDefault: false

このようにvalueを-5に設定したpriorityを指定することによってballoon Podの優先度がその他のPodよりも下がるため、「Chatwork」AppのPodを起動するための余剰がNodeにない場合は、このballoon Podがkillされるようになります。

これによって、CAでNodeをスケールアウトする時間を待つことなくHPAによってPodをスケールアウトすることができ、サービスが不安定になることなくサービスの起動・停止ができるようにしています。

何でKarpenterを使わないのか

弊社では2022年に一度Karpenter(ver.0.5.0)を検証しました。
当時の大きな目的は以下の通りです。

  • KubernetesにはCAという機能があるが、ノードがスケールアウトするまでに少し時間がかかってしまい、その期間サービスが不安定になってしまう
  • AWSは提供するCAツールであるKarpenterがGAされたので良さそうなら導入したい

Karpenterの特徴は資料にも記載をしていますが、

  • ASGを使用せずにEC2のAPIを直接実行することで起動する
  • K8s Schedulerを使わずに起動したEC2にKarpernterがPodを配置する

これによって、CAのオーバーヘッドを少しでも減らして、Nodeの起動の時間だけに削減するという点です。
その代わりにEKSの設定をeksctl経由で行っている場合、launch templateに記載されているK8sの設定を全てKarpenterの設定に記載する必要があり、CAとは共存できないようになっています。

ということで、実際に検証をしてみました。
手順としては、

  1. インストール. 公式のGetting Startedを参照しつつ、helmで導入しました。
  2. provisionerの準備
    Nodeの設定をprovisionerという名前のCustom Resourceで定義する必要があります。
    複数定義することが可能で、AZを意識した構成になっている場合は以下のようにAZ毎に定義する必要があります。(簡略版)
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: karpenter-1a
spec:
  ttlSecondsAfterEmpty: 30
  labels:
    node.karpenter.io/role: "worker"
    node.karpenter.io/network: "private"
    node.karpenter.io/purchaseType: "spot"
    aws.amazon.com/purchaseType: "spot"  # Requirements that constrain the parameters of provisioned nodes.
  # These requirements are combined with pod.spec.affinity.nodeAffinity rules.
  # Operators { In, NotIn } are supported to enable including or excluding values
  requirements:
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values:
        - t2.micro
    - key: "topology.kubernetes.io/zone"
      operator: In
      values: ["ap-northeast-1a"]
    - key: karpenter.sh/capacity-type
      operator: In
      values:
      - spot
    - key: kubernetes.io/arch
      operator: In
      values:
      - amd64
  # These fields vary per cloud provider, see your cloud provider specific documentation
  provider:
    instanceProfile: sandbox-eks-nodegroup-worker
    kind: AWS
    launchTemplate: eksctl-nodegroup-spot-worker-1a-private
    subnetSelector:
      kubernetes.io/cluster/eks-karpenter: 'shared'
    securityGroupSelector:
      Name: sg-000000000000

実際に使用してみると起動時間は30s程度短縮されました。
しかし、

  • 当時eksctlを使用してEKSの構築を行っていたため、launch templateとprovisionerのダブルメンテが必要になってしまい管理が大変になる *1
  • Pod Affinityに対応していなかったので、reauiredなPod Affinityを付与しているアプリケーションがデプロイできない
  • Node起動が30s短縮されたといえども数分はかかってしまい、アグレッシブにNodeの増減が発生する「Chatwork」のサービスとしては1分程度まで短縮されて欲しかった

という点から、Karpenterを使用はせず前述したballoon✖️ClusterAutoScalerの仕組みで運用しています。

まとめ

発表の中で詳しくお話しできなかった点についてご紹介しました。
その他の部分については以下に記載する参考記事をぜひご覧になってみてください!

参考

creators-note.chatwork.com 新入社員は見た!ChatworkにおけるEKSの運用と取り組み creators-note.chatwork.com KubernetesとTerraformのセキュリティ/ガバナンス向上委員会 with OPA

*1:もちろんeksctlでも予めlaunch templateの指定はできますが、eksctlで作成できるものはeksctlで作成する、という方針で、launch templateの作成自体をeksctlで作成していたため