DevOps-15일차 로그 파이프라인 실습

문한성·2023년 3월 30일
0

부트캠프

목록 보기
27/123
post-thumbnail

1. nginx 로그 분석

  • sample.log
    // sample.log
    10.0.210.17 - - [28/Nov/2022:11:33:28 +0900] "GET /hello HTTP/1.1" 200 615 "-" "curl/7.84.0" "-"
    • 여기엔 어떤 정보들이 담겨있나요? 웹 서버 액세스 로그의 로그 항목으로, 서버에 대한 요청을 보여줍니다.
    • 자바스크립트 언어를 이용해 한 줄로부터 출발지 IP, HTTP 메소드, 응답 코드, 주소, 접속 시간을 추출해 낼 수 있나요?
      • 자바스크립트로 추출
        const log = '10.0.210.17 - - [28/Nov/2022:11:33:28 +0900] "GET /hello HTTP/1.1" 200 615 "-" "curl/7.84.0" "-"'
        
        const logPs = log.split(" ");
        
        const ipPs = logPs[0];
        const methodPs = logPs[5].split("\"")[1];
        const statusPs = logPs[8];
        const pathPs = logPs[6];
        
        const regex = /\[(.+)\]/g
        const match = regex.exec(raw)[1];
        const timestamp = dayjs(match, 'DD/MMM/YYYY:hh:mm:ss +ZZ').toISOString()
        
        console.log(`Source IP: ${ipPs}`);
        console.log(`HTTP Method: ${methodPs}`);
        console.log(`Status Code: ${statusPs}`);
        console.log(`Path: ${pathPs}`);
        console.log(`Timestamp: ${timestamp}`);
      • 결과
        Source IP: 10.0.210.17
        HTTP Method: GET
        Status Code: 200
        Path: /hello
        Timestamp: 28/Nov/2022:11:33:28 +0900

2. 파서(parser) 작성

  • parser.js
    • 수정코드
      #!/usr/bin/env node
      
      const dayjs = require('dayjs')
      const customParseFormat = require('dayjs/plugin/customParseFormat')
      dayjs.extend(customParseFormat)
      
      process.stdin.on("data", data => {
        let raw = data.toString()
      
        let parsed = raw.split(" ");
      
        let ipPs = parsed[0];
        let methosPs = parsed[5].split("\"")[1];
        let statusPs = parsed[8];
        let pathPs = parsed[6];
      
        let regex = /\[(.+)\]/g
        let match = regex.exec(raw)[1];
        let timestamp = dayjs(match, 'DD/MMM/YYYY:hh:mm:ss +ZZ').toISOString()
      
        let source_ip = ipPs;
        let method = methosPs;
        let status_code = statusPs;
        let path = pathPs;
      
        let jsonString = `
      {
        "source_ip": "${source_ip}",
        "method": "${method}",
        "status_code": ${status_code},
        "path": "${path}",
        "timestamp": "${timestamp}"
      }`
      
        process.stdout.write(jsonString)
      })
    • $ cat sample.log | ./parser.js
      {
        "source_ip": "10.0.210.17",
        "method": "GET",
        "status_code": 200,
        "path": "/hello",
        "timestamp": "2022-11-28T02:33:28.000Z"
      }

. 데이터 웨어하우스 준비

