KustomizeからHelmfileに移行してmanifestを45k行削減しました

はじめに

こんにちは!アソビューSREチームの( @kassshi_dev )です。

日々サービスを運営していると、開発や運用の方法も少しずつ変化していきます。特に事業やチームの成長に伴って、以前は十分だった仕組みが少しずつ現状にフィットしなくなることもありますよね。

そこで今回は、Kubernetesのmanifest管理をKustomizeからHelmfileへ移行した事例をご紹介します!

きっかけは、事業成長によるアプリケーション数の増加に伴い、既存のmanifestの運用負荷や視認性低下が顕著になったことでした。より柔軟かつ保守しやすいmanifest管理方法の実現手段としてHelmfileを選択しました。 移行プロセスについてご紹介します!

前提

  • マネージドサービス : EKS
  • デプロイ : ArgoCD
  • ArgoCD Application数 : 155個 (1stパーティ(自前のアプリケーション) + 3rdパーティ(主にOSS))

manifest管理の歴史

アソビューではKubernetes導入以降、以下の通りmanifest管理方法の変遷を辿ってきました。 今回はフェーズ2 -> フェーズ3のお話になります。

  • フェーズ1 : 生manifest×CIOps
  • フェーズ2 : Kustomize×GitOps
  • フェーズ3 : Helmfile×GitOps

移行の背景

フェーズ2の運用について

Kustomize は非常に便利なmanifestのカスタマイズツールで、NamespaceTransformer/ReplicaCountTransformer/ImageTagTransformer/PatchTransformer/ConfigMapGeneratorなど多くの機能が用意されています。

ディレクトリ構成は base + overlays を採用し、base配下にはユースケース毎に必要なリソース郡をまとめたテンプレートを定義していました。また、Kustomizeの Exec KRM functions を利用し、Shell Scriptによるmanifest出力を実現するカスタムプラグインを実装していました。Shell Script はbase配下のテンプレートにoverlaysから渡したパラメータを埋め込みmanifestを出力します。 以下のようなイメージになります。

ディレクトリ構成

root
├── base
│   └── components
│       └── java-app
│           ├── kustomization.yaml
│           └── config.yaml
│
└── overlays
    └── staging
        └── java-app-1
            ├── kustomization.yaml
            └── generators.yaml

ファイル

# base/java-app/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - config.yaml

---
# base/java-app/config.yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          resources :
            requests:
              cpu: < requests_cpu >
              memory: < requests_memory >

# overlays/staging/java-app-1/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: staging-java-app-1

generators:
- generators.yaml

---
# overlays/staging/java-app-1/generators.yaml
apiVersion: kustomize.asoview.com/v1alpha1
kind: ResourceGenerator
metadata:
  name: resource
  annotations:
    config.kubernetes.io/function: |
      exec: { path: "resourcegenerator.sh" }
spec:
  resources:
    - path: ../../../base/components/java-app
      replacements:
        requests_cpu: 128m
        requests_memory: 128Mi

フェーズ2における課題

カスタムプラグインは Shell Script で実装しており、機能としては置換と上書きのみでした。 条件分岐でリソースの有無を判定したり、新しいリソースを作成したりする処理や、ループによるリスト構造の表現はテンプレート化しにくいものでした。さらに Shell Script が年々複雑になり、テンプレートを運用するモチベーションも下がっていました。 テンプレート機能の柔軟性がないこと、テンプレートの運用モチベーション低下により課題が多く発生しました。

PatchTransformerの多用による視認性の低下

テンプレートの運用モチベーションが低下した結果、各アプリケーションがテンプレートの外側でPatchTransformerによるreplaceやdeleteを多用するようになり、manifestの見通しが悪化してしまいました。

認知負荷の高騰

Kubernetes のリソースには依存関係がある場合があります。しかし、テンプレート側では条件分岐を使って「リソースの有無を判定して作成する」といった処理を定義できません。そのためアプリケーション側が依存関係を把握して各テンプレートの参照定義する必要性がありました。SREチーム以外のメンバーによる新規アプリケーション作成・更新の難易度がかなり高くなっていました。

テンプレートの増加

上記のように、条件によって必要となるリソースが異なる場合があるため、その条件ごとにテンプレートを作成する必要がありました。その結果、テンプレートの数が徐々に増えていきました。

ツール検討

