From f4be149851c595ab54bb73b188968af0478aae14 Mon Sep 17 00:00:00 2001 From: eedo_y Date: Mon, 6 Apr 2026 00:38:48 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20SecretManager=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=EB=B0=8F=20=EC=9A=B4=EC=98=81=EC=9A=A9=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- terraform/environments/prod/outputs.tf | 13 +++++ terraform/environments/prod/secret-manager.tf | 13 +++++ .../prod/terraform.tfvars.example | 16 ++++++ terraform/environments/prod/variables.tf | 29 ++++++++++ terraform/modules/secret-manager/main.tf | 57 +++++++++++++++++++ terraform/modules/secret-manager/outputs.tf | 18 ++++++ terraform/modules/secret-manager/variables.tf | 52 +++++++++++++++++ 7 files changed, 198 insertions(+) create mode 100644 terraform/environments/prod/secret-manager.tf create mode 100644 terraform/modules/secret-manager/main.tf create mode 100644 terraform/modules/secret-manager/outputs.tf create mode 100644 terraform/modules/secret-manager/variables.tf diff --git a/terraform/environments/prod/outputs.tf b/terraform/environments/prod/outputs.tf index 265799b..2dfccf8 100644 --- a/terraform/environments/prod/outputs.tf +++ b/terraform/environments/prod/outputs.tf @@ -70,6 +70,19 @@ output "artifact_registry_docker_repository_urls" { value = module.artifact_registry.docker_repository_urls } +# ======================================== +# Secret Manager 출력값 +# ======================================== +output "secret_manager_secrets" { + description = "생성된 Secret Manager secret 정보입니다." + value = module.secret_manager.secrets +} + +output "secret_manager_secret_ids" { + description = "생성된 Secret Manager secret ID 목록입니다." + value = module.secret_manager.secret_ids +} + # ======================================== # 로드 밸런서 출력값 # ======================================== diff --git a/terraform/environments/prod/secret-manager.tf b/terraform/environments/prod/secret-manager.tf new file mode 100644 index 0000000..fb2c4c9 --- /dev/null +++ b/terraform/environments/prod/secret-manager.tf @@ -0,0 +1,13 @@ +# ======================================== +# Secret Manager 모듈 +# ======================================== +module "secret_manager" { + source = "../../modules/secret-manager" + + project_id = var.project_id + common_tags = var.common_tags + + secret_ids = var.secret_manager_secret_ids + secret_iam_bindings = var.secret_manager_secret_iam_bindings + secret_iam_members = var.secret_manager_secret_iam_members +} diff --git a/terraform/environments/prod/terraform.tfvars.example b/terraform/environments/prod/terraform.tfvars.example index 26e1143..17425e3 100644 --- a/terraform/environments/prod/terraform.tfvars.example +++ b/terraform/environments/prod/terraform.tfvars.example @@ -80,3 +80,19 @@ allowed_cors_origins = [ # 로드 밸런서 관련 값 # ======================================== create_load_balancer = true + +# ======================================== +# Secret Manager 관련 값 +# ======================================== +secret_manager_secret_ids = [ + "Prod_BE_DB_URL" + ] + +ESO 또는 특정 서비스 계정에 접근 권한을 줄 때만 아래 값을 채웁니다. +secret_manager_secret_iam_members = { + eso = { + secret_id = "Prod_BE_DB_URL" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:example-secrets@example-pinhouse.iam.gserviceaccount.com" + } +} \ No newline at end of file diff --git a/terraform/environments/prod/variables.tf b/terraform/environments/prod/variables.tf index b676147..dc902f6 100644 --- a/terraform/environments/prod/variables.tf +++ b/terraform/environments/prod/variables.tf @@ -142,6 +142,35 @@ variable "google_api_domain_option" { } } +# ======================================== +# Secret Manager 관련 변수 +# ======================================== +variable "secret_manager_secret_ids" { + description = "생성할 Secret Manager secret ID 목록입니다. 값 자체는 생성하지 않고 secret container만 생성합니다." + type = set(string) + default = [] +} + +variable "secret_manager_secret_iam_bindings" { + description = "Secret Manager secret별 IAM 바인딩 정의 목록입니다." + type = map(object({ + secret_id = string + role = string + members = list(string) + })) + default = {} +} + +variable "secret_manager_secret_iam_members" { + description = "Secret Manager secret별 IAM 멤버 정의 목록입니다." + type = map(object({ + secret_id = string + role = string + member = string + })) + default = {} +} + # ======================================== # 컴퓨트 관련 변수 # ======================================== diff --git a/terraform/modules/secret-manager/main.tf b/terraform/modules/secret-manager/main.tf new file mode 100644 index 0000000..dab7ced --- /dev/null +++ b/terraform/modules/secret-manager/main.tf @@ -0,0 +1,57 @@ +# ======================================== +# 공통 로컬 값 +# ======================================== +locals { + normalized_common_tags = { + for key, value in var.common_tags : + lower(key) => lower(value) + } +} + +# ======================================== +# Secret Manager API 리소스 +# ======================================== +resource "google_project_service" "secret_manager_api" { + project = var.project_id + service = "secretmanager.googleapis.com" + disable_on_destroy = false +} + +# ======================================== +# Secret Manager 리소스 +# ======================================== +resource "google_secret_manager_secret" "secrets" { + for_each = var.secret_ids + + project = var.project_id + secret_id = each.value + + replication { + auto {} + } + + labels = local.normalized_common_tags + + depends_on = [google_project_service.secret_manager_api] +} + +# ======================================== +# Secret IAM 리소스 +# ======================================== +resource "google_secret_manager_secret_iam_binding" "secret_iam" { + for_each = var.secret_iam_bindings + + project = var.project_id + secret_id = google_secret_manager_secret.secrets[each.value.secret_id].secret_id + role = each.value.role + members = each.value.members +} + +resource "google_secret_manager_secret_iam_member" "secret_iam_member" { + for_each = var.secret_iam_members + + project = var.project_id + secret_id = google_secret_manager_secret.secrets[each.value.secret_id].secret_id + role = each.value.role + member = each.value.member +} diff --git a/terraform/modules/secret-manager/outputs.tf b/terraform/modules/secret-manager/outputs.tf new file mode 100644 index 0000000..24b3463 --- /dev/null +++ b/terraform/modules/secret-manager/outputs.tf @@ -0,0 +1,18 @@ +# ======================================== +# Secret Manager 출력값 +# ======================================== +output "secrets" { + description = "생성된 Secret Manager secret 정보입니다." + value = { + for key, value in google_secret_manager_secret.secrets : key => { + id = value.id + name = value.name + secret_id = value.secret_id + } + } +} + +output "secret_ids" { + description = "생성된 Secret Manager secret ID 목록입니다." + value = sort(keys(google_secret_manager_secret.secrets)) +} diff --git a/terraform/modules/secret-manager/variables.tf b/terraform/modules/secret-manager/variables.tf new file mode 100644 index 0000000..ce787f5 --- /dev/null +++ b/terraform/modules/secret-manager/variables.tf @@ -0,0 +1,52 @@ +# ======================================== +# Secret Manager 기본 변수 +# ======================================== +variable "project_id" { + description = "Secret Manager를 생성할 GCP 프로젝트 ID입니다." + type = string +} + +variable "common_tags" { + description = "모든 Secret Manager 리소스에 공통 적용할 태그입니다." + type = map(string) + default = {} +} + +# ======================================== +# Secret 정의 변수 +# ======================================== +variable "secret_ids" { + description = "생성할 Secret Manager secret ID 목록입니다." + type = set(string) + default = [] + + validation { + condition = alltrue([ + for secret_id in var.secret_ids : length(regexall("^[A-Za-z0-9_-]{1,255}$", secret_id)) > 0 + ]) + error_message = "secret_ids 값은 1~255자의 영문 대소문자, 숫자, 밑줄(_), 하이픈(-)만 사용할 수 있습니다." + } +} + +# ======================================== +# Secret IAM 변수 +# ======================================== +variable "secret_iam_bindings" { + description = "secret별 IAM 바인딩 정의 목록입니다." + type = map(object({ + secret_id = string + role = string + members = list(string) + })) + default = {} +} + +variable "secret_iam_members" { + description = "secret별 IAM 멤버 정의 목록입니다." + type = map(object({ + secret_id = string + role = string + member = string + })) + default = {} +} From 94c67344416e616e74a8996e55c8d132ae171e85 Mon Sep 17 00:00:00 2001 From: eedo_y Date: Mon, 6 Apr 2026 01:23:04 +0900 Subject: [PATCH 2/5] =?UTF-8?q?chore:=20argoCD=20helm=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s-helm/.gitignore | 6 ++ .../releases/argocd/values-image-updater.yaml | 72 +++++++++++++++++++ .../argocd/values-nonprod.yaml.example | 19 +++++ .../releases/argocd/values-prod.yaml.example | 19 +++++ 4 files changed, 116 insertions(+) create mode 100644 k8s-helm/releases/argocd/values-image-updater.yaml create mode 100644 k8s-helm/releases/argocd/values-nonprod.yaml.example create mode 100644 k8s-helm/releases/argocd/values-prod.yaml.example diff --git a/k8s-helm/.gitignore b/k8s-helm/.gitignore index 7092c96..ce625d2 100644 --- a/k8s-helm/.gitignore +++ b/k8s-helm/.gitignore @@ -12,6 +12,12 @@ platform-chart/**/README.md releases/**/secrets/*.yaml !releases/**/secrets/*.yaml.example +# ======================================== +# ArgoCD +# ======================================== +releases/argocd/values-*.yaml +!releases/argocd/*.yaml.example + # ======================================== # 로컬 오버라이드 파일 # ======================================== diff --git a/k8s-helm/releases/argocd/values-image-updater.yaml b/k8s-helm/releases/argocd/values-image-updater.yaml new file mode 100644 index 0000000..de811e9 --- /dev/null +++ b/k8s-helm/releases/argocd/values-image-updater.yaml @@ -0,0 +1,72 @@ +# ArgoCD Image Updater 생성 + +# 기본 설정 +fullnameOverride: argocd-image-updater + +config: + # ArgoCD 연결 설정 + argocd: + # ArgoCD가 같은 클러스터에 있으므로 내부 서비스 사용 + grpcWeb: false + serverAddress: "http://argocd-server.argocd.svc.cluster.local" + insecure: true + plaintext: true + + # Git write-back 설정 + git: + user: argocd-image-updater + email: argocd-image-updater@noreply.github.com + + # GCP Artifact Registry 설정 + registries: + - name: GCP Artifact Registry + prefix: asia-northeast3-docker.pkg.dev + api_url: https://asia-northeast3-docker.pkg.dev + credentials: ext:/scripts/gcp-artifact-registry-login.sh + credsexpire: 45m + default: true + + # 로그 레벨 + logLevel: info + +# GCP Artifact Registry 인증 스크립트 +# self-managed GCE Kubernetes 환경을 전제로, 노드에 연결된 서비스 계정의 +# metadata server access token을 사용합니다. +# 해당 서비스 계정에는 roles/artifactregistry.reader 권한이 필요합니다. +authScripts: + enabled: true + scripts: + gcp-artifact-registry-login.sh: | + #!/bin/sh + set -eu + + METADATA_URL="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" + TOKEN_RESPONSE="$(wget -qO- --header='Metadata-Flavor: Google' "${METADATA_URL}" | tr -d '\n')" + ACCESS_TOKEN="$(printf '%s' "${TOKEN_RESPONSE}" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')" + + if [ -z "${ACCESS_TOKEN}" ]; then + echo "failed to fetch Artifact Registry access token from metadata server" >&2 + exit 1 + fi + + printf 'oauth2accesstoken:%s\n' "${ACCESS_TOKEN}" + +# 리소스 제한 +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + +# 업데이트 주기 +image: + pullPolicy: IfNotPresent + +# ServiceAccount 설정 +serviceAccount: + create: true + name: argocd-image-updater + +# Git 환경 변수는 UI에서 주입 diff --git a/k8s-helm/releases/argocd/values-nonprod.yaml.example b/k8s-helm/releases/argocd/values-nonprod.yaml.example new file mode 100644 index 0000000..d144534 --- /dev/null +++ b/k8s-helm/releases/argocd/values-nonprod.yaml.example @@ -0,0 +1,19 @@ +# Copy to values-nonprod.yaml and add argo/argo-cd overrides here. +# Example: +global: + domain: domain + +configs: + cm: + # Git/Helm 변경 감지 주기. + timeout.reconciliation: "60s" + timeout.reconciliation.jitter: "15s" + + # TLS Termination을 Gateway에서 수행하므로 ArgoCD Server는 insecure 모드로 실행 + params: + server.insecure: "true" + +# ArgoCD Server에 --insecure 플래그 추가 +server: + extraArgs: + - --insecure diff --git a/k8s-helm/releases/argocd/values-prod.yaml.example b/k8s-helm/releases/argocd/values-prod.yaml.example new file mode 100644 index 0000000..d144534 --- /dev/null +++ b/k8s-helm/releases/argocd/values-prod.yaml.example @@ -0,0 +1,19 @@ +# Copy to values-nonprod.yaml and add argo/argo-cd overrides here. +# Example: +global: + domain: domain + +configs: + cm: + # Git/Helm 변경 감지 주기. + timeout.reconciliation: "60s" + timeout.reconciliation.jitter: "15s" + + # TLS Termination을 Gateway에서 수행하므로 ArgoCD Server는 insecure 모드로 실행 + params: + server.insecure: "true" + +# ArgoCD Server에 --insecure 플래그 추가 +server: + extraArgs: + - --insecure From f1b769273a7127b4ddb412a91a07a0953add39fe Mon Sep 17 00:00:00 2001 From: eedo_y Date: Mon, 6 Apr 2026 01:29:41 +0900 Subject: [PATCH 3/5] =?UTF-8?q?chore:=20argoCD=20kustomize=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/argocd/base/kustomization.yaml | 5 + .../argocd/base/notifications-cm.yaml | 736 ++++++++++++++++++ .../argocd/overlays/nonprod/httproute.yaml | 27 + .../overlays/nonprod/kustomization.yaml | 9 + .../notifications-cm-context-patch.yaml | 8 + .../argocd/overlays/prod/httproute.yaml | 27 + .../argocd/overlays/prod/kustomization.yaml | 9 + .../prod/notifications-cm-context-patch.yaml | 8 + .../secrets/notifications-secret.yaml.example | 48 ++ 9 files changed, 877 insertions(+) create mode 100644 k8s-kustomize/platform/argocd/base/kustomization.yaml create mode 100644 k8s-kustomize/platform/argocd/base/notifications-cm.yaml create mode 100644 k8s-kustomize/platform/argocd/overlays/nonprod/httproute.yaml create mode 100644 k8s-kustomize/platform/argocd/overlays/nonprod/kustomization.yaml create mode 100644 k8s-kustomize/platform/argocd/overlays/nonprod/notifications-cm-context-patch.yaml create mode 100644 k8s-kustomize/platform/argocd/overlays/prod/httproute.yaml create mode 100644 k8s-kustomize/platform/argocd/overlays/prod/kustomization.yaml create mode 100644 k8s-kustomize/platform/argocd/overlays/prod/notifications-cm-context-patch.yaml create mode 100644 k8s-kustomize/platform/argocd/secrets/notifications-secret.yaml.example diff --git a/k8s-kustomize/platform/argocd/base/kustomization.yaml b/k8s-kustomize/platform/argocd/base/kustomization.yaml new file mode 100644 index 0000000..00f81e0 --- /dev/null +++ b/k8s-kustomize/platform/argocd/base/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - notifications-cm.yaml diff --git a/k8s-kustomize/platform/argocd/base/notifications-cm.yaml b/k8s-kustomize/platform/argocd/base/notifications-cm.yaml new file mode 100644 index 0000000..197b7d3 --- /dev/null +++ b/k8s-kustomize/platform/argocd/base/notifications-cm.yaml @@ -0,0 +1,736 @@ +# =================================== +# ArgoCD Discord 알림 템플릿 및 트리거 설정 +# =================================== +# +# 이 파일은 Argo CD 운영 부가 리소스의 base ConfigMap입니다. +# 환경별 Argo CD URL은 overlays/prod 또는 overlays/nonprod 패치에서 덮어씁니다. +# =================================== + +apiVersion: v1 +kind: ConfigMap + +# 기본 정보 +metadata: + name: argocd-notifications-cm + namespace: argocd + +# 데이터 +data: + context.argocdUrl: "https://argo.example.com" + + # =================================== + # Discord Webhook 서비스 설정 + # =================================== + # Discord는 webhook 타입으로 설정해야 합니다 + + # Frontend Nonprod (Dev + Staging) + service.webhook.frontend-nonprod: | + url: $discord-frontend-nonprod-webhook + headers: + - name: Content-Type + value: application/json + + # Backend Nonprod (Dev + Staging) + service.webhook.backend-nonprod: | + url: $discord-backend-nonprod-webhook + headers: + - name: Content-Type + value: application/json + + # Frontend Production + service.webhook.frontend-prod: | + url: $discord-frontend-prod-webhook + headers: + - name: Content-Type + value: application/json + + # Backend Production + service.webhook.backend-prod: | + url: $discord-backend-prod-webhook + headers: + - name: Content-Type + value: application/json + + # =================================== + # 템플릿: 배포 완료 (Deployed) + # =================================== + template.app-deployed: | + webhook: + frontend-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "✅ 배포 완료", + "description": "ArgoCD 배포가 성공적으로 완료되었습니다.", + "color": 3066993, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "🔖 Revision", + "value": "{{.app.status.sync.revision | substr 0 7}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ], + "timestamp": "{{.app.status.operationState.finishedAt}}" + }] + } + backend-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "✅ 배포 완료", + "description": "ArgoCD 배포가 성공적으로 완료되었습니다.", + "color": 3066993, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "🔖 Revision", + "value": "{{.app.status.sync.revision | substr 0 7}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ], + "timestamp": "{{.app.status.operationState.finishedAt}}" + }] + } + ai-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "✅ 배포 완료", + "description": "ArgoCD 배포가 성공적으로 완료되었습니다.", + "color": 3066993, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "🔖 Revision", + "value": "{{.app.status.sync.revision | substr 0 7}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ], + "timestamp": "{{.app.status.operationState.finishedAt}}" + }] + } + frontend-prod: + method: POST + body: | + { + "embeds": [{ + "title": "✅ 배포 완료", + "description": "ArgoCD 배포가 성공적으로 완료되었습니다.", + "color": 3066993, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "🔖 Revision", + "value": "{{.app.status.sync.revision | substr 0 7}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ], + "timestamp": "{{.app.status.operationState.finishedAt}}" + }] + } + backend-prod: + method: POST + body: | + { + "embeds": [{ + "title": "✅ 배포 완료", + "description": "ArgoCD 배포가 성공적으로 완료되었습니다.", + "color": 3066993, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "🔖 Revision", + "value": "{{.app.status.sync.revision | substr 0 7}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ], + "timestamp": "{{.app.status.operationState.finishedAt}}" + }] + } + ai-prod: + method: POST + body: | + { + "embeds": [{ + "title": "✅ 배포 완료", + "description": "ArgoCD 배포가 성공적으로 완료되었습니다.", + "color": 3066993, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "🔖 Revision", + "value": "{{.app.status.sync.revision | substr 0 7}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ], + "timestamp": "{{.app.status.operationState.finishedAt}}" + }] + } + + # =================================== + # 템플릿: 동기화 실패 (Sync Failed) + # =================================== + template.app-sync-failed: | + webhook: + frontend-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "❌ 동기화 실패", + "description": "ArgoCD 동기화에 실패했습니다.", + "color": 15158332, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "⚠️ Error", + "value": "{{.app.status.operationState.message}}", + "inline": false + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + backend-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "❌ 동기화 실패", + "description": "ArgoCD 동기화에 실패했습니다.", + "color": 15158332, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "⚠️ Error", + "value": "{{.app.status.operationState.message}}", + "inline": false + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + ai-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "❌ 동기화 실패", + "description": "ArgoCD 동기화에 실패했습니다.", + "color": 15158332, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "⚠️ Error", + "value": "{{.app.status.operationState.message}}", + "inline": false + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + frontend-prod: + method: POST + body: | + { + "embeds": [{ + "title": "❌ 동기화 실패", + "description": "ArgoCD 동기화에 실패했습니다.", + "color": 15158332, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "⚠️ Error", + "value": "{{.app.status.operationState.message}}", + "inline": false + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + backend-prod: + method: POST + body: | + { + "embeds": [{ + "title": "❌ 동기화 실패", + "description": "ArgoCD 동기화에 실패했습니다.", + "color": 15158332, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "⚠️ Error", + "value": "{{.app.status.operationState.message}}", + "inline": false + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + ai-prod: + method: POST + body: | + { + "embeds": [{ + "title": "❌ 동기화 실패", + "description": "ArgoCD 동기화에 실패했습니다.", + "color": 15158332, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "📍 Namespace", + "value": "{{.app.spec.destination.namespace}}", + "inline": true + }, + { + "name": "⚠️ Error", + "value": "{{.app.status.operationState.message}}", + "inline": false + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + + # =================================== + # 템플릿: 헬스 체크 이상 (Health Degraded) + # =================================== + template.app-health-degraded: | + webhook: + frontend-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "⚠️ 헬스 체크 이상", + "description": "애플리케이션 헬스 상태가 비정상입니다.", + "color": 16776960, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "🏥 Health Status", + "value": "{{.app.status.health.status}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + backend-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "⚠️ 헬스 체크 이상", + "description": "애플리케이션 헬스 상태가 비정상입니다.", + "color": 16776960, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "🏥 Health Status", + "value": "{{.app.status.health.status}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + ai-nonprod: + method: POST + body: | + { + "embeds": [{ + "title": "⚠️ 헬스 체크 이상", + "description": "애플리케이션 헬스 상태가 비정상입니다.", + "color": 16776960, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "🏥 Health Status", + "value": "{{.app.status.health.status}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + frontend-prod: + method: POST + body: | + { + "embeds": [{ + "title": "⚠️ 헬스 체크 이상", + "description": "애플리케이션 헬스 상태가 비정상입니다.", + "color": 16776960, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "🏥 Health Status", + "value": "{{.app.status.health.status}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + backend-prod: + method: POST + body: | + { + "embeds": [{ + "title": "⚠️ 헬스 체크 이상", + "description": "애플리케이션 헬스 상태가 비정상입니다.", + "color": 16776960, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "🏥 Health Status", + "value": "{{.app.status.health.status}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + ai-prod: + method: POST + body: | + { + "embeds": [{ + "title": "⚠️ 헬스 체크 이상", + "description": "애플리케이션 헬스 상태가 비정상입니다.", + "color": 16776960, + "fields": [ + { + "name": "🌍 Environment", + "value": "{{ index .app.metadata.labels "pinhouse.co.kr/environment" | upper }}", + "inline": true + }, + { + "name": "📦 Application", + "value": "{{.app.metadata.name}}", + "inline": true + }, + { + "name": "🏥 Health Status", + "value": "{{.app.status.health.status}}", + "inline": true + }, + { + "name": "🔗 ArgoCD", + "value": "[바로가기]({{.context.argocdUrl}}/applications/{{.app.metadata.name}})", + "inline": false + } + ] + }] + } + + # =================================== + # 트리거 설정 + # =================================== + # 배포 완료 시 알림 + trigger.on-deployed: | + - description: Application is synced and healthy + send: + - app-deployed + when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy' + + # 동기화 실패 시 알림 + trigger.on-sync-failed: | + - description: Application syncing has failed + send: + - app-sync-failed + when: app.status.operationState.phase in ['Error', 'Failed'] + + # 헬스 체크 이상 시 알림 + trigger.on-health-degraded: | + - description: Application has degraded + send: + - app-health-degraded + when: app.status.health.status == 'Degraded' diff --git a/k8s-kustomize/platform/argocd/overlays/nonprod/httproute.yaml b/k8s-kustomize/platform/argocd/overlays/nonprod/httproute.yaml new file mode 100644 index 0000000..e68fe3f --- /dev/null +++ b/k8s-kustomize/platform/argocd/overlays/nonprod/httproute.yaml @@ -0,0 +1,27 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute + +# 기본 설정 +metadata: + name: argocd + namespace: argocd + +# 스펙 +spec: + parentRefs: + - name: pinhouse-gateway + namespace: nginx-gateway + + # 도메인 명 + hostnames: + - "nonprod.argo.pinhouse.co.kr" + + # 참조 규칙 + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: argocd-server + port: 80 diff --git a/k8s-kustomize/platform/argocd/overlays/nonprod/kustomization.yaml b/k8s-kustomize/platform/argocd/overlays/nonprod/kustomization.yaml new file mode 100644 index 0000000..5b89931 --- /dev/null +++ b/k8s-kustomize/platform/argocd/overlays/nonprod/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + - httproute.yaml + +patches: + - path: notifications-cm-context-patch.yaml diff --git a/k8s-kustomize/platform/argocd/overlays/nonprod/notifications-cm-context-patch.yaml b/k8s-kustomize/platform/argocd/overlays/nonprod/notifications-cm-context-patch.yaml new file mode 100644 index 0000000..5019792 --- /dev/null +++ b/k8s-kustomize/platform/argocd/overlays/nonprod/notifications-cm-context-patch.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: argocd-notifications-cm + namespace: argocd + +data: + context.argocdUrl: "https://nonprod.argo.pinhouse.co.kr" diff --git a/k8s-kustomize/platform/argocd/overlays/prod/httproute.yaml b/k8s-kustomize/platform/argocd/overlays/prod/httproute.yaml new file mode 100644 index 0000000..339215a --- /dev/null +++ b/k8s-kustomize/platform/argocd/overlays/prod/httproute.yaml @@ -0,0 +1,27 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute + +# 기본 설정 +metadata: + name: argocd + namespace: argocd + +# 스펙 +spec: + parentRefs: + - name: pinhouse-gateway + namespace: nginx-gateway + + # 도메인 명 + hostnames: + - "argo.pinhouse.co.kr" + + # 참조 규칙 + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: argocd-server + port: 80 diff --git a/k8s-kustomize/platform/argocd/overlays/prod/kustomization.yaml b/k8s-kustomize/platform/argocd/overlays/prod/kustomization.yaml new file mode 100644 index 0000000..5b89931 --- /dev/null +++ b/k8s-kustomize/platform/argocd/overlays/prod/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../base + - httproute.yaml + +patches: + - path: notifications-cm-context-patch.yaml diff --git a/k8s-kustomize/platform/argocd/overlays/prod/notifications-cm-context-patch.yaml b/k8s-kustomize/platform/argocd/overlays/prod/notifications-cm-context-patch.yaml new file mode 100644 index 0000000..b475a1c --- /dev/null +++ b/k8s-kustomize/platform/argocd/overlays/prod/notifications-cm-context-patch.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: argocd-notifications-cm + namespace: argocd + +data: + context.argocdUrl: "https://argo.pinhouse.co.kr" diff --git a/k8s-kustomize/platform/argocd/secrets/notifications-secret.yaml.example b/k8s-kustomize/platform/argocd/secrets/notifications-secret.yaml.example new file mode 100644 index 0000000..8c46abf --- /dev/null +++ b/k8s-kustomize/platform/argocd/secrets/notifications-secret.yaml.example @@ -0,0 +1,48 @@ +# =================================== +# ArgoCD Notifications Secret +# Discord Webhook 설정 +# =================================== + +# 사용 방법: +# 1. 이 파일을 복사하여 notifications-secret.yaml 생성 +# cp k8s-kustomize/platform/argocd/secrets/notifications-secret.yaml.example \ +# k8s-kustomize/platform/argocd/secrets/notifications-secret.yaml +# +# 2. Discord Webhook URL을 실제 값으로 변경 +# +# 3. kubectl apply -f k8s-kustomize/platform/argocd/secrets/notifications-secret.yaml +# =================================== + +apiVersion: v1 +kind: Secret + +# 기본 정보 +metadata: + name: argocd-notifications-secret + namespace: argocd + +# 시크릿 타입 +type: Opaque + +# 값 +stringData: + + # =================================== + # NonProd + # =================================== + + # Frontend Nonprod 환경 Discord Webhook URL (Dev + Staging) + discord-frontend-nonprod-webhook: "YOUR_FRONTEND_NONPROD_DISCORD_WEBHOOK_URL_HERE" + + # Backend Nonprod 환경 Discord Webhook URL (Dev + Staging) + discord-backend-nonprod-webhook: "YOUR_BACKEND_NONPROD_DISCORD_WEBHOOK_URL_HERE" + + # =================================== + # Prod + # =================================== + + # Frontend Production 환경 Discord Webhook URL + discord-frontend-prod-webhook: "YOUR_FRONTEND_PROD_DISCORD_WEBHOOK_URL_HERE" + + # Backend Production 환경 Discord Webhook URL + discord-backend-prod-webhook: "YOUR_BACKEND_PROD_DISCORD_WEBHOOK_URL_HERE" From b752a09c7e1c31621889eb9bde9e4460432bfeb1 Mon Sep 17 00:00:00 2001 From: eedo_y Date: Mon, 6 Apr 2026 01:29:57 +0900 Subject: [PATCH 4/5] =?UTF-8?q?chore:=20argoCD=20cronJob=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EA=B8=B0=EB=8A=A5=20=EC=A0=9C=EA=B1=B0=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s-helm/platform-chart/values-nonprod.yaml.example | 7 ------- k8s-helm/platform-chart/values-prod.yaml.example | 8 -------- k8s-helm/platform-chart/values.yaml | 9 --------- k8s-kustomize/.gitignore | 5 +++++ 4 files changed, 5 insertions(+), 24 deletions(-) create mode 100644 k8s-kustomize/.gitignore diff --git a/k8s-helm/platform-chart/values-nonprod.yaml.example b/k8s-helm/platform-chart/values-nonprod.yaml.example index 4044283..d78960f 100644 --- a/k8s-helm/platform-chart/values-nonprod.yaml.example +++ b/k8s-helm/platform-chart/values-nonprod.yaml.example @@ -189,10 +189,3 @@ externalSecrets: - regexp: source: "^Stg_BE_(.*)$" target: "$1" - -argocd: - imageUpdater: - restartCronJob: - enabled: true - # ECR 토큰은 12시간마다 만료되므로 6시간마다 재시작 - schedule: "0 */6 * * *" diff --git a/k8s-helm/platform-chart/values-prod.yaml.example b/k8s-helm/platform-chart/values-prod.yaml.example index 2b8303c..8edcac8 100644 --- a/k8s-helm/platform-chart/values-prod.yaml.example +++ b/k8s-helm/platform-chart/values-prod.yaml.example @@ -168,11 +168,3 @@ externalSecrets: - regexp: source: "^Prod_BE_(.*)$" target: "$1" - - -# ArgoCD 적용 -argocd: - imageUpdater: - restartCronJob: - enabled: true - schedule: "0 */6 * * *" diff --git a/k8s-helm/platform-chart/values.yaml b/k8s-helm/platform-chart/values.yaml index 04f8f68..468aacc 100644 --- a/k8s-helm/platform-chart/values.yaml +++ b/k8s-helm/platform-chart/values.yaml @@ -112,12 +112,3 @@ externalSecrets: # ExternalSecret 리소스 목록 secrets: [] - -# ArgoCD 설정 -argocd: - # ArgoCD Image Updater ECR 토큰 자동 갱신 - imageUpdater: - restartCronJob: - enabled: false - # 0 */6 * * * = 매 6시간마다 (0시, 6시, 12시, 18시) - schedule: "0 */6 * * *" diff --git a/k8s-kustomize/.gitignore b/k8s-kustomize/.gitignore new file mode 100644 index 0000000..c53f714 --- /dev/null +++ b/k8s-kustomize/.gitignore @@ -0,0 +1,5 @@ +# ======================================== +# ArgoCD 차트 +# ======================================== +platform/**/secrets/*.yaml +!platform/**/secrets/*.yaml.example From 689dc890a04f159f5372f284f29840a531d6f55f Mon Sep 17 00:00:00 2001 From: eedo_y Date: Mon, 6 Apr 2026 01:39:03 +0900 Subject: [PATCH 5/5] =?UTF-8?q?style:=20terraform=20fmt=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- terraform/environments/prod/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/environments/prod/variables.tf b/terraform/environments/prod/variables.tf index dc902f6..4a85902 100644 --- a/terraform/environments/prod/variables.tf +++ b/terraform/environments/prod/variables.tf @@ -148,7 +148,7 @@ variable "google_api_domain_option" { variable "secret_manager_secret_ids" { description = "생성할 Secret Manager secret ID 목록입니다. 값 자체는 생성하지 않고 secret container만 생성합니다." type = set(string) - default = [] + default = [] } variable "secret_manager_secret_iam_bindings" {