4. 데이터베이스 연결 테스트

  • ElephantSQL rWvWTQD9QuhzmDCHCWLmv-1670860489226.png
    • .env
      HOSTNAME= // 호스트 이름
      USERNAMEE= // 아이디
      PASSWORD= // 암호
      DATABASE= // 데이터베이스 이름
    • $ ./sql-runner.js < sql/1_reset.sql
      DROP TABLE IF EXISTS public.nginx;
      
      CREATE TABLE public.nginx (
              id serial4 NOT NULL,
              source_ip varchar NULL,
              "method" varchar NULL,
              status_code varchar NULL,
              "path" varchar NULL,
              "timestamp" timestamptz NULL,
              CONSTRAINT nginx_pk PRIMARY KEY (id)
      );
      
      undefined
      
      데이터베이스 연결 닫는 중...
      데이터베이스 연결 종료
      • sql/1_reset.sql PostgreSQL 데이터베이스의 "공용" 스키마에 "nginx"라는 이름의 새 테이블을 생성하고 이미 존재하는 경우 해당 테이블을 삭제하는 SQL 쿼리문이 적혀있음 "nginx" 테이블에는 각각의 데이터 유형이 있는 6개의 열이 생성됨
        • "id": 직렬 정수(자동 증가)
        • "source_ip": 소스 IP 주소 문자열
        • "method": 요청에 사용된 HTTP 메서드의 문자열
        • "status_code": 서버가 반환한 HTTP 상태 코드 문자열
        • "path": 요청된 URL 경로에 대한 문자열
        • "timestamp": 시간대가 포함된 타임스탬프
    • $ ./sql-runner.js < sql/2_describe_table.sql
      SELECT column_name, udt_name, is_nullable FROM information_schema.columns WHERE table_name = 'nginx'
      
      ┌─────────┬───────────────┬───────────────┬─────────────┐
      │ (index) │  column_name  │   udt_name    │ is_nullable │
      ├─────────┼───────────────┼───────────────┼─────────────┤
      │    0'id''int4''NO'     │
      │    1'timestamp''timestamptz''YES'    │
      │    2'source_ip''varchar''YES'    │
      │    3'method''varchar''YES'    │
      │    4'status_code''varchar''YES'    │
      │    5'path''varchar''YES'    │
      └─────────┴───────────────┴───────────────┴─────────────┘
      
      데이터베이스 연결 닫는 중...
      데이터베이스 연결 종료
      • sql/2_describe_table.sql 이전 SQL 파일("1_reset.sql")에서 생성된 "nginx" 테이블의 구조에 대한 정보를 얻기 위해 PostgreSQL 데이터베이스를 쿼리 이 SQL 쿼리는 열 이름, 데이터 유형 및 열이 “nginx” 테이블의 모든 열에 대해 NULL 값을 허용하는지 여부를 선택함
        • 이는 “information_shema.columns” 시스템 카탈로그를 사용하여 정보를 얻음

          출력에는 “nginx” 테이블의 각 열에 대해 하나씩 6개의 행이 있는 테이블과 각 열의 이름, 데이터 유형 및 null 허용 여부를 나타내는 3개의 열이 표시됨

5. 수집기(collector) 작성

  • collector.js
    • 수정코드
      #!/usr/bin/env node
      
      const dotenv = require('dotenv')
      const { Client } = require('pg')
      dotenv.config()
      
      const { HOSTNAME, USERNAMEE, PASSWORD, DATABASE } = process.env
      const client = new Client({
        host: HOSTNAME,
        user: USERNAMEE,
        password: PASSWORD,
        database: DATABASE
      })
      
      client.connect().then(() => {
      
        process.stdin.on("data", async data => {
          let raw = data.toString()
          let json = JSON.parse(raw)
      
          let queryString = `
            INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
            VALUES (
              '${json.source_ip}',
              '${json.method}',
              '${json.status_code}',
              '${json.path}',
              '${json.timestamp}'
            );
          `
      
          console.log(queryString)
          try {
            await client.query(queryString)
          }
          catch(e) {
            console.log(e)
          }
        })
      
      }).catch(err => console.log('연결 오류', err.stack))
      
      // Ctrl+C가 입력되면, 데이터베이스를 닫습니다
      process.on('SIGINT', async (sig) => {
        console.log('\n데이터베이스 연결 닫는 중...')
        await client.end()
        console.log('데이터베이스 연결 종료')
        process.exit(1)
      })
    • cat sample.json | ./collector.js
      INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
            VALUES (
              '127.0.0.1',
              'POST',
              '404',
              '/replace-me',
              '2022-11-28T02:33:28.000Z'
            );
    • ./sql-runner.js < sql/3_display_table_data.sql
      SELECT * FROM nginx;
      
      ┌─────────┬────┬─────────────┬────────┬─────────────┬───────────────┬──────────────────────────┐
      │ (index)id │  source_ip  │ method │ status_code │     path      │        timestamp         │
      ├─────────┼────┼─────────────┼────────┼─────────────┼───────────────┼──────────────────────────┤
      │    011'127.0.0.1''POST''404''/replace-me'2022-11-28T02:33:28.000Z │
      └─────────┴────┴─────────────┴────────┴─────────────┴───────────────┴──────────────────────────┘
      
      데이터베이스 연결 닫는 중...
      데이터베이스 연결 종료
      • sql/3_display_table_data.sql “nginx" 테이블에서 모든 행과 열을 선택하고 결과를 표시하는 쿼리문 쿼리는 별표(*)가 있는 SELECT 문을 와일드카드로 사용하여 모든 열을 선택함 쿼리 결과에는 "id"(기본 키), "source_ip", "method", "status_code", "path" 및 "timestamp"를 포함하여 "nginx" 테이블에 포함된 데이터가 표시됨