柔軟なテンプレート機能を実現するためにHelmを採用する方針は固まっていましたが、Helmの扱い方にいくつか選択肢がありました。 以下3つの候補からPros/Consを整理してSREチーム内で議論しました。

  • Helm : Kubernetesにおけるパッケージマネージャ。運用実績が多い。
  • Helmfile : Helm Chartを宣言的にデプロイできるCLI。
  • kustomize built-in helm : KustomizeでHelmを部分的に利用できるビルトイン機能

以下の理由によりHelmfileに決定しました!

  • 宣言的デプロイができる
  • helmfile.yaml1つで複数リリースを一元管理できて視認性がよい
  • テンプレート以外のリソースも作成できる

移行前後の比較

以下が移行前後のmanifestのイメージになります。

移行前

apiVersion: kustomize.asoview.com/v1alpha1
kind: ResourceGenerator
metadata:
  name: resource
  annotations:
    config.kubernetes.io/function: |
      exec: { path: "resourcegenerator.sh" }
spec:
  replacements:
    env: production
    service_name: java-app
    eks_cluster: production
  resources:
    - path: ../../../../base/components/app-java-grpc/v1
      replacements:
        app_name: java-app
        requests_cpu: 1
        requests_memory: 256Mi
        limits_cpu: 1
        limits_memory: 256Mi
      env:
        ENV: production
      env_from:
        secret:
          - eso
    - path: ../../../../base/components/virtualservice/v1
      replacements:
        app_name: java-app
    - path: ../../../../base/components/hpa/v1
      replacements:
        app_name: java-app
        min_replicas: 1
        max_replicas: 3
    - path: ../../../../base/components/rollout-istio/v1
      replacements:
        app_name: java-app
        canary_pause_duration: 600s
    - path: ../../../../base/components/external-secret/v1
      replacements:
        app_name: java-app
      external_secret:
        parameters:
          HOGE: HOGE

移行後

namespace: production-java-app

templates:
  default:
    values:
      - basename: java-app
        env: production
        service_name: java-app
releases:
  - name: java-app
    chart: ../../../../helm/app-web/1.0.0
    inherit:
      - template: default
    values:
      - values-ci.yaml
      - source: java
        deployment:
          requests:
            cpu: 1
            memory: 256Mi
          limits:
            cpu: 1
            memory: 256Mi
          env:
            ENV: production
          env_from:
            secret:
              - es
        hpa:
          enabled: true
          min_replicas: 1
          max_replicas: 3
        service:
          grpc:
            enabled: true
        virtualservice:
          enabled: true
        canary:
          enabled: true
          canary_pause_duration: 600s
  - name: es
    chart: ../../../../helm/externalsecret/1.0.0
    inherit:
      - template: default
    values:
      - name: eso
        parameters:
          HOGE: HOGE

アプリケーション側

参照するテンプレートが減り、1つのテンプレートに対して設定値やフラグを渡すだけでよいので直感的にわかりやすくなりました。

テンプレート側

Helmテンプレートで作成したオールインワンテンプレートにより柔軟な設定が可能になりました。 以前は同じJavaのアプリケーションでもHTTPサーバで稼働する場合とgRPCサーバで稼働する場合で作成するリソースや設定が異なるため、似たような設定が存在するテンプレートが複数ありました。これらのメンテナンスコストも不要になり、また横串で全アプリケーションに設定の追加をしたい場合も非常に楽です。

移行プロセス

アプリケーションを1stパーティ(自前のアプリケーション) と3rdパーティ(主にOSS)に分類し、影響範囲がある程度把握できている1stパーティから移行作業を開始しました。 また今回の移行プロセスを円滑に進めるにあたっての要点は以下だったため、事前準備を一定実施して移行作業を開始しました。

  • Kustomize と Helmfile における出力結果manifestに差分がないことを確認できること
  • ArgoCDのプラグインをKustomize から Helmfile へ容易に切り替えられること

事前準備

  • Helmテンプレート作成
    • 共通的に必要なリソース及び条件分岐を定義して、移行作業プロセスの中でHelmテンプレートをブラッシュアップする方針としました
  • 移行前後のmanifest出力結果の差分を容易に確認できる
    • CIで出力結果の差分をPRにコメントします
  • KustomizeとHelmfileを併用可能にする
    • ArgoCDのサイドカープラグインにKustomizeとHelmfileを共存させつつ、デフォルトではKustomizeでデプロイします
  • Kustomize -> Helmfile にmanifestを変換するスクリプト作成
    • 既存のkustomization.yamlgenerators.yamlから、helmfile.yamlを作成するスクリプトを実装することで地道な変換作業の時間を大幅に短縮します

