icon

안동민 개발노트

14장 : 실무에서 만나는 OS 개념

리눅스 필수 명령어


서버 개발자에게 리눅스 명령어는 도구 상자입니다. 서버 장애가 발생했을 때, 성능 문제를 추적할 때, 배포 스크립트를 작성할 때 — 이 명령어들을 자유자재로 사용할 수 있어야 합니다. 이 절에서는 실무에서 가장 자주 사용하는 명령어들을 범주별로 정리합니다.


프로세스 관리

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

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

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

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

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

의미예시
USER프로세스 소유자root, www-data
%CPUCPU 사용률95.3
%MEM메모리 사용률12.4
VSZ가상 메모리 크기 (KB)주소 공간 전체
RSS실제 물리 메모리 (KB)실제 사용량
STAT상태S(sleep), R(run), Z(zombie), D(disk wait)
TIME+누적 CPU 시간03:42:15
프로세스 제어
# 시그널 전송
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)을 사용합니다. SIGKILL은 프로세스에게 정리할 기회를 주지 않으므로 파일이 손상되거나 임시 자원이 남을 수 있습니다.

시그널번호기본 동작잡을 수 있나용도
SIGHUP1종료O데몬 설정 리로드
SIGINT2종료OCtrl+C
SIGKILL9강제 종료X최후의 수단
SIGTERM15종료O정상 종료 요청
SIGSTOP19일시 정지X디버깅
SIGCONT18재개O정지된 프로세스 재개

백그라운드 작업

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을 무시하여 터미널이 닫혀도 프로세스가 살아남습니다. 하지만 실무에서는 tmuxscreen을 사용하는 것이 더 편리합니다.


메모리 확인

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

#              total   used   free   shared  buff/cache  available
# Mem:          16Gi   8.2Gi  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는 OS가 I/O 성능을 위해 사용 중이지만, 필요하면 반환되는 메모리입니다. used가 높아도 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)}GB")
    print(f"사용 가능: {available // (1024**3)}GB")
    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()

디스크와 파일

디스크 사용량
# 파일 시스템 사용량
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은 매우 유용합니다. 파일을 삭제했는데 공간이 회복되지 않는다면, 프로세스가 여전히 삭제된 파일의 파일 디스크립터를 열고 있는 것입니다. 해당 프로세스를 재시작하면 공간이 회복됩니다.

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

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

# 출력 핵심 열:
# %util: 디스크 사용률 (100%에 가까우면 병목)
# await: 평균 I/O 대기 시간 (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로 프로세스가 비정상 종료되면 5초 후 자동 재시작합니다. LimitNOFILE로 파일 디스크립터 제한을 설정합니다.

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

목차