Profile picture

[Shell Script] 리눅스 시스템 정보 수집 스크립트

JaehyoJJAng2024년 05월 06일

▶︎ 스크립트 설명

  • 시스템 기본 정보 수집
    • 호스트명, 작동날짜, 시스템 정보
  • 마지막 리부팅으로부터 며칠째 운영인지 확인
  • 크론탭에 등록된 사용자 작업 점검
  • 커널 정보 확인
  • 네트워크 인터페이스 조회
  • 게이트웨이 정보
  • 프로토콜 상태 정보
  • 파일시스템 사용량 (용량)
  • 파일시스템 사용량 (inode)
  • 파일시스템 테이블 확인
  • 마운트된 디스크 확인
  • CPU 사용량
  • 실메모리(RSS) 사용량 확인
  • 디스크/메모리 IO 확인
  • 해당일 사용자 로그인 정보
  • /var/log/messages에서 error|fail|warn 조회
  • 좀비 프로세스 조회

▶︎ 스크립트 작성

‣ 시스템 기본 정보 수집

  • 호스트명, 작동날짜, 시스템 정보

• OS 지정

  • 시스템 정보 수집 수행에 앞서 현재 시스템의 OS 정보를 파악
#!/usr/bin/bash

HOSTNAME=$(hostname)
UNAME=$(uname)
if [[ "$UNAME" == "Linux" ]]; then
  SYSLOG="/var/log/syslog"
else
  SYSLOG="/var/log/messages"
fi

RHELID="RedHatEnterpriseServer"
UBUNTUID="Ubuntu"
DEBIANID="Debian"
CENTOSID="CentOS"

• 라인 지정

line() 
{ 
 	eval printf %.0s\= '{1..'${COLUMNS:-$(tput cols)}'}'; echo
} 
  • eval
    • 인자로 주어진 문자열을 셸 명령으로 평가. 여기서는 eval을 사용해 뒤에 나오는 명령을 실행함.
  • printf %.0s\=
    • 포맷에 따라 출력을 생성. 여기서 %.0s\=는 빈 문자열(.0s)을 0번 출력하라는 의미. 즉, 포맷 문자열 안에서 %s가 무시됨
    • 대신 =가 출력된다.
  • '{1..'${COLUMNS:-$(tput cols)}'}'
    • 브레이스 확장을 사용해 특정 숫자까지의 범위를 생성
    • $COLUMNS는 현재 터미널의 가로 크기를 나타내는 환경 변수이다. 만약 COLUMNS가 설정되어 있지 않다면 $(tput cols)를 사용해 터미널의 가로 크기를 가져온다.
    • tput cols는 현재 터미널의 가로 크기르 반환하는 명령이다.
    • 즉 1부터 CULUMNS 값까지의 숫자 시퀀스를 생성. 이 숫자 시퀀스는 printf에 의해 각 숫자마다 = 문자를 출력하는데 사용됨.

• 섹션 구분

