> For the complete documentation index, see [llms.txt](https://docs.tibero.com/sysmasterdb/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tibero.com/sysmasterdb/8.4_manual/installation-guide/installation/kubernetes-environment.md).

# Kubernetes 환경

SysMasterDB 8.4는 Kubernetes 환경에 **kustomize 기반 매니페스트**로 배포된다. 배포 파일의 **kubernetes** 디렉터리에 전체 스택 매니페스트와 클러스터 부트스트랩 스크립트가 포함되어 있으며, `kubectl apply -k` 명령 한 번으로 11개 모듈이 배포된다.

### 구성

<figure><img src="https://264733866-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fgw24XwSa6QXLFywb7Txd%2Fuploads%2FWO9ENHmhyERZNGBWFCpK%2Fsysmasterdb-8.4-kubernetes-v2.png?alt=media" alt="SysMasterDB 8.4 Kubernetes 구성도"><figcaption></figcaption></figure>

### 배포 절차

1. **이미지 로드 및 push** — 배포 파일의 이미지를 사이트 레지스트리에 등록한다.
2. **파라미터 설정** — 레지스트리 주소, 계정 암호, 데이터베이스 초기화 설정을 지정한다.
3. **클러스터 사전 준비** — `setup-once.sh`로 라이선스 Secret, 크리덴셜 Secret, DNS 설정을 등록한다.
4. **배포** — `kubectl apply -k kubernetes`
5. **접속** — LoadBalancer에 할당된 주소로 접속한다.

### 사전 요구사항

| 항목           | 요건                                                    |
| ------------ | ----------------------------------------------------- |
| Kubernetes   | 이미지 pull이 가능한 컨테이너 레지스트리                              |
| StorageClass | default StorageClass (ReadWriteOnce 영구 볼륨 3종, 총 40Gi) |
| 노드 자원        | 메모리 16GB 이상, CPU 4코어 이상 권장                            |
| 파일 디스크립터     | `nofile` 65536 이상 (노드 측 설정)                           |
| 라이선스         | SysMasterDB 8.4용 라이선스 파일 (version="1.1")              |
| 외부 노출        | LoadBalancer 또는 NodePort / Ingress                    |

***

### 매니페스트 구성

배포 파일의 **kubernetes** 디렉터리 구성은 다음과 같다.

{% code collapsedlinecount="100" %}

```
kubernetes/
├── kustomization.yml      배포 진입점. 레지스트리 주소와 외부 노출 방식을 지정한다
├── setup-once.sh          클러스터 1회 부트스트랩 (라이선스·크리덴셜 Secret, CoreDNS)
├── base/                  전체 스택 매니페스트. 파일은 추가·삭제하지 않고 값만 수정한다
│   ├── kustomization.yml    base 파일 목록
│   ├── foundation.yml       Namespace + 공통 환경변수(ConfigMap) + PVC 3개
│   ├── meta.conf            Meta DB 초기화 설정
│   ├── repo.conf            Repository DB 초기화 설정
│   ├── db/metadb.yml, db/repodb.yml
│   ├── kafka/zookeeper.yml, kafka/broker.yml
│   ├── sysmaster/sdm.yml, collector.yml, analyzer.yml, dbmanager.yml, chatbot.yml
│   └── client/client-gateway.yml, client/client-tibero.yml
├── gateway-lb.yml         사용자 UI/API를 LoadBalancer로 노출 (기본 적용)
├── collector-lb.yml       수집 포트 8292를 LoadBalancer로 노출 (기본 적용)
├── image-pull-secret.yml  [선택] 사설 레지스트리 인증
├── image-pull-policy.yml  [선택] 폐쇄망 환경의 이미지 pull 정책
├── https-cert.yml         [선택] 사이트 HTTPS 인증서 반입
├── logs-pvc.yml           [선택] 로그 영속화
├── logs-pvc-patch.yml     [선택] 위와 한 쌍
└── network-policy.yml     [선택] 파드 간 통신 제한
```

{% endcode %}

각 모듈 파일은 **디플로이먼트(Deployment) 1개 + 서비스(Service) 1개**로 구성된다.

{% hint style="info" %}
**참고**

매니페스트 파일에는 주석이 없다. 배포 환경의 터미널에서 문자가 정상적으로 표시되지 않는 경우가 있어, 설정값에 대한 설명은 모두 이 매뉴얼에 수록하였다. 아래 예시의 주석은 설명을 위해 추가한 것이다.
{% endhint %}

***

#### 1. 수정 위치 원칙

배포 파일은 제공된 **파일 구성 그대로** 사용한다. **새 파일을 만들거나 제공된 파일을 삭제·이름 변경하지 않고, 제공된 파일 안에서 파라미터 값만 수정한다.** 사이트별로 달라지는 값과 그 수정 위치는 아래 표와 같다.

`base` 디렉터리의 파일도 아래 표에 지정된 값은 직접 수정하되, 리소스(디플로이먼트·서비스·볼륨 등)를 추가하거나 삭제하지 않는다. 별도의 patch 파일은 만들지 않으며, 선택 구성 파일(`image-pull-secret.yml` 등)은 이미 제공된 파일이므로 **kustomization.yml**에 등록만 한다. 다음 버전으로 업그레이드할 때는 새 배포 파일에 변경했던 값을 다시 반영한다.

| 변경 대상                    | 수정 위치                                                      |
| ------------------------ | ---------------------------------------------------------- |
| 이미지 레지스트리 주소             | kustomization.yml의 `images[].newName`                      |
| 외부 노출 방식(LoadBalancer 등) | gateway-lb.yml, collector-lb.yml의 `type`                   |
| 메모리·CPU 상한, 개별 환경변수      | 각 모듈 매니페스트(`base/sysmaster/sdm.yml` 등)의 `resources`, `env` |
| 전 모듈 공통 설정               | base/foundation.yml의 `smdb-env`                            |
| DB 초기화 계정·암호             | base/meta.conf, base/repo.conf                             |

***

#### 2. kustomization.yml

배포는 이 파일 하나에서 시작한다. 배포 파일에 포함된 상태는 다음과 같다.

```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - base                  # 전체 스택 매니페스트
  - collector-lb.yml      # 수집 포트(8292) 외부 노출

images:                   # 각 이미지의 레지스트리 주소를 사이트 값으로 교체한다
  - { name: sysmaster-db-sdm,                    newName: {registry}/sysmaster-db-sdm }
  - { name: sysmaster-db-dbmanager,              newName: {registry}/sysmaster-db-dbmanager }
  - { name: sysmaster-db-collector,              newName: {registry}/sysmaster-db-collector }
  - { name: sysmaster-db-analyzer,               newName: {registry}/sysmaster-db-analyzer }
  - { name: sysmaster-db-tiberoopensql-postgres, newName: {registry}/sysmaster-db-tiberoopensql-postgres }
  - { name: sysmaster-db-zookeeper-loggable,     newName: {registry}/sysmaster-db-zookeeper-loggable }
  - { name: sysmaster-db-kafka-loggable,         newName: {registry}/sysmaster-db-kafka-loggable }
  - { name: sysmaster-db-client,                 newName: {registry}/sysmaster-db-client }
  - { name: sysmaster-db-client-tibero,          newName: {registry}/sysmaster-db-client-tibero }
  - { name: sysmaster-db-chatbot,                newName: {registry}/sysmaster-db-chatbot }
  - { name: sysmaster-db-alpine-linux,           newName: {registry}/sysmaster-db-alpine-linux }

patches:
  - path: gateway-lb.yml                              # 사용자 UI/API 외부 노출
    target: { kind: Service, name: client-gateway }
```

{% hint style="danger" %}
**주의 — `resources:`** **`patches:`** **`images:`는 파일에 한 번씩만 작성한다.**

선택 구성의 예시를 파일 끝에 그대로 덧붙이면 같은 키가 두 번 생기고, **kustomize는 오류나 경고 없이 마지막 것만 적용한다.** 앞에 있던 항목은 조용히 사라진다.

* `patches:`를 새로 작성하면 → `gateway-lb.yml`이 무효화되어 UI가 외부에 노출되지 않는다.
* `resources:`를 새로 작성하면 → `collector-lb.yml`이 무효화되어 수집 경로가 사라진다.
* `images:`를 새로 작성하면 → 나머지 이미지의 레지스트리 주소가 사라져 전체 파드가 `ImagePullBackOff` 상태가 된다.

항상 **기존 키의 목록 아래에 항목만** 추가한다. 수정 후에는 아래로 확인한다.

```
kubectl kustomize kubernetes | grep -c 'type: LoadBalancer'       # 2
kubectl kustomize kubernetes | grep -c 'name: collector-ingest'   # 1
```

{% endhint %}

***

#### 3. foundation.yml — 네임스페이스 · 공통 설정 · 영구 볼륨

{% code collapsedlinecount="100" %}

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: sysmasterdb
---
# 전 모듈이 공통으로 주입받는 환경변수.
# 값을 변경한 뒤에는 파드를 교체해야 반영된다.
#   kubectl -n sysmasterdb rollout restart deploy
# 계정 암호는 여기 두지 않는다 - setup-once.sh가 생성하는 smdb-credentials Secret이 담당한다.
apiVersion: v1
kind: ConfigMap
metadata:
  name: smdb-env
  namespace: sysmasterdb
data:
  TZ: "Asia/Seoul"
  USE_SENTRY: "false"
  DONT_CHECK_HTTP_HEALTH: "false"
  HEALTH_LOG_RETENTION_DAY: "30"
  ...
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: metadb-data
  namespace: sysmasterdb
spec:
  # storageClassName 미지정 -> 클러스터 default StorageClass로 프로비저닝된다.
  # default가 없거나 특정 StorageClass를 써야 하면 이 위치에 storageClassName: {StorageClass} 를 추가한다.
  # 미지정 상태에서 default도 없으면 PVC가 Pending에 머물고 파드가 기동하지 못한다.
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: repodb-data
  namespace: sysmasterdb
spec:
  # 수집 메트릭 저장소. 관제 대상 수와 보관 주기에 비례해 커진다.
  # 배포 후 확장은 StorageClass가 볼륨 확장을 지원할 때만 가능하므로 최초에 넉넉히 잡는다.
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 20Gi
---
# sdm이 생성하는 TPR/ASH 리포트 파일 저장소(/sysmaster/reports).
# 리포트 목록은 Meta DB에 남으므로, 파일이 사라지면 목록에는 보이는데 열리지 않는 상태가 된다.
# 반드시 영구 볼륨이어야 한다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sdm-reports
  namespace: sysmasterdb
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi
```

{% endcode %}

**영구 볼륨 3종**

| PVC           | 크기   | 저장 내용              | 유실 시                 |
| ------------- | ---- | ------------------ | -------------------- |
| `metadb-data` | 10Gi | 설정, 계정, 관제 대상 목록   | 초기 설치 상태로 돌아간다       |
| `repodb-data` | 20Gi | 수집된 메트릭 전체         | 관제 이력이 사라진다          |
| `sdm-reports` | 10Gi | 생성된 TPR/ASH 리포트 파일 | 목록에는 남지만 리포트를 열 수 없다 |

***

#### 4. 모듈 매니페스트 읽는 법

전체 모듈이 동일한 구조를 가진다. 다음은 **base/sysmaster/sdm.yml**의 예시이다.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata: { name: sdm, namespace: sysmasterdb }
spec:
  replicas: 1
  # 새 파드를 먼저 띄우지 않고 기존 파드를 내린 뒤 교체한다.
  # 기본값(RollingUpdate)이면 배포 중 두 파드가 잠시 공존하는데,
  # reports PVC가 ReadWriteOnce라 두 번째 파드가 볼륨을 붙지 못해 배포가 끝나지 않는다.
  strategy: { type: Recreate }
  selector: { matchLabels: { app: sdm } }
  template:
    metadata: { labels: { app: sdm } }
    spec:
      containers:
        - name: sdm
          image: sysmaster-db-sdm:{version}
          imagePullPolicy: Always
          envFrom: [{ configMapRef: { name: smdb-env } }]   # 공통 설정 일괄 주입
          env:
            # JVM 힙. 아래 메모리 limits의 약 2/3이며, limits를 조정하면 함께 변경한다.
            - { name: java_heap_size_max, value: "2g" }
            - { name: java_heap_size_min, value: "512m" }
            - { name: spring_datasource_hikari_jdbc-url, value: "jdbc:postgresql://metadb:5432/metadb" }
            - { name: spring_datasource_hikari_username, value: "sysmaster" }
            # 암호는 매니페스트에 두지 않고 Secret에서 참조한다.
            - { name: spring_datasource_hikari_password,
                valueFrom: { secretKeyRef: { name: smdb-credentials, key: METADB_PASSWORD } } }
            - { name: metric_retention_day, value: "7" }    # 수집 메트릭 보관 주기(일)
            - { name: logging_file_max-history, value: "30" }  # 로그 보관 일수
            - { name: logging_file_max-size, value: "20MB" }
            - { name: logging_file_total-size-cap, value: "1GB" }
            # 업그레이드 시 수행되는 데이터 마이그레이션 패치 제어.
            # 문제가 발생하면 해당 패치만 즉시 비활성화할 수 있다.
            #   kubectl -n sysmasterdb set env deploy/sdm patch_v8-2-1-to-v8-3-0-migration_skip=true
            - { name: "patch_v8-2-1-to-v8-3-0-migration_skip", value: "false" }
          ports:
            - { containerPort: 8090 }   # 내부 통신용
            - { containerPort: 8080 }   # 사용자 API용
          # 기동이 완료될 때까지 readiness/liveness를 유예한다(기동 중 재시작 방지).
          # 8080이 아니라 8090을 확인하는 이유: 8080에는 라이선스 상태 필터가 있어
          # 라이선스 만료·코어 초과 시 401을 반환하며, 이 경우 정상 동작 중인 파드가
          # 재시작되고 재기동도 실패해 복구가 불가능해진다.
          startupProbe:
            httpGet:
              path: /actuator/health
              port: 8090
              httpHeaders: [{ name: Authorization, value: smdb-internal }]
            periodSeconds: 5
            failureThreshold: 180       # 대기 한도 15분 (5초 x 180)
          readinessProbe:               # 실패 시 트래픽만 차단, 재시작하지 않는다
            httpGet:
              path: /actuator/health
              port: 8090
              httpHeaders: [{ name: Authorization, value: smdb-internal }]
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:                # 실패 시 컨테이너를 재시작한다
            httpGet:
              path: /actuator/health
              port: 8090
              httpHeaders: [{ name: Authorization, value: smdb-internal }]
            periodSeconds: 20
            failureThreshold: 6
          resources:
            requests: { memory: 1Gi, cpu: 200m }   # 스케줄링에 필요한 예약량
            limits:   { memory: 3Gi }              # 초과 시 컨테이너가 강제 종료(OOMKilled)된다
          volumeMounts:
            - { name: license, mountPath: /sysmaster/license }
            - { name: reports, mountPath: /sysmaster/reports }
            # subPath로 모듈별 하위 디렉터리에 기록한다. 로그 영속화(선택 구성) 시
            # 모듈 간 로그 파일명이 겹치는 것을 방지한다.
            - { name: logs, mountPath: /sysmaster/logs, subPath: sdm }
      volumes:
        # setup-once.sh가 생성한 라이선스 Secret. 없으면 sdm이 기동하지 못한다.
        - { name: license, secret: { secretName: sdm-license } }
        - { name: reports, persistentVolumeClaim: { claimName: sdm-reports } }
        # 로그는 파드 내부 임시 볼륨. 컨테이너가 재시작해도 남지만 파드가 교체되면 사라진다.
        - { name: logs, emptyDir: {} }
---
apiVersion: v1
kind: Service
metadata: { name: sdm, namespace: sysmasterdb }
spec:
  selector: { app: sdm }
  ports:
    - { name: master, port: 8090, targetPort: 8090 }
    - { name: http,   port: 8080, targetPort: 8080 }
```

{% hint style="warning" %}
**주의**

Kubernetes에서는 **서비스(Service)에 정의된 포트로만** 다른 파드에 접근할 수 있다. 컨테이너가 열어둔 포트라도 서비스에 없으면 다른 모듈이 접근하지 못한다. Meta DB / Repository DB 서비스에 헬스 확인용 7000 포트가 함께 정의되어 있는 것도 같은 이유이며, 이 포트를 제거하면 sdm이 DB 준비 상태를 확인하지 못해 계속 대기한다.
{% endhint %}

***

#### 5. 헬스 체크(probe)

전 모듈에 3종의 헬스 체크가 설정되어 있어, `kubectl get pods`에서 **`1/1 Running`으로 표시되면 실제로 요청을 처리할 수 있는 상태**를 의미한다.

| 종류               | 역할              | 실패 시                   |
| ---------------- | --------------- | ---------------------- |
| `startupProbe`   | 기동이 완료되었는가      | 한도 초과 시 컨테이너 재시작       |
| `readinessProbe` | 지금 요청을 받을 수 있는가 | 서비스에서 트래픽만 차단 (재시작 없음) |
| `livenessProbe`  | 살아 있는가          | 컨테이너 재시작               |

기동 대기 한도는 `periodSeconds` × `failureThreshold`이며 모듈마다 다르다.

| 모듈                                                      | 기동 대기 한도 | 확인 방식                           |
| ------------------------------------------------------- | -------- | ------------------------------- |
| metadb, repodb                                          | 10분      | 헬스 응답 본문 확인(최초 기동은 DB 생성으로 수 분) |
| sdm                                                     | 15분      | HTTP `/actuator/health` (8090)  |
| collector, analyzer, dbmanager, chatbot, client-gateway | 20분      | HTTP 또는 포트 접속                   |
| zookeeper, broker, client-tibero                        | 2분       | 포트 접속(client-tibero는 HTTP /)    |

기동이 지속적으로 실패하는 환경에서는 `failureThreshold` 값을 늘린다. `periodSeconds`는 장애 감지 속도에 영향을 주므로 변경하지 않는 것을 권장한다.

***

#### 6. 자원 할당

| 모듈             | requests (메모리/CPU)  | limits (메모리) | JVM 힙 |
| -------------- | ------------------- | ------------ | ----- |
| repodb         | 2Gi / 200m          | 16Gi         | –     |
| analyzer       | 1Gi / 200m          | 6Gi          | 4g    |
| sdm            | 1Gi / 200m          | 3Gi          | 2g    |
| collector      | 1Gi / 200m          | 3Gi          | 2g    |
| metadb         | 512Mi / 100m        | 2Gi          | –     |
| broker         | 1Gi / 100m          | 2Gi          | –     |
| dbmanager      | 512Mi / 100m        | 1Gi          | 640m  |
| chatbot        | 256Mi / 50m         | 1Gi          | –     |
| zookeeper      | 256Mi / 50m         | 512Mi        | –     |
| client-gateway | 64Mi / 20m          | 256Mi        | –     |
| client-tibero  | 64Mi / 20m          | 256Mi        | –     |
| **합계**         | **약 7.6Gi / 1.2코어** | **35Gi**     |       |

* `requests` 합계는 파드가 스케줄링되기 위해 노드에 남아 있어야 하는 최소 여유이다. 부족하면 파드가 `Pending` 상태에 머문다.
* `limits`는 상한이며, 메모리가 이를 초과하면 컨테이너가 강제 종료(OOMKilled)된다.
* CPU에는 상한을 두지 않았다. 수집이 몰리는 순간이나 기동 시 불필요하게 지연되는 것을 방지하기 위함이다.
* Meta DB / Repository DB의 공유 메모리(`/dev/shm`, 각 1Gi / 8Gi)는 **메모리 limits에 포함된다.** 공유 메모리를 늘리는 경우 limits도 함께 상향해야 한다.

{% hint style="warning" %}
**주의**

JVM을 사용하는 모듈(sdm, analyzer, collector, dbmanager)은 메모리 `limits`와 `java_heap_size_max`를 **함께** 조정해야 한다. limits만 낮추면 JVM이 힙을 확장하다 컨테이너 한도를 넘어 강제 종료된다.
{% endhint %}

***

#### 7. 모듈을 2개 이상으로 늘릴 수 없는 이유

전 모듈이 `replicas: 1`로 구성되어 있으며 이는 의도된 값이다.

| 모듈                                      | 사유                                              |
| --------------------------------------- | ----------------------------------------------- |
| sdm, metadb, repodb                     | 영구 볼륨이 ReadWriteOnce — 두 번째 파드가 볼륨을 붙지 못한다      |
| broker                                  | 브로커 ID가 고정되어 있어 두 파드가 동일 ID로 등록할 수 없다           |
| zookeeper                               | 단일 노드 구성 — 두 파드가 동시에 기동하면 broker의 접속 대상이 불확정해진다 |
| analyzer, collector, dbmanager, chatbot | 단일 인스턴스를 전제로 sdm에 등록·동작한다                       |

같은 이유로 위 모듈에는 `strategy: Recreate`가 적용되어 있다. **이 설정을 제거하면 배포가 완료되지 않거나, zookeeper의 경우 오류 없이 Kafka 메타데이터가 초기화될 수 있다.** 또한 자동 확장(HPA)은 사용할 수 없다.

***
