Argo CD で Secret を暗号化して反映する
- 公開日
- カテゴリ:ArgoCD
- タグ:Kubernetes,ArgoCD,学習メモ

パスワードのような秘密の値は、Secret の YAML をそのまま Git に置くと平文で残ってしまう。かといって Secret だけ kubectl create secret で手で入れると、Git にないものがクラスタにある状態になり、クラスタの中身を Git で決めるという前提が崩れる。
この記事では、Secret の値だけを sops で暗号化して Git に置き、Argo CD が Git を読むときに復号して、クラスタに平文の Secret ができる状態にする。
contents
- 前提
- Secret の置き方は 2 系統ある
- 鍵を作る
- Secret を暗号化して Git に置く
- Argo CD に復号させる
- push して Secret ができる
- 値を変える
- 鍵・暗号文・平文の全体像
- 用語メモ
- 参考 URL
前提
次の状態から始める。
- GitHub のリポジトリの
web/ディレクトリに、nginx の Deployment・ConfigMap・Service・Ingress の YAML がある - Argo CD の Application
webが、そのweb/をdefaultnamespace に入れている - Application には
automated・selfHeal: true・prune: trueが設定してあり、Git に push すれば人が操作しなくても反映される - バージョンは Argo CD v3.5.5、sops 3.13.3、age 1.2.1
$ kubectl get applications -n argocd
NAME SYNC STATUS HEALTH STATUS
web Synced Healthy
$ kubectl get secret -n default
No resources found in default namespace.
この記事で使う Argo CD の用語。
| 言葉 | 意味 |
|---|---|
| Sync | Git の YAML をクラスタに apply すること。自分で kubectl apply を実行する代わりに、Argo CD が実行する |
argocd-repo-server | Argo CD の中で、Git からファイルを取得して YAML を取り出すプログラム(argocd namespace の Pod)。クラスタと比べて apply するのは別の argocd-application-controller |
| Argo CD の画面 | ブラウザで開く管理画面。kubectl port-forward svc/argocd-server -n argocd 8080:443 を実行して https://localhost:8080 を開く |
Secret の置き方は 2 系統ある
Argo CD で Secret を扱う方法は 2 系統あり、公式ドキュメントはどちらを薦めているかを先に確認しておく。
| 方式 | 何をするか | 例 |
|---|---|---|
| クラスタ側で Secret を作る | Git には「どの Secret が必要か」だけを置き、クラスタの中のプログラムが値を取得して Secret を作る。Argo CD は値に触れない | External Secrets Operator、Sealed Secrets |
| Argo CD が YAML を作るときに復号する | 暗号化した YAML を Git に置き、Argo CD のプラグインが復号して平文の YAML を作る | sops + プラグイン、argocd-vault-plugin |
公式ドキュメントは前者を強く推奨し、後者には強く注意を促している。理由は、Argo CD が平文を持つことになる(復号した YAML は Argo CD の Redis に平文で置かれる)ことと、Secret の更新が Sync と一体になることの 2 つ。
この記事では、それでも後者の sops を使う。鍵・暗号文・復号の場所が全部手元で見えて、GitOps で秘密の値をどう扱うかの仕組みがわかること、学習用のクラスタなので上のリスクが問題にならないことが理由。実務でこの方式を使うなら、Argo CD へのアクセスを絞る前提になる。
sops と age
この記事で使う暗号化のツールは 2 つで、暗号化そのものを sops が、その鍵を age が受け持つ。
| 名前 | 何をするか | この記事での役割 |
|---|---|---|
| sops | YAML や JSON の「値だけ」を暗号化するコマンド。kind や name は平文のまま、password の値だけが ENC[...] になる。CNCF のプロジェクト | 手元で暗号化し、Argo CD の中で復号する |
| age | ファイル暗号化のための鍵の方式とコマンド。公開鍵(age1...)で暗号化し、秘密鍵(AGE-SECRET-KEY-1...)で復号する | sops が使う鍵。手元の学習ではいちばん手軽 |
sops は AWS KMS や GCP KMS のようなクラウドの鍵管理サービスも使える。その場合は秘密鍵のファイルをどこにも置かず、「クラウドに復号を頼む権限があるか」で復号できるかが決まる。
鍵を作る
sops と age を入れて、age の鍵を 1 組作る。
$ brew install sops age
鍵の置き場所は、macOS の sops が秘密鍵を探す既定の場所 ~/Library/Application Support/sops/age/keys.txt にする(Linux では ~/.config/sops/age/keys.txt)。ここに置けば、復号するときに鍵の場所を指定しなくてよい。
$ mkdir -p ~/Library/Application\ Support/sops/age
$ age-keygen -o ~/Library/Application\ Support/sops/age/keys.txt
# => Public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ cat ~/Library/Application\ Support/sops/age/keys.txt
# created: 2026-10-18T00:50:00Z
# public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AGE-SECRET-KEY-1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1 つのファイルに公開鍵と秘密鍵が両方入っている。それぞれの置き場所と使う人は次のとおり。
| 鍵 | 置く場所 | 誰が使うか |
|---|---|---|
公開鍵 age1... | Git リポジトリの .sops.yaml。見られてよい | 手元の sops が暗号化に使う |
秘密鍵 AGE-SECRET-KEY-1... | 手元の keys.txt と、クラスタの argocd namespace の Secret。Git には置かない | 手元の sops(復号の確認)と、Argo CD の中の sops(復号) |
Secret を暗号化して Git に置く
.sops.yaml
sops は、カレントディレクトリから上に向かって .sops.yaml を探し、そこに書いてある鍵で暗号化する。リポジトリのルートに置く。
creation_rules:
- encrypted_regex: ^(data|stringData)$
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
encrypted_regex: 暗号化するキーの条件。dataとstringDataの下だけを暗号化し、kindやmetadata.nameは平文のまま残すage: 暗号化に使う公開鍵
平文を書いて暗号化する
平文の Secret を web/secret.yaml に書く。
apiVersion: v1
kind: Secret
metadata:
name: web-secret
type: Opaque
stringData:
password: hello-sops
sops -e(encrypt)で暗号化したものを web/secret.enc.yaml に書き出す。
$ sops -e web/secret.yaml > web/secret.enc.yaml
$ cat web/secret.enc.yaml
apiVersion: v1
kind: Secret
metadata:
name: web-secret
type: Opaque
stringData:
password: ENC[AES256_GCM,data:MgV/CwnAS4J0uw==,iv:6DK/W6FlcsX158eix4XwMp6bQiHO2uO1FxV64/NOpbQ=,tag:j3fudPJ3RR87jbQFMaQu0g==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
(省略)
-----END AGE ENCRYPTED FILE-----
recipient: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
encrypted_regex: ^(data|stringData)$
lastmodified: "2026-10-18T00:57:35Z"
mac: ENC[AES256_GCM,data:C/KcfLHc7aR7...,type:str]
version: 3.13.3
passwordの値だけがENC[...]になり、kindとnameはそのまま残っている- 末尾の
sops:は sops が追加したもので、どの公開鍵で暗号化したか(recipient)や改ざん検知用の値(mac)が入っている。復号に使うので消さない
手元の秘密鍵で戻せることを sops -d(decrypt)で確認する。
$ sops -d web/secret.enc.yaml
apiVersion: v1
kind: Secret
metadata:
name: web-secret
type: Opaque
stringData:
password: hello-sops
平文を消す
Git に入れるのは .sops.yaml と web/secret.enc.yaml で、平文の web/secret.yaml は削除する。
$ rm web/secret.yaml
$ git status --short
?? .sops.yaml
?? web/secret.enc.yaml
この時点ではまだ push しない。push しても Argo CD は secret.enc.yaml を復号できず、apply に失敗する。復号のやり方を Argo CD に教えるのが次の節。
Argo CD に復号させる
仕組み
Argo CD で Git を読んで YAML を取り出すのは argocd-repo-server。そのままではディレクトリの YAML をそのまま読む。argocd-repo-server には Config Management Plugin(CMP)という仕組みがあり、「このディレクトリの YAML は、このコマンドの出力を使う」と差し替えられる。プラグインは、argocd-repo-server の Pod に同居する 2 つ目のコンテナ(sidecar)として動く。
今回のプラグイン sops が決めるのは 2 つ。
| 設定 | 内容 |
|---|---|
discover | ディレクトリに *.enc.yaml があれば、このプラグインの担当にする |
generate | ディレクトリの *.yaml を順に読み、*.enc.yaml は sops -d で復号、それ以外は cat で出力する。その出力を Argo CD に渡す |
入れるものは 3 つ。
ここまで、クラスタの中身は Git に置いて Argo CD に反映させてきたが、この 3 つは Git に置かず、kubectl のコマンドで直接クラスタに入れる。本来の GitOps の形から外れている点なので、理由を先に書いておく。
- 書き換える相手が Argo CD 自身(
argocd-repo-server)で、この記事の環境では Argo CD 本体をkubectl applyで入れたままにしていて、Argo CD 自身を指す Application を作っていない。Git に YAML を置いても、それを反映する Application がない - 実務では、Argo CD 自身も Argo CD に管理させることが多い。公式の
install.yamlに sidecar の追加を Kustomize で重ねたものを Git に置き、それを指す Application を作る。そうすれば、以下のkubectlは鍵の Secret を作る 1 回を除いて不要になる - この記事では、Argo CD 本体の Git 管理と Kustomize が別の話になるため、そこには踏み込まず
kubectlで入れる。学習用の手順であり、実務の形ではない
| 入れるもの | 何か |
|---|---|
Secret sops-age | 秘密鍵。sidecar にファイルとしてマウントする |
ConfigMap sops-plugin | プラグインの設定(plugin.yaml) |
argocd-repo-server の変更 | sidecar コンテナ sops と、上の 2 つをマウントする volume を追加する |
秘密鍵を Secret にする
$ kubectl create secret generic sops-age -n argocd --from-file=keys.txt="$HOME/Library/Application Support/sops/age/keys.txt"
# => secret/sops-age created
この Secret は、Argo CD 自身が復号に使う鍵なので Git には置けない。鍵を Git に置いたら暗号化の意味がなくなる。この 1 つだけは手で入れる(実務では、クラスタを構築する手順に組み込み、鍵の元はパスワード管理ツールやクラウドの秘密保管サービスに置く)。
プラグインの設定を ConfigMap にする
apiVersion: v1
kind: ConfigMap
metadata:
name: sops-plugin
namespace: argocd
data:
plugin.yaml: |
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: sops
spec:
discover:
fileName: "*.enc.yaml"
generate:
command: [sh, -c]
args:
- |
set -e
for f in *.yaml; do
case "$f" in
*.enc.yaml) sops -d "$f" ;;
*) cat "$f" ;;
esac
printf '\n---\n'
done
ConfigManagementPlugin は Kubernetes のリソースではなく、sidecar の中に置く設定ファイルの書式。apply するのは、それを包んだ ConfigMap のほう。
$ kubectl apply -f plugin-configmap.yaml
# => configmap/sops-plugin created
repo-server に sidecar を追加する
Deployment argocd-repo-server に、sidecar コンテナと volume を追加する patch。
spec:
template:
spec:
containers:
- name: sops
image: ghcr.io/getsops/sops:v3.13.3-alpine
command: [/var/run/argocd/argocd-cmp-server]
securityContext:
runAsNonRoot: true
runAsUser: 999
env:
- name: SOPS_AGE_KEY_FILE
value: /sops-age/keys.txt
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
- mountPath: /home/argocd/cmp-server/plugins
name: plugins
- mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: plugin.yaml
name: sops-plugin
- mountPath: /tmp
name: sops-tmp
- mountPath: /sops-age
name: sops-age
readOnly: true
volumes:
- name: sops-plugin
configMap:
name: sops-plugin
- name: sops-tmp
emptyDir: {}
- name: sops-age
secret:
secretName: sops-age
image: sops 公式の image(alpine 版)。sops を入れる以外に image を自作しないcommand: Argo CD の決まり。argocd-cmp-serverは Argo CD がプラグインと通信するための小さなサーバーで、repo-server の volumevar-filesの中にある。sidecar はこれを起動し、generateを頼まれたときにスクリプトを実行するrunAsUser: 999と、var-files・plugins・/tmpのマウントは公式ドキュメントの指定どおり。pluginsは repo-server と共有していて、ここにプラグインのソケットができるSOPS_AGE_KEY_FILE: sops が秘密鍵を読む場所。Secretsops-ageを/sops-age/keys.txtにマウントして、そこを指す
kubectl patch は、クラスタにあるリソースの一部だけを書き換えるコマンド。kubectl apply のように定義全体を渡すのではなく、追加したい部分だけを書いた YAML(--patch-file)を既存の定義に重ねる。containers と volumes は名前で突き合わされるので、既存の argocd-repo-server コンテナは残り、sops コンテナと volume が追加される。
$ kubectl patch deployment argocd-repo-server -n argocd --patch-file repo-server-sidecar.yaml
# => deployment.apps/argocd-repo-server patched
$ kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-repo-server
NAME READY STATUS RESTARTS AGE
argocd-repo-server-84d54f897c-jqmzj 2/2 Running 0 27s
$ kubectl logs -n argocd deploy/argocd-repo-server -c sops
{"level":"info","msg":"ArgoCD ConfigManagementPlugin Server is starting", ... "version":"v3.5.5"}
{"level":"info","msg":"argocd-cmp-server v3.5.5 serving on /home/argocd/cmp-server/plugins/sops.sock", ...}
Pod のコンテナが argocd-repo-server と sops の 2 つになり(2/2)、sidecar のログにプラグイン sops が登録されたことが出る。この時点では web/ に *.enc.yaml がまだないので、Application web は何も変わらない。
push して Secret ができる
Deployment に Secret を読ませる
Secret ができただけでは何も使わないので、Deployment の Pod にファイルとして置く。web/deployment.yaml の volumeMounts と volumes に 1 つずつ追加する。
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
- name: secret
mountPath: /etc/web-secret
readOnly: true
volumes:
- name: html
configMap:
name: web-html
- name: secret
secret:
secretName: web-secret
Secret web-secret の password が、コンテナの /etc/web-secret/password というファイルになる。
push する
git status に平文の web/secret.yaml がないことを確認してから push する。Application web の YAML は変えない。
$ git status --short
M web/deployment.yaml
?? .sops.yaml
?? web/secret.enc.yaml
$ git add .sops.yaml web
$ git commit -m "Add encrypted secret"
$ git push
Argo CD は GitHub からファイルを定期的に取得する。既定では 2〜3 分に 1 回で、設定で変えられる。(すぐに反映したいときは、Argo CD の画面の REFRESH ボタンを押す)
push 後に Argo CD の中で起きることは次のとおり。
argocd-repo-serverが GitHub からweb/を取得する- ディレクトリの種類を判定する。Argo CD はプラグインの
discoverを最初に試すので、web/にsecret.enc.yamlがあることでプラグインsopsの担当になる - sidecar の中で
generateが動き、secret.enc.yamlだけをsops -dで復号して、5 ファイル分の YAML をargocd-application-controllerに渡す argocd-application-controllerがクラスタと比べて apply する。Secretweb-secretができ、Deployment が変わったので Pod が入れ替わる
$ kubectl get applications -n argocd
NAME SYNC STATUS HEALTH STATUS
web Synced Healthy
$ kubectl get secret,pods -n default
NAME TYPE DATA AGE
secret/web-secret Opaque 1 68s
NAME READY STATUS RESTARTS AGE
pod/web-7dd4f56c74-8zwh6 1/1 Running 0 68s
クラスタの Secret には平文(base64)で入っていて、Pod の中からも読める。
$ kubectl get secret web-secret -n default -o jsonpath='{.data.password}' | base64 -d; echo
# => hello-sops
$ kubectl exec deploy/web -n default -- cat /etc/web-secret/password; echo
# => hello-sops
GitHub にあるのは ENC[...]、クラスタにあるのは hello-sops。間で復号したのは argocd-repo-server の sidecar。
Argo CD の画面で Application web を開くと、Secret web-secret が追加されている。

