133 lines
7.6 KiB
Markdown
133 lines
7.6 KiB
Markdown
# 배포 정의
|
||
|
||
이 디렉터리는 **배포될 대상**을 정의한다. 빌드·이미지·배포 실행 방식은 사내 표준 CI/CD가 담당하며
|
||
이 저장소가 정하지 않는다.
|
||
|
||
## Helm Chart
|
||
|
||
[helm/mcp-server/](helm/mcp-server/)가 유일한 배포 정의다. values는 두 축으로 나뉜다.
|
||
|
||
| 파일 | 소유하는 것 |
|
||
|---|---|
|
||
| `values.yaml` | **배포 토폴로지.** 어떤 MCP가 어떤 Tool Service를 보는가, 공개 path, 가용성 등급 |
|
||
| `values-{dev,test,prod}.yaml` | **환경 차이.** namespace, 이미지, 공개 host·허용 CIDR, 등급별 replica·PDB, 리소스 |
|
||
|
||
설치할 때 두 번째 축을 `-f`로, 첫 번째 축에서 고를 배포 하나를 `--set deploymentKey=`로 지정한다.
|
||
|
||
```bash
|
||
helm upgrade --install processing-critical-mcp helm/mcp-server -f helm/mcp-server/values-dev.yaml --set deploymentKey=processing-critical -n <namespace>
|
||
```
|
||
|
||
`deploymentKey`에는 기본값이 없다. 지정을 빠뜨리면 렌더링 단계에서 멈춘다.
|
||
엉뚱한 배포가 조용히 설치되는 것보다 낫다.
|
||
|
||
### 공유 host와 배포별 path
|
||
|
||
[ADR-0009](../docs/decisions/ADR-0009-container-handles-public-mcp-path.md)에 따라 한 환경은 하나의 공개 host를 사용하고,
|
||
각 Helm release는 고유 path의 OpenShift Route를 만든다. Route는 Service만 선택하고 공개 path를 그대로
|
||
전달하며, 컨테이너가 같은 path를 직접 처리한다.
|
||
|
||
```text
|
||
https://mcp-dev.apps.example.internal/mcp/processing-critical -> processing-critical-mcp:8080/mcp/processing-critical
|
||
https://mcp-dev.apps.example.internal/mcp/information-standard -> information-standard-mcp:8080/mcp/information-standard
|
||
```
|
||
|
||
공개 URL은 각각 독립된 MCP다. Agent Builder는 URL별로 등록하고 initialize하며, 한 Route나 MCP Pod의
|
||
장애가 다른 path의 Deployment로 전파되지 않는다.
|
||
|
||
### MCP 하나는 Tool Service 하나만 본다
|
||
|
||
[ADR-0007](../docs/decisions/ADR-0007-one-mcp-per-tool-service.md)의 결정이다. 대상을 늘리는 방법은
|
||
bundle 목록을 늘리는 것이 아니라 **배포를 하나 더 만드는 것**이다.
|
||
|
||
```yaml
|
||
deployments:
|
||
processing-critical:
|
||
name: processing-critical-mcp
|
||
service: processing-critical-tools # ← 이름만. 주소는 template이 만든다
|
||
namePrefix: "processing." # ← 업무 단위. 등급을 넣지 않는다
|
||
tier: critical
|
||
publicPath: /mcp/processing-critical # ← 환경 host 안에서 유일
|
||
```
|
||
|
||
배포가 10개든 20개든 **파일 수는 늘지 않는다.** 전체 매핑을 한 화면에서 검토할 수 있고,
|
||
`--set`으로 고르는 값 하나만 배포마다 달라진다.
|
||
|
||
MCP Server와 Tool Service는 같은 namespace에 배포하므로 values에는 서비스 이름만 적고
|
||
주소는 template이 조립한다. 환경마다 URL을 반복하지 않으므로 오타로 다른 대상을 호출할 수 없다.
|
||
|
||
`identity`도 `{배포 이름}-{global.env}`로 template이 조립한다. 현재 Redis cache 구현이 이 값을
|
||
사용하지만, key namespace와 공유 정책은 아직 확정되지 않았다.
|
||
|
||
### 가용성 등급
|
||
|
||
배포를 업무 × 중요도로 나누는 목적은 **중요 등급에만 비용을 쓰기 위해서**다.
|
||
|
||
```yaml
|
||
tiers:
|
||
critical: { replicas: 3, podDisruptionBudget: true, spreadAcrossNodes: true }
|
||
standard: { replicas: 2, podDisruptionBudget: false, spreadAcrossNodes: false }
|
||
```
|
||
|
||
test와 prod의 `critical`은 **replica 2 이상, PodDisruptionBudget, 노드 분산 설정이 필수**다. replica가
|
||
1이면 rolling update 중 반드시 공백이 생기고, PDB가 없으면 노드 drain이 마지막 Pod을 내릴 수 있다.
|
||
`HelmDeploymentContractTest`는 values와 template의 정적 규칙을 검사한다. dev는 배포마다 Pod 1개로
|
||
운영하므로 이 검사 대상이 아니다.
|
||
|
||
정적 테스트는 Helm 렌더러를 실행하지 않는다. 실제 배포 파이프라인은 사용하는 환경과 등급별로
|
||
`helm lint`와 `helm template`을 실행해 병합된 values와 생성 YAML을 확인해야 한다.
|
||
|
||
```bash
|
||
helm lint helm/mcp-server -f helm/mcp-server/values-prod.yaml --set deploymentKey=processing-critical
|
||
helm template processing-critical-mcp helm/mcp-server -f helm/mcp-server/values-prod.yaml --set deploymentKey=processing-critical
|
||
helm template processing-standard-mcp helm/mcp-server -f helm/mcp-server/values-prod.yaml --set deploymentKey=processing-standard
|
||
```
|
||
|
||
**나누는 것만으로 가용성이 생기지는 않는다.** 같은 노드 배치, namespace 쿼터, 공통 Redis·클러스터
|
||
장애는 분할로 막히지 않는다. 남은 작업은 [extension-points.md](../docs/extension-points.md)의
|
||
"운영 적용 전 필수 보완"에서 관리한다.
|
||
|
||
### dev에서 MCP에 연결되지 않을 때
|
||
|
||
**먼저 Tool Service가 떠 있는지 확인한다.** readiness가 usable snapshot을 요구하므로, Tool Service가
|
||
없으면 MCP Pod은 Ready가 되지 못하고 Service endpoint에서 빠진다. dev는 배포마다 Pod 1개라
|
||
그 순간 그 MCP로는 아예 연결되지 않는다. "MCP가 죽었다"가 아니라 "읽을 Tool이 없다"는 뜻이다.
|
||
|
||
```bash
|
||
kubectl get pod -l app=<배포 이름> # 0/1 Ready이면 이 경우다
|
||
kubectl describe pod <pod> # Readiness probe 실패 사유
|
||
kubectl port-forward <pod> 9090:9090 # /actuator/toolBundles로 bundle 상태 확인
|
||
```
|
||
|
||
Tool Service가 뜨면 다음 refresh 주기(기본 30초) 안에 스스로 Ready가 된다. 재기동할 필요가 없다.
|
||
`/actuator/toolBundles`는 management 포트라 NetworkPolicy가 관제 namespace로 제한하므로,
|
||
개발자는 위처럼 `port-forward`로 본다.
|
||
|
||
## 확정 전 임시값
|
||
|
||
`values.yaml`의 Tool Service 이름·이미지 경로와 `values-{env}.yaml`의 namespace·공개 host·Route 허용 CIDR은 자리표시자다.
|
||
각 파일의 `TODO` 주석을 참고해 확정 시 교체하고, 존재하지 않는 배포는 `deployments`에서 삭제한다.
|
||
|
||
## 배포 시 알아야 할 앱 제약
|
||
|
||
아래는 이 애플리케이션의 동작에서 나온 사실이다. 각 항목의 정본은 링크한 문서이며 값을 여기 옮겨 적지 않는다.
|
||
|
||
| 제약 | 정본 |
|
||
|---|---|
|
||
| readiness·liveness는 management 포트에서 제공한다 | [architecture.md](../docs/architecture.md) |
|
||
| readiness는 첫 Tool 조회 시도 완료 후 usable snapshot이 있을 때만 UP이다 | [architecture.md](../docs/architecture.md) |
|
||
| `terminationGracePeriodSeconds`는 Spring drain보다 길어야 한다 | [architecture.md의 요청 시간 예산](../docs/architecture.md#요청-시간-예산) |
|
||
| **NetworkPolicy는 필수다. 비활성화 스위치를 두지 않았다** | [ADR-0006](../docs/decisions/ADR-0006-no-authentication-in-mcp.md) |
|
||
| 공개 path는 Route와 컨테이너 endpoint가 동일하게 사용한다 | [ADR-0009](../docs/decisions/ADR-0009-container-handles-public-mcp-path.md) |
|
||
| Redis key·TTL·공유 정책은 확정 전이다 | [extension-points.md](../docs/extension-points.md#운영-적용-전-필수-보완) |
|
||
| bundle은 정확히 하나다 | [ADR-0007](../docs/decisions/ADR-0007-one-mcp-per-tool-service.md) |
|
||
|
||
Route IP allowlist와 NetworkPolicy는 특히 중요하다. 이 서버는 인증·인가를 하지 않으므로 `/mcp`에
|
||
도달할 수 있다는 것이 곧 인가다. Route는 Agent Builder 고정 egress CIDR만 받고, NetworkPolicy는 Route
|
||
backend인 ingress controller와 명시한 Agent Builder namespace만 업무 포트에 허용한다.
|
||
|
||
## 미확정 항목
|
||
|
||
배포 정의를 이 저장소가 어디까지 소유하는지, namespace·registry 명명 규칙은 아직 확정되지 않았다.
|
||
[docs/extension-points.md](../docs/extension-points.md)에서 관리한다.
|