
서비스 · 프로세스 관리 47 / 50 · Part 5. 보안과 SOC
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
관제 대시보드에서 가장 자주 보는 경보 중 하나가 "CPU 사용률 90% 이상"이다. 원인은 대부분 정상 업무(배치 작업, 트래픽 증가)지만, 가끔은 승인되지 않은 계산 작업 이나 장애의 전조다. 중요한 것은 경보가 울렸을 때 몇 분 안에 원인 프로세스와 소속을 특정 하고, 일시적인 피크와 지속적인 이상을 구분하는 것이다.
이번 글에서는 07편(top), 18편(cgroup), 20편(메모리)의 도구를 하나의 절차로 묶는다. 실습용 계산 부하(40초 후 자동 종료)를 만들고, 지표 → 프로세스 → 소속 → 지속 여부 순서로 좁힌 뒤, 두 번 측정해 지속적인 고부하만 보고하는 점검 스크립트를 만든다.
| 지표 | 확인 | 해석 기준 |
|---|---|---|
| load average | /proc/loadavg, uptime | 코어 수(nproc)와 비교 (07편) |
| %Cpu us / sy / wa | top -b -n 2 | us 높음 = 계산, wa 높음 = I/O 대기 |
| 프로세스 %CPU | ps --sort=-%cpu (수명 평균), top (최근 구간) | 코어 1개 = 100% |
| 메모리 RSS | ps --sort=-rss | 실제 물리 메모리 사용량 |
| 소속 | /proc/PID/cgroup, systemd-cgtop | 서비스 / 사용자 세션 / 컨테이너 |
| 방법 | 설명 |
|---|---|
| 지속 시간 | 한 번의 측정이 아니라 일정 간격으로 여러 번 임계치를 넘은 경우만 |
| 소속 확인 | 승인된 배치 서비스(cgroup)라면 정상 가능성 높음 |
| 시간대 | 예정된 작업 시간과 비교 |
| 기준선 | 서버별 평소 사용률 대비 변화 (42편) |
ps의 %CPU는 프로세스 수명 전체의 평균 이고 top은 최근 갱신 구간 의 값이다(07편). 방금 시작한 프로세스는 두 값이 비슷하지만, 오래된 프로세스는 크게 다를 수 있다. 지속 여부를 보려면 짧은 간격으로 두 번 이상 측정한다.

