목록으로 가기

Linux / OS 기본 정리

프로세스, CPU, 메모리, 디스크, 네트워크, 권한, SSH, 패키지 관리, systemd, 로그, cron, Shell Script를 정리합니다.

Linux / OS 기본 정리 ko posts linux os devops 프로세스, CPU, 메모리, 디스크, 네트워크, 권한, SSH, 패키지 관리, systemd, 로그, cron, Shell Script를 정리합니다.

개요

Linux는 서버 운영과 배포 환경을 이해하기 위한 기본입니다. 장애가 발생하면 결국 프로세스, CPU, 메모리, 디스크, 네트워크, 로그, 서비스 상태를 확인해야 합니다.

이 글은 Linux / OS에서 자주 다시 보게 되는 주제를 하나씩 펼쳐볼 수 있게 정리합니다. 입문자는 각 항목의 개념을, 경험자는 빠르게 명령어와 판단 기준을 확인할 수 있습니다.

1. 프로세스와 스레드 차이

정리 보기

개념

프로세스는 실행 중인 프로그램의 단위입니다. 운영체제는 각 프로세스에 독립된 메모리 공간(코드, 데이터, 힙, 스택)을 할당합니다. 프로세스끼리는 기본적으로 메모리를 공유하지 않기 때문에, 하나가 죽어도 다른 프로세스에 직접 영향을 주지 않습니다.

스레드는 프로세스 안에서 실행되는 작업 흐름입니다. 같은 프로세스의 스레드들은 힙 메모리를 공유합니다. 그래서 스레드 간 통신은 빠르지만, 하나의 스레드가 잘못된 메모리에 접근하면 전체 프로세스가 영향을 받을 수 있습니다.

확인 방법

# 전체 프로세스 목록 (CPU/메모리 순)
# -e: 모든 프로세스, -o: 출력할 컬럼 지정
ps -eo pid,ppid,user,cmd,%cpu,%mem --sort=-%cpu | head -20

# 실시간 모니터링
top
htop  # 스레드까지 보려면 htop에서 H 키

# 특정 프로세스의 스레드 수 확인
# nlwp: Number of Light Weight Processes (스레드 수)
ps -o nlwp,pid,cmd -p <PID>

실무 판단 기준

  • 프로세스가 많은 구조(nginx worker, PHP-FPM): 하나 죽어도 다른 요청에 영향 없음
  • 스레드가 많은 구조(Java, Node.js worker threads): 메모리 효율적이지만 하나의 문제가 전체에 퍼질 수 있음
  • 좀비 프로세스(Z 상태)가 쌓이면 부모 프로세스가 wait()를 제대로 호출하지 않는 것

2. CPU 사용률이 높을 때 확인 순서

정리 보기

1단계: 어떤 프로세스가 CPU를 쓰는지 확인

# -b: batch 모드 (비대화형), -n1: 1회만 실행
top -bn1 | head -20
ps -eo pid,ppid,user,cmd,%cpu,%mem --sort=-%cpu | head -10

2단계: 해당 프로세스가 왜 높은지 좁히기

# 프로세스가 어떤 시스템 콜을 하는지
# -c: 요약 통계, -p: PID 지정
strace -cp <PID>

# Java라면 스레드 덤프
kill -3 <PID>
jstack <PID>

# 몇 개 코어를 쓰는지 (-P ALL: 모든 코어, 1 5: 1초 간격 5회)
mpstat -P ALL 1 5

3단계: 원인 분류

원인 확인 방법
최근 배포 배포 시간과 CPU 상승 시간 비교
트래픽 급증 로드밸런서 메트릭, access log 카운트
무한 루프 strace로 같은 시스템 콜 반복 확인
배치 작업 cron 실행 시간과 겹치는지 확인
외부 연동 지연 I/O wait 높은지 확인 (wa in top)

핵심: CPU가 높다는 사실보다, 어떤 프로세스가 언제부터 왜 높아졌는지 좁혀가는 것이 중요합니다.