ステップ1 : [1stパーティ] 各アプリケーションのhelmfile.yamlを作成してリリースする

事前準備で作成した変換スクリプトを利用してhelmfile.yamlを作成します。出力結果のmanifestが既存のものと差分がない状態がゴールです。 PRコメントにmanifestの差分が通知されるので、レビューを円滑に進めることができました。

リリース時点ではKustomizeでデプロイされるため本番影響はありません。

ステップ2 : [1stパーティ] ArgoCDのプラグインをKustomizeからHelmfileに切り替える

アソビューではApplicationSetを利用して複数のアプリケーションを管理しています。 ApplicationSetではgeneratorsでプラグイン名を指定してtemplateにパラメータとして渡すことで、アプリケーション毎にプラグインを切り替えるようにしました。 これにより安全にロールバックも可能です。

ApplicationSetのmanifestは以下のようなイメージです。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: installs
spec:
  generators:
    - merge:
        mergeKeys:
          - path.basename
        generators:
          - matrix:
              generators:
                - git:
                    repoURL: hoge
                    revision: main
                    directories:
                      - path: >-
                          hoge/*
                - list:
                    elements:
                      - targetRevision: main
                        plugin: kustomize-plugins
          - list:
              elements:
                - path.basename: helmfile-app
                  plugin: helmfile-plugin
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        path: '{{path}}'
        plugin:
          name: '{{plugin}}' # ここでpluginを変数にすることで容易に切り替え可能

ステップ3 : [3rdパーティ] 各アプリケーションのhelmfile.yamlを作成してリリースする

各OSSが提供しているHelm Chart を見て現状と差分が極力ないように values を定義していきます。 この辺は生成AIを利用しつつ、CIで差分を確認しながら正確性と速度のバランスを取りながら進めました。

ステップ4 : [3rdパーティ] ArgoCDのプラグインをKustomizeからHelmfileに切り替える

1stパーティ同様です。

ステップ5 : Kustomizeの関連リソース・ファイルのお掃除

以下を中心に削除することができました。

  • ArgoCDのKustomizeプラグイン
  • Kustomizeのmanifestファイル
  • 独自実装のカスタムプラグイン

結果

before/after

移行によりテンプレート数は 83%削減、ファイル数は 58%削減、コード行数は 49%削減 されました。 また、移行後の構成については以下に統一しています。

  • helmfile.yaml : 参照するChart及びvaluesを定義
  • values-ci.yaml : CIで更新するimageタグなどを定義(helmfile.yamlのGitのLogを見やすくするためCIで頻繁に更新されるフィールドだけファイルを分割しています)

よかったこと

manifestの視認性向上

helmfile.yamlを見るだけで構成を把握できるようになりました

テンプレートの運用モチベーション向上

既にテンプレートのアップデートが継続的に実施されています

3rdパーティアプリケーションの設定最適化

OSSが提供しているHelm Chartに対してvaluesを定義していくプロセスにおいて、OSSの各設定への理解が深まり、不要な設定の削除や、設定の最適化まで行うことができました

丁寧な事前準備によるPRレビュー効率化

特にCIによる差分通知はPRレビュー時間を大きく短縮させました。一目瞭然で問題ないことを確認できたので非常によかったです。

こうすればよかった

3rdパーティから移行するのが良かったと思います。3rdパーティが提供しているHelm Chart は整理されていて、自前のHelmテンプレート作成時に参考にすることで初版のクオリティがもっと上がったと思います。

まとめ

Helmfileに移行してよかったです! 組織規模やプロダクトのフェーズの変遷に伴い、都度最適なツールや手段の選定を柔軟に行うことで、よりよいプラットフォームの運用を実現できると思います。

他にもCIやモノレポ運用など最適化の余地があるので、引き続きSREチームがEmbedded SREやプロダクトチームと連携しながら施策を進めていく予定です。

最後に

アソビューではより良いプロダクトを世の中に届けられるよう共に挑戦していくエンジニアを募集しています。 カジュアル面談も行っておりますので、お気軽にエントリーください! お待ちしております。 www.asoview.co.jp