Elasticsearch 입문 4편 Quickstart. Docker Compose 로 ES + Kibana 한 번에 띄우고 _cat/health·문서 색인·Dev Tools 까지 hands-on.
이 글은 Elasticsearch 입문에서 운영까지 시리즈 38편 중 4편이에요. 1편에서 "검색·로그·AI 통합 플랫폼" 으로 정체를 잡았고, 2편에서 Index·Document·Shard·Replica·Mapping 다섯 단어를, 3편에서 Lucene 의 Segment·Inverted Index·Posting List 까지 따라갔어요. 이쯤 오면 머리는 어느 정도 만들어졌는데, 손이 비어 있어요. 이번 글은 손을 채우는 hands-on 입니다.
이 글은 Elasticsearch 8.x 공식 docs 와 Docker 공식 image (elastic/elasticsearch:8.x) 를 따라 풀어쓴 자료예요. 화면 앞에 노트북 한 대만 있으면 30분 안에 첫 검색 응답까지 갑니다.
막히는 자리는 8번째 섹션 *자주 만나는 사고* 에 거의 다 들어 있어요. 막히면 그 표부터 펴 보세요.
도입 — hands-on 이 가장 빠른 학습
검색 엔진은 글로만 읽으면 Inverted Index 가 머리에 안 박힌다 는 게 정설이에요. 문서를 한 번 직접 색인하고, 그 문서가 검색되는 응답을 두 눈으로 본다 — 이 한 사이클을 한 번이라도 돌리면 그다음 글들이 다 잘 읽혀요. 그래서 이 시리즈는 1~3편으로 머리를 잡자마자 4편에서 손을 묶도록 짰어요.
이번 글이 다루는 범위는 좁아요. Elasticsearch + Kibana 를 Docker Compose 로 띄우고, curl 로 첫 명령을 쳐 보고, Kibana Dev Tools 에서 같은 명령을 GUI 로 다시 쳐 보는 것 까지. 색인된 문서 한 건을 검색해서 hits 응답을 본다, 거기서 멈춰요. 인덱스 관리·매핑 설계·운영 튜닝은 5편부터 차례대로 들어가요.
처음 ES 를 만져 보는 사람이 가장 자주 막히는 자리가 환경 셋업 이에요. security 활성화·메모리 한도·포트 충돌 같은 자리에서 30분, 한 시간씩 빠지는데, 이 글은 최소 마찰 로 single-node + security 비활성 조합을 일부러 골랐어요. 운영에서 절대 따라 하면 안 되는 조합이라 9번째 섹션 운영 권장 패턴 에 별도로 정리해 뒀습니다.
사전 준비 — Docker Desktop·메모리 4GB+
이 글을 따라가려면 로컬에 세 가지가 갖춰져 있어야 해요. Docker Desktop (또는 OrbStack·colima 같은 대체) 이 깔려 있고, 메모리 4GB 이상 이 Docker 엔진에 할당돼 있어야 합니다. 운영 환경은 8GB+ 가 표준이지만 hands-on 한 사이클은 4GB 로도 충분해요. 마지막으로 curl — 맥/리눅스는 기본 설치, 윈도우는 WSL 또는 git bash 에 들어 있어요.
Docker Desktop 의 리소스 한도는 Settings → Resources → Memory 에서 확인합니다. 기본값이 2GB 인 환경이 종종 있어서 Elasticsearch 컨테이너가 OOMKilled 로 죽는 사고가 잦아요. 4GB 이상으로 올려 두는 게 안전해요.
리눅스 호스트는 한 가지가 더 있어요. 커널 파라미터 vm.max_map_count 가 262144 이상 이어야 ES 가 뜹니다. 기본값이 65530 인 배포판이 많아서 — sudo sysctl -w vm.max_map_count=262144 한 줄을 호스트에 박아 두세요. 맥/윈도우는 Docker Desktop 내부 VM 이 알아서 잡아 줘서 따로 안 해도 됩니다. 이 자리에서 막히는 사고가 가장 흔해서 8번째 섹션 사고 1 로 따로 떼서 다뤘어요.
작성 시점(2026-05-19) 기준 안정 메이저는 Elasticsearch 8.x · Kibana 8.x. 이 글은 elastic/elasticsearch:8.15.0 · docker.elastic.co/kibana/kibana:8.15.0 두 이미지를 사용합니다. 8.15 가 아니어도 8.x 안에서는 거의 그대로 동작해요.
Docker Compose 로 ES + Kibana 한 줄에
작업 폴더 하나를 만들어 둡니다. mkdir es-quickstart && cd es-quickstart 정도면 충분해요. 그 안에 docker-compose.yml 한 파일만 두면 끝입니다.
# docker-compose.yml
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
container_name: es-quickstart
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
ports:
- "9200:9200"
- "9300:9300"
volumes:
- es-data:/usr/share/elasticsearch/data
healthcheck:
test: ["CMD-SHELL", "curl -fs http://localhost:9200/_cluster/health || exit 1"]
interval: 10s
timeout: 5s
retries: 10
kibana:
image: docker.elastic.co/kibana/kibana:8.15.0
container_name: kibana-quickstart
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
elasticsearch:
condition: service_healthy
volumes:
es-data:
driver: local
읽어 둘 자리가 네 군데 있어요. discovery.type=single-node 는 마스터 선출 없이 노드 한 대로 클러스터를 구성 하는 옵션이에요. 운영에서는 master-eligible 노드 3대 이상이 표준이지만, 학습용은 한 대로 충분합니다.
xpack.security.enabled=false 는 ES 8.x 의 기본 활성 보안 (HTTPS·자체 서명 인증서·자동 비밀번호 생성) 을 끄는 옵션이에요. 이 한 줄 때문에 입문이 훨씬 매끈해져요. 운영에서는 절대 끄면 안 되고, 31편(Security) 에서 다시 다룹니다.
ES_JAVA_OPTS=-Xms1g -Xmx1g 는 JVM (Java Virtual Machine) heap 을 1GB 로 고정해요. ES 는 JVM heap = 컨테이너 메모리의 50%·최대 31GB 가 표준 가이드인데, 학습용은 1GB 면 충분합니다.
memlock + ulimits 는 swap 으로 밀려나는 사고 를 막는 옵션이에요. Elasticsearch 의 mmap 기반 검색 이 swap 으로 밀리면 응답이 ms → 초 단위로 폭증해서 — 운영은 거의 항상 켜요.
띄우기는 한 줄. docker compose up -d (또는 docker-compose up -d) 를 치면 두 컨테이너가 백그라운드로 뜹니다. 처음 한 번은 이미지를 받느라 몇 분 걸려요. 두 번째부터는 10초 안 에 헬스체크까지 떨어집니다.
상태 확인은 docker compose ps. State 가 Up (healthy) 이고, kibana-quickstart 가 Up 이면 끝. 안 떨어지면 docker compose logs elasticsearch | tail -50 로 로그를 까서 vm.max_map_count · memory · JVM 셋 중 어느 자리인지 보면 80% 진단 됩니다.
첫 curl 명령 — _cat 한 묶음
가장 먼저 칠 명령 세 개를 묶어서 클러스터가 살아 있는지 한 호흡에 확인해요. ES 의 _cat API 는 사람이 읽기 좋은 텍스트 응답 을 주는 진단용 엔드포인트 모음입니다.
# 클러스터 헬스
curl -s "http://localhost:9200/_cat/health?v"
# 노드 목록
curl -s "http://localhost:9200/_cat/nodes?v"
# 인덱스 목록
curl -s "http://localhost:9200/_cat/indices?v"
?v 는 verbose — 헤더를 같이 찍어 주는 플래그예요. 첫 명령 응답이 이런 모양이면 정상.
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_active_shards_percent
1747623890 12:24:50 docker-cluster green 1 1 0 0 0 0 0 0 - 100.0%
status 가 green 이면 모든 primary + replica shard 가 할당됨. yellow 면 primary 는 OK 인데 replica 미할당. red 면 primary 중 일부가 미할당 — 데이터 일부 읽기·쓰기 X. single-node 환경은 replica = 1 인 인덱스가 있으면 자연스레 yellow 가 떠요. 학습에서는 무시해도 됩니다.
두 번째 명령은 노드 한 대가 떠 있는지, 세 번째 명령은 지금 인덱스 목록 을 보여 줘요. 처음 띄운 상태라면 시스템 인덱스 (.security-*·.kibana_*) 몇 개 만 보여요. 사용자 인덱스는 다음 절에서 만듭니다.
기본 응답이 JSON 인 일반 API 와 달리 _cat 은 텍스트 표 라 셸 파이프라인 에 그대로 꽂아 쓰기 좋아요. 운영에서 현재 상태 한눈에 보기 용도로 가장 많이 손이 가는 자리예요.
첫 문서 색인·검색
이제 진짜 ES 다운 작업. 문서 한 건 색인 → 한 건 조회 → 검색 쿼리 한 사이클을 돌립니다.
# 1) 문서 색인 — my-index 의 _doc 타입에 id=1
curl -s -X PUT "http://localhost:9200/my-index/_doc/1" \
-H 'Content-Type: application/json' \
-d '{
"title": "Elasticsearch 입문",
"author": "smartlifen4n",
"tags": ["search", "lucene", "study"],
"published_at": "2026-05-19"
}'
응답에 "result": "created" 와 "_version": 1 이 박혀 있으면 색인 성공. 이 순간 ES 가 자동으로 동적 매핑(Dynamic Mapping) 으로 title=text·keyword·author=text·keyword·tags=text·keyword·published_at=date 매핑을 잡아 둬요. 매핑 확인은 curl -s "http://localhost:9200/my-index/_mapping?pretty" 한 줄.
조회는 GET 한 번이면 끝.
curl -s "http://localhost:9200/my-index/_doc/1?pretty"
응답의 _source 자리에 색인한 JSON 이 그대로 박혀 있어요. ES 가 원본 JSON 을 _source 필드에 저장하고, 별도로 Inverted Index 를 만들어 검색에 쓰는 구조 (3편에서 다뤘던 자리) 가 바로 이 응답에서 확인됩니다.
진짜 검색은 _search 엔드포인트.
curl -s -X POST "http://localhost:9200/my-index/_search?pretty" \
-H 'Content-Type: application/json' \
-d '{
"query": {
"match": {
"title": "입문"
}
}
}'
응답의 hits.total.value = 1 과 hits.hits[0]._score 가 0.x 양수 면 풀텍스트 검색 + BM25 점수 가 정상 동작하는 자리예요. BM25 (Best Matching 25, Lucene 기본 랭킹 함수) 는 문서 안 단어 빈도와 전체 문서 빈도를 함께 계산 해서 점수를 매기는데, 14편(Full-text 검색) 에서 깊이.
여기까지 가면 Quickstart 의 핵심 사이클 은 한 바퀴 다 돈 거예요. 30분 안에 여기까지 도착했으면 환경 셋업·기본 API 가 손에 잡힌 셈이고, 5편부터 인덱스 설계·매핑·검색 DSL 로 넘어가도 따라가는 데 큰 무리가 없어요.
Kibana Dev Tools — Console UI
같은 명령을 GUI 에서 쳐 볼 차례. 브라우저로 http://localhost:5601 을 열면 Kibana 가 떠요. 보안을 껐기 때문에 로그인 화면 없이 바로 홈으로 진입합니다.
좌측 햄버거 메뉴 → Management → Dev Tools 로 들어가면 화면이 왼쪽 에디터 / 오른쪽 응답 두 칸으로 갈려요. 이 화면이 Console 이고, 일상에서 ES 를 만질 때 가장 손이 많이 가는 자리예요.
쳐 보기는 curl 그대로 가 아니라 축약된 DSL. 예를 들어 위에서 친 문서 색인 을 Dev Tools 에서는 이렇게 씁니다.
PUT /my-index/_doc/2
{
"title": "Quickstart hands-on",
"author": "smartlifen4n",
"tags": ["docker", "kibana", "study"],
"published_at": "2026-05-19"
}
HTTP 메서드 + 경로 + 본문 만 쓰면 호스트·헤더·인증 은 Kibana 가 알아서 붙여 줘요. curl 대비 한 줄이 짧다 는 게 가장 큰 장점이에요.
세 가지 단축키를 같이 익혀 두면 작업이 빨라집니다. Ctrl+Enter (맥은 Cmd+Enter) 가 현재 커서가 놓인 명령을 실행해요. Ctrl+/ 가 줄 주석. Ctrl+I (맥은 Cmd+I) 가 JSON 본문을 자동 정렬해 줘요. 한 화면에 명령 10개를 쌓아 두고 Ctrl+Enter 로 하나씩 돌리는 게 일상 워크플로예요.
오른쪽 위 시계 아이콘이 Request History 예요. 직전에 친 명령들을 보존해 줘서, 어제 친 검색 쿼리를 다시 꺼내 쓸 수 있어요. 50개까지 자동 보존됩니다.
curl 과 Dev Tools 중 어느 쪽을 쓰느냐는 상황 에 따라요. 셸 스크립트·CI·자동화 는 curl, 탐색·디버깅·시연 은 Dev Tools 가 답이에요. 두 표기 사이의 변환은 Kibana 가 도와줘요 — 명령 옆 ⚙ → Copy as cURL 버튼이 curl 한 줄 로 자동 변환해 줍니다.
다음 단계 — 5편으로 연결
여기까지 따라왔으면 환경 + 첫 사이클 이 끝났고, 다음 자리는 인덱스를 의도적으로 설계해서 만드는 것 이에요. 5편(Index 관리) 에서 create index API · settings · alias · reindex 를 묶어서 다룹니다. 동적 매핑에 의존하지 않고 매핑을 미리 박는 법, 운영에서 alias 로 노출하는 법 두 가지가 핵심.
그 사이에 손에 안 익은 자리는 Dev Tools 에서 같은 명령을 다섯 번 더 쳐 보는 것 으로 메우는 게 좋아요. 색인하는 문서를 상품·블로그 글·로그 한 줄 처럼 형태를 바꿔 가며 넣고, _search 의 query.match · query.term · query.range 를 한 번씩 만져 보면 12~16편의 검색 DSL 들이 머리에 훨씬 잘 박혀요.
자주 만나는 사고
사고 1 — vm.max_map_count 미설정으로 ES 미기동
원인 — 리눅스 호스트의 vm.max_map_count 기본값이 65530 인 배포판이 흔해요. ES 가 mmap 으로 segment 파일을 매핑 하면서 최소 262144 를 요구해서, 컨테이너가 bootstrap check failure 로그를 남기고 죽어요.
해결 — 호스트에서 sudo sysctl -w vm.max_map_count=262144 를 즉시 적용하고, /etc/sysctl.conf 에 vm.max_map_count=262144 줄을 박아 재부팅에도 살아 남게 합니다. 맥/윈도우는 Docker Desktop 의 내부 VM 이 알아서 잡아 줘서 따로 안 해도 됩니다.
사고 2 — JVM Heap 부족 OOM
원인 — ES_JAVA_OPTS=-Xms2g -Xmx2g 를 박았는데 Docker Desktop 의 Memory 한도가 2GB 라서, JVM heap + off-heap + Lucene mmap 합산이 컨테이너 한도를 넘어 OOMKilled 가 떨어져요. docker compose ps 에 Restarting 이 반복되는 패턴.
해결 — Docker Desktop Memory 를 JVM heap × 2 이상으로 올려요. 학습은 4GB 한도 + heap 1g, 운영은 8GB+ 한도 + heap 4~8g 가 거친 가이드.
사고 3 — 9200·5601 포트 충돌
원인 — 다른 ES 인스턴스·Spring Boot 가 9200·5601 을 이미 점유 중이라 bind: address already in use 에러가 떨어져요.
해결 — lsof -i :9200 (맥/리눅스) 또는 netstat -ano | findstr 9200 (윈도우) 로 점유 프로세스 확인. 죽이거나, compose 의 ports 매핑을 9201:9200·5602:5601 로 바꿔 외부 포트만 갈아 끼우면 끝.
사고 4 — Kibana 가 ES 를 못 찾음
원인 — ELASTICSEARCH_HOSTS=http://localhost:9200 처럼 localhost 로 박으면 Kibana 컨테이너 안에서는 자기 자신 을 가리켜서 연결 실패. 컨테이너 네트워크에서는 서비스 이름 으로 가리켜야 해요.
해결 — ELASTICSEARCH_HOSTS=http://elasticsearch:9200 으로, compose 의 service 이름 을 쓰세요. 이 글의 compose 가 이미 맞춰 둔 상태.
사고 5 — security 비활성 미설정으로 https 강제
원인 — ES 8.x 는 기본으로 security on + 자체 서명 HTTPS 인증서 자동 생성 이라, 옵션 없이 띄우면 curl http://localhost:9200 응답이 empty reply / 401 로 떨어져요.
해결 — 학습 환경은 이 글처럼 xpack.security.enabled=false · xpack.security.http.ssl.enabled=false 두 줄을 박아 plain HTTP 로 띄워요. 운영은 반드시 on — 31편(Security) 에서 자체 서명 → 정식 인증서 전환과 elastic 비밀번호 재설정 까지 다룹니다.
사고 6 — network.host=0.0.0.0 누락
원인 — single-node + Docker 안에서 는 기본 bind 가 localhost (127.0.0.1) 라서 컨테이너 밖 호스트의 9200 으로는 응답이 안 와요. production mode 자동 전환 + bootstrap check 실패 패턴도 같이 나타나요.
해결 — network.host=0.0.0.0 또는 network.publish_host=_site_ 환경변수를 추가합니다. 이 글의 compose 는 discovery.type=single-node 가 development mode 를 켜 줘서 별도 설정 없이도 외부 접근이 가능해요.
사고 7 — mmapfs 한도 초과
원인 — Many indices · many segments 환경에서 open file 한도 (nofile) 가 부족하면 Too many open files 가 떨어져요.
해결 — compose 의 ulimits 에 nofile: 65536 을 추가하거나, 운영 호스트는 /etc/security/limits.conf 에 elasticsearch 사용자 한도를 박아 둡니다. 26편(Cluster Operations) 에서 깊이.
운영 권장 패턴
이 글의 compose 는 학습 최단 경로 라 운영에 그대로 쓰면 사고가 납니다. 운영 시작 전에 네 가지를 반드시 갈아 끼우세요.
첫째, security 활성. xpack.security.enabled=true 로 켜고 elastic / kibana_system 계정 비밀번호를 박은 뒤, 자체 서명 인증서 를 정식 CA 로 갈아 끼웁니다. 31편(Security) 의 메인 주제.
둘째, single-node → multi-node. master-eligible 3대 + data 노드 N대 구성이 표준이에요. single-node 는 split-brain 방지 quorum 이 X 라서 운영 데이터에 절대 X. 26편(Cluster Operations) 에서 깊이.
셋째, 데이터 볼륨 분리. 이 글은 named volume 한 개에 데이터를 박지만, 운영은 전용 디스크 (NVMe SSD) 를 bind mount 로 잡아 줘요. iops · throughput · latency 셋이 ES 성능의 60% 를 결정해요. 30편(Performance) 에서 깊이.
넷째, 모니터링 표준 셋업. _cluster/health · _nodes/stats · slow log 세 가지를 Grafana 또는 Kibana 대시보드에 박아 두는 게 표준. 30편(Monitoring) 에서 깊이.
시험 직전 한 번 더 — 압축 노트
- Docker Compose 한 파일 로 ES + Kibana 두 컨테이너를 한 번에 띄움. 이미지: elastic/elasticsearch:8.15.0 + kibana:8.15.0.
- discovery.type=single-node = 마스터 선출 없이 한 대로 클러스터 구성. 학습용.
- xpack.security.enabled=false = 8.x 의 기본 활성 보안을 꺼서 plain HTTP. 운영 X.
- ES_JAVA_OPTS=-Xms1g -Xmx1g = JVM heap 고정. 학습 1GB, 운영은 컨테이너 메모리의 50%·최대 31GB.
- 첫 사이클:
_cat/health→_cat/nodes→_cat/indices→PUT /my-index/_doc/1→GET /my-index/_doc/1→POST /my-index/_search. _catAPI = 사람이 읽기 좋은 텍스트 응답. 진단용.?v로 헤더 같이.- health 상태 = green (정상) · yellow (replica 미할당, single-node 의 정상 모습) · red (primary 미할당, 데이터 손실 위험).
- 동적 매핑(Dynamic Mapping) = 첫 색인 때 ES 가 자동으로 필드 타입 추론. 운영은 strict 권장.
- BM25 = Lucene 기본 랭킹.
hits.hits[].\_score자리. - Kibana Dev Tools = Management → Dev Tools. HTTP 메서드 + 경로 + 본문 형태.
- 단축키: Ctrl/Cmd+Enter 실행, Ctrl+/ 주석, Ctrl/Cmd+I JSON 정렬.
- Request History 50개 자동 보존, ⚙ → Copy as cURL 변환 버튼.
- 7대 사고: vm.max_map_count · JVM OOM · 포트 충돌 · Kibana 가 localhost 로 찾기 · security on/off · network.host · nofile 한도.
- 운영 X 조합: single-node + security off + heap 1g + named volume. 운영은 multi-node + security on + 4~8g + NVMe bind mount.
- 리눅스 호스트는
vm.max_map_count=262144가 필수. - 학습 4GB · 운영 8GB+ 가 Docker Memory 한도 거친 가이드.
시리즈 다른 편
- 이전 글 = 3편 Lucene 내부 — Segment·Inverted Index·Posting List
- 다음 글 = 5편 Index 관리 — Create·Settings·Alias·Reindex
- 6편 = ILM (Index Lifecycle Management) — Rollover·Hot·Warm·Cold·Delete
- 8편 = Mapping Deep — Static·Dynamic·Multi-field·Runtime
- 11편 = Korean Analyzer — Nori·mecab-ko·사용자 사전
- 26편 = Cluster Operations — Node 역할·Quorum·Allocation
- 30편 = Performance — Heap·GC·Slow Log·Profile API
- 31편 = Security — RBAC·TLS·API Key·Audit
- 32편 = Spring Data Elasticsearch — Repository·Template·POJO
한 줄 정리 — Docker Compose 한 파일 + _cat/health + PUT /my-index/_doc/1 + Dev Tools 단축키 (Ctrl+Enter) 까지, Elasticsearch 첫 사이클 30분 hands-on. 운영 X 조합 (single-node + security off) 으로 마찰을 줄이고, 5편부터 의도적 인덱스 설계로 넘어가는 디딤돌.