Merge branch 'main' of https://git.devjun.net/AX-HUB/dap-was-dapmt
All checks were successful
Deploy Tools / deploy (push) Successful in 1m4s

This commit is contained in:
jade
2026-08-25 13:09:08 +09:00
7 changed files with 476 additions and 38 deletions

129
README.md
View File

@@ -2,7 +2,7 @@
신한라이프 업무 기능을 MCP(Model Context Protocol) Tool로 제공하는 Java 멀티 모듈 프로젝트입니다. 각 업무 모듈은 독립 실행 가능한 Spring Boot 애플리케이션이며, MCP Streamable HTTP와 REST 실행 API를 함께 제공합니다.
이 저장소에는 DAPMS(Gateway) 애플리케이션이 포함되어 있지 않습니다. DATMT는 Gateway로 Tool을 push 등록하지 않으며, 각 Pod가 `GET /tool-manifest`를 제공하면 DAPMS가 이 Manifest를 pull하여 Tool 목록을 구성합니다. Tool 조회와 직접 실행은 각 Pod에서도 자체적으로 처리합니다.
이 저장소에는 DATMS(Gateway) 애플리케이션이 포함되어 있지 않습니다. DATMT는 Gateway로 Tool을 push 등록하지 않으며, 각 Pod가 `GET /tool-manifest`를 제공하면 DATMS가 이 Manifest를 pull하여 Tool 목록을 구성합니다. Tool 조회와 직접 실행은 각 Pod에서도 자체적으로 처리합니다.
## 기술 기준
@@ -23,9 +23,9 @@
| `dat-was-cus` | 고객·CRM·VOC·웹 콘텐츠 관리 Tool | 8084 | 51 |
| `dat-was-sal` | 영업·청구·인수·동의·현장지원 Tool | 8082 | 52 |
| `dat-was-pro` | 상품·계약·고객·GA 설계사 Tool | 8085 | 50 |
| `dat-was-sys` | IAM·시스템 상태·공지·점검·배포 Tool | 8086 | 50 |
| `dat-was-sys` | IAM·시스템 상태·공지·점검·배포 Tool | 8086 | 51 |
총 203개의 `@McpTool` 선언과 203개의 V17 Tool YAML 정의가 있습니다.
업무 모듈에는 총 204개의 `@McpTool` 선언이 있습니다. 현재 업무 모듈의 `src/main/resources`에는 별도 `tool-definitions` YAML이 없으며, Tool 메타데이터는 어노테이션과 `@GrowToolHint`를 기준으로 생성됩니다.
현재 업무 구현은 개발·연동 검증 단계입니다. `dat-was-sal``cmm_claim_search``cmm_memo_retriever`는 각각 MCI와 HTTP Client 흐름을 사용하며, 나머지 Tool은 외부 시스템을 변경하지 않는 모의 응답을 중심으로 구현되어 있습니다.
@@ -44,7 +44,7 @@ Tool Pod
│ ├─ Tool 메타데이터 생성
│ └─ tool-definitions YAML 병합
├─ ToolManifestService
│ └─ DAPMS가 pull할 bundle 단위 Manifest 생성
│ └─ DATMS가 pull할 bundle 단위 Manifest 생성
├─ McpToolExecutionService
│ ├─ 입력 Schema 검증
│ ├─ 요청 DTO 변환 및 Tool 호출
@@ -54,18 +54,18 @@ Tool Pod
애플리케이션 시작 시 다음 순서로 Tool이 준비됩니다.
1. `ToolDefinitionRepository` `classpath*:tool-definitions/**/*.yml` 읽고 V17 필수 항목을 검증합니다.
2. `ToolRegistryHeartbeatSender``@McpTool` 메서드를 스캔하고 YAML 정의 병합해 `ToolMetadata`를 생성합니다.
1. `ToolDefinitionRepository` `classpath*:tool-definitions/**/*.yml` 경로의 정의가 있을 경우 이를 읽고 V17 필수 항목을 검증합니다.
2. `ToolRegistryHeartbeatSender``@McpTool` 메서드를 스캔하고, 존재하는 YAML 정의가 있으면 병합해 `ToolMetadata`를 생성합니다.
3. `McpToolMethodRegistry`가 실제 호출 가능한 Bean과 메서드를 Tool 이름으로 캐시합니다.
4. `ToolPodMcpToolSynchronizer`가 Tool을 MCP SDK 서버에 등록합니다.
5. REST와 MCP 요청은 공통 `McpToolExecutionService`를 통해 실행됩니다.
DATMT 내부에는 `/registry/register`, `/registry/deregister` 호출이나 주기적인 Gateway heartbeat 전송이 없습니다. `ToolRegistryHeartbeatSender`라는 클래스명은 호환성을 위해 남아 있지만 현재 역할은 로컬 Tool 스캔과 메타데이터 생성뿐입니다.
### DAPMS 연동 방식
### DATMS 연동 방식
```text
DAPMS
DATMS
└─ GET {DATMT Pod URL}/tool-manifest
└─ bundleId + revision + tools[]
└─ 각 Tool endpoint: {Pod URL}/mcp/{toolName}
@@ -73,7 +73,7 @@ DAPMS
- `mcp.manifest.bundle-id`는 Manifest를 제공하는 Pod의 고유 식별자이며 필수입니다.
- 현재 값은 `was-sal`, `was-cus`, `was-pro`, `was-sys`입니다.
- DAPMS에 설정한 bundle ID와 DATMT가 반환하는 `bundleId`가 일치해야 같은 Tool bundle로 관리됩니다.
- DATMS에 설정한 bundle ID와 DATMT가 반환하는 `bundleId`가 일치해야 같은 Tool bundle로 관리됩니다.
- `mcp.manifest.name-prefix`가 비어 있지 않으면 모든 Tool 이름이 해당 prefix로 시작해야 합니다.
- Manifest 내용이 바뀌면 `revision`이 증가하며, `If-None-Match`가 일치하면 `304 Not Modified`를 반환합니다.
@@ -115,11 +115,11 @@ Invoke-RestMethod `
### 요청 헤더 계약
DAPMS가 DATMT Tool Service를 호출할 때 사용하는 헤더는 다음과 같습니다. HTTP 헤더 이름은 대소문자를 구분하지 않지만, 문서와 구현에서는 아래 표기를 기준으로 사용합니다.
DATMS가 DATMT Tool Service를 호출할 때 사용하는 헤더는 다음과 같습니다. HTTP 헤더 이름은 대소문자를 구분하지 않지만, 문서와 구현에서는 아래 표기를 기준으로 사용합니다.
| 헤더 | 필수 여부 | 용도 | 전달 동작 |
|---|---|---|---|
| `X-Tool-Server-API-Key` | 인증 설정 시 필수 | DAPMS와 DATMT 사이의 Tool Server 인증 | `mcp.security.api-key` 또는 `api-keys`와 비교 |
| `X-Tool-Server-API-Key` | 인증 설정 시 필수 | DATMS와 DATMT 사이의 Tool Server 인증 | `mcp.security.api-key` 또는 `api-keys`와 비교 |
| `guid` | 선택 | 업무 호출 상관관계 식별자 | 실행 로그, 성공 응답, 하위 HTTP 호출로 전달 |
| `x-request-id` | 선택 | 요청 추적 식별자 | 실행 로그, 성공 응답, 하위 HTTP 호출로 전달 |
| `employee-no` | 선택 | 실제 사용자 사번 | 하위 HTTP 호출로 전달 |
@@ -165,17 +165,17 @@ SystemStatusResponse getSystemStatus(SystemStatusRequest request);
예: `cmm_claim_search`, `crm_customer_detail`, `iam_system_status`
이름 중복과 형식은 `validateMcpToolNames`, V17 정의`validateToolSchemaV17` Gradle 작업으로 검사합니다. 모든 `bootJar` 작업은 두 검증 작업에 의존합니다.
이름 중복과 형식은 `validateMcpToolNames` Gradle 작업으로 검사합니다. 현재 루트 `build.gradle``validateToolSchemaV17` Gradle 작업이 등록되어 있지 않으며, 모든 `bootJar` 작업은 `validateMcpToolNames`에만 의존합니다.
## Tool YAML 정의
각 Tool은 업무 모듈의 다음 경로에 YAML 정의를 가집니다.
공통 라이브러리는 Tool 메타데이터를 보강하기 위한 선택적 YAML 정의를 지원합니다. YAML 정의를 추가할 경우 업무 모듈의 다음 경로를 사용합니다.
```text
dat-was-*/src/main/resources/tool-definitions/{category}/{tool-name}.yml
```
V17 정의의 주요 필수 항목은 다음과 같습니다.
현재 업무 모듈에는 이 경로의 YAML 정의가 없습니다. 따라서 실행 시 메타데이터는 `@McpTool`, `@GrowToolHint`, DTO Schema를 기준으로 구성됩니다. YAML 정의를 도입하는 경우 V17 주요 필수 항목은 다음과 같습니다.
- `name`, `display_name`, `version`, `category_key`
- `description.function`, `when_to_use`, `when_not_to_use`, `io_limits`
@@ -185,7 +185,7 @@ V17 정의의 주요 필수 항목은 다음과 같습니다.
- `parameters_schema.additionalProperties: false`
- 각 입력 property의 `description`
입력·출력 Schema는 `ToolSchemaResolver`가 어노테이션의 Schema 리소스와 인라인 Schema, DTO에서 생성한 Schema를 해석합니다. `ToolRegistryHeartbeatSender`는 여기에 YAML의 `parameters_schema``output_schema`를 병합하여 최종 메타데이터를 만듭니다.
입력·출력 Schema는 `ToolSchemaResolver`가 어노테이션의 Schema 리소스와 인라인 Schema, DTO에서 생성한 Schema를 해석합니다. YAML 정의가 있으면 `ToolRegistryHeartbeatSender` `parameters_schema``output_schema`를 병합하여 최종 메타데이터를 만듭니다.
실행 시 Schema 검증은 MCP Java SDK의 `DefaultJsonSchemaValidator`를 사용하며 JSON Schema 2020-12 기준으로 처리합니다. 입력 불일치는 `422 INVALID_PARAM`, 출력 불일치는 `500 INVALID_TOOL_RESPONSE`로 반환됩니다. 단, Schema 해석 또는 검증기 자체에서 예외가 발생하면 현재 구현은 오류를 로그에 기록하고 해당 검증을 건너뜁니다.
@@ -204,7 +204,7 @@ $env:SPRING_PROFILES_ACTIVE = 'local'
$env:TOOL_SERVER_API_KEY = 'tool-server-key'
```
`TOOL_SERVER_API_KEY`를 지정하지 않으면 현재 개발 기본값인 `tool-server-key`가 사용됩니다. 운영 환경에서는 기본값을 사용하지 말고 DAPMS의 Tool Server API Key와 동일한 별도 Secret을 주입해야 합니다.
`TOOL_SERVER_API_KEY`를 지정하지 않으면 현재 개발 기본값인 `tool-server-key`가 사용됩니다. 운영 환경에서는 기본값을 사용하지 말고 DATMS의 Tool Server API Key와 동일한 별도 Secret을 주입해야 합니다.
각 Pod는 별도 터미널에서 실행합니다.
@@ -240,7 +240,7 @@ http://localhost:8086/swagger-ui/index.html
|---|---|---|
| `SPRING_PROFILES_ACTIVE` | Spring 활성 프로필 | `local` |
| `PORT` | Pod 수신 포트 | 모듈별 기본 포트 |
| `TOOL_SERVER_API_KEY` | DAPMS가 `X-Tool-Server-API-Key`로 전달할 공통 인증 Key | `tool-server-key` |
| `TOOL_SERVER_API_KEY` | DATMS가 `X-Tool-Server-API-Key`로 전달할 공통 인증 Key | `tool-server-key` |
| `AXHUB_TOOL_URL` | Manifest의 Tool endpoint 생성에 사용할 Pod 외부 URL | `http://localhost:${server.port}` |
| `AXHUB_GATEWAY_URL` | 프로필 및 Compose 호환용 Gateway URL. 현재 DATMT의 push 등록에는 사용하지 않음 | `http://localhost:8081` |
| `SPRING_DATA_REDIS_HOST` | Redis 호스트 | `localhost` |
@@ -248,7 +248,7 @@ http://localhost:8086/swagger-ui/index.html
| `GLOW_COMMUNICATION_MCI_HOST` | MCI 대상 호스트 | 프로필별 설정 |
| `GLOW_COMMUNICATION_MCI_PORT` | MCI 대상 포트 | 프로필별 설정 |
`mcp.security.api-key`는 단일 DAPMS 공통 Key를, `mcp.security.api-keys``API Key → tenant ID` 형태의 다중 Key를 지원합니다. 둘 중 하나라도 설정되어 있으면 올바른 `X-Tool-Server-API-Key`가 없는 `/rpc/**`, `/mcp/**` 요청은 `401 Unauthorized`가 됩니다. 두 설정이 모두 비어 있을 때만 익명 요청을 허용합니다. 현재 네 업무 Pod의 기본 `application.yml`은 단일 Key를 설정하므로 API Key 없이 Tool을 호출할 수 없습니다.
`mcp.security.api-key`는 단일 DATMS 공통 Key를, `mcp.security.api-keys``API Key → tenant ID` 형태의 다중 Key를 지원합니다. 둘 중 하나라도 설정되어 있으면 올바른 `X-Tool-Server-API-Key`가 없는 `/rpc/**`, `/mcp/**` 요청은 `401 Unauthorized`가 됩니다. 두 설정이 모두 비어 있을 때만 익명 요청을 허용합니다. 현재 네 업무 Pod의 기본 `application.yml`은 단일 Key를 설정하므로 API Key 없이 Tool을 호출할 수 없습니다.
## Scaffold
@@ -278,11 +278,11 @@ mcp:
생성되는 Compose 서비스에도 `TOOL_SERVER_API_KEY=${TOOL_SERVER_API_KEY:-tool-server-key}`가 추가됩니다. 생성 후에는 다음 항목을 반드시 확인해야 합니다.
1. `bundle-id`를 DAPMS에 등록할 bundle ID와 일치시킵니다.
2. 운영 환경의 `TOOL_SERVER_API_KEY`를 DAPMS가 전달하는 Key와 동일한 Secret으로 설정합니다.
1. `bundle-id`를 DATMS에 등록할 bundle ID와 일치시킵니다.
2. 운영 환경의 `TOOL_SERVER_API_KEY`를 DATMS가 전달하는 Key와 동일한 Secret으로 설정합니다.
3. 실제 배포 주소에 맞게 `AXHUB_TOOL_URL`을 설정합니다.
4. MCI·EAI·HTTP 연동 대상과 timeout을 환경별 설정으로 교체합니다.
5. `validateMcpToolNames`, `validateToolSchemaV17`, 전체 테스트를 실행합니다.
5. `validateMcpToolNames`와 모듈·전체 테스트를 실행합니다. YAML 정의를 추가했다면 `ToolSchemaV17ValidationRunner` 또는 관련 테스트로 V17 항목을 별도 검증합니다.
## 테스트와 검증
@@ -303,22 +303,17 @@ mcp:
# Tool 이름·중복 검사
.\gradlew.bat validateMcpToolNames
# 203개 Tool V17 정의 검사
.\gradlew.bat validateToolSchemaV17
# 검증, 테스트, 패키징
.\gradlew.bat clean build
```
### 현재 검증 상태
### 실행 전 확인
2026-08-18 기준 확인 결과입니다.
테스트 수와 성공 여부는 소스 변경에 따라 달라지므로 고정된 수치를 문서화하지 않습니다. 배포 전 현재 작업 트리에서 다음 명령을 실행해 확인합니다.
- 전체 `test`: 성공
- 총 94개 테스트 성공, 실패·오류·건너뜀 0개
- `validateMcpToolNames`: 성공
- `validateToolSchemaV17`: 203개 Tool 성공
- 실행 명령: `.\gradlew.bat test validateMcpToolNames validateToolSchemaV17`
```powershell
.\gradlew.bat test validateMcpToolNames
```
## Docker Compose
@@ -343,17 +338,75 @@ Compose 파일의 용도와 현재 주의점은 다음과 같습니다.
| 파일 | 용도 | 현재 소스 기준 주의점 |
|---|---|---|
| `docker-compose.yml` | DATMT 네 Pod 단독 실행 | `gateway` 서비스가 없지만 push 등록이 제거되어 DATMT 시작에는 필요하지 않음 |
| `docker-compose.local.yml` | DAPMS와 DATMT의 로컬 통합 구성 | 두 저장소가 같은 상위 디렉터리에 있는 구조를 가정 |
| `docker-compose.prod.yml` | DAPMS와 DATMT의 개발 프로필 기반 OCI 구성 | 저장소의 runner 등록 토큰을 운영 Secret으로 분리해야 함 |
| `docker-compose.local.yml` | DATMS와 DATMT의 로컬 통합 구성 | 두 저장소가 같은 상위 디렉터리에 있는 구조를 가정 |
| `docker-compose.prod.yml` | DATMS와 DATMT의 개발 프로필 기반 OCI 구성 | 저장소의 runner 등록 토큰을 운영 Secret으로 분리해야 함 |
`docker-compose.local.yml``docker-compose.prod.yml`의 build context는 각각 `./dat-was-dapms`, `./dat-was-datmt`입니다. 현재 파일 위치에서 사용할 때는 context 기준을 두 저장소의 상위 디렉터리로 맞춰야 합니다.
`docker-compose.local.yml``docker-compose.prod.yml`의 build context는 각각 `./dat-was-DATMS`, `./dat-was-datmt`입니다. 현재 파일 위치에서 사용할 때는 context 기준을 두 저장소의 상위 디렉터리로 맞춰야 합니다.
```powershell
# DATMT 저장소 디렉터리에서 실행
docker compose --project-directory .. -f docker-compose.local.yml up --build
```
현재 기존 네 Pod의 Compose 정의에는 `TOOL_SERVER_API_KEY` 환경 변수 전달이 없습니다. 따라서 컨테이너는 애플리케이션 기본값 `tool-server-key`를 사용합니다. 운영 배포 전 각 서비스에 Secret 기반 `TOOL_SERVER_API_KEY` 전달 설정을 추가하고 DAPMS의 Key와 일치시켜야 합니다. DAPMS는 각 Pod의 `AXHUB_TOOL_URL` 또는 배포 URL에 접근해 `/tool-manifest`를 pull할 수 있어야 합니다.
현재 기존 네 Pod의 Compose 정의에는 `TOOL_SERVER_API_KEY` 환경 변수 전달이 없습니다. 따라서 컨테이너는 애플리케이션 기본값 `tool-server-key`를 사용합니다. 운영 배포 전 각 서비스에 Secret 기반 `TOOL_SERVER_API_KEY` 전달 설정을 추가하고 DATMS의 Key와 일치시켜야 합니다. DATMS는 각 Pod의 `AXHUB_TOOL_URL` 또는 배포 URL에 접근해 `/tool-manifest`를 pull할 수 있어야 합니다.
## OpenShift (Kubernetes) 배포
OpenShift 개발 환경용 Kustomize 매니페스트는 [`k8s/`](k8s/)에 있습니다. 대상은 DATMT의 네 Tool Pod뿐이며 DATMS(Gateway)의 Deployment·Service·Route는 이 저장소에서 만들지 않습니다.
```text
k8s/
├─ base/ # 네 Pod 공통 ConfigMap, Service, Deployment
└─ overlays/dev/ # 개발 namespace, Registry 이미지 경로와 tag
```
### 배포 구조
각 Tool Pod는 Deployment 1개와 외부에 노출되지 않는 `ClusterIP` Service 1개를 사용합니다. OpenShift Route와 LoadBalancer Service는 생성하지 않으며, Gateway가 클러스터 내부 DNS로 호출합니다.
| Service | Pod 포트 | Gateway 호출 주소 |
|---|---:|---|
| `was-sal` | 8082 | `http://was-sal:8082` |
| `was-cus` | 8084 | `http://was-cus:8084` |
| `was-pro` | 8085 | `http://was-pro:8085` |
| `was-sys` | 8086 | `http://was-sys:8086` |
Gateway가 다른 namespace에 있으면 `was-sal.axhub-datmt-dev.svc`와 같은 FQDN을 사용하고, NetworkPolicy에서 Gateway namespace의 ingress를 별도로 허용해야 합니다.
### 반영 전 설정
다음 값은 실제 신한라이프 개발망 값으로 교체해야 합니다.
1. [`k8s/overlays/dev/kustomization.yaml`](k8s/overlays/dev/kustomization.yaml)의 namespace, 내부 Container Registry 경로, 배포 image tag
2. [`k8s/base/configmap.yaml`](k8s/base/configmap.yaml)의 `CHANGE_ME` MCI·EXTMCI·EAI 호스트
3. 실제 Secret 값
`datmt-runtime-secrets` Secret은 Git에 저장하지 않고 OpenShift namespace에서 별도로 생성합니다. 최소한 `TOOL_SERVER_API_KEY`는 DATMS가 전달하는 `X-Tool-Server-API-Key`와 같은 값이어야 합니다. DB 계정·비밀번호, API Key, 인증서 비밀번호 등도 이 Secret으로 관리합니다.
```powershell
# runtime-secrets.env는 저장소 밖에 보관합니다.
oc -n axhub-datmt-dev create secret generic datmt-runtime-secrets `
--from-env-file=runtime-secrets.env
```
Secret이 없으면 각 Deployment의 `envFrom.secretRef`를 해석할 수 없어 Pod가 시작하지 않을 수 있습니다.
### 이미지 빌드와 배포
모듈 Dockerfile은 미리 생성된 Boot JAR를 복사하므로, 이미지를 만들기 전에 네 모듈의 JAR를 빌드합니다. 개발망에서는 JDK/JRE 베이스 이미지와 Gradle/Maven 의존성을 내부 Registry·Nexus에서 사용할 수 있어야 합니다.
```powershell
.\gradlew.bat :dat-was-sal:bootJar :dat-was-cus:bootJar :dat-was-pro:bootJar :dat-was-sys:bootJar
# OpenShift 로그인 및 project 선택 후
oc kustomize k8s/overlays/dev
oc apply -k k8s/overlays/dev
oc get deployment,pod,svc -n axhub-datmt-dev
```
적용 전에는 `oc kustomize k8s/overlays/dev | oc apply --dry-run=client -f -`로 서버 측 스키마 검증을 수행합니다. 현재 매니페스트의 readiness/liveness probe는 TCP 포트 확인 방식입니다. Actuator health endpoint를 추가한 뒤에는 HTTP readiness/liveness probe로 변경하는 것을 권장합니다.
상세한 명령과 Gateway 연결 확인 방법은 [`k8s/README.md`](k8s/README.md)를 참고합니다.
## 보안 및 운영 주의사항
@@ -361,7 +414,7 @@ docker compose --project-directory .. -f docker-compose.local.yml up --build
- API Key 인터셉터는 `/rpc/**`, `/mcp/**`에 적용됩니다. 따라서 `/mcp`, `/mcp/{toolName}`, `/mcp/api/v1/tools/local`은 인증 대상입니다.
- `/tool-manifest`는 위 인터셉터 경로 밖에 있어 현재 API Key 인증 대상이 아닙니다. 내부망·Ingress 정책 또는 별도 인증이 필요한지 운영 기준을 확인해야 합니다.
- 네 업무 Pod의 기본 Key는 모두 `tool-server-key`입니다. 운영에서는 반드시 별도 Secret으로 교체하고 DAPMS의 `X-Tool-Server-API-Key` 값과 일치시켜야 합니다.
- 네 업무 Pod의 기본 Key는 모두 `tool-server-key`입니다. 운영에서는 반드시 별도 Secret으로 교체하고 DATMS의 `X-Tool-Server-API-Key` 값과 일치시켜야 합니다.
- 설정된 단일 Key와 다중 Key가 모두 없을 때만 익명 요청이 허용됩니다.
- `mcp.security.tenant-domains`는 설정 객체에 바인딩되지만 Tool별 인가에 사용되지 않습니다.
- `requiresApproval`은 메타데이터에만 기록되며 실행 차단이나 승인 확인 로직은 없습니다.
@@ -393,8 +446,8 @@ docker compose --project-directory .. -f docker-compose.local.yml up --build
1. Tool 메서드와 요청·응답 DTO를 구현합니다.
2. 동일 이름의 V17 YAML 정의를 `tool-definitions` 아래에 추가합니다.
3. `validateMcpToolNames``validateToolSchemaV17`을 실행합니다.
3. `validateMcpToolNames`모듈·전체 테스트를 실행합니다. Tool YAML 정의를 추가한 경우에는 V17 항목을 별도 검증합니다.
4. 모듈 테스트와 전체 테스트를 실행합니다.
5. 로컬 Pod에서 `/mcp/api/v1/tools/local``/tool-manifest`를 확인합니다.
6. DAPMS의 bundle ID, Pod Manifest URL, Tool Server API Key가 DATMT 설정과 일치하는지 확인합니다.
6. DATMS의 bundle ID, Pod Manifest URL, Tool Server API Key가 DATMT 설정과 일치하는지 확인합니다.
7. REST와 MCP 양쪽에서 동일한 Tool 결과·요청 헤더 전달·오류 계약을 확인합니다.

30
k8s/README.md Normal file
View File

@@ -0,0 +1,30 @@
# dat-was-datmt OpenShift 배포
이 매니페스트는 `was-sal`, `was-cus`, `was-pro`, `was-sys`를 Gateway 전용 내부 Service로 배포합니다. OpenShift Route는 만들지 않습니다.
## 반영 전 준비
1. `k8s/overlays/dev/kustomization.yaml`의 namespace, 내부 Registry 경로, release tag를 실제 개발망 값으로 바꿉니다.
2. `k8s/base/configmap.yaml``CHANGE_ME` 값을 개발망 MCI/EXTMCI/EAI 주소로 바꿉니다.
3. Secret 값은 Git에 저장하지 말고, 배포 namespace에서 별도로 만듭니다.
```powershell
oc -n axhub-datmt-dev create secret generic datmt-runtime-secrets --from-env-file=runtime-secrets.env
```
`runtime-secrets.env`에는 DB 계정·비밀번호, API 키, 인증서 비밀번호 등 민감값만 넣고 저장소에 추가하지 않습니다. 애플리케이션이 필요한 Secret 키를 사용하지 않는 경우에도 빈 Secret은 먼저 생성해야 Pod의 `envFrom.secretRef`가 정상 해석됩니다.
## 렌더링과 적용
```powershell
oc kustomize k8s/overlays/dev
oc kustomize k8s/overlays/dev | oc apply --dry-run=client -f -
oc apply -k k8s/overlays/dev
oc get deployment,pod,svc -n axhub-datmt-dev
```
## Gateway 연결 확인
Gateway가 같은 namespace에 있다면 `http://was-sal:8082`, `http://was-cus:8084`, `http://was-pro:8085`, `http://was-sys:8086`로 호출합니다. 다른 namespace에 있으면 `was-<module>.axhub-datmt-dev.svc` FQDN을 사용하고 NetworkPolicy에서 Gateway namespace의 ingress를 허용합니다.
현재 애플리케이션에는 Actuator health endpoint가 없으므로 TCP readiness/liveness probe를 사용합니다. Actuator를 추가한 뒤에는 HTTP health probe로 바꾸는 것을 권장합니다.

16
k8s/base/configmap.yaml Normal file
View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: datmt-runtime-config
labels:
app.kubernetes.io/part-of: axhub-datmt
data:
SPRING_PROFILES_ACTIVE: dev
TZ: Asia/Seoul
AXHUB_GATEWAY_URL: http://gateway:8080
GLOW_COMMUNICATION_MCI_HOST: CHANGE_ME
GLOW_COMMUNICATION_MCI_PORT: "26160"
GLOW_COMMUNICATION_EXTMCI_HOST: CHANGE_ME
GLOW_COMMUNICATION_EXTMCI_PORT: "8080"
GLOW_COMMUNICATION_EAI_HOST: CHANGE_ME
GLOW_COMMUNICATION_EAI_PORT: "9999"

243
k8s/base/deployments.yaml Normal file
View File

@@ -0,0 +1,243 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: was-sal
labels:
app.kubernetes.io/name: was-sal
app.kubernetes.io/part-of: axhub-datmt
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: was-sal
template:
metadata:
labels:
app.kubernetes.io/name: was-sal
app.kubernetes.io/part-of: axhub-datmt
spec:
containers:
- name: was-sal
image: was-sal:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8082
envFrom:
- configMapRef:
name: datmt-runtime-config
- secretRef:
name: datmt-runtime-secrets
env:
- name: AXHUB_TOOL_URL
value: http://was-sal:8082
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 60
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: was-cus
labels:
app.kubernetes.io/name: was-cus
app.kubernetes.io/part-of: axhub-datmt
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: was-cus
template:
metadata:
labels:
app.kubernetes.io/name: was-cus
app.kubernetes.io/part-of: axhub-datmt
spec:
containers:
- name: was-cus
image: was-cus:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8084
envFrom:
- configMapRef:
name: datmt-runtime-config
- secretRef:
name: datmt-runtime-secrets
env:
- name: AXHUB_TOOL_URL
value: http://was-cus:8084
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 60
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: was-pro
labels:
app.kubernetes.io/name: was-pro
app.kubernetes.io/part-of: axhub-datmt
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: was-pro
template:
metadata:
labels:
app.kubernetes.io/name: was-pro
app.kubernetes.io/part-of: axhub-datmt
spec:
containers:
- name: was-pro
image: was-pro:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8085
envFrom:
- configMapRef:
name: datmt-runtime-config
- secretRef:
name: datmt-runtime-secrets
env:
- name: AXHUB_TOOL_URL
value: http://was-pro:8085
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 60
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: was-sys
labels:
app.kubernetes.io/name: was-sys
app.kubernetes.io/part-of: axhub-datmt
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: was-sys
template:
metadata:
labels:
app.kubernetes.io/name: was-sys
app.kubernetes.io/part-of: axhub-datmt
spec:
containers:
- name: was-sys
image: was-sys:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8086
envFrom:
- configMapRef:
name: datmt-runtime-config
- secretRef:
name: datmt-runtime-secrets
env:
- name: AXHUB_TOOL_URL
value: http://was-sys:8086
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 60
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault

View File

@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- configmap.yaml
- services.yaml
- deployments.yaml

63
k8s/base/services.yaml Normal file
View File

@@ -0,0 +1,63 @@
apiVersion: v1
kind: Service
metadata:
name: was-sal
labels:
app.kubernetes.io/name: was-sal
app.kubernetes.io/part-of: axhub-datmt
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: was-sal
ports:
- name: http
port: 8082
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: was-cus
labels:
app.kubernetes.io/name: was-cus
app.kubernetes.io/part-of: axhub-datmt
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: was-cus
ports:
- name: http
port: 8084
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: was-pro
labels:
app.kubernetes.io/name: was-pro
app.kubernetes.io/part-of: axhub-datmt
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: was-pro
ports:
- name: http
port: 8085
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: was-sys
labels:
app.kubernetes.io/name: was-sys
app.kubernetes.io/part-of: axhub-datmt
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: was-sys
ports:
- name: http
port: 8086
targetPort: http

View File

@@ -0,0 +1,26 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: axhub-datmt-dev
resources:
- ../../base
labels:
- pairs:
environment: dev
includeSelectors: true
images:
- name: was-sal
newName: image-registry.openshift-image-registry.svc:5000/axhub-datmt-dev/was-sal
newTag: latest
- name: was-cus
newName: image-registry.openshift-image-registry.svc:5000/axhub-datmt-dev/was-cus
newTag: latest
- name: was-pro
newName: image-registry.openshift-image-registry.svc:5000/axhub-datmt-dev/was-pro
newTag: latest
- name: was-sys
newName: image-registry.openshift-image-registry.svc:5000/axhub-datmt-dev/was-sys
newTag: latest