안동민 개발노트

본문 시작

리눅스 필수 명령어

프로세스·메모리·디스크·텍스트·네트워크·systemd를 다루는 Linux 명령을 서버 운영 상황별로 익힙니다.

서버 개발자에게 리눅스 명령어는 도구 상자입니다.

서버 장애가 발생했을 때, 성능 문제를 추적할 때, 배포 스크립트를 작성할 때 — 이 명령어들을 자유자재로 사용할 수 있어야 합니다.

이 절에서는 실무에서 가장 자주 사용하는 명령어들을 범주별로 정리합니다.


프로세스 관리

프로세스 확인
# 현재 실행 중인 프로세스 목록
ps aux

# CPU/메모리 사용량 기준 상위 프로세스
ps aux --sort=-%cpu | head -10
ps aux --sort=-%mem | head -10

# 프로세스 트리 (부모-자식 관계)
ps auxf
pstree -p 1234

# 실시간 모니터링
top
htop  # 더 직관적인 인터페이스

ps aux의 출력에서 각 열의 의미를 알아두면 좋습니다.

ps aux의 열을 해석하는 기준

비율의 분모와 메모리 단위를 구별합니다.

ps aux의 열을 해석하는 기준
열읽는 값해석 범위
USER프로세스 소유 사용자실행 권한의 일부 정보이며 모든 권한을 요약하지는 않음
%CPU누적 CPU 시간 ÷ 실행 경과 시간ps의 생애 평균 비율. top의 최근 구간 값과 구분
%MEMRSS ÷ 물리 메모리프로세스의 상주 메모리 비율
VSZ가상 메모리 크기 · KiB예약·매핑된 주소 공간이며 RAM 사용량과 다름
RSS상주 메모리 크기 · KiB공유 페이지도 포함하므로 프로세스별 합계는 중복될 수 있음
STAT실행·대기 등의 상태와 부가 플래그R: 실행 가능, S: interruptible sleep, D: uninterruptible sleep, Z: 좀비
TIME누적 CPU 시간ps aux의 열 이름. 경과 시간이나 top의 TIME+와 혼동하지 않음
USER
읽는 값: 프로세스 소유 사용자
해석 범위: 실행 권한의 일부 정보이며 모든 권한을 요약하지는 않음
%CPU
읽는 값: 누적 CPU 시간 ÷ 실행 경과 시간
해석 범위: ps의 생애 평균 비율. top의 최근 구간 값과 구분
%MEM
읽는 값: RSS ÷ 물리 메모리
해석 범위: 프로세스의 상주 메모리 비율
VSZ
읽는 값: 가상 메모리 크기 · KiB
해석 범위: 예약·매핑된 주소 공간이며 RAM 사용량과 다름
RSS
읽는 값: 상주 메모리 크기 · KiB
해석 범위: 공유 페이지도 포함하므로 프로세스별 합계는 중복될 수 있음
STAT
읽는 값: 실행·대기 등의 상태와 부가 플래그
해석 범위: R: 실행 가능, S: interruptible sleep, D: uninterruptible sleep, Z: 좀비
TIME
읽는 값: 누적 CPU 시간
해석 범위: ps aux의 열 이름. 경과 시간이나 top의 TIME+와 혼동하지 않음

PID·PPID도 함께 볼 때는 ps -o pid,ppid,state,pcpu,pmem,comm을 사용할 수 있습니다. 한 번의 목록은 원인을 확정하는 증거가 아니라 대상을 고르는 단서입니다.

프로세스 제어
# 시그널 전송
kill -15 1234     # SIGTERM: 정상 종료 요청
kill -9 1234      # SIGKILL: 강제 종료 (최후 수단)
kill -1 1234      # SIGHUP: 리로드 처리기를 구현한 데몬에서만 설정 다시 읽기
kill -USR1 1234   # SIGUSR1: 애플리케이션 정의 시그널

# 이름으로 프로세스 종료
pkill -f "python.*worker"
killall nginx

# 프로세스 우선순위 변경
nice -n 10 ./heavy_job        # 낮은 우선순위로 실행
renice -n -5 -p 1234          # 실행 중 프로세스 우선순위 변경

SIGTERM(15)을 먼저 보내고, 응답이 없을 때만 SIGKILL(9)을 사용합니다.

애플리케이션이 중간까지 기록한 파일이나 임시 자원의 상태도 종료 후 확인해야 합니다.

시그널의 기본 동작과 처리 가능 여부

정리 가능한 요청과 잡을 수 없는 제어를 구별합니다.

