본문으로 건너뛰기

Hadoop Eco Dataflow 유형을 이용한 실시간 웹서버 로그 분석 및 모니터링

카카오클라우드 Hadoop Eco 서비스를 이용하면 웹서버 로그를 실시간으로 분석하고 모니터링하는 환경을 쉽게 구축할 수 있습니다. 이 문서에서는 Hadoop Eco Dataflow 유형을 이용한 핸즈온 튜토리얼을 제공합니다.


안내
  • 예상 소요 시간: 60분
  • 사용자 환경
    • 권장 운영 체제: Mac OS, Ubuntu
    • Region: kr-central-2

시나리오 소개

이 튜토리얼은 Hadoop Eco Dataflow 유형을 사용하여 실시간 웹서버 로그 분석 및 모니터링을 구현하는 방법을 안내합니다. 데이터 수집, 전처리, 분석, 시각화의 단계를 실습하면서, 실시간 데이터 파이프라인 구축의 기본 원리를 이해하고, Hadoop EcoDataflow를 활용한 실시간 분석 및 모니터링 경험을 쌓을 수 있습니다.

이 시나리오의 주요 내용은 다음과 같습니다.

  • Filebeat, Kafka를 이용한 로그 데이터의 전처리 및 분석
  • Druid, Superset을 이용한 실시간 데이터 시각화를 통한 모니터링 대시보드 구축

Getting started

실시간 웹서버 로그 분석을 위한 Hadoop Eco의 Dataflow 설정 및 모니터링 환경 구축의 실습 단계는 다음과 같습니다.

Step 1. Hadoop Eco 서비스 생성

  1. 카카오클라우드 콘솔에서 Hadoop Eco 메뉴를 선택합니다.

  2. [클러스터 생성] 버튼을 클릭한 뒤 다음과 같이 Hadoop Eco 클러스터를 생성합니다.

    항목설정값
    클러스터 이름hands-on-dataflow
    클러스터 버전Hadoop Eco 2.0.1
    클러스터 유형Dataflow
    클러스터 가용성표준
    관리자 ID${ADMIN_ID}
    관리자 비밀번호  ${ADMIN_PASSWORD}
    주의

    관리자 ID와 비밀번호는 데이터 탐색 및 시각화 플랫폼인 Superset 접근 시 필요하므로 안전하게 저장해야 합니다.

  3. 마스터 노드와 워커 노드 인스턴스를 설정합니다.

    1. 키 페어 및 네트워크 구성(VPC, 서브넷)은 사용자가 ssh 접속을 진행할 수 있는 환경에 맞게 설정합니다.

      구분마스터 노드워커 노드
      인스턴스 개수1개2개
      인스턴스 유형  m2a.xlarge   m2a.xlarge
      볼륨 크기50GB100GB
    2. 다음으로 새로운 보안 그룹 생성을 선택합니다.

  4. 이후 단계들은 아래와 같이 설정합니다.

    • 작업 스케줄링 설정

      항목설정값
      작업 스케줄링 설정  선택 안함
    • 클러스터 상세 설정

      항목설정값
      HDFS 블록 크기128
      HDFS 복제 개수2
      클러스터 구성 설정  설정 안함
    • 서비스 연동 설정

      항목설정값
      모니터링 에이전트 설치   설치 안함
      서비스 연동연동하지 않음
  5. 입력한 정보를 확인한 뒤 [만들기] 버튼을 클릭하여 클러스터를 생성합니다.

Step 2. 보안 그룹 설정

Hadoop Eco 클러스터를 생성 시 새로 만들어지는 보안 그룹은 보안을 위해 인바운드 규칙이 설정되어있지 않습니다. 해당 클러스터에 접근하기 위하여 보안 그룹의 인바운드 규칙을 설정합니다.

  1. Hadoop Eco 클러스터 목록 > 생성된 클러스터 > 클러스터 정보 > 보안 그룹 링크를 클릭합니다. [인바운드 규칙 관리] 버튼을 클릭하여, 아래와 같이 인바운드 규칙을 설정합니다.

    온라인 서비스나 웹사이트를 통해 사용자 퍼블릭 IP 주소를 확인할 수 있습니다. 예를 들어, WhatIsMyIP.com을 방문하여 사용자 퍼블릭 IP 주소를 확인할 수 있습니다. 키 파일 권한 문제로 ‘bad permissions’ 오류가 발생할 경우, sudo 명령어를 추가하여 문제를 해결할 수 있습니다.

    프로토콜패킷 출발지포트 번호정책 설명
    TCP{사용자 퍼블릭 IP 주소}/3222ssh 연결
    TCP  {사용자 퍼블릭 IP 주소}/3280NGINX
    TCP{사용자 퍼블릭 IP 주소}/324000Superset
    TCP{사용자 퍼블릭 IP 주소}/323008      Druid

