blog

DeNAのエンジニアが考えていることや、担当しているサービスについて情報発信しています

2026.03.02 技術記事

Helmfile + Argo CD + Renovate で複数環境の K8s 運用を回す構成と工夫 [DeNA インフラ SRE]

by Hayato Fujita

#infrastructure #sre #kubernetes #gke #helm #helmfile #renovate

はじめに

こんにちは、IT 本部 IT 基盤部第二グループの藤田です。IT 基盤部では、組織横断で複数プロダクトのインフラ運用を担当し、基盤の安定稼働やコスト削減に取り組んでいます。

私たちのチームでは 2〜3 年ほど前から Helm + Helmfile を活用して Kubernetes(以降 K8s)を運用しています。Helm は Kubernetes のパッケージマネージャーで、アプリケーションを「Chart」として配布・インストールできる仕組みです。Helmfile は複数の Helm リリースを宣言的にまとめて管理し、環境ごとの差分を扱いやすくするためのツールです。

これらを使い、複雑な環境でもストレスが少なく管理できるようになってきたため、運用の考え方と構成をご紹介します。

方針として「すべてを自動化して DRY を極限まで追う」ことはしていません。運用でつらくなりやすいポイント(環境差分の管理、変更差分の把握、責務分界の明確化)を避けることに主眼を置いています。

前提:必要な環境

まずは全体の前提となる必要な環境と用語を説明します。

環境構成

環境構成

案件ごとに複数の環境が必要で、ここでは 1 つの独立した環境単位を sector と呼びます。さらに複数の sector をまとめる概念として env があります(例: dev / prod)。

  • sector: 1 つの独立した環境単位(例: dev1 / dev2 / dev3)。アプリケーションのバージョンや設定差分が存在する
  • env: 複数の sector を束ねる上位概念(例: dev / prod)。安定度・利用者・求める運用品質が異なる

sector は「他 sector の影響を受けない独立性」が求められます。一方でコストや運用の都合上、影響のない範囲でリソースを共有します。

実装上の対応関係は次のとおりです。

  • sector ⇔ K8s Namespace(ほぼ 1:1)
  • env ⇔ K8s クラスタ
  • クラウドアカウント ⇔ env 対応、または prod / 非 prod で分離

ディレクトリ構成

具体的にどのように環境を管理しているか、例をもとに紹介します。

ディレクトリ構成

まずリポジトリの分割方針です。更新頻度が高い非 prod 側の変更で誤って prod を触ってしまう事故を防ぐため、1 つの案件でも「prod」と「それ以外(例: dev)」でリポジトリを分けています。また env 間では構成が異なることもあるため、設定は独立させています。

1 つの env 内のディレクトリ構造を、以下 3 つの観点で説明します。

  1. helmfile とクラスタの対応関係
  2. sector の管理方法
  3. 開発チームとインフラチームの設定境界

1. helmfile とクラスタの対応関係

ディレクトリ構成1

helmfile はすべて cluster ディレクトリ配下に置き、「特定のクラスタの設定はその配下を見れば完結する」状態にしています。

またクラスタ内でも独立した helmfile を複数用意し、役割ごとに大きく次のように分けています。

  1. helmfile.yaml.gotmpl: アプリケーション設定(sector ごとに作成、後述)
  2. helmfile.cluster.yaml.gotmpl: ミドルウェアなど、クラスタごとに 1 つ必要なもの
  3. helmfile.<middleware>.yaml.gotmpl: 依存関係の都合で cluster 用にまとめにくい Custom Resource など

helmfile.yaml.gotmplhelmfile.cluster.yaml.gotmpl は、 Argo CD Config Management Plugins を用いて管理しています。一方 helmfile.<middleware>.yaml.gotmpl は更新頻度が低いため、手動で helmfile apply して反映しています。

2. sector の管理方法

ディレクトリ構成2

各 sector のアプリケーション設定を管理する helmfile.yaml.gotmpl の中身を説明します。

### helmfile.yaml.gotmpl
# sector ごとに environment を作成
environments:
  dev1:
  dev2:
  dev3:

values:
- vars/common/main.yaml.gotmpl # sector によらない共通変数ファイル
{{- if isFile ( printf "vars/sector/%s.yaml.gotmpl" .Environment.Name ) }}
- vars/sector/{{ .Environment.Name }}.yaml.gotmpl # sector ごとの変数ファイル
{{- end }}

---
releases:
- name: A # 機能 A
  namespace: {{ .Environment.Name }}
  chart: xxx
  version: 0.1.0
  values:
  - {{ toYaml .Values.A | nindent 4  }}
- name: B # 機能 B
  namespace: {{ .Environment.Name }}
  chart: yyy
  version: 0.1.0
  values:
  - {{ toYaml .Values.B | nindent 4  }}

sector は Helmfile の environments 機能で管理し、K8s 上では Namespace を分けつつ(ほぼ同一の)リソースを作成します。sector ごとの差異は、environment ごとに変数を切り替えることで吸収します。

変数は values: 以下で管理しています。1 つの変数ファイルで、この helmfile が管理するすべての Chart の値をまとめています。

変数ファイルは次の 2 種類です。

  • 共通変数ファイル(sector に依らない)
  • sector 変数ファイル(必要な sector のみ上書き)

例は以下です。

### vars/common/main.yaml.gotmpl
A:
  domain: {{ .Environment.Name }}-a.example.com
  resources:
    requests:
      memory: 1Gi
      cpu: 1
B:
  domain: {{ .Environment.Name }}-b.example.com
### vars/sector/dev3.yaml.gotmpl
A:
  resources:
    requests: # 共通ファイルから変数を上書き
      cpu: 2