Secret を開いて LIVE MANIFEST を見ると、data の値は ++++++++ に伏せられている。Argo CD は Secret の値を画面に表示しない。

値を変える
暗号化したファイルは、エディタで ENC[...] の部分を書き換えると壊れる。値を変えるときは sops に任せる。sops set は、復号・値の差し替え・再暗号化を 1 回で行う。
$ sops set web/secret.enc.yaml '["stringData"]["password"]' '"hello-again"'
$ sops -d web/secret.enc.yaml | grep password
password: hello-again
$ git diff --stat
web/secret.enc.yaml | 6 +++---
$ git commit -am "Change password"
$ git push
git diff に出るのは ENC[...]・mac・lastmodified の変化だけで、新しい値は Git のどこにも出ない。(エディタで直したいときは sops edit web/secret.enc.yaml を使う。復号した内容がエディタで開き、保存すると再暗号化される)
push が反映されると、クラスタの Secret が新しい値になる。
$ kubectl get secret web-secret -n default -o jsonpath='{.data.password}' | base64 -d; echo
# => hello-again
Deployment の template は変わっていないので Pod は入れ替わらず、Pod の中のファイル /etc/web-secret/password は、kubelet が差し替えるまで少し遅れて(1 分ほど)新しい値になる。
鍵・暗号文・平文の全体像
| もの | どこにあるか | 誰が読むか |
|---|---|---|
公開鍵 age1... | Git の .sops.yaml | 手元の sops(暗号化するとき) |
秘密鍵 AGE-SECRET-KEY-1... | 手元の keys.txt、クラスタの Secret sops-age(argocd namespace) | 手元の sops(-d、set)、sidecar の sops(Argo CD の復号) |
暗号文 ENC[...] | Git の web/secret.enc.yaml | sidecar の sops |
平文 hello-again | クラスタの Secret web-secret(default namespace)、Pod の /etc/web-secret/password。Argo CD の Redis にも置かれる(公式ドキュメントが注意している点) | nginx の Pod |
自分が git push したあと、Argo CD が Secret を作った状態。(図の角が二重の箱は動くプログラム、それ以外の箱は etcd に保存されたデータ)
用語メモ
| 用語 | 意味 |
|---|---|
| sops | YAML や JSON の値だけを暗号化するコマンド。Secrets OPerationS の略 |
| age | ファイル暗号化のための鍵の方式とコマンド。公開鍵で暗号化し、秘密鍵で復号する |
| Config Management Plugin | Argo CD の argocd-repo-server に、Git から YAML を作る処理を追加する仕組み。sidecar コンテナとして動く |
| sidecar | 同じ Pod に同居させる 2 つ目のコンテナ。主役のコンテナを変えずに機能を追加するときに使う |
まとめ
- Secret の値だけを sops で暗号化すれば、
kindやnameは読める状態のまま Git に置ける。暗号化は公開鍵、復号は秘密鍵 - Argo CD に復号させるには、
argocd-repo-serverに sops の sidecar を Config Management Plugin として追加し、秘密鍵を Secret としてマウントする。discoverで担当するディレクトリを決め、generateで復号済みの YAML を作る - Argo CD の公式ドキュメントは、クラスタ側で Secret を作る方式(External Secrets Operator、Sealed Secrets)を推奨している。Argo CD の中で復号する方式は、Argo CD が平文を持つことを理解したうえで使う
参考 URL
- [official] Secret Management - Argo CD
- [official] Config Management Plugins - Argo CD
- [official] SOPS Documentation
- [official] age - GitHub
- [official] Secret | Kubernetes
- [official] サイドカーコンテナ | Kubernetes

