> 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/installation-parameters.md).

# 설치 및 파라미터 설정

Kubernetes 환경에서 SysMasterDB를 설치하는 과정이다.

SysMasterDB 8.4의 Kubernetes 배포 파일은 **kustomize** 기반 매니페스트로 제공된다. 배포 파일의 **kubernetes** 디렉터리에 전체 스택 매니페스트(**base**)와 클러스터 1회 부트스트랩 스크립트(**setup-once.sh**)가 포함되어 있으며, `kubectl apply -k` 명령 한 번으로 전체 스택이 배포된다.

각 매니페스트 파일의 구성과 설정값의 의미는 「Kubernetes 환경」 문서의 **매니페스트 구성** 항목을, 환경에 따라 선택적으로 적용하는 구성은 아래 [**9. 선택 구성**](#id-9) 항목을 참고한다.

### 1. **도커(Docker) 이미지 로드**

설치 파일이 준비된 디렉터리에서 아래의 명령을 수행하여 도커 이미지를 로드한다.

`docker load -i sysmaster-db-{version}.tar`

이때 로드된 도커 이미지 중 Kubernetes 배포에 사용하는 이미지는 다음과 같다.

* sysmaster-db-client:{version}
* sysmaster-db-client-tibero:{version}
* sysmaster-db-sdm:{version}
* sysmaster-db-dbmanager:{version}
* sysmaster-db-collector:{version}
* sysmaster-db-analyzer:{version}
* sysmaster-db-chatbot:{version}
* sysmaster-db-tiberoopensql-postgres:{version}
* sysmaster-db-kafka-loggable:{version}
* sysmaster-db-zookeeper-loggable:{version}
* sysmaster-db-alpine-linux:{version}

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

`sysmaster-db-alpine-linux`는 셸 도구가 포함된 유틸리티 이미지로, 평상시 배포에는 기동되지 않는다. 다음 두 경우에 사용되므로 다른 이미지와 함께 레지스트리에 push해 두는 것을 권장한다.

* [**9. 선택 구성**](#id-9)의 로그 영속화를 적용할 때 생성되는 로그 확인 전용 파드
* 셸 도구가 없는 파드를 조사할 때 사용하는 임시 디버그 컨테이너

로드된 전체 목록은 `docker images | grep sysmaster-db` 명령으로 확인할 수 있다.
{% endhint %}

***

### 2. **도커(Docker) 이미지 푸시**

로드한 도커 이미지들을 해당 환경에서 사용 중인 이미지 레지스트리로 태그를 변경하여 푸시한다.

```shell
docker tag sysmaster-db-sdm:{version} {registry}/sysmaster-db-sdm:{version}
docker push {registry}/sysmaster-db-sdm:{version}
# 나머지 이미지들도 동일하게 수행한다.
```

푸시가 완료되면 **kubernetes/kustomization.yml** 파일의 `images` 항목에서 각 이미지의 `newName`을 해당 레지스트리 주소로 수정한다. 이미지 태그는 base 매니페스트에 릴리스 버전으로 이미 지정되어 있으므로, 릴리스 버전과 다른 태그로 푸시한 경우에만 `newTag`를 추가로 지정한다.

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

레지스트리가 인증을 요구하는 환경에서는 [**9. 선택 구성**](#id-9)의 사설 레지스트리 인증을 함께 적용한다. 적용하지 않으면 전체 파드가 `ImagePullBackOff` 상태가 된다.
{% endhint %}

***

### 3. **쿠버네티스(Kubernetes) 오브젝트 구성**

**kubernetes/base** 디렉터리의 매니페스트가 전체 스택(11개 디플로이먼트)을 정의한다. 모든 오브젝트는 `sysmasterdb` 네임스페이스에 생성되며, 컴포넌트 간 통신은 서비스(Service) 이름으로 이루어진다. 각 디플로이먼트에 대한 설명은 다음과 같다.

| 디플로이먼트         | 설명                                     | 포트                  |
| -------------- | -------------------------------------- | ------------------- |
| client-gateway | 사용자가 브라우저를 통해 접속하게 될 웹 진입점(리버스 프록시)    | 443                 |
| client-tibero  | UI 정적 자원을 제공하는 웹 서버                    | 80                  |
| sdm            | 수집 정보를 조회하고 클라이언트와 통신하는 API 서버         | 8080(사용자), 8090(내부) |
| dbmanager      | 관제 데이터베이스에 대한 상태 확인과 Admin 기능을 수행하는 서버 | 8082                |
| collector      | 관제 데이터베이스로부터 데이터를 수집하는 서버              | 8292(수집), 8083(내부)  |
| analyzer       | 수집한 정보를 가공해 분석 및 저장하는 서버               | 8081                |
| chatbot        | AI 챗봇 서버                               | 8089                |
| metadb         | UI 관련 설정 정보를 저장하는 DB 서버                | 5432, 7000(헬스)      |
| repodb         | 관제 데이터베이스 수집 데이터를 저장하는 DB 서버           | 5432, 7000(헬스)      |
| broker         | Kafka 브로커                              | 9092                |
| zookeeper      | Kafka 코디네이터                            | 2181                |

***

### 4. **클러스터 사전 준비 (setup-once.sh)**

배포 전에 반드시 **kubernetes/setup-once.sh**를 클러스터당 1회 실행해야 한다. 실행하지 않으면 파드가 기동하지 못한다.

```shell
ADMIN_PASSWORD=... METADB_PASSWORD=... REPODB_PASSWORD=... \
  ./setup-once.sh /path/to/sysmaster-db-license.xml
```

스크립트가 수행하는 내용은 다음과 같다.

1. **sdm-license Secret 생성** — 발급받은 라이선스 파일을 Secret으로 등록하며, sdm 파드가 이를 `/sysmaster/license` 경로로 마운트한다. SysMasterDB 8.4는 license version="1.1"만 허용하며, 다른 버전의 라이선스를 전달하면 스크립트가 실패한다.
2. **smdb-credentials Secret 생성** — Admin 계정과 Meta DB/Repository DB 접속 암호를 Secret으로 등록한다. 매니페스트에는 암호가 평문으로 존재하지 않으며, 암호를 사용하는 파드(sdm/analyzer/chatbot)만 Secret을 주입받는다.
3. **CoreDNS rewrite 등록** — client-gateway가 `sdm` 이름을 해석할 수 있도록 `rewrite name sdm sdm.sysmasterdb.svc.cluster.local` 규칙을 CoreDNS에 추가한다.

스크립트는 `kubectl`과 `python3`를 사용하며, 두 명령이 PATH에 없으면 즉시 중단된다. 실행 메시지는 영문으로 출력된다.

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

크리덴셜 3종(ADMIN\_PASSWORD, METADB\_PASSWORD, REPODB\_PASSWORD)은 반드시 모두 환경변수로 전달해야 하며, 미지정 시 스크립트가 실패한다(평가/테스트 목적에 한해 `ALLOW_DEV_DEFAULTS=1` 설정으로 기본값 사용 가능).

이때 METADB\_PASSWORD와 REPODB\_PASSWORD는 아래 [**6. Meta DB 및 Repository DB 파라미터 설정**](#id-6.-meta-db-repository-db)의 **meta.conf**, **repo.conf**에 설정한 `pg_users`의 `pass` 값과 반드시 일치해야 한다(DB 초기화 계정 = 앱 접속 계정). 불일치 시 setup-once.sh가 실패한다.
{% endhint %}

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

CoreDNS 구성을 자동으로 변경할 수 없는 환경(다른 DNS 제공자를 사용하거나 kube-system 권한이 없는 경우)에서는 스크립트가 수동 적용 방법을 안내하며 중단된다. 이때는 클러스터 DNS 설정의 `.:53 {` 블록에 아래 한 줄을 직접 추가하고 DNS 파드를 재기동한다. 이 설정이 없으면 client-gateway가 `sdm` 이름을 해석하지 못해 UI 접속 시 502 오류가 발생한다.

```
rewrite name sdm sdm.sysmasterdb.svc.cluster.local
```

{% endhint %}

***

### 5. **설치 파라미터 설정**

**kubernetes/base/foundation.yml** 파일을 열어 `smdb-env` ConfigMap에서 전 모듈이 공통으로 사용하는 파라미터 값을 설정한다.

```yaml
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"
  KAFKA_MESSAGE_MAX_BYTES: "20971520"
  METADB_USER: "sysmaster"
  REPODB_USER: "sysmaster"
```

| 파라미터 이름                     | 설명                                                                                                                                                             | 초기값           |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| TZ                          | SysMasterDB 서버 Time-Zone 설정                                                                                                                                    | Asia/Seoul    |
| USE\_SENTRY                 | 외부 오류 리포팅 사용 여부. 운영 환경에서는 false로 둔다                                                                                                                            | false         |
| DONT\_CHECK\_HTTP\_HEALTH   | 컨테이너 내부 헬스 체크 스크립트 비활성화 여부                                                                                                                                     | false         |
| HEALTH\_LOG\_RETENTION\_DAY | 헬스 체크 로그 보관 주기                                                                                                                                                 | 30            |
| KAFKA\_MESSAGE\_MAX\_BYTES  | <p>카프카 메시지 사이즈 설정,<br>1MB \~ 2GB 범위로 설정 가능<br>smdb-env 값은 broker에만 반영된다. collector가 보내는 메시지의 최대 크기는 collector 디플로이먼트의 broker\_message-max-size이므로 함께 변경한다.</p> | 20971520 Byte |
| METADB\_USER                | chatbot 모듈의 Meta DB 접속 사용자 이름                                                                                                                                  | sysmaster     |
| REPODB\_USER                | chatbot 모듈의 Repository DB 접속 사용자 이름                                                                                                                            | sysmaster     |

{% hint style="warning" %}
**주의 — 아래 파라미터는 `smdb-env`에서 변경해도 반영되지 않는다.**

smdb-env에는 Docker Compose 환경과 같은 이름의 파라미터(아래 표의 왼쪽)도 들어 있지만, Kubernetes 배포에서는 사용되지 않는다. 각 모듈이 실제로 읽는 설정은 해당 디플로이먼트의 `env` 항목에 직접 지정되어 있으며, ConfigMap 쪽만 변경하면 **오류 없이 무시된다.** 아래 표의 오른쪽 값을 변경해야 한다.
{% endhint %}

| Compose 환경의 이름                               | Kubernetes 배포에서 실제 설정 위치                                                                   |
| -------------------------------------------- | ------------------------------------------------------------------------------------------ |
| RETENTION\_DAY (수집 정보 보관 주기)                 | **sdm 디플로이먼트의 `metric_retention_day`**                                                     |
| LOG\_RETENTION\_DAY                          | 각 디플로이먼트의 `logging_file_max-history`                                                       |
| LOG\_FILE\_SIZE / LOG\_TOTAL\_SIZE           | 각 디플로이먼트의 `logging_file_max-size` / `logging_file_total-size-cap`                          |
| LOG\_LEVEL                                   | 각 디플로이먼트의 `logging_level_root`                                                             |
| CONTAINER\_LOG\_PATH                         | 각 디플로이먼트의 `logging_file_path` 및 볼륨 마운트 경로                                                  |
| ADMIN\_USERNAME                              | sdm 디플로이먼트의 `application_admin_username`                                                   |
| SQL\_FLUSH\_THRESHOLD / SQL\_RS\_FETCH\_SIZE | collector 디플로이먼트의 `sql-loading_flush-threshold` / `sql-loading_fetch-size`                 |
| LIMIT\_SQL\_HASH\_COUNT                      | collector 디플로이먼트의 sql-loading\_limit-sql-hash-count                                        |
| PERFORMANCE\_LOGGING                         | sdm / analyzer / collector / dbmanager 각 디플로이먼트의 동일 이름 `env` 항목                            |
| ACCOUNT\_INACTIVE\_LOCK\_DAYS                | sdm 디플로이먼트의 `account_inactive-lock-days`                                                   |
| ACCOUNT\_REFRESH\_TOKEN\_EXPIRE\_DAYS        | sdm 디플로이먼트의 `account_refresh-token-expire-days`                                            |
| ACCOUNT\_PASSWORD\_HISTORY\_COUNT            | sdm 디플로이먼트의 `account_password-history-count`                                               |
| ACCOUNT\_PASSWORD\_CHANGE\_DAYS              | sdm 디플로이먼트의 `account_password-change-days`                                                 |
| SESSION\_MAX\_CONCURRENT\_SESSIONS           | sdm 디플로이먼트의 `security_session_max-concurrent-sessions`                                     |
| SESSION\_IDLE\_TIMEOUT\_MINUTES              | sdm 디플로이먼트의 `security_session_idle-timeout-minutes` (배포 파일에 기본 포함되지 않으며, 필요 시 `env`에 추가한다) |
| IP\_RESTRICTION\_TARGET                      | sdm 디플로이먼트의 `security_ip-restriction_target`                                               |
| IP\_RESTRICTION\_SUPER\_ADMIN\_IP            | sdm 디플로이먼트의 `security_ip-restriction_super-admin-ip`                                       |
| IP\_RESTRICTION\_TRUSTED\_PROXIES            | sdm 디플로이먼트의 `security_ip-restriction_trusted-proxies`                                      |
| ACCESS\_LOG\_RETENTION\_DAY                  | sdm 디플로이먼트의 `access-log_retention_day`                                                     |

예를 들어 수집 정보 보관 주기를 7일에서 14일로 변경하는 경우, **kubernetes/base/sysmaster/sdm.yml**에서 `metric_retention_day` 값을 직접 수정한다.

{% code collapsedlinecount="100" %}

```yaml
# kubernetes/base/sysmaster/sdm.yml
          env:
            ...
            - { name: metric_retention_day, value: "14" }    # 기본값 "7"
```

{% endcode %}

변경 후 `kubectl apply -k kubernetes`를 수행하면 sdm 파드가 자동으로 교체된다. 반영 여부는 아래로 확인한다.

```shell
kubectl -n sysmasterdb exec deploy/sdm -- printenv metric_retention_day
```

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

* 계정 암호(ADMIN\_PASSWORD, METADB\_PASSWORD, REPODB\_PASSWORD)는 ConfigMap이 아니라 위 [**4. 클러스터 사전 준비**](#id-4.-setup-once.sh)의 setup-once.sh가 생성하는 `smdb-credentials` Secret으로 관리된다.
* `smdb-env`의 값을 직접 변경한 경우에는 파드가 자동으로 교체되지 않는다. `kubectl -n sysmasterdb rollout restart deploy` 명령을 함께 수행해야 반영된다.
* 수집 정보 보관 주기를 늘리면 Repository DB 사용량도 비례해 증가하므로, `repodb-data` 볼륨 크기를 함께 검토한다.
  {% endhint %}

#### 계정 및 보안 파라미터 설정

**kubernetes/base/sysmaster/sdm.yml** 파일을 열어 `env` 항목에서 계정, 세션, 접속 IP 제한, 이벤트 로그 보관에 필요한 파라미터 값을 설정한다.

{% code collapsedlinecount="100" %}

```yaml
# kubernetes/base/sysmaster/sdm.yml
          env:
            ...
            - { name: "account_inactive-lock-days", value: "0" }
            - { name: "account_refresh-token-expire-days", value: "0" }
            - { name: "account_password-history-count", value: "1" }
            - { name: "account_password-change-days", value: "0" }
            - { name: "security_session_max-concurrent-sessions", value: "0" }
            - { name: "security_ip-restriction_target", value: "NONE" }
            - { name: "security_ip-restriction_super-admin-ip", value: "" }
            - { name: "security_ip-restriction_trusted-proxies", value: "127.0.0.0/8,::1,172.16.0.0/12,10.88.0.0/15" }
            - { name: "access-log_retention_day", value: "365" }
```

{% endcode %}

해당 과정에서 설정하는 파라미터에 대한 설명은 다음과 같다.

| 파라미터 이름                                    | 설명                                                                                                                                                                       | 초기값                                           |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------- |
| account\_inactive-lock-days                | <p>장기 미사용 계정 잠금<br>마지막으로 사용한 뒤 이 기간(일)이 지나면 로그인, 토큰 재발급, 역할 변경을 막는다.<br>0 이하면 기능을 끈다.</p>                                                                                | 0                                             |
| account\_refresh-token-expire-days         | <p>Refresh Token 유효기간<br>0 이하면 따로 정하지 않은 것으로 본다. 이때 유효기간은 장기 미사용 잠금이 켜져 있으면 잠금 기간과 상한이 같다.</p>                                                                           | 0                                             |
| account\_password-history-count            | <p>이전 비밀번호 재사용 금지<br>비밀번호를 바꿀 때 직전 N개와 같은 값으로는 바꿀 수 없다.<br>1\~24로 설정 가능하다.</p>                                                                                           | 1                                             |
| account\_password-change-days              | <p>비밀번호 주기적 변경 안내<br>마지막으로 비밀번호를 바꾼 뒤(값이 없으면 계정을 만든 뒤) 이 기간이 지나면 로그인 응답으로 변경을 안내한다.<br>0 이하면 기능을 끈다.</p>                                                                 | 0                                             |
| security\_session\_max-concurrent-sessions | <p>계정당 동시 세션 수<br></p><ul><li>0이면 제한하지 않는다(기본).</li><li>1 이상이면, 이 수를 넘는 로그인이 성공할 때 가장 먼저 로그인한 세션부터 끊는다.</li></ul>                                                        | 0                                             |
| security\_session\_idle-timeout-minutes    | <p>유휴 세션 자동 로그오프(분)<br>0 이하면 기능을 끈다.<br><br><strong>\[참고]</strong> sdm.yml에 기본 포함되지 않으며, 필요 시 <code>env</code>에 추가한다.</p>                                                | 30                                            |
| security\_ip-restriction\_target           | <p>허용IP 외 접속차단서비스</p><ul><li>NONE: 비활성화.</li><li>ADMIN: super admin과, 어느 그룹에서든 ADMIN 이상 역할이 있는 사용자에게만 적용한다.</li><li>ALL: 모든 사용자에게 적용한다.</li></ul>                        | NONE                                          |
| security\_ip-restriction\_super-admin-ip   | <p>super admin의 허용 IP<br>super admin의 허용 IP는 이 값으로만 정한다.</p>                                                                                                             | 빈값                                            |
| security\_ip-restriction\_trusted-proxies  | <p>프록시 대역<br></p><p>Kubernetes 환경에서는 client-gateway 파드가 속한 Pod 대역을 넣어야 한다.</p><p>사내 PC 대역(10.0.0.0/8, 192.168.0.0/16 등)은 넣으면 안 된다. 그 대역의 PC가 헤더를 위조해 IP 제한을 피할 수 있다.</p> | 127.0.0.0/8, ::1, 172.16.0.0/12, 10.88.0.0/15 |
| access-log\_retention\_day                 | <p>이벤트 로그 보관 기간(일)<br>ACCESS\_LOG ( 로그인/ 로그아웃 로그) 보관 기간을 설정한다.</p><p>0 이하면 기록, 보관, 삭제 정책이 모두 멈춘다. 이미 쌓인 데이터는 그대로 둔다.</p>                                                 | 365                                           |

변경 후 `kubectl apply -k kubernetes`를 수행하면 sdm 파드가 자동으로 교체된다.

***

### 6. **Meta DB 및 Repository DB 파라미터 설정**

**kubernetes/base/meta.conf**, **kubernetes/base/repo.conf** 파일이 Meta DB와 Repository DB의 초기화(사용자·데이터베이스·확장 생성)에 사용된다. 다음은 Meta DB에 대한 파라미터 설정 예시이다.

```yaml
pg_superuser: "postgres"
pg_superuser_password: "postgres"
pg_data: "/pgdata"
pg_log: "/sysmaster/logs"        # 로그 볼륨 마운트 경로와 일치해야 한다
pg_users:
  - name: "sysmaster"
    pass: "sysmaster"            # setup-once.sh 의 METADB_PASSWORD 와 같은 값이어야 한다
    role_attr_flags: LOGIN
pg_databases:
  - name: metadb
    owner: sysmaster
pg_extensions:
  - name: "timescaledb"
    database: "metadb"
    state: present
  - name: "vector"
    database: "metadb"
    state: present
pg_max_connections: 100
pg_owner_id: 1000
pg_group_id: 1000

pg_multi_params:
  - name: "shared_preload_libraries"
    value: "pg_stat_statements,timescaledb"
    state: present

pg_postgres_conf_params:
  - name: "shared_buffers"
    value: "256MB"
  - name: "effective_cache_size"
    value: "768MB"
  - name: "maintenance_work_mem"
    value: "64MB"
  - name: "work_mem"
    value: "3276kB"
  - name: "log_filename"
    value: "metadb-%H%M.log"
  - name: "log_timezone"
    value: "Asia/Seoul"
  - name: "log_min_duration_statement"
    value: "2000"
  - name: "log_min_messages"
    value: "INFO"
  - name: "log_parameter_max_length"
    value: "0"
  - name: "log_rotation_age"
    value: "60"
  - name: "log_rotation_size"
    value: "100MB"
  - name: "log_truncate_on_rotation"
    value: "on"
```

운영 환경에서는 `pg_superuser_password`(기본 postgres)와 `pg_users` 계정의 암호(기본 sysmaster)를 반드시 교체한다. 그 외 conf 파일은 기본적으로 제공되는 설정값을 사용하되, 다음에서 설명하는 파라미터는 필요 시 구동 환경에 맞게 사용자가 직접 설정해준다.

| 파라미터 이름              | 설명                                         |
| -------------------- | ------------------------------------------ |
| pg\_max\_connections | Meta DB(또는 Repository DB)의 최대 커넥션 수        |
| log\_timezone        | Meta DB(또는 Repository DB)의 로그 시간 Time-Zone |

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

이 파일들은 **데이터베이스 최초 초기화 시에만** 사용된다. 이미 초기화된 데이터베이스에는 값을 변경하고 파드를 재기동해도 반영되지 않으므로, 배포 전에 값을 확정해야 한다.

또한 `pg_log` 경로는 Meta DB / Repository DB의 로그 볼륨 마운트 경로(`/sysmaster/logs`)와 일치해야 한다. 다른 경로로 변경하면 DB 로그가 볼륨 외부에 기록되어 파드가 교체될 때마다 사라진다.
{% endhint %}

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

Meta DB 및 Repository DB 파라미터 변경이 필요한 경우, 관련 파라미터 설정은 OpenSQL 사용 가이드를 참조할 수 있다. 그러나 관련 파라미터 설정을 임의 수정하는 경우, SysMasterDB 서버의 정상 동작이 보장되지 않는다. 따라서 위 표에 명시된 파라미터 외 다른 파라미터는, 기본 제공 설정값을 그대로 사용하는 것을 권장한다.
{% endhint %}

***

### 7. **외부 노출 포트 설정**

외부 노출이 필요한 서비스는 LoadBalancer 타입으로 구성되어 있다.

| 서비스              | 포트   | 용도                                     |
| ---------------- | ---- | -------------------------------------- |
| client-gateway   | 443  | 사용자 UI/API 접속(https)                   |
| collector-ingest | 8292 | 관제 데이터베이스의 TPM Agent가 수집 정보를 push하는 포트 |

배포 후 아래 명령으로 할당된 주소를 확인한다.

```shell
kubectl -n sysmasterdb get svc client-gateway collector-ingest
```

`collector-ingest`에 할당된 `EXTERNAL-IP`의 8292 포트를 관제 데이터베이스의 TPM Agent에 수집 서버 주소로 등록한다. 이 주소에 Agent가 도달하지 못하면 수집 정보가 유입되지 않아 대시보드가 비어 있게 된다.

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

`EXTERNAL-IP`가 계속 `<pending>` 상태로 표시되는 경우 클러스터에 LoadBalancer 구현체가 없는 것이다. 이때는 **kubernetes/gateway-lb.yml**, **kubernetes/collector-lb.yml**의 `type`을 `NodePort`로 수정하거나 Ingress를 앞에 구성한다.
{% endhint %}

***

### 8. **기타 배포 전제**

| 항목                   | 요건                                                                              | 확인 방법                                                                 |
| -------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| default StorageClass | PVC 3종(metadb 10Gi / repodb 20Gi / sdm-reports 10Gi, ReadWriteOnce)이 이를 통해 생성된다 | `kubectl get sc`                                                      |
| 클러스터 DNS 주소          | client-gateway의 `NGINX_RESOLVER`(기본 10.96.0.10)가 실제 값과 일치해야 한다                  | `kubectl get svc kube-dns -n kube-system`                             |
| 노드 여유 자원             | 메모리 16GB 이상, CPU 4코어 이상 권장                                                      | `kubectl describe node`                                               |
| 파일 디스크립터             | `nofile` 65536 이상                                                               | 배포 후 `kubectl -n sysmasterdb exec deploy/repodb -- sh -c 'ulimit -n'` |

* default StorageClass가 없거나 특정 StorageClass를 사용해야 하는 경우 **kubernetes/base/foundation.yml**의 PVC 3종에 `storageClassName`을 지정한다. 지정하지 않은 상태에서 default도 없으면 PVC가 `Pending`에 머물고 파드가 기동하지 못한다.
* 클러스터 DNS 주소가 다른 경우 **kubernetes/base/client/client-gateway.yml**의 `NGINX_RESOLVER` 값을 수정한다. 값이 틀리면 UI 접속 시 502 오류가 발생한다.
* 파일 디스크립터는 **Kubernetes에서 파드 단위로 설정할 수단이 없다.** 컨테이너 런타임의 기본값을 상속하므로 노드 측에서 보장해야 한다(containerd의 `LimitNOFILE`). 값이 낮으면 Kafka가 `Too many open files` 오류로 종료되거나 PostgreSQL 접속이 실패한다.

***

### 9. **선택 구성**

기본 배포에는 포함되지 않으며, 환경에 따라 필요한 것만 **kubernetes/kustomization.yml**에 추가한다.

아래 예시에서 `[기존]`이 표시된 줄은 이미 파일에 있는 줄이고, `<- 추가`가 표시된 줄만 새로 작성하는 것이다. **`resources:`** **`patches:` 키를 새로 작성하면 기존 항목이 무효화된다**(매니페스트 구성 문서의 주의 사항 참고).

| 파일                                    | 사용 환경                                    |
| ------------------------------------- | ---------------------------------------- |
| `image-pull-secret.yml`               | 레지스트리가 인증을 요구하는 경우                       |
| `image-pull-policy.yml`               | 폐쇄망 — 레지스트리 장애 시에도 노드 캐시로 파드가 기동되도록      |
| `https-cert.yml`                      | 사이트 HTTPS 인증서로 교체                        |
| `logs-pvc.yml` + `logs-pvc-patch.yml` | 파드가 교체되어도 로그를 보존 (ReadWriteMany 스토리지 필요) |
| `network-policy.yml`                  | 파드 간 통신 제한 (CNI 지원 필요)                   |

***

#### 사설 레지스트리 인증 — image-pull-secret.yml

레지스트리가 익명 pull을 허용하지 않는 환경에서 필요하다. 적용하지 않으면 **전체 파드가 `ImagePullBackOff` 상태가 된다.**

```shell
kubectl -n sysmasterdb create secret docker-registry smdb-registry \
  --docker-server={registry} --docker-username={계정} --docker-password={암호}
```

```yaml
patches:
  - path: gateway-lb.yml                 # [기존] 삭제하지 않는다
    target: { kind: Service, name: client-gateway }
  - path: image-pull-secret.yml          # <- 추가
    target: { kind: Deployment }
```

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

`target`을 좁혀 일부 디플로이먼트에만 적용하면, 적용되지 않은 파드만 `ImagePullBackOff` 상태로 남는다. 전체 디플로이먼트에 적용해야 한다.
{% endhint %}

***

#### 폐쇄망 이미지 정책 — image-pull-policy.yml

기본값 `imagePullPolicy: Always`는 파드가 기동될 때마다(노드 재부팅, 재스케줄 포함) 레지스트리를 조회한다. 릴리스 이미지는 태그가 버전으로 고정되어 내용이 변하지 않으므로, 운영 환경에서는 **레지스트리가 점검·장애로 중단된 경우 이미지가 노드에 캐시되어 있어도 파드가 기동하지 못하는(`ErrImagePull`)** 위험만 남는다. 이 구성은 정책을 `IfNotPresent`로 완화한다.

```yaml
patches:
  - path: gateway-lb.yml                 # [기존] 삭제하지 않는다
    target: { kind: Service, name: client-gateway }
  - path: image-pull-policy.yml          # <- 추가
    target: { kind: Deployment }
```

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

적용 후에는 동일한 태그로 이미지를 다시 push해도 노드가 새 이미지를 가져오지 않는다. 이미지를 교체할 때는 새 태그를 사용하고 **kustomization.yml**의 `images` 항목에 `newTag`를 지정한다.
{% endhint %}

***

#### HTTPS 인증서 — https-cert.yml

기본 상태에서 client-gateway는 이미지에 내장된 self-signed 인증서를 사용한다. 서비스는 정상 동작하지만 브라우저에 인증서 경고가 표시되므로, 사이트 도메인 인증서를 반입한다.

```shell
kubectl -n sysmasterdb create secret generic smdb-https-cert \
  --from-file=sysmaster-db-server.crt={인증서 파일} \
  --from-file=sysmaster-db-server.key={개인키 파일}
```

```yaml
patches:
  - path: gateway-lb.yml                 # [기존] 삭제하지 않는다
    target: { kind: Service, name: client-gateway }
  - path: https-cert.yml                 # <- 추가
    target: { kind: Deployment, name: client-gateway }
```

`https-cert.yml`의 내용은 다음과 같다.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-gateway
  namespace: sysmasterdb
spec:
  template:
    spec:
      volumes:
        - name: ssl
          # base의 emptyDir를 지우지 않으면 볼륨 소스가 2개가 되어 API 서버가 거부한다.
          emptyDir: null
          secret:
            secretName: smdb-https-cert
```

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

Secret의 키 이름(= 마운트될 파일명)은 `sysmaster-db-server.crt`, `sysmaster-db-server.key`여야 한다. 다른 이름을 사용하면 nginx가 인증서를 읽지 못해 client-gateway 파드가 `CrashLoopBackOff` 상태가 되고, 사용자 UI 접속이 중단된다.
{% endhint %}

***

#### 로그 영속화 — logs-pvc.yml + logs-pvc-patch.yml

기본 구성에서 로그는 파드 내부 임시 볼륨에 기록되므로, **파드가 교체되면(배포, 노드 drain, eviction) 로그가 사라진다.** 장애 조사를 위해 로그를 보존해야 하는 환경에서는 전 모듈이 공유하는 영구 볼륨으로 전환한다.

**두 파일은 한 쌍이다.** `logs-pvc.yml`만 추가하면 파드는 여전히 임시 볼륨을 사용하고, patch만 추가하면 참조할 PVC가 없어 **전체 파드가 기동하지 못한다.**

```yaml
resources:
  - base                                 # [기존]
  - collector-lb.yml                     # [기존] 삭제하면 수집 진입점이 사라진다
  - logs-pvc.yml                         # <- 추가

patches:
  - path: gateway-lb.yml                 # [기존] 삭제하지 않는다
    target: { kind: Service, name: client-gateway }
  - path: logs-pvc-patch.yml             # <- 추가
    target: { kind: Deployment }
```

* **ReadWriteMany를 지원하는 StorageClass가 필요하다**(NFS, CephFS 등). 지원하지 않는 스토리지에 적용하면 PVC가 `Pending`에 머물고 전체 파드가 기동하지 못한다.
* `smdb-logs` PVC는 `storageClassName`을 지정하지 않은 상태로 제공된다. 즉 클러스터의 **default StorageClass**로 생성되므로, default가 ReadWriteMany를 지원하지 않으면 그대로는 `Pending`이 된다. Kubernetes는 접근 모드를 보고 StorageClass를 선택해 주지 않으므로, ReadWriteMany를 제공하는 StorageClass가 default가 아니면 직접 지정해야 한다.

```shell
kubectl get sc            # default 표시와 ReadWriteMany 제공 StorageClass 확인
```

{% code collapsedlinecount="100" %}

```yaml
# kubernetes/logs-pvc.yml
spec:
  storageClassName: {RWX-StorageClass}     # <- 추가
  accessModes: ["ReadWriteMany"]
```

{% endcode %}

* 용량 기본값은 50Gi이다. 수집 규모와 보관 주기에 맞춰 조정한다. **가득 차면 로그 기록이 실패한다.**
* 볼륨 안에서 각 모듈은 자기 이름의 하위 디렉터리에 기록한다(`sdm/`, `analyzer/` 등). 모듈별로 디렉터리를 분리하지 않으면 압축 보관되는 오류 로그 파일명이 모듈 간에 겹쳐 서로 덮어쓰게 된다.
* 이 구성에는 **로그 확인 전용 파드**가 함께 포함된다. 공유 볼륨을 읽기 전용으로 마운트하고 대기만 하므로, 애플리케이션 파드가 기동하지 못하는 상태에서도 전체 모듈의 로그를 확인할 수 있다.

```shell
kubectl -n sysmasterdb exec -it deploy/smdb-logs-viewer -- sh
kubectl -n sysmasterdb exec deploy/smdb-logs-viewer -- sh -c 'tail -f /sysmaster/logs/sdm/sdm.v.*.log'
```

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

로그 확인 전용 파드는 유틸리티 이미지(`sysmaster-db-alpine-linux`)를 사용한다. 이 구성을 사용하려면 해당 이미지도 레지스트리에 push되어 있어야 한다.
{% endhint %}

***

#### 파드 간 통신 제한 — network-policy.yml

Kubernetes는 기본적으로 동일 네임스페이스의 모든 파드가 서로 자유롭게 통신할 수 있으며, 클러스터 내의 다른 워크로드에서도 파드 IP로 직접 접근할 수 있다. 클러스터를 다른 서비스와 공유하거나 내부망 격리 요건이 있는 환경에서는 이 구성으로 **실제로 필요한 통신 경로만** 허용한다.

```yaml
resources:
  - base                                 # [기존]
  - collector-lb.yml                     # [기존]
  - network-policy.yml                   # <- 추가
```

{% hint style="danger" %}
**주의 — 파일 전체를 그대로 적용한다.**

첫 규칙이 모든 수신을 차단하고 나머지 규칙이 필요한 경로만 다시 허용하는 구조이다. 일부만 선택해 적용하거나 라벨·포트를 수정하면 해당 경로가 차단되어 파드가 기동하지 못하거나, **수집 파이프라인(collector → broker → analyzer)이 오류 없이 중단된다.**
{% endhint %}

허용되는 통신 경로는 다음과 같다. 구성이나 포트를 변경한 경우 이 규칙도 함께 갱신해야 한다.

| 대상             | 허용 출처                                   | 포트         |
| -------------- | --------------------------------------- | ---------- |
| client-gateway | 제한 없음(브라우저)                             | 443        |
| collector      | 제한 없음(TPM Agent)                        | 8292       |
| collector      | sdm                                     | 8083       |
| sdm            | client-gateway                          | 8080, 8090 |
| sdm            | analyzer, collector, dbmanager, chatbot | 8090       |
| metadb, repodb | sdm, analyzer, chatbot, dbmanager       | 5432, 7000 |
| broker         | collector, analyzer, broker             | 9092       |
| zookeeper      | broker                                  | 2181       |
| client-tibero  | client-gateway                          | 80         |
| chatbot        | sdm                                     | 8089       |
| dbmanager      | sdm                                     | 8082       |
| analyzer       | sdm                                     | 8081       |

**적용 후 검증**

**CNI가 NetworkPolicy를 지원해야 한다.** calico, cilium 등은 정상 동작하지만, 지원하지 않는 CNI는 **정책을 오류 없이 무시한다.** 적용된 것으로 오인하지 않도록 반드시 실제 차단 여부를 확인한다.

애플리케이션 이미지에는 `nc` 등의 네트워크 도구가 없으므로 `python`으로 확인한다. chatbot 파드에서 sdm 파드로 두 포트를 시도하며, **허용 경로인 8090은 `OPEN`, 차단 대상인 8080은 `BLOCKED`** 여야 정상이다(8080은 client-gateway만 접근할 수 있는 포트이다).

```shell
SDM_IP=$(kubectl -n sysmasterdb get pod -l app=sdm -o jsonpath='{.items[0].status.podIP}')
kubectl -n sysmasterdb exec deploy/chatbot -- python -c 'import socket,sys
ip=sys.argv[1]
for p in (8090, 8080):
    try:
        socket.create_connection((ip, p), 5).close(); print(p, "OPEN")
    except Exception as e:
        print(p, "BLOCKED", type(e).__name__)' "$SDM_IP"
```

* **8090이 `OPEN`이어야 이 판정이 유효하다.** 두 포트 모두 `BLOCKED`로 표시되면 정책이 아니라 확인 절차 자체가 실패한 것이다(파드 IP 오기재, sdm 미기동 등). 이 경우 차단되었다고 판단하지 않는다.
* 8080이 `OPEN`이면 CNI가 정책을 무시하고 있는 것이다. 클러스터의 CNI 종류를 확인한다.
