서비스 · 프로세스 관리 40 / 50 · Part 4. 로그·스케줄링·운영
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정 analyst)

1. 들어가며

Part 4의 마지막 글이다. 지금까지 상태 해석(24편), 로그 분석(31편), 포트·소켓(16편), 권한(30편), 재시작 정책(29편)을 따로 배웠다. 실제 장애 현장에서는 이 도구들을 정해진 순서대로 써야 빠르게 원인에 도달한다. 순서 없이 재시작만 반복하면 증거는 사라지고 원인은 그대로 남는다.

이번 글에서는 증상 → 상태 → 로그 → 원인 → 조치 → 검증 6단계 절차를 정리하고, labapp 서비스에 두 가지 장애(포트 충돌, 파일 권한)를 일부러 만들어 이 절차로 해결한다.


2. 핵심 개념

2-1. 6단계 절차

단계질문도구
① 증상지금 어떤 상태인가?systemctl is-active, is-failed
② 상태어떻게 끝났나? (종료 코드·시그널·실행 시간)systemctl status, show -p Result,ExecMainStatus
③ 로그프로그램이 무엇이라고 말했나?journalctl -u UNIT -n 50, -xeu
④ 원인왜 그 오류가 났나?ss -tlnp, ls -l, systemctl cat, id
⑤ 조치원인을 제거설정 수정, 충돌 프로세스 처리, 권한 수정
⑥ 검증정말 해결되었나?is-active, 포트 확인, 실제 요청

2-2. 오류 문구별 원인 후보

로그·상태원인 후보④에서 볼 것
Address already in use (errno 98)포트 충돌ss -tlnp 'sport = :PORT'
Permission denied (errno 13)파일 권한, SELinux, 샌드박스ls -l, User=, ausearch -m AVC, 38편 옵션
No such file or directory경로·WorkingDirectory 오류systemctl cat
status=203/EXEC실행 파일 없음·실행 권한 없음ExecStart 경로
status=217/USER계정 없음getent passwd
Start request repeated too quickly반복 실패로 한도 초과첫 번째 실패 로그
code=killed, status=9/KILLOOM, 타임아웃, 외부 killjournalctl -k, 13·20편

3. 동작 원리

서비스 장애 트러블슈팅 6단계

[장애 1] 포트 충돌
labapp 재시작 → python 이 127.0.0.1:8082 bind 시도 → errno 98 → exit 1
→ Restart=on-failure 로 몇 번 재시도 → 모두 같은 오류 → failed
→ ss 로 8082 주인 확인 → 다른 사용자가 수동으로 띄운 프로세스

[장애 2] 권한 문제
app.py 권한 600 (root 전용) → 서비스는 User=analyst → python 이 파일을 못 읽음 → exit 2
→ ls -l 과 User= 를 비교하면 원인이 바로 보인다

두 장애 모두 systemd 설정은 정상 이다. 상태 화면만 보고 unit 파일을 고치기 시작하면 시간을 낭비한다. 로그의 오류 문구가 방향을 정해 준다.


4. 명령어 실습

# 준비: 서비스를 잠시 중지 (점검 시간 가정)
systemctl stop labapp

# 장애 1 유발: 다른 사용자가 같은 포트를 먼저 점유
cd /tmp; nohup python3 -m http.server 8082 --bind 127.0.0.1 > /dev/null 2>&1 &   # (analyst)

# ① 증상 → ② 상태 → ③ 로그
systemctl restart labapp; sleep 2; systemctl is-active labapp
systemctl status labapp --no-pager | sed -n '1,8p'
journalctl -u labapp --since "-1min" --no-pager -o cat | grep -E 'Error|error|Address' | tail -3

# ④ 원인 → ⑤ 조치 → ⑥ 검증
ss -tlnp 'sport = :8082'
ps -o pid,ppid,user,lstart,cmd -p <점유 PID>
kill <점유 PID>
systemctl reset-failed labapp; systemctl start labapp; systemctl is-active labapp
python3 -c 'import urllib.request as u; print("HTTP", u.urlopen("http://127.0.0.1:8082/").status)'

# 장애 2: 권한
chmod 600 /opt/labapp/app.py; systemctl restart labapp; systemctl is-active labapp
systemctl show labapp -p Result,ExecMainStatus
journalctl -u labapp --since "-30s" --no-pager -o cat | grep -m1 -i 'permission'
ls -l /opt/labapp/app.py; systemctl show labapp -p User --value
chmod 644 /opt/labapp/app.py; systemctl reset-failed labapp; systemctl start labapp

실무에서 ⑤ 조치로 다른 사용자의 프로세스를 종료하기 전에는 소유자와 용도를 먼저 확인 한다. 이번 실습은 원인 프로세스가 실습용 테스트 프로세스라는 것을 알고 있는 상황이다.


5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 준비: 점검 시간에 서비스를 잠시 중지

