[Elasticsearch] Kibana ๊ตฌ์„ฑ (with Docker)

[์›๋ณธ ๋งํฌ]

Docker Compose ๊ธฐ๋ฐ˜์œผ๋กœ Kibana + Elasticsearch ์Šคํƒ์„ ๋ง์•„์„œ ์˜ฌ๋ฆฌ๋Š” ๋ฒ•์„ ๋Œ€๊ฐ• ๋‹ค๋ค„๋ณด๊ฒ ๋‹ค.

Kibana๋Š” Elasticsearch Stack์— ์˜ˆ์†๋œ ๋Œ€์‹œ๋ณด๋“œ ์ „์šฉ ํ™˜๊ฒฝ์ด๋‹ค. ์ด๊ฒƒ ์ž์ฒด๋กœ ๋ญ ์—„์ฒญ๋‚œ๊ฑธ ํ•˜์ง€ ์•Š๊ณ , ๋ณด์—ฌ์ฃผ๋Š” ๋Œ€์‹œ๋ณด๋“œ ์—ญํ• ๋งŒ ํ•œ๋‹ค. Grafana Dashboard์— ๋Œ€์‘๋œ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

Elasticsearch ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ ๋„๊ตฌ, ๋ณด์•ˆ ๊ด€๋ฆฌ, ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค ๋ชจ๋‹ˆํ„ฐ๋ง, ๋ฉ”ํŠธ๋ฆญ ๊ด€๋ฆฌ ๋“ฑ ๋ชจ๋‹ˆํ„ฐ๋ง/๊ด€๋ฆฌ์— ํ•„์š”ํ•œ ๊ฑฐ์˜ ๋ชจ๋“  ๊ฒƒ์ด ์˜ฌ์ธ์›์œผ๋กœ ๋“ค์–ด์žˆ๋Š”๊ฒŒ ํŠน์ง•์ด๋‹ค. ์‚ฌ์šฉ์„ฑ๋„ ๋‚˜์˜์ง€ ์•Š๋‹ค.

Docker compose ๊ธฐ๋ฐ˜์œผ๋กœ elasticsearch์™€ kibana๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋„์›Œ์„œ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฒ•์„ ๋‹ค๋ค„๋ณด๊ฒ ๋‹ค.
ํด๋Ÿฌ์Šคํ„ฐ ๊ตฌ์„ฑ์ด๋‚˜ ๊ณ ๊ฐ€์šฉ์„ฑ ๋“ฑ์€ ๊ณ ๋ คํ•˜์ง€ ์•Š๋Š”๋‹ค.




Elasticsearch ๊ตฌ์„ฑ

