Helm Chart Release / release-chart (push) Successful in 7s
phpfpm の smtp.existingMsmtprcSecret が「references non-existent secret key: msmtprc」でマウント失敗する件の対処。renderMsmtprc が opt-in で忘れやすかった。 - renderMsmtprc を既定 true に(明示 false のときだけ抑止) - smtp.tlsVerify(既定 true)を追加。false で msmtprc に tls_certcheck off values.schema.json の smtp にも tlsVerify を追加 - 派生キー SMTP_TLS / SMTP_SSL / SMTP_TLS_VERIFY / SMTP_AUTH を Secret に追加 - from 未設定時に msmtprc へ空の from 行を出さないよう修正 - values.yaml / examples / README を更新。0.1.2。
383 lines
21 KiB
Markdown
383 lines
21 KiB
Markdown
# secret-manager
|
||
|
||
クラスター内の複数プロジェクトで横断的に使う **認証情報(Secrets)を一元管理** する Helm チャートです。
|
||
|
||
外部メールの SMTP/IMAP 設定、外部 API の認証キー、その他の汎用資格情報を 1 箇所(この
|
||
チャートの `values.yaml`)で定義し、指定した **複数のネームスペースへ同一の Kubernetes
|
||
Secret として配布** します。
|
||
|
||
> **設計目標** — 認証情報の変更が発生したら、このチャートの values を更新して
|
||
> `helm upgrade` するだけで、クラスター上の全プロジェクトに反映される。
|
||
|
||
- Gitea リポジトリ: `ssh://git@192.168.9.65/helmchart/secret-manager`
|
||
- Helm リポジトリ: `https://git.cafepieters.com/api/packages/helmchart/helm`
|
||
|
||
---
|
||
|
||
## 目次
|
||
|
||
- [なぜ必要か](#なぜ必要か)
|
||
- [仕組み](#仕組み)
|
||
- [セキュリティモデル](#セキュリティモデル)
|
||
- [インストール](#インストール)
|
||
- [values リファレンス](#values-リファレンス)
|
||
- [distribution — 配布先](#distribution--配布先)
|
||
- [common — 共通設定](#common--共通設定)
|
||
- [mail — 外部メール接続情報](#mail--外部メール接続情報)
|
||
- [api — API 認証情報](#api--api-認証情報)
|
||
- [generic — 汎用 Secret](#generic--汎用-secret)
|
||
- [生成される Secret の構造](#生成される-secret-の構造)
|
||
- [他プロジェクトからの参照方法](#他プロジェクトからの参照方法)
|
||
- [代表的なメールプロバイダの設定値](#代表的なメールプロバイダの設定値)
|
||
- [変更の反映(伝播)とライフサイクル](#変更の反映伝播とライフサイクル)
|
||
- [自動化ワークフロー(Gitea Actions)](#自動化ワークフローgitea-actions)
|
||
- [reflector / Reloader 連携](#reflector--reloader-連携)
|
||
- [トラブルシューティング](#トラブルシューティング)
|
||
|
||
---
|
||
|
||
## なぜ必要か
|
||
|
||
Kubernetes の Secret は **ネームスペース単位** です。同じ SMTP パスワードや API キーを
|
||
10 個のプロジェクトで使う場合、通常は 10 個のネームスペースそれぞれに Secret を作り、
|
||
変更のたびに 10 箇所を更新することになります。
|
||
|
||
このチャートは「認証情報の定義」を 1 リポジトリに集約し、配布はチャートに任せます。
|
||
パスワードローテーションやエンドポイント変更は **このチャートの 1 回の更新** で全体に
|
||
行き渡ります。
|
||
|
||
## 仕組み
|
||
|
||
```
|
||
値の定義(1箇所)
|
||
┌──────────────────────────────┐
|
||
│ secret-manager / values.yaml│
|
||
│ mail.providers.gmail │
|
||
│ api.credentials.stripe │
|
||
│ generic.secrets.registry │
|
||
└───────────────┬──────────────┘
|
||
│ helm upgrade
|
||
┌───────────────┼───────────────┐
|
||
▼ ▼ ▼
|
||
ns: web ns: batch ns: ml
|
||
Secret cred-gmail Secret cred-... Secret cred-...
|
||
Secret cred-stripe ...
|
||
```
|
||
|
||
- 1 つの論理エントリ(`mail.providers.gmail` など)から、配布先ネームスペースの数だけ
|
||
**同名の Secret** が生成されます(`common.namePrefix` + エントリ名)。
|
||
- 配布方式は 2 通り。既定は **Helm による複製**(外部依存なし)。オプションで
|
||
**kubernetes-reflector による自動同期** も選べます。
|
||
|
||
## セキュリティモデル
|
||
|
||
**この `values.yaml` には平文の資格情報を書かないでください。Git にコミットしないでください。**
|
||
|
||
Kubernetes の Secret は etcd 上では base64 で「エンコード」されているだけで「暗号化」では
|
||
ありません。同様に Helm values に平文で書けば、それが Git・CI ログ・Helm リリース履歴
|
||
(`helm get values`)に残ります。実値は次のいずれかで注入します。
|
||
|
||
| 方法 | 用途 | 例 |
|
||
|---|---|---|
|
||
| **CI から `--set` / `--set-file`** | Gitea Actions が Gitea Secrets を注入(推奨) | `--set mail.providers.gmail.smtp.password="$GMAIL_APP_PASSWORD"` |
|
||
| **SOPS 暗号化 values** | 暗号化したまま Git 管理したい場合(`helm secrets` プラグイン) | `helm secrets upgrade -f secrets.enc.yaml ...` |
|
||
| **git 管理外の values ファイル** | 手元運用 | `-f secrets.values.yaml`(`.gitignore` 済み) |
|
||
|
||
追加の推奨:
|
||
|
||
- **etcd の保存時暗号化(EncryptionConfiguration)** をクラスターで有効化する。
|
||
- 配布先を必要なネームスペースだけに絞る(`distribution.namespaces` を最小化、または
|
||
`reflector.namespaceRegex` で限定)。`default` への配布は避ける。
|
||
- 変更頻度が低い資格情報は `common.immutable: true` にして誤更新を防ぐ。
|
||
- Secret を参照する RBAC(`get secrets`)を各ネームスペースで最小権限に保つ。
|
||
- このチャートは Secret 以外のリソース(Deployment 等)を一切作りません。監査が容易です。
|
||
|
||
## インストール
|
||
|
||
```bash
|
||
# Helm リポジトリ経由
|
||
helm repo add cafepieters https://git.cafepieters.com/api/packages/helmchart/helm
|
||
helm repo update
|
||
helm upgrade --install secrets cafepieters/secret-manager \
|
||
-n secret-manager --create-namespace \
|
||
-f my-secrets.values.yaml
|
||
```
|
||
|
||
```bash
|
||
# ローカルのチャートディレクトリから
|
||
helm upgrade --install secrets ./secret-manager \
|
||
-n secret-manager --create-namespace \
|
||
-f examples/mail-providers.values.yaml \
|
||
--set mail.providers.gmail.smtp.password="$GMAIL_APP_PASSWORD" \
|
||
--set mail.providers.m365.smtp.password="$M365_PASSWORD"
|
||
```
|
||
|
||
> 配布先ネームスペース(`distribution.namespaces`)は **事前に存在している必要があります**。
|
||
|
||
## values リファレンス
|
||
|
||
### distribution — 配布先
|
||
|
||
| キー | 型 | 既定 | 説明 |
|
||
|---|---|---|---|
|
||
| `distribution.namespaces` | `[]string` | `["default"]` | 同一 Secret を複製配置するネームスペース。リストから外して `helm upgrade` すると、そのコピーは削除される。 |
|
||
| `distribution.includeReleaseNamespace` | `bool` | `true` | リリース先ネームスペース(`-n` の値)も配布対象へ自動追加する。 |
|
||
| `distribution.reflector.enabled` | `bool` | `false` | kubernetes-reflector による自動同期を使う。有効時、Secret はリリース先のみに作成し reflector アノテーションを付与する。 |
|
||
| `distribution.reflector.namespaceRegex` | `string` | `".*"` | 同期先ネームスペースを絞る正規表現。例 `"prod-.*\|shared-services"`。 |
|
||
| `distribution.reloader.enabled` | `bool` | `false` | 各 Secret に `reloader.stakater.com/match: "true"` を付与。参照側 Deployment に `reloader.stakater.com/auto: "true"` があれば Secret 変更時に自動ローリング再起動される。 |
|
||
|
||
### common — 共通設定
|
||
|
||
| キー | 型 | 既定 | 説明 |
|
||
|---|---|---|---|
|
||
| `common.namePrefix` | `string` | `"cred-"` | 生成される Secret 名のプレフィックス。`cred-` + `gmail` → `cred-gmail`。 |
|
||
| `common.labels` | `map` | `{}` | 全 Secret に付与する追加ラベル。 |
|
||
| `common.annotations` | `map` | `{}` | 全 Secret に付与する追加アノテーション。 |
|
||
| `common.immutable` | `bool` | `false` | Secret を `immutable: true` にする。値変更には一度 `kubectl delete secret` が必要。 |
|
||
|
||
### mail — 外部メール接続情報
|
||
|
||
`mail.providers.<名前>` ごとに 1 つの Secret を生成します。**パスワードだけでなく
|
||
SMTP/IMAP/POP3 のサーバ情報も一元管理** できます。
|
||
|
||
| キー | 型 | 必須 | 説明 |
|
||
|---|---|---|---|
|
||
| `enabled` | `bool` | ✓ | `true` で生成対象。 |
|
||
| `smtp.host` | `string` | ✓ | SMTP サーバホスト名。 |
|
||
| `smtp.port` | `int` | | 既定 `587`。 |
|
||
| `smtp.security` | `string` | | `none` / `starttls`(既定) / `tls`。 |
|
||
| `smtp.username` | `string` | | 認証ユーザー名。空なら認証なし。 |
|
||
| `smtp.password` | `string` | | 認証パスワード。`--set` / SOPS で注入。 |
|
||
| `smtp.from` | `string` | | 既定送信元アドレス。省略時は `username`。 |
|
||
| `smtp.fromName` | `string` | | 送信者表示名。 |
|
||
| `smtp.replyTo` | `string` | | Reply-To アドレス。 |
|
||
| `smtp.authMethod` | `string` | | `plain`(既定) / `login` / `cram-md5`。 |
|
||
| `smtp.tlsVerify` | `bool` | | 既定 `true`。`false` で TLS 証明書検証を無効化(社内リレー・自己署名向け。生成される `msmtprc` に `tls_certcheck off` が入る)。 |
|
||
| `imap.host` ほか | | | 受信 (IMAP) 情報。`host` があれば `IMAP_*` キーを追加。`username`/`password` 省略時は SMTP の値を流用。既定ポート `993`。 |
|
||
| `pop3.host` ほか | | | 受信 (POP3) 情報。同上。既定ポート `995`。 |
|
||
| `renderMsmtprc` | `bool` | | **既定 `true`**。Secret に完成済み `msmtprc`(msmtp / PHP sendmail 用)を含める。IMAP だけ使う等で不要なら `false`。 |
|
||
| `namespacesOverride` | `[]string` | | このエントリだけ別のネームスペースへ配布する。 |
|
||
| `annotations` | `map` | | このエントリの Secret だけに付ける追加アノテーション。 |
|
||
|
||
### api — API 認証情報
|
||
|
||
`api.credentials.<名前>` ごとに 1 つの Opaque Secret を生成します。`data` 配下のキーが
|
||
そのまま Secret のキーになります(`envFrom` でまとめて注入しやすい形)。
|
||
|
||
| キー | 型 | 必須 | 説明 |
|
||
|---|---|---|---|
|
||
| `enabled` | `bool` | ✓ | `true` で生成対象。 |
|
||
| `data` | `map` | ✓ | 1 つ以上のキー/値。値は文字列化される。 |
|
||
| `namespacesOverride` | `[]string` | | 配布先の上書き。 |
|
||
| `annotations` | `map` | | 追加アノテーション。 |
|
||
|
||
### generic — 汎用 Secret
|
||
|
||
`generic.secrets.<名前>` ごとに 1 つの Secret を生成します。**任意の Secret type** を
|
||
指定できます(`kubernetes.io/dockerconfigjson`、`kubernetes.io/tls`、
|
||
`kubernetes.io/basic-auth` など)。
|
||
|
||
| キー | 型 | 必須 | 説明 |
|
||
|---|---|---|---|
|
||
| `enabled` | `bool` | ✓ | `true` で生成対象。 |
|
||
| `type` | `string` | | Secret の type。既定 `Opaque`。 |
|
||
| `data` | `map` | ✓ | 1 つ以上のキー/値。`kubernetes.io/tls` なら `tls.crt` / `tls.key` を入れる。 |
|
||
| `namespacesOverride` | `[]string` | | 配布先の上書き。 |
|
||
| `annotations` | `map` | | 追加アノテーション。 |
|
||
|
||
## 生成される Secret の構造
|
||
|
||
### mail エントリ(例: `cred-gmail`)
|
||
|
||
```
|
||
SMTP_HOST=smtp.gmail.com
|
||
SMTP_PORT=587
|
||
SMTP_SECURITY=starttls
|
||
SMTP_TLS=true # security=starttls のとき true(派生フラグ)
|
||
SMTP_SSL=false # security=tls(暗黙TLS)のとき true
|
||
SMTP_TLS_VERIFY=true # smtp.tlsVerify の値
|
||
SMTP_USERNAME=no-reply@example.com
|
||
SMTP_PASSWORD=********
|
||
SMTP_AUTH=true # username があれば true
|
||
SMTP_FROM=no-reply@example.com
|
||
SMTP_FROM_NAME=Example
|
||
SMTP_REPLY_TO=
|
||
SMTP_AUTH_METHOD=login
|
||
IMAP_HOST=imap.gmail.com # imap 定義時のみ
|
||
IMAP_PORT=993
|
||
IMAP_SECURITY=tls
|
||
IMAP_USERNAME=no-reply@example.com
|
||
IMAP_PASSWORD=********
|
||
smtp.env # ↑ 全キーを `export KEY="value"` にした source 可能なファイル
|
||
export SMTP_HOST="smtp.gmail.com"
|
||
...
|
||
msmtprc # 完成済み msmtp 設定(既定で常に含む。renderMsmtprc: false で抑止)
|
||
```
|
||
|
||
### api / generic エントリ
|
||
|
||
`data` に書いたキーがそのまま Secret のキーになります。
|
||
|
||
## 他プロジェクトからの参照方法
|
||
|
||
配布先ネームスペースにいる Deployment から、通常の Secret として参照します。
|
||
**このチャートに依存関係を張る必要はありません**(Secret が既にそのネームスペースにある)。
|
||
|
||
```yaml
|
||
# まとめて環境変数へ(api / generic 向き)
|
||
envFrom:
|
||
- secretRef:
|
||
name: cred-stripe
|
||
|
||
# 個別キーを参照(mail 向き)
|
||
env:
|
||
- name: SMTP_HOST
|
||
valueFrom: { secretKeyRef: { name: cred-gmail, key: SMTP_HOST } }
|
||
- name: SMTP_PASSWORD
|
||
valueFrom: { secretKeyRef: { name: cred-gmail, key: SMTP_PASSWORD } }
|
||
|
||
# env ファイル / msmtprc をファイルとしてマウント
|
||
volumes:
|
||
- name: smtp
|
||
secret:
|
||
secretName: cred-gmail
|
||
items:
|
||
- { key: smtp.env, path: smtp.env }
|
||
- { key: msmtprc, path: msmtprc, mode: 0600 }
|
||
volumeMounts:
|
||
- { name: smtp, mountPath: /etc/smtp, readOnly: true }
|
||
```
|
||
|
||
```sh
|
||
# コンテナ起動時に env ファイルを読み込む例
|
||
set -a; . /etc/smtp/smtp.env; set +a
|
||
```
|
||
|
||
`helmchart/phpfpm` チャートと組み合わせる場合は、phpfpm 側の `phpEnv` や既存の SMTP
|
||
セクションに、ここで配布した `cred-*` Secret を `secretKeyRef` で渡してください。
|
||
|
||
## 代表的なメールプロバイダの設定値
|
||
|
||
| プロバイダ | SMTP host | port | security | username | 備考 |
|
||
|---|---|---|---|---|---|
|
||
| Gmail / Google Workspace | `smtp.gmail.com` | 587 | starttls | メールアドレス | 2 段階認証 + **アプリ パスワード**が必要 |
|
||
| Microsoft 365 | `smtp.office365.com` | 587 | starttls | メールアドレス | SMTP AUTH の有効化が必要な場合あり |
|
||
| SendGrid | `smtp.sendgrid.net` | 587 | starttls | `apikey`(固定) | password が API キー本体 |
|
||
| Amazon SES | `email-smtp.<region>.amazonaws.com` | 587 | starttls | SES SMTP 認証情報 | IAM キーとは別物。要リージョン |
|
||
| Mailgun | `smtp.mailgun.org` | 587 | starttls | `postmaster@mg.<domain>` | |
|
||
| 社内 SMTP リレー | クラスター内 Service 等 | 25 | none | (なし) | 認証・TLS なし構成 |
|
||
|
||
IMAP 受信の代表値: Gmail `imap.gmail.com:993/tls`、M365 `outlook.office365.com:993/tls`。
|
||
|
||
そのまま使えるサンプル: [`examples/mail-providers.values.yaml`](examples/mail-providers.values.yaml)、
|
||
[`examples/api-and-generic.values.yaml`](examples/api-and-generic.values.yaml)、
|
||
[`examples/reflector.values.yaml`](examples/reflector.values.yaml)。
|
||
|
||
## 変更の反映(伝播)とライフサイクル
|
||
|
||
- **値の変更** — values を更新して `helm upgrade` すると、全配布先の Secret が同時に
|
||
更新されます。実行中の Pod の環境変数は**再起動するまで変わりません**。ファイル
|
||
マウント(`smtp.env` 等)は数十秒〜で更新されます。自動再起動が必要なら
|
||
`distribution.reloader.enabled: true` + 参照側の Reloader アノテーションを使います。
|
||
- **配布先の追加** — `distribution.namespaces` に足して `helm upgrade`。
|
||
- **配布先の削除** — `distribution.namespaces` から外して `helm upgrade` すると、
|
||
そのネームスペースのコピーは Helm が削除します。
|
||
- **エントリの削除** — `enabled: false` にするか、エントリごと削除して `helm upgrade`。
|
||
全ネームスペースから当該 Secret が削除されます。
|
||
- **アンインストール** — `helm uninstall` で、このチャートが作った全ネームスペースの
|
||
Secret が削除されます。
|
||
|
||
## reflector / Reloader 連携
|
||
|
||
### kubernetes-reflector(配布の自動化)
|
||
|
||
ネームスペースが頻繁に増減する環境では、`distribution.namespaces` の列挙が手間になります。
|
||
[emberstack/kubernetes-reflector](https://github.com/emberstack/kubernetes-reflector) を
|
||
クラスターに導入し、`distribution.reflector.enabled: true` にすると、Secret はリリース先
|
||
ネームスペースにのみ作られ、`namespaceRegex` に一致する全ネームスペースへ reflector が
|
||
複製・追従します。
|
||
|
||
```yaml
|
||
distribution:
|
||
namespaces: []
|
||
reflector:
|
||
enabled: true
|
||
namespaceRegex: "prod-.*|shared-services"
|
||
```
|
||
|
||
### stakater/Reloader(参照側の自動再起動)
|
||
|
||
[stakater/Reloader](https://github.com/stakater/Reloader) 導入済みなら
|
||
`distribution.reloader.enabled: true` で各 Secret に `reloader.stakater.com/match: "true"`
|
||
が付きます。参照側 Deployment に `reloader.stakater.com/auto: "true"` を付けておくと、
|
||
Secret 更新時に自動でローリング再起動されます。
|
||
|
||
## 自動化ワークフロー(Gitea Actions)
|
||
|
||
`.gitea/workflows/helm-release.yaml` の 1 本のみ。`main` への push(`Chart.yaml` /
|
||
`values.yaml` / `values.schema.json` / `templates/**` 変更時)と手動実行で発火し、
|
||
`helm lint --strict` のあと、次の 3 つの成果物を **それぞれ独立に冪等チェック**して、
|
||
不足しているものだけを作る:
|
||
|
||
1. **Package Registry** — `Chart.yaml` の `version` が未登録なら `.tgz` を発行
|
||
2. **Git タグ `v<version>`** — リモートに無ければ作成して push
|
||
3. **Gitea リリース** — 同名リリースが無ければ作成(`.tgz` 添付)
|
||
|
||
3 つすべて揃っていれば何もしない(README・examples だけの変更ではリリースされない)。
|
||
独立チェックなので、いずれかのステップだけ失敗しても次の push で不足分だけが
|
||
再実行されて自己修復する。
|
||
|
||
> **上流イメージの週次自動追従は行わない** — 他チャート(phpfpm・n8n 等)は
|
||
> `image-update-and-release.yaml` で Docker Hub の最新イメージを毎週チェックしバージョンを
|
||
> 自動採番するが、secret-manager は Secret を生成するだけで追従対象の上流イメージが
|
||
> 無いため、このワークフロー自体を持たない。バージョンは常に手動で `Chart.yaml` を更新する。
|
||
|
||
新しいバージョンを出す手順: `Chart.yaml` の `version` と `appVersion` を上げて
|
||
`main` に push するだけ。あとは `helm-release.yaml` がリリースする。
|
||
|
||
必要な Gitea Secrets: `GITEA_TOKEN`(リリース・タグ)、`REGISTRY_USER` /
|
||
`REGISTRY_TOKEN`(Package Registry)。
|
||
|
||
## トラブルシューティング
|
||
|
||
| 症状 | 原因 / 対処 |
|
||
|---|---|
|
||
| `namespaces "xxx" not found` で install 失敗 | 配布先ネームスペースは事前作成が必要。`kubectl create namespace xxx`。 |
|
||
| `helm upgrade` しても Pod の挙動が変わらない | Pod の環境変数は再起動まで不変。Pod を再起動するか Reloader 連携を使う。 |
|
||
| reflector 有効なのに他 ns に複製されない | reflector コントローラー未導入 / `namespaceRegex` 不一致 / 対象 ns がまだ無い。 |
|
||
| `cannot convert int64 to string` 等の型エラー | 通常は発生しない(テンプレートで文字列化済み)。values の該当箇所を確認し Issue へ。 |
|
||
| Secret に平文が見える | Secret は設計上、参照側から平文で読める。RBAC で `get secrets` を最小化し、etcd 保存時暗号化を有効化する。 |
|
||
| `helm lint` で `metadata.namespace` 警告 | 意図的(クロス ns 配布)。エラーではない。 |
|
||
|
||
---
|
||
|
||
## 変更履歴
|
||
|
||
各リリースの詳細は `CLAUDE.md` の「チャート改修履歴」を参照。
|
||
|
||
### v0.1.2(2026-09-08)
|
||
|
||
- **`renderMsmtprc` の既定を `true` に変更**。`mail.providers.<名前>` を定義すれば
|
||
`msmtprc` キーが常に生成されるようになった(従来は明示 `true` が必要で、忘れると
|
||
phpfpm 等の `smtp.existingMsmtprcSecret` が `references non-existent secret key: msmtprc`
|
||
でマウント失敗した)。不要なら `renderMsmtprc: false`。
|
||
- `smtp.tlsVerify`(既定 `true`)を追加。`false` で `msmtprc` に `tls_certcheck off` を出力。
|
||
- 派生キー `SMTP_TLS` / `SMTP_SSL` / `SMTP_TLS_VERIFY` / `SMTP_AUTH` を Secret に追加
|
||
(フレームワーク側が 3 値 enum を解釈しなくて済むよう真偽値文字列で提供)。
|
||
|
||
### v0.1.1(2026-09-08)
|
||
|
||
- `values.schema.json` の `$schema` 行を削除。新しめの JSON Schema バリデータを内蔵する
|
||
デプロイ環境(Helm / Argo CD / Flux 等)で
|
||
`no schema with key or ref "https://json-schema.org/draft-07/schema#"` エラーが出て
|
||
デプロイできない問題を修正。`helm lint --strict` の検証内容は不変。
|
||
|
||
### v0.1.0(2026-08-31)
|
||
|
||
- 初版。`distribution`(namespaces 複製 / reflector / reloader)、`common`、
|
||
`mail.providers`(SMTP/IMAP/POP3 + `smtp.env` + 任意 `msmtprc`)、`api.credentials`、
|
||
`generic.secrets`(任意 type)を実装。`values.schema.json`・`examples/` を同梱。
|
||
- CI: `helm-release.yaml`(push 時・未公開バージョンのみリリース)を追加。
|
||
Package Registry 発行 + Git タグ + Gitea リリースまで実施。上流イメージが無いため
|
||
週次の自動更新ワークフローは持たない。
|