실제 실행 결과 — Rocky Linux 9.8 · analyst@rocky9-lab — 장애 유발: 누군가 같은 포트를 먼저 점유

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — ① 증상 확인 → ② 상태 → ③ 로그

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — ④ 원인 확인 → ⑤ 조치 → ⑥ 검증

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 두 번째 장애: 권한 문제

텍스트 원본(실제 출력):

[root@rocky9-lab ~]# systemctl stop labapp; systemctl is-active labapp
inactive
[analyst@rocky9-lab ~]$ cd /tmp; nohup python3 -m http.server 8082 --bind 127.0.0.1 > /dev/null 2>&1 &
[analyst@rocky9-lab tmp]$ sleep 1; ss -tln 'sport = :8082' | tail -1
LISTEN        0             5                         127.0.0.1:8082                      0.0.0.0:*
[root@rocky9-lab ~]# systemctl restart labapp; sleep 2; systemctl is-active labapp
failed
[root@rocky9-lab ~]# systemctl status labapp --no-pager | sed -n '1,8p'
× labapp.service - Lab web app (python http.server)
     Loaded: loaded (/etc/systemd/system/labapp.service; enabled; preset: disabled)
    Drop-In: /etc/systemd/system/labapp.service.d
             └─50-sandbox.conf, override.conf
     Active: failed (Result: exit-code) since Thu 2026-09-24 12:33:15 UTC; 825ms ago
   Duration: 96ms
    Process: 6896 ExecStart=/usr/bin/python3 /opt/labapp/app.py (code=exited, status=1/FAILURE)
   Main PID: 6896 (code=exited, status=1/FAILURE)
[root@rocky9-lab ~]# journalctl -u labapp --since "-1min" --no-pager -o cat | grep -E 'Error|error|Address' | tail -3
OSError: [Errno 98] Address already in use
OSError: [Errno 98] Address already in use
OSError: [Errno 98] Address already in use
[root@rocky9-lab ~]# ss -tlnp 'sport = :8082'
State    Recv-Q   Send-Q     Local Address:Port      Peer Address:Port   Process
LISTEN   0        5              127.0.0.1:8082           0.0.0.0:*       users:(("python3",pid=6844,fd=3))
[root@rocky9-lab ~]# ps -o pid,ppid,user,lstart,cmd -p $(ss -tlnpH 'sport = :8082' | grep -oP 'pid=\K[0-9]+')
    PID    PPID USER                      STARTED CMD
   6844       1 analyst  Thu Sep 24 12:33:11 2026 python3 -m http.server 8082 --bind 127.0.0.1
[root@rocky9-lab ~]# kill $(ss -tlnpH 'sport = :8082' | grep -oP 'pid=\K[0-9]+'); sleep 1
[root@rocky9-lab ~]# systemctl reset-failed labapp; systemctl start labapp; sleep 1; systemctl is-active labapp
active
[root@rocky9-lab ~]# ss -tlnp 'sport = :8082'
State    Recv-Q   Send-Q     Local Address:Port      Peer Address:Port   Process
LISTEN   0        5              127.0.0.1:8082           0.0.0.0:*       users:(("python3",pid=6951,fd=3))
[root@rocky9-lab ~]# python3 -c 'import urllib.request as u; print("HTTP", u.urlopen("http://127.0.0.1:8082/").status)'
HTTP 200
[root@rocky9-lab ~]# chmod 600 /opt/labapp/app.py; systemctl restart labapp; sleep 2; systemctl is-active labapp
failed
[root@rocky9-lab ~]# systemctl show labapp -p Result,ExecMainStatus
Result=exit-code
ExecMainStatus=2
[root@rocky9-lab ~]# journalctl -u labapp --since "-30s" --no-pager -o cat | grep -m1 -i 'permission'
/usr/bin/python3: can't open file '/opt/labapp/app.py': [Errno 13] Permission denied
[root@rocky9-lab ~]# ls -l /opt/labapp/app.py; systemctl show labapp -p User --value
-rw------- 1 root root 257 Sep 24 12:10 /opt/labapp/app.py
analyst
[root@rocky9-lab ~]# chmod 644 /opt/labapp/app.py; systemctl reset-failed labapp; systemctl start labapp; systemctl is-active labapp
active

6. 결과 해석

