Collecting and Analysis for syslog via ELK 8.17.x

Collecting and Analysis for syslog via ELK 8.17.x

개요

  1. 시스템 로그와 App 로그를 rsyslog로 통합하여 수집(logstash) → 저장(elasticsearch).

  2. Elasticsearch의 text analysis 기능을 사용하여 형태소 기반 토큰 분리 즉, 역색인화(Inverted Indexing).

  3. 역색인을 활용한 log message 분석 기법.

등 [비정형] 로그 text를 색인화하여 저장하고, 이를 문제 발견 및 해결을 위해 root cause을 분석할 수 있도록 활용하는 방안을 도출하여 보는 것을 목적으로 한다.

Set up ELK 8.17.x as a container

[Ref.] elasticsearch/docs/reference/setup/install/doc

Elasticsearch 8이상부터는 보안 설정이 기본적으로 활성화되어, 각종 인증서, 암호화 키 등을 발급하고, 알맞게 셋팅되어야 한다.
그런데, 공식 가이드를 따라하다보면 군데군데 구멍이 뚫려있어 각 config 옵션이 영향을 미치는 범위 등을 이해하지 않고는, 처음 세팅할때 어려움을 겪었다. (물론 Docker compose로 편하게 실행할때의 얘기이다.)

해서, 위 [Ref.] github의 내용을 기반으로 아래와 같이 single mode와 multi mode 두 개의 과정을 편하게 세팅하여 사용해 볼 수 있도록 하였다.

Multi-node는 단순한 형태로만 제시하며, 개요에서 밝힌 과정은 편의상 Single node에서만 진행할 것 이다.

Basic single mode

./.env

# Password for the 'elastic' user (at least 6 characters) ELASTIC_PASSWORD=changeme # Password for the 'kibana_system' user (at least 6 characters) KIBANA_PASSWORD=changeme # Version of Elastic products STACK_VERSION=8.17.3 # Set the cluster name CLUSTER_NAME=docker-cluster # Set to 'basic' or 'trial' to automatically start the 30-day trial LICENSE=basic #LICENSE=trial # Port to expose Elasticsearch HTTP API to the host ES_PORT=9200 #ES_PORT=127.0.0.1:9200 # Port to expose Kibana to the host KIBANA_PORT=5601 #KIBANA_PORT=80 LOGSTASH_PORT=5045 LOGSTASH_SYSLOG_PORT=5140 LOGSTASH_MONITORING_PORT=9601 # Increase or decrease based on the available host memory (in bytes) # MEM_LIMIT=1073741824 # 1GiB MEM_LIMIT=2147483648 # 2GiB # MEM_LIMIT=6442450944 # 6GiB # Project namespace (defaults to the current folder name if not set) COMPOSE_PROJECT_NAME=elk-single

./elasticsearch/Dockerfile

ARG ELK_VERSION # https://www.docker.elastic.co/ FROM docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION} # Add your elasticsearch plugins setup here RUN elasticsearch-plugin install analysis-nori

./kibana/Dockerfile

ARG ELK_VERSION # https://www.docker.elastic.co/ FROM docker.elastic.co/kibana/kibana:${ELK_VERSION} # Add your kibana plugins setup here # Example: RUN kibana-plugin install <name|url>

./logstash/Dockerfile

ARG ELK_VERSION # https://www.docker.elastic.co/ FROM docker.elastic.co/logstash/logstash:${ELK_VERSION} # Add your logstash plugins setup here # Example: RUN logstash-plugin install logstash-filter-json # RUN logstash-plugin install --version 1.3.1 logstash-input-snmp # RUN logstash-plugin install logstash-filter-mutate RUN logstash-plugin install logstash-filter-ruby RUN logstash-plugin install logstash-filter-grok RUN logstash-plugin install logstash-output-elasticsearch # RUN logstash-plugin install logstash-integration-rabbitmq # RUN logstash-plugin install logstash-output-influxdb

./docker-compose.yml

setup 서비스

  • ELASTIC_PASSWORDKIBANA_PASSWORD 환경 변수가 설정되어 있는지를 확인하며, 누락 시 오류를 출력하고 종료한다.

  • config/certs/ca.zipcerts.zip 파일이 존재하지 않을 경우, elasticsearch-certutil 명령어를 통해 CA 및 인증서를 생성하고 압축을 해제한다.

  • 생성된 인증서의 소유자를 root:root로 설정하며, 디렉터리 권한은 755, 파일 권한은 644로 지정한다.

  • Elasticsearch가 기동되고 "missing authentication credentials" 메시지를 반환할 때까지 대기하며, 이후 Kibana 시스템 계정의 비밀번호를 설정한다.

  • 헬스체크는 config/certs/es/es.crt 파일의 존재 여부를 기준으로 수행한다.

