diff --git a/deploy/application.yaml b/deploy/application.yaml new file mode 100644 index 0000000..7f097ca --- /dev/null +++ b/deploy/application.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: profanity-filter + namespace: argocd +spec: + project: profanity + source: + repoURL: https://github.com/Whale0928/profanity-filter-api + path: deploy/overlays/production + targetRevision: deploy + destination: + server: https://kubernetes.default.svc + namespace: profanity-production + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/deploy/overlays/production/deployment.yaml b/deploy/overlays/production/deployment.yaml new file mode 100644 index 0000000..1946c62 --- /dev/null +++ b/deploy/overlays/production/deployment.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: profanity-api + namespace: profanity-production + labels: + app: profanity-api + environment: production +spec: + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app: profanity-api + template: + metadata: + labels: + app: profanity-api + environment: production + spec: + imagePullSecrets: + - name: ghcr-secret + nodeSelector: + kubernetes.io/arch: arm64 + containers: + - name: profanity-api + image: ghcr.io/whale0928/profanity-api:deploy-877aa7d + imagePullPolicy: Always + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + - name: SERVER_PORT + value: "8080" + - name: ACTUATOR_PATH + value: "/health" + - name: SPRING_PROFILES_ACTIVE + value: "prod" + envFrom: + - secretRef: + name: profanity-secrets + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "250m" + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 120 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 90 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 diff --git a/deploy/overlays/production/external-secret.yaml b/deploy/overlays/production/external-secret.yaml new file mode 100644 index 0000000..3a1877f --- /dev/null +++ b/deploy/overlays/production/external-secret.yaml @@ -0,0 +1,18 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: profanity-secrets + namespace: profanity-production +spec: + refreshInterval: 1h + secretStoreRef: + name: onepassword-store + kind: SecretStore + target: + name: profanity-secrets + creationPolicy: Owner + dataFrom: + - extract: + key: secret-source + - extract: + key: database-source diff --git a/deploy/overlays/production/kustomization.yaml b/deploy/overlays/production/kustomization.yaml new file mode 100644 index 0000000..5d9274c --- /dev/null +++ b/deploy/overlays/production/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: profanity-production + +resources: + - resources.yaml + - deployment.yaml + - external-secret.yaml diff --git a/deploy/overlays/production/resources.yaml b/deploy/overlays/production/resources.yaml new file mode 100644 index 0000000..f8435bd --- /dev/null +++ b/deploy/overlays/production/resources.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: profanity-production + labels: + app: profanity-api + environment: production +--- +apiVersion: v1 +kind: Service +metadata: + name: profanity-api + namespace: profanity-production + labels: + app: profanity-api + environment: production +spec: + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 8080 + protocol: TCP + selector: + app: profanity-api +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: profanity-api + namespace: profanity-production + labels: + app: profanity-api + environment: production + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" +spec: + ingressClassName: nginx + tls: + - hosts: + - api.profanity.kr-filter.com + secretName: profanity-api-tls + rules: + - host: api.profanity.kr-filter.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: profanity-api + port: + number: 80 +--- +apiVersion: external-secrets.io/v1 +kind: SecretStore +metadata: + name: onepassword-store + namespace: profanity-production +spec: + provider: + onepassword: + connectHost: http://onepassword-connect.external-secrets-system.svc.cluster.local:8080 + vaults: + profanity-filter: 1 + auth: + secretRef: + connectTokenSecretRef: + name: op-token + key: token diff --git a/module.platform b/module.platform index 00dfc41..686fd99 160000 --- a/module.platform +++ b/module.platform @@ -1 +1 @@ -Subproject commit 00dfc418b570b365fe8e5dacd608e9ec813f041c +Subproject commit 686fd99e86c4f835ef23623ae5c3dd81edabe94d diff --git a/module.secrets b/module.secrets index 2fc25db..9fed138 160000 --- a/module.secrets +++ b/module.secrets @@ -1 +1 @@ -Subproject commit 2fc25db8b6d8acc55af4b81dbc817bee998a8ffb +Subproject commit 9fed138ab832382a30fb205222587459f0c05f81 diff --git a/plan/2025.11.21-k3s-migration.md b/plan/2025.11.21-k3s-migration.md index d4ca986..6b44b62 100644 --- a/plan/2025.11.21-k3s-migration.md +++ b/plan/2025.11.21-k3s-migration.md @@ -1,3 +1,14 @@ +``` +╔══════════════════════════════════════════════════════════════╗ +║ ║ +║ ✅ k3s 마이그레이션 완료 ║ +║ ║ +║ 완료일: 2025.11.21 ║ +║ 상태: 운영 환경 배포 완료 및 정상 작동 확인 ║ +║ ║ +╚══════════════════════════════════════════════════════════════╝ +``` + # k3s 마이그레이션 계획 ## 개요 @@ -13,6 +24,8 @@ - 모노레포: 애플리케이션 코드 + K8s 매니페스트 - 이유: 코드와 배포 설정의 버전을 함께 관리하여 일관성 유지 - 위치: `deploy/overlays/production/` +- 브랜치 전략: `deploy` 브랜치를 ArgoCD가 추적 + - 이유: main 브랜치와 분리하여 배포 설정 변경 관리 - 플랫폼: `module.platform` 서브모듈 (https://github.com/Whale0928/k8s-platform.git) - 이유: 공통 인프라를 서브모듈로 참조하여 bottlenote와 일관성 유지 @@ -20,44 +33,52 @@ - ghcr.io 사용 (ECR 불필요) - 이유: 무료, GitHub Actions 통합 용이, 토큰 갱신 불필요 (bottlenote의 ECR CronJob 제거) -- 이미지: `ghcr.io/whale0928/profanity-api:main-SHA` +- 이미지: `ghcr.io/whale0928/profanity-api:deploy-SHA` +- imagePullSecrets: `ghcr-secret` (GHCR 인증용) ### 3. 비밀 관리 - External Secrets Operator + 1Password Connect - 이유: bottlenote의 AWS Secrets Manager 대신 1Password 사용, 플랫폼 공통 컴포넌트로 관리 - 1Password Connect는 platform에 배포 (`platform/external-secrets/onepassword-connect/`) +- 자동 동기화: 1시간마다 1Password vault에서 Kubernetes Secret으로 동기화 - 주의: credentials, token은 절대 Git 커밋 금지 (공개 레포) ### 4. 배포 -- ArgoCD GitOps + Webhook - - 이유: Image Updater 대신 webhook 사용으로 즉각 반응, 경량화 -- GitHub Actions에서 이미지 태그 업데이트 후 ArgoCD webhook 호출 +- ArgoCD GitOps + 자동 동기화 + - 자동 동기화 활성화: `prune: true`, `selfHeal: true` + - Git 폴링 주기: 3분마다 + - Webhook: 선택사항 (더 빠른 배포를 원할 경우) +- GitHub Actions에서 이미지 태그 업데이트 후 deploy 브랜치에 push - 네임스페이스: `profanity-production` - 리소스: 512Mi/250m, 레플리카 2 - 이유: bottlenote(768Mi/500m)보다 경량화 +- Health Check: + - livenessProbe: initialDelaySeconds 120초 (애플리케이션 시작 시간 고려) + - readinessProbe: initialDelaySeconds 90초 - 도메인: `api-profanity.kr-filter.com` - - 테스트 완료 후 기존 도메인으로 변경 ## 디렉토리 구조 ``` profanity-filter-api/ -└── deploy/overlays/production/ - ├── namespace.yaml - ├── deployment.yaml - ├── service.yaml - ├── ingress.yaml (cert-manager TLS) - ├── secret-store.yaml (1Password Connect 참조) - └── external-secret.yaml (1Password vault 참조) - -module.platform/ (서브모듈 - 추가할 항목) -├── argocd/ -│ └── webhook-config.yaml (ArgoCD webhook 설정) +├── deploy/ +│ ├── application.yaml (ArgoCD Application 정의) +│ └── overlays/production/ +│ ├── kustomization.yaml +│ ├── namespace.yaml +│ ├── deployment.yaml (imagePullSecrets 포함) +│ ├── service.yaml +│ ├── ingress.yaml (cert-manager TLS) +│ ├── secret-store.yaml (1Password Connect 참조) +│ └── external-secret.yaml (1Password vault 참조) + +module.platform/ (서브모듈) +├── argocd/ (기존 활용) └── platform/ └── external-secrets/ - └── onepassword-connect/ + └── onepassword-connect/ (기존 활용) ├── namespace.yaml (external-secrets-system) ├── deployment.yaml ├── service.yaml @@ -66,14 +87,21 @@ module.platform/ (서브모듈 - 추가할 항목) ## 배포 흐름 +### 현재 (수동 배포) +1. 로컬에서 Docker 이미지 빌드 +2. ghcr.io에 이미지 푸시 (태그: deploy-SHA) +3. `deploy/overlays/production/deployment.yaml` 이미지 태그 수동 업데이트 +4. Git commit & push to deploy 브랜치 +5. ArgoCD 자동 감지 및 배포 (최대 3분 이내) + +### 향후 (자동화 예정) 1. 개발자 커밋 (main 브랜치) 2. GitHub Actions 자동 실행: - 빌드 및 테스트 - - ghcr.io에 이미지 푸시 (태그: main-SHA) - - deployment.yaml 이미지 태그 업데이트 (sed) - - Git commit & push (매니페스트 변경) - - ArgoCD webhook 호출 (즉시 배포 트리거) -3. ArgoCD: Git 변경 감지 후 즉시 sync 및 배포 (Rolling Update) + - ghcr.io에 이미지 푸시 (태그: deploy-SHA) + - deployment.yaml 이미지 태그 업데이트 + - Git commit & push to deploy 브랜치 +3. ArgoCD: Git 변경 감지 후 자동 sync 및 배포 (Rolling Update) ## 환경변수 @@ -88,37 +116,48 @@ module.platform/ (서브모듈 - 추가할 항목) - 1password-credentials.json - Connect token - 모든 패스워드, API 키 +- GitHub Personal Access Token -### 수동 생성 (kubectl) +### 수동 생성 (kubectl) - 1회성 작업 ```bash -# 1Password Connect credentials -kubectl create secret generic onepassword-credentials \ - --from-file=credentials.json \ +# 1Password Connect token (external-secrets-system에 생성) +kubectl create secret generic op-token \ + --from-literal=token= \ -n external-secrets-system -# Connect token -kubectl create secret generic op-token \ - --from-literal=token=TOKEN \ +# GHCR imagePullSecret (profanity-production에 생성) +kubectl create secret docker-registry ghcr-secret \ + --docker-server=ghcr.io \ + --docker-username= \ + --docker-password= \ -n profanity-production ``` +### 자동 관리되는 Secret + +```bash +# profanity-secrets (ExternalSecret이 1시간마다 동기화) +# 1Password vault: profanity-filter > profanity-api-production +kubectl get secret profanity-secrets -n profanity-production +``` + ## GitHub Actions -### 필요한 Secrets +### 필요한 Secrets (향후 설정) - GHCR_TOKEN: ghcr.io 푸시 권한 -- ARGOCD_TOKEN: ArgoCD API 토큰 (webhook 호출용) -- ARGOCD_SERVER: ArgoCD 서버 주소 +- (선택) ARGOCD_TOKEN: ArgoCD API 토큰 (webhook 호출용) +- (선택) ARGOCD_SERVER: ArgoCD 서버 주소 -### 워크플로우 +### 워크플로우 (향후 구현) - 트리거: push to main - 빌드 및 테스트 - 이미지 빌드 및 ghcr.io 푸시 -- deployment.yaml 이미지 태그 업데이트 (sed) -- Git commit & push -- ArgoCD webhook 호출 (curl) +- deployment.yaml 이미지 태그 업데이트 +- Git commit & push to deploy 브랜치 +- ArgoCD 자동 감지 및 배포 ## Platform 인프라 @@ -129,16 +168,99 @@ k8s-platform의 공통 인프라를 활용하여 별도 설치 불필요: - cert-manager: Let's Encrypt SSL 자동 발급 및 갱신 - monitoring: Prometheus metrics 자동 수집 - ArgoCD: GitOps 기반 배포 관리 +- External Secrets Operator: 1Password Connect 연동 +- 1Password Connect: Secret 관리 + +--- + +## 최종 배포 상태 + +### 배포 일시 +- 2025.11.21 + +### Namespace +``` +✅ profanity-production (생성 완료) +``` + +### ArgoCD Application +``` +NAME: profanity-filter +STATUS: Synced ✅ +HEALTH: Healthy ✅ +TARGET REVISION: deploy 브랜치 +SYNC POLICY: Automated (prune: true, selfHeal: true) +``` + +### Deployment +``` +NAME: profanity-api +REPLICAS: 2/2 (Ready) +IMAGE: ghcr.io/whale0928/profanity-api:deploy-877aa7d +RESOURCES: + - CPU: 250m (requests/limits) + - Memory: 512Mi (requests/limits) +HEALTH CHECKS: + - livenessProbe: /health (initialDelaySeconds: 120s) + - readinessProbe: /health (initialDelaySeconds: 90s) +``` + +### Pods +``` +NAME READY STATUS RESTARTS AGE +profanity-api-77bf8d6485-27499 1/1 Running 0 정상 작동 +profanity-api-77bf8d6485-w45xb 1/1 Running 0 정상 작동 +``` + +### Service +``` +NAME: profanity-api +TYPE: ClusterIP +PORT: 80/TCP +``` + +### Ingress +``` +HOST: api-profanity.kr-filter.com +TLS: cert-manager 자동 관리 +HTTPS: ✅ 정상 작동 +``` + +### Secrets +``` +✅ ghcr-secret (imagePullSecrets) - 수동 관리 +✅ profanity-secrets (애플리케이션 환경변수) - ExternalSecret 자동 관리 +✅ profanity-api-tls (HTTPS 인증서) - cert-manager 자동 관리 +✅ op-token (1Password Connect) - 수동 관리 +``` + +### External Secrets +``` +NAME: profanity-secrets +STATUS: SecretSynced ✅ +READY: True ✅ +REFRESH INTERVAL: 1시간 +SOURCE: 1Password vault (profanity-filter > profanity-api-production) +``` + +### 1Password Connect +``` +NAMESPACE: external-secrets-system +POD: onepassword-connect +STATUS: 2/2 Running ✅ +``` + +--- -### 추가 필요 (platform 레포에 등록) +## 다음 단계 -profanity 프로젝트를 위해 platform에 추가할 컴포넌트: +### 자동화 구현 필요 +- [ ] GitHub Actions workflow 생성 (이미지 빌드 + 배포 자동화) +- [ ] ArgoCD Webhook 설정 (즉시 배포) -1. 1Password Connect (`platform/external-secrets/onepassword-connect/`) - - 1Password vault와 K8s 연동을 위한 Connect 서버 - - Deployment, Service 매니페스트 - - credentials Secret은 수동 생성 (Git 제외) +### 모니터링 +- [ ] Prometheus metrics 확인 +- [ ] Grafana 대시보드 설정 -2. ArgoCD Webhook 설정 (`argocd/webhook-config.yaml`) - - GitHub Actions에서 배포 트리거용 - - API Token 생성 및 webhook endpoint 구성 +### 도메인 전환 +- [ ] 테스트 완료 후 기존 도메인으로 변경