-
k8s에 Elasticsearch 환경 구축하기환경 구축 2024. 10. 14. 06:29
자동목차
진행중인 프로젝트에서 query dsl 을 사용해서 검색기능을 구현했었는데,
제목, 카테고리, 해시태그 등에서 키워드를 찾다보니 join 연산이 많이 필요했습니다.
그러다보니 검색 성능이 좋지 못했고 따라서 elasticsearch를 사용해 검색 성능을 개선시켰습니다.
현재 백엔드, 프론트엔드, 데이터베이스 서버가 모두 k8s에 배포되어있는데요, 여기에 elk 환경을 함께 구축했습니다.
이번 포스팅에서는 우선 elasticsearch 환경을 구축해보고자 합니다.
추가로 한글 형태소 분석기인 nori-analyzer, 초성검색을 위해 사용한 korean-analyzer를 위한 환경 구축도 다뤄보겠습니다.

Elasticsearch 환경 구축
1️⃣ configMap
먼저 Elasticsearch에 필요한 설정을 정의할 configMap을 생성합니다.
apiVersion: v1 kind: ConfigMap metadata: name: elasticsearch-config namespace: elk data: elasticsearch.yml: | discovery.type: "single-node" xpack.security.enabled: false node.roles: ["master", "data"] network.host: 0.0.0.0 # 접속하려면 이게 꼭 필요discovery.type: "single-node"
이 설정은 단일 노드 모드로 실행하겠다는 의미입니다. 이 설정을 사용하면 클러스터 탐색이 비활성화되고, 단일 노드 환경에서 쉽게 실행할 수 있습니다.
xpack.security.enabled: false
보안 인증이나 암호화가 필요 없는 경우 X-Pack 보안을 비활성화하여 간단하게 사용할 수 있도록 설정합니다.
node.roles: ["master", "data"]
이 노드를 마스터와 데이터 노드 역할로 지정하여 클러스터 관리와 데이터 저장 역할을 동시에 수행하도록 합니다.
만약 노드가 2개 이상의 구성이였다면 노드 하나는 클러스터를 관리하는 master역할, 데이터 저장을 관리하는 data역할을 각각 맡도록 할 수 있지만, 현재 단일노드로 구성했기 때문에 하나의 노드가 master와 data 역할 둘 다 맡도록 했습니다.
network.host: 0.0.0.0
0.0.0.0은 모든 네트워크 인터페이스에서의 접속을 허용하여, 다른 애플리케이션이나 클라이언트가 이 노드에 접근할 수 있도록 설정합니다. 추후에 logstash가 접속하는 등 외부에서 es로 접속하기 위해서는 해당 설정이 필요합니다.
2️⃣ Statefulset
다음은 Elasticsearch 서버를 실행할 StatefulSet 형태의 Pod를 생성합니다.
Elasticsearch는 데이터 관리 차원에서 3개 이상의 노드를 구성하길 권장합니다. 따라서 Deployment보다는 고유한 pod ID를 제공해주는 Statefulset이 더 적절합니다.
(하지만 우선 초기 구축이기 때문에 노드는 하나로만 구성했습니다.)
Elasticsearch - Statefulset
apiVersion: apps/v1 kind: StatefulSet metadata: name: elasticsearch namespace: elasticsearch spec: replicas: 1 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: elasticsearch:7.17.16 ports: - containerPort: 9200 name: http-rest - containerPort: 9300 name: tcp volumeMounts: - name: elasticsearch-data mountPath: /usr/lib/elasticsearch/data - name: elasticsearch-config mountPath: /usr/share/elasticsearch/config/elasticsearch.yml subPath: elasticsearch.yml volumes: - name: elasticsearch-config configMap: name: elasticsearch-config volumeClaimTemplates: - metadata: name: elasticsearch-data spec: storageClassName: "longhorn-storage" # Longhorn 스토리지 사용 accessModes: [ "ReadWriteOnce" ] # RWO 모드 resources: requests: storage: 5Gielasticsearch의 데이터들은 elasticsearch-data 볼륨에 저장됩니다. loghorn-storage를 사용해 데이터가 관리되도록했습니다.
처음에 볼륨 설정을 /usr/share/elasticsearch/data로 했을때 에러가 났는데요,
Elastic search AccessDeniedException[/usr/share/elasticsearch/data/nodes/0] -- Access denied error
다음과 같이 경로를 수정하니 해결됐습니다.
/usr/share/elasticsearch/data → /usr/lib/elasticsearch/data또한 위에서 만든 configMap또한 볼륨 설정을 해주었습니다.
subPath는 configMap의 키 중 elasticsearch.yml라는 키만 해당 경로로 볼륨설정을 해주기 위함입니다.
현재는 configMap에 elasticsearch.yml라는 키 하나만 존재하지만, 만약 configMap에 키가 여러개 있을 경우 subPath를 설정 안해주면 모든 키가 볼륨설정이 돼서 의도치 않게 다른 파일들이 덮여씌워질 수 있기 때문에 안전하게 subPath를 사용해 특정 키만 볼륨설정되도록 해주었습니다.
3️⃣ Service
마지막으로 Elasticsearch에 접근할 수 있도록 Service를 생성합니다.
내부에서만 접근하기 때문에 기본 type인 ClusterIP로 생성하면 됩니다.
Elasticsearch - Service
apiVersion: v1 kind: Service metadata: name: elasticsearch namespace: elasticsearch spec: ports: - name: http-rest protocol: TCP port: 9200 targetPort: 9200 - name: tcp protocol: TCP port: 9300 targetPort: 9300 selector: app: elasticsearch추가로 저는 postman으로 요청날려서 간편하게 es연결 상태를 확인하고 싶었기 때문에 외부에서 접근할 수 있도록 임시로 NodePort Service를 만들어주었습니다.
apiVersion: v1 kind: Service metadata: name: elasticsearch namespace: elk spec: type: NodePort ports: - name: http-rest protocol: TCP port: 9200 targetPort: 9200 nodePort: 32000 - name: tcp protocol: TCP port: 9300 targetPort: 9300 nodePort: 32001 selector: app: elasticsearch4️⃣ 연결 확인
이렇게 elasticsearch 환경 구축이 완료됐습니다.
postman에서 NodePort Service로 접속해 http://[node ip주소]:32000 으로 GET요청을 날리거나 또는 es서버에 접속해 curl 요청을 보냈을 때 다음과 같은 응답을 받으면 연결이 성공적으로 완료된것입니다.
curl -X GET "http://[node ip 주소]:32000"
{ "name": "783cf9ff43a3", "cluster_name": "es-cluster", "cluster_uuid": "bS0a0Zk3TmeQK0540-QwfQ", "version": { "number": "7.17.16", "build_flavor": "default", "build_type": "docker", "build_hash": "2b23fa076334f8d4651aeebe458a955a2ae23218", "build_date": "2023-12-08T10:06:54.672540567Z", "build_snapshot": false, "lucene_version": "8.11.1", "minimum_wire_compatibility_version": "6.8.0", "minimum_index_compatibility_version": "6.0.0-beta1" }, "tagline": "You Know, for Search" }
Nori-Analyzer, Korean-Analyzer 사용하기
위와 같은 방법으로 es환경을 구축했지만, 검색 기능 개선을 위해서 추가적인 플러그인을 사용했습니다.
elasticsearch에는 analyzer를 사용해 텍스트를 분석해 저장하는데, 한글의 경우 기본 analyzer로는 정확한 형태소 분석이 어렵기 때문에 한글 형태소 분석기인 nori-analyzer와 초성검색을 위한 korean-analyzer도 함께 사용하려고 했습니다.
외부 analyzer를 사용하기 위해서는 Plugin을 설치해줘야합니다.
만들어진 pod에 직접 install해도 되지만, 그러면 pod가 삭제되고 생성될 때마다 다시 설치해줘야해서 관리가 어려워지겠죠?
따라서, pod가 생성될 때 자동으로 플러그인이 설치되도록 설정하는 것이 필요합니다.
처음에는 PV와 ConfigMap을 이용해 /usr/share/elasticsearch/bin/elasticsearch-plugin에 직접 볼륨을 설정하거나, Docker Hub를 사용해 컨테이너를 만들 때 플러그인을 함께 설치하는 방법을 고려했습니다.
하지만 PV와 ConfigMap은 직접 생성해 관리해야 한다는 점, Docker를 사용할 경우 이미지 재빌드와 배포 과정이 복잡해진다는 점에서 해당 방법들은 적절하지 못하다고 생각했습니다.
다른 방법으로 postStart 라이프사이클 훅을 이용해 Pod가 시작될 때 플러그인이 자동으로 설치되도록 설정했습니다.
postStart 라이프사이클 훅은 Kubernetes에서 컨테이너가 시작된 직후에 실행할 명령을 지정할 수 있는 기능입니다.
이를 위해서는 위에서 작성한 statefulset 파일을 조금 수정해야하는데요, 그 전에 nori-analyzer는 간단하게 install 명령어로 설치가 가능하지만, korean-analyzer는 간단 install 명령어로는 설치가 안되기 때문에 사전 작업이 필요합니다.
github로 설치 링크 만들기
korean-analyzer를 간단한 install 명령어로 설치되게하기 위해 github로 설치 링크를 만드는 방법입니다.
먼저 korean-analyzer는 아래 링크에서 코드를 다운받아 사용했습니다.
https://github.com/HYS1753/elasticsearch-custom-plugin-korean-analyzer
🚨 주의할 점은 es 버전을 맞춰줘야합니다. 저는 7.17.16 버전을 사용했기 때문에, build.gradle에서 es 관련된 버전을 모두 7.17.16로 바꿔줬습니다.
1. zip파일 생성하기
./gradlew assemble --warning-mode all위 명령어를 치면 build/distributions 경로에 zip파일이 생성됩니다.
2. github에서 repository생성해서 zip파일을 올리기.
3. github에서 해당 파일로 이동하기
4. Raw라는 버튼을 우클릭 후에 링크주소를 복사하기.
(복사한 링크가 다운로드 주소입니다)