3. 메모리 사용량 확인과 OOM

정리 보기

전체 메모리 상태 확인

free -h  # -h: human-readable (GB, MB 단위로 표시)

출력 예시:

              total        used        free      shared  buff/cache   available
Mem:           7.8G        3.2G        512M        128M        4.1G        4.2G
  • used: 실제 사용 중인 메모리
  • buff/cache: 디스크 캐시로 쓰이는 메모리 (필요하면 해제 가능)
  • available: 새 프로세스에 할당 가능한 메모리 (free + 해제 가능한 캐시)

free가 적어도 available이 넉넉하면 문제 아닙니다.

프로세스별 메모리 사용량

ps -eo pid,ppid,user,cmd,%mem,rss --sort=-%mem | head -10
# rss: Resident Set Size (실제 물리 메모리 사용량, KB 단위)

OOM (Out Of Memory)

메모리가 진짜 부족하면 Linux 커널의 OOM Killer가 메모리를 많이 쓰는 프로세스를 강제 종료합니다.

# OOM 발생 여부 확인
dmesg | grep -i "oom"
journalctl -k | grep -i "oom"

# 어떤 프로세스가 죽었는지
dmesg | grep "Killed process"

실무 대응

  • 컨테이너 환경에서는 memory limit 초과 시 컨테이너가 재시작됨 (exit code 137)
  • JVM 앱은 -Xmx 설정을 컨테이너 limit의 70~80%로 잡는 게 안전
  • 메모리 누수가 의심되면 시간대별 RSS 추이를 모니터링

4. 디스크 Full 장애 대응

정리 보기

상태 확인

# 파일시스템별 사용량 (-h: human-readable)
df -h

