Slide 1

Slide 1 text

Karpenter を活用した GitLab CI/CD ジョブ実行基盤の自動スケール 株式会社スリーシェイク 下村俊貴

Slide 2

Slide 2 text

自己紹介|下村俊貴 2 2018年〜 Web アプリ開発(PHP) 2019年〜 自社サービスの クラウドアーキテクト(AWS) 2021年〜 スリーシェイクに入社 Sreake 事業部にて SRE 支援業務(AWS メイン)

Slide 3

Slide 3 text

01 About 3-shake 02 GitLab CI/CD の概要 03 プロジェクトにおける CI/CD の課題 04 Karpenter の導入によるオートスケール化 05 導入結果 06 まとめ 目次 3

Slide 4

Slide 4 text

About 3-shake 4 01

Slide 5

Slide 5 text

話すこと ● ジョブ実行基盤の オートスケール事例 ● Karpenter の活用事例 本日お話しすること 話さないこと ● CI/CD の実践方法 ● GitLab CI/CD の使い方 5

Slide 6

Slide 6 text

   xOps Plattform DesignOps IaaS DevOps / SRE RevOps (Revenue Ops) HR(Engineer Hiring) HROps Data Engineering DataOps Security DevSecOps SecOps 事業者が抱える セキュリティリスクを無くす 本格的な脆弱性診断を 無料で手軽に セキュリファイ Security 良いエンジニアに良い案件を フリーランスエンジニアに 「今よりいい条件」を リランス HR(Engineer Hiring) あらゆるサービスを連携する ハブになる クラウド型ETL/データパイプ ラインサービスの決定版 レコナー Data Engineering 日本のSREをリード SRE総合支援からセキュリティ 対策を全方位支援 スリーク SRE スリーシェイク = xOps領域のプラットフォーマーへ 6 About 3-shake

Slide 7

Slide 7 text

 ・SRE の考え方に従って、AWS や Google Cloud を利用しているお客様サービスの   技術戦略、設計、構築、運用までをワンストップで支援するサービス  ・マイクロサービス、Kubernetes などクラウドネイティブな技術領域に強み  ・ベンダー的な役割ではなく「お客様のチームメンバー」という立ち位置で最新技術の提案から   運用支援までをトータルご支援  ・SRE チーム組成・SRE 文化の定着化・SRE 技術者の採用/育成コンサルティング等も対応 運用支援 アセスメント (パフォーマンス/セキュリティ) 構築/実装 支援 システム設計 Sreake SRE(スリーシェイクによる SRE 総合支援サービス) 技術戦略・コンサルティング 7

Slide 8

Slide 8 text

GitLab CI/CD の概要 8 02

Slide 9

Slide 9 text

● GitLab に付属する CI/CD 機能 ● リポジトリに .gitlab-ci.yml を置いて 処理を書くと,手軽に CI/CD を 実現できる。 ● GitLab Runner が CI/CD ジョブの 実行を担う。 GitLab CI/CD の簡単な説明 9 GitLab リポジトリ .gitlab-ci.yml GitLab Runner ジョブ stages: - build - test build-code-job: stage: build script: - echo "Check the ruby version, then build some Ruby project files:" - ruby -v - rake test-code-job: stage: test script: - echo "If the files are built successfully, test some files with one command:" - rake test .gitlab-ci.yml の例 https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html

Slide 10

Slide 10 text

GitLab 本体とは別のマシンに Runner をインストールできる GitLab Runner の仕組み 10 GitLab CI/CD 機能で作られた ジョブを Runner に 渡す Runner ジョブを受け取り Executor で実行 動かせる OS: ● Linux ● Windows ● macOS Executor ジョブの実行環境 ● Docker ● Docker Machine ● Kubernetes ● VirtualBox ● SSH ● Shell など Runner 登録 ジョブ割り当て ジョブ実行

Slide 11

Slide 11 text

Runner (agent) は Kubernetes クラスタ上で Deployment として動き, ジョブごとに Pod を作って実行 GitLab Runner の Kubernetes Executor 11 https://docs.gitlab.com/runner/executors/kubernetes.html

Slide 12

Slide 12 text

プロジェクトにおける CI/CD の課題 12 03

Slide 13

Slide 13 text