section()
{ 
	line 
    printf "%*s\n" $(( (${#1}+$(tput cols)) /2 )) "$1" 
	line 
}
  • "%*s\n": 해당 포맷 문자열은 다음을 의미함.
    • %*s: 가변 폭 필드 너비를 사용하여 문자열을 출력. *는 필드 너비를 의미. s는 문자열을 의미
    • \n: 줄바꿈 문자 출력
  • $(( (${#1} + $(tput cols)) /2 )): 필드 너비 계산
    • ${#1}: 함수의 첫 번째 인자의 길이를 구함.
    • $(tput cols): 터미널의 가로 크기 구함.
    • ${#1} + $(tput cols): 인자의 길이와 터미널 가로 크기 더함.
    • (${#1} + $(tput cols)) /2: 이를 2로 나눠서 중앙에 정렬할 위치 계산
  • $1: 함수의 첫 번째 인자를 출력할 문자열로 사용.

• 타이틀 출력

title()
{
    eval printf %.0s\# '{1..'${COLUMNS:-$(tput cols)}'}'; echo    
    echo
    section "LINUX SERVER STATUS CHECK"
    echo -e "#### Author   : jaehyojjang.dev"
    echo -e "Release       : 2024.02.04"
    echo -e "Require       : Root Permission"
    echo
    eval printf %.0s\# '{1..'${COLUMNS:-$(tput cols)}'}'; echo    
}

• 날짜 출력

cur_date()
{ 
	echo `date`
} 

• 시스템 정보 출력

sysinfo()
{  
	echo "HOSTNAME   : " "$HOSTNAME"
	echo "CHECK DATE : " cur_date
	echo "SYSTEM     : " "$UNAME"
} 

‣ 부팅 후 경과시간 출력

pmcheck()
{ 
	UPTIME=`awk '{print int($1)}' /proc/uptime`  
	PMTIME=$((UPTIME / 86400)) 
	line
	echo 'UPTIME SINCE LAST REBOOT : ' ${PMTIME} 'days'
}
  • /proc/uptime 파일에서 부팅 후 경과 시간을 초 단위로 읽어와 UPTIME 변수에 저장.
  • UPTIME 값을 일(day) 단위로 변환하여 PMTIME 변수에 저장.

‣ 크론탭에 등록된 사용자 작업 점검

croninfo()
{ 
	section 'Crontab list'
	echo `for user in $(grep /bin/bash /etc/passwd | cut -f1 -d:); do crontab -u $user -l; done` 
	echo 'tab content check'
}

‣ 커널 정보 출력

kernelchk()
{ 
	line
	echo 'Kernel version : '`uname -o -r -v` 
} 

‣ 네트워크 인터페이스 조회

networkchk()
{  
	section 'Network Interface check' 
 	ifconfig 
}

‣ 게이트웨이 정보

routechk()
{ 
	section 'Default route'
	route
}

‣ 프로토콜 정보

protocolinfo()
{ 
	section 'Protocol statistics'
	 netstat -s
} 

‣ 파일 시스템 사용량 조회

  • 파일시스템 용량, inode, swap 정보 조회
fsinfo()
{ 
	section 'File system information (human readable)' 
	df -h 
	section 'File system information (Inode)' 
	df -i
	section 'Swap information' 
	swapon -s
}  

‣ 파일 시스템 테이블 조회

diskinfo()
{ 
	section "/etc/fstab entry"
	cat /etc/fstab
	section "Mounted disk"
	mount -l
} 

‣ CPU 사용량 조회

cpuusage()
{
    section "CPU Usage"
    index=0

    mpstat -P ALL > ./cpu.txt
    
    while read line; do
        # 컬럼 제거
        if [[ "$index" -gt 1 ]]; then
            echo $line
        fi
        index=$(( $index +1 ))
    done < ./cpu.txt

    section "CPU Idle alert"
    idle_limit=10.0
    cpu_idle=$(mpstat 1 5 | tail -n 1 | awk '{print $NF}')
    is_alert=$(echo "$cpu_idle < $idle_limit" | bc)
    
    if [[ "$is_alert" -eq 1 ]]; then
        date_str=$(date '+%Y/%m/%d %H:%M:%S')
        echo "[$date_str] CPU %idle Alert: $cpu_idle(%)"
    else
        echo "No Alert"
    fi
    rm ./cpu.txt
}
  • is_alert
    • echo "$cpu_idle < $idle_limit": 비교식을 문자열로 만든다.
    • | bc: bc 명령을 사용하여 비교식을 평가함. bc는 수치 비교를 수행하고, 결과를 is_alert 변수에 저장.
    • 결과는 1(참) 또는 0(거짓)으로 출력된다.

‣ 실메모리 사용량 확인

memoryusage()
{  
	section "Memory Usage"
	TOTAL=`free | grep ^Mem | awk '{print $2}'`
	USED1=`free | grep ^Mem | awk '{print $3}'`
	USED2=`free | grep ^-/+ | awk '{print $3}'`
	NOMINAL=$((100*USED1/TOTAL))
	ACTUAL=$((100*USED2/TOTAL))
	echo NOMINAL=${NOMINAL}% ACTUAL=${ACTUAL}% 
}

<br.

‣ 디스크/메모리 IO 확인

iousage()
{ 
	section "IO Stat"

	index=0
	iostat ALL  > ./ios.txt

	while read line; do 
		if [[ "$index" -gt 1 ]]; then  
			echo "$line"
		fi
		index=$(( $index+1 )) 
	done < ./ios.txt
  rm ./ios.txt
} 

‣ 해당일 사용자 로그인 정보

lastchk()
{ 
	section "Last log"
	td=`date +"%a %b %e"`
	echo $td 
	last | grep -i "$td" 
}  

‣ 로그 메시지 점검

  • /var/log/messages에서 error|fail|warn 추출
errorchk()
{  
	section "${SYSLOG} error check"
    "$(cat $SYSLOG | grep -Ei "(fail|error|warn)")"
}

‣ 좀비 프로세스 조회

zombiechk()
{
    section "Zombie process check"

    NOZ="$(ps -ef | grep defunct | gre -v grep | wc -l)"
    echo "Number of Zombie : $NOZ"
    if [[ "$NOZ" -gt 0 ]]; then
        section "Zombie process list"
        ps -ef | grep 'defunct' | grep -v 'grep'
    fi
}

‣ 스크립트 Usage 작성

  • 스크립트의 원활한 사용을 위한 가이드 작성
usage()
{ 
	echo "Usage   : check_sys.sh [--save {filename}  |  --print] " 
	echo "Options : \"--save filename\" will save log to filename"
	echo "          \"--save \" will save log to systemchk_{TODAY}.log"
	echo "          \"--print\" will print log on screen"
	echo "          \"--help\" show this help screen"
}

▶︎ 전체 스크립트

HOSTNAME=`hostname`
UNAME=`uname`  
if [ ${UNAME}  == "Linux" ]
then
	SYSLOG="/var/log/syslog"
else  
	SYSLOG="/var/log/messages" 
fi   

RHELID="RedHatEnterpriseServer"
UBUNTUID="Ubuntu"
DEBIANID="Debian"
CENTOSID="CentOS"

### 라인 지정
line() 
{ 
 	eval printf %.0s\= '{1..'${COLUMNS:-$(tput cols)}'}'; echo
 	#eval printf %.0s\= '{1..'${COLUMNS:-80}'}'; echo    
} 


### 섹션 구분
section()
{ 
	line 
    printf "%*s\n" $(((${#1}+$(tput cols))/2)) "$1" 
	line 
}

### 제목 지정
title()
{
    eval printf %.0s\# '{1..'${COLUMNS:-$(tput cols)}'}'; echo    
    echo
    section "LINUX SERVER STATUS CHECK"
    echo -e "#### Author   : jaehyojjang.dev"
    echo -e "Release       : 2024.02.04"
    echo -e "Require       : Root Permission"
    echo
    eval printf %.0s\# '{1..'${COLUMNS:-$(tput cols)}'}'; echo    
}

### 날짜 추출
cur_date()
{ 
	echo `date`
} 


### 시스템 정보 출력
sysinfo()
{  
	echo "HOSTNAME   : " "$HOSTNAME"
	echo "CHECK DATE : " cur_date
	echo "SYSTEM     : " "$UNAME"
} 

### 부팅 후 경과시간 출력
pmcheck()
{ 
	UPTIME=`awk '{print int($1)}' /proc/uptime`  
	PMTIME=$((UPTIME / 86400)) 
	line
	echo 'UPTIME SINCE LAST REBOOT : ' ${PMTIME} 'days'
}

### 크론탭 작업 목록 점검
croninfo()
{ 
	section 'Crontab list'
	echo `for user in $(grep /bin/bash /etc/passwd | cut -f1 -d:); do crontab -u $user -l; done` 
	echo 'tab content check'
}


### 커널 정보 출력
kernelchk()
{ 
	line
	echo 'Kernel version : '`uname -o -r -v` 
} 

### 네트워크 인터페이스 조회
networkchk()
{  
	section 'Network Interface check' 
 	ifconfig 
}

### 게이트웨이 정보
routechk()
{ 
	section 'Default route'
	route
}

### 프로토콜 정보
protocolinfo()
{ 
	section 'Protocol statistics'
	 netstat -s
} 

### 파일 시스템 사용량 조회
fsinfo()
{ 
	section 'File system information (human readable)' 
	df -h 
	section 'File system information (Inode)' 
	df -i
	section 'Swap information' 
	swapon -s
}  

### 파일 시스템 테이블 조회
diskinfo()
{ 
	section "/etc/fstab entry"
	cat /etc/fstab
	section "Mounted disk"
	mount -l
} 


### 시스템 정보 전체 출력 함수
basicchk()
{     
	sysinfo
	pmcheck 
	kernelchk 
	kdumpchk
	croninfo
	networkchk	
	routechk
	protocolinfo 
	diskinfo
	fsinfo 
} 

### CPU 사용량 조회
cpuusage()
{
    section "CPU Usage"
    index=0

    mpstat -P ALL > ./cpu.txt
    
    while read line; do
        # 컬럼 제거
        if [[ "$index" -gt 1 ]]; then
            echo $line
        fi
        index=$(( $index +1 ))
    done < ./cpu.txt

    section "CPU Idle alert"
    idle_limit=10.0
    cpu_idle=$(mpstat 1 5 | tail -n 1 | awk '{print $NF}')
    is_alert=$(echo "$cpu_idle < $idle_limit" | bc)
    
    if [[ "$is_alert" -eq 1 ]]; then
        date_str=$(date '+%Y/%m/%d %H:%M:%S')
        echo "[$date_str] CPU %idle Alert: $cpu_idle(%)"
    else
        echo "No Alert"
    fi

    rm ./cpu.txt
}

### 실 메모리 사용량 확인
memoryusage()
{  
	section "Memory Usage"
	TOTAL=`free | grep ^Mem | awk '{print $2}'`
	USED1=`free | grep ^Mem | awk '{print $3}'`
	USED2=`free | grep ^-/+ | awk '{print $3}'`
	NOMINAL=$((100*USED1/TOTAL))
	ACTUAL=$((100*USED2/TOTAL))
	echo NOMINAL=${NOMINAL}% ACTUAL=${ACTUAL}% 
}

### 디스크/메모리 IO 확인
iousage()
{ 
	section "IO Stat"

	index=0
	iostat ALL  > ./ios.txt

	while read line; do 
		if [[ "$index" -gt 1 ]]; then  
			echo "$line"
		fi
		index=$(( $index+1 )) 
	done < ./ios.txt
  rm ./ios.txt
} 


### 해당일 사용자 로그인 정보
lastchk()
{ 
	section "Last log"
	td=`date +"%a %b %e"`
	echo $td
	last | grep -i "$td"
}

### 로그 메시지 점검
errorchk()
{  
	section "${SYSLOG} error check"
    "$(cat $SYSLOG | grep -Ei "(fail|error|warn)")"
}

### 좀비 프로세스 체크
zombiechk()
{
    section "Zombie process check"

    NOZ="$(ps -ef | grep defunct | gre -v grep | wc -l)"
    echo "Number of Zombie : $NOZ"
    if [[ "$NOZ" -gt 0 ]]; then
        section "Zombie process list"
        ps -ef | grep 'defunct' | grep -v 'grep'
    fi
}

### 사용량 전부 출력 함수
usagechk()
{ 
	cpuusage
	memoryusage
	iousage 
	vmstatchk 
	lastchk 
	zombiechk
	errorchk 
} 

### USAGE
usage()
{ 
	echo "Usage   : check_sys.sh [--save {filename}  |  --print] " 
	echo "Options : \"--save filename\" will save log to filename"
	echo "          \"--save \" will save log to systemchk_{TODAY}.log"
	echo "          \"--print\" will print log on screen"
	echo "          \"--help\" show this help screen"
}

if [[ "$#" -lt 1 ]]; then
    usage
else
    case $1 in
        "--save" )
            if [[ -z "$2" ]]; then
                filedate="$(date +'%Y%m%d-%H%M%S')"
                file="${HOSTNAME}_$filedate.log"
            elif [[ -f "$2" ]]; then
                usage
                echo "Already exists $2"
            else
                file=$2
            fi

            title >> "$file" 2>&1
            basicchk >> "$file" 2>&1
            usagechk >> "$file" 2>&1
        ;;
        "--print" )
            clear
            title
            basicchk
            usagechk
        ;;
        "--help" )
            usage
            exit
        ;;
        esac
fi

Loading script...