# 어떤 디렉터리가 큰지 (-s: 요약, -h: human-readable)
du -sh /* 2>/dev/null | sort -rh | head -10

# 더 좁히기
du -sh /var/log/* | sort -rh | head -10

자주 발생하는 원인

원인 위치 해결
애플리케이션 로그 /var/log/, 앱 로그 경로 logrotate 설정
오래된 배포 파일 /opt/, /home/deploy/ 정리 스크립트
Docker 이미지/볼륨 /var/lib/docker/ docker system prune
임시 파일 /tmp/ 정기 정리
삭제됐지만 열려있는 파일 lsof +L1로 확인

삭제됐는데 공간이 안 늘어나는 경우

파일을 삭제해도 프로세스가 해당 파일을 열고 있으면 디스크 공간이 반환되지 않습니다.

# 삭제됐지만 열려있는 파일 확인
lsof +L1

# 해당 프로세스 재시작하면 공간 반환됨

예방

  • logrotate 설정으로 로그 파일 자동 관리
  • 모니터링에서 디스크 80% 알림 설정
  • Docker 환경이면 주기적으로 docker system prune -f

5. 네트워크 상태 확인

정리 보기

서버 장애의 상당수는 네트워크 문제입니다. 프로세스는 살아있는데 응답이 안 오면 네트워크를 확인합니다.

포트 확인 — 서비스가 리슨하고 있는지

# 열려있는 포트 목록
ss -tlnp
# t: TCP, l: LISTEN, n: 숫자로 표시, p: 프로세스 정보

# 특정 포트 확인
ss -tlnp | grep :8080

# 구버전 시스템
netstat -tlnp

연결 상태 확인

# 현재 TCP 연결 상태별 카운트
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

# TIME_WAIT가 너무 많으면 커넥션 풀 문제 의심
# CLOSE_WAIT가 쌓이면 애플리케이션이 연결을 제대로 닫지 않는 것

외부 연결 테스트

# 기본 연결 확인
ping -c 4 google.com

# 특정 포트로 연결 가능한지
curl -v telnet://db-server:3306
nc -zv db-server 3306

# DNS 확인
nslookup example.com
dig example.com

# 경로 추적 (어디서 막히는지)
traceroute example.com
mtr example.com  # 더 자세한 경로 추적

방화벽 확인

# iptables 규칙 확인
iptables -L -n

# 클라우드 환경이면 보안그룹/방화벽 규칙도 확인
# AWS: Security Group, NACL
# GCP: Firewall Rules

실무 판단 흐름

  1. 서비스 포트가 LISTEN 상태인지 (ss -tlnp)
  2. 로컬에서 접속 되는지 (curl localhost:8080)
  3. 외부에서 접속 되는지 (curl server-ip:8080)
  4. 안 되면 방화벽, 보안그룹, DNS 순서로 확인

6. 파일 권한과 소유자

정리 보기

권한 읽는 법

ls -l app.sh
# -rwxr-xr-- 1 deploy deploy 1024 Jul 7 09:00 app.sh
-rwxr-xr--
│├─┤├─┤├─┤
│ │   │  └── 기타 사용자: r-- (읽기만 가능)
│ │   └───── 그룹: r-x (읽기 + 실행)
│ └───────── 소유자: rwx (읽기 + 쓰기 + 실행)
└──────────── 파일 유형 (-: 일반 파일, d: 디렉터리, l: 심볼릭 링크)

숫자 표기법

r = 4, w = 2, x = 1

755 = rwxr-xr-x (소유자: 전부, 나머지: 읽기+실행)
644 = rw-r--r-- (소유자: 읽기+쓰기, 나머지: 읽기만)
600 = rw------- (소유자만 읽기+쓰기)

변경 명령어

# 권한 변경
chmod 755 app.sh
chmod +x deploy.sh  # 실행 권한 추가

# 소유자 변경
chown deploy:deploy app.sh

# 하위 전체 재귀 변경
chown -R deploy:deploy /opt/app/
chmod -R 755 /opt/app/bin/

실무에서 자주 터지는 케이스

증상 원인 해결
Permission denied 실행 권한 없음 chmod +x
배포 스크립트 실패 소유자 불일치 chown deploy:deploy
로그 파일 못 씀 디렉터리 쓰기 권한 없음 디렉터리에 w 권한
SSH 키 거부 키 파일 권한 너무 open chmod 600 ~/.ssh/id_rsa
cron 실행 안 됨 스크립트 실행 권한 없음 chmod +x

7. 환경 변수와 PATH

정리 보기

개념

환경 변수는 프로세스가 실행될 때 참조하는 key-value 설정입니다. 애플리케이션이 DB 주소, API 키, 실행 모드 같은 설정을 환경 변수에서 읽는 경우가 많습니다.

확인

# 전체 환경 변수
env
printenv

# 특정 변수
echo $PATH
echo $HOME
echo $LANG

PATH

PATH는 명령어를 실행할 때 바이너리를 찾는 디렉터리 목록입니다.

echo $PATH
# /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

# 명령어가 어디에 있는지
which python3
type python3

설정 방법

# 현재 세션에만 적용
export DB_HOST=localhost
export PATH=$PATH:/opt/app/bin

# 영구 적용 (로그인 시 자동 로드)
echo 'export DB_HOST=localhost' >> ~/.bashrc
source ~/.bashrc

# 시스템 전체 적용
sudo vi /etc/environment

실무에서 자주 발생하는 문제

증상 원인 해결
command not found PATH에 바이너리 경로 없음 PATH에 추가
배포 후 환경 변수 못 읽음 systemd 서비스는 사용자 셸 환경 안 씀 서비스 파일에 Environment= 또는 EnvironmentFile=
cron에서 실행 안 됨 cron은 최소 환경으로 실행 스크립트 안에서 직접 export 또는 full path 사용
SSH로 접속하면 변수 있는데, 서비스로 돌리면 없음 로그인 셸 vs non-login 셸 차이 systemd에서 직접 지정

systemd 서비스에서 환경 변수 설정

[Service]
Environment=DB_HOST=localhost
Environment=DB_PORT=5432

# 또는 파일로 분리
EnvironmentFile=/etc/app/env

8. 유저와 그룹 관리

정리 보기

왜 중요한가

서버에서 애플리케이션은 root가 아닌 전용 유저로 실행하는 것이 보안 기본입니다. 배포 유저, 앱 실행 유저를 분리합니다.

유저 관리

# 유저 생성 (홈 디렉터리 포함)
useradd -m -s /bin/bash deploy

# 패스워드 설정
passwd deploy

# 유저 정보 확인
id deploy
# uid=1001(deploy) gid=1001(deploy) groups=1001(deploy)

# 유저 삭제
userdel -r deploy  # -r: 홈 디렉터리도 삭제

그룹 관리

# 그룹 생성
groupadd devops

# 유저를 그룹에 추가
usermod -aG devops deploy
# -a: 기존 그룹 유지, -G: 보조 그룹에 추가

# 그룹 확인
groups deploy

sudo 권한

# sudo 그룹에 추가 (Ubuntu)
usermod -aG sudo deploy

# 또는 sudoers 직접 편집
visudo
# deploy ALL=(ALL) NOPASSWD: ALL

실무 구성 예시

root        — 시스템 관리 (직접 로그인 금지)
deploy      — 배포 작업 (sudo 가능, SSH 키 접속)
appuser     — 앱 실행 전용 (sudo 없음, 로그인 불가능)
# 로그인 불가능한 앱 실행 유저
useradd -r -s /usr/sbin/nologin appuser

9. 패키지 관리

정리 보기

서버에 소프트웨어를 설치하고 업데이트하는 방법입니다. 배포판에 따라 명령어가 다릅니다.

Debian/Ubuntu 계열 (apt)

# 패키지 목록 업데이트
apt update

# 패키지 설치
apt install -y nginx

# 패키지 제거
apt remove nginx
apt purge nginx  # 설정 파일까지 제거

# 설치된 패키지 확인
dpkg -l | grep nginx

# 전체 업그레이드
apt upgrade -y

RHEL/CentOS/Amazon Linux 계열 (yum/dnf)

# 패키지 설치
yum install -y nginx     # CentOS 7
dnf install -y nginx     # CentOS 8+, Amazon Linux 2023

# 패키지 제거
yum remove nginx

# 설치된 패키지 확인
rpm -qa | grep nginx

# 전체 업데이트
yum update -y

실무 주의사항

  • 운영 서버에서 apt upgradeyum update를 무작정 실행하지 않음 — 의존성 깨질 수 있음
  • 특정 버전 고정이 필요하면 apt-mark hold <package> 또는 yum versionlock
  • 자동 보안 업데이트는 unattended-upgrades (Ubuntu) 또는 dnf-automatic (RHEL)
  • Docker 환경에서는 이미지 빌드 시 apt update && apt install을 한 레이어에서 처리

10. SSH 접속과 키 관리

정리 보기

SSH 키 생성

# Ed25519 키 생성 (현재 권장)
ssh-keygen -t ed25519 -C "deploy@company.com"

# 기본 경로: ~/.ssh/id_ed25519 (개인키), ~/.ssh/id_ed25519.pub (공개키)

서버에 공개키 등록

# 방법 1: ssh-copy-id
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server

# 방법 2: 직접 복사
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

SSH 접속

# 기본 접속
ssh deploy@192.168.1.100

# 포트 지정
ssh -p 2222 deploy@server

# 키 지정
ssh -i ~/.ssh/my-key deploy@server

SSH config로 편하게 접속

# ~/.ssh/config
Host prod-web
    HostName 10.0.1.50
    User deploy
    Port 22
    IdentityFile ~/.ssh/id_ed25519

Host staging
    HostName 10.0.2.50
    User deploy
    IdentityFile ~/.ssh/id_ed25519
# 이제 이렇게 접속 가능
ssh prod-web

보안 설정 (/etc/ssh/sshd_config)

# root 직접 로그인 금지
PermitRootLogin no

# 비밀번호 인증 금지 (키 인증만 허용)
PasswordAuthentication no

# 특정 유저만 허용
AllowUsers deploy

# 설정 변경 후 재시작
systemctl restart sshd

파일 권한 (틀리면 SSH 거부됨)

~/.ssh/              → 700
~/.ssh/authorized_keys → 600
~/.ssh/id_ed25519    → 600
~/.ssh/id_ed25519.pub → 644

SCP/SFTP로 파일 전송

# 파일 복사
scp app.tar.gz deploy@server:/opt/app/

# 디렉터리 복사 (-r: recursive)
scp -r ./dist deploy@server:/opt/app/

# rsync (-a: archive 모드, -v: verbose, -z: 압축 전송)
rsync -avz ./dist/ deploy@server:/opt/app/dist/

11. systemd 서비스 관리

정리 보기

기본 명령어

# 서비스 상태 확인
systemctl status nginx

# 시작 / 중지 / 재시작
systemctl start nginx
systemctl stop nginx
systemctl restart nginx

# 설정만 다시 읽기 (다운타임 없이)
systemctl reload nginx

# 부팅 시 자동 시작 등록
systemctl enable nginx

# 자동 시작 해제
systemctl disable nginx

서비스 파일 구조 (/etc/systemd/system/myapp.service)

[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/app
ExecStart=/opt/app/bin/server
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=/etc/app/env

# 로그 출력
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
# 서비스 파일 수정 후
systemctl daemon-reload
systemctl restart myapp

로그 확인

# 서비스 로그
journalctl -u nginx -n 100
journalctl -u nginx -f          # 실시간
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2026-07-07 09:00" --until "2026-07-07 10:00"

실무 체크리스트

  • Restart=on-failure: 비정상 종료 시 자동 재시작
  • RestartSec=5: 재시작 간격 (너무 짧으면 장애 시 부하 가중)
  • enable 했는지: 서버 재부팅 후 서비스 안 올라오는 원인 1위
  • 로그가 journal로 가는지, 파일로 가는지 확인

12. 로그 확인 명령어

정리 보기

자주 사용하는 명령어

# 실시간 로그 보기
tail -f /var/log/app/app.log

# 마지막 100줄
tail -n 100 app.log

# 에러만 필터링
grep "ERROR" app.log
grep -i "exception" app.log

# 시간대로 필터링 (로그 포맷에 따라)
grep "2026-07-07 09:" app.log

# 여러 조건
grep -E "ERROR|WARN" app.log

# 파일 전체를 페이지 단위로 보기
less app.log
# less 안에서: /ERROR 로 검색, n으로 다음, q로 종료

systemd journal

# 전체 시스템 로그
journalctl -xe

# 커널 로그
journalctl -k

# 부팅 이후 로그
journalctl -b

# 특정 시간 범위
journalctl --since "2026-07-07 09:00" --until "2026-07-07 10:00"

로그 분석 팁

상황 확인 순서
서비스 안 됨 journalctl -u <service> -n 50 → 에러 메시지 확인
간헐적 에러 grep -c "ERROR" app.log → 빈도 확인 → 시간대 좁히기
장애 시점 특정 배포 시간, 트래픽 변화, 외부 시스템 응답 시간 비교
디스크 다 찼을 때 ls -lhS /var/log/ → 큰 파일부터 확인

핵심: 에러 메시지만 보는 것이 아니라, 발생 시간 / 빈도 / 배포 시점 / 트래픽 변화를 함께 봐야 원인을 좁힐 수 있습니다.

13. cron과 배치 작업

정리 보기

cron 기본

# 현재 유저의 cron 목록
crontab -l

# cron 편집
crontab -e

# 다른 유저의 cron 확인 (root)
crontab -u deploy -l

cron 표현식

┌─── 분 (0-59)
│ ┌─── 시 (0-23)
│ │ ┌─── 일 (1-31)
│ │ │ ┌─── 월 (1-12)
│ │ │ │ ┌─── 요일 (0-7, 0과 7 = 일요일)
│ │ │ │ │
* * * * *  command

예시

# 매일 새벽 2시
0 2 * * * /home/deploy/backup.sh

# 5분마다
*/5 * * * * /home/deploy/health-check.sh