์ผ๋‹จ elasticsearch๋ฅผ ๋จผ์ € ๋„์›Œ์•ผ ํ•œ๋‹ค.

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.18.0
    container_name: elasticsearch
    environment:
      - node.name=elasticsearch
      - cluster.name=elastic-cluster
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - xpack.security.enabled=true
      - xpack.security.enrollment.enabled=true
      - xpack.security.http.ssl.enabled=false
      - xpack.security.transport.ssl.enabled=false
      - xpack.security.authc.token.enabled=true
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data
    ports:
      - "19200:9200"
      - "19300:9300"
    networks:
      - elastic
    healthcheck:
      test: ["CMD-SHELL", "curl -s http://localhost:9200 > /dev/null || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3

elasticsearch๊ฐ€ ๋œฌ ๋‹ค์Œ์— kibana๊ฐ€ ๋– ์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋–ด๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ํ—ฌ์Šค์ฒดํฌ๋ฅผ ๊ตฌ์„ฑํ–ˆ๋‹ค.

๊ทธ ๋‹ค์Œ์—๋Š” ํŒจ์Šค์›Œ๋“œ์™€ ํ† ํฐ ๋“ฑ์„ ๋ฐœ๊ธ‰ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
์ด๊ฒŒ ์ข€ ์งœ์ฆ๋‚˜๋Š”๊ฒŒ, Docker ์ปจํ…Œ์ด๋„ˆ ์ˆ˜์ค€์—์„œ๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ํŒจ์Šค์›Œ๋“œ๋‚˜ ํ† ํฐ ๋“ฑ์„ ๋ฐ”๋กœ ์ƒ์„ฑํ•˜๋„๋ก ์ง€์ •ํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค. ์˜ˆ์ „์—๋Š” ๋๋˜๊ฑฐ ๊ฐ™์€๋ฐ ์ตœ์‹  elasticsearch ๊ธฐ์ค€์œผ๋กœ๋Š” ์•ˆ๋œ๋‹ค.

FATAL  Error: [config validation of [elasticsearch].username]: value of "elastic" is forbidden. This is a superuser account that cannot write to system indices that Kibana needs to function. Use a service account token instead. Learn more: https://www.elastic.co/guide/en/elasticsearch/reference/8.0/service-accounts.html

์ด์ „ ๋ฒ„์ „์ธ 6์ด๋‚˜ 7 ๋“ฑ์—์„œ๋Š” ๊ธฐ๋ณธ ๊ณ„์ •์ด elastic:changeme์˜€๋Š”๋ฐ, username์ด elastic์ธ ๊ฒƒ์€ ๋™์ผํ•˜์ง€๋งŒ ํŒจ์Šค์›Œ๋“œ๊ฐ€ ๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •๋˜์ง€๋„ ์•Š์„ ๋ฟ๋”๋Ÿฌ, ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ง€์ •ํ•˜์ง€๋„ ๋ชปํ•œ๋‹ค. ์™œ ์ด๋”ฐ์œ„๋กœ ๋งŒ๋“ ๊ฑฐ์ง€?




Elasticsearch ํŒจ์Šค์›Œ๋“œ ์„ค์ •

์•„๋ฌดํŠผ ๊ทธ๋ž˜์„œ ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ ค๋ฉด elasticsearch-reset-password ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์จ์•ผ ํ•œ๋‹ค.

  password-setter:
    image: docker:latest 
    container_name: password-setter
    command: >
      /bin/sh -c "
        if [ -f /config/elastic_password.env ]; then
          echo 'Password has already been set. Skipping.';
          exit 0;
        fi;

        echo 'Waiting for Elasticsearch to be ready before setting password...';
        sleep 5; 

        echo 'Setting password for elastic user...';

        PASSWORD=$(docker exec elasticsearch bin/elasticsearch-reset-password -u elastic -s -b -a);
        echo \"ELASTIC_PASSWORD=$$PASSWORD\" > /config/elastic_password.env;

        echo 'Password set successfully.';
      "
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./config:/config
    depends_on:
      elasticsearch:
        condition: service_healthy

์ž๋™ํ™”๋ฅผ ์œ„ํ•ด ๊ทธ๊ฑธ ๋Œ€์‹  ์ด์ฃผ๋Š” ์ผํšŒ์„ฑ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋˜ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค.
์ด๋Ÿฌ๋ฉด ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ ๊ธฐ์ค€์œผ๋กœ config ํด๋”์— ํŒจ์Šค์›Œ๋“œ ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.




ํ‚ค๋ฐ”๋‚˜ ํ† ํฐ ์ƒ์„ฑ

์ด๊ฒƒ์ด ๋์ด ์•„๋‹ˆ๋‹ค.
ํ‚ค๋ฐ”๋‚˜๋Š” ๋˜ ์ด์ƒํ•œ ์ž์ฒด์ ์ธ ๋ณด์•ˆ ์ •์ฑ…์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋ฐ, ๊ธฐ๋ณธ๊ณ„์ •์ธ elastic ๊ณ„์ •์œผ๋กœ๋Š” ๋กœ๊ทธ์ธ์„ ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์˜ˆ์ „ ๊ตฌ๋ฒ„์ „์€ ์ด๋Ÿฐ๊ฑฐ ์—†๋˜๊ฑฐ๊ฐ™์€๋ฐ, ์–ด๋А์ƒŒ๊ฐ€ ์ด๋Ÿฐ๊ฒŒ ์ƒ๊ฒผ๋‹ค.

๊ทธ๋ž˜์„œ ํ† ํฐ์„ ๋˜ ๋ฐœ๊ธ‰ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
๋ฐฉ๊ธˆ ๋งŒ๋“  ํŒจ์Šค์›Œ๋“œ๋ฅผ ์ฝ์–ด์„œ ๋Œ€์‹  ์˜๊ณ  ๋ฐ›์•„์˜ค๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค.

  kibana-token-setter:
    image: alpine:latest
    container_name: kibana-token-setter
    command: >
      sh -c "
        source /config/elastic_password.env
        env
        ELASTIC_PASSWORD=$${ELASTIC_PASSWORD}
        echo $$ELASTIC_PASSWORD

        if [ -f /config/kibana_token.env ]; then
          echo 'Kibana service token already exists. Skipping creation.';
          exit 0;
        fi;

        if [ -f /config/kibana_token.env ]; then
          echo 'Kibana token file exists. Skipping creation.';
          exit 0;
        fi;

        echo 'Creating Kibana service token...';

        apk add --no-cache curl jq;

        echo 'Creating Kibana service token...';
        # ์„œ๋น„์Šค ํ† ํฐ ์ƒ์„ฑ API ํ˜ธ์ถœ (๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€์ˆ˜๋ฅผ ํฐ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ๊ณ  $$๋กœ ์ด์Šค์ผ€์ดํ”„)
        TOKEN_RESPONSE=$(curl -s -X POST 'http://elasticsearch:9200/_security/service/elastic/kibana/credential/token/kibana?pretty' -u "elastic:$${ELASTIC_PASSWORD}" -H 'Content-Type: application/json');
        echo \"Response: $$TOKEN_RESPONSE\";

        # ์‘๋‹ต์—์„œ ํ† ํฐ ๊ฐ’ ์ถ”์ถœ
        TOKEN=$(echo $${TOKEN_RESPONSE} | jq -r '.token.value');

        if [ '$$TOKEN' = null ] || [ -z '$$TOKEN' ] || [ "$$TOKEN" = "" ]; then
          echo 'Error: Failed to create or parse service token.';
          echo \"Response: $$TOKEN_RESPONSE\";
          exit 1;
        fi;

        # ํ˜ธ์ŠคํŠธ์™€ ๊ณต์œ ๋œ ๋ณผ๋ฅจ์— .env ํŒŒ์ผ๋กœ ์ €์žฅ
        echo \"ELASTICSEARCH_SERVICEACCOUNTTOKEN=$$TOKEN\" > /config/kibana_token.env;
        echo 'Kibana token has been saved to ./config/kibana_token.env';
      "
    volumes:
      - ./config:/config
    networks:
      - elastic
    depends_on:
      password-setter:
        condition: service_completed_successfully



ํ‚ค๋ฐ”๋‚˜ ๊ตฌ์„ฑ

๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ํ‚ค๋ฐ”๋‚˜๋ฅผ ๋ถ™์—ฌ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

  kibana:
    image: docker.elastic.co/kibana/kibana:8.18.0
    container_name: kibana
    command: >
      sh -c "
        # ํ† ํฐ ํŒŒ์ผ์ด ์กด์žฌํ•˜๋ฉด ์ฝ์–ด์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋กœ๋“œ
        if [ -f /config/kibana_token.env ]; then
          . /config/kibana_token.env;
          echo 'Service account token loaded.';
        else
          echo 'WARNING: Service account token file not found.';
        fi;
        # Kibana ์›๋ž˜ ์‹คํ–‰ ๋ช…๋ น์–ด ์‹คํ–‰
        exec /usr/local/bin/kibana-docker;
      "
    environment:
      - ELASTICSEARCH_SERVICEACCOUNTTOKEN=$${ELASTICSEARCH_SERVICEACCOUNTTOKEN}
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
      - SERVER_NAME=kibana
      - SERVER_SSL_ENABLED=false
      - SERVER_HOST=0.0.0.0
      - xpack.security.enabled=true
      - xpack.encryptedSavedObjects.encryptionKey=min-32-byte-long-strong-encryption-key
    ports:
      - "15601:5601"
    networks:
      - elastic
    depends_on:
      elasticsearch:
        condition: service_healthy
      kibana-token-setter:
        condition: service_completed_successfully
    volumes:
      - ./config:/config
    healthcheck:
      test: ["CMD-SHELL", "curl http://localhost:5601"]
      interval: 10s
      timeout: 5s
      retries: 3

๋ฐฉ๊ธˆ ๋งŒ๋“  ํ† ํฐ์„ ๊ธฐ๋ฐ˜์œผ๋กœ Elasticsearch์— ์—ฐ๊ฒฐํ•œ๋‹ค.

๊ทธ๋ž˜์„œ ๋œฌ๋‹ค๋ฉด

elastic ๊ณ„์ • ์ •๋ณด๋กœ ์ ‘๊ทผํ•ด์„œ ์จ๋ณผ ์ˆ˜ ์žˆ๋‹ค.



์ „์ฒด docker compose ๊ตฌ์„ฑ์€ ๋‹ค์Œ ๋งํฌ์— ์žˆ๋‹ค.
https://github.com/myyrakle/docker-compose/tree/master/elastic-and-kibana