refactor: 구조 변경

This commit is contained in:
donghyeon-ka
2026-08-28 17:24:26 +09:00
parent a6f6c663e0
commit b8626946b1
192 changed files with 2251 additions and 206 deletions
+18
View File
@@ -0,0 +1,18 @@
# Application Deployment Catalog
애플리케이션의 source code가 아니라 Kubernetes 배포 정의를 둡니다. app 팀이
별도 source/deploy 저장소를 소유하면 이곳에는 immutable artifact를 참조하는
GitOps 리소스만 둘 수 있습니다.
```text
apps/
└── example-api/
├── base/
└── overlays/
├── dev/
└── prod/
```
base는 환경을 모르고, overlay에는 replica/resource/config처럼 필요한 차이만
둡니다. image는 mutable tag 대신 조직 정책에 따른 고정 tag 또는 digest를
사용합니다.
+22
View File
@@ -0,0 +1,22 @@
# __REPLACE_ME_APPLICATION_NAME__
## 소유자
Team: __REPLACE_ME_OWNER__
repository와 on-call 정보를 적습니다.
## 배포 계약
- Namespace:
- Image/artifact source:
- Ports/protocol:
- Dependency:
- SLO/alerts:
## 구성
- `base`: 공통 Kubernetes 배포 정의
- `overlays`: 환경별 replica, resource, config 차이
비밀은 ExternalSecret 같은 참조 또는 승인된 암호화 형식으로만 추가합니다.
+4
View File
@@ -0,0 +1,4 @@
# Base
환경을 모르는 application의 공통 manifest를 둡니다. namespace 자체의 소유권이
tenant catalog에 있다면 이곳에서 중복 생성하지 않습니다.
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: []
+6
View File
@@ -0,0 +1,6 @@
# Overlays
필요한 환경에만 overlay를 추가합니다. 각 overlay는 `../../base`를 참조하고
환경별 patch만 포함합니다.
비밀값, 임시 debug 설정과 수동 hotfix 결과를 overlay에 커밋하지 않습니다.
@@ -0,0 +1,8 @@
# Project Auth system
Project Auth bounded context가 소유하는 PostgreSQL, Keycloak과 client sync의
환경 중립 base입니다. `dev-k3s` namespace, host, Vault annotation과
NetworkPolicy는
`gitops/clusters/dev-k3s/overlays/systems/auth-system`에서 결합합니다.
Owner: Project Auth system
@@ -0,0 +1,22 @@
{
"realm": "project-auth",
"enabled": true,
"displayName": "Project Auth",
"sslRequired": "NONE",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"clients": [
{
"clientId": "project-auth-server",
"name": "project-auth-server",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false
}
]
}
@@ -0,0 +1,19 @@
#!/bin/sh
set -eu
psql \
-v ON_ERROR_STOP=1 \
--set=auth_db_user="${AUTH_DB_USER}" \
--set=auth_db_password="${AUTH_DB_PASSWORD}" \
--set=auth_db_name="${AUTH_DB_NAME}" \
--set=keycloak_db_user="${KEYCLOAK_DB_USER}" \
--set=keycloak_db_password="${KEYCLOAK_DB_PASSWORD}" \
--set=keycloak_db_name="${KEYCLOAK_DB_NAME}" \
--username "${POSTGRES_USER}" \
--dbname "${POSTGRES_DB}" <<-'EOSQL'
CREATE USER :"auth_db_user" WITH PASSWORD :'auth_db_password';
CREATE DATABASE :"auth_db_name" OWNER :"auth_db_user";
CREATE USER :"keycloak_db_user" WITH PASSWORD :'keycloak_db_password';
CREATE DATABASE :"keycloak_db_name" OWNER :"keycloak_db_user";
EOSQL
@@ -0,0 +1,97 @@
apiVersion: batch/v1
kind: Job
metadata:
name: keycloak-client-sync
annotations:
argocd.argoproj.io/hook: Sync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
argocd.argoproj.io/sync-wave: "4"
spec:
activeDeadlineSeconds: 600
backoffLimit: 1
template:
metadata:
labels:
app: keycloak-client-sync
spec:
serviceAccountName: keycloak-client-sync
automountServiceAccountToken: false
restartPolicy: Never
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: keycloak-client-sync
image: quay.io/keycloak/keycloak:26.5.5@sha256:a7b0cb7a43a1235a61872883414d3f1d9a3ceac9df6e5907bd12202778a6265c
command:
- /bin/sh
- -c
- |
set -eu
ready=false
for _ in $(seq 1 60); do
if /opt/keycloak/bin/kcadm.sh config credentials \
--server http://keycloak \
--realm master \
--user "$KC_BOOTSTRAP_ADMIN_USERNAME" \
--password "$KC_BOOTSTRAP_ADMIN_PASSWORD" >/dev/null 2>&1; then
ready=true
break
fi
sleep 5
done
test "$ready" = true
CLIENT_UUID=$(/opt/keycloak/bin/kcadm.sh get clients \
-r project-auth \
-q clientId="$KEYCLOAK_CLIENT_ID" | sed -n 's/.*"id" : "\([^"]*\)".*/\1/p' | head -n 1)
test -n "$CLIENT_UUID"
/opt/keycloak/bin/kcadm.sh update "clients/${CLIENT_UUID}" \
-r project-auth \
-s "secret=$KEYCLOAK_CLIENT_SECRET" \
-s "baseUrl=$AUTH_SERVER_BASE_URL" \
-s 'redirectUris=["'"$AUTH_SERVER_BASE_URL"'/login/oauth2/code/keycloak-google","'"$AUTH_SERVER_BASE_URL"'/login/oauth2/code/keycloak-github"]' \
-s 'webOrigins=["'"$AUTH_SERVER_BASE_URL"'"]'
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
env:
- name: KC_BOOTSTRAP_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: auth-system-config
key: KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-bootstrap-admin
key: KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD
- name: KEYCLOAK_CLIENT_ID
valueFrom:
configMapKeyRef:
name: auth-system-config
key: KEYCLOAK_CLIENT_ID
- name: KEYCLOAK_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: keycloak-client-auth-server
key: KEYCLOAK_CLIENT_SECRET
- name: AUTH_SERVER_BASE_URL
valueFrom:
configMapKeyRef:
name: auth-system-config
key: AUTH_SERVER_BASE_URL
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: keycloak-client-sync
automountServiceAccountToken: false
@@ -0,0 +1,104 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
annotations:
argocd.argoproj.io/sync-wave: "3"
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
serviceAccountName: keycloak
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:26.5.5@sha256:a7b0cb7a43a1235a61872883414d3f1d9a3ceac9df6e5907bd12202778a6265c
args:
- start-dev
- --import-realm
- --http-port=8080
ports:
- containerPort: 8080
name: http
- containerPort: 9000
name: management
env:
- name: KC_DB
value: postgres
- name: KC_DB_URL
value: jdbc:postgresql://postgres:5432/keycloak
- name: KC_DB_USERNAME
valueFrom:
configMapKeyRef:
name: auth-system-config
key: KEYCLOAK_DB_USER
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-keycloak-credentials
key: KEYCLOAK_DB_PASSWORD
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_BOOTSTRAP_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: auth-system-config
key: KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-bootstrap-admin
key: KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD
volumeMounts:
- name: keycloak-realm-import
mountPath: /opt/keycloak/data/import/project-auth-realm.json
subPath: project-auth-realm.json
readinessProbe:
httpGet:
path: /health/ready
port: management
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
httpGet:
path: /health/live
port: management
initialDelaySeconds: 60
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 6
startupProbe:
httpGet:
path: /health/ready
port: management
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 30
resources:
requests:
cpu: 250m
memory: 768Mi
limits:
cpu: 1000m
memory: 1536Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumes:
- name: keycloak-realm-import
configMap:
name: keycloak-realm-import
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: keycloak
spec:
selector:
app: keycloak
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: keycloak
automountServiceAccountToken: false
@@ -0,0 +1,19 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- postgres-serviceaccount.yaml
- keycloak-serviceaccount.yaml
- keycloak-client-sync-serviceaccount.yaml
- postgres-service.yaml
- postgres-statefulset.yaml
- keycloak-service.yaml
- keycloak-deployment.yaml
- keycloak-client-sync-job.yaml
configMapGenerator:
- name: postgres-init-script
files:
- files/postgres/01-init-project-auth-databases.sh
- name: keycloak-realm-import
files:
- files/keycloak/project-auth-realm.json
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None
selector:
app: postgres
ports:
- name: postgres
port: 5432
targetPort: 5432
type: ClusterIP
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: postgres
automountServiceAccountToken: false
@@ -0,0 +1,131 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
annotations:
argocd.argoproj.io/sync-wave: "2"
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
serviceAccountName: postgres
securityContext:
fsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: postgres
image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
ports:
- containerPort: 5432
name: postgres
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: auth-system-config
key: POSTGRES_SUPERUSER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-superuser-credentials
key: POSTGRES_SUPERUSER_PASSWORD
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: auth-system-config
key: POSTGRES_DEFAULT_DB
- name: AUTH_DB_NAME
valueFrom:
configMapKeyRef:
name: auth-system-config
key: AUTH_DB_NAME
- name: AUTH_DB_USER
valueFrom:
configMapKeyRef:
name: auth-system-config
key: AUTH_DB_USER
- name: AUTH_DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-auth-server-credentials
key: AUTH_DB_PASSWORD
- name: KEYCLOAK_DB_NAME
valueFrom:
configMapKeyRef:
name: auth-system-config
key: KEYCLOAK_DB_NAME
- name: KEYCLOAK_DB_USER
valueFrom:
configMapKeyRef:
name: auth-system-config
key: KEYCLOAK_DB_USER
- name: KEYCLOAK_DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-keycloak-credentials
key: KEYCLOAK_DB_PASSWORD
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
- name: postgres-init-script
mountPath: /docker-entrypoint-initdb.d/01-init-project-auth-databases.sh
subPath: 01-init-project-auth-databases.sh
- name: postgres-run
mountPath: /var/run/postgresql
readinessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
livenessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 3
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1024Mi
volumes:
- name: postgres-init-script
configMap:
name: postgres-init-script
defaultMode: 0555
- name: postgres-run
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
@@ -0,0 +1,8 @@
# API server
별도 source repository에서 빌드되는 API server의 환경 중립 Kubernetes
base입니다. Image digest, dev configuration, ingress와 pull credential
reference는 `gitops/clusters/dev-k3s/overlays/workloads/api-server`
소유합니다.
Owner: Project API workload
@@ -0,0 +1,71 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
annotations:
argocd.argoproj.io/sync-wave: "10"
spec:
replicas: 1
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
serviceAccountName: api-server
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api-server
image: ghcr.io/donghyeonka/project-api-server
imagePullPolicy: Always
ports:
- containerPort: 8082
name: http
envFrom:
- configMapRef:
name: api-server-config
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 15
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 20
timeoutSeconds: 3
failureThreshold: 5
periodSeconds: 15
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 5
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 12
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- serviceaccount.yaml
- service.yaml
- deployment.yaml
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: api-server
spec:
selector:
app: api-server
ports:
- name: http
port: 80
targetPort: 8082
type: ClusterIP
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-server
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-regcred
@@ -0,0 +1,7 @@
# Auth server
별도 source repository에서 빌드되는 Auth server의 환경 중립 Kubernetes
base입니다. Image digest, dev configuration, ingress와 Vault annotation은
`gitops/clusters/dev-k3s/overlays/workloads/auth-server`가 소유합니다.
Owner: Project Auth workload
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: auth-db-migration
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-regcred
@@ -0,0 +1,70 @@
apiVersion: batch/v1
kind: Job
metadata:
name: auth-db-migration
annotations:
argocd.argoproj.io/hook: Sync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
argocd.argoproj.io/sync-wave: "5"
spec:
activeDeadlineSeconds: 600
backoffLimit: 1
template:
metadata:
labels:
app: auth-db-migration
spec:
serviceAccountName: auth-db-migration
automountServiceAccountToken: false
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: auth-db-migration
image: ghcr.io/donghyeonka/project-auth-server
imagePullPolicy: IfNotPresent
command:
- java
- -jar
- /app/migration.jar
env:
- name: SPRING_MAIN_WEB_APPLICATION_TYPE
value: none
- name: APP_PERSISTENCE_MIGRATION_RUN_ON_STARTUP
value: "true"
- name: APP_PERSISTENCE_MIGRATION_LOCATION
value: classpath:db/migration
- name: APP_DATASOURCE_URL
valueFrom:
configMapKeyRef:
name: auth-server-config
key: APP_DATASOURCE_URL
- name: APP_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: postgres-auth-server-credentials
key: APP_DATASOURCE_USERNAME
- name: APP_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-auth-server-credentials
key: APP_DATASOURCE_PASSWORD
- name: APP_DATASOURCE_DRIVER_CLASS_NAME
value: org.postgresql.Driver
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
@@ -0,0 +1,78 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-server
annotations:
argocd.argoproj.io/sync-wave: "10"
spec:
replicas: 1
selector:
matchLabels:
app: auth-server
template:
metadata:
labels:
app: auth-server
spec:
serviceAccountName: auth-server
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: auth-server
image: ghcr.io/donghyeonka/project-auth-server
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef:
name: auth-server-config
- secretRef:
name: postgres-auth-server-credentials
- secretRef:
name: keycloak-client-auth-server
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1024Mi
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 20
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 30
timeoutSeconds: 3
failureThreshold: 5
periodSeconds: 15
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 10
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 12
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- serviceaccount.yaml
- auth-db-migration-serviceaccount.yaml
- service.yaml
- db-migration-job.yaml
- deployment.yaml
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: auth-server
spec:
selector:
app: auth-server
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: auth-server
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-regcred