# 매주 월요일 오전 9시
0 9 * * 1 /home/deploy/weekly-report.sh

# 매월 1일 자정
0 0 1 * * /home/deploy/monthly-cleanup.sh

배치 작업 로그 남기기

# stdout과 stderr 모두 로그로 남기기
0 2 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>&1

# 날짜별 로그
0 2 * * * /home/deploy/backup.sh >> /var/log/backup-$(date +\%Y\%m\%d).log 2>&1

실무 주의사항

  • cron은 최소 환경으로 실행됨 — PATH가 거의 비어있으므로 full path 사용
  • 실행은 됐지만 실패한 건지, 아예 실행 안 된 건지 구분할 수 있어야 함
  • 로그를 남기지 않으면 디버깅 불가능
  • 여러 서버에서 같은 cron이 도는 경우 중복 실행 주의
  • flock으로 중복 실행 방지:
* * * * * flock -n /tmp/myjob.lock /home/deploy/job.sh

14. Shell Script 자동화

정리 보기

기본 템플릿

#!/usr/bin/env bash
set -euo pipefail

# set -e: 에러 발생 시 즉시 중단
# set -u: 미정의 변수 사용 시 에러
# set -o pipefail: 파이프라인 중간 실패도 감지

LOG_FILE="/var/log/deploy.log"

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

