refactor: reorganize GitOps control plane
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# ADR 0001: Internal Gitea is canonical
|
||||
|
||||
Status: accepted
|
||||
|
||||
The internal repository
|
||||
`https://git.learn.hyeonworks.com/donghyeon.kang/project-gitops` is the only
|
||||
writable deployment source.
|
||||
|
||||
Argo CD and Gitea Actions use this URL. A GitHub copy may exist only as a
|
||||
read-only mirror with monitored replication; it must never be an independent
|
||||
deployment branch. GHCR remains an image registry and does not require GitHub
|
||||
to host the GitOps source.
|
||||
@@ -0,0 +1,24 @@
|
||||
# ADR 0002: Terraform state ownership
|
||||
|
||||
Status: accepted
|
||||
|
||||
Terraform은 VM/네트워크뿐 아니라 provider가 제공되는 Vault API 객체도
|
||||
관리할 수 있다. 현재 저장소의 Terraform 범위는 Vault API이고 실제
|
||||
machine provisioning은 provider가 확정될 때 별도 root로 추가한다.
|
||||
|
||||
`dev-k3s`는 두 state만 사용한다.
|
||||
|
||||
- `vault-core`: mounts, auth backends, policies, Kubernetes/JWT roles,
|
||||
application Transit key
|
||||
- `vault-database`: PostgreSQL connection과 dynamic roles
|
||||
|
||||
resource/API path 하나는 한 state에만 속한다. state는 암호화, versioning,
|
||||
access control, locking이 가능한 remote backend에 저장한다.
|
||||
|
||||
`vault-core`는 privilege-escalation 가능한 객체를 포함하므로 제한된
|
||||
관리자 실행만 허용한다. `vault-database`는 core가 생성한
|
||||
`vault-database-automation-dev` 정책의 short-lived identity로 실행한다.
|
||||
|
||||
Secret payload는 Terraform resource/data source로 관리하지 않는다.
|
||||
필수 credential은 ephemeral variable과 provider write-only argument를
|
||||
통해서만 apply에 전달한다.
|
||||
@@ -0,0 +1,25 @@
|
||||
# ADR 0003: Vault topology
|
||||
|
||||
Status: accepted for dev, production decision pending
|
||||
|
||||
동일한 단일 노드 K3s 안의 두 Vault는 failure domain을 분리하지 못하면서
|
||||
초기화, Transit credential, rotation과 staged apply 절차를 추가했다.
|
||||
따라서 `dev-k3s`는 단일 self-hosted Vault로 단순화한다.
|
||||
|
||||
Dev profile:
|
||||
|
||||
- single-node integrated Raft
|
||||
- Shamir 1-of-1 init/unseal
|
||||
- TLS 미적용
|
||||
- 명시적 backup/recovery runbook
|
||||
|
||||
이 구성은 production에 사용할 수 없다. production은 다음 중 하나를
|
||||
선택해야 한다.
|
||||
|
||||
- managed Vault
|
||||
- workload cluster 밖의 독립 HA Vault
|
||||
- 최소 3-node integrated-Raft + TLS + KMS/HSM auto-unseal + PDB,
|
||||
anti-affinity와 정기 restore exercise
|
||||
|
||||
production Vault가 결정되기 전에는 production manifest와 Terraform root를
|
||||
만들지 않는다.
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR 0004: Single Argo CD root
|
||||
|
||||
Status: accepted
|
||||
|
||||
Argo CD 설치 후 `bootstrap/argocd/root-application.yaml` 하나만 seed한다.
|
||||
root는 `clusters/dev-k3s`의 AppProject와 모든 child Application을 소유한다.
|
||||
|
||||
반복 `kubectl apply`와 foundation/platform/application별 root wrapper는
|
||||
제거한다. routine deployment는 Git merge만으로 시작한다.
|
||||
|
||||
Child Application의 sync wave는 객체 생성 순서를 가독성 있게 표현하지만
|
||||
서로 다른 Application의 readiness dependency로 간주하지 않는다.
|
||||
Workload와 hook은 Vault/DB가 늦게 준비되는 상황을 retry할 수 있어야 한다.
|
||||
|
||||
Root가 child Application을 prune하거나 삭제하려면 확인이 필요하다.
|
||||
shared resource 소유권 충돌은 sync를 실패시킨다. 현재 규모에서는 명시적
|
||||
Application을 사용하고 두 번째 클러스터가 생길 때 ApplicationSet을
|
||||
검토한다.
|
||||
@@ -0,0 +1,17 @@
|
||||
# ADR 0005: Cluster-first repository layout
|
||||
|
||||
Status: accepted
|
||||
|
||||
현재는 하나의 platform 팀, 하나의 dev cluster와 소수 workload를 가지므로
|
||||
GitOps configuration monorepo를 유지한다. application source repository와
|
||||
deployment configuration repository는 분리한다.
|
||||
|
||||
- `platform/`, `workloads/`: 환경 중립 base
|
||||
- `clusters/<cluster>/manifests`: cluster-specific final composition
|
||||
- `clusters/<cluster>/applications`: Argo reconciliation inventory
|
||||
- `iac/terraform`: Kubernetes manifest와 분리된 external API IaC
|
||||
- `bootstrap`: controller가 존재하기 전의 최소 seed
|
||||
|
||||
production 접근권한, 소유 팀, Terraform backend 또는 release cadence가
|
||||
실제로 갈라질 때 platform GitOps, workload GitOps, IaC repo 분리를
|
||||
재검토한다. 존재하지 않는 환경의 skeleton은 유지하지 않는다.
|
||||
@@ -0,0 +1,33 @@
|
||||
# ADR 0006: Gateway API first, Istio deferred
|
||||
|
||||
Status: accepted
|
||||
|
||||
현재 단일 노드 K3s와 auth/api 중심 workload에는 service mesh 운영 비용을
|
||||
정당화할 mTLS identity, L7 authorization, canary traffic policy 또는
|
||||
multi-team 요구가 없다. 이번 개편에는 Istio를 설치하지 않는다.
|
||||
|
||||
선행 작업:
|
||||
|
||||
1. Traefik Gateway API provider와 GatewayClass 검증
|
||||
2. Ingress를 Gateway/HTTPRoute로 이관
|
||||
3. north-south TLS
|
||||
4. 내부 호출의 ingress hairpin 제거
|
||||
5. Vault/PostgreSQL native TLS
|
||||
6. NetworkPolicy regression test와 observability/SLO
|
||||
|
||||
Istio 요구가 실제화되면 sidecar가 아니라 ambient mode로 제한 pilot한다.
|
||||
초기 범위는 api-server와 auth-server이며 Vault, Vault injector,
|
||||
PostgreSQL은 제외한다. ztunnel L4부터 시작하고 L7 정책이 필요할 때만
|
||||
waypoint를 추가한다.
|
||||
|
||||
다음 기능 요구 중 두 개 이상과 운영 선행조건이 모두 충족될 때 ADR을
|
||||
재검토한다.
|
||||
|
||||
- ServiceAccount identity 기반 east-west mTLS
|
||||
- path/JWT 기반 L7 authorization
|
||||
- canary traffic split/retry/timeout/outlier detection
|
||||
- 지속적인 서비스·namespace·팀 증가
|
||||
- application instrumentation만으로 해결하기 어려운 장애 분석
|
||||
|
||||
현재 Ingress를 즉시 제거하지 않는다. TLS, DNS, GatewayClass 계약이
|
||||
확정되기 전 가상의 Gateway 설정을 배포하지 않기 위함이다.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Argo CD layout
|
||||
|
||||
`bootstrap/argocd/root-application.yaml`이 유일한 수동 seed입니다. 이
|
||||
Application은 `clusters/dev-k3s`를 source로 사용하고 다음 리소스를
|
||||
소유합니다.
|
||||
|
||||
```text
|
||||
clusters/dev-k3s
|
||||
├── projects
|
||||
└── applications
|
||||
├── foundation
|
||||
│ ├── sealed-secrets
|
||||
│ ├── vault
|
||||
│ └── vault-agent-injector
|
||||
├── platform
|
||||
│ └── auth-system
|
||||
└── workloads
|
||||
├── auth-server
|
||||
└── api-server
|
||||
```
|
||||
|
||||
AppProject는 root sync wave `-10`, foundation은 `0~1`, platform은 `10`,
|
||||
workload는 `20`입니다. 이 wave는 child Application 객체 생성 순서만
|
||||
표현하며 서로 다른 Application의 readiness dependency로 사용하지
|
||||
않습니다. Vault Agent와 workload는 필요한 Vault/DB API가 준비될 때까지
|
||||
자체 retry 가능한 형태여야 합니다.
|
||||
|
||||
모든 child Application은 auto-sync, prune, self-heal을 사용합니다.
|
||||
Application 삭제와 parent prune은 확인이 필요하며, shared resource
|
||||
소유권 충돌은 `FailOnSharedResource=true`로 실패시킵니다.
|
||||
|
||||
Sync hook이 있는 `auth-server`와 `auth-system`에는 selective sync 옵션을
|
||||
사용하지 않습니다. DB migration과 Keycloak client sync는 같은
|
||||
Application 내부 wave로 순서를 제어합니다.
|
||||
|
||||
클러스터가 하나이고 child Application 수가 적으므로 현재는 명시적
|
||||
Application을 사용합니다. 두 번째 클러스터나 실제 production이 생길
|
||||
때 foundation/platform/workload별 ApplicationSet 도입을 검토합니다.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Deployment architecture
|
||||
|
||||
## Reconciliation boundaries
|
||||
|
||||
```text
|
||||
Gitea main
|
||||
|
|
||||
+-- Argo CD root -> AppProjects + child Applications -> Kubernetes
|
||||
|
|
||||
+-- approved Terraform runner -> Vault API
|
||||
```
|
||||
|
||||
Argo CD는 Kubernetes desired state만 관리합니다. 최초 Argo 설치/root
|
||||
seed와 문서화된 recovery 외에는 직접 cluster mutation을 하지 않습니다.
|
||||
Terraform은 Config Management Plugin이나 Argo hook 안에서 실행하지
|
||||
않습니다.
|
||||
|
||||
## Kustomize ownership
|
||||
|
||||
- `platform/`, `workloads/`: 환경 중립 base
|
||||
- `clusters/dev-k3s/manifests/`: namespace, host, image, Vault role 및
|
||||
NetworkPolicy를 포함하는 최종 cluster composition
|
||||
- Argo CD Application: final composition만 source로 사용
|
||||
|
||||
지원하지 않는 production overlay는 존재하지 않습니다. production
|
||||
계약과 승인 경계가 확정될 때 별도로 생성합니다.
|
||||
|
||||
## In-application ordering
|
||||
|
||||
`auth-server`의 한 sync operation 안에서:
|
||||
|
||||
- generated ConfigMap과 일반 리소스: wave `0`
|
||||
- database migration Sync hook: wave `5`
|
||||
- Deployment: wave `10`
|
||||
- north-south route: wave `20`
|
||||
|
||||
`auth-system`의 Keycloak client sync도 idempotent Sync hook이며 deadline,
|
||||
backoff, `BeforeHookCreation,HookSucceeded` cleanup을 사용합니다.
|
||||
|
||||
## Stateful lifecycle
|
||||
|
||||
Vault와 PostgreSQL PVC는 `Prune=false`로 보호합니다. child Application
|
||||
prune/delete는 확인이 필요합니다. path 이동이나 Application rename 전에는
|
||||
새 owner가 동일 live resource를 정상적으로 추적하는지 확인한 후 이전
|
||||
owner를 non-cascading 방식으로 제거합니다.
|
||||
|
||||
## Image promotion
|
||||
|
||||
첫-party image는 애플리케이션 CI가 얻은 정확한 GHCR digest를 Gitea
|
||||
workflow에 전달합니다. workflow는 digest 변경 PR을 만들고, validation과
|
||||
승인을 거쳐 merge된 뒤 Argo CD가 배포합니다.
|
||||
|
||||
현재 short-SHA tag는 migration 시점의 예외입니다. private GHCR을 읽을
|
||||
자격증명이 이 저장소 실행 환경에 없으므로 임의 digest로 바꾸지 않았고,
|
||||
다음 정상 promotion에서 `digest:`로 교체됩니다.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Secret trust boundaries
|
||||
|
||||
## Dev Vault
|
||||
|
||||
`dev-k3s`는 단일 self-hosted Vault를 사용합니다. 동일 workload
|
||||
클러스터에 별도의 Transit Vault를 두지 않습니다. 단일 Vault는 다음을
|
||||
소유합니다.
|
||||
|
||||
- KV-v2 runtime secret path
|
||||
- Kubernetes auth와 workload role
|
||||
- dynamic PostgreSQL credential
|
||||
- 애플리케이션 JWT signing용 Transit key
|
||||
|
||||
dev Vault는 Shamir 1-of-1로 한 번 초기화하고 재시작 시 명시적으로
|
||||
unseal합니다. 이 방식은 개발 환경 전용입니다. production에서는 managed
|
||||
Vault 또는 독립 failure domain의 HA integrated-Raft와 KMS/HSM
|
||||
auto-unseal을 사용해야 합니다.
|
||||
|
||||
## Terraform
|
||||
|
||||
`vault-core` state는 mounts, auth, policies, roles와 JWT key를 소유하며
|
||||
제한된 관리자만 적용합니다. `vault-database`는 PostgreSQL connection과
|
||||
dynamic roles만 소유하고 `vault-database-automation-dev` 정책을 사용합니다.
|
||||
|
||||
Terraform variable로 전달되는 token과 PostgreSQL password는 ephemeral/
|
||||
write-only 경계를 사용합니다. KV payload는 Terraform resource/data
|
||||
source로 읽거나 쓰지 않습니다.
|
||||
|
||||
## Workload authentication
|
||||
|
||||
workload는 audience `vault`, TTL 1시간의 projected ServiceAccount token으로
|
||||
Vault Kubernetes auth에 로그인합니다. token은 Vault Agent가 사용하며
|
||||
application container에 Kubernetes bearer token을 직접 노출하지 않습니다.
|
||||
|
||||
Secret payload는 승인된 운영자가 Vault에 직접 기록합니다. 값은 Git,
|
||||
Gitea Actions log, Terraform state, Kubernetes manifest에 남기지 않습니다.
|
||||
|
||||
## Bootstrap material
|
||||
|
||||
Vault init output은 기본적으로 `.local/vault/dev-k3s-init.json`에 mode
|
||||
`0600`으로 생성됩니다. encrypted custody로 이동한 후 working copy를
|
||||
제거합니다. initial root token은 `vault-core`와 operator auth 검증 직후
|
||||
폐기합니다.
|
||||
|
||||
Sealed Secrets는 private GHCR pull credential에만 사용합니다. controller
|
||||
private key는 별도 복구 저장소에 백업해야 합니다.
|
||||
|
||||
## Dev limitations
|
||||
|
||||
- Vault, PostgreSQL, ingress가 아직 TLS를 사용하지 않음
|
||||
- single-node Vault와 PostgreSQL
|
||||
- Kubernetes API egress CIDR가 현재 dev cluster에 종속
|
||||
- 정적 bootstrap secret은 coordinated rotation 필요
|
||||
|
||||
이 제약은 production에서 허용되지 않습니다.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Agent Guide
|
||||
|
||||
이 파일은 이 저장소에서 작업을 시작하는 사람과 에이전트를 위한 첫 진입 문서입니다.
|
||||
|
||||
## Read First
|
||||
|
||||
작업을 시작하면 아래 순서로 문서를 읽습니다.
|
||||
|
||||
1. 이 파일 `AGENT.md`
|
||||
2. 루트 [README.md](/home/donghyeon/dev/Project-Auth-GitOps/README.md)
|
||||
3. 현재 작업과 직접 관련된 runbook
|
||||
- Vault transit: [runbooks/vault-transit/dev/README.md](/home/donghyeon/dev/Project-Auth-GitOps/runbooks/vault-transit/dev/README.md)
|
||||
- Workload Vault: [runbooks/vault/dev/README.md](/home/donghyeon/dev/Project-Auth-GitOps/runbooks/vault/dev/README.md)
|
||||
- Argo CD 구조: [argocd/README.md](/home/donghyeon/dev/Project-Auth-GitOps/argocd/README.md)
|
||||
|
||||
## Working Rules
|
||||
|
||||
- README 최상단 `현재 최신 Dev 아키텍처` 는 최신 상태로 유지합니다.
|
||||
- 기존 cycle은 지우지 말고 README 맨 아래에 새 cycle을 추가합니다.
|
||||
- ops 변경이나 장애 대응을 했으면 명령, 관찰, 판단 근거, 수정, 검증을 함께 남깁니다.
|
||||
- 워크플로 구조를 크게 바꿨으면 README에 새 cycle을 추가합니다.
|
||||
- 민감한 값은 절대 문서에 기록하지 않습니다.
|
||||
- token, password, kubeconfig 본문, secret payload 금지
|
||||
- 대신 존재 여부, 길이, secret name, 리소스 상태만 기록
|
||||
- repo 밖 임시 파일(`/tmp/...`)로 작업한 secret manifest는 Git에 넣지 않습니다.
|
||||
- Vault 관련 자동화는 **bootstrap** 과 **reconcile** 을 분리합니다.
|
||||
- bootstrap: privileged token 필요, 수동 runbook
|
||||
- reconcile: workflow AppRole 기반 routine CI
|
||||
- GitHub Actions YAML에는 긴 Bash를 직접 넣지 않고 `scripts/ci/*.sh` wrapper로 분리합니다.
|
||||
- GitHub context(`inputs`, `client_payload`) 는 inline Bash에서 직접 쓰지 않고 step `env:` 로 매핑한 뒤 스크립트에서 읽습니다.
|
||||
- `scripts/ci/reconcile-vault-dev.sh` 는 기본적으로 port-forward 모드지만, 장기적으로는 `RECONCILE_USE_PORT_FORWARD=false` + in-cluster service URL로 돌리는 방향이 목표입니다.
|
||||
- `vault-dev-reconcile.yaml` 은 아래 GitHub Variables 로 runner/네트워크 전환을 제어합니다.
|
||||
- `VAULT_DEV_RECONCILE_RUNS_ON`
|
||||
- `RECONCILE_USE_PORT_FORWARD`
|
||||
- `TRANSIT_VAULT_ADDR`
|
||||
- `WORKLOAD_VAULT_ADDR`
|
||||
|
||||
## Troubleshooting Format
|
||||
|
||||
README cycle의 `### 6. 트러블슈팅 메모` 에 아래 형식으로 남깁니다.
|
||||
|
||||
- 재현/확인 명령: 실제로 사용한 명령
|
||||
- 핵심 관찰값: 에러 문구, 상태 변화, 이벤트, 로그 핵심
|
||||
- 판단 근거: 왜 그 관찰값을 보고 해당 원인이라고 판단했는지
|
||||
- 수정 또는 조치: 어떤 파일/리소스를 바꿨는지
|
||||
- 검증 명령: 해결 여부를 다시 확인한 명령
|
||||
|
||||
## Current Dev Pitfalls
|
||||
|
||||
- Argo CD `Application` 이 `Repository not found` 를 내면 로컬 git 인증이 아니라 `argocd` namespace의 repo credential secret부터 확인합니다.
|
||||
- self-hosted runner 문제는 workflow YAML보다 먼저 runner 호스트의 실제 CLI 설치 여부를 확인합니다.
|
||||
- `command -v kubectl vault jq base64 curl terraform`
|
||||
- Vault raft 사용 시 `api_addr`, `cluster_addr`, listener `cluster_address`, service/deployment의 `8201` 포트 일관성을 먼저 확인합니다.
|
||||
- `hashicorp/vault` 이미지를 ConfigMap mount와 함께 쓸 때는 read-only mount 충돌과 단일 PVC rollout 충돌을 같이 봅니다.
|
||||
- 필요하면 `/tmp` 로 config 복사 후 실행, `strategy: Recreate` 적용
|
||||
- Vault Agent template에 여러 `export` 줄을 렌더링할 때는 aggressive trim(`{{- ... -}}`) 때문에 줄바꿈이 붙지 않는지 실제 `/vault/secrets/*` 파일을 확인합니다.
|
||||
- Postgres PVC를 유지하는 상태에서 Vault KV만 바꾸면 DB 내부 사용자 비밀번호와 주입값이 어긋날 수 있습니다. KV 값과 persisted DB state를 함께 확인합니다.
|
||||
- Headless Service를 DB connection host로 쓸 때는 pod 안에서 실제로 어떤 이름이 해석되는지 먼저 확인합니다.
|
||||
- `postgres.platform.svc.cluster.local` 이 안 되면 `postgres-0.postgres.platform.svc.cluster.local` 같이 pod FQDN 확인
|
||||
- CI에서 Terraform local backend를 쓰면 self-hosted runner가 기존 `.terraform-state/*.tfstate` 를 실제로 보고 있는지 먼저 확인합니다. state가 완전히 없을 때뿐 아니라 일부 리소스만 남은 partial state도 위험합니다.
|
||||
- `terraform state list`
|
||||
- `terraform state show <resource>`
|
||||
- workflow에서 빠진 리소스를 개별 import 하도록 유지
|
||||
- 단, `vault_approle_auth_backend_role_secret_id` 처럼 provider가 import를 지원하지 않는 리소스는 예외로 두고 재생성으로 수렴시킵니다.
|
||||
- Vault KV-v2 mount는 provider import 결과가 `type=kv` + `options.version=2` 로 보이므로, 선언도 그 형태로 맞추고 `prevent_destroy = true` 를 유지합니다.
|
||||
- seal token을 state로 다시 맞출 때는 `auth/token/revoke-accessor` 권한이 필요합니다.
|
||||
- `vault_mount`, `vault_auth_backend`, `vault_token.seal` 같이 bootstrap 성격이 강한 리소스는 routine CI에서 replacement가 나지 않게 drift를 최소화합니다.
|
||||
- 필요하면 `ignore_changes`
|
||||
- 더 좋게는 bootstrap 단계와 reconcile 단계를 분리
|
||||
- `kubectl rollout status` 실패 시 `describe`, `logs`, `events` 를 세트로 봅니다.
|
||||
|
||||
## Default Command Order
|
||||
|
||||
Argo CD app 문제:
|
||||
|
||||
```bash
|
||||
kubectl -n argocd get applications
|
||||
kubectl -n argocd describe application <name>
|
||||
kubectl -n argocd get secrets
|
||||
```
|
||||
|
||||
Runner 문제:
|
||||
|
||||
```bash
|
||||
command -v kubectl
|
||||
command -v vault
|
||||
command -v terraform
|
||||
```
|
||||
|
||||
Vault pod 문제:
|
||||
|
||||
```bash
|
||||
kubectl -n <ns> get deploy,pods
|
||||
kubectl -n <ns> describe deployment <name>
|
||||
kubectl -n <ns> logs deploy/<name> --tail=200
|
||||
kubectl -n <ns> get events --sort-by=.lastTimestamp | tail -n 30
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
# Archived documentation
|
||||
|
||||
Files in this directory describe historical repository states. Paths,
|
||||
workflows, credentials and operational commands may no longer exist.
|
||||
|
||||
Do not execute archived procedures. Use the root README and `docs/runbooks/`.
|
||||
@@ -0,0 +1,209 @@
|
||||
# Bootstrap an empty dev-k3s cluster
|
||||
|
||||
이 runbook은 폐기 가능한 개발 클러스터만 대상으로 합니다. production에
|
||||
사용하지 않습니다.
|
||||
|
||||
## 1. Preflight
|
||||
|
||||
```bash
|
||||
kubectl config current-context
|
||||
kubectl cluster-info
|
||||
make validate
|
||||
```
|
||||
|
||||
의도한 dev cluster가 아니면 중단합니다. 내부 Gitea가 private이면 Argo CD가
|
||||
root repository를 읽을 수 있는 read-only credential을 외부 secret
|
||||
authority에서 먼저 provision해야 합니다. credential은 이 저장소에
|
||||
commit하지 않습니다.
|
||||
|
||||
remote state backend 파일을 준비합니다.
|
||||
|
||||
```bash
|
||||
mkdir -p .local/terraform-backend/dev-k3s
|
||||
cp iac/terraform/backend/dev-k3s/vault-core.s3.hcl.example \
|
||||
.local/terraform-backend/dev-k3s/vault-core.s3.hcl
|
||||
cp iac/terraform/backend/dev-k3s/vault-database.s3.hcl.example \
|
||||
.local/terraform-backend/dev-k3s/vault-database.s3.hcl
|
||||
```
|
||||
|
||||
실제 bucket, endpoint와 workload identity를 설정합니다. backend credential은
|
||||
파일에 넣지 않습니다.
|
||||
|
||||
## 2. Argo CD와 root Application
|
||||
|
||||
```bash
|
||||
make bootstrap KUBE_CONTEXT="$(kubectl config current-context)"
|
||||
kubectl -n argocd get application project-gitops-dev-k3s
|
||||
```
|
||||
|
||||
이 명령이 수행하는 직접 cluster mutation은 Argo CD 설치와 root seed뿐입니다.
|
||||
Child Application은 root가 생성합니다.
|
||||
|
||||
## 3. Dev Vault 초기화
|
||||
|
||||
Vault Pod가 생성될 때까지 기다린 뒤 operator workstation에서 forward합니다.
|
||||
이 port-forward는 최초 dev bootstrap용이며 routine runner 모델이 아닙니다.
|
||||
|
||||
```bash
|
||||
kubectl -n vault wait --for=create pod -l app=vault --timeout=300s
|
||||
kubectl -n vault port-forward deployment/vault 8200:8200
|
||||
```
|
||||
|
||||
별도 terminal:
|
||||
|
||||
```bash
|
||||
export VAULT_ADDR=http://127.0.0.1:8200
|
||||
./hack/vault-init.sh init
|
||||
```
|
||||
|
||||
`.local/vault/dev-k3s-init.json`을 즉시 encrypted custody로 복사합니다.
|
||||
dev-only 1-of-1 unseal key와 initial root token이 있으므로 일반 backup과
|
||||
분리합니다.
|
||||
|
||||
## 4. Vault core
|
||||
|
||||
초기 root token을 shell history에 직접 적지 않습니다.
|
||||
|
||||
```bash
|
||||
export TF_VAR_vault_addr="$VAULT_ADDR"
|
||||
export TF_VAR_vault_token="$(
|
||||
jq -r '.root_token' .local/vault/dev-k3s-init.json
|
||||
)"
|
||||
|
||||
make terraform-plan \
|
||||
TF_ROOT=vault-core \
|
||||
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-core.s3.hcl
|
||||
|
||||
make terraform-apply \
|
||||
TF_ROOT=vault-core \
|
||||
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-core.s3.hcl \
|
||||
APPROVE_APPLY=dev-k3s/vault-core
|
||||
```
|
||||
|
||||
plan에서 mount, auth backend, policy, role, JWT Transit key 이외 객체가
|
||||
나오면 apply하지 않습니다.
|
||||
|
||||
## 5. Runtime secret seed
|
||||
|
||||
Secret 값은 Git/Terraform을 통과하지 않습니다. 아래 변수는 terminal
|
||||
session에만 유지합니다.
|
||||
|
||||
```bash
|
||||
read -r -s -p "PostgreSQL superuser password: " POSTGRES_SUPERUSER_PASSWORD
|
||||
echo
|
||||
read -r -s -p "Auth database password: " AUTH_DB_PASSWORD
|
||||
echo
|
||||
read -r -s -p "Keycloak database password: " KEYCLOAK_DB_PASSWORD
|
||||
echo
|
||||
read -r -s -p "Keycloak bootstrap admin password: " KEYCLOAK_ADMIN_PASSWORD
|
||||
echo
|
||||
read -r -s -p "Auth-server Keycloak client secret: " KEYCLOAK_CLIENT_SECRET
|
||||
echo
|
||||
|
||||
secret_file="$(mktemp)"
|
||||
trap 'rm -f "$secret_file"' EXIT
|
||||
chmod 0600 "$secret_file"
|
||||
|
||||
jq -n --arg password "$POSTGRES_SUPERUSER_PASSWORD" \
|
||||
'{POSTGRES_SUPERUSER_PASSWORD: $password}' >"$secret_file"
|
||||
vault kv put kv/dev/platform/postgres/superuser @"$secret_file"
|
||||
|
||||
jq -n \
|
||||
--arg password "$AUTH_DB_PASSWORD" \
|
||||
'{AUTH_DB_PASSWORD: $password, APP_DATASOURCE_USERNAME: "project_auth", APP_DATASOURCE_PASSWORD: $password}' >"$secret_file"
|
||||
vault kv put kv/dev/platform/postgres/auth-server @"$secret_file"
|
||||
|
||||
jq -n --arg password "$KEYCLOAK_DB_PASSWORD" \
|
||||
'{KEYCLOAK_DB_PASSWORD: $password}' >"$secret_file"
|
||||
vault kv put kv/dev/platform/postgres/keycloak @"$secret_file"
|
||||
|
||||
jq -n --arg password "$KEYCLOAK_ADMIN_PASSWORD" \
|
||||
'{KC_BOOTSTRAP_ADMIN_PASSWORD: $password}' >"$secret_file"
|
||||
vault kv put kv/dev/platform/keycloak/bootstrap-admin @"$secret_file"
|
||||
|
||||
jq -n --arg secret "$KEYCLOAK_CLIENT_SECRET" \
|
||||
'{KEYCLOAK_CLIENT_SECRET: $secret, APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_SECRET: $secret}' >"$secret_file"
|
||||
vault kv put kv/dev/platform/keycloak/client-auth-server @"$secret_file"
|
||||
|
||||
rm -f "$secret_file"
|
||||
trap - EXIT
|
||||
```
|
||||
|
||||
PostgreSQL이 Vault Agent 주입 후 시작하는지 확인합니다.
|
||||
|
||||
```bash
|
||||
kubectl -n platform rollout status statefulset/postgres --timeout=600s
|
||||
```
|
||||
|
||||
## 6. Vault database state
|
||||
|
||||
초기 root token으로 TTL이 짧은 database 전용 token을 발급합니다.
|
||||
|
||||
```bash
|
||||
export TF_VAR_vault_token="$(
|
||||
vault token create \
|
||||
-policy=vault-database-automation-dev \
|
||||
-ttl=30m \
|
||||
-format=json |
|
||||
jq -r '.auth.client_token'
|
||||
)"
|
||||
export TF_VAR_postgres_admin_password="$POSTGRES_SUPERUSER_PASSWORD"
|
||||
export TF_VAR_postgres_admin_password_version=1
|
||||
|
||||
make terraform-plan \
|
||||
TF_ROOT=vault-database \
|
||||
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-database.s3.hcl
|
||||
|
||||
make terraform-apply \
|
||||
TF_ROOT=vault-database \
|
||||
BACKEND_CONFIG=.local/terraform-backend/dev-k3s/vault-database.s3.hcl \
|
||||
APPROVE_APPLY=dev-k3s/vault-database
|
||||
```
|
||||
|
||||
## 7. Root token 폐기
|
||||
|
||||
Kubernetes auth operator login이 동작하는지 먼저 검증합니다.
|
||||
|
||||
```bash
|
||||
operator_jwt="$(
|
||||
kubectl -n vault create token vault-operator \
|
||||
--audience=vault \
|
||||
--duration=10m
|
||||
)"
|
||||
operator_token="$(
|
||||
VAULT_TOKEN= vault write \
|
||||
-format=json \
|
||||
auth/kubernetes/login \
|
||||
role=vault-operator-dev \
|
||||
jwt="$operator_jwt" |
|
||||
jq -r '.auth.client_token'
|
||||
)"
|
||||
VAULT_TOKEN="$operator_token" vault token lookup >/dev/null
|
||||
```
|
||||
|
||||
검증 후 initial root token을 폐기합니다.
|
||||
|
||||
```bash
|
||||
./hack/vault-init.sh revoke-root
|
||||
unset operator_jwt operator_token
|
||||
unset TF_VAR_vault_token TF_VAR_postgres_admin_password
|
||||
unset POSTGRES_SUPERUSER_PASSWORD AUTH_DB_PASSWORD KEYCLOAK_DB_PASSWORD
|
||||
unset KEYCLOAK_ADMIN_PASSWORD KEYCLOAK_CLIENT_SECRET
|
||||
```
|
||||
|
||||
encrypted custody로 옮긴 init material의 local working copy는 조직의
|
||||
dev recovery 정책에 따라 제거합니다.
|
||||
|
||||
## 8. 확인
|
||||
|
||||
```bash
|
||||
kubectl -n argocd get applications
|
||||
kubectl -n vault get pods
|
||||
kubectl -n platform get pods
|
||||
kubectl -n auth-dev get pods
|
||||
kubectl -n api-dev get pods
|
||||
```
|
||||
|
||||
모든 Application의 sync/health를 확인하고 DB migration 및 Keycloak client
|
||||
sync hook 결과를 검토합니다. 실패한 hook을 고치기 위해 child manifest를
|
||||
직접 apply하지 말고 Git PR을 사용합니다.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Sealed Secrets backup and recovery
|
||||
|
||||
Existing SealedSecrets are decryptable only with a controller private key.
|
||||
Back up every key after first install and after rotation.
|
||||
|
||||
## Backup
|
||||
|
||||
Use an encrypted operator workstation:
|
||||
|
||||
```bash
|
||||
umask 077
|
||||
kubectl -n kube-system get secret \
|
||||
-l sealedsecrets.bitnami.com/sealed-secrets-key \
|
||||
-o json > .local/sealed-secrets-keys.json
|
||||
chmod 0600 .local/sealed-secrets-keys.json
|
||||
```
|
||||
|
||||
Encrypt the file with the organization's recovery mechanism, store at least
|
||||
two independently controlled copies, and delete the plaintext working copy.
|
||||
Record cluster, date and checksum without recording private key data.
|
||||
|
||||
## Restore
|
||||
|
||||
Restore keys before application SealedSecrets sync:
|
||||
|
||||
```bash
|
||||
kubectl -n kube-system apply -f .local/sealed-secrets-keys.json
|
||||
kubectl -n kube-system rollout restart deployment/sealed-secrets-controller
|
||||
kubectl -n kube-system rollout status deployment/sealed-secrets-controller
|
||||
```
|
||||
|
||||
Verify both `auth-dev/ghcr-regcred` and `api-dev/ghcr-regcred` are created.
|
||||
|
||||
If no key backup exists, old ciphertext cannot be recovered. Create a new
|
||||
controller key and reseal every Secret from the original credential source.
|
||||
|
||||
## Rotation
|
||||
|
||||
The chart requests periodic key renewal. Old keys must remain until all
|
||||
ciphertext has been resealed and verified. GHCR credential rotation requires:
|
||||
|
||||
1. issue an organization-owned read-only package credential;
|
||||
2. create namespace-scoped SealedSecrets with the current controller cert;
|
||||
3. merge and verify image pulls;
|
||||
4. revoke the previous credential;
|
||||
5. update the encrypted key backup.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Terraform v2 state migration
|
||||
|
||||
새 클러스터에는 이 runbook이 필요하지 않습니다. legacy local state 또는
|
||||
이전 `provider-foundation`, `workload-foundation`, `workload-config`,
|
||||
`database-config` remote state가 실제로 존재할 때만 수행합니다.
|
||||
|
||||
State 이동은 live object 삭제보다 위험할 수 있습니다. maintenance window와
|
||||
독립 backup 없이 진행하지 않습니다.
|
||||
|
||||
## 목표
|
||||
|
||||
| 이전 state | 목표 |
|
||||
|---|---|
|
||||
| provider Transit Vault state | archive 후 provider Vault 폐기 절차에서 별도 처리 |
|
||||
| workload foundation | `vault-core`의 기준 state |
|
||||
| workload config | 소유 객체를 `vault-core`로 이동 |
|
||||
| database config | `vault-database`로 backend key migration |
|
||||
|
||||
동일 클러스터 Transit Vault는 더 이상 desired state가 아닙니다. Terraform
|
||||
state에서 먼저 삭제하거나 destroy하지 않습니다. snapshot과 seal dependency
|
||||
해제 확인 후 별도 decommission 승인을 받아 처리합니다.
|
||||
|
||||
## 1. Inventory와 backup
|
||||
|
||||
모든 operator machine, runner, remote backend에서 state 위치를 확인합니다.
|
||||
|
||||
```bash
|
||||
find . -type f \
|
||||
\( -name 'terraform.tfstate*' -o -name '*.tfplan' \) \
|
||||
-not -path './.git/*'
|
||||
```
|
||||
|
||||
각 state를 `terraform state pull`로 encrypted offline custody에 저장하고
|
||||
checksum을 기록합니다. backup에는 secret data가 포함될 수 있습니다.
|
||||
|
||||
## 2. Backend key migration
|
||||
|
||||
`workload-foundation` backend에 연결한 상태에서 새 `vault-core` backend
|
||||
configuration으로 `terraform init -migrate-state`를 수행합니다.
|
||||
`database-config`도 같은 방식으로 `vault-database` key로 이동합니다.
|
||||
|
||||
실제 backend 파일과 이전 key는 환경마다 다르므로 명령에 값을 하드코딩하지
|
||||
않습니다. migration 전후 `terraform state pull` checksum과 `state list`를
|
||||
비교합니다.
|
||||
|
||||
## 3. Workload configuration ownership 이동
|
||||
|
||||
이전 `workload-config` state의 다음 객체를 `vault-core` state의 선언된
|
||||
address로 이동합니다.
|
||||
|
||||
- application Vault policies
|
||||
- workload Kubernetes auth roles
|
||||
|
||||
`terraform state mv -state=<source-backup> -state-out=<target-working-copy>`를
|
||||
사용해 offline copy에서 먼저 연습합니다. target address는 현재
|
||||
`module.workload_policies`와 `module.workload_roles`의 `terraform state list`
|
||||
결과를 기준으로 합니다. resource 이름을 추측하지 않습니다.
|
||||
|
||||
이동 후 두 state 모두 plan합니다.
|
||||
|
||||
- `vault-core`: 변경 없음 또는 address-only 이동
|
||||
- legacy workload-config: 삭제할 live object 없음
|
||||
|
||||
두 plan 중 하나라도 destroy를 제안하면 중단하고 backup state를 복원합니다.
|
||||
|
||||
## 4. Database state
|
||||
|
||||
기존 database state가 없고 live Vault 객체만 존재할 때만 다음 import ID를
|
||||
사용합니다.
|
||||
|
||||
```text
|
||||
vault_database_secret_backend_connection.platform_postgres database/config/platform-postgres-dev
|
||||
vault_database_secret_backend_role.auth_db_migration database/roles/auth-db-migration-dev
|
||||
vault_database_secret_backend_role.postgres_operator database/roles/postgres-operator-dev
|
||||
```
|
||||
|
||||
이미 다른 state에 address가 있으면 import하지 말고 state ownership을 먼저
|
||||
이동합니다.
|
||||
|
||||
## 5. Cutover 완료 조건
|
||||
|
||||
- 두 목표 state가 remote backend와 locking을 사용
|
||||
- 동일 Vault path가 두 state list에 나타나지 않음
|
||||
- plan에 예상하지 않은 create/delete가 없음
|
||||
- legacy state와 backup은 immutable archive
|
||||
- repo와 runner에 local state/provider directory가 없음
|
||||
|
||||
검증이 끝나기 전 legacy backend를 삭제하지 않습니다.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Vault backup and recovery
|
||||
|
||||
`dev-k3s` Vault는 single-node integrated Raft입니다. upgrade, Terraform
|
||||
state migration, storage 작업 전에 snapshot을 생성합니다.
|
||||
|
||||
## Snapshot
|
||||
|
||||
short-lived authorized token과 Vault API 연결을 준비합니다.
|
||||
|
||||
```bash
|
||||
umask 077
|
||||
vault operator raft snapshot save .local/vault/dev-k3s.snap
|
||||
sha256sum .local/vault/dev-k3s.snap
|
||||
chmod 0600 .local/vault/dev-k3s.snap
|
||||
```
|
||||
|
||||
즉시 암호화해 init/unseal material과 다른 위치에 보관합니다. snapshot에는
|
||||
secret payload가 포함됩니다.
|
||||
|
||||
## Recovery material
|
||||
|
||||
- Raft snapshot
|
||||
- unseal key
|
||||
- 현재 manifest revision
|
||||
- Terraform remote state backup
|
||||
- Sealed Secrets controller key
|
||||
|
||||
Initial root token은 recovery material이 아닙니다. bootstrap 완료 후
|
||||
폐기해야 합니다.
|
||||
|
||||
## Restore exercise
|
||||
|
||||
분기마다 isolated disposable cluster에서 다음을 검증합니다.
|
||||
|
||||
1. 동일 Vault version과 storage 설정 배포
|
||||
2. Vault init 후 snapshot restore
|
||||
3. 복구 key로 unseal
|
||||
4. Kubernetes auth 재연결 확인
|
||||
5. KV read, JWT signing, dynamic database lease issue/revoke 확인
|
||||
6. recovery time과 누락된 의존성 기록
|
||||
|
||||
현재 dev 구성은 production recovery 설계가 아닙니다. production은
|
||||
independent HA Vault, TLS, auto-unseal과 더 엄격한 backup custody가
|
||||
필요합니다.
|
||||
Reference in New Issue
Block a user