はじめに
現在、社内システム部門のSREチームではここ数年で導入したテクノロジーについて、暗黙知の排除、安定化、平準化といった開発効率改善の取り組みを進めています。
今回は、各Kubernetesクラスターのミドルウェア管理の改善について紹介します。
ミドルウェアってどれのこと?
現在、作ったアプリケーションをコンテナ基盤で動かす際、Podをはじめとしたアプリケーション周りのリソースをデプロイするとブラウザで見られるようになっていますが、それを支えるものとして裏で様々なミドルウェアが動作しています。
例えば、以下のようなものがあります。
ミドルウェア | 説明 |
---|---|
Traefik | ブラウザからのHTTPリクエストを受け取るL7ロードバランサー。ユーザー認証やSSL終端なども担う |
OAuth2-Proxy | Traefikがユーザー認証のために利用。Microsoft EntraIDを使ったOIDC認証を提供 |
MetalLB(オンプレのみ) | type="LoadBalancer"のServiceリソースを使用するために利用。社内のL3スイッチにBGPでルーティング情報を広告 |
Vault Secret Operator | Vaultに格納しているシークレット情報をPodに受け渡す |
Logging Operator | Podの標準出力やイベント情報をログ基盤に転送 |
他にも様々なミドルウェアが動作しており、ざっとこの倍はあると思います。
Helmについて
ほぼ全てのミドルウェアは、Helmによって管理しています。
HelmはKubernetes向けのパッケージマネージャーで、Valuesという変数ファイルをYAML形式で書いて、実行するとそれに応じたKubernetesマニフェストを生成して、クラスターに適用できるツールです。
例えば、TraefikのHelmチャートでは以下のようなValuesが定義されています。
# Default values for Traefik
# This is a YAML-formatted file.
# Declare variables to be passed into templates
image: # @schema additionalProperties: false
# -- Traefik image host registry
registry: docker.io
# -- Traefik image repository
repository: traefik
# -- defaults to appVersion
tag: # @schema type:[string, null]
# -- Traefik image pull policy
pullPolicy: IfNotPresent
# -- Add additional label to all resources
commonLabels: {}
deployment:
# -- Enable deployment
enabled: true
# -- Deployment or DaemonSet
kind: Deployment
# -- Number of pods of the deployment (only applies when kind == Deployment)
replicas: 1
# -- Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10)
revisionHistoryLimit: # @schema type:[integer, null];minimum:0
# -- Amount of time (in seconds) before Kubernetes will send the SIGKILL signal if Traefik does not shut down
terminationGracePeriodSeconds: 60
# -- The minimum number of seconds Traefik needs to be up and running before the DaemonSet/Deployment controller considers it available
minReadySeconds: 0
(以下略)
各ベンダーがチャートを作成して公開しているため、当然チャートによってValuesの定義方法はバラバラです。
Traefikでは、deployment.kindでPodをDeploymentとするかDaemonSetとするかを選べることや、deployment.replicasでレプリカ数を設定できることが分かります。
このYAMLを適宜修正して、helm installを実行すると各Kubernetesリソースの定義が生成されてクラスターに適用されます。(実際にはValuesに書いた内容がどう展開されるのか分かりづらかったりするので、使っている箇所のテンプレートと行ったり来たりしながらValuesを用意する苦行が待ってます)
これまでのミドルウェア管理
上述の通り、ミドルウェアごとにHelm Valuesを用意してクラスターに適用していくわけですが、現在管理しているクラスターは用途別にオンプレ、AWS合わせて7つあります。
Valuesの内容は、各クラスターで全く同じということはほぼなく、設定する値が変わることが大半です。
例えば、TraefikであればダッシュボードページのIngressのURLが異なるなどの違いがあります。
これまでは、各SREメンバーが自分でValuesを用意してhelm installするか、helm get valuesして現在適用されているValuesを持ってきて、変更した後helm upgradeするなどの運用を行っていました。
設定変更やチャートのバージョンアップでは、1クラスターずつ上記作業を行うため、非常に煩雑でかつ重要なコンポーネントが壊れるリスクもありました。
これからのミドルウェア管理
ほぼ手動のミドルウェア管理から脱却するために、今回 Helmfileというツールの利用を検証中です。
Helmfileは、Helmのラッパーとして動作し、複数のチャートや複数のクラスターの管理を容易にしてくれるツールです。(Terraformを使っている方は、Terragruntをイメージしてもらうと分かりやすいです)
基本的な使い方は、まず以下のようにhelmfile.yamlを用意します。
repositories:
- name: prometheus-community
url: https://prometheus-community.github.io/helm-charts
releases:
- name: prom-norbac-ubuntu
namespace: prometheus
chart: prometheus-community/prometheus
values:
- values.yaml
その後、以下のコマンドを実行すると、指定したHelmチャートがダウンロードされ、Kubernetesクラスターに適用されます。
helmfile apply
helmfile.yamlで設定できる項目は多岐に渡りますが、特筆すべきはenvironmentによる環境ごとの記述を行える点です。
例えば、以下のように記述すると、values.yamlの内容を環境ごとに変えることができます。(YAML内でGoテンプレート構文を使う場合は、拡張子を .gotmplにする必要があります。)
helmfile.yaml.gotmpl
environments:
dev:
- serverName: bar
- myChartVer: 1.5.0
prod:
- serverName: baz
- myChartVer: 1.1.0
repositories:
- name: prometheus-community
url: https://prometheus-community.github.io/helm-charts
releases:
- name: prom-norbac-ubuntu
namespace: prometheus
chart: prometheus-community/prometheus
version: {{ .Values.myChartVer }}
values:
- values.yaml.gotmpl
values.yaml.gotmpl
server:
name: {{ .Values.serverName }}
Helmfileはその多様な設定項目により、様々な管理方法ができる作りになっていますが、我々の環境では以下のような構造にしました。
.
├── bases
│ ├── environments
│ │ ├── _defaults.yaml.gotmpl
│ │ ├── dev.yaml.gotmpl
│ │ └── prod.yaml.gotmpl
│ └── environments.yaml.gotmpl
├── releases
│ ├── oauth2-proxy
│ │ ├── helmfile.yaml.gotmpl
│ │ └── values-7.7.28.yaml.gotmpl
│ └── traefik
│ ├── helmfile.yaml.gotmpl
│ └── values-32.1.1.yaml.gotmpl
└── helmfile.yaml
releasesディレクトリは各ミドルウェアごとにディレクトリを切って、 helmfile.yaml.gotmplを置き、ルートのhelmfile.yamlで子helmfileとして読み込むようにしています。
basesディレクトリは環境ごとの変数を定義できるようにしています。
bases/environments/_default.yaml.gotmplは、環境によらないデフォルト値を記述し、必要に応じて (環境).yaml.gotmplで上書く構成にしています。
以降では、順に内容を追っていきたいと思います。
./helmfile.yaml
ルートのhelmfile.yamlでは、まずbasesでhelmコマンドのオプション値を記述した helmDefaults.yamlと環境ごとの設定値を集約した environments.yaml.gotmplを読み込みます。
次の helmfilesでは、各ミドルウェアのhelmfileを子として列記しています。
./helmfile.yaml
missingFileHandler: Error
bases:
- bases/environments.yaml.gotmpl
helmfiles:
- releases/oauth2-proxy/helmfile.yaml.gotmpl
- releases/traefik/helmfile.yaml.gotmpl
./releases/traefik/helmfile.yaml.gotmpl
サンプルとしてTraefikのhelmfileです。
repositoriesでチャートのリポジトリを指定します。releasesでリポジトリ内のどのチャートをインストールするか指定します。
releases[].installedを変数にしているのは、環境によってインストールの有無をコントロールするためです。
releases[].versionを変数にしているのは、例えばチャートのアップデート時にどこかのクラスターだけ先行してアップデートするといったことをできるようにするためです。
releases[].valuesはvaluesファイルを列記しますが、上記により環境ごとにチャートバージョンが異なる可能性があるため、values-(バージョン).yaml.gotmplを読み込むようにしています。
---
bases:
- ../../bases/environments.yaml.gotmpl
---
repositories:
- name: traefik
url: ghcr.io/traefik/helm/traefik
oci: true
---
releases:
- name: traefik1
namespace: traefik
createNamespace: true
chart: traefik/traefik
installed: {{ .Values | get "traefik.installed" false }}
version: {{ .Values | get "traefik.chartVersion" | quote }}
values:
- values-{{ .Values | get "traefik.chartVersion" }}.yaml.gotmpl
./releases/traefik/values-32.1.1.yaml.gotmpl
Traefikチャートのvaluesを記述します。
サンプルとして環境ごとにPodのレプリカ数を変更できるようにしています。
deployment:
replicas: {{ .Values.traefik.deploymentReplicas }}
./bases/environments.yaml.gotmpl
各環境ごとの定義と、その環境で使用するクラスターのコンテキスト、読み込むvaluesを定義しています。 (コンテキストは我々の環境の都合で、環境変数から読み込むようにしていますが、.kube/configのコンテキストを記述してもOKです。)
{{- $envDir := print (regexReplaceAll "/releases/[^/]+$" (env "PWD") "") "/bases/environments" }}
environments:
prod:
kubeContext: {{ env "KUBE_PROD" }}
values:
- {{ $envDir }}/_defaults.yaml.gotmpl
- {{ $envDir }}/prod.yaml.gotmpl
dev:
kubeContext: {{ env "KUBE_DEV" }}
values:
- {{ $envDir }}/_defaults.yaml.gotmpl
- {{ $envDir }}/dev.yaml.gotmpl
./bases/environments/_defaults.yaml.gotmpl
各ミドルウェアにおけるデフォルト値を記述します。
oauth2-proxy:
installed: true
chartVersion: 7.7.28
traefik:
installed: true
chartVersion: 32.1.1
deploymentReplicas: 1
./bases/environments/prod.yaml.gotmpl
_defaults.yaml.gotmplの上書き内容を記述します。
ここでは本番環境のみTraefikのPodが2レプリカとなるように変更しています。
traefik:
deploymentReplicas: 2
長くなりましたが、以上が各ファイルの説明でした。
これらを実際に適用する際は、以下のコマンドを実行します。
helmfile -e dev apply
また、ミドルウェア単体でApplyすることも可能です。
helmfile -e dev -f releases/traefil/helmfile.yaml.gotmpl apply
なお、Helm Diffプラグインのラッパーとしても動作するため、以下のように実行すると、適用されるリソースの差分を確認することもできます。
helmfile -e dev diff
今後に向けて
まだ検証中の内容となりますが、今後に向けてCIによる自動化も検討中です。
マージリクエストを起点として、helmfile diffの適用差分を確認し、手動によるApplyができるようにしたいと考えています。
また、Helmfileのドキュメントには、Argo CDとの連携も記載されているため、合わせてこちらも検証していきます。