log "=== 배포 시작 ==="

# 작업 내용
log "작업 완료"

조건문

# 파일 존재 확인
if [ -f /opt/app/config.yml ]; then
  echo "설정 파일 있음"
fi

# 디렉터리 존재 확인
if [ ! -d /opt/app/logs ]; then
  mkdir -p /opt/app/logs
fi

# 명령어 성공/실패
if systemctl is-active --quiet nginx; then
  echo "nginx 실행 중"
else
  echo "nginx 중지 상태"
  systemctl start nginx
fi

반복문

# 여러 서버에 명령 실행
SERVERS="web01 web02 web03"
for server in $SERVERS; do
  echo "=== $server ==="
  ssh deploy@"$server" "systemctl status app"
done

실무 스크립트 예시: 서버 상태 점검

#!/usr/bin/env bash
set -euo pipefail

echo "=== 서버 상태 점검 ==="
echo ""

echo "[디스크]"
df -h | grep -E "^/dev" | awk '{print $6, $5}'
echo ""

echo "[메모리]"
free -h | grep Mem | awk '{print "사용:", $3, "/", $2}'
echo ""

echo "[CPU Load]"
uptime | awk -F'load average:' '{print $2}'
echo ""

echo "[주요 서비스]"
for svc in nginx app-server redis; do
  if systemctl is-active --quiet "$svc" 2>/dev/null; then
    echo "  $svc: 정상"
  else
    echo "  $svc: 중지됨 ⚠️"
  fi
done

스크립트 작성 원칙

  • 항상 set -euo pipefail로 시작
  • 로그를 남겨서 실행 결과를 추적 가능하게
  • 위험한 작업(rm, drop) 전에 dry-run 옵션 만들기
  • 절대 경로 사용 (cron에서 실행될 때 PATH 문제 방지)
  • 중요 작업 전후로 상태 확인 추가

정리

Linux / OS는 운영 문제를 확인하는 출발점입니다. 프로세스, CPU, 메모리, 디스크, 네트워크, 권한, SSH, 서비스, 로그를 순서대로 확인할 수 있으면 대부분의 서버 장애를 더 빠르게 좁혀갈 수 있습니다.

각 항목을 외우기보다는, 장애가 발생했을 때 어떤 순서로 어떤 명령어를 실행할지 흐름을 기억하는 것이 중요합니다.