logstash 서비스

  • setup 서비스가 헬시 상태가 된 이후에 기동되도록 설정되어 있다.

  • 인증서 디렉터리와 Logstash 파이프라인 디렉터리를 컨테이너 내부에 마운트한다.

  • Beats(5044), 모니터링 API(9600), Syslog(5140) 포트를 외부에 노출한다.

    • 사실 여기서는 Beats를 사용할 계획이 없기때문에 필요는 없으나, 일단 노출하기로 한다.

  • X-Pack 모니터링 기능과 Elasticsearch 연동을 위해 인증 정보 및 CA 경로를 환경 변수로 설정한다.

  • 컨테이너는 고정 IP 192.168.16.3을 사용하며, 9600 포트에 대한 HTTP 200 응답을 통해 헬스체크를 수행한다.

es 서비스

  • setuplogstash 서비스가 모두 헬시 상태일 때 기동되도록 구성되어 있다.

  • 인증서 및 데이터 디렉터리를 컨테이너에 마운트하며, Elasticsearch 포트(9200)를 외부에 노출한다.

  • 보안 기능(X-Pack Security) 및 HTTP/Transport 계층의 SSL 설정을 활성화하며, 인증서 및 키 파일 경로를 환경 변수로 지정한다.

  • 클러스터는 단일 노드로 구성되며, 라이선스 유형 및 메모리 자동 설정 기능을 활성화한다.

  • 고정 IP는 192.168.16.4이며, syslog 드라이버를 통해 tcp://10.20.2.216:514로 로그를 전송한다.

  • 헬스체크는 HTTPS 요청 결과 "missing authentication credentials" 문자열의 존재 여부를 기준으로 판단한다.

kibana 서비스

  • es 서비스가 헬시 상태가 된 이후 기동되도록 설정되어 있다.

  • 인증서 및 Kibana 데이터 디렉터리를 마운트하며, Kibana 포트(5601)를 외부에 개방한다.

  • Elasticsearch와의 연동을 위해 호스트, 사용자 이름, 비밀번호, CA 경로를 환경 변수로 지정한다.

  • 고정 IP는 192.168.16.5이며, 로그는 syslog 드라이버를 통해 tcp://10.20.2.216:514로 전송된다.

  • 헬스체크는 5601 포트로의 HTTP 요청에 대해 302 리다이렉트 응답 여부를 확인하여 수행된다.

공유 볼륨 및 네트워크

  • certs, esdata, kibanadata, logstash_pipeline 볼륨은 로컬 드라이버를 사용하여 정의되어 있다.

  • 네트워크는 브리지 모드를 사용하며, 서브넷은 192.168.16.0/24, 게이트웨이는 192.168.16.1로 지정되어 있어 각 서비스에 고정 IP를 할당할 수 있도록 구성되어 있다.
    참고: IP 대역을 반드시 지정하고 각 서비스에 고정 할당할 필요는 없으나, 여기서는 만일을 위해 지정하여 사용한다.