Statefulset 수정하기 (⛔️ 잘못된 방법 ⛔️)
이 방법은 플러그인을 적용하면서 겪은 시행착오로 최종 방법이 아닙니다. 최종 방법은 아래의 "Elasticsearch Operator(ECK) 사용하기"를 참고해주세요
이제 다운로드 링크를 만들었으니, statefulset을 수정해보겠습니다.
앞서 말했듯이 postStart 라이프사이클 훅을 이용해 컨테이너 생성 직후에 플러그인을 다운받도록 해줍니다.
수정된 Statefulset yml파일은 다음과 같습니다.
apiVersion: apps/v1 kind: StatefulSet metadata: name: elasticsearch namespace: elk spec: replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: elasticsearch:7.17.16 ports: - containerPort: 9200 name: http-rest - containerPort: 9300 name: tcp volumeMounts: - name: elasticsearch-config mountPath: /usr/share/elasticsearch/config/elasticsearch.yml subPath: elasticsearch.yml lifecycle: postStart: exec: command: - /bin/sh - -c - | echo "Attempt to Install nori analyzer plugin" if ! bin/elasticsearch-plugin list | grep -q analysis-nori; then echo "Installing nori analyzer plugin" >> /usr/share/elasticsearch/logs/plugin_install.log bin/elasticsearch-plugin install analysis-nori else echo "nori analyzer already installed" >> /usr/share/elasticsearch/logs/plugin_install.log if ! bin/elasticsearch-plugin list | grep -q korean-analyzer; then echo "Installing korean analyzer plugin" >> /usr/share/elasticsearch/logs/plugin_install.log bin/elasticsearch-plugin install [아까 만든 다운로드 링크] echo "Restarting Elasticsearch to apply changes" >> /usr/share/elasticsearch/logs/plugin_install.log kill -HUP 1 # Elasticsearch 프로세스 재시작 else echo "korean analyzer already installed" >> /usr/share/elasticsearch/logs/plugin_install.log fi volumes: - name: elasticsearch-config configMap: name: elasticsearch-config volumeClaimTemplates: - metadata: name: elasticsearch-data spec: storageClassName: "longhorn-storage" # Longhorn 스토리지 사용 accessModes: [ "ReadWriteOnce" ] # RWO 모드 resources: requests: storage: 5Gicommand 부분을 보면 먼저 nori 플러그인이 있는지 확인 후에 없으면 설치하고, korean analyzer도 같은 방식으로 설치를 진행합니다.
설치 상태를 확인하기 위해 log가 찍히도록 했고,
echo "log message" >> /usr/share/elasticsearch/logs/plugin_install.logkorean-analyzer까지 설치된 후에 변경 사항이 적용되도록 하기 위해 es서버를 재시작합니다.
kill -HUP 1이렇게 pod를 생성한 후에 다음 명령어로 플러그인 목록을 확인해보면, nori-analyzer와 korean-analyzer가 있는걸 확인할 수 있습니다.
/usr/share/elasticsearch/bin/elasticsearch-plugin list
하.지.만.
이렇게 플러그인까지 설치된 후에 인덱스를 두 analyzer를 사용해 생성해보면 nori-analyzer는 적용이 잘 되지만, korean-analyzer의 filter들을 찾지 못하는 문제가 있었습니다.
원인을 찾아보니,
1. korean-analyzer는 설치 후에 꼭 서버 재시작을 해줘야하는데, kill -HUP 1 명령어가 잘 안먹힌다는 것이 문제인 것 같았습니다.
kill -HUP 1 명령어가 잘 안먹히는 이유는 PID 1 프로세스라는 것과 관련있었습니다. 정확한 원인은 참고만 해주시면 될 것 같습니다.
StatefulSet 또는 Kubernetes에서 특정 컨테이너 실행 방식에 따라 Elasticsearch가 PID 1로 실행되지 않을 수 있습니다.
이 경우, kill -HUP 1 명령을 통해 프로세스를 재시작하려 해도 Elasticsearch 프로세스가 대상이 아니기 때문에 신호가 전달되지 않습니다.
PID 1이 Elasticsearch가 아닌 경우
kill -HUP 1 신호는 컨테이너의 PID 1 프로세스에만 전달됩니다. Elasticsearch가 PID 1 프로세스가 아닌 경우, 이 신호가 Elasticsearch가 아닌 init 프로세스나 다른 스크립트에 전달되므로 재시작 효과가 없습니다.2. 또한, 해당 명령어가 잘 먹힌다해도 문제는 서버가 pod로 돌아가다보니 재시작을 하면 재시작이 아니라 pod가 삭제됐다가 재생성돼서,
플러그인 설치 -> 재실행 -> pod 재생성 -> 플러그인 설치 -> ......
이런 무한 루프에 빠질 가능성이 있어보였습니다.
(정답은 아닐 수도 있어서 혹시 다른 정확한 원인을 아신다면 알려주시면 감사하겠습니다!)
그래서 또다시 다른 방법을 찾아야했습니다. 😂
Elasticsearch Operator(ECK) 사용하기 (👍 최종 방법 👍)
이 문제를 해결하기 위해 StatefulSet 대신 Elasticsearch Operator(ECK)를 사용했습니다.
ECK(Elastic Cloud on Kubernetes)은 Kubernetes에서 Elasticsearch클러스터를 쉽게 배포하고 관리하도록 도와주는 도구입니다.
일반적으로 Kubernetes에서는 Deployment, StatefulSet, ConfigMap 같은 리소스 종류에 따라 kind를 지정하지만, ECK를 사용하면 리소스 종류 자체가 Elasticsearch가됩니다.
ECK는 우선 Statefulset과 달리 pod를 재시작할 수 있고, ECK가 사용하는 Elasticsearch 공식 Docker 이미지는 기본적으로 Elasticsearch 프로세스를 PID 1로 실행하도록 설계되어있습니다.
그럼 ECK를 설치한 후에 yml 파일을 작성해보겠습니다.
1. 아래 명령어로 eck를 설치합니다
kubectl create -f https://download.elastic.co/downloads/eck/2.14.0/crds.yaml kubectl apply -f https://download.elastic.co/downloads/eck/2.14.0/operator.yaml2. pod를 만들 yml 파일을 작성합니다.
apiVersion: elasticsearch.k8s.elastic.co/v1 kind: Elasticsearch metadata: name: elasticsearch-eck namespace: elk spec: version: 7.17.16 nodeSets: - name: default count: 1 volumeClaimTemplates: - metadata: name: elasticsearch-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 3Gi storageClassName: longhorn-storage podTemplate: spec: containers: - name: elasticsearch env: - name: READINESS_PROBE_PROTOCOL value: http volumeMounts: - name: elasticsearch-config mountPath: /usr/share/elasticsearch/config/elasticsearch.yml subPath: elasticsearch.yml lifecycle: postStart: exec: command: - /bin/sh - -c - | if ! bin/elasticsearch-plugin list | grep -q analysis-nori; then echo "Installing nori analyzer plugin" >> /usr/share/elasticsearch/logs/plugin_install.log bin/elasticsearch-plugin install analysis-nori else echo "nori analyzer already installed" >> /usr/share/elasticsearch/logs/plugin_install.log if ! bin/elasticsearch-plugin list | grep -q korean-analyzer; then echo "Installing korean analyzer plugin" >> /usr/share/elasticsearch/logs/plugin_install.log bin/elasticsearch-plugin install [아까 만든 다운로드 링크] echo "Restarting Elasticsearch to apply changes" >> /usr/share/elasticsearch/logs/plugin_install.log kill -HUP 1 # Elasticsearch 프로세스 재시작 else echo "korean analyzer already installed" >> /usr/share/elasticsearch/logs/plugin_install.log fi volumes: - name: elasticsearch-config configMap: name: elasticsearch-config참고로, eck로 파드를 생성하면 gui방식으로 삭제하거나 delete명령어로는 삭제가 안되고 아래 명령어로 삭제해야합니다.
kubectl delete elasticsearch elasticsearch-custom -n [namespace]3. Service도 새로 만듭니다.
apiVersion: v1 kind: Service metadata: name: elasticsearch namespace: elk spec: ports: - name: http-rest protocol: TCP port: 9200 targetPort: 9200 - name: tcp protocol: TCP port: 9300 targetPort: 9300 selector: apps.kubernetes.io/pod-index: "0" common.k8s.elastic.co/type: elasticsearchselector에서 apps.kubernetes.io/pod-index: "0" 와 common.k8s.elastic.co/type: elasticsearch 는 Elasticsearch pod를 만들면 자동으로 생성되는 레이블입니다.
apps.kubernetes.io/pod-index: "0" 는 현재 싱글노드지만, 노드가 여러개일 경우 마스터 파드만 관리하도록 하기 위해 필요한 레이블입니다.
common.k8s.elastic.co/type: elasticsearch 는 혹시 이 Service가 상관 없는 pod를 관리하게 되는 것을 방지하기 위해 필요한 레이블로, 이 레이블 말고 다른 커스텀 레이블을 사용해도 됩니다.
마무리 요약
드디어 한글 형태소 분석과 초성검색이 가능한 elasticsearch 환경 구축이 끝났습니다.
여러 최종 환경구축 과정을 정리해보겠습니다.
1. es 설정파일을 관리하는 configMap yml 작성하기
2. korean-analyzer 다운로드 링크 만들기
3. eck 설치 후에 Elasticsearch 리소스 yml 파일 작성하기
4. service yml 작성하기
깃허브 주소
필요한 파일들을 정리해둔 깃허브 주소입니다.
https://github.com/shinebyul/elk_k8s_setting
GitHub - shinebyul/elk_k8s_setting: k8s에 elk 환경 세팅하기
k8s에 elk 환경 세팅하기. Contribute to shinebyul/elk_k8s_setting development by creating an account on GitHub.
github.com
'환경 구축' 카테고리의 다른 글
docker로 elk환경 세팅하기 (1) 2024.11.01 k8s에 Logstash 환경 구축하기 (1) 2024.10.28 서버 이중화(6) - Clustering 클러스터링 / Galera (1) 2024.05.29 서버 이중화(5) - DB 서버에 HAproxy 연결하기 (2) 2024.05.28 서버 이중화(4) - DB서버로 핫사이트 실습해보기 / Keepalived / Virtual IP (3) 2024.05.16