Step 3. 웹서버 및 로그 파이프라인 구성

생성한 Hadoop Eco 클러스터 마스터 노드에 웹서버인 Nginx와 Filebeat를 이용하여 로그 파이프라인을 구성합니다. Filebeat은 로그 파일을 주기적으로 스캔하여 파일로 적재된 로그를 Kafka로 전달합니다.

  1. 생성된 Hadoop 클러스터의 마스터 노드에 ssh를 사용하여 접속합니다.

    마스터 노드에 접속
    chmod 400 ${PRIVATE_KEY_FILE}
    ssh -i ${PRIVATE_KEY_FILE} ubuntu@${HADOOP_MST_NODE_ENDPOINT}
    주의

    생성된 마스터 노드는 프라이빗 IP로 구성되어 있어 퍼블릭 네트워크 환경에서 접근할 수 없습니다. 따라서 퍼블릭 IP를 연결하거나 Bastion 호스트를 사용하는 등의 방식으로 접속할 수 있습니다.

  2. 웹서버 Nginx와 로그를 JSON 형식으로 출력하기 위한 JQ를 설치합니다.

    sudo apt update -y
    sudo apt install nginx -y
    sudo apt install jq -y
    정보

    설치 도중 보라색 배경화면과 함께 Pending kernel upgrade, Daemons using outdated libraries 창이 나타나면 당황하지 말고 Enter 키를 눌러주세요!

  3. API 요청 클라이언트 지역 정보에 대한 로그를 수집하기 위해 GeoIP 설치 및 설정을 수행합니다.

    sudo apt install libnginx-mod-http-geoip geoip-database gzip
    cd /usr/share/GeoIP
    sudo wget https://centminmod.com/centminmodparts/geoip-legacy/GeoLiteCity.gz
    sudo gunzip GeoLiteCity.gz
  4. Nginx 액세스 로그 포맷을 설정합니다.

    nginx 설정 수정
    cat << 'EOF' | sudo tee /etc/nginx/nginx.conf
    user www-data;
    worker_processes auto;
    pid /run/nginx.pid;
    include /etc/nginx/modules-enabled/*.conf;

    events {
    worker_connections 768;
    }

    http {

    # Basic Settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # SSL Settings
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    # Logging Settings
    geoip_country /usr/share/GeoIP/GeoIP.dat;

    log_format nginxlog_json escape=json
    '{'
    '"remote_addr":"$remote_addr",'
    '"remote_user":"$remote_user",'
    '"http_user_agent":"$http_user_agent",'
    '"host":"$host",'
    '"hostname":"$hostname",'
    '"request":"$request",'
    '"request_method":"$request_method",'
    '"request_uri":"$request_uri",'
    '"status":"$status",'
    '"time_iso8601":"$time_iso8601",'
    '"time_local":"$time_local",'
    '"uri":"$uri",'
    '"http_referer":"$http_referer",'
    '"body_bytes_sent":"$body_bytes_sent",'
    '"geoip_country_code": "$geoip_country_code",'
    '"geoip_latitude": "$geoip_latitude",'
    '"geoip_longitude": "$geoip_longitude"'
    '}';

    access_log /var/log/nginx/access.log nginxlog_json;
    error_log /var/log/nginx/error.log;

    # Virtual Host Configs
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
    }
    EOF
    Nginx 재시작 및 상태 확인
    sudo systemctl restart nginx
    sudo systemctl status nginx
  5. 웹페이지에 접속하고, 접속 로그를 확인합니다.

    • 웹페이지에 접속합니다. 웹서버가 정상이라면 아래와 같은 화면이 출력됩니다.

      http://{MASTER_NODE_PUBLIC_IP)

      Nginx 접속 확인 Nginx 접속 확인

    • 마스터 노드 인스턴스에서 정상적으로 로그가 기록되는지 확인합니다.

      tail /var/log/nginx/access.log | jq
    • 접속 로그 예시

      {
      "remote_addr": "220.12x.8x.xx",
      "remote_user": "",
      "http_user_agent": "",
      "host": "10.xx.xx.1x",
      "hostname": "host-172-30-4-5",
      "request": "GET http://210.109.8.104:80/php/scripts/setup.php HTTP/1.0",
      "request_method": "GET",
      "request_uri": "/php/scripts/setup.php",
      "status": "404",
      "time_iso8601": "2023-11-15T06:24:49+00:00",
      "time_local": "15/Nov/2023:06:24:49 +0000",
      "uri": "/php/scripts/setup.php",
      "http_referer": "",
      "body_bytes_sent": "162",
      "geoip_country_code": "KR",
      "geoip_latitude": "37.3925",
      "geoip_longitude": "126.9269"
      }
      주의

      Nginx의 기본 timezone은 UTC입니다. 따라서 time_iso8601, time_local 필드는 UTC로 표시되며, 이는 KST(+9:00)와 다를 수 있습니다.

  6. Filebeat의 설치 및 설정을 수행합니다.

    Filebeat 설치
    cd ~
    sudo curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.9.1-linux-x86_64.tar.gz
    tar xzvf filebeat-8.9.1-linux-x86_64.tar.gz
    ln -s filebeat-8.9.1-linux-x86_64 filebeat
    Filebeat 설정 (Hadoop Eco 클러스터 워커 노드의 Kafka와 연동)
    cat << EOF | sudo tee ~/filebeat/filebeat.yml

    ########################### Filebeat Configuration ##############################
    filebeat.config.modules:
    # Glob pattern for configuration loading
    path: \${path.config}/modules.d/*.yml
    # Set to true to enable config reloading
    reload.enabled: false
    # ================================== Outputs ===================================
    output.kafka:
    hosts: ["${WORKER-NODE1-HOSTNAME}:9092","${WORKER-NODE2-HOSTNAME}:9092"]
    topic: 'nginx-from-filebeat'
    partition.round_robin:
    reachable_only: false
    required_acks: 1
    compression: gzip
    max_message_bytes: 1000000
    # ================================= Processors =================================
    processors:
    - add_host_metadata:
    when.not.contains.tags: forwarded
    - decode_json_fields:
    fields: ["message"]
    process_array: true
    max_depth: 2
    target: log
    overwrite_keys: true
    add_error_key: false

    EOF
주의

${path.config}는 수정하면 안됩니다. output.kafka -> hosts에 IP 주소 대신 Hostname을 입력해야 합니다.

  • 예시: host-172-16-0-0:9092
Filebeat 설정 (Filebeat Nginx 모듈 설정)
cat << EOF | sudo tee ~/filebeat/modules.d/nginx.yml
- module: nginx
access:
enabled: true

error:
enabled: false

ingress_controller:
enabled: false
EOF
Filebeat 설정 (Filebeat Service 생성)
cat << 'EOF' | sudo tee /etc/systemd/system/filebeat.service
[Unit]
Description=Filebeat sends log files to Kafka.
Documentation=https://www.elastic.co/products/beats/filebeat
Wants=network-online.target
After=network-online.target

[Service]
User=ubuntu
Group=ubuntu
ExecStart=/home/ubuntu/filebeat/filebeat -c /home/ubuntu/filebeat/filebeat.yml -path.data /home/ubuntu/filebeat/data
Restart=always

[Install]
WantedBy=multi-user.target

EOF
Filebeat 실행
sudo systemctl daemon-reload
sudo systemctl enable filebeat
sudo systemctl start filebeat
sudo systemctl status filebeat

Step 4. Druid 접속 및 설정

  1. Hadoop Eco 클러스터 > 클러스터 정보 > [Druid URL]을 통해 Druid에 접속합니다.

    http://{MASTER_NODE_PUBLIC_IP):3008
  2. 메인 화면 상단의 Load Data > Streaming 버튼을 클릭합니다. 우측 상단의 [Edit Spec] 버튼을 클릭합니다.

    Druid 설정 버튼 Druid 설정 버튼

  3. 아래 JSONbootstarp.server에 Hadoop Eco 워커 노드의 호스트 네임을 수정하고, 해당 내용을 붙여 넣은 뒤 [Submit] 버튼을 클릭합니다.

    주의

    bootstraps.servers에 워커 노드의 IP 주소 대신 호스트 네임을 입력해야 합니다.
    호스트 네임은 VM 인스턴스 > 세부 정보에서 확인 가능합니다. 예시) host-172-16-0-2

    JSON
    {
    "type": "kafka",
    "spec": {
    "ioConfig": {
    "type": "kafka",
    "consumerProperties": {
    "bootstrap.servers": "{WORKER-NODE1-HOSTNAME}:9092,{WORKDER-NODE2-HOSTNAME}:9092"
    },
    "topic": "nginx-from-filebeat",
    "inputFormat": {
    "type": "json",
    "flattenSpec": {
    "fields": [
    {
    "name": "agent.ephemeral_id",
    "type": "path",
    "expr": "$.agent.ephemeral_id"
    },
    {
    "name": "agent.id",
    "type": "path",
    "expr": "$.agent.id"
    },
    {
    "name": "agent.name",
    "type": "path",
    "expr": "$.agent.name"
    },
    {
    "name": "agent.type",
    "type": "path",
    "expr": "$.agent.type"
    },
    {
    "name": "agent.version",
    "type": "path",
    "expr": "$.agent.version"
    },
    {
    "name": "ecs.version",
    "type": "path",
    "expr": "$.ecs.version"
    },
    {
    "name": "event.dataset",
    "type": "path",
    "expr": "$.event.dataset"
    },
    {
    "name": "event.module",
    "type": "path",
    "expr": "$.event.module"
    },
    {
    "name": "event.timezone",
    "type": "path",
    "expr": "$.event.timezone"
    },
    {
    "name": "fileset.name",
    "type": "path",
    "expr": "$.fileset.name"
    },
    {
    "name": "host.architecture",
    "type": "path",
    "expr": "$.host.architecture"
    },
    {
    "name": "host.containerized",
    "type": "path",
    "expr": "$.host.containerized"
    },
    {
    "name": "host.hostname",
    "type": "path",
    "expr": "$.host.hostname"
    },
    {
    "name": "host.id",
    "type": "path",
    "expr": "$.host.id"
    },
    {
    "name": "host.ip",
    "type": "path",
    "expr": "$.host.ip"
    },
    {
    "name": "host.mac",
    "type": "path",
    "expr": "$.host.mac"
    },
    {
    "name": "host.name",
    "type": "path",
    "expr": "$.host.name"
    },
    {
    "name": "host.os.codename",
    "type": "path",
    "expr": "$.host.os.codename"
    },
    {
    "name": "host.os.family",
    "type": "path",
    "expr": "$.host.os.family"
    },
    {
    "name": "host.os.kernel",
    "type": "path",
    "expr": "$.host.os.kernel"
    },
    {
    "name": "host.os.name",
    "type": "path",
    "expr": "$.host.os.name"
    },
    {
    "name": "host.os.platform",
    "type": "path",
    "expr": "$.host.os.platform"
    },
    {
    "name": "host.os.type",
    "type": "path",
    "expr": "$.host.os.type"
    },
    {
    "name": "host.os.version",
    "type": "path",
    "expr": "$.host.os.version"
    },
    {
    "name": "input.type",
    "type": "path",
    "expr": "$.input.type"
    },
    {
    "name": "log.body_bytes_sent",
    "type": "path",
    "expr": "$.log.body_bytes_sent"
    },
    {
    "name": "log.file.path",
    "type": "path",
    "expr": "$.log.file.path"
    },
    {
    "name": "log.geoip_country_code",
    "type": "path",
    "expr": "$.log.geoip_country_code"
    },
    {
    "name": "log.geoip_latitude",
    "type": "path",
    "expr": "$.log.geoip_latitude"
    },
    {
    "name": "log.geoip_longitude",
    "type": "path",
    "expr": "$.log.geoip_longitude"
    },
    {
    "name": "log.host",
    "type": "path",
    "expr": "$.log.host"
    },
    {
    "name": "log.hostname",
    "type": "path",
    "expr": "$.log.hostname"
    },
    {
    "name": "log.http_referer",
    "type": "path",
    "expr": "$.log.http_referer"
    },
    {
    "name": "log.http_user_agent",
    "type": "path",
    "expr": "$.log.http_user_agent"
    },
    {
    "name": "log.offset",
    "type": "path",
    "expr": "$.log.offset"
    },
    {
    "name": "log.remote_addr",
    "type": "path",
    "expr": "$.log.remote_addr"
    },
    {
    "name": "log.remote_user",
    "type": "path",
    "expr": "$.log.remote_user"
    },
    {
    "name": "log.request",
    "type": "path",
    "expr": "$.log.request"
    },
    {
    "name": "log.request_method",
    "type": "path",
    "expr": "$.log.request_method"
    },
    {
    "name": "log.request_uri",
    "type": "path",
    "expr": "$.log.request_uri"
    },
    {
    "name": "log.status",
    "type": "path",
    "expr": "$.log.status"
    },
    {
    "name": "log.time_iso8601",
    "type": "path",
    "expr": "$.log.time_iso8601"
    },
    {
    "name": "log.time_local",
    "type": "path",
    "expr": "$.log.time_local"
    },
    {
    "name": "log.uri",
    "type": "path",
    "expr": "$.log.uri"
    },
    {
    "name": "service.type",
    "type": "path",
    "expr": "$.service.type"
    },
    {
    "name": "$.@metadata.beat",
    "type": "path",
    "expr": "$['@metadata'].beat"
    },
    {
    "name": "$.@metadata.pipeline",
    "type": "path",
    "expr": "$['@metadata'].pipeline"
    },
    {
    "name": "$.@metadata.type",
    "type": "path",
    "expr": "$['@metadata'].type"
    },
    {
    "name": "$.@metadata.version",
    "type": "path",
    "expr": "$['@metadata'].version"
    }
    ]
    }
    },
    "useEarliestOffset": true
    },
    "tuningConfig": {
    "type": "kafka"
    },
    "dataSchema": {
    "dataSource": "nginx-from-filebeat",
    "timestampSpec": {
    "column": "@timestamp",
    "format": "iso"
    },
    "dimensionsSpec": {
    "dimensions": [
    "host.name",
    {
    "name": "log.body_bytes_sent",
    "type": "float"
    },
    "log.file.path",
    "log.geoip_country_code",
    "log.geoip_latitude",
    "log.geoip_longitude",
    "log.host",
    "log.hostname",
    "log.http_referer",
    "log.http_user_agent",
    "log.offset",
    "log.remote_addr",
    "log.remote_user",
    "log.request",
    "log.request_method",
    "log.request_uri",
    {
    "name": "log.status",
    "type": "long"
    },
    "log.time_iso8601",
    "log.time_local",
    "log.uri"
    ]
    },
    "granularitySpec": {
    "queryGranularity": "none",
    "rollup": false
    },
    "transformSpec": {
    "filter": {
    "type": "not",
    "field": {
    "type": "selector",
    "dimension": "log.status",
    "value": null
    }
    }
    }
    }
    }
    }
  4. Ingestion 탭에서 Kafka와 연동 상태를 확인할 수 있습니다. 아래 사진과 같이 Status가 RUNNING이면 정상적으로 연결된 상태입니다.

    Druid 상태 확인 Druid 상태 확인

Step 5. Superset 접속 및 설정

Superset을 통해 실시간으로 데이터를 모니터링할 수 있습니다.

  1. Hadoop Eco 클러스터 > 클러스터 정보 > [Superset URL]을 통해 Superset에 접속합니다. 클러스터 생성 시 입력했던 관리자 ID, 비밀번호를 이용하여 로그인합니다.

    http://{MASTER_NODE_PUBLIC_IP):4000
  2. 상단 메뉴의 [Datasets] 버튼을 클릭합니다. 이후 Druid에서 데이터세트를 가져오기 위해 우측 상단의 [+ DATASET] 버튼을 클릭합니다.

  3. 아래와 같이 데이터베이스와 스키마를 설정합니다. 이후 [CREATE DATASET AND CREATE CHART] 버튼을 클릭합니다.

    항목설정값
    DATABASEdruid
    SCHEMAdruid
    TABLEnginx-from-filebeat
  4. 원하는 차트들을 선택하고 [CREATE NEW CHART] 버튼을 클릭합니다.

  5. 확인하고 싶은 데이터와 설정값들을 입력하고 [CREATE CHART] 버튼을 클릭하여 차트를 생성하고, 우측 상단의 [SAVE] 버튼을 클릭하여 차트를 저장합니다.

  6. 생성한 차트를 대시보드에 추가하여 아래와 같이 모니터링할 수 있습니다.

    대시보드 확인 대시보드