시그널의 기본 동작과 처리 가능 여부
시그널기본 동작처리기와 용도
SIGHUP종료처리기 등록 가능. 리로드는 앱이 그렇게 구현한 경우
SIGINT종료처리기 등록 가능. 터미널의 Ctrl+C로 흔히 전달
SIGTERM종료처리기 등록 가능. 정상 정리 후 종료를 요청할 때 사용
SIGKILL종료잡거나 무시할 수 없음. 사용자 정리 코드는 실행되지 않음
SIGSTOP정지잡거나 무시할 수 없음
SIGCONT정지 상태에서 재개처리기 등록 가능. 재개 동작과 핸들러 처리를 구분
SIGHUP
기본 동작: 종료
처리기와 용도: 처리기 등록 가능. 리로드는 앱이 그렇게 구현한 경우
SIGINT
기본 동작: 종료
처리기와 용도: 처리기 등록 가능. 터미널의 Ctrl+C로 흔히 전달
SIGTERM
기본 동작: 종료
처리기와 용도: 처리기 등록 가능. 정상 정리 후 종료를 요청할 때 사용
SIGKILL
기본 동작: 종료
처리기와 용도: 잡거나 무시할 수 없음. 사용자 정리 코드는 실행되지 않음
SIGSTOP
기본 동작: 정지
처리기와 용도: 잡거나 무시할 수 없음
SIGCONT
기본 동작: 정지 상태에서 재개
처리기와 용도: 처리기 등록 가능. 재개 동작과 핸들러 처리를 구분

시그널 번호는 아키텍처에 따라 달라질 수 있으므로 이름을 우선 사용합니다. 전송 성공이 정리 완료나 프로세스 종료 완료를 뜻하지는 않습니다.

백그라운드 작업

job_control.sh
# 백그라운드 실행
./long_task.sh &

# 현재 백그라운드 작업 목록
jobs -l

# 포그라운드로 전환
fg %1

# 실행 중인 작업을 백그라운드로
# Ctrl+Z로 일시 정지 후
bg %1

# 터미널 종료 후에도 실행 유지
nohup ./server.sh > output.log 2>&1 &

# 또는 screen/tmux 사용 (권장)
tmux new -s worker
# ... 작업 실행 ...
# Ctrl+B, D 로 detach
tmux attach -t worker

nohup은 SIGHUP을 무시하도록 실행합니다. 다른 종료 신호나 세션 관리자의 정리 정책까지 막는 것은 아닙니다.

하지만 실무에서는 tmux나 screen을 사용하는 것이 더 편리합니다.


메모리 확인

메모리 상태
# 시스템 메모리 요약
free -h

# 예시 값: 현재 procps-ng의 used = total - available
#              total   used   free   shared  buff/cache  available
# Mem:          16Gi   8.7Gi  1.1Gi   256Mi      6.7Gi      7.3Gi
# Swap:          4Gi   0.5Gi  3.5Gi

# 상세 메모리 정보
cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree"

# 가상 메모리 통계 (2초 간격)
vmstat 2

# 특정 프로세스의 메모리 맵
pmap -x 1234

# 공유 메모리 확인
ipcs -m

free -h의 available은 스왑 없이 새 작업에 쓸 수 있는 메모리의 추정치입니다. buff/cache 전체가 즉시 회수되는 것은 아닙니다. 단일 사용량 대신 available의 변화, 회수·스왑 활동과 애플리케이션 지연을 함께 봅니다.

memory_check.py
import subprocess
import re

def check_memory():
    """시스템 메모리 상태를 확인하고 경고"""
    result = subprocess.run(["free", "-b"], capture_output=True, text=True)
    lines = result.stdout.strip().split("\n")
    mem = lines[1].split()

    total = int(mem[1])
    available = int(mem[6])
    usage_pct = (1 - available / total) * 100

    print(f"전체: {total // (1024**3)}GiB")
    print(f"사용 가능: {available // (1024**3)}GiB")
    print(f"사용률: {usage_pct:.1f}%")

    if usage_pct > 90:
        print("[위험] 메모리 부족!")
        # 메모리 많이 쓰는 프로세스 상위 5개
        ps = subprocess.run(
            ["ps", "aux", "--sort=-%mem"],
            capture_output=True, text=True
        )
        for line in ps.stdout.split("\n")[1:6]:
            print(f"  {line}")
    elif usage_pct > 70:
        print("[주의] 메모리 사용률이 높습니다.")

check_memory()

위 파서는 procps-ng의 해당 열 순서와 명령 성공을 전제합니다. 70%·90% 경고는 이 예제의 기준이며 시스템 전체의 장애 판정 기준은 아닙니다.


디스크와 파일

디스크 사용량
# 파일 시스템 사용량
df -h

# inode 사용량 (파일 수 제한)
df -i

# 디렉토리 크기 (깊이 1)
du -h --max-depth=1 /var | sort -rh | head -10

# 큰 파일 찾기
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null

# 최근 24시간 내 수정된 파일
find /var/log -type f -mtime -1

# 파일을 열고 있는 프로세스 확인
lsof /var/log/syslog

# 삭제되었지만 프로세스가 잡고 있는 파일 찾기 (공간 미회복)
lsof +L1