共通変数ファイルには sector 間の共通値を記載します。ポイントは {{ .Environment.Name }} を使えるため、environment 名から導出できる値(例: ドメインや識別子)を共通側で表現できる点です。結果として、sector 変数ファイルが必要なのは「本質的に設定が異なるときだけ」に限定できます。

3. 開発チームとインフラチームの設定境界

私たちの体制では開発とインフラの担当が明確に分かれており、K8s マニフェストでも両者が管理する値が混在します。Deployment を例に挙げると、imageenv, args は開発チームが設定することが多い一方、それ以外の多くはインフラチームで管理します。

そのため Helm chart でも一部の値は開発チームが設定できるようにする必要があり、変数の共同管理が必要です。

共同管理にあたり、要件は次のとおりです。

  • 開発: 自分たちが設定すべき変数が分かる
  • インフラ: 開発チームの変更で、インフラ側の値が意図せず変わらない仕組みがほしい

これを実現するための設定が、ディレクトリ構成の「③」に含まれています。

ディレクトリ構成3

開発チームが編集できる変数は、インフラチームのファイルとは物理的に別のディレクトリに分離し、あらかじめ入力ルール(スキーマ)を提供しています。 このディレクトリに対する変更は、CI で自動的にスキーマ検証を行うようにしています。 スキーマは JSON Schema を用いてホワイトリスト形式で定義しているため、許可されていないインフラ関連の変数を誤って追記しようとしても、CI がエラーで弾いてくれます。 これにより、インフラ側の値が意図せず書き換えられるのをシステム的に防いでいます。

helmfile.yaml.gotmplvalues は以下のように開発ファイルの変数ファイルを読み込むように設定します。

values:
- vars/common/main.yaml.gotmpl # sector によらない共通変数ファイル
{{- if isFile ( printf "vars/sector/%s.yaml.gotmpl" .Environment.Name ) }}
- vars/sector/{{ .Environment.Name }}.yaml.gotmpl # sector ごとの変数ファイル
{{- end }}
- ../../../../<developer dir>/vars/{{ .Environment.Name }}.yaml # 開発の変数ファイル

また 1 つのリポジトリを共同管理するため、どのチームがどのパスを管理するのかを CODEOWNERS で明示しています。

ここまでの「ディレクトリ構成」の要点をまとめると、次のとおりです。

  • クラスタの設定は cluster ディレクトリ配下で完結させ、見通しをよくする
  • sector は Helmfile の environments と変数ファイルの階層(共通 → sector)で差分を表現する
  • 開発/インフラで共同管理が必要な値は、読み込み元ファイルを分け、CODEOWNERS とスキーマで責務を固定する

運用を支える工夫

差分確認 CI

紹介した構成は変数ファイルが多層になり、ぱっと見では変更影響が追いにくいことがあります。そこで変更内容の差分を機械的に確認できる CI を用意しています。

Argo CD の差分を出す方法はいくつかありますが、私たちは「すべての Helmfile environment で default branch と target branch に対して helmfile template を実行し、生成物に diff を取る」方式にしています。

HELMFILE_ENVS="dev1 dev2 dev3"
HELMFILE_NAME="helmfile.yaml.gotmpl" # 実際のファイル名に合わせて指定

# default branch
echo $HELMFILE_ENVS | tr ' ' '\n' | xargs -P 0 -I {} helmfile -e {} -f $HELMFILE_NAME template --include-crds --output-dir {}-main --output-dir-template "main/{{ .OutputDir }}/{{ .State.BaseName }}-{{ .Release.Name }}"
# target branch
echo $HELMFILE_ENVS | tr ' ' '\n' | xargs -P 0 -I {} helmfile -e {} -f $HELMFILE_NAME template --include-crds --output-dir {}-target --output-dir-template "target/{{ .OutputDir }}/{{ .State.BaseName }}-{{ .Release.Name }}"

# 差分を出力
echo $HELMFILE_ENVS | tr ' ' '\n' | xargs -P 0 -I {} sh -c "echo {} && diff -u -r -N main/{}-main target/{}-target | tee {}.txt ||':'"

単純に template した結果へ diff を取っているだけなので、実際の適用結果と差が出る場合はあります。一方で権限まわりをシンプルにでき、CI パイプラインも軽く保てるため、現在はこの形にしています。

自己管理 Helm chart の更新

管理のしやすさのため、自己管理の Helm chart は基本的に常に最新を使うようにしています。そのため Helm chart を変更した場合、差分が小さくても、それを参照している helmfile 側で version 更新が必要になります。

helmfile が複数存在し、さらに Helm chart によっては複数案件で使い回しているため、「どこで使っているか」を探して version を更新するだけでも手間になりがちです。

ここは Renovate で解消しています。Renovate は依存関係(ライブラリや各種ツールのバージョン)を自動で更新する PR を作成するツールで、helmfile もサポートしています。Renovate を定期実行して作成されたオープンな PR を Slack に通知しています。

前述の差分確認 CI に加え、Renovate では Changelog を PR に表示できます。Helm chart を OCI レジストリで扱っている場合は、Chart.yaml の sources にソースコード URL を入れておくと、そこにある CHANGELOG.md の内容を拾ってくれるため、確認コストが下がります。

Slack 通知例

Slack 通知例

これにより軽微な変更であれば PR を確認しマージするだけで安全で簡単に更新できるようになっています。

おわりに

本記事では、複数環境(env / sector)を持つ構成で、Helmfile を使って複雑さを抑えながら運用している事例をご紹介しました。 年数が経つにつれて状況は変化し、当初は「手作業でも大した手間ではない」と思っていたものが、気づくと面倒になっていることはよくあります。

状況に応じてベストな構成は変わりますが、Kubernetes における環境管理の一例として参考になれば幸いです。

最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。

recruit

DeNAでは、失敗を恐れず常に挑戦し続けるエンジニアを募集しています。