직접 만든 이미지로 OpenSearch 를 Kubernetes 에 올리면 docker compose 에서는 잘 뜨던 것이 파드에서는 로그 한 줄만 남기고 아무것도 하지 않는 상태가 되곤 한다. 원인은 애플리케이션이 아니라 커널 파라미터 · 권한 · 설정 주입 방식에 있는 경우가 대부분이다. 순서대로 짚는다.
OpenSearch 는 메모리 맵 파일을 많이 쓴다. vm.max_map_count 가 기본값이면 기동 중 검사에 걸려 조용히 멈춘다.
sysctl vm.max_map_count # 262144 이상이어야 한다
노드에 직접 설정하는 것이 정석이다.
# /etc/sysctl.d/99-opensearch.conf
vm.max_map_count = 262144
sysctl --system
노드를 손댈 수 없으면 특권 init 컨테이너에서 설정한다. 그 노드 전체에 영향을 주므로 다른 워크로드를 고려해 정한다.
initContainers:
- name: sysctl
image: busybox:1.36
command: ["sh", "-c", "sysctl -w vm.max_map_count=262144"]
securityContext:
privileged: true
메모리 잠금을 쓰려면 컨테이너에 IPC_LOCK 능력이 필요하고 스왑이 꺼져 있어야 한다. Kubernetes 노드는 원래 스왑을 끄므로 보통 문제가 되지 않는다.
컨테이너 로그는 표준 출력만 수집된다. OpenSearch 기본 설정은 로그를 파일로 쓰므로 kubectl logs 에는 JVM 경고 몇 줄만 남고 조용해진다. log4j2.properties 에 콘솔 출력을 켜서 ConfigMap 으로 주입한다.
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%c{1.}] %m%n
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
이 파일은 $OPENSEARCH_PATH_CONF 아래에 있어야 읽힌다. 경로가 어긋나면 설정이 반영되지 않은 채로 돌아간다.
kubectl exec -it opensearch-0 -- sh -c 'echo $OPENSEARCH_PATH_CONF; ls -l $OPENSEARCH_PATH_CONF'
파일 하나만 갈아 끼울 때는 subPath 로 마운트한다. 디렉터리를 통째로 마운트하면 그 안의 기존 파일이 전부 가려져 필요한 것까지 사라진다.
volumeMounts:
- name: config
mountPath: /opt/opensearch/config/opensearch.yml
subPath: opensearch.yml
- name: config
mountPath: /opt/opensearch/config/log4j2.properties
subPath: log4j2.properties
volumes:
- name: config
configMap:
name: opensearch-config
subPath 로 마운트한 파일은 ConfigMap 이 바뀌어도 갱신되지 않는다. 설정을 고치면 파드를 다시 만든다.
단일 노드로 띄울 때는 발견 설정을 섞지 않는다. discovery.type: single-node 를 주면서 discovery.seed_hosts 나 cluster.initial_cluster_manager_nodes 를 함께 두면 기동이 꼬인다. 단일 노드에서는 앞의 한 줄만 둔다.
node.name 은 파드 이름과 맞춘다. StatefulSet 이면 downward API 로 받아 넣는 편이 안전하다.
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
오타에 주의한다. sngle-node 처럼 값이 틀려도 설정 파서가 조용히 넘어가고 나중에 엉뚱한 증상으로 나타난다.
node.max_local_storage_nodes 는 옛 Elasticsearch 시절 설정으로 지금은 쓰지 않는다. 옛 문서에서 옮겨 온 설정은 현재 버전의 문서와 대조한다.
데이터와 로그 디렉터리는 컨테이너가 실행되는 UID 소유여야 한다. 파드에 fsGroup 을 주고 PVC 를 쓰면 대부분 해결된다.
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
path.data 를 PVC 에, path.logs 를 그와 다른 위치에 두었다면 두 경로 모두 쓰기가 되는지 확인한다. 자세한 내용은 볼륨 권한 — fsGroup 과 UID 매핑 에 있다.
kubectl describe pod opensearch-0 | tail -30 # 이벤트에 OOMKilled 나 FailedMount 가 있는지
kubectl exec -it opensearch-0 -- ps -ef
kubectl exec -it opensearch-0 -- sh -c 'id; ls -ln /opt/opensearch/data /var/log/opensearch'
kubectl exec -it opensearch-0 -- curl -s localhost:9200
프로세스는 도는데 9200 이 열리지 않으면 아직 기동 중이거나 발견 단계에서 멈춘 것이다. 힙을 너무 작게 주면 기동 중에 죽으므로 OPENSEARCH_JAVA_OPTS 로 명시하고, 컨테이너 메모리 상한은 그보다 넉넉히 잡는다.
env:
- name: OPENSEARCH_JAVA_OPTS
value: "-Xms2g -Xmx2g"
resources:
limits:
memory: 4Gi