6. 잘못 쌓인 데이터 지우기 (optional)

  • 4_clean_up_table.sql
    DELETE FROM public.nginx;
    • ./sql-runner.js < sql/4_clean_up_table.sql
      DELETE FROM public.nginx;
      
      ┌─────────┐
      │ (index) │
      ├─────────┤
      └─────────┘
      
      데이터베이스 연결 닫는 중...
      데이터베이스 연결 종료

7. 파이프라인 완성

  • nginx access.log
    • tail -f /var/log/nginx/access.log
      127.0.0.1 - - [30/Mar/2023:15:02:05 +0900] "GET / HTTP/1.1" 200 412 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
      127.0.0.1 - - [30/Mar/2023:15:02:06 +0900] "GET /static/js/main.50329669.js HTTP/1.1" 304 0 "http://localhost:10024/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
      127.0.0.1 - - [30/Mar/2023:15:02:06 +0900] "GET /static/css/main.dec85a0c.css HTTP/1.1" 200 815 "http://localhost:10024/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
      127.0.0.1 - - [30/Mar/2023:15:02:06 +0900] "GET /logo.svg HTTP/1.1" 200 589 "http://localhost:10024/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
    • tail -f /var/log/nginx/access.log |./parser.js tail -f access.log의 표준 출력(stdout)을 parser.js의 표준 입력(stdin)이 되도록 파이프 연결
      {
        "source_ip": "127.0.0.1",
        "method": "GET",
        "status_code": 200,
        "path": "/",
        "timestamp": "2023-03-30T06:02:05.000Z"
      }
    • tail -f -n 0 /var/log/nginx/access.log |./parser.js | ./collector.js 새로운 출력만 로그로 쌓을 수 있도록 tail -f -n 0 옵션 설정 parser.js의 표준 출력을 수집기의 표준 입력이 되도록 파이프 연결
      INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
            VALUES (
              '127.0.0.1','GET','304','/','2023-03-30T06:04:19.000Z'
            );
          
      
            INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
            VALUES (
              '127.0.0.1','GET','304','/','2023-03-30T06:04:24.000Z'
            );
          
      
            INSERT INTO public.nginx (source_ip, method, status_code, path, timestamp)
            VALUES (
              '127.0.0.1','GET','304','/','2023-03-30T06:04:25.000Z'
            );
  • ./sql-runner.js < sql/3_display_table_data.sql
    SELECT * FROM nginx;
    
    ┌─────────┬────┬─────────────┬────────┬─────────────┬───────────────┬──────────────────────────┐
    │ (index)id │  source_ip  │ method │ status_code │     path      │        timestamp         │
    ├─────────┼────┼─────────────┼────────┼─────────────┼───────────────┼──────────────────────────┤
    │    01'127.0.0.1''POST''404''/replace-me'2022-11-28T02:33:28.000Z │
    │    12'127.0.0.1''GET''304''/'2023-03-30T06:04:19.000Z │
    │    23'127.0.0.1''GET''304''/'2023-03-30T06:04:24.000Z │
    │    34'127.0.0.1''GET''304''/'2023-03-30T06:04:25.000Z │
    └─────────┴────┴─────────────┴────────┴─────────────┴───────────────┴──────────────────────────┘
    
    데이터베이스 연결 닫는 중...
    데이터베이스 연결 종료
profile
기록하고 공유하려고 노력하는 DevOps 엔지니어

0개의 댓글