● 構成 ○ GitLab Community Edition を Amazon EKS クラスタ上で運用 ○ GitLab Runner も同じクラスタ上で運用 ○ 当初は固定のノード数で運用,都度手動でノードを増減 ● リポジトリ数:~500個 ● 利用テナント(チーム)数:21 ● 利用者数:~1000名 プロジェクトの概要 13

Slide 14

Slide 14 text

● 平均待ち時間:75秒(2022年9月) CI/CD の課題:キューが詰まる・待ち時間が長い 14 ジョブ数・実行時間 増加 ジョブ待ち時間も 増加

Slide 15

Slide 15 text

課題解決の方針 15 ☞ジョブ数に合わせてノードをオートスケールさせよう

Slide 16

Slide 16 text

Karpenter の導入によるオートスケール化 16 04

Slide 17

Slide 17 text

17 Karpenter の概要 04-01

Slide 18

Slide 18 text

● AWS が開発した OSS の Kubernetes ノードオートスケーラー ● Cluster Autoscaler より高速・柔軟なオートスケールを狙う ● クラウドプロバイダ非依存だが,現状 Amazon EKS のみに対応 ● Helm でインストールできる Karpenter の特徴 18 https://karpenter.sh/

Slide 19

Slide 19 text

①既存ノードのリソースに空きがあればそのままスケジュールされる。 Karpenter によるオートスケールの流れ 1/3 19 https://karpenter.sh/

Slide 20

Slide 20 text

②空きがなければ Karpenter がノードを立ち上げてスケジュールする。 Karpenter によるオートスケールの流れ 2/3 20 https://karpenter.sh/

Slide 21

Slide 21 text

③さらに利用状況に応じてノードタイプやサイズを最適化する。 Karpenter によるオートスケールの流れ 3/3 21 https://karpenter.sh/

Slide 22

Slide 22 text

Cluster Autoscaler ● Auto Scaling グループ (ASG)との紐付けが必要 ● ノードの増減は ASG の サイズを制御することで実現 Cluster Autoscaler との違い 22 Karpenter ● ASG の用意は不要 (起動テンプレートは必要) ● ノードの増減は Amazon EC2 API から直接インスタンスを 起動・停止することで実現 Cluster Autoscaler よりもスケールが速い・柔軟

Slide 23

Slide 23 text

コンテナ数を増やした場合のスケーリングは Karpenter のほうが速い。 参考:Cluster Autoscaler と Karpenter の速度比較 23 コンテナ数を1から3500にスケールアウトするまでにかかる時間の比較 https://www.vladionescu.me/posts/scaling-containers-on-aws-in-2022/

Slide 24

Slide 24 text

● ECS on Fargate:イメージはタスク定義のもの固定なので, ジョブ定義のイメージは使えない。 ● EKS on Fargate:1 Node 1 Pod のため,全ジョブにノード起動の 待機時間が発生する。 ● EKS on EC2:バルーニング(後述)すれば上記の課題を解決 できる。 その他の実行基盤の選択肢について検討 24

Slide 25

Slide 25 text

Cluster Autoscaler ● インスタンスタイプが固定 ☞ワークロードに応じて変える なら ASG が複数必要 Cluster Autoscaler との違い(続き) 25 Karpenter ● ワークロードに応じて インスタンスタイプを 自動選定 基盤運用の立場からすると どの規模のジョブを利用するか想定できないため 要求リソースに合わせてノードのスペックを決めたい

Slide 26

Slide 26 text

Cluster Autoscaler との違い(まとめ) 26 結論: 現状 EKS でノードのオートスケールを実現するなら Karpenter が第一候補

Slide 27

Slide 27 text

27 Karpenter の設定 04-02

Slide 28

Slide 28 text

カスタムリソースの役割と関係 28 Unschedulable Provisioner (AWS)NodeTemplate ノードの制約や振る舞いを指定 ● taints ● labels ● requirements ノードのテンプレートを定義 ①スケジュール待ちの Pod を Provisioner に割り当てる ● tolerations ● nodeSelector ● nodeAffinity ②テンプレートを使って 条件に合致するノードを 立ち上げる ③ Pod をスケジュール

Slide 29

Slide 29 text

