실습내용

IIS에서 웹서버 로그를 읽어서 분석서버(같은 랜 네트워크 우분투기반)의 kafka로 전송한다.

 

filebeat는?

filebeat는 ELK로 유명한 Elastic에서 제공하는 경량 로그 수집기이다.

같은 회사 Logstash와의 차이는 후자는 수집뿐 아니라 처리와 변환을 수행가능하다.

filebeat는 그러한 처리 변환 기능을 Flink등 별도의 실시간 로그처리 시스템에 맡기는 구조.

Java가 아닌 Go언어 기반으로 작성됨(Logstash는 Java)

 

로그수집방식은?

기본적으로 모니터링할 폴더를 지정하고, 그안에 생기는 로그들을 라인단위로 읽어서 kafka나 ElasticSearch쪽에 보내는 구조.

 

inputs방식 vs modules 방식

filebeat에는 2가지 설정법이 존재한다.

inputs방식: 일반적인 로그에 대해서 지정하는 방식. 어떤 로그도 가능하여 범용성이 좋다.

modules방식: Apache, Nginx, MySQL등 널리 알려진 특정한 로그에 대해서 수집하는 방식. 해당 로그형식에 대한 사전 구성된 설정을 제공하여 좀더 적합도가 높다.

이번 프로젝트에서는 전자를 사용했다. (후자는 시도했는데 뭔가 잘 안됨)

 

IIS가 돌 고 있는 윈도우 서버에 filebeat 설치하기

여기에 가서 window zip을 고른후 c:\program files\filebeat폴더에 풀어준다.

filebeat.yml을 아래와 같이 수정하여, IIS로 부터 로그를 분석하고, kafaka로 전송하도록 설정한다.

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - c:\inetpub\logs\LogFiles\W3SVC1\u_ex23*
  harvester_limit: 1
  
output.kafka:
  enabled: true
  hosts: ["192.168.0.20:9092"]
  topic: "iis_log"

관리자권한으로 PowerShell 프롬프트를 열고, 다음 명령어를 실행해서 윈도우 서비스로 등록하여 재부팅시에도 실행되도록 만들자.

PS > cd 'C:\Program Files\Filebeat'
PS C:\Program Files\Filebeat> .\install-service-filebeat.ps1

# 다음 커맨드까지 해야 서비스에 등록된다(리눅스에서 systemctl start 와 비슷한 역할)
Start-Service filebeat

# 상태 확인은 다음으로..
Get-Service filebeat

 

 

 

트러블슈팅

PowerShell에서 스크립트 실행이 안되는 경우 아래처럼 정책설정을 해준다.

Set-ExecutionPolicy UnRestricted -File .\install-service-filebeat.ps1
# 또는
Set-ExecutionPolicy Unrestricted -Scope Process
# 후자는 모든 프로세스를 허용하는 구조로 돌려놓으려면 아래 커맨드 실행

Set-ExecutionPolicy Default -Scope Process

 

kafka로 로그전달이 안될때,

가장 먼저 확인할 것은 아래처럼 telnet등으로 kafka쪽 서버 9092포트로 접속되는지 확인

telnet 192.168.0.20 9092

 

다음처럼 host등록이 필요한 경우가 있었다.

(설정파일에 IP로 설정했지만 어느순간 hostname으로 변경된 다음 못찾는다고 나옴;)

Windows에서의 호스트 파일 경로: C:\Windows\System32\drivers\etc\hosts
Linux에서의 호스트 파일 경로: /etc/hosts

127.0.0.1   localhost
::1         localhost

# 추가된 매핑 정보
192.168.0.20 sevityubuntu

위처럼 192.168.0.20과 sevityubuntu라는 호스트명을 매핑

 

filebeat를 콘솔에서 실행하고 로그를 보려면 다음처럼 하면 된다.

PS C:\Program Files\Filebeat> .\filebeat -e

 

반응형

'Data Engineering' 카테고리의 다른 글

flink Table API를 사용한 실시간 Reporting샘플  (0) 2023.10.28
flink  (1) 2023.10.28
Spark, Flink를 사용한 실시간 스트림 분석  (0) 2023.08.02
Apache Flink 설치  (0) 2023.08.02
kafka Consume  (0) 2023.08.02

먼저 IIS서버에 Filebeat를 깔아서 우분투 분석 서버에 kafka전송을 한다.

반응형

'Programming > Linux' 카테고리의 다른 글

Redis  (0) 2023.08.10
kafka 설치(우분투 기존)  (0) 2023.07.31
스팍 - 실습 - 웹서버 세션분석  (0) 2023.07.30
스팍(Spark) 설치  (0) 2023.07.30
하둡 - 실습 - 웹서버 세션분석  (0) 2023.07.29

먼저 하둡버전을 보고 오길 권한다.

스팍 설치에 관한 부분은 여기 참조.

우리는 하둡버전과 비슷하게 IIS웹서버를 분석해서 map - reduce과정을 통해 IP별 접근 시간및 URL을 정리해서 볼 것이다. 오리지널 웹서버 로그는 대략 다음과 같으며,