디스크가 가득 찬 상황에서 lsof +L1은 매우 유용합니다.

삭제된 파일을 프로세스가 계속 열어 둔 것은 공간이 회복되지 않는 원인 중 하나입니다. lsof -p PID로 대상 프로세스의 열린 파일을 좁혀 보고, 해당 파일을 유지하는 마지막 참조가 해제되는지 확인합니다.

디스크 I/O 모니터링
# 프로세스별 I/O 사용량
iotop

# 디스크별 I/O 통계 (2초 간격)
iostat -xz 2

# 출력 핵심 열:
# %util: I/O가 진행된 시간 비율 (병렬 SSD/RAID의 포화 지표와는 다름)
# await: 요청의 큐 대기와 처리 시간을 합한 평균 (ms)
# r/s, w/s: 초당 읽기/쓰기 요청 수

텍스트 처리

서버 로그 분석에 필수인 텍스트 처리 명령어입니다.

text_processing.sh
# 패턴 검색
grep -rn "ERROR" /var/log/app/     # 재귀, 줄 번호 포함
grep -c "404" access.log           # 404 문자열이 포함된 줄 수
grep -v "healthcheck" access.log   # healthcheck 제외

# 실시간 로그 모니터링
tail -f /var/log/app/error.log
tail -f /var/log/app/error.log | grep --line-buffered "CRITICAL"

# 정렬과 중복 제거
sort access.log | uniq -c | sort -rn | head -20

# 필드 추출 (awk)
# 접속 IP별 요청 수 (Apache/Nginx 로그)
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

# 특정 시간대의 요청 수
awk '/15:3[0-9]:/' access.log | wc -l

# 문자열 치환 (sed)
sed -i 's/old_domain/new_domain/g' config.conf

# JSON 처리 (jq)
cat response.json | jq '.data[] | {id, name, status}'

집계 결과에서 눈에 띄는 값이 나오면 원본 로그 줄로 돌아가 필드와 문맥을 확인합니다. 반복하는 분석 파이프라인은 스크립트로 남겨 같은 조건으로 비교합니다.


네트워크

네트워크 진단
# 열린 포트와 연결 상태
ss -tlnp        # TCP 리스닝 포트
ss -tanp        # 모든 TCP 연결
ss -s           # 연결 상태 요약 (established, time-wait 수)

# 특정 포트에 연결된 프로세스
ss -tlnp | grep :8080

# 네트워크 인터페이스 정보
ip addr show
ip route show   # 라우팅 테이블

# 연결 테스트
ping -c 4 10.0.0.1
traceroute 10.0.0.1
mtr 10.0.0.1           # ping + traceroute 합체

# DNS 조회
dig example.com
nslookup example.com

# 특정 포트 연결 테스트
nc -zv 10.0.0.1 3306   # MySQL 포트 열려있는지

# 패킷 캡처 (tcpdump)
tcpdump -i eth0 port 80 -c 100
tcpdump -i any host 10.0.0.1 -w capture.pcap

# HTTP 요청 테스트
curl -v https://api.example.com/health
curl -o /dev/null -s -w "%{http_code} %{time_total}s\n" https://api.example.com

ss -s로 TIME_WAIT 소켓 수를 확인합니다.

TIME_WAIT 수가 많으면 연결 생성률·포트 사용량·실패율을 함께 확인합니다. 수만으로 과도함을 판정하지 않으며, 짧은 연결이 실제 병목이라면 Connection Pool이나 Keep-Alive를 검토합니다.

방화벽과 포트 관리

firewall.sh
# iptables 규칙 확인
iptables -L -n -v

# ufw (Ubuntu 방화벽)
ufw status
ufw allow 22/tcp
ufw allow from 10.0.0.0/24 to any port 3306

# firewalld (CentOS/RHEL)
firewall-cmd --list-all
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload

systemd 서비스 관리

systemd.sh
# 서비스 상태 확인
systemctl status nginx

# 서비스 시작/중지/재시작
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx    # 서비스가 제공하는 설정 리로드 동작 요청

# 부팅 시 자동 시작
systemctl enable nginx
systemctl disable nginx

# 서비스 로그 확인
journalctl -u nginx -f
journalctl -u nginx --since "1 hour ago"

# 실패한 서비스 확인
systemctl --failed
/etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server
Restart=on-failure
RestartSec=5
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

Restart=on-failure는 해당 실패 조건에서 재시작을 시도하며 RestartSec=5는 그 대기 시간을 정합니다. 명시적인 stop이나 시작 횟수 제한 등은 별도로 적용됩니다. After=network.target은 순서만 정하며 네트워크 연결 완료를 보장하지 않습니다. 예제의 사용자와 실행 경로도 미리 존재해야 합니다.

LimitNOFILE로 파일 디스크립터 제한을 설정합니다.

다음 절에서는 시스템 콜 추적과 로그 분석 등 심층 디버깅 기법을 다루겠습니다.