[경보] CPU 사용률 임계치 초과
① nproc · loadavg · top -b -n 2 → 코어 대비 부하, us/wa 구분
② ps --sort=-%cpu / --sort=-rss → 상위 프로세스
③ readlink /proc/PID/exe · cgroup → 실행 파일 · 서비스인가 세션인가
④ 5초 후 재측정 → 두 번 모두 임계치 이상이면 ALERT
→ 41편 기준 판단 → 필요 시 43편 증거 수집
# 1) (analyst) 실습용 부하: CPU 계산 루프와 400MB 메모리 — 모두 40초 후 자동 종료
nohup timeout 40 bash -c 'exec -a lab-compute sh -c "while :; do :; done"' > /dev/null 2>&1 &
nohup timeout 40 python3 -c 'b=bytearray(400*1024*1024); import time; time.sleep(40)' > /dev/null 2>&1 &
# 2) (root) 지표 → 프로세스 → 소속
nproc; cat /proc/loadavg
top -b -n 2 -d 2 -w 110 | grep '^%Cpu' | tail -1
ps -eo pid,user,%cpu,rss,etimes,comm --sort=-%cpu | head -4
ps -eo pid,user,%cpu,rss,etimes,comm --sort=-rss | head -3
for p in $(ps -eo pid= --sort=-%cpu | head -2); do
printf '%-6s %-30s %s\n' $p "$(readlink /proc/$p/exe)" "$(tail -1 /proc/$p/cgroup | sed 's#.*/##')"; done
# 3) 지속 여부를 보는 점검 스크립트 /root/cpu-watch.sh LIMIT
# 5초 간격 두 번 측정 → 두 번 모두 LIMIT% 이상인 PID 만 ALERT (exe · unit · cmd 포함)
/root/cpu-watch.sh 80
실습 부하는
timeout으로 반드시 종료 시간을 둔다. 운영 서버에서 부하 테스트를 할 때는 사전 승인과 시간대 조정이 필요하다.



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ nohup timeout 40 bash -c 'exec -a lab-compute sh -c "while :; do :; done"' > /dev/null 2>&1 &
[analyst@rocky9-lab ~]$ nohup timeout 40 python3 -c 'b=bytearray(400*1024*1024); import time; time.sleep(40)' > /dev/null 2>&1 &
[analyst@rocky9-lab ~]$ sleep 5; echo started
started
[root@rocky9-lab ~]# nproc; cat /proc/loadavg
2
0.65 0.25 0.08 2/191 2581
[root@rocky9-lab ~]# top -b -n 2 -d 2 -w 110 | grep -A0 '^%Cpu' | tail -1
%Cpu(s): 42.2 us, 8.2 sy, 0.0 ni, 49.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.5 st
[root@rocky9-lab ~]# ps -eo pid,user,%cpu,rss,etimes,comm --sort=-%cpu | head -4
PID USER %CPU RSS ELAPSED COMMAND
2516 analyst 103 3808 7 sh
2515 analyst 3.1 416824 7 python3
1 root 0.5 14460 254 systemd
[root@rocky9-lab ~]# ps -eo pid,user,%cpu,rss,etimes,comm --sort=-rss | head -3
PID USER %CPU RSS ELAPSED COMMAND
2515 analyst 3.1 416824 7 python3
31 analyst 0.0 17944 254 python3
[root@rocky9-lab ~]# for p in $(ps -eo pid= --sort=-%cpu | head -2); do printf '%-6s %-30s %s\n' $p "$(readlink /proc/$p/exe)" "$(cut -d: -f3 /proc/$p/cgroup | tail -1 | sed 's#.*/##')"; done
2516 /usr/bin/bash session-51.scope
2515 /usr/bin/python3.9 session-51.scope
[root@rocky9-lab ~]# cat > /root/cpu-watch.sh <<'EOF'
> #!/bin/bash
> # 두 번 측정해 두 번 모두 CPU 사용률이 LIMIT% 이상인 프로세스만 보고
> LIMIT=${1:-80}
> a=$(ps -eo pid=,%cpu= --sort=-%cpu | awk -v l=$LIMIT '$2>=l{print $1}'); sleep 5
> for p in $(ps -eo pid=,%cpu= --sort=-%cpu | awk -v l=$LIMIT '$2>=l{print $1}'); do
> grep -qw $p <<<"$a" || continue
> printf 'ALERT pid=%s user=%s cpu=%s%% exe=%s unit=%s cmd=%s\n' $p "$(ps -o user= -p $p)" "$(ps -o %cpu= -p $p | tr -d ' ')" \
> "$(readlink /proc/$p/exe)" "$(tail -1 /proc/$p/cgroup | sed 's#.*/##')" "$(tr '\0' ' ' < /proc/$p/cmdline | cut -c1-40)"
> done
> EOF
[root@rocky9-lab ~]# chmod 700 /root/cpu-watch.sh; /root/cpu-watch.sh 80
ALERT pid=2516 user=analyst cpu=96.5% exe=/usr/bin/bash unit=session-51.scope cmd=lab-compute -c while :; do :; done
| 관찰 | 의미 |
|---|---|
nproc 2, loadavg 0.65 | 부하를 방금 시작해 1분 평균이 아직 1 미만이다. load는 느리게 반영 되므로 순간 판단에는 %Cpu를 함께 본다 |
%Cpu 42.2 us ... 49.0 id | 코어 2개 중 약 1개가 사용자 계산에 쓰이고 있다 |
%CPU 상위 sh 103 (PID 2516) | 계산 루프. ps의 %CPU는 짧은 수명에서 100%를 조금 넘게 계산되기도 한다. ps에는 exec -a로 바꾼 이름이 아니라 comm인 sh 로 보인다 |
RSS 상위 python3 416824 KB | 400MB 메모리 사용 프로세스. CPU는 거의 쓰지 않는다 — CPU와 메모리 상위는 다를 수 있다 |
exe /usr/bin/bash, /usr/bin/python3.9 | 실행 파일은 정상 경로의 패키지 프로그램이다 |
소속 session-51.scope | 두 프로세스 모두 서비스가 아니라 analyst의 로그인 세션 에서 시작되었다 |
ALERT pid=2516 user=analyst cpu=96.5% exe=/usr/bin/bash unit=session-51.scope cmd=lab-compute -c while ... | 두 번 측정 모두 80% 이상이라 보고되었다. cmdline에는 exec -a로 붙인 이름 lab-compute가 보인다 |
exe(bash)와 cmdline 이름(lab-compute) 불일치 | 이름은 바꿀 수 있지만 exe는 바꿀 수 없다. 판단은 exe 기준 으로 한다 (41편) |
| 주제 | 내용 |
|---|---|
| 승인되지 않은 자원 사용 | 서버 자원을 무단으로 쓰는 계산 작업은 비용·가용성 문제이자 침해의 결과일 수 있다. 소속·실행 파일·시작 세션으로 출처를 확인한다 |
| 가용성 | 고부하 자체가 서비스 장애로 이어진다. 보안 여부와 관계없이 CPUQuota=(18편) 같은 제한이 방어선이 된다 |
| 이름 신뢰 금지 | cmdline·comm은 바뀔 수 있다. exe와 패키지 소유를 기준으로 판단한다 |
| 은폐 가능성 | 낮은 우선순위(nice, 09편)나 간헐적 실행으로 경보를 피하는 경우도 있다. 짧은 순간값보다 추이 와 ni 지표를 함께 본다 |
[경보] 모니터링: web01 CPU 90% 이상 10분 지속
↓
[1분 확인] top -b -n 2 | head -15 > top.txt ; /root/cpu-watch.sh 80
↓
[소속] unit=*.service → 해당 서비스의 예정 작업·배포 기록 확인
unit=session-N.scope → 로그인 기록(journal) · 사용자 확인
↓
[판단] 예정된 정상 작업 → 경보 규칙에 예외(시간대·unit) 추가
설명되지 않음 → 43편 수집 · 44편 네트워크 확인 · 에스컬레이션
↓
[완화] 필요 시 systemctl set-property <unit> CPUQuota=.. --runtime (서비스 영향 최소화)
| 보고서 항목 | 예 |
|---|---|
| 지표 | CPU us 42.2%, 코어 2 |
| 원인 | PID 2516, analyst, /usr/bin/bash, session-51.scope |
| 지속 | 5초 간격 2회 모두 ≥ 80% |
| 판단 근거 | 승인된 배치 목록에 없음 / 사용자 확인 결과 |
| 실수 | 결과 | 예방 |
|---|---|---|
| 순간값 한 번으로 경보 | 일시 피크 오탐 | 두 번 이상 측정, 지속 시간 조건 |
| load만 보고 판단 | 반영 지연·I/O 대기와 혼동 | %Cpu us/wa 함께 |
| 프로세스 이름으로 판단 | 이름 변경에 속음 | exe 기준 |
| CPU 상위만 확인 | 메모리 문제 누락 | RSS 정렬 병행 |
| 원인 프로세스 즉시 종료 | 증거·업무 손실 | 소속·소유자 확인 → 수집 → 조치 |
[ ] nproc · loadavg · %Cpu 로 시스템 지표를 확인했다
[ ] %CPU 와 RSS 상위 프로세스가 다를 수 있음을 확인했다
[ ] exe 와 cgroup 으로 원인 프로세스의 실행 파일과 소속을 확인했다
[ ] 두 번 측정 방식의 점검 스크립트로 지속적인 고부하만 보고했다
[ ] cmdline 이름과 exe 가 다른 것을 확인하고 exe 기준으로 판단했다
[ ] 경보 판단 결과별 조치(예외·수집·완화)를 정리했다
다음 글 「48. auditd로 프로세스 실행 감사」 에서는 "누가 언제 무엇을 실행했는가"를 커널 수준에서 기록하는 auditd 규칙을 설계한다. 로그인 사용자의 실행 기록과 예약 작업·unit 디렉터리 변경 감시 규칙을 적재하고, ausearch·aureport로 조회한다.