개요
Linux는 서버 운영과 배포 환경의 기반이 되는 운영체제입니다.
이 글은 프로세스, 메모리, 권한, systemd, 로그, cron을 주제별로 정리합니다.
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>구조별 차이
- 멀티프로세스 구조: 프로세스마다 메모리 공간이 분리됨
- 멀티스레드 구조: 같은 프로세스의 스레드가 힙을 공유함
- 좀비 프로세스(
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 -102단계: 해당 프로세스가 왜 높은지 좁히기
# 프로세스가 어떤 시스템 콜을 하는지
# -c: 요약 통계, -p: PID 지정
strace -cp <PID>
# Java라면 스레드 덤프
kill -3 <PID>
jstack <PID>
# 몇 개 코어를 쓰는지 (-P ALL: 모든 코어, 1 5: 1초 간격 5회)
mpstat -P ALL 1 53단계: 출력값이 나타내는 것
top의%Cpu(s)행은 CPU 시간을us(사용자 공간),sy(커널),wa(I/O 대기),st(하이퍼바이저에 빼앗긴 시간) 등으로 나눠 표시합니다mpstat -P ALL은 코어별 사용률을 각각 출력하므로 부하가 한 코어에 몰렸는지 여러 코어에 퍼졌는지 구분됩니다strace -c는 프로세스가 호출한 시스템 콜을 호출 횟수와 소요 시간으로 집계합니다
3. 메모리 사용량 확인과 OOM
정리 보기
전체 메모리 상태 확인
free -h # -h: 각 값을 세 자리 숫자가 되는 가장 짧은 단위로 자동 환산free의 기본 출력 단위는 kibibyte이고, -h는 B/Ki/Mi/Gi/Ti/Pi 단위로 환산합니다. 1000 기반 단위로 보려면 --si를 씁니다.
출력 예시:
total used free shared buff/cache available
Mem: 7.8G 3.2G 512M 128M 4.1G 4.2Gfree(1) man page의 정의는 이렇습니다.
used: 사용 중이거나 사용할 수 없는 메모리.total - available로 계산됩니다buff/cache: 커널 버퍼(Buffers)와 페이지 캐시 + 회수 가능한 slab(Cached+SReclaimable)의 합available: 스왑 없이 새 애플리케이션을 시작할 수 있는 메모리의 추정치(/proc/meminfo의MemAvailable). 페이지 캐시를 감안하고, 회수 가능한 slab이 전부 회수되지는 않는다는 점까지 반영한 값입니다
used는 total - available로 계산됩니다. 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가 프로세스를 강제 종료합니다. 대상 선택은 /proc/<pid>/oom_score를 기준으로 하며, 점수가 높을수록 선택될 가능성이 커집니다. 점수의 기준은 해당 프로세스가 사용한 메모리 양이고, 특권 프로세스 여부와 oom_score_adj 설정이 여기에 반영됩니다.
# OOM 발생 여부 확인
dmesg | grep -i "oom"
journalctl -k | grep -i "oom"
# 어떤 프로세스가 죽었는지
dmesg | grep "Killed process"OOM 발생 시 동작
- 컨테이너 환경(cgroup v2)에서는 cgroup의 메모리 사용량이
memory.max에 도달하고 줄일 수 없으면 해당 cgroup 안에서 OOM Killer가 호출됩니다 - 프로세스가
SIGKILL(9)로 종료되면 셸이 보고하는 종료 상태는128 + 시그널 번호규칙에 따라 137이 됩니다. 컨테이너가 종료된 뒤 자동으로 다시 뜨는지는 재시작 정책에 달려 있고, Docker의--restart기본값은no입니다 - JVM 앱은 Linux 컨테이너에서
-XX:+UseContainerSupport(기본값 활성)로 컨테이너에 할당된 메모리와 프로세서 수를 감지합니다. 힙 상한 계산의 기준이 되는-XX:MaxRAM은 JVM 프로세스가 쓸 수 있는 메모리(머신의 물리 메모리와 컨테이너 같은 환경 제약 중 더 작은 값)와 128GB 중 더 작은 값입니다. 이 값에 적용되는 비율은 힙 크기에 따라 갈립니다.-XX:MaxRAMPercentage기본값은 25%이고, 약 125MB 이하의 작은 힙에는-XX:MinRAMPercentage기본값인 50%가 적용됩니다. 컨테이너 메모리 한도를 고려해 힙 상한을 직접 정하려면-Xmx또는-XX:MaxRAMPercentage를 지정합니다
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삭제됐는데 공간이 안 늘어나는 경우
파일을 삭제해도 프로세스가 해당 파일을 열고 있으면 디스크 공간이 반환되지 않습니다.
# 삭제됐지만 열려있는 파일 확인
lsof +L1
# 파일을 열고 있던 프로세스가 종료되면 커널이 블록을 회수함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: 연결을 먼저 닫은 쪽이 2MSL 동안 유지하는 상태
# CLOSE_WAIT: 상대의 FIN을 받았지만 로컬 애플리케이션이 close()를 하지 않은 상태외부 연결 테스트
# 기본 연결 확인
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 Rules6. 파일 권한과 소유자
정리 보기
권한 읽는 법
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/7. 환경 변수와 PATH
정리 보기
개념
환경 변수는 프로세스가 실행될 때 참조하는 key-value 설정입니다. 애플리케이션이 DB 주소, API 키, 실행 모드 같은 설정을 환경 변수에서 읽는 경우가 많습니다.
확인
# 전체 환경 변수
env
printenv
# 특정 변수
echo $PATH
echo $HOME
echo $LANGPATH
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/environmentsystemd 서비스에서 환경 변수 설정
[Service]
Environment=DB_HOST=localhost
Environment=DB_PORT=5432
# 또는 파일로 분리
EnvironmentFile=/etc/app/env8. 유저와 그룹 관리
정리 보기
유저 분리
systemd 유닛의 User=로 서비스를 실행할 계정을 지정할 수 있습니다. 배포 작업용 계정과 애플리케이션 실행용 계정을 따로 두면 각 계정에 부여된 권한 범위가 나뉩니다.
유저 관리
# 유저 생성 (홈 디렉터리 포함)
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 deploysudo 권한
# sudo 그룹에 추가 (Ubuntu)
usermod -aG sudo deploy
# 또는 sudoers 직접 편집
visudo
# deploy ALL=(ALL) NOPASSWD: ALL구성 예시
root — UID 0, 시스템 관리 계정
deploy — 배포 작업 (sudo 가능, SSH 키 접속)
appuser — 앱 실행 전용 (sudo 없음, 로그인 불가능)# 로그인 불가능한 시스템 계정으로 앱 실행 유저 생성 (-r: 시스템 계정)
useradd -r -s /usr/sbin/nologin appusernologin 경로는 배포판에 따라 다릅니다. Debian/Ubuntu는 /usr/sbin/nologin, RHEL 계열은 /sbin/nologin입니다.
9. 패키지 관리
정리 보기
서버에 소프트웨어를 설치하고 업데이트하는 방법입니다. 배포판에 따라 명령어가 다릅니다.
Debian/Ubuntu 계열 (apt)
# 패키지 목록 업데이트
apt update
# 패키지 설치
apt install -y nginx
# 패키지 제거
apt remove nginx
apt purge nginx # 설정 파일까지 제거
# 설치된 패키지 확인
dpkg -l | grep nginx
# 전체 업그레이드
apt upgrade -yRHEL/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-mark hold <package>또는yum versionlock을 사용합니다 - 자동 보안 업데이트는
unattended-upgrades(Ubuntu) 또는dnf-automatic(RHEL)이 담당합니다
10. SSH 접속과 키 관리
정리 보기
SSH 키 생성
# Ed25519 키 생성 (OpenSSH 9.5부터 ssh-keygen의 기본 키 타입)
ssh-keygen -t ed25519 -C "[email protected]"
# 기본 경로: ~/.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_keysSSH 접속
# 기본 접속
ssh [email protected]
# 포트 지정
ssh -p 2222 deploy@server
# 키 지정
ssh -i ~/.ssh/my-key deploy@serverSSH 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(1) man page가 권장하는 값입니다.
~/.ssh/ → 700 (소유자만 rwx, 다른 사용자 접근 불가)
~/.ssh/authorized_keys → 600 (소유자만 rw)
~/.ssh/id_ed25519 → 600 (개인키)
~/.ssh/id_ed25519.pub → 644 (공개키는 민감하지 않음)개인키가 다른 사용자에게 접근 가능하면 ssh는 그 키 파일을 그냥 무시합니다. 서버 쪽은 sshd_config의 StrictModes(기본값 yes)가 로그인을 받기 전에 사용자 파일과 홈 디렉터리의 모드·소유권을 검사합니다.
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
# 유닛에 정의된 ExecReload= 실행 (설정 다시 읽기)
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: 0이 아닌 종료 코드, 시그널에 의한 종료(SIGHUP/SIGINT/SIGTERM/SIGPIPE제외), 작업 타임아웃, watchdog 타임아웃, OOM으로 인한 종료에서 재시작합니다. systemd 문서는 장기 실행 서비스에 이 값을 권장합니다RestartSec=5: 재시작 전 대기 시간. 지정하지 않으면 기본값은 100ms입니다. 재시작이 반복되면StartLimitIntervalSec=/StartLimitBurst=의 시작 속도 제한이 적용됩니다systemctl enable: 유닛 파일의[Install]섹션(WantedBy=등)에 따라 심볼릭 링크를 만들어 부팅 시 활성화되도록 합니다.enable을 하지 않아도 다른 유닛이Wants=/Requires=로 끌어오거나 소켓·타이머로 활성화되면 시작될 수 있습니다StandardOutput=/StandardError=가 로그 대상을 결정함
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"13. cron과 배치 작업
정리 보기
cron 기본
# 현재 유저의 cron 목록
crontab -l
# cron 편집
crontab -e
# 다른 유저의 cron 확인 (root)
crontab -u deploy -lcron 표현식
┌─── 분 (0-59)
│ ┌─── 시 (0-23)
│ │ ┌─── 일 (1-31)
│ │ │ ┌─── 월 (1-12)
│ │ │ │ ┌─── 요일 (0-7, 0 또는 7 = 일요일, 이름도 사용 가능)
│ │ │ │ │
* * * * * command월과 요일 필드는 jan, mon처럼 앞 세 글자 이름으로도 쓸 수 있습니다. 그리고 crontab(5)에 따르면 ‘일’과 ‘요일’ 두 필드가 모두 *가 아니면 둘 중 하나만 일치해도 실행됩니다. AND가 아니라 OR입니다.
예시
# 매일 새벽 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>&1cron 실행 환경
- cron은 로그인 셸과 다른 최소 환경에서 실행되며 PATH가 제한적입니다
flock은 지정한 락 파일을 잡지 못하면 명령을 실행하지 않습니다 (-n: 대기 없이 종료)
* * * * * flock -n /tmp/myjob.lock /home/deploy/job.sh14. 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