Files
platform-core/README.md
T

244 lines
13 KiB
Markdown

# 플랫폼 인프라
단일 노드 k3s에서 사용하는 공통 플랫폼 인프라의 선언형 구성을 관리하는
저장소입니다. 상세 아키텍처, 운영 절차, 의사결정 기록은
[중앙 Platform 문서](/home/donghyeon/workspace/docs/platform)에서 관리합니다.
## 현재 상태
Phase 1과 Phase 2의 선언 및 수동 초기 구축 진입점이 구현되어 있습니다.
Phase 1에는 namespace, SSD Local PV, CloudNativePG, 공용 PostgreSQL, Gitea,
Traefik Ingress와 NetworkPolicy가 포함됩니다. Phase 2 선언에는 Keycloak, 공용
PostgreSQL의 Keycloak DB·Role, AIStor Operator, 단일 MinIO AIStor ObjectStore,
namespace와 Local PV가 포함됩니다.
Phase 1은 실제 클러스터에 적용했습니다. CloudNativePG·PostgreSQL·Gitea,
PV/PVC와 내부 Traefik health가 정상이며 Gitea 네 컨테이너의 restricted
securityContext도 검증했습니다. Host Nginx의 Gitea proxy 전환과 로컬·public
HTTP/HTTPS 검증도 통과했습니다.
Phase 2는 전체를 한 번에 적용하지 않았습니다. **Keycloak-only 범위**인 전용 DB와
Role, NetworkPolicy, 공식 Operator, 단일 Keycloak 인스턴스, `hyeonworks` realm,
confidential `gitea` client와 `gitea/gitea-keycloak-oidc` Secret까지 실제 적용을
완료했습니다. Traefik `web` entrypoint에는 실제 관측한 Host Nginx source
`10.42.0.1/32`만 forwarded-header trusted IP로 적용했습니다.
`id.learn.hyeonworks.com`의 Host Nginx static hold를 Keycloak proxy로 바꾸는
전환과 Gitea OIDC·브랜딩 rollout도 완료했습니다. Host Nginx 후보 SHA-256은
`5c5cd74b4992f537fd27c50cf2209573a80a9904e0b19b58c3154717be6ff4a5`, 백업은
`/etc/nginx/sites-available/learn-services.before-keycloak-20260723160519`이며,
적용한 Gitea manifest SHA-256은 `d25a757...a157`입니다. 활성 OAuth source,
외부 인증 전용 가입 정책, authorization-code redirect와 브랜딩 자동 검증을 모두
통과했습니다. 실제 realm 사용자의 브라우저 login/callback/logout와 비상 관리자
로그인은 별도 수용 시험으로 남아 있습니다.
AIStor는 Keycloak 경로와 분리해 실제 적용했습니다. `aistor``object-storage`
namespace, 900Gi Retain Local PV, 공식 Operator와 단일 ObjectStore, 기본 차단
NetworkPolicy가 동작 중입니다. ObjectStore는 `Initialized/green`, PVC는
`aistor-data-local-pv`에 Bound이며 S3와 Console은 `ClusterIP`로만 노출됩니다.
고정 다이제스트의 공식 AIStor Client로 버킷 생성, 객체 쓰기·읽기 checksum,
객체·버킷 삭제까지 인증된 스모크 테스트를 통과했습니다.
| 구성요소 | 고정 버전 | 상태 |
| --- | --- | --- |
| CloudNativePG | Operator `1.30.0`, Chart `0.29.0` | Phase 1 적용, Ready/Available `1/1` |
| PostgreSQL | `17.9-standard-trixie` | Phase 1 적용, healthy `1/1`, PVC Bound |
| Gitea | 애플리케이션 `1.27.0`, Chart `12.7.0` | OIDC·브랜딩 rollout 완료; OAuth source·정책·redirect·자산 자동 검증 통과 |
| Keycloak | Operator·애플리케이션 `26.7.0` | Ready `1/1`; realm·client·OIDC Secret·Host Nginx 공개 전환 완료 |
| Traefik | Chart `40.1.3+up40.1.0`, 이미지 `3.7.4` | NodePort `30080/30443`, `web` trust `10.42.0.1/32` 적용 |
| AIStor Operator | Chart `5.10.0` | 적용, Operator·AdminJob·Webhook Ready `1/1` |
| AIStor ObjectStore | Chart `1.0.16` | 적용, `Initialized/green`, 900Gi PVC Bound, 인증 S3 스모크 통과 |
| 렌더 도구 | Kustomize `5.8.1`, Helm `3.19.4` | 정확히 일치해야 함 |
Argo CD와 Istio는 현재 단계에서 구현하거나 배포하지 않습니다.
## 실제 Kustomize 빌드 루트
Phase 1 최초 적용은 다음 경로만 빌드 루트로 사용합니다.
| 순서 | 빌드 루트 | 렌더 방식 |
| ---: | --- | --- |
| 1 | `infrastructure/namespaces/overlays/home` | Kustomize |
| 2 | `infrastructure/storage/ssd-local-pv` | Kustomize |
| 3 | `infrastructure/controllers/cloudnative-pg` | Kustomize + Helm |
| 4 | `services/platform-postgres` | Kustomize |
| 5 | `services/gitea` | Kustomize + Helm |
`services/gitea`는 신규 설치용 baseline 빌드 루트이며 렌더 결과는
`gitea.yaml`입니다. Keycloak OIDC와 브랜딩은 최초 Phase 1 적용 대상이 아니며,
Host Nginx ID 전환 뒤 전용 빌드 루트 `services/gitea/profiles/oidc`에서
`gitea-oidc.yaml`로 렌더링하고 수명주기 스크립트로만 적용합니다.
Phase 2는 Phase 1 전체를 먼저 검증한 뒤 다음 빌드 루트를 추가로 렌더합니다.
| 순서 | 빌드 루트 | 렌더 방식 |
| ---: | --- | --- |
| 1 | `infrastructure/namespaces/phase2` | Kustomize |
| 2 | `infrastructure/storage/aistor-local-pv` | Kustomize |
| 3 | `infrastructure/controllers/keycloak-operator` | Kustomize 원격 리소스 |
| 4 | `services/platform-postgres-keycloak` | Kustomize |
| 5 | `services/keycloak` | Kustomize |
| 6 | `infrastructure/controllers/aistor-operator` | Kustomize + Helm |
| 7 | `services/minio-aistor` | Kustomize + Helm |
하위 `base``overlays/home`는 직접 적용 대상이 아닐 수 있습니다. Helm values와
환경 patch가 형제 디렉터리에 있는 구성은 Kustomize 기본 `RootOnly` 제한을
유지하기 위해 서비스 또는 컨트롤러 루트에서 렌더합니다.
## Secret 원칙
평문 Secret, 비밀번호, 라이선스, 개인 키와 소스 관리되는 `kind: Secret` YAML을
저장소에 두지 않습니다. 각 단계의 초기 구축 스크립트가 계약 전체의
존재·타입·키·교차 namespace 동일성을 검증하고, 전체가 없을 때만 명시 확인 후
생성합니다. 일부만 존재하면 중단하며 자동 회전하지 않습니다.
최초 생성은 여러 Kubernetes API 요청으로 수행되므로 중간 실패 시 일부 객체가 남을
수 있습니다. 후속 실행은 이를 삭제하거나 덮어쓰지 않고 부분 상태로 감지해
중단합니다.
Phase 1 계약:
- `platform-data/gitea-db-credentials`
- `gitea/gitea-db-credentials`
- `gitea/gitea-admin`
Phase 2 계약:
- `platform-data/keycloak-db-credentials`
- `keycloak/keycloak-db-credentials`
- `gitea/gitea-keycloak-oidc`
- `aistor/minio-license`
- `object-storage/aistor-root-configuration`
`gitea/gitea-keycloak-oidc``Opaque` 유형이며 정확히 `key`, `secret` 두 key만
갖습니다. 현재 Keycloak-only 작업으로 생성됐지만 payload는 문서, values와 렌더
결과에 기록하지 않습니다.
자세한 계약은 [Secret 문서](infrastructure/security/secrets/README.md)를 따릅니다.
Helm 차트가 렌더하는 초기화 스크립트·비민감 설정용 내부 Secret은 애플리케이션
자격 증명 계약과 구분합니다.
## k3s Secret 저장 암호화와 복구 증거
k3s Secret encryption at rest의 상태 확인과 fail-stop 활성화 절차는
[암호화 수동 운영 절차](bootstrap/manual/k3s-secret-encryption.md)를 따릅니다. datastore나
snapshot 단독 탈취를 완화하지만 같은 host의 root 침해나 full-disk 탈취를 해결하지는
않습니다. server token과 backup을 함께 얻으면 복구·복호화할 수 있으므로 recovery bundle
전체를 Secret으로 취급합니다.
2026-08-09 live 실행에서 수동 LUKS header 복구 proof, recovery close·잔류 없음 검사,
closed validator, Secret encryption `Enabled/reencrypt_finished`, hash·integrity·API·node
검사와 post bundle 기록을 모두 통과했고 최신 bundle marker도 검증했습니다. 다만 일반
lifecycle 자동화, 격리 restore drill, off-host 복제와 암호화 escrow 검증은 아직
완료되지 않았으므로 관측성 Phase 4 gate는 열리지 않았습니다.
Phase 4 Secret 생성 전에는 live `--expect-reencrypted`와 restore evidence `--check`를 매번
독립 호출합니다. backend별 수동 복구, 일회용 격리 환경, 세 mode 결과 생성·등록·검사와
파기 기준은 [k3s Secret 복구 drill](bootstrap/manual/k3s-secret-encryption-restore-drill.md),
중앙 보호 경계는
[보안과 Secret 관리](/home/donghyeon/workspace/docs/platform/architecture/security-and-secrets.md)와
[백업과 복구 설계](/home/donghyeon/workspace/docs/platform/architecture/backup-and-recovery.md)를
따릅니다. 완료된 live hardening의 비민감 정정은
[2026-08-08 복구 명령 원장](/home/donghyeon/workspace/docs/platform/runbooks/2026-08-08-k3s-local-recovery-command-log.md)의
2026-08-09 addendum과
[2026-08-09 로컬 정책 기록](/home/donghyeon/workspace/docs/platform/runbooks/2026-08-09-k3s-secret-encryption-local-policy.md)에
남긴다. 별도의 Task 6 실행 runbook은 만들지 않았으며, 이를 암시하지 않는다.
## 검증과 수동 적용
아무 리소스도 적용하지 않는 렌더 검증 명령은 다음과 같습니다.
```sh
cd /home/donghyeon/workspace/platform
bash scripts/validate/render-phase1.sh
bash scripts/validate/render-phase2.sh
```
Phase 1 검증은 baseline과 OIDC profile을 포함한 manifest 여섯 개를 생성합니다.
최초 구축 스크립트는 그중 `gitea.yaml` baseline만, OIDC 수명주기 스크립트는
`gitea-oidc.yaml` 하나만 각각 적용합니다.
Helm이 시스템 `PATH`에 없다면 검증된 `3.19.4` 실행 파일의 절대 경로를
`PLATFORM_HELM_BIN`으로 지정합니다. Phase 2 검증은 Phase 1을 먼저 검증한 뒤
공식 AIStor Chart 패키지 다이제스트, Keycloak 단일 인스턴스·HTTP hostname/Ingress,
AIStor 1x1·900Gi·PVC 보호·ClusterIP, RootOnly 유지와 Secret 소스·렌더링 부재를
확인합니다. 실제 클러스터의 ObjectStore CRD는 로컬 렌더에 필요하지 않습니다.
Phase 1 상세 절차는
[중앙 Phase 1 런북](/home/donghyeon/workspace/docs/platform/runbooks/2026-07-22-phase1-gitea-bootstrap.md)과
[짧은 실행 진입점](bootstrap/manual/phase1-gitea.md)을 따릅니다.
Keycloak에서 Gitea OIDC까지의 실제 실행 순서와 현재 완료·대기 경계는
[중앙 Keycloak-Gitea OIDC 전환 런북](/home/donghyeon/workspace/docs/platform/runbooks/2026-07-23-keycloak-gitea-oidc-cutover.md)에
기록합니다. 실행 진입점은 다음 네 개입니다.
```sh
cd /home/donghyeon/workspace/platform
# 완료: Keycloak DB·Operator·인스턴스
bash scripts/bootstrap/apply-keycloak.sh --execute
# 완료: realm·confidential Gitea client·OIDC Secret
bash scripts/bootstrap/configure-keycloak-gitea-oidc.sh --execute
# 완료: id.learn.hyeonworks.com Host Nginx 전환
sudo bash scripts/bootstrap/apply-host-nginx-keycloak.sh --execute
# 완료: 고정 SHA 렌더 결과의 gitea-oidc.yaml만 적용하고 OIDC·브랜딩 검증
PLATFORM_HELM_BIN=/home/donghyeon/.local/bin/helm \
bash scripts/bootstrap/apply-gitea-oidc.sh --execute
```
Traefik 신뢰 경계의 적용 값과 첫 실패·복구 내용은
[중앙 Traefik trust 런북](/home/donghyeon/workspace/docs/platform/runbooks/2026-07-23-traefik-forwarded-header-trust-boundary.md)에
기록합니다. AIStor의 실제 배포 명령·결과·실패와 복구 과정은
[중앙 AIStor 배포 기록](/home/donghyeon/workspace/docs/platform/runbooks/2026-07-23-aistor-deployment.md)과
[Phase 2 수동 진입점](bootstrap/manual/phase2-keycloak-aistor.md)에 기록합니다.
AIStor 재검증 진입점은 다음과 같습니다.
```sh
cd /home/donghyeon/workspace/platform
PLATFORM_HELM_BIN=/home/donghyeon/.local/bin/helm \
bash scripts/bootstrap/apply-aistor.sh \
--license-file /home/donghyeon/.secrets/aistor/minio.license \
--root-config-file /home/donghyeon/.secrets/aistor/root.env \
--execute
bash scripts/validate/aistor-s3-smoke.sh --execute
```
첫 명령은 기존 Secret과 Retain PV를 회전·삭제하지 않고 계약과 readiness를
재검증한다. 두 번째 명령은 임시 버킷과 객체를 만들었다가 삭제하며, 자격 증명
payload는 출력하지 않는다.
## 호스트 Nginx
[Gitea Nginx 설정](infrastructure/networking/host-nginx/learn-services.conf)을
기반으로 한 Gitea proxy는 현재 Host Nginx에 적용되어 있고 로컬·public HTTPS
health를 통과했습니다. `id.learn.hyeonworks.com`
[Keycloak 포함 설정](infrastructure/networking/host-nginx/learn-services-keycloak.conf)으로
전환되어 공개 discovery issuer 검사를 통과했습니다.
ID 전환은 `scripts/bootstrap/apply-host-nginx-keycloak.sh --execute`가 공개
discovery 사전 검사, 기존 설정 백업, `nginx -t`, 다시 불러오기와 HTTPS 사후
검사를 수행하도록 합니다. 실패 시 후보를 방치하지 않고 백업 설정을 복원합니다.
실제 전환 성공 뒤 `scripts/bootstrap/apply-gitea-oidc.sh --execute`로 Gitea
OIDC source와 브랜딩도 live Deployment에 적용했습니다. 세부 경계는
[Nginx 수동 전환 절차](infrastructure/networking/host-nginx/README.md)를 따릅니다.
## 저장소 규칙
- Kubernetes 리소스는 Kustomize `base`와 환경별 `overlays`로 구분합니다.
- 공식 Helm 차트 원본은 수정하지 않고 고정 버전, SHA-256, 저장소 관리
values·patch로 설정합니다.
- 클러스터 진입점과 향후 GitOps 애플리케이션 선언은 `clusters/home`에서
관리합니다.
- 공통 컨트롤러와 기반 리소스는 `infrastructure`, 플랫폼 서비스는
`services`에서 관리합니다.
- 반복 정책과 향후 서비스 메시 확장 지점은 `components`에서 관리합니다.
- 자동화는 `scripts`, 사람이 검토하는 최초 적용 절차는 `bootstrap/manual`,
상세 운영 기록은 중앙 문서에 둡니다.
- 생성된 Helm 캐시와 렌더 산출물 대신 재현 가능한 선언만 버전 관리합니다.