設定できる項目例: ● labels, annotations, taints ● requirements: インスタンスタイプ,AZ など ● limits:リソース合計の上限 ● ttlSecondsUntilExpired: ノードの失効時間 ● ttlSecondsAfterEmpty: スケールイン判定 Provisioner でノードの制約を定義する 29 apiVersion: karpenter.sh/v1alpha5 kind: Provisioner metadata: name: gitlab-runner spec: providerRef: name: gitlab-runner labels: workload: gitlab-runner requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot"] - key: kubernetes.io/arch operator: In values: ["amd64"] - key: karpenter.k8s.aws/instance-family operator: In values: ["c6i", "m6i", "t3", "m5"] - key: karpenter.k8s.aws/instance-size operator: In values: ["large", "xlarge", "2xlarge"] limits: resources: cpu: 1000 ttlSecondsUntilExpired: 86400 # 1 Day ttlSecondsAfterEmpty: 600

Slide 30

Slide 30 text

クラウド依存 AWS の場合は AWSNodeTemplate で, 起動テンプレート相当のものを 作れる ● どんなノードを ● どこのサブネットで ● どんな設定で 起動するかを定義 NodeTemplate でノードの雛形を作る 30 apiVersion: karpenter.k8s.aws/v1alpha1 kind: AWSNodeTemplate metadata: name: gitlab-runner spec: subnetSelector: Name: "*-private" securityGroupSelector: karpenter.sh/discovery: my-cluster amiFamily: AL2 blockDeviceMappings: - deviceName: /dev/xvda ebs: volumeSize: 100Gi volumeType: gp3

Slide 31

Slide 31 text

今回はシンプルにラベル(Pod 側は nodeSelector)で指定する セレクタ設定|Karpenter によるセレクタの設定 31 apiVersion: karpenter.sh/v1alpha5 kind: Provisioner metadata: name: gitlab-runner spec: labels: workload: gitlab-runner workload = gitlab-runner のラベルが付いたノードに スケジュールされる Pod がスケール判定対象になる

Slide 32

Slide 32 text

GitLab Runner では全ジョブの Pod に nodeSelector を付与するよう 設定 ☞ nodeSelector に対応したラベルの付いたノードで Pod が起動 セレクタ設定|セレクタに合わせた GitLab Runner の設定 32 [runners.kubernetes] [runners.kubernetes.node_selector] workload = gitlab-runner GitLab Runner の config.toml(抜粋)

Slide 33

Slide 33 text

あらかじめスペース確保用の Pod (バルーン)をデプロイして少数 のノードを常時起動しておき, ジョブが発生したら入れ替える ☞ノード起動待ちを減らせる バルーニング|考え方 33 https://wdenniss.com/gke-autopilot-spare-capacity ※ Overprovisioning とも呼ばれる。 https://aws.github.io/aws-eks-best-practices/cluster-autoscaling/#overprovisioning

Slide 34

Slide 34 text

スケジュール待ちの Pod に優先度を付与できる。 Kubernetes の PriorityClass の機能 34 優先度の高いものから順に起動 優先度の低いものを退避させて 高いものを起動(「横取り」) Pending 優先度100 優先度0 優先度-10 優先度100 優先度-10 優先度0 優先度-10 優先度100 優先度0

Slide 35

Slide 35 text

バルーニング|ノードのバルーニングの設定 35 apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: gitlab-runner-balloon value: -10 preemptionPolicy: Never globalDefault: false apiVersion: apps/v1 kind: Deployment metadata: name: gitlab-runner-balloon spec: replicas: 10 template: spec: priorityClassName: gitlab-runner-balloon containers: - name: sleep-forever image: ubuntu command: ["sleep"] args: ["infinity"] nodeSelector: workload: gitlab-runner # 説明に必要な箇所のみ抜粋 低優先度クラスを指定 1. PriorityClass で優先度を 定義 2. バルーンの Pod spec で 1. の PriorityClass を指定

Slide 36

Slide 36 text

● 無負荷時の(常時起動)ノードのコスト ● ジョブ発生の頻度:バルーンよりも多いジョブが同時に 発生すると,超えた分は新規ノード起動まで待機が必要 バルーニング|レプリカ数を設定するための考慮点 36 Pending バルーン ジョブ ジョブ 発生 ノード 起動

