Helmfile + Argo CD + Renovate による複数環境のKubernetes運用の構成と工夫 [DeNA インフラ SRE]
DeNAはHelmfileとArgo CD、Renovateを組み合わせた構成により、複数環境(sector/env)のKubernetes運用における環境差分管理と自動化のバランスを取る実用的なパターンを提示している。
キーポイント
環境概念の定義と分離
独立した開発単位を'sector'、上位概念を'env'と定義し、K8s Namespaceやクラスタ、クラウドアカウントとの対応関係を明確にしている。
リポジトリ分割による事故防止
非prodとprodでリポジトリを分けることで、更新頻度の高い変更が誤って本番環境に影響を与えるリスクを物理的に隔離している。
DRY極限化からの戦略的撤退
すべてを自動化・一元化するのではなく、環境差分の管理や責務分界といった運用上の「つらいポイント」を避けることを優先する設計思想を採用している。
ツールチェーンの統合運用
Helmfileで宣言的設定を管理し、Argo CDで同期、Renovateで依存関係更新を行うことで、安定した運用フローを構築している。
クラスタごとの Helmfile 分離
特定のクラスタの設定は cluster ディレクトリ配下で完結し、アプリケーション設定、ミドルウェア、カスタムリソースごとに独立した helmfile ファイルを役割別に用意している。
更新頻度に応じた管理手法の使い分け
アプリケーション設定とクラスタ共通設定は Argo CD の Config Management Plugins で自動化管理する一方、更新頻度が低いミドルウェア関連は手動の helmfile apply で反映している。
Sectorごとの環境管理と変数分離
Helmfileのenvironments機能でSectorを管理し、共通変数ファイルとSector固有の変数ファイルを組み合わせることで、環境ごとの差異を最小限に抑えつつ柔軟な設定を実現している。
影響分析・編集コメントを表示
影響分析
この記事は、大規模なインフラ運用における「自動化の落とし穴」を指摘し、実務的な解決策を示している。技術的には革新的ではないものの、多くの組織が直面する環境差分管理の問題に対し、リポジトリ分割とツール連携という具体的で再現可能なパターンを提供しており、SREやインフラエンジニアにとって高い実用価値を持つ。
編集コメント
過度な自動化への警鐘として、運用負荷を軽減するための「意図的な複雑さ(リポジトリ分割など)」の導入は、成熟したインフラ組織では重要な判断基準となる。
はじめに こんにちは、IT 本部 IT 基盤部第二グループの藤田です。IT 基盤部では、組織横断で複数プロダクトのインフラ運用を担当し、基盤の安定稼働やコスト削減に取り組んでいます。
私たちのチームでは 2〜3 年ほど前から Helm と Helmfile を活用して Kubernetes(以降 K8s)を運用しています。Helm は Kubernetes のパッケージマネージャーで、アプリケーションを「Chart」として配布・インストールできる仕組みです。Helmfile は複数の Helm リリースを宣言的にまとめて管理し、環境ごとの差分を扱いやすくするためのツールです。
原文を表示
はじめに
こんにちは、IT 本部 IT 基盤部第二グループの藤田です。IT 基盤部では、組織横断で複数プロダクトのインフラ運用を担当し、基盤の安定稼働やコスト削減に取り組んでいます。
私たちのチームでは 2〜3 年ほど前から
+
を活用して 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 つの観点で説明します。
- helmfile とクラスタの対応関係
- sector の管理方法
- 開発チームとインフラチームの設定境界
1. helmfile とクラスタの対応関係
helmfile はすべて cluster ディレクトリ配下に置き、「特定のクラスタの設定はその配下を見れば完結する」状態にしています。
またクラスタ内でも独立した helmfile を複数用意し、役割ごとに大きく次のように分けています。
- helmfile.yaml.gotmpl: アプリケーション設定(sector ごとに作成、後述)
- helmfile.cluster.yaml.gotmpl: ミドルウェアなど、クラスタごとに 1 つ必要なもの
- helmfile.<middleware>.yaml.gotmpl: 依存関係の都合で cluster 用にまとめにくい Custom Resource など
helmfile.yaml.gotmpl と helmfile.cluster.yaml.gotmpl は、
で
を用いて管理しています。一方 helmfile.<middleware>.yaml.gotmpl は更新頻度が低いため、手動で helmfile apply して反映しています。
2. sector の管理方法
各 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 を例に挙げると、image や env, args は開発チームが設定することが多い一方、それ以外の多くはインフラチームで管理します。
そのため Helm chart でも一部の値は開発チームが設定できるようにする必要があり、変数の共同管理が必要です。
共同管理にあたり、要件は次のとおりです。
- 開発: 自分たちが設定すべき変数が分かる
- インフラ: 開発チームの変更で、インフラ側の値が意図せず変わらない仕組みがほしい
これを実現するための設定が、ディレクトリ構成の「③」に含まれています。
開発チームが編集できる変数は、インフラチームのファイルとは物理的に別のディレクトリに分離し、あらかじめ入力ルール(スキーマ)を提供しています。
このディレクトリに対する変更は、CI で自動的にスキーマ検証を行うようにしています。
スキーマは JSON Schema を用いてホワイトリスト形式で定義しているため、許可されていないインフラ関連の変数を誤って追記しようとしても、CI がエラーで弾いてくれます。
これにより、インフラ側の値が意図せず書き換えられるのをシステム的に防いでいます。
helmfile.yaml.gotmpl の values は以下のように開発ファイルの変数ファイルを読み込むように設定します。
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 の内容を拾ってくれるため、確認コストが下がります。

これにより軽微な変更であれば PR を確認しマージするだけで安全で簡単に更新できるようになっています。
おわりに
本記事では、複数環境(env / sector)を持つ構成で、Helmfile を使って複雑さを抑えながら運用している事例をご紹介しました。
年数が経つにつれて状況は変化し、当初は「手作業でも大した手間ではない」と思っていたものが、気づくと面倒になっていることはよくあります。
状況に応じてベストな構成は変わりますが、Kubernetes における環境管理の一例として参考になれば幸いです。
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み