services: setup: image: elk/elasticsearch:${STACK_VERSION} build: context: elasticsearch/ args: ELK_VERSION: ${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs user: "0" command: > bash -c ' if [ x${ELASTIC_PASSWORD} == x ]; then echo "Set the ELASTIC_PASSWORD environment variable in the .env file"; exit 1; elif [ x${KIBANA_PASSWORD} == x ]; then echo "Set the KIBANA_PASSWORD environment variable in the .env file"; exit 1; fi; if [ ! -f config/certs/ca.zip ]; then echo "Creating CA"; bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip; unzip config/certs/ca.zip -d config/certs; fi; if [ ! -f config/certs/certs.zip ]; then echo "Creating certs"; echo -ne \ "instances:\n"\ " - name: es\n"\ " dns:\n"\ " - es\n"\ " - localhost\n"\ " ip:\n"\ " - 127.0.0.1\n"\ > config/certs/instances.yml; bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key; unzip config/certs/certs.zip -d config/certs; fi; echo "Setting file permissions" chown -R root:root config/certs; find . -type d -exec chmod 755 \{\} \;; find . -type f -exec chmod 644 \{\} \;; echo "Waiting for Elasticsearch availability"; until curl -s --cacert config/certs/ca/ca.crt https://es:9200 | grep -q "missing authentication credentials"; do sleep 30; done; echo "Setting kibana_system password"; until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done; echo "All done!"; ' healthcheck: test: ["CMD-SHELL", "[ -f config/certs/es/es.crt ]"] interval: 1s timeout: 5s retries: 120 logstash: depends_on: setup: condition: service_healthy image: elk/logstash:${STACK_VERSION} build: context: logstash/ args: ELK_VERSION: ${STACK_VERSION} volumes: - certs:/usr/share/logstash/config/certs - logstash_pipeline:/usr/share/logstash/pipeline ports: - ${LOGSTASH_PORT}:5044 # Beats, Logstash Forwarder, etc. - ${LOGSTASH_MONITORING_PORT}:9600 # Logstash Monitoring API - ${LOGSTASH_SYSLOG_PORT}:5140 environment: xpack.monitoring.elasticsearch.hosts: "https://es:9200" xpack.monitoring.elasticsearch.username: elastic xpack.monitoring.elasticsearch.password: changeme xpack.monitoring.elasticsearch.ssl.certificate_authority: config/certs/ca/ca.crt ELASTICSEARCH_HOSTS: "https://es:9200" ELASTICSEARCH_USERNAME: "elastic" ELASTICSEARCH_PASSWORD: ${ELASTIC_PASSWORD} ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES: config/certs/ca/ca.crt mem_limit: ${MEM_LIMIT} networks: default: ipv4_address: 192.168.16.3 healthcheck: test: [ "CMD-SHELL", "curl -s -I http://localhost:9600 | grep -q 'HTTP/1.1 200 OK'", ] interval: 10s timeout: 10s retries: 120 es: depends_on: logstash: condition: service_healthy setup: condition: service_healthy image: elk/elasticsearch:${STACK_VERSION} build: context: elasticsearch/ args: ELK_VERSION: ${STACK_VERSION} volumes: - certs:/usr/share/elasticsearch/config/certs - esdata:/usr/share/elasticsearch/data ports: - ${ES_PORT}:9200 environment: # logger.org.elasticsearch.discovery: DEBUG node.name: es discovery.type: single-node cluster.name: ${CLUSTER_NAME} ELASTIC_PASSWORD: ${ELASTIC_PASSWORD} bootstrap.memory_lock: "true" xpack.security.enabled: "true" xpack.security.http.ssl.enabled: "true" xpack.security.http.ssl.key: certs/es/es.key xpack.security.http.ssl.certificate: certs/es/es.crt xpack.security.http.ssl.certificate_authorities: certs/ca/ca.crt xpack.security.transport.ssl.enabled: "true" xpack.security.transport.ssl.key: certs/es/es.key xpack.security.transport.ssl.certificate: certs/es/es.crt xpack.security.transport.ssl.certificate_authorities: certs/ca/ca.crt xpack.security.transport.ssl.verification_mode: certificate xpack.license.self_generated.type: ${LICENSE} xpack.ml.use_auto_machine_memory_percent: "true" mem_limit: ${MEM_LIMIT} networks: default: ipv4_address: 192.168.16.4 ulimits: memlock: soft: -1 hard: -1 logging: driver: syslog options: syslog-address: "tcp://10.20.2.216:514" tag: "ai01 elasticsearch" syslog-facility: "local0" healthcheck: test: [ "CMD-SHELL", "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'", ] interval: 10s timeout: 10s retries: 120 kibana: depends_on: es: condition: service_healthy image: elk/kibana:${STACK_VERSION} build: context: kibana/ args: ELK_VERSION: ${STACK_VERSION} volumes: - certs:/usr/share/kibana/config/certs - kibanadata:/usr/share/kibana/data ports: - ${KIBANA_PORT}:5601 environment: # LOGGING_ROOT_LEVEL: debug SERVERNAME: kibana ELASTICSEARCH_HOSTS: https://es:9200 ELASTICSEARCH_USERNAME: kibana_system ELASTICSEARCH_PASSWORD: ${KIBANA_PASSWORD} ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES: config/certs/ca/ca.crt mem_limit: ${MEM_LIMIT} networks: default: ipv4_address: 192.168.16.5 logging: driver: syslog options: syslog-address: "tcp://10.20.2.216:514" tag: "ai01 kibana" syslog-facility: "local1" healthcheck: test: [ "CMD-SHELL", "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'", ] interval: 10s timeout: 10s retries: 120 volumes: certs: driver: local esdata: driver: local kibanadata: driver: local logstash_pipeline: driver: local networks: default: driver: bridge ipam: config: - subnet: 192.168.16.0/24 gateway: 192.168.16.1

Basic multi-nodes mode

https://seversky.atlassian.net/wiki/x/BgCJlw

Set vm.max_map_count to at least 262144

The vm.max_map_count kernel setting must be set to at least 262144 for production use.

To view the current value for the vm.max_map_count setting, run:

$ sudo sysctl -a|grep vm.max_map_count vm.max_map_count = 65536

To apply the setting on a live system, run:

$ sudo vim /etc/sysctl.d/00-vm.conf vm.max_map_count=262144 $ sudo sysctl --system

ES index mapping

아래는 기본 제공되는 영어 형태소 분석기를 사용한 syslog 인덱스 매핑이다.
Kibana의 “Dev tools”에서 적용하도록 한다.

PUT _index_template/syslog-tpl { "index_patterns": [ "syslog-*" ], "template": { "mappings": { "properties": { "event": { "properties": { "original": { "type": "text", "analyzer": "english" } } }, "host": { "properties": { "hostname": { "type": "keyword" }, "ip": { "type": "ip" } } }, "log": { "properties": { "syslog": { "properties": { "facility": { "properties": { "code": { "type": "long" } } }, "priority": { "type": "long" }, "severity": { "properties": { "code": { "type": "long" } } } } } } }, "message": { "type": "text", "analyzer": "english", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "process": { "properties": { "name": { "type": "keyword" }, "pid": { "type": "long" } } }, "service": { "properties": { "type": { "type": "keyword" } } }, "type": { "type": "keyword" } } } } }

Setting Syslog data collect

logstash configuration for syslog:

Docker system 로그 중에는 아래와 같은 로그가 자주 발생한다.
"run-docker-runtime\x2drunc-moby-77ab16d03d5107e7be0b1050a97781f7f1d57311a1e6e74be9768b7f8d6e8256-runc.apZ7Tn.mount: Deactivated successfully."

여기서 \x2d‘-'의 ASCII 값인데 '-’으로 변환되지 않고 전달된다.
이는 형태소 분석기에서 오류가 발생하기 때문에, 아래와 같이 logstash의 filter로 문자로 변환하도록 하는 내용이 포함되어 있음에 주의한다.

/var/lib/docker/volumes/elk-single_logstash_pipeline/_data$ sudo cat logstash.rb input { syslog { port => 5140 type => "syslog" use_labels => false syslog_field => "message" } } filter { ruby { code => ' if event.get("message") decoded = event.get("message").gsub(/\\x([0-9A-Fa-f]{2})/) { [$1].pack("H*") } event.set("message", decoded) end ' } } output { # stdout for debugging stdout { codec => rubydebug } elasticsearch { hosts => ["${ELASTICSEARCH_HOSTS}"] user => "${ELASTICSEARCH_USERNAME}" password => "${ELASTICSEARCH_PASSWORD}" cacert => "${ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES}" ssl_certificate_verification => false index => "syslog-%{+YYYY.MM.dd}" } }

And then restart logstash container.

rsyslog configuration:

UTC로 통일한 후, logstash로 전송

/etc/rsyslog.d$ sudo cat 51-logstash.conf template(name="UtcSyslogFormat" type="string" string="<%PRI%>%timereported:::date-rfc3339% %hostname% %syslogtag%%msg%\n") *.* @@localhost:5140;UtcSyslogFormat $ sudo systemctl restart rsyslog

You can see the logs via Kibana dashboard

See via Kibana Discover Menu

아래와 같이 출력되려면 몇 가지 선행 작업이 필요하나, 이는 kibana 매뉴얼이나 다른 가이드를 참고하길 바란다.

image-20250425-060223.png

다음은 Log Message에 대해 어떤 방식으로 tokenize 되었는지, 이를 활용하여 어떤 작업이 가능한지에 대해 살펴보겠다.

https://seversky.atlassian.net/wiki/x/JYCPlw