sevity@sevityubuntu:~/workspace/hadoop_sevity.com/session_analysis$ cat ../iis_logs/* | head
#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2015-09-06 08:32:43
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2015-09-06 08:32:43 127.0.0.1 GET / - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 - 200 0 0 211
2015-09-06 08:32:43 127.0.0.1 GET /iisstart.png - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 http://127.0.0.1/ 200 0 0 2
2015-09-06 08:32:43 127.0.0.1 GET /favicon.ico - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 http://127.0.0.1/ 404 0 2 2

map-reduce를 거친 결과는 대략 다음과 같을 것이다.

sevity@sevityubuntu:~/workspace/hadoop_spark/session_analysis$ cat session_output_merged.txt | head
157.55.39.90
[[(datetime.datetime(2016, 6, 7, 20, 44, 21), '/')], [(datetime.datetime(2016, 6, 8, 4, 20, 31), '/menu_wonilnet.asp'), (datetime.datetime(2016, 6, 8, 4, 20, 43), '/menu_friend.asp')], [(datetime.datetime(2019, 9, 16, 23, 58, 6), '/menu_friend.asp')], [(datetime.datetime(2021, 8, 17, 15, 8, 50), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 17, 17, 36, 23), '/menu_wonilnet.asp'), (datetime.datetime(2021, 8, 17, 17, 36, 24), '/menu_friend.asp')], [(datetime.datetime(2021, 8, 18, 1, 53, 52), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 19, 2, 40, 48), '/wiki/doku.php')], [(datetime.datetime(2021, 8, 19, 6, 45, 32), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 20, 10, 0, 31), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 21, 3, 55, 36), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 21, 4, 55, 13), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 23, 6, 49, 26), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 24, 1, 45, 47), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 24, 9, 58, 34), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 25, 11, 55, 24), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 26, 8, 22, 7), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 26, 13, 56, 19), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 27, 21, 59, 24), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 28, 20, 3, 46), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 28, 23, 51, 21), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 29, 18, 40, 40), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 29, 20, 6, 8), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 31, 1, 38, 52), '/wiki/doku.php')], [(datetime.datetime(2021, 8, 31, 5, 29, 5), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 8, 31, 6, 28, 20), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 9, 2, 0, 32, 17), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 9, 2, 16, 19, 20), '/wiki/lib/exe/detail.php')], [(datetime.datetime(2021, 9, 9, 3, 2, 43), '/menu_wonilnet.asp'), (datetime.datetime(2021, 9, 9, 3, 2, 45), '/menu_friend.asp')], [(datetime.datetime(2021, 9, 16, 7, 52, 4), '/wiki/doku.php')]]
138.197.96.197
[[(datetime.datetime(2017, 2, 15, 17, 21, 24), '/'), (datetime.datetime(2017, 2, 15, 17, 21, 26), '/menu_wonilnet.asp'), (datetime.datetime(2017, 2, 15, 17, 21, 26), '/board/dhtml_logo.js')]]
104.236.164.185
[[(datetime.datetime(2017, 11, 28, 11, 45, 56), '/')]]
181.213.93.231
[[(datetime.datetime(2018, 1, 30, 13, 1, 24), '/hndUnblock.cgi'), (datetime.datetime(2018, 1, 30, 13, 1, 28), '/tmUnblock.cgi'), (datetime.datetime(2018, 1, 30, 13, 1, 32), '/moo'), (datetime.datetime(2018, 1, 30, 13, 1, 36), '/'), (datetime.datetime(2018, 1, 30, 13, 1, 42), '/getcfg.php'), (datetime.datetime(2018, 1, 30, 13, 1, 49), '/getcfg.php')]]
117.80.147.63
[[(datetime.datetime(2018, 2, 14, 8, 13, 12), '/')]]

실습을 위한 디렉토리 구조는 대략 다음과 같다.

sevity@sevityubuntu:~/workspace/hadoop_spark$ tree -L 3
.
├── iis_logs (여기에 분석하려는 웹로그 파일들이 있습니다. 실제로는 수없이 많음)
│   ├── u_ex230101.log
│   ├── u_ex230102.log
│   └── u_ex230103.log
├── session_analysis
│   ├── session_output
│   │   ├── part-00001
│   │   ├── part-00002
│   │   ├── part-00003
│   │   └── _SUCCESS
│   ├── session_output_merged.txt
│   └── spark_session_analysis.py(우리가 작성할 python 코드의 위치)
└── spark_test

다음과 같이 spark_session_analysis.py를 작성하자.

여기서 중요한 점은 다음과 같다.

  • 스팍은 하둡과 다르게 하나의 파일에서 map과 reduce를 모두 수행한다.
  • 각 동작은 함수호출 형태로 진행되며,
  • 아래에서 reduce에 해당하는 함수는 groupByKey()이다.
from pyspark import SparkConf, SparkContext
from datetime import datetime, timedelta
from pyspark.sql import SparkSession

def parse_log_line(line):
    try:
        parts = line.strip().split()
        if len(parts) != 15:
            return None

        date_time = parts[0] + " " + parts[1]
        date_time = datetime.strptime(date_time, '%Y-%m-%d %H:%M:%S')
        url = parts[4]
        ip = parts[8]  # changed from parts[2] to parts[8] to use the client IP

        return (ip, (date_time, url))
    except:
        return None

def calculate_sessions(group):
    ip, data = group
    data = list(data)
    data.sort(key = lambda x: x[0])
    sessions = []
    session = [data[0]]
    for i in range(1, len(data)):
        if data[i][0] - session[-1][0] > timedelta(seconds=1800):
            sessions.append(session)
            session = [data[i]]
        else:
            session.append(data[i])
    sessions.append(session)
    return (ip, sessions)


# Spark configuration
# [*]하면 모든 코어 사용
conf = SparkConf().setMaster('local[*]').setAppName('Log Analysis')
sc = SparkContext(conf=conf)

log_lines = sc.textFile('hdfs://localhost:9000/logs/u_ex*')
parsed_lines = log_lines.map(parse_log_line).filter(lambda x: x is not None)
grouped_by_ip = parsed_lines.groupByKey()
sessions = grouped_by_ip.flatMap(calculate_sessions)
sessions.saveAsTextFile('hdfs://localhost:9000/session_output')

다음 명령을 통해 실행하고 결과를 확인한다.

# hdfs에 reduce결과를 쌓을 곳을 clean-up해준다. 
hdfs dfs -rm -r /session_output

# 로컬파일시스템에 복사해올 경로도 clean-up해준다.
rm -rf session_output
rm -f session_output_merged.txt

# python 파일을 실행한다. spark-submit을 통해 실행하면 4040포트를 통해 웹에서 진행상황 확인가능
$SPARK_HOME/bin/spark-submit spark_session_analysis.py 2>&1 | tee r.txt 

# HDFS에서 로컬파일시스템으로 결과 폴더 복사
hadoop fs -get /session_output .

# 위의 폴더구조에서 볼 수 있듯 병렬처리 때문에 결과파일이 여러개로 분리돼 있는데 합쳐준다.
# 합치고 로컬파일시스템으로 복사하는 것까지.
hadoop fs -getmerge hdfs://localhost:9000/session_output ./session_output_merged.txt

# 위의 방법대신 python코드내에서 다음 방법으로 머지해도 된다.
# 단 스팍을 써서 머지하는 방법인 만큼 메모리가 터질수도 있으니 주의
sessions.coalesce(1).saveAsTextFile('hdfs://localhost:9000/session_output')
반응형

'Programming > Linux' 카테고리의 다른 글

kafka 설치(우분투 기존)  (0) 2023.07.31
ELK연습  (0) 2023.07.30
스팍(Spark) 설치  (0) 2023.07.30
하둡 - 실습 - 웹서버 세션분석  (0) 2023.07.29
하둡 - 실습 - 웹서버로그분석  (0) 2023.07.29

기본개념

스파크(Spark)는 하둡과 마찬가지로 자바(Java) 기반의 빅 데이터 처리 프레임워크입니다. 
하지만 스파크는 자바 외에도 스칼라(Scala), 파이썬(Python), R 등 여러 언어를 지원합니다.

스파크의 핵심 코드는 스칼라로 작성되어 있습니다. 스칼라는 함수형 프로그래밍과 객체지향 프로그래밍을 모두 지원하는 현대적인 다중 패러다임 프로그래밍 언어입니다. 스칼라는 JVM(Java Virtual Machine) 위에서 동작하므로 자바와 호환성이 좋습니다.

스파크는 또한 파이스파크(PySpark)라는 인터페이스를 통해 파이썬에서도 사용할 수 있습니다. 이는 데이터 과학자들이 파이썬으로 빅 데이터 처리를 수행할 수 있게 해주는 큰 장점입니다. 

 

 

설치과정

가장 간단하게는 그냥 pip install pyspark 하면 깔린다.

하지만, 먼저 설치한 하둡하고 연결하려면 아래 과정을 거치면 된다.

 

먼저 공식홈페이지에 가서 Pre-built for Apache Hadoop을 선택하고 다운로드를 받는다.

아래는 해당 다운로드 링크를 wget으로 받는 과정을 설명한다.

아래처럼 wget으로 받고 압축을 풀어준다.

cd /opt/spark
sudo wget https://dlcdn.apache.org/spark/spark-3.4.1/spark-3.4.1-bin-hadoop3.tgz
sudo tar -xvzf spark-3.4.1-bin-hadoop3.tgz

그다음 ~/.bashrc파일에 아래 3줄을 추가하고 source ~/.bashrc로 적용해준다.

export SPARK_HOME=/opt/spark/spark-3.4.1-bin-hadoop3
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin
export PYSPARK_PYTHON=/usr/bin/python3

그다음 설정파일에 대한 설정을 해준다.

cd /opt/spark/spark-3.4.1-bin-hadoop3/conf
cp spark-env.sh.template spark-env.sh

#spark-env.sh에 아래 두 줄 추가
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export SPARK_HOME=$SPARK_HOME

#추가한 내용 반영
source spark-env.sh

그 다음 pyspark을 설치해준다.

pip3 install pyspark

 

 

설치가 잘 됐는지 테스트

다음 명령어를 통해 spark shell이 잘 뜨는지 확인(scala> 프롬프트가 뜨면 성공)

$SPARK_HOME/bin/spark-shell

다음 파이선 스크립트를 통해 하둡 HDFS와 잘 연동되는지 확인

from pyspark.sql import SparkSession

spark = SparkSession.builder.getOrCreate()
df = spark.read.text("hdfs://localhost:9000/test.txt")
df.show()
# 먼저 hdfs로 test.txt를 복사해준다.
$ cat test.txt
hello spark
$ hdfs dfs -put test.txt /

# 그다음 다음명령으로 test.py 수행
$ $SPARK_HOME/bin/spark-submit test.py
+-----------+
|      value|
+-----------+
|hello spark|
+-----------+

위의 spark-submit을 통한 실행시 test.py가 끝나기 전에 4040포트로 웹접속을 하면 다음과 같이 웹으로 경과확인이 가능하다!

반응형

'Programming > Linux' 카테고리의 다른 글

ELK연습  (0) 2023.07.30
스팍 - 실습 - 웹서버 세션분석  (0) 2023.07.30
하둡 - 실습 - 웹서버 세션분석  (0) 2023.07.29
하둡 - 실습 - 웹서버로그분석  (0) 2023.07.29
하둡(Hadoop)  (0) 2023.07.29

먼저 필요한경우 하둡을 포맷하고 초기화 해준다.

stop-dfs.sh  # 분산 파일 시스템(HDFS) 중지
stop-yarn.sh # 리소스 관리자(YARN)중지
hdfs namenode -format
start-dfs.sh
# YARN은 Hadoop의 주요 컴포넌트 중 하나로, 클러스터 리소스 관리 및 job scheduling을 담당하고 있습니다.
# 따라서, 만약 MapReduce 작업을 수행할 예정이라면, 이 명령어를 통해 YARN을 시작해야 합니다.
start-yarn.sh

# 나같은 경우 아래를 해야하는 경우가 있었다.
rm -rf /hadoop/data/*

# 헬스체크
hadoop fsck /

 

내 웹서버 로그 샘플은 다음과 같음

evity@sevityubuntu:~/workspace/hadoop_sevity.com/session_analysis$ cat ../iis_logs/* | head
#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2015-09-06 08:32:43
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2015-09-06 08:32:43 127.0.0.1 GET / - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 - 200 0 0 211
2015-09-06 08:32:43 127.0.0.1 GET /iisstart.png - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 http://127.0.0.1/ 200 0 0 2
2015-09-06 08:32:43 127.0.0.1 GET /favicon.ico - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 http://127.0.0.1/ 404 0 2 2

이중에서 다음 컬럼을 뽑아서 map-reduce를 해보도록 하자.

날짜와 시간 (2015-09-06 08:32:43)
클라이언트 IP (c-ip)
요청된 URL (cs-uri-stem)

먼저 매핑과정을 통해 다음처럼 클라이언트 IP별로 30분 단위로 세션으로 묶어서 URL들을 뽑는다.

119.207.72.161  {"start_time": "2020-10-03 00:14:47", "end_time": "2020-10-03 00:14:48", "duration": 1.0, "url_list": ["/favicon.ico", "/board/dhtml_logo.js", "/menu_wonilnet.asp", "/", "/css/common.css"]}
66.249.65.54    {"start_time": "2020-10-03 00:27:31", "end_time": "2020-10-03 00:27:31", "duration": 0.0, "url_list": ["/wiki/doku.php"]}
80.82.70.187    {"start_time": "2020-10-03 01:59:09", "end_time": "2020-10-03 01:59:09", "duration": 0.0, "url_list": ["/cache/global/img/gs.gif"]}
139.59.95.139   {"start_time": "2020-10-03 02:11:31", "end_time": "2020-10-03 02:11:31", "duration": 0.0, "url_list": ["/"]}

그다음 리듀스 과정을 통해 IP별로 총 체류시간과 url_list를 묶어준다.

66.249.64.128   {"total_duration": 1694.0, "url_list": ["/menu_wonilnet.asp", "/menu_friend.asp", "/robots.txt", "/", "/menu_wonilnet_newImage.asp", "/favicon.ico"], "start_time": "2017-09-03 04:19:21", "end_time": "2018-07-29 20:43:11"}
66.249.70.30    {"total_duration": 1694.0, "url_list": ["/menu_wonilnet.asp", "/menu_friend.asp", "/robots.txt", "/wiki/doku.php", "/wiki/lib/tpl/dokuwiki/images/favicon.ico", "/", "/wiki/feed.php", "/wiki/lib/exe/js.php", "/wiki/lib/exe/css.php", "/css/common.css", "/wiki/lib/exe/detail.php", "/favicon.ico", "/wiki/lib/exe/fetch.php"], "start_time": "2017-07-26 01:31:14", "end_time": "2022-02-01 09:49:49"}
2.37.132.159    {"total_duration": 1689.0, "url_list": ["/"], "start_time": "2018-03-23 22:13:24", "end_time": "2018-03-23 22:43:34"}

폴더구조는 다음과 같으며, 이번 프로젝트의 현재 디렉토리는 session_analysis이다.

sevity@sevityubuntu:~/workspace/hadoop_sevity.com$ tree -L 1
.
├── iis_logs
├── ip_cnt
└── session_analysis

mapper.py를 다음과 같이 작성한다.

sevity@sevityubuntu:~/workspace/hadoop_sevity.com/session_analysis$ cat mapper.py
#!/usr/bin/env python3

import sys
import json
import codecs
from datetime import datetime, timedelta
import logging

logging.basicConfig(stream=sys.stderr, level=logging.ERROR)

previous_ip = None
url_list = []
start_time = None
end_time = None
session_timeout = timedelta(seconds=1800)  # 1800 seconds = 30 minutes

def reset_variables():
    global url_list, start_time, end_time
    url_list = []
    start_time = None
    end_time = None

# Using codecs to get stdin with a fallback in case of a UTF-8 decoding error
stdin = codecs.getreader('utf-8')(sys.stdin.buffer, errors='ignore')

for line in stdin:
    try:
        parts = line.strip().split()
        if len(parts) != 15:
            continue

        date_time = parts[0] + " " + parts[1]
        date_time = datetime.strptime(date_time, '%Y-%m-%d %H:%M:%S')

        ip = parts[8]  # changed from parts[2] to parts[8] to use the client IP
        url = parts[4]

        # If there is a previous ip and the current ip is different or a session timeout has occurred
        if previous_ip and (previous_ip != ip or (date_time - end_time) > session_timeout):
            print('%s\t%s' % (previous_ip, json.dumps({"start_time": str(start_time), "end_time": str(end_time), "duration": (end_time - start_time).total_seconds(), "url_list": list(set(url_list))})))
            reset_variables()

        if not start_time or date_time < start_time:
            start_time = date_time
        if not end_time or date_time > end_time:
            end_time = date_time

        url_list.append(url)
        previous_ip = ip

    except Exception as e:
        logging.error(f"Error processing line: {line.strip()}, Error: {e}")

# Print the last session
if previous_ip:
    print('%s\t%s' % (previous_ip, json.dumps({"start_time": str(start_time), "end_time": str(end_time), "duration": (end_time - start_time).total_seconds(), "url_list": list(set(url_list))})))

reducer.py를 다음과 같이 작성한다.

sevity@sevityubuntu:~/workspace/hadoop_sevity.com/session_analysis$ cat reducer.py
#!/usr/bin/env python3

import sys
import json

previous_ip = None
total_duration = 0.0
url_list = []
start_time = None
end_time = None

for line in sys.stdin:
    ip, session_info = line.strip().split('\t')
    session_info = json.loads(session_info)

    if previous_ip and previous_ip != ip:
        print('%s\t%s' % (previous_ip, json.dumps({"total_duration": total_duration, "url_list": list(set(url_list)), "start_time": start_time, "end_time": end_time})))

        total_duration = 0.0
        url_list = []
        start_time = None
        end_time = None

    if not start_time or session_info["start_time"] < start_time:
        start_time = session_info["start_time"]
    if not end_time or session_info["end_time"] > end_time:
        end_time = session_info["end_time"]

    total_duration += float(session_info["duration"])
    url_list.extend(session_info["url_list"])

    previous_ip = ip

if previous_ip:
    print('%s\t%s' % (previous_ip, json.dumps({"total_duration": total_duration, "url_list": list(set(url_list)), "start_time": start_time, "end_time": end_time})))

다음명령을 통해 map-reduce과정을 병렬로 진행한다.

hdfs dfs -rm -r /output && \
	hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar    \
    -file ~/workspace/hadoop_sevity.com/session_analysis/mapper.py     \
    -mapper 'python3 mapper.py'     \
    -file ~/workspace/hadoop_sevity.com/session_analysis/reducer.py     \
    -reducer 'python3 reducer.py'     \
    -input /logs/* -output /output \
    2>&1 | tee r.txt

결과를 HDFS에서 로컬파일시스템으로 가져온다.

rm -rf output && hadoop fs -get /output .

다음 python파일을 통해 duration역순으로 정렬한다.

import json
import sys

def get_duration(json_str):
    data = json.loads(json_str)
    return data.get('total_duration', 0)

lines = sys.stdin.readlines()

# 각 줄을 total_duration 값에 따라 정렬
lines.sort(key=lambda line: get_duration(line.split('\t', 1)[1]), reverse=True)

for line in lines:
    print(line, end='')
python3 sort.py < output/part-00000 > result.txt

result.txt를 보면 다음과 같다.

45.61.187.81    {"total_duration": 186353.0, "url_list": ["/"], "start_time": "2022-07-23 09:13:06", "end_time": "2022-08-22 20:57:09"}
127.0.0.1       {"total_duration": 161933.0, "url_list": ["/w/lib/tpl/dokuwiki/images/favicon.ico", "/wonilnet_pub/2010-06-09_192759_resize.png", "/w/lib/exe/css.php", "/image/top_menu_icon.gif", "/upgsvr/service.exe", "/favicon.ico", "/index.asp", "/w/lib/tpl/dokuwiki/images/pagetools-sprite.png", "/scrape", "/w/lib/tpl/dokuwiki/images/search.png", "/wiki/doku.php", "/w/lib/tpl/dokuwiki/images/usertools.png", "/", "/images/next5.gif", "/wonilnet_pub/2010-06-09_192656_resize.png", "/w/lib/tpl/dokuwiki/images/button-php.gif", "/image/rightconer_shadow.gif", "/menu_wonilnet_dbwork.asp", "/iisstart.png", "/w/lib/images/external-link.png", "/tc.js", "/w", "/css/common.css", "/a.php", "/images/num1_on_a.gif", "/w/lib/tpl/dokuwiki/images/button-donate.gif", "/w/lib/tpl/dokuwiki/images/button-html5.png", "/menu_friend.asp", "/w/lib/tpl/dokuwiki/images/button-dw.png", "/announce.php", "/w/doku.php", "/w/lib/tpl/dokuwiki/images/logo.png", "/announce", "/images/nonext.gif", "/images/num5_off.gif", "/image/garo_shadow.gif", "/scrape.php", "/image/sero_shadow.gif", "/w/lib/tpl/dokuwiki/images/button-css.png", "/images/num2_off.gif", "/menu_wonilnet.asp", "/favicon.png", "/image/leftconer_shadow.gif", "/images/num4_off.gif", "/images/num3_off.gif", "/image/line02.gif", "/board/dhtml_logo.js", "/w/lib/tpl/dokuwiki/images/page-gradient.png", "/w/lib/images/license/button/cc-by-sa.png", "/w/lib/exe/js.php", "/pub/02111401.jpg", "/w/lib/exe/indexer.php"], "start_time": "2015-09-06 08:32:43", "end_time": "2021-10-13 12:56:47"}
45.61.188.237   {"total_duration": 121316.0, "url_list": ["/"], "start_time": "2022-10-03 20:31:14", "end_time": "2022-10-26 20:10:54"}
64.62.252.174   {"total_duration": 102775.0, "url_list": ["/stock/test1/main.cpp", "/menu_wonilnet.asp", "/menu_friend.asp", "/stock/test1/id.cpp", "/robots.txt", "/wiki/lib/exe/css.php", "/wiki/lib/exe/opensearch.php", "/wiki/feed.php", "/wiki/doku.php", "/wiki/lib/exe/", "/wiki/lib/exe/indexer.php", "/wiki/lib/exe/js.php", "/wiki/lib/exe/mediamanager.php", "/wiki/", "/wiki/lib/exe/detail.php", "/wiki/lib/exe/fetch.php"], "start_time": "2022-08-11 04:54:40", "end_time": "2023-07-06 16:23:29"}

 

이 과정을 통해 다음과 같이 해킹을 시도하는 서버접근을 발견할 수 있었다.

124.173.69.66   {"total_duration": 1685.0, "url_list": ["/xw1.php", "/9510.php", "/link.php", "/sbkc.php", "/index.php", "/phpmyadmin/scripts/setup.php", "/xmlrpc.php", "/jsc.php.php", "/phpmyadmin3333/index.php", "/phpNyAdmin/index.php", "/hm.php", "/411.php", "/hhhhhh.php", "/key.php", "/92.php", "/pma/scripts/db___.init.php", "/aojiao.php", "/1/index.php", "/3.php", "/slider.php", "/plus/tou.php", "/g.php", "/ssaa.php", "/sss.php", "/lala-dpr.php", "/605.php", "/admin/index.php", "/plus/yunjitan.php", "/d.php", "/boots.php", "/win1.php", "/hell.php", "/uploader.php", "/images/vuln.php", "/error.php", "/que.php", "/099.php", "/aa.php", "/12345.php", "/666.php", "/api.php", "/plus/90sec.php", "/xiao.php", "/fusheng.php", "/xiaoxi.php", "/plus/ma.php", "/paylog.php", "/321/index.php", "/qwqw.php", "/qiangkezhi.php", "/liangchen.php", "/mysql.php", "/ganshiqiang.php", "/images/stories/cmd.php", "/xiong.php", "/datas.php", "/xixi.php", "/aaa.php", "/awstatstotals/awstatstotals.php", "/conflg.php", "/php2MyAdmin/index.php", "/think.php",

 

반응형

'Programming > Linux' 카테고리의 다른 글

스팍 - 실습 - 웹서버 세션분석  (0) 2023.07.30
스팍(Spark) 설치  (0) 2023.07.30
하둡 - 실습 - 웹서버로그분석  (0) 2023.07.29
하둡(Hadoop)  (0) 2023.07.29
Docker 설치, 초기설정, 명령어가이드  (0) 2023.07.17

어떤 IP에서 가장많이 접속했는지 확인

필자는 개인 IIS 웹서버를 운영하고 있음

다음을 통해 웹서버에서 하둡이 설치된 우분투 리눅스로 가져오고 HDFS로 복사(파일 복사가 RDB에서는 insert에 해당한다)

# 웹서버에서 하둡이설치된 리눅스로 로그파일들 복사
scp linowmik@secsm.org@192.168.0.6:/inetpub/logs/LogFiles/W3SVC1/* ./iis_logs

# HDFS로 복사
hdfs dfs -put ./iis_logs/* /logs/

# 위 복사가 너무 오래걸리면 아래 명령을 통해 진행상황 모니터링 가능
cd $HADOOP_HOME/logs
sudo tail -f $(ls -Art $HADOOP_HOME/logs | tail -n 1)
# 위명령어 설명은 다음과 같음
# ls -Art $HADOOP_HOME/logs: $HADOOP_HOME/logs 디렉토리 내의 파일을 날짜별로 오름차순 정렬합니다. -Art 옵션은 -A (숨겨진 파일 포함), -r (역순), -t (수정 시간별 정렬) 옵션을 동시에 사용하는 것입니다.
# tail -n 1: 파일 리스트 중에서 가장 최신의 파일 (리스트의 마지막 항목)을 선택합니다.
# tail -f: 선택한 파일의 내용을 실시간으로 출력합니다. -f 옵션은 'follow'의 약자로, 파일의 내용이 변경될 때마다 이를 반영하여 출력합니다.

로그가 어떤 형식인지 잠시 확인한다

sevity@sevityubuntu:~/workspace/hadoop_sevity.com$ cat iis_logs/*.log | head
#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2015-09-06 08:32:43
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2015-09-06 08:32:43 127.0.0.1 GET / - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 - 200 0 0 211
2015-09-06 08:32:43 127.0.0.1 GET /iisstart.png - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 http://127.0.0.1/ 200 0 0 2
2015-09-06 08:32:43 127.0.0.1 GET /favicon.ico - 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/45.0.2454.85+Safari/537.36 http://127.0.0.1/ 404 0 2 2

#으로 시작하는 라인을 제외하면 날짜 시간 IP 등의 순임을 알 수 있다.

  • 2023-03-23 00:20:47 : 요청이 발생한 날짜와 시간.
  • 192.168.0.6 : 서버의 IP 주소 또는 호스트 이름.
  • GET : HTTP 요청 방법. 이 경우, 클라이언트는 서버로부터 정보를 요청하고 있습니다.
  • /wiki/doku.php : 클라이언트가 요청한 리소스의 경로.
  • id=learning_rate&do=login&sectok=bac058c83457f1bfade853577f509baf : 요청에 전달된 파라미터입니다.
  • 80 : 사용된 포트 번호. HTTP의 기본 포트는 80입니다.
  • - : 이 필드는 일반적으로 RFC 1413 ident 프로토콜을 통해 확인된 원격 사용자 이름을 나타냅니다. 여기서 "-"는 이 정보가 사용 불가능하다는 것을 의미합니다.
  • 216.244.66.237 : 클라이언트의 IP 주소.
  • Mozilla/5.0+(compatible;+DotBot/1.2;++https://opensiteexplorer.org/dotbot;+help@moz.com) : 사용자 에이전트. 이는 클라이언트가 사용하는 웹 브라우저 또는 봇을 설명합니다. 이 경우에는 DotBot이라는 봇이 서버에 요청을 보냈습니다.
  • - : 이 필드는 일반적으로 "referrer" URL을 나타냅니다. 여기서 "-"는 이 정보가 사용 불가능하다는 것을 의미합니다.
  • 200 : HTTP 상태 코드. 200은 성공을 의미합니다.
  • 0 : 서브 상태 코드. 이는 서버에 따라 다르게 해석될 수 있습니다.
  • 0 : Win32 상태 코드. 이는 서버에 따라 다르게 해석될 수 있습니다.
  • 1068 : 서버가 클라이언트에게 보낸 바이트 .

 

이를위한 mapper.py를 다음처럼 작성한다.

import sys
import logging

# Set up logging
# logging.basicConfig(filename="mapper.log", level=logging.DEBUG)
# 아래처럼 하면 하둡로그랑 섞여서 나와서 디버깅할때 좋다.
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)


# Ignore bad characters
for line in sys.stdin.buffer:
    try:
        line = line.decode('utf-8', 'ignore')
    except UnicodeDecodeError as e:
        logging.error(f"Error decoding line: {line.strip()}, Error: {e}")
        continue

    try:
        data = line.strip().split(" ")
        if len(data) == 15:
            date, time, s_ip, cs_method, cs_uri_stem, cs_uri_query, s_port, cs_username, c_ip, cs_user_agent, cs_referer, sc_status, sc_substatus, sc_win32_status, time_taken = data
            logging.debug(f"Client IP: {c_ip}, Count: 1")
            print("{0}\t{1}".format(c_ip, 1))
    except Exception as e:
        # 예외 발생 시 로그 기록
        logging.error(f"Error processing line: {line.strip()}, Error: {e}")

관심있는 IP와 카운트1을 결과로 출력함을 알 수 있다. (이러면 하둡이 스트리밍하여 Reduce쪽으로 넘긴다)

 

이제 아래와 같이 reducer.py를 작성하여 IP별 카운트를 집계한다.

#!/usr/bin/env python3

import sys
import logging

# Set up logging
logging.basicConfig(filename="reducer.log", level=logging.DEBUG)

last_key = None
running_total = 0
key = None

try:
    for input in sys.stdin:
        key, value = input.strip().split("\t", 1)
        if last_key == key:
            running_total += int(value)
        else:
            if last_key:
                logging.debug(f"Key: {last_key}, Running Total: {running_total}")
                print("{0}\t{1}".format(last_key, running_total))
            running_total = int(value)
            last_key = key

    if last_key == key and key is not None:
        logging.debug(f"Key: {last_key}, Running Total: {running_total}")
        print("{0}\t{1}".format(last_key, running_total))

except Exception as e:
    # 예외 발생 시 로그 기록
    logging.error(f"Error processing input: {e}")

다음명령을 통해 map-reduce를 수행(RDB에서 SELECT에 해당한다)

# 이전실행 클린업
hdfs dfs -rm -r /output

# 실행
hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar \
	-file ~/workspace/hadoop_sevity.com/mapper.py \
    -mapper 'python3 mapper.py' \
    -file ~/workspace/hadoop_sevity.com/reducer.py \
    -reducer 'python3 reducer.py' \
    -input /logs/* -output /output 2>&1 | tee r.txt

그다음 결과를 hdfs에서 로컬로 가져와서 결과 보기

# 로컬로 가져오기
hadoop fs -get /output .

# IP카운트(2번째컬럼) 역순으로 정렬
sort -k2,2nr -t $'\t' output/* > sorted_output.txt

# 결과보기(상위 10개)
cat sorted_output.txt | head
216.244.66.237	69027
192.168.0.1	27738
211.189.165.105	20114
64.62.252.174	13246
119.207.72.84	8959
211.53.177.20	6565
66.249.82.88	5636
66.249.82.89	5632
66.249.82.90	5312
194.110.203.7	5157

# 가장 많이 접속한 IP에 대한 정보를 역추적해서 보기
grep -m 10 216.244.66.237 iis_logs/* | head
iis_logs/u_ex180725.log:2018-07-25 05:40:56 192.168.0.2 GET /robots.txt - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 404 0 2 499
iis_logs/u_ex180725.log:2018-07-25 05:44:26 192.168.0.2 GET / - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 200 0 0 641
iis_logs/u_ex180805.log:2018-08-05 02:39:00 192.168.0.2 GET /robots.txt - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 404 0 2 194
iis_logs/u_ex180805.log:2018-08-05 02:42:02 192.168.0.2 GET / - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 200 0 0 181
iis_logs/u_ex181027.log:2018-10-27 06:25:55 192.168.0.2 GET /robots.txt - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 404 0 2 183
iis_logs/u_ex181027.log:2018-10-27 06:35:36 192.168.0.2 GET / - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 200 0 0 176
iis_logs/u_ex181107.log:2018-11-07 01:04:26 192.168.0.2 GET /robots.txt - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 404 0 2 448
iis_logs/u_ex181107.log:2018-11-07 01:13:17 192.168.0.2 GET / - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 200 0 0 668
iis_logs/u_ex190201.log:2019-02-01 21:31:17 192.168.0.2 GET /robots.txt - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 404 0 2 118
iis_logs/u_ex190201.log:2019-02-01 21:45:12 192.168.0.2 GET / - 80 - 216.244.66.237 Mozilla/5.0+(compatible;+DotBot/1.1;+http://www.opensiteexplorer.org/dotbot,+help@moz.com) - 200 0 0 118
sevity@sevityubuntu:~/workspace/hadoop_sevity.com$
반응형

'Programming > Linux' 카테고리의 다른 글

스팍(Spark) 설치  (0) 2023.07.30
하둡 - 실습 - 웹서버 세션분석  (0) 2023.07.29
하둡(Hadoop)  (0) 2023.07.29
Docker 설치, 초기설정, 명령어가이드  (0) 2023.07.17
vimdiff  (0) 2021.04.07

설치

하둡은 java기반이라 java sdk설치가 필요.

sudo apt-get update
sudo apt-get install default-jdk

그다음, 도커 이미지 받아서 그 안에서 연습해볼수도 있지만, 제대로 하려면 아래처럼 다운받고 설정하는 과정이 필요

# 하둡의 공식 웹사이트에서 최신 버전을 다운받아 설치
sudo wget https://dlcdn.apache.org/hadoop/common/hadoop-3.3.6/hadoop-3.3.6.tar.gz

# 압축풀고 설정진행
tar -xvf hadoop-3.3.0.tar.gz
sudo mv hadoop-3.3.0 /usr/local/hadoop
#~/.bashrc에 아래 것들 추가
export HADOOP_HOME=/usr/local/hadoop
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin
export HADOOP_MAPRED_HOME=$HADOOP_HOME
export HADOOP_COMMON_HOME=$HADOOP_HOME
export HADOOP_HDFS_HOME=$HADOOP_HOME
export YARN_HOME=$HADOOP_HOME
export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64


#반영
source ~/.bashrc

그다음 어떤 파일시스템을 하부로 쓸것인지 설정파일들을 좀 만져야함. 기본으로 깔렸을때는 hdfs가 아닌 로컬파일시스템을 쓰도록 되어 있음(;;)

# sudo vi $HADOOP_HOME/etc/hadoop/core-site.xml
# <configuration> 태그 사이에 아래내용 추가
<property>
    <name>fs.defaultFS</name>
    <value>hdfs://localhost:9000</value><!--이거 해줘야 로컬파일시스템이 아닌 hdfs씀-->
</property>

# sudo vi $HADOOP_HOME/etc/hadoop/hdfs-site.xml
# <configuration> 태그 사이에 아래내용 추가

<property>
    <name>dfs.replication</name>
    <value>1</value> <!--HDFS에 저장되는 각 데이터 블록이 클러스터 전체에서 복제되는 횟수를 결정-->
</property>

단일노드가 아닌 클러스터 구성시 추가 설정들이 필요(여기서는 생략)

 

다음은 포맷하고 전체시작

# 처음에 포맷해줘야 함
hdfs namenode -format

#전체 재시작
cd /usr/local/hadoop/sin
./stop-all.sh
./start-all.sh

#java ps. 아래처럼 NameNode, DataNode, SecondaryNameNode가 뜨는것을 확인
jps
128290 SecondaryNameNode
127865 NameNode
128026 DataNode

 

HDFS기본 핸들링 방법

아래처럼 hdfs dfs로 시작하는 명령을 주거나,  Hadoop이 제공하는 Hadoop FUSE(FIlesystem in USErspace) 모듈을 이용하면 기존파일시스템에 mount해서 ls,mv,cp등 그대로 사용하는 것도 가능

# /logs라는 폴더 만들기
hdfs dfs -mkdir /logs

# ls하기. Hadoop Distributed File System (HDFS)의 루트 디렉토리 내용을 나열
hadoop dfs -ls /
Found 1 items
drwxr-xr-x   - sevity supergroup          0 2023-07-29 13:20 /logs

# 파일 복사하기.
hdfs dfs -put iis_logs/* /logs

# 헬스체크
hdfs fsck /

 

 

트러블슈팅

기본적인 로그 모니터링

tail -f /usr/local/hadoop/logs/*.log

start-dfs.sh로 하둡을 시작했을때 namenode가 시작되지 않았을때(jps했을때 안보일때)

# 아래 명령으로 이유 파악
 grep -C 5 'ERROR' $HADOOP_HOME/logs/hadoop-sevity-namenode-sevityubuntu.log

# 만약 아래처럼 /tmp 아래 디렉토리 접근이 안된다는 것이면
2023-07-30 16:03:30,469 ERROR org.apache.hadoop.hdfs.server.namenode.NameNode: Failed to start namenode.
org.apache.hadoop.hdfs.server.common.InconsistentFSStateException: Directory /tmp/hadoop-sevity/dfs/name is in an inconsistent state: storage directory does not exist or is not accessible.
        at org.apache.hadoop.hdfs.server.namenode.FSImage.recoverStorageDirs(FSImage.java:392)

# hdfs-site.xml을 열어서 경로를 /tmp가 아닌 /var/lib등 임시가 아닌곳으로 옮겨준다.
vi /usr/local/hadoop/etc/hadoop/hdfs-site.xml
아래 내용 추가
    <property>
      <name>dfs.namenode.name.dir</name>
      <value>/var/lib/hadoop-hdfs/cache/hdfs/dfs/name</value>
    </property>

# 관련 폴더 만들고 권한 부여
sudo mkdir -p /var/lib/hadoop-hdfs/cache/hdfs/dfs/name
sudo chown -R sevity:sevity /var/lib/hadoop-hdfs/cache/hdfs/dfs/name

# 아래명령어로 다시 실행
stop-dfs.sh
start-dfs.sh

datanode가 시작되지 않을때

# jps 했는데 datanode관련된게 안보이면
grep -C 5 "ERROR" $HADOOP_HOME/logs/hadoop-*-datanode-*.log
# 아래와 같은 로그면
2023-07-30 16:24:23,819 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: Initialization failed for Block pool <registering> (Datanode Uuid 6b5e2ab5-f8a4-4eb8-b3d3-fe37de728e60) service to localhost/127.0.0.1:9000. Exiting.

# 다음처럼 /hadoop/data 안에 있는 데이터를 날려준다(데이터 포맷은 각오)
rm -rf /hadoop/data/*

부팅할때마다 hdfs 구동되도록 하기(기본적으로는 항상 start-dfs.sh로 시작해줘야함)

sudo vi /etc/systemd/system/hadoop-hdfs.service # 해서 아래내용 입력
[Unit]
Description=Hadoop HDFS
After=network.target

[Service]
User=sevity
Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ExecStart=/usr/local/hadoop/sbin/start-dfs.sh
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

# 그 다음 다음을 입력해서 systemctl 리로딩하고, enable을 통해서 부팅시마다 시작되도록 함
sudo systemctl daemon-reload
sudo systemctl enable hadoop-hdfs

# 재부팅해서 jps해서 다음처럼 namenode, datanode, SecondaryNameNode 3가지 띁거 확인
jps
4673 SecondaryNameNode
3929 NameNode
4255 DataNode

# 마지막으로 헬스체크
hdfs fsck /

 

 

 

 

병렬처리 관련 중요 포인트

mapper가 병렬처리되는것으로 보이나 reducer는 모든 mapper가 끝나야 작업을 시작할 수 있나?
네, 맞습니다. MapReduce 모델에서 Mapper와 Reducer의 작업은 서로 다른 단계에 속하며, 이는 병렬 처리와 데이터 의존성 문제 때문입니다.
Mapper: 각 Mapper는 독립적으로 입력 데이터의 한 부분을 처리하며, 그 결과를 키-값 쌍의 형태로 출력합니다. 이 과정은 병렬로 진행될 수 있으며, 여러 컴퓨터 또는 프로세서 코어에서 동시에 일어날 수 있습니다.
Shuffle and Sort: Mapper의 출력은 모든 Reducer에게 보내지기 전에 "Shuffle and Sort" 단계를 거칩니다. 이 단계에서는 동일한 키를 가진 모든 데이터를 같은 Reducer로 라우팅하고, 키에 대한 데이터를 정렬합니다.
Reducer: Reducer는 키별로 그룹화된 데이터를 받아 처리합니다. 이 과정에서 Reducer는 자신에게 할당된 특정 키에 대한 모든 데이터를 받아야만 작업을 시작할 수 있습니다. 이는 각 키에 대한 모든 값들이 Reducer에서 합쳐지거나 처리되어야 하기 때문입니다. 따라서 Reducer는 모든 Mapper의 작업이 끝나야만 시작됩니다.
이와 같은 방식은 분산 환경에서 대규모 데이터를 처리하는 데 효율적인 방법을 제공합니다. Mapper는 독립적으로 실행되므로 컴퓨터 클러스터의 모든 머신을 활용할 수 있습니다. Reducer는 특정 키에 대한 모든 데이터를 처리하므로, 결과를 재결합하거나 요약하는 작업을 처리할 수 있습니다. 이 모델의 중요한 특성 중 하나는 개발자가 병렬 처리에 대한 세부 사항을 걱정할 필요가 없다는 것입니다. 이는 MapReduce 프레임워크가 처리하기 때문입니다.

반응형

초기설치

airflow는 python기반입니다.

python설치후 아래 명령으로 설치 및 초기화

pip install apache-airflow
airflow db init

위는 sqlite기반으로 설치되며 병렬처리에 한계가 있어서 postgres에 설치하는 방법이 따로 있음(아래 더보기 참조)

더보기

1. postgres에 설치하기 위해서는 airflow를 설치할때 아래 명령사용

pip install 'apache-airflow[postgres]'


2. PostgreSQL 설치 및 설정: PostgreSQL이 설치되어 있지 않다면 먼저 설치해야 합니다. 
Airflow에서 사용할 데이터베이스와 사용자를 생성해야 합니다.
PostgreSQL에서 다음과 같은 명령어를 실행하여 데이터베이스와 사용자를 생성할 수 있습니다.

CREATE DATABASE airflow_db;
CREATE USER airflow_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE airflow_db TO airflow_user;

3. airflow.cfg 수정: 이제 airflow.cfg 파일을 열어서 sql_alchemy_conn 설정을 수정 합니다.

sql_alchemy_conn = postgresql+psycopg2://airflow_user:your_password@localhost/airflow_db

다음 커멘드를 실행하여 웹서버및 스케줄러 띄움

airflow webserver -p 8080
airflow scheduler

단 위의 각 라인이 실행후 블럭되는 형태라, supervisorctl등으로 데몬으로 띄우는게 좋음(아래 더보기 참조)

더보기

1. Supervisor 설치: 먼저 Supervisor를 설치해야 합니다. Ubuntu에서는 다음 명령어로 설치할 수 있습니다.

sudo apt-get install supervisor

2. Supervisor 설정 파일 작성: Supervisor는 설정 파일을 사용하여 관리할 프로세스를 정의합니다. 각 프로세스는 설정 파일의 [program] 섹션에 정의됩니다. Airflow 웹 서버와 스케줄러를 위한 설정 파일을 작성해야 합니다. 예를 들어, /etc/supervisor/conf.d/airflow-webserver.conf 파일을 다음과 같이 작성할 수 있습니다.

[program:airflow-webserver]
command=/home/sevity/.local/bin/airflow webserver -p 8081
user=sevity
autostart=true
autorestart=true
redirect_stderr=true
environment=PATH="/home/sevity/.local/bin:%(ENV_PATH)s"

3. 이와 유사하게, /etc/supervisor/conf.d/airflow-scheduler.conf 파일을 다음과 같이 작성할 수 있습니다.

[program:airflow-scheduler]
command=bash -c "trap '/path/to/kill_gunicorn.sh' EXIT; /home/sevity/.local/bin/airflow scheduler"
user=sevity
autostart=true
autorestart=true
redirect_stderr=true
environment=PATH="/home/sevity/.local/bin:%(ENV_PATH)s"

kill_gunicorn.sh의 내용은 아래와 같으며, airflow를 죽여도 gunicorn이 남는 것을 같이 종료해주는 코드다.

#!/bin/bash
PIDS=$(pgrep -f gunicorn)
for PID in $PIDS
do
    PARENT_PID=$(ps -o ppid= -p $PID)
    kill -9 $PID $PARENT_PID
done

 

 

http://server_ip:8080으로 접속하면 로그인 창이 뜨는데, 로그인을 위해 리눅스 콘솔에서 아래명령어 수행

airflow users create \
    --username your_username \
    --firstname your_firstname \
    --lastname your_lastname \
    --role Admin \
    --email your_email@example.com

 

dag파일 샘플

sevity@sevityubuntu:~$ cat ~/airflow/dags/deploy_online_judge_services.py
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
from datetime import datetime, timedelta
from pendulum import timezone

KST = timezone("Asia/Seoul")

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2023, 7, 28, tzinfo=KST),
    'email': ['your-email@example.com'],
    'email_on_failure': False,
    'email_on_retry': False,
    'retries': 0,
    'retry_delay': timedelta(minutes=5),
}

dag = DAG(
    'build_online-judge_services', default_args=default_args, schedule_interval="0 22 * * *")

# 이전에 stop 명령 실행
t0 = BashOperator(
    task_id='stop_services',
    bash_command='sudo supervisorctl stop online-judge_auth-service online-judge_frontend-service',
    dag=dag
)

t1 = BashOperator(
    task_id='build_auth_service',
    bash_command='cd ~/workspace/online_judge/auth-service && ./mvnw clean install && docker build -t auth-service .',
    dag=dag)

t2 = BashOperator(
    task_id='build_frontend_service',
    bash_command="""
        export NVM_DIR="/home/sevity/.nvm"
        [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
        nvm use 20.5.0
        cd ~/workspace/online_judge/frontend-service && npm install && npm run build
        docker build -t frontend-service .
    """,
    dag=dag
)

# 이후에 start 명령 실행
t3 = BashOperator(
    task_id='start_services',
    bash_command='sudo supervisorctl start online-judge_auth-service online-judge_frontend-service',
    dag=dag
)


t0 >> t1 >> t3
t0 >> t2 >> t3

마지막에 dag에서 선후관계를 설정해주는건데, 단순 선형관계 말고 아래처럼 다이아몬드처럼 할때는 위처럼 하면된다.

중간에 실패한 task가 있을때 그거 부터 시작하려고 하면 clear라는 명령을 주면 된다.

Future옵션을 끄면 후속작업을 안하게 할 수도 있다.

 

트러블슈팅

 

supervisor와 연동하기

airflow webser는 airflow프로세스를 죽여도 gunicorn관련 프로세스가 남고, 

airflow scheduler는 executor, worker 등이 남는다. 관련해서 아래처럼 kill script를 만들고 연동해줬다.

# kill_webserver.sh
#!/bin/bash

# Gunicorn 프로세스 종료
PIDS=$(pgrep -f gunicorn)
for PID in $PIDS
do
    PARENT_PID=$(ps -o ppid= -p $PID)
    kill -9 $PID $PARENT_PID
done


# kill_scheduler.sh
#!/bin/bash

# airflow scheduler 프로세스 종료
AIRFLOW_SCHEDULER_PIDS=$(pgrep -f "airflow scheduler")
for PID in $AIRFLOW_SCHEDULER_PIDS
do
    PARENT_PID=$(ps -o ppid= -p $PID)
    kill -9 $PID $PARENT_PID
done

# airflow executor 프로세스 종료
PIDS=$(pgrep -f "airflow executor")
for PID in $PIDS
do
    PARENT_PID=$(ps -o ppid= -p $PID)
    kill -9 $PID $PARENT_PID
done

# airflow worker 프로세스 종료
PIDS=$(pgrep -f "airflow worker")
for PID in $PIDS
do
    PARENT_PID=$(ps -o ppid= -p $PID)
    kill -9 $PID $PARENT_PID
done

supervisor conf.d에 다음처럼 연동해준다.

# cat /etc/supervisor/conf.d/airflow-webserver.conf
[program:airflow-webserver]
command=bash -c "trap '/home/sevity/airflow/kill_webserver.sh' EXIT; /home/sevity/.local/bin/airflow webserver -p 8081"
user=sevity
stopasgroup=true
killasgroup=true
autostart=true
autorestart=true
redirect_stderr=true
environment=PATH="/home/sevity/.local/bin:%(ENV_PATH)s"


# cat /etc/supervisor/conf.d/airflow-scheduler.conf
[program:airflow-scheduler]
command=bash -c "trap '/home/sevity/airflow/kill_scheduler.sh' EXIT; /home/sevity/.local/bin/airflow scheduler"
user=sevity
stopasgroup=true
killasgroup=true
autostart=true
autorestart=true
redirect_stderr=true
environment=PATH="/home/sevity/.local/bin:%(ENV_PATH)s"
반응형

여기를 참조했다.

아래 코드에서 find_intersection 함수를 라이브러리로 활용하자.

이 문제의 정답이기도 하다.

#include <bits/stdc++.h>
#define int long long
using namespace std;
#define REP(i,n) for(int i=1;i<=(int)(n);i++)

struct point { double x, y; 
    bool operator==(const point& other) const {return x == other.x && y == other.y;}
    bool operator<=(const point& other) const {return y < other.y || (y == other.y && x <= other.x);}
    bool operator>(const point& other) const {return y > other.y || (y == other.y && x > other.x);}
};
double CCW(point A, point B, point C, bool sign_only=true) {
    double r = (B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y);
    if (sign_only == false) return r;
    if (r == 0)return 0;
    return r > 0 ? 1 : -1;
}
struct line { point s, e; };
//touch_ok가 false이면, 두 선분이 교차하지 않고 만나기만 하는 경우에는 false를 리턴
bool Intersect(line x, line y, bool touch_ok=false) {
    point a = x.s, b = x.e;
    point c = y.s, d = y.e;
    double ab = CCW(a, b, c) * CCW(a, b, d);
    double cd = CCW(c, d, a) * CCW(c, d, b);
    if (ab == 0 && cd == 0) { // 이건 두 선분이 평행한 경우
        pair<double, double> aa = { a.x, a.y }, bb = { b.x,b.y }, 
            cc = { c.x, c.y }, dd = { d.x,d.y };
        if (aa > bb)swap(aa, bb);
        if (cc > dd)swap(cc, dd);
        if(touch_ok) return cc <= bb && aa <= dd; // 0이면 점끼리 만나는 것
        return cc < bb && aa < dd; // a<d이면서 b,c가 교차하면 선분
    }
    if(touch_ok) return ab <= 0 && cd <= 0; // 0이면 두 선분이 한점에서 만나는 것
    return ab < 0 && cd < 0; // 이게 기본. 각선분에서 나머지 2개점 방향이 달라야 교차
}

bool find_intersection(line l1, line l2, point& out) // 교점 구하기
{
    point A = l1.s, B=l1.e, C=l2.s, D=l2.e;
	if (A > B) swap(A, B);
	if (C > D) swap(C, D);
	double px = (A.x * B.y - A.y * B.x) * (C.x - D.x) - (A.x - B.x) * (C.x * D.y - C.y * D.x);
	double py = (A.x * B.y - A.y * B.x) * (C.y - D.y) - (A.y - B.y) * (C.x * D.y - C.y * D.x);
	double p = (A.x - B.x) * (C.y - D.y) - (A.y - B.y) * (C.x - D.x);

    bool found = false;
	if (p == 0) // 평행할 때
	{
		// 교점이 하나일 때
		if (B == C && A <= C) found=true, out = B;
		else if (A == D && C <= A) found=true, out = A;
	}
	else // 교차할 때
	{
		double x = px / p;
		double y = py / p;
        out = {x,y};
        found=true;
	}
    return found;
}


int32_t main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    vector<line> l;
    double a, b, c, d; REP(i, 2)cin >> a >> b >> c >> d, l.push_back({ a,b,c,d });
    if(Intersect(l[0], l[1], true)==false) puts("0");
    else{
        puts("1");
        point intercection;
        bool found = find_intersection(l[0], l[1], intercection);
        if(found) printf("%.16lf %.16lf", intercection.x, intercection.y);
    }
    return 0;
}
반응형

ubuntu 기준으로는 sudo apt install docker.io로 설치가능하나, 라즈베리파이 기준으로는 너무 오래된 docker가 설치되거나 설치가 안될수 있어서 다음 방법으로 설치하는걸 추천한다고 함

curl -sSL https://get.docker.com | sh

만약 sudo apt-get install로 하려면 다음과정을 거쳐도 됨

# 또는 아래 방법도 가능
#1. 먼저, 기존에 설치되어 있는 Docker 패키지를 제거합니다(있는 경우):
sudo apt-get remove docker docker-engine docker.io containerd runc
#2. Docker설치를 위한 패키지 업데이트
sudo apt-get update
#3. Dockertㅓㄹ치에 필요한 패키지들 미리 설치
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
#4. Docker의 공식 GPG추가(Docker에서 제공했고 신뢰할수 있다는 디지털 서명)
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -
#5. Docker repository를 APT sources에 추가합니다(APT:패키지관리자 가 인식할수있도록 저장소추가)
echo "deb [arch=armhf] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
    $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
#6. 다시 패키지를 업데이트한 후 Docker를 설치합니다: (이제 apt-get사용가능)
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

설치후 잘 설치됐는지 확인하고 특정 사용자에게 docker그룹추가를 통한 일종의 docker sudo권한 부여

# 잘 설치되었는지 확인
sudo docker images 
sudo docker run hello-world
# sevity사용자를 Docker그룹에추가(해당사용자가 root가 아니어도 docker명령을 다룰수 있게 해줌)
sudo usermod -aG docker sevity

 

docker images해보면 현재 서버에 있는 도커이미지들 목록이 나온다. (일종의 git으로 치면 로컬 저장소개념으로 원격서버에 저장된건 아님)

$ docker images
REPOSITORY                  TAG       IMAGE ID       CREATED        SIZE
auth-service_auth-service   latest    cf72ade83f7f   7 hours ago    455MB
postgres                    13        9ca11a5f9994   12 days ago    350MB
hello-world                 latest    38d49488e3b0   2 months ago   4.85kB

특정 image를 지우려면 docker rmi 사용가능. 예를들어 docker rmi  9ca11a5f9994

 

Dockerfile을 만들고 아래처럼  build하면 된다.(맨뒤 .은 생략불가하면 Dockerfile 위치를 나타낸다)

docker build -t auth-service .

Dockerfile에 대한 샘플은 여기참조

 

실행은 다음처럼 docker run으로 하면 된다.

docker run auth-service

환경변수 설정이 필요한 경우 다음처럼 -e를 쓰면됨

docker run -e "ENV_VAR_NAME1=value1" -e "ENV_VAR_NAME2=value2" my-image

하지만 커멘드라인이 길어지니 docker-compose.yml에 다음처럼 기록가능

version: '3.8'
services:
  auth-service:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL
      - DATABASE_USERNAME
      - DATABASE_PASSWORD

이경우 docker run이 아닌 docker-compose 를 쓰면 된다.

docker-compose up을 하면 docker build와 docker run을 모두 수행하며,

docker-compose build를 하면 docker build까지만 된다.

docker-compose를 쓰면 여러 도커이미지를 동시에 켜거나 끄는등의 동시 핸들링에도 용이하다.

docker-compose up --build를 해서 --build 옵션을 주면 실행전 무조건 빌드를 거치게 된다.

 

AWS와 연동하기

Elastic Container Service (ECS)는 Docker 컨테이너를 쉽게 배포, 실행 및 스케일링할 수 있게 해주는 완전관리형 컨테이너 오케스트레이션 서비스입니다.이걸 사용해서 연동해보겠습니다.

ECS는 아마존 EC2 인스턴스나 AWS Fargate를 사용하여 실행할 수 있습니다.

 

AWS Elastic Container Service(ECS)를 사용해 애플리케이션을 배포하려면 다음과 같은 단계를 따릅니다.

1. AWS 계정 생성: AWS에서 제공하는 서비스를 이용하려면 먼저 AWS 계정이 필요합니다. 계정이 없다면 AWS 웹사이트에서 생성할 수 있습니다.

2. Docker 이미지를 Docker Hub에 푸시: 먼저, 로컬에서 Docker 이미지를 생성한 후 이를 Docker Hub에 푸시합니다.

# 로컬에서 Docker 이미지를 빌드합니다. 
docker build -t online-judge-auth-service .

# Docker Hub에 로그인합니다.
docker login --username=your-username --password=your-password

# Docker Hub에 이미지를 푸시합니다.
docker tag online-judge-auth-service:latest your-username/online-judge-auth-service:latest
docker push your-username/online-judge-auth-service:latest


3. ECS 클러스터 생성: AWS Management Console에 로그인한 후, ECS 페이지로 이동해 새 클러스터를 생성합니다. 

4. Task Definition 생성: ECS 클러스터 내에 'Task Definitions' 페이지로 이동해 새로운 Task Definition을 생성합니다. 이 과정에서 Docker 이미지의 위치(Docker Hub의 URL), 컨테이너에 필요한 CPU와 메모리, 포트 매핑 등을 설정합니다.
* 이 과정이 좀 중복적으로 느껴지고 직관적이지 않았지만 필요했다.

5. ECS Service 생성: 생성한 Task Definition을 이용해 ECS Service를 생성합니다. 이 과정에서 원하는 수의 Task를 실행하도록 설정하고, 필요한 경우 로드 밸런서를 설정합니다.

* 이번 프로젝트에서는 로드밸런서는 사용하지 않고 그냥 EC2인스턴스로 실행

6. 보안 그룹 설정 확인: 생성한 ECS Service가 외부에서 접근 가능하도록, 해당 서비스가 사용하는 보안 그룹의 인바운드 규칙을 확인합니다. 필요한 경우 포트를 열어줍니다.
* 보안규칙을 통해 8080포트를 열어줘야 했다.

7. 서비스 확인: ECS Service가 성공적으로 시작되면, Task의 IP 주소나 연결된 로드 밸런서의 DNS 이름을 이용해 애플리케이션에 접근해봅니다.

* curl로 접근해보니 잘 되었다.

이러한 단계들은 AWS Management Console을 통해 수행할 수 있지만, AWS CLI나 SDK를 이용해 스크립트로 자동화할 수도 있습니다. 비록 초기 설정에는 약간의 시간이 소요될 수 있지만, 한 번 환경을 구성하고 나면 새로운 버전의 애플리케이션을 쉽게 배포할 수 있습니다.

 

이해하기 따라로웠던개념들

1. VPC: 이건 가상서버라는 개념이 아니라 가상랜환경이란 의미에 가까웠다. 랜환경안에 여러 호스트가 있을 수 있는..

2. 서브넷개념: 서브넷을 여러개 분리해서 둘 수 있었는데 물리적으로 다른 위치로 구성할 수 있었다. 서브넷은 쉽게 말해서 공유기나 스위치로 묶이는 작은 단위라고 볼 수 있다.

3. 인스턴스 설정후 키페어 설정후 다음 커멘드로 ssh접속이 가능했다.

ssh -i /path/my-key-pair.pem ec2-user@my-instance-public-dns-name

4. ssh로 들어가서 도커를 수동으로 시작하려면 다음명령어를 사용

sudo docker run -p 8080:8080 -e DATABASE_URL='jdbc:postgresql://sevity.com:5432/online_judge' -e DATABASE_USERNAME='online_judge_admin' -e DATABASE_PASSWORD='password_here' ee6ac0195619

 

Docker Image관련

docker images: 생성된 image나열하기

sevity@sevityubuntu:~/workspace/online_judge/problem-frontend$ docker images
REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
frontend-service   latest    34ae6e43cd4e   4 hours ago     1.92GB
problem-service    latest    a360d436d839   4 hours ago     517MB
auth-service       latest    befdc9e37ac1   4 hours ago     526MB
<none>             <none>    96430f927c57   4 hours ago     526MB
<none>             <none>    58e82ecb1610   11 hours ago    1.92GB
<none>             <none>    f9bb20adb713   35 hours ago    1.92GB
<none>             <none>    de59f27bcff9   2 days ago      1.92GB
<none>             <none>    c4cf8ec43872   3 days ago      1.92GB
<none>             <none>    25e3f42a4b64   4 days ago      1.92GB

docker system prune: 불필요한 이미지, 컨테이너, 볼륨 및 네트워크를 일괄 삭제

위의 <none>처럼 누적되는 image들 정리가능(Docker 이미지 빌드 과정에서 새로운 이미지 태그를 생성할 때마다 새로운 이미지 ID가 생성되는데, 만약 이전 이미지 태그를 유지하지 않고 새로운 태그를 계속 생성한다면, <none> 태그와 함께 이전 버전의 이미지가 누적될 수 있다. 이러한 누적은 디스크 공간을 차지하므로, 불필요한 이미지를 정리하는 것이 좋다.)

sevity@sevityubuntu:~/workspace/online_judge/problem-frontend$ docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N] y
Deleted Containers:
7e93e0f073dccae0ea2ec28b7051138a38fb8137feb8bd1b9e9c5d9df1a084ab
c18f484efede43192c18b4987096409454ac358a6ca802c93061ad233d3a214a

Deleted Networks:
auth-service_default
my-network
problem-service_default

Deleted Images:
deleted: sha256:2d86f8a15708197b0dcdb54d2461d060b289ac122e1f49d27e79ada0b34f01b2
deleted: sha256:52c9722d7ab889e55af2f0f8d1540f5b218a6e40df5b5fa331bcb1a08ac6aa5b
deleted: sha256:d6d5fafa7731073ac0bf4e2d731d04dc413fae64a39de29255e6b530fada5f97
deleted: sha256:708bb9cc93ee48ba372c9d329441b1735717e09516670cf08dbe2518f02261d5

Deleted build cache objects:
i8eaq1edr2a8koj2n1o043xva
wpr37m52re2lf3gmkndy630t6

Total reclaimed space: 31.52GB

 

docker rmi: 특정 image 삭제

 

 

모든 image삭제하기(아래 과정을 거쳐야 의존성 문제가 해결되면서 다 지워진다)

# 모든 컨테이너 정지:
docker stop $(docker ps -aq)
# 모든 컨테이너 삭제:
docker rm $(docker ps -aq)
# 모든 이미지 삭제:
docker image prune -a

 

Docker Container 관련

Docker Container는 현재 실행 중인 Docker image를 의미하며

docker ps를 하면 컨테이너의 목록을 나열한다(어떤 포트를 listen하고 있는지도 알 수 있다)

$ docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED       STATUS       PORTS                                       NAMES
7e93e0f073dc   problem-service    "java -jar /app/app.…"   3 hours ago   Up 3 hours   0.0.0.0:9993->9993/tcp, :::9993->9993/tcp   cranky_yonath
b2fb854a135c   frontend-service   "docker-entrypoint.s…"   3 hours ago   Up 3 hours                                               frontend-service
b4bf33f71cf4   auth-service       "java -jar /app/app.…"   3 hours ago   Up 3 hours                                               affectionate_feynman4

docker ps --filter "expose=9993"를 하면 특정 포트만 필터링해서 볼 수도 있다.

$ docker ps --filter "expose=9993"
CONTAINER ID   IMAGE             COMMAND                  CREATED       STATUS       PORTS                                       NAMES
7e93e0f073dc   problem-service   "java -jar /app/app.…"   3 hours ago   Up 3 hours   0.0.0.0:9993->9993/tcp, :::9993->9993/tcp   cranky_yonath

docker stop을 통해 실행중인 docker container를 종료할 수 있다.

주의할점은 위의 docker ps결과에 나온 컬럼기준으로 IMAGE이름으로는 안되고 CONTAINER_ID또는 맨 오른쪽 컬럼인 NAMES를 인자로 주어야 동작한다는 점이다.

# IMAGE이름으로는 stop이 안된다.
$ docker stop problem-service
Error response from daemon: No such container: problem-service

# CONTAINER_ID나 NAME으로는 가능
$ docker stop 7e93e0f073dc
7e93e0f073dc

만약 IMAGE이름으로 stop하고 싶다면 다음처럼 해준다.

# problem-service라는 이름의 IMAGE로 부터 생성된 container 모두를 stop한다.
$ docker ps -q --filter ancestor=problem-service | xargs docker stop
7e93e0f073dc

 

docker stop을 했다고 해도 지워진건 아니며 docker rm을 통해 지워줘야 한다.

stop된 컨테이너를 포함해서 조회하려면 docker ps -a를 해주면 된다.

 

 

 

 

 

 

 

 

 

 

 

 

반응형

'Programming > Linux' 카테고리의 다른 글

하둡 - 실습 - 웹서버로그분석  (0) 2023.07.29
하둡(Hadoop)  (0) 2023.07.29
vimdiff  (0) 2021.04.07
X Window System(X11) - 여러 호스트에서 ssh로 붙어서 사용하기  (0) 2021.01.08
Ansible  (1) 2020.10.22

+ Recent posts