Slide 37

Slide 37 text

導入結果 37 05

Slide 38

Slide 38 text

38 導入効果 05-01

Slide 39

Slide 39 text

● 利用者視点:ジョブの平均待ち時間が75秒から20秒に減った ● 運用者視点:手動でのノード増減作業がなくなった 利用者・運用者双方のメリット 39 20秒前後に減少

Slide 40

Slide 40 text

40 発生したトラブルと対処 05-02

Slide 41

Slide 41 text

● リソース要求によっては巨大なインスタンスタイプが選択される ○ ジョブが同時に大量に追加された ○ JVM 系などリソース要求が大きいジョブが含まれている ● 結果,32xlarge が起動してしまう トラブル1:巨大なインスタンスが立ち上がる 41

Slide 42

Slide 42 text

Provisioner を変更し,巨大なインスタンスタイプを除外するようにした 対処1-1:巨大なインスタンスタイプを除外する 42 apiVersion: karpenter.sh/v1alpha5 kind: Provisioner metadata: name: gitlab-runner spec: requirements: - key: karpenter.k8s.aws/instance-size operator: In values: ["large", "xlarge", "2xlarge"]

Slide 43

Slide 43 text

GitLab Runner の設定で,ジョブの CPU・メモリリソースの上限を設定 ● Request ○ CPU:0.5 ○ メモリ:2 GiB ● Limit ○ CPU:2 ○ メモリ:8 GiB 対処1-2:ジョブサイズの上限を設定する 43

Slide 44

Slide 44 text

● ノードが ap-northeast-1a ゾーンでのみ起動した。 ● ただちに影響はないが,AZ 障害で影響を受けやすいので, 可能なら AZ 分散させたい。 トラブル2:デフォルト設定では単一ゾーンでのみノードが立ち上がった 44

Slide 45

Slide 45 text

● バルーン Pod に topologySpreadCondition 設定を追加 ● 2ゾーンまでは分散できた。 ● 3ゾーン以上にはなかなかならなかった。 対処2(暫定):topologySpreadCondition で2ゾーンに分散する 45 topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: balloon

Slide 46

Slide 46 text

Karpenter のソースコードを読むと,料金キャパシティ最適化で 選ばれている。その制約で可能な範囲で分散されている。 https://github.com/aws/karpenter/blob/024e4a9357feea183806 72726f563e2ebbfda234/pkg/cloudprovider/instance.go#L167 コードリーディングによるゾーン選択アルゴリズムの考察 46

Slide 47

Slide 47 text

● ジョブ数は落ち着いているが,ノードのリソースが供給過剰なのに スケールインしない。 ● 原因:まばらでもジョブが発生すると,ノード停止の TTL が リセットされてしまう。 トラブル3:リソース過剰なのにスケールインしない 47 TTL: 3 TTL: 1800 ジョブ 発生 ノード ノード

Slide 48

Slide 48 text

スケールインさせたいジョブの閑散期を踏まえ, ノードの TTL を短めに設定する。 対処3-1:ジョブ間隔を踏まえてノードのタイムアウトを短くする 48 apiVersion: karpenter.sh/v1alpha5 kind: Provisioner metadata: name: gitlab-runner spec: # 省略 ttlSecondsAfterEmpty: 600

Slide 49

Slide 49 text

設定した時間が経過すればノードを停止する CI ジョブが途中終了しないよう do-not-evict アノテーションを 追加する 対処3-2:ノード停止の期限を設定する 49 apiVersion: karpenter.sh/v1alpha5 kind: Provisioner metadata: name: gitlab-runner spec: # 省略 ttlSecondsUntilExpired: 86400

Slide 50

Slide 50 text

まとめ 50 06

Slide 51

Slide 51 text

● GitLab Runner のジョブ実行基盤を,Karpenter を用いて ノードレベルでオートスケールする事例を紹介した。 ● Karpenter を用いると,ワークロードの要求リソースに基づいて 最適なノードを短時間で起動・停止できる。 ● GitLab CI/CD のようなジョブ実行基盤に限らず, ワークロードが動的に変化する要件は, Karpenter によるオートスケールと相性が良い。 まとめ 51