기존 Docker Compose로 실행하던 yeonghoon.kim과 money.yeonghoon.kim 서비스를 Kubernetes로 이전함.
애플리케이션 이미지는 기존 DGX GitLab Registry에 push하고 Kubernetes Deployment에서 pull하도록 구성함.
Namespace, PV/PVC, ConfigMap, Secret 예제, Deployment 또는 StatefulSet, Service, Ingress manifest를 작성하고 kubectl apply로 배포함.
실제 Secret 파일은 제외하고 secret.example.yaml만 기록함.
이미지 빌드와 push
# Rhymix PHP 이미지를 빌드하고 Registry에 올린다.
docker build -t registry-dgx.kyh:5050/ioniere/3009-yeonghoon.kim/rhymix-php:8.2-001 docker/php
docker push registry-dgx.kyh:5050/ioniere/3009-yeonghoon.kim/rhymix-php:8.2-001
# Money Web 이미지를 빌드하고 Registry에 올린다.
docker build \
-f ~/gitlab-project-clones/ioniere/3006-money.yeonghoon.kim/app-image/Dockerfile \
-t registry-dgx.kyh:5050/ioniere/3006-money.yeonghoon.kim/money-web:0.1.0 \
~/gitlab-project-clones/ioniere/3006-money.yeonghoon.kim
docker push registry-dgx.kyh:5050/ioniere/3006-money.yeonghoon.kim/money-web:0.1.0
Kubernetes manifest 원문
===== 3009-yeonghoon.kim =====
----- k8s/configmap-nginx.yaml -----
apiVersion: v1
kind: ConfigMap
metadata:
name: rhymix-nginx-config
namespace: 3009-yeonghoon-kim
data:
default.conf: |
server {
listen 80;
server_name localhost;
server_tokens off;
root /var/www/html;
index index.php index.html;
client_max_body_size 64M;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
include /etc/nginx/conf.d/rhymix.inc;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
rhymix.inc: |
location ~ ^/modules/editor/(skins|styles)/.+\.html$ {
# pass
}
location ~ ^/(addons|common/tpl|files/(faceOff|ruleset)|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml|blade\.php)$ {
return 403;
}
location ~ ^/files/(attach|config|cache)/.+\.(ph(p|t|ar)?[0-9]?|p?html?|cgi|pl|exe|[aj]spx?|inc|bak)$ {
return 403;
}
location ~ ^/files/(env|member_extra_info/(new_message_flags|point))/ {
return 403;
}
location ~ ^/(\.git|\.ht|\.travis|codeception\.|composer\.|Gruntfile\.js|package\.json|CONTRIBUTING|COPYRIGHT|LICENSE|README) {
return 403;
}
location ~ ^/(.+)/(addons|files|layouts|m\.layouts|modules|widgets|widgetstyles)/(.+) {
try_files $uri $uri/ /$2/$3;
}
location ~ ^/(.+)\.min\.(css|js)$ {
try_files $uri $uri/ /$1.$2;
}
location ~ ^/files/download/ {
try_files $uri $uri/ /index.php$is_args$args;
}
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
----- k8s/deployment-web.yaml -----
apiVersion: apps/v1
kind: Deployment
metadata:
name: rhymix-web
namespace: 3009-yeonghoon-kim
spec:
replicas: 1
selector:
matchLabels:
app: rhymix-web
template:
metadata:
labels:
app: rhymix-web
spec:
imagePullSecrets:
- name: gitlab-registry-secret
containers:
- name: nginx
image: nginx:alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts:
- name: html
mountPath: /var/www/html
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
- name: nginx-config
mountPath: /etc/nginx/conf.d/rhymix.inc
subPath: rhymix.inc
readOnly: true
- name: php
image: registry-dgx.kyh:5050/ioniere/3009-yeonghoon.kim/rhymix-php:8.2-001
imagePullPolicy: IfNotPresent
envFrom:
- secretRef:
name: rhymix-db-secret
env:
- name: DB_HOST
value: db
- name: DB_PORT
value: "3306"
- name: TZ
value: Asia/Seoul
ports:
- name: php-fpm
containerPort: 9000
volumeMounts:
- name: html
mountPath: /var/www/html
volumes:
- name: html
persistentVolumeClaim:
claimName: rhymix-html
- name: nginx-config
configMap:
name: rhymix-nginx-config
----- k8s/ingress.yaml -----
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rhymix-web
namespace: 3009-yeonghoon-kim
spec:
ingressClassName: nginx
rules:
- host: yeonghoon.kim
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rhymix-web
port:
number: 80
- host: www.yeonghoon.kim
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rhymix-web
port:
number: 80
----- k8s/namespace.yaml -----
apiVersion: v1
kind: Namespace
metadata:
name: 3009-yeonghoon-kim
----- k8s/pv-pvc.yaml -----
apiVersion: v1
kind: PersistentVolume
metadata:
name: 3009-rhymix-html-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: 3009-rhymix-nfs
nfs:
server: 192.168.200.100
path: /mnt/k8s-nfs/3009-yeonghoon.kim/html
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rhymix-html
namespace: 3009-yeonghoon-kim
spec:
accessModes:
- ReadWriteMany
storageClassName: 3009-rhymix-nfs
resources:
requests:
storage: 20Gi
volumeName: 3009-rhymix-html-pv
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: 3009-rhymix-mariadb-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: 3009-rhymix-nfs
nfs:
server: 192.168.200.100
path: /mnt/k8s-nfs/3009-yeonghoon.kim/mariadb
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rhymix-mariadb
namespace: 3009-yeonghoon-kim
spec:
accessModes:
- ReadWriteOnce
storageClassName: 3009-rhymix-nfs
resources:
requests:
storage: 5Gi
volumeName: 3009-rhymix-mariadb-pv
----- k8s/secret.example.yaml -----
apiVersion: v1
kind: Secret
metadata:
name: rhymix-db-secret
namespace: 3009-yeonghoon-kim
type: Opaque
stringData:
MYSQL_ROOT_PASSWORD: "CHANGE_ME_ROOT_PASSWORD"
MYSQL_DATABASE: "rhymix"
MYSQL_USER: "rhymix"
MYSQL_PASSWORD: "CHANGE_ME_USER_PASSWORD"
----- k8s/service-web.yaml -----
apiVersion: v1
kind: Service
metadata:
name: rhymix-web
namespace: 3009-yeonghoon-kim
spec:
selector:
app: rhymix-web
ports:
- name: http
port: 80
targetPort: 80
----- k8s/statefulset-db.yaml -----
apiVersion: v1
kind: Service
metadata:
name: db
namespace: 3009-yeonghoon-kim
spec:
selector:
app: rhymix-db
ports:
- name: mysql
port: 3306
targetPort: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: rhymix-db
namespace: 3009-yeonghoon-kim
spec:
serviceName: db
replicas: 1
selector:
matchLabels:
app: rhymix-db
template:
metadata:
labels:
app: rhymix-db
spec:
containers:
- name: mariadb
image: mariadb:10.6
imagePullPolicy: IfNotPresent
args:
- "--character-set-server=utf8mb4"
- "--collation-server=utf8mb4_unicode_ci"
envFrom:
- secretRef:
name: rhymix-db-secret
env:
- name: TZ
value: Asia/Seoul
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: mariadb-data
mountPath: /var/lib/mysql
volumes:
- name: mariadb-data
persistentVolumeClaim:
claimName: rhymix-mariadb
===== 3006-money.yeonghoon.kim =====
----- k8s/00-namespace.yaml -----
apiVersion: v1
kind: Namespace
metadata:
name: money
labels:
app.kubernetes.io/name: money
app.kubernetes.io/part-of: money
----- k8s/10-mysql-pv-pvc.yaml -----
apiVersion: v1
kind: PersistentVolume
metadata:
name: money-mysql-data-pv
labels:
app.kubernetes.io/name: money-mysql
app.kubernetes.io/part-of: money
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nfs:
server: 192.168.200.100
path: /mnt/k8s-nfs/3006-money.yeonghoon.kim/mysql
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: money-mysql-data
namespace: money
labels:
app.kubernetes.io/name: money-mysql
app.kubernetes.io/part-of: money
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
volumeName: money-mysql-data-pv
resources:
requests:
storage: 5Gi
----- k8s/20-web-configmap.yaml -----
apiVersion: v1
kind: ConfigMap
metadata:
name: money-web-config
namespace: money
labels:
app.kubernetes.io/name: money-web
app.kubernetes.io/part-of: money
data:
APP_ENV: "production"
PHP_TIMEZONE: "Asia/Seoul"
API_BASE_PATH: "/api"
CORS_ALLOWED_ORIGINS: "https://money.yeonghoon.kim"
USE_DOCKER_MYSQL: "true"
DB_HOST: "money-mysql"
DB_PORT: "3306"
DB_NAME: "money_management"
DB_USER: "root"
MAX_LOGIN_ATTEMPTS: "5"
LOGIN_LOCKOUT_TIME: "900"
----- k8s/21-mysql-configmap.yaml -----
apiVersion: v1
kind: ConfigMap
metadata:
name: money-mysql-config
namespace: money
labels:
app.kubernetes.io/name: money-mysql
app.kubernetes.io/part-of: money
data:
TZ: "Asia/Seoul"
MYSQL_DATABASE: "money_management"
MYSQL_CHARACTER_SET_SERVER: "utf8mb4"
MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci"
----- k8s/22-mysql-files-configmap.yaml -----
apiVersion: v1
data:
my.cnf: |-
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
init-connect='SET NAMES utf8mb4'
[mysql]
default-character-set=utf8mb4
[client]
default-character-set=utf8mb4
schema.sql: "-- =====================================\n-- 머니매니저 데이터베이스 스키마\n-- =====================================\n--\n--
개인 자산관리 웹 애플리케이션의 전체 데이터베이스 구조를 정의합니다.\n-- 현금, 투자, 연금 자산 관리와 지출 추적, 아카이브 시스템을
지원합니다.\n--\n-- 주요 테이블 그룹:\n-- 1. 자산 관리 테이블\n-- - cash_assets: 현금성 자산 (은행 계좌,
현금 등)\n-- - investment_assets: 투자 자산 (주식, 펀드, ISA 등)\n-- - pension_assets:
연금 자산 (연금저축, 퇴직연금 등)\n--\n-- 2. 지출 관리 테이블\n-- - daily_expenses: 일간 지출 내역\n--
\ - fixed_expenses: 고정 지출 (월세, 공과금 등)\n-- - prepaid_expenses: 선납 지출 (보험료
등)\n--\n-- 3. 아카이브 시스템\n-- - monthly_archives: 월별 아카이브 기본 정보\n-- - *_archive:
각 자산/지출의 아카이브 데이터\n-- - archive_summary_cache: 아카이브 요약 정보 캐시\n--\n-- 4. 시스템
관리 테이블\n-- - user_sessions: 세션 기반 인증\n-- - assets_monthly_snapshot: 월별 자산
스냅샷\n-- - expenses_monthly_summary: 월별 지출 요약\n--\n-- 데이터베이스 특징:\n-- - UTF-8
완전 지원 (utf8mb4, 이모지 포함)\n-- - 소프트 삭제 지원 (deleted_at 컬럼)\n-- - 자동 타임스탬프 (created_at,
updated_at)\n-- - 외래키 제약조건으로 데이터 무결성 보장\n-- - 성능 최적화 인덱스\n-- - 계산된 컬럼으로 실시간 집계\n--\n--
@database money_management\n-- @charset utf8mb4\n-- @collation utf8mb4_unicode_ci\n--
@engine InnoDB\n-- @version 1.0\n-- @author YeongHoon Kim\n--\n-- MySQL dump 10.13
\ Distrib 8.0.43, for Linux (x86_64)\n--\n-- Host: localhost Database: money_management\n--
------------------------------------------------------\n-- Server version\t8.0.43\n\n/*!40101
SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS
*/;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!50503
SET NAMES utf8mb4 */;\n/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n/*!40103 SET
TIME_ZONE='+00:00' */;\n/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0
*/;\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0
*/;\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n/*!40111
SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n\n--\n-- Table structure for
table `archive_summary_cache`\n--\n\nDROP TABLE IF EXISTS `archive_summary_cache`;\n/*!40101
SET @saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `archive_summary_cache` (\n `archive_id` int NOT
NULL,\n `cash_total` bigint DEFAULT '0',\n `cash_count` int DEFAULT '0',\n `investment_total`
bigint DEFAULT '0',\n `investment_count` int DEFAULT '0',\n `pension_total`
bigint DEFAULT '0',\n `pension_count` int DEFAULT '0',\n `total_assets` bigint
GENERATED ALWAYS AS (((`cash_total` + `investment_total`) + `pension_total`))
STORED,\n `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n
\ PRIMARY KEY (`archive_id`),\n CONSTRAINT `archive_summary_cache_ibfk_1` FOREIGN
KEY (`archive_id`) REFERENCES `monthly_archives` (`id`) ON DELETE CASCADE\n) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET character_set_client
= @saved_cs_client */;\n\n--\n-- Table structure for table `assets_archive_data`\n--\n\nDROP
TABLE IF EXISTS `assets_archive_data`;\n/*!40101 SET @saved_cs_client = @@character_set_client
*/;\n/*!50503 SET character_set_client = utf8mb4 */;\nCREATE TABLE `assets_archive_data`
(\n `id` int NOT NULL AUTO_INCREMENT,\n `archive_id` int NOT NULL,\n `asset_table`
enum('cash_assets','investment_assets','pension_assets') COLLATE utf8mb4_unicode_ci
NOT NULL,\n `asset_data` json NOT NULL COMMENT '원본 테이블 데이터 완전 보존',\n `created_at`
timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n KEY `idx_archive_table`
(`archive_id`,`asset_table`),\n KEY `idx_archive_id` (`archive_id`),\n CONSTRAINT
`assets_archive_data_ibfk_1` FOREIGN KEY (`archive_id`) REFERENCES `monthly_archives`
(`id`) ON DELETE CASCADE\n) ENGINE=InnoDB AUTO_INCREMENT=859 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET character_set_client = @saved_cs_client
*/;\n\n--\n-- =====================================\n-- 현금성 자산 테이블 (cash_assets)\n--
=====================================\n--\n-- 사용자의 현금성 자산을 관리하는 핵심 테이블입니다.\n--
은행 계좌, 현금, 외화 등 유동성이 높은 자산을 추적합니다.\n--\n-- 주요 기능:\n-- - 자산 유형별 분류 (현금/통장)\n--
- 계좌별 잔액 관리\n-- - 드래그 앤 드롭 순서 변경 (display_order)\n-- - 소프트 삭제 지원 (deleted_at)\n--
- 자동 타임스탬프 (created_at, updated_at)\n--\n-- 인덱스 최적화:\n-- - 활성 자산 조회 (deleted_at
IS NULL)\n-- - 순서 정렬 (display_order)\n--\n-- 관련 테이블:\n-- - cash_assets_archive:
월별 아카이브 데이터\n--\n\nDROP TABLE IF EXISTS `cash_assets`;\n/*!40101 SET @saved_cs_client
\ = @@character_set_client */;\n/*!50503 SET character_set_client = utf8mb4
*/;\nCREATE TABLE `cash_assets` (\n `id` int NOT NULL AUTO_INCREMENT,\n `type`
enum('현금','통장') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '자산 유형',\n `account_name`
varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '계좌명',\n `item_name`
varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '항목명',\n `balance` int
NOT NULL DEFAULT '0' COMMENT '잔액(원)',\n `display_order` int NOT NULL DEFAULT
'0' COMMENT '표시 순서',\n `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n
\ `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n
\ `deleted_at` timestamp NULL DEFAULT NULL COMMENT '삭제일시 (NULL이면 활성)',\n PRIMARY
KEY (`id`),\n KEY `idx_display_order` (`display_order`),\n KEY `idx_cash_assets_deleted`
(`deleted_at`)\n) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101
SET character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for
table `cash_assets_archive`\n--\n\nDROP TABLE IF EXISTS `cash_assets_archive`;\n/*!40101
SET @saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `cash_assets_archive` (\n `id` int NOT NULL AUTO_INCREMENT,\n
\ `archive_id` int NOT NULL COMMENT '월별 아카이브 ID',\n `type` enum('현금','통장') COLLATE
utf8mb4_unicode_ci NOT NULL COMMENT '자산 유형',\n `account_name` varchar(100) COLLATE
utf8mb4_unicode_ci DEFAULT NULL COMMENT '계좌명',\n `item_name` varchar(200) COLLATE
utf8mb4_unicode_ci NOT NULL COMMENT '항목명',\n `balance` int NOT NULL DEFAULT '0'
COMMENT '잔액(원)',\n `display_order` int NOT NULL DEFAULT '0' COMMENT '표시 순서',\n
\ `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n
\ KEY `idx_archive_id` (`archive_id`),\n CONSTRAINT `cash_assets_archive_ibfk_1`
FOREIGN KEY (`archive_id`) REFERENCES `monthly_archives` (`id`) ON DELETE CASCADE\n)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET
character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for table
`daily_expenses`\n--\n\nDROP TABLE IF EXISTS `daily_expenses`;\n/*!40101 SET @saved_cs_client
\ = @@character_set_client */;\n/*!50503 SET character_set_client = utf8mb4
*/;\nCREATE TABLE `daily_expenses` (\n `id` int NOT NULL AUTO_INCREMENT,\n `expense_date`
date NOT NULL COMMENT '지출일자',\n `total_amount` int NOT NULL DEFAULT '0' COMMENT
'총 금액(원)',\n `food_cost` int DEFAULT '0' COMMENT '식비(원)',\n `necessities_cost`
int DEFAULT '0' COMMENT '생필품비(원)',\n `transportation_cost` int DEFAULT '0' COMMENT
'교통비(원)',\n `other_cost` int DEFAULT '0' COMMENT '기타(원)',\n `created_at` timestamp
NULL DEFAULT CURRENT_TIMESTAMP,\n `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,\n `deleted_at` timestamp NULL DEFAULT NULL,\n PRIMARY
KEY (`id`),\n UNIQUE KEY `unique_expense_date` (`expense_date`,`deleted_at`),\n
\ KEY `idx_daily_expenses_deleted` (`deleted_at`),\n KEY `idx_daily_expenses_date`
(`expense_date`)\n) ENGINE=InnoDB AUTO_INCREMENT=1119 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET character_set_client = @saved_cs_client
*/;\n\n--\n-- Table structure for table `fixed_expenses`\n--\n\nDROP TABLE IF
EXISTS `fixed_expenses`;\n/*!40101 SET @saved_cs_client = @@character_set_client
*/;\n/*!50503 SET character_set_client = utf8mb4 */;\nCREATE TABLE `fixed_expenses`
(\n `id` int NOT NULL AUTO_INCREMENT,\n `category` varchar(50) COLLATE utf8mb4_unicode_ci
DEFAULT NULL COMMENT '분류 (보험, 통신, 주거 등)',\n `item_name` varchar(200) COLLATE
utf8mb4_unicode_ci NOT NULL COMMENT '항목명',\n `amount` int NOT NULL COMMENT '금액(원)',\n
\ `payment_date` int DEFAULT NULL COMMENT '매월 결제일 (1-31)',\n `payment_method`
enum('신용','체크','현금') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '결제수단',\n `is_active`
tinyint(1) DEFAULT '1' COMMENT '활성 여부',\n `created_at` timestamp NULL DEFAULT
CURRENT_TIMESTAMP,\n `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON
UPDATE CURRENT_TIMESTAMP,\n `deleted_at` timestamp NULL DEFAULT NULL,\n PRIMARY
KEY (`id`),\n KEY `idx_fixed_expenses_deleted` (`deleted_at`)\n) ENGINE=InnoDB
AUTO_INCREMENT=75 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101
SET character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for
table `fixed_expenses_archive`\n--\n\nDROP TABLE IF EXISTS `fixed_expenses_archive`;\n/*!40101
SET @saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `fixed_expenses_archive` (\n `id` int NOT NULL AUTO_INCREMENT,\n
\ `archive_id` int NOT NULL COMMENT '월별 아카이브 ID',\n `category` varchar(50) COLLATE
utf8mb4_unicode_ci DEFAULT NULL COMMENT '카테고리',\n `item_name` varchar(200) COLLATE
utf8mb4_unicode_ci NOT NULL COMMENT '항목명',\n `amount` int NOT NULL DEFAULT '0'
COMMENT '금액(원)',\n `payment_date` int DEFAULT NULL COMMENT '결제일(1-31)',\n `payment_method`
enum('신용','체크','현금') COLLATE utf8mb4_unicode_ci DEFAULT '신용' COMMENT '결제수단',\n
\ `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n
\ KEY `idx_archive_id` (`archive_id`),\n CONSTRAINT `fixed_expenses_archive_ibfk_1`
FOREIGN KEY (`archive_id`) REFERENCES `monthly_archives` (`id`) ON DELETE CASCADE\n)
ENGINE=InnoDB AUTO_INCREMENT=4381 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101
SET character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for
table `investment_assets`\n--\n\nDROP TABLE IF EXISTS `investment_assets`;\n/*!40101
SET @saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `investment_assets` (\n `id` int NOT NULL AUTO_INCREMENT,\n
\ `category` enum('저축','혼합','주식') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT
'자산 분류',\n `account_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL
COMMENT '계좌명',\n `item_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL
COMMENT '종목명',\n `current_value` int NOT NULL DEFAULT '0' COMMENT '현재 평가금액(원)',\n
\ `deposit_amount` int NOT NULL DEFAULT '0' COMMENT '납입 원금(원)',\n `display_order`
int NOT NULL DEFAULT '0',\n `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n
\ `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n
\ `deleted_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `idx_investment_assets_deleted`
(`deleted_at`)\n) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101
SET character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for
table `investment_assets_archive`\n--\n\nDROP TABLE IF EXISTS `investment_assets_archive`;\n/*!40101
SET @saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `investment_assets_archive` (\n `id` int NOT NULL
AUTO_INCREMENT,\n `archive_id` int NOT NULL COMMENT '월별 아카이브 ID',\n `category`
enum('저축','혼합','주식') COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '자산 분류',\n `account_name`
varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '계좌명',\n `item_name`
varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '종목명',\n `current_value`
int NOT NULL DEFAULT '0' COMMENT '현재 평가금액(원)',\n `deposit_amount` int NOT NULL
DEFAULT '0' COMMENT '납입 원금(원)',\n `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n
\ PRIMARY KEY (`id`),\n KEY `idx_archive_id` (`archive_id`),\n CONSTRAINT `investment_assets_archive_ibfk_1`
FOREIGN KEY (`archive_id`) REFERENCES `monthly_archives` (`id`) ON DELETE CASCADE\n)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET
character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for table
`monthly_archives`\n--\n\nDROP TABLE IF EXISTS `monthly_archives`;\n/*!40101 SET
@saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `monthly_archives` (\n `id` int NOT NULL AUTO_INCREMENT,\n
\ `archive_month` date NOT NULL COMMENT '아카이브 월 (YYYY-MM-01 형식)',\n `modification_notes`
text COLLATE utf8mb4_unicode_ci COMMENT '수정 내역',\n `created_at` timestamp NULL
DEFAULT CURRENT_TIMESTAMP,\n `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,\n `last_modified` timestamp NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `unique_archive_month`
(`archive_month`)\n) ENGINE=InnoDB AUTO_INCREMENT=170 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET character_set_client = @saved_cs_client
*/;\n\n--\n-- Table structure for table `pension_assets`\n--\n\nDROP TABLE IF
EXISTS `pension_assets`;\n/*!40101 SET @saved_cs_client = @@character_set_client
*/;\n/*!50503 SET character_set_client = utf8mb4 */;\nCREATE TABLE `pension_assets`
(\n `id` int NOT NULL AUTO_INCREMENT,\n `type` enum('연금저축','퇴직연금') COLLATE utf8mb4_unicode_ci
NOT NULL COMMENT '연금 유형',\n `item_name` varchar(200) COLLATE utf8mb4_unicode_ci
NOT NULL COMMENT '상품명',\n `current_value` int NOT NULL DEFAULT '0' COMMENT '평가금액(원)',\n
\ `deposit_amount` int NOT NULL DEFAULT '0' COMMENT '납입잔액(원)',\n `display_order`
int NOT NULL DEFAULT '0',\n `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n
\ `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n
\ `deleted_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `idx_pension_assets_deleted`
(`deleted_at`)\n) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101
SET character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for
table `pension_assets_archive`\n--\n\nDROP TABLE IF EXISTS `pension_assets_archive`;\n/*!40101
SET @saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `pension_assets_archive` (\n `id` int NOT NULL AUTO_INCREMENT,\n
\ `archive_id` int NOT NULL COMMENT '월별 아카이브 ID',\n `type` enum('연금저축','퇴직연금')
COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '연금 유형',\n `item_name` varchar(200)
COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '상품명',\n `current_value` int NOT
NULL DEFAULT '0' COMMENT '평가금액(원)',\n `deposit_amount` int NOT NULL DEFAULT '0'
COMMENT '납입잔액(원)',\n `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n
\ PRIMARY KEY (`id`),\n KEY `idx_archive_id` (`archive_id`),\n CONSTRAINT `pension_assets_archive_ibfk_1`
FOREIGN KEY (`archive_id`) REFERENCES `monthly_archives` (`id`) ON DELETE CASCADE\n)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET
character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for table
`prepaid_expenses`\n--\n\nDROP TABLE IF EXISTS `prepaid_expenses`;\n/*!40101 SET
@saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `prepaid_expenses` (\n `id` int NOT NULL AUTO_INCREMENT,\n
\ `item_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '항목명',\n
\ `amount` int NOT NULL COMMENT '금액(원)',\n `payment_date` int DEFAULT NULL COMMENT
'결제일',\n `payment_method` enum('신용','체크','현금') COLLATE utf8mb4_unicode_ci NOT
NULL COMMENT '결제수단',\n `expiry_date` date DEFAULT NULL COMMENT '만료일',\n `is_active`
tinyint(1) DEFAULT '1' COMMENT '활성 여부',\n `created_at` timestamp NULL DEFAULT
CURRENT_TIMESTAMP,\n `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON
UPDATE CURRENT_TIMESTAMP,\n `deleted_at` timestamp NULL DEFAULT NULL,\n PRIMARY
KEY (`id`),\n KEY `idx_prepaid_expenses_deleted` (`deleted_at`)\n) ENGINE=InnoDB
AUTO_INCREMENT=45 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101
SET character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for
table `prepaid_expenses_archive`\n--\n\nDROP TABLE IF EXISTS `prepaid_expenses_archive`;\n/*!40101
SET @saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `prepaid_expenses_archive` (\n `id` int NOT NULL
AUTO_INCREMENT,\n `archive_id` int NOT NULL COMMENT '월별 아카이브 ID',\n `item_name`
varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '항목명',\n `amount` int
NOT NULL DEFAULT '0' COMMENT '금액(원)',\n `payment_date` int DEFAULT NULL COMMENT
'결제일(1-31)',\n `payment_method` enum('신용','체크','현금') COLLATE utf8mb4_unicode_ci
DEFAULT '신용' COMMENT '결제수단',\n `expiry_date` date DEFAULT NULL COMMENT '만료일',\n
\ `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n
\ KEY `idx_archive_id` (`archive_id`),\n CONSTRAINT `prepaid_expenses_archive_ibfk_1`
FOREIGN KEY (`archive_id`) REFERENCES `monthly_archives` (`id`) ON DELETE CASCADE\n)
ENGINE=InnoDB AUTO_INCREMENT=1054 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n/*!40101
SET character_set_client = @saved_cs_client */;\n\n--\n-- Table structure for
table `user_sessions`\n--\n\nDROP TABLE IF EXISTS `user_sessions`;\n/*!40101 SET
@saved_cs_client = @@character_set_client */;\n/*!50503 SET character_set_client
= utf8mb4 */;\nCREATE TABLE `user_sessions` (\n `session_id` varchar(128) COLLATE
utf8mb4_unicode_ci NOT NULL,\n `user_email` varchar(255) COLLATE utf8mb4_unicode_ci
NOT NULL,\n `session_data` text COLLATE utf8mb4_unicode_ci,\n `created_at` timestamp
NULL DEFAULT CURRENT_TIMESTAMP,\n `expires_at` timestamp NOT NULL,\n `last_activity`
timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n `ip_address`
varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `user_agent` text COLLATE
utf8mb4_unicode_ci,\n `csrf_token` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT
NULL COMMENT 'CSRF 방지 토큰',\n `is_active` tinyint(1) DEFAULT '1',\n PRIMARY KEY
(`session_id`),\n KEY `idx_user_email` (`user_email`),\n KEY `idx_expires_at`
(`expires_at`),\n KEY `idx_active` (`is_active`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;\n/*!40101 SET character_set_client = @saved_cs_client
*/;\n\n--\n-- Dumping routines for database 'money_management'\n--\n/*!40103 SET
TIME_ZONE=@OLD_TIME_ZONE */;\n\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n/*!40014
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS
*/;\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101
SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION
*/;\n/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n\n-- Dump completed on 2025-09-29
10:02:48\n-- 로그인 시도 기록 테이블 (Rate Limiting용)\n-- IP + email 기준으로 실패 횟수 추적, 일정 횟수
초과 시 잠금\nCREATE TABLE IF NOT EXISTS login_attempts (\n id INT AUTO_INCREMENT
PRIMARY KEY,\n email VARCHAR(255) NOT NULL,\n ip_address VARCHAR(45) NOT
NULL,\n attempted_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n INDEX idx_email_ip
(email, ip_address),\n INDEX idx_attempted_at (attempted_at)\n);\n"
kind: ConfigMap
metadata:
name: money-mysql-files
namespace: money
----- k8s/30-secret.example.yaml -----
apiVersion: v1
kind: Secret
metadata:
name: money-secret
namespace: money
labels:
app.kubernetes.io/name: money
app.kubernetes.io/part-of: money
type: Opaque
stringData:
DB_PASSWORD: "REPLACE_ME_DO_NOT_COMMIT"
LOGIN_USERNAME: "REPLACE_ME_DO_NOT_COMMIT"
LOGIN_PASSWORD_HASH: "REPLACE_ME_DO_NOT_COMMIT"
TELEGRAM_BOT_TOKEN: "REPLACE_ME_DO_NOT_COMMIT"
TELEGRAM_CHAT_ID: "REPLACE_ME_DO_NOT_COMMIT"
----- k8s/40-mysql-deployment.yaml -----
apiVersion: apps/v1
kind: Deployment
metadata:
name: money-mysql
namespace: money
labels:
app.kubernetes.io/name: money-mysql
app.kubernetes.io/part-of: money
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: money-mysql
template:
metadata:
labels:
app.kubernetes.io/name: money-mysql
app.kubernetes.io/part-of: money
spec:
containers:
- name: mysql
image: mysql:8.4
imagePullPolicy: IfNotPresent
ports:
- name: mysql
containerPort: 3306
envFrom:
- configMapRef:
name: money-mysql-config
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: money-secret
key: DB_PASSWORD
args:
- "--character-set-server=utf8mb4"
- "--collation-server=utf8mb4_unicode_ci"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: mysql-config
mountPath: /etc/mysql/conf.d/my.cnf
subPath: my.cnf
- name: mysql-schema
mountPath: /docker-entrypoint-initdb.d/01-schema.sql
subPath: schema.sql
readinessProbe:
exec:
command:
- sh
- -c
- mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD"
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
exec:
command:
- sh
- -c
- mysqladmin ping -h 127.0.0.1 -uroot -p"$MYSQL_ROOT_PASSWORD"
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: money-mysql-data
- name: mysql-config
configMap:
name: money-mysql-files
items:
- key: my.cnf
path: my.cnf
- name: mysql-schema
configMap:
name: money-mysql-files
items:
- key: schema.sql
path: schema.sql
----- k8s/50-mysql-service.yaml -----
apiVersion: v1
kind: Service
metadata:
name: money-mysql
namespace: money
labels:
app.kubernetes.io/name: money-mysql
app.kubernetes.io/part-of: money
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: money-mysql
ports:
- name: mysql
port: 3306
targetPort: mysql
----- k8s/60-web-deployment.yaml -----
apiVersion: apps/v1
kind: Deployment
metadata:
name: money-web
namespace: money
labels:
app.kubernetes.io/name: money-web
app.kubernetes.io/part-of: money
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: money-web
template:
metadata:
labels:
app.kubernetes.io/name: money-web
app.kubernetes.io/part-of: money
spec:
imagePullSecrets:
- name: gitlab-registry-secret
containers:
- name: web
image: registry-dgx.kyh:5050/ioniere/3006-money.yeonghoon.kim/money-web:0.1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
envFrom:
- configMapRef:
name: money-web-config
- secretRef:
name: money-secret
volumeMounts:
- name: app-logs
mountPath: /var/log/app
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: app-logs
emptyDir: {}
----- k8s/70-web-service.yaml -----
apiVersion: v1
kind: Service
metadata:
name: money-web
namespace: money
labels:
app.kubernetes.io/name: money-web
app.kubernetes.io/part-of: money
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: money-web
ports:
- name: http
port: 80
targetPort: http
----- k8s/80-ingress.yaml -----
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: money-web
namespace: money
labels:
app.kubernetes.io/name: money-web
app.kubernetes.io/part-of: money
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "20m"
spec:
ingressClassName: nginx
rules:
- host: money.yeonghoon.kim
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: money-web
port:
name: http
적용 명령
# 각 프로젝트 저장소에서 manifest를 적용한다.
kubectl apply -f k8s/
# 배포 상태를 확인한다.
kubectl get pods,svc,ingress -A -o wide
댓글 2
| 번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
|---|---|---|---|---|
| 19 | DGX Kubernetes 구성 계획 | 김영훈 | 2026.06.08 | 25 |
| 18 | Kubernetes 기본 클러스터, ingress-nginx 설치 | 김영훈 | 2026.06.09 | 15 |
| » | Docker Compose 서비스를 Kubernetes로 이전(깃랩 도커 저장소 필요) [2] | 김영훈 | 2026.06.10 | 20 |
| 16 | yeonghoon.kim 배포 파일을 Helm Chart로 전환 [1] | 김영훈 | 2026.06.12 | 18 |
| 15 | SOPS age로 Kubernetes Secret 암복호화 | 김영훈 | 2026.06.15 | 23 |
| 14 | ArgoCD와 GitOps 구조 준비 | 김영훈 | 2026.06.16 | 16 |
| 13 | CoreDNS로 GitLab Registry 내부 주소 고정 | 김영훈 | 2026.06.17 | 19 |
| 12 | 복구 런북 초반 누락 사항 정리 | 김영훈 | 2026.06.17 | 15 |
| 11 | SOPS Secret 복구와 ArgoCD root Application 적용 | 김영훈 | 2026.06.18 | 28 |
| 10 | 관측 스택과 앱 배포 저장소 구조 정리 | 김영훈 | 2026.06.19 | 23 |
| 9 | ArgoCD 복구 중 GitLab 접근 문제 정리 | 김영훈 | 2026.06.22 | 26 |
| 8 | ImagePullBackOff와 registry pull secret 누락 | 김영훈 | 2026.06.22 | 17 |
| 7 | worker 재부팅 전 drain 절차 정리 | 김영훈 | 2026.06.26 | 20 |
| 6 | control-plane 장애 시나리오 관찰 | 김영훈 | 2026.06.27 | 12 |
| 5 | OutOfSync와 Degraded 구분하기 | 김영훈 | 2026.06.28 | 25 |
| 4 | Git revert는 기록을 지우는 게 아니라 되돌리는 기록을 남기는 것 | 김영훈 | 2026.06.29 | 19 |
| 3 | Stateful과 Stateless 구분해보기 | 김영훈 | 2026.07.02 | 23 |
| 2 | GitLab Registry PAT 만료로 이미지 Pull 실패 | 김영훈 | 2026.07.08 | 14 |
| 1 | yeonghoon.kim php 업그레이드 | 김영훈 | 2026.07.09 | 0 |