관찰의미
준비: inactive 후 analyst의 python이 127.0.0.1:8082 LISTEN서비스가 멈춘 사이 다른 프로세스가 포트를 차지했다
① failed증상 확인
② Drop-In: 50-sandbox.conf, override.conf28·38편에서 적용한 drop-in이 함께 표시된다. 설정 출처 도 status에서 확인한다
② Duration: 96ms, status=1/FAILURE시작 직후 오류로 종료했다. 203처럼 exec 단계 실패가 아니라 프로그램이 실행된 뒤 실패했다
③ OSError: [Errno 98] Address already in use × 3오류 문구가 원인을 가리킨다. 같은 줄이 여러 번인 것은 Restart=on-failure로 재시도했기 때문이다
④ ss → python3 pid=6844, ps → analyst, PPID 1, python3 -m http.server 8082포트 주인은 서비스가 아니라 사용자가 nohup으로 띄운 프로세스 다 (10편)
⑤ kill → reset-failed → start → active원인 제거 후 실패 기록을 초기화하고 시작했다 (29편)
⑥ 8082 주인이 새 PID 6951, HTTP 200포트 확인과 실제 요청 까지 성공해야 해결이다
장애 2: failed, Result=exit-code, ExecMainStatus=2이번에도 프로그램이 실행된 뒤 실패했다
can't open file '/opt/labapp/app.py': [Errno 13] Permission deniedpython이 스크립트 파일을 읽지 못했다
-rw------- root root vs User=analyst파일 권한과 서비스 실행 계정의 불일치 가 원인이다
chmod 644 후 active권한 복구로 해결

7. 보안 관점

주제내용
포트 선점서비스가 재시작되는 짧은 틈에 다른 프로세스가 포트를 차지하면, 사용자 요청이 엉뚱한 프로세스 로 간다. 1024 이상 포트는 일반 사용자도 열 수 있으므로 서비스 재시작 후 포트 주인을 확인한다
소켓 활성화37편의 socket unit을 쓰면 포트를 PID 1이 계속 쥐고 있어 선점 문제가 생기지 않는다
권한 변경의 흔적서비스 파일의 권한·소유자가 바뀌었다면 누가 바꿨는지 확인한다. 설정 관리 도구, 배포 스크립트, 또는 무단 변경일 수 있다
증거 보존원인 프로세스를 바로 종료하지 말고 ps·/proc 정보를 먼저 기록한다 (05편). 장애 원인이 침해일 수도 있다

8. 보안관제 관점

장애 대응 결과는 보고서 로 남겨야 재발 방지와 보안 판단이 가능하다.

[장애 보고서 템플릿]
  대상        : host / unit / 발생 시각 (journal 기준, UTC)
  증상        : systemctl is-active 결과, 영향 범위
  상태        : Result, ExecMainCode/Status, Duration
  로그 원문   : journalctl -u <unit> 의 핵심 오류 줄
  원인        : 근거 명령과 결과 (ss / ls -l / systemctl cat)
  조치        : 실행한 명령 (그대로)
  검증        : is-active, 포트, 실제 요청 결과
  보안 판단   : 원인이 운영 실수인가, 무단 변경·침해 신호인가
  재발 방지   : 소켓 활성화, 권한 관리, 모니터링 항목 추가
보안 판단 신호추가 확인
원인 프로세스의 소유자가 서비스와 무관한 계정해당 계정 로그인 기록, 프로세스 exe·cmdline
서비스 파일 권한이 예고 없이 바뀜파일 ctime, 같은 시각 sudo 로그
같은 장애가 반복자동화된 원인(스케줄 작업, 39편 경로) 확인

9. 실무에서 자주 발생하는 실수

실수결과예방
로그를 보지 않고 재시작 반복StartLimit에 걸려 더 복잡해짐③ 로그 먼저
status의 마지막 몇 줄만 봄첫 번째 원인 로그를 놓침journalctl -u -n 50 또는 --since
원인 제거 없이 reset-failed같은 장애 반복④ 원인 확정 후 ⑤
is-active만 보고 해결 선언기능은 여전히 장애실제 요청으로 검증
조치 명령을 기록하지 않음보고·재현 불가명령과 결과를 그대로 기록

10. 실습 체크리스트

[ ] 6단계(증상·상태·로그·원인·조치·검증) 절차를 설명할 수 있다
[ ] Address already in use 로그로 포트 충돌을 판단했다
[ ] ss -tlnp 와 ps 로 포트 점유 프로세스의 정체를 확인했다
[ ] Permission denied 로그와 ls -l · User= 비교로 권한 문제를 찾았다
[ ] reset-failed 후 재시작하고 HTTP 요청으로 검증했다
[ ] 장애 보고서 템플릿에 맞춰 결과를 정리할 수 있다

11. 핵심 정리

  • 서비스 장애는 증상 → 상태 → 로그 → 원인 → 조치 → 검증 순서로 좁힌다.
  • status의 종료 코드와 실행 시간으로 exec 단계 실패인지 프로그램 오류인지 먼저 구분한다.
  • 로그의 오류 문구(errno 98, 13 등)가 원인 확인 도구를 정해 준다.
  • 해결 여부는 is-active가 아니라 실제 요청 성공 으로 판단한다.
  • 원인이 무단 변경·낯선 프로세스라면 장애 대응이 곧 보안 조사의 시작이다.

12. 다음 편 예고

Part 5 「보안과 SOC」를 시작하는 「41. 의심 프로세스 식별 기준」 에서는 지금까지 배운 프로세스 정보(exe, cwd, PPID, 사용자, 네트워크, cgroup)를 판단 기준표 로 정리하고, 정상 프로세스와 확인이 필요한 프로세스를 구분하는 점검을 실습한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글