Chapter 10. 텍스트 정렬, 집계 & 비교
grep, sed, awk
grep, sed, awk 는 단독으로도 유용하지만, 파이프(|)로 연결하여 데이터 분석 파이프라인을 구성할 때 더 많은 작업을 할 수 있다.
# 로그에서 IP별 접속 횟수 상위 10개
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
sort — 텍스트 정렬
sort는 입력을 줄 단위로 정렬한다. 기본은 알파벳(사전) 순서이다.
# 알파벳순 정렬
sort names.txt
# 역순 정렬 (-r: reverse)
sort -r names.txt
# 숫자순 정렬 (-n: numeric) ★
sort -n numbers.txt
# 숫자 정렬 없이 하면: 1, 10, 100, 2, 20 (문자열 비교)
# -n을 붙이면: 1, 2, 10, 20, 100 (숫자 비교)
# 사람이 읽기 쉬운 크기 단위로 정렬 (-h: human-numeric)
du -sh */ | sort -rh
# 2.1G, 500M, 128K 순서로 정렬
특정 필드 기준 정렬
# 특정 필드 기준으로 정렬 (-k: key, -t: delimiter)
# /etc/passwd를 UID(3번째 필드) 기준으로 숫자 정렬
sort -t: -k3 -n /etc/passwd
# CSV에서 3번째 열 기준 숫자 역순 정렬
sort -t',' -k3 -rn data.csv
# 여러 키로 정렬 (1차: 2번째 필드, 2차: 3번째 필드 숫자)
sort -t',' -k2,2 -k3,3n data.csv
유용한 옵션
# 중복 줄 제거하면서 정렬 (-u: unique)
sort -u names.txt
# 대소문자 무시 정렬 (-f: fold case)
sort -f mixed_case.txt
# 결과를 원본 파일에 덮어쓰기 (-o)
sort -n numbers.txt -o numbers.txt
# 주의: sort file > file 은 안된다 (파일이 비어버림). -o를 사용해야 안전
uniq — 중복 처리
uniq는 인접한 중복 줄을 처리한다. 핵심 주의사항: uniq는 연속된 중복만 감지하므로, 반드시 sort와 함께 사용해야 한다.
# 잘못된 사용 — 비연속 중복은 제거되지 않음
echo -e "a\nb\na" | uniq
# a
# b
# a ← 여전히 남아있음
# 올바른 사용 — sort로 먼저 정렬
echo -e "a\nb\na" | sort | uniq
# a
# b
주요 옵션
# 중복 제거 (기본 동작)
sort data.txt | uniq
# 중복 횟수와 함께 출력 (-c: count) ★★★
sort data.txt | uniq -c
# 3 apple
# 1 banana
# 5 cherry
# 중복된 줄만 출력 (-d: duplicated)
sort data.txt | uniq -d
# 중복되지 않은 줄만 출력 (-u: unique)
sort data.txt | uniq -u
# 대소문자 무시 (-i)
sort data.txt | uniq -ci
핵심 패턴: sort | uniq -c | sort -rn
# 패턴 분해:
# sort → 동일한 값을 인접하게 모음
# uniq -c → 중복 횟수를 센다
# sort -rn → 횟수 기준 내림차순 정렬
# 실무 예시: Nginx 로그에서 가장 많이 접속한 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 실무 예시: 가장 많이 발생한 에러 메시지
grep "ERROR" app.log | awk -F'ERROR' '{print $2}' | sort | uniq -c | sort -rn | head -10
# 실무 예시: HTTP 상태 코드 분포
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# 45231 200
# 3214 304
# 567 404
# 23 500
wc — 텍스트 통계
Word Count의 약자이다.
# 파이프와 조합 — 결과의 줄 수 세기
grep "ERROR" app.log | wc -l # 에러 발생 횟수
ps aux | wc -l # 실행 중인 프로세스 수
find /tmp -type f | wc -l # /tmp의 파일 수
docker ps | wc -l # 실행 중인 컨테이너 수 (+1 헤더)
# 여러 파일의 줄 수 비교
wc -l /var/log/*.log
cut — 필드 추출
cut vs awk — 언제 무엇을 쓰는가
| 상황 | 추천 | 이유 |
|---|---|---|
| 단순히 N번째 필드 추출 | cut |
더 간결하고 빠르다 |
| 구분자가 여러 공백일 때 | awk |
cut은 연속 공백을 하나로 처리하지 못한다 |
| 조건부 필드 추출 | awk |
조건 처리 기능이 있다 |
| 필드 계산이 필요할 때 | awk |
프로그래밍 기능 |
# cut: 구분자가 명확한 경우 (CSV, /etc/passwd 등)
cut -d: -f1,3 /etc/passwd
# awk: 공백으로 구분된 출력 (ps, df 등)
ps aux | awk '{print $1, $11}' # 사용자, 명령어
df -h | awk 'NR>1 {print $5, $6}' # 사용률, 마운트포인트
tr — 문자 변환 (보충)
# Windows 줄바꿈(\r\n)을 Unix 줄바꿈(\n)으로 변환
tr -d '\r' < windows_file.txt > unix_file.txt
# 여러 구분자를 통일
echo "a;b,c:d" | tr ';,:' '\t\t\t'
# a b c d
# 알파벳 외 문자 모두 제거
echo "Hello, World! 123" | tr -cd 'a-zA-Z\n'
# HelloWorld
# 연속된 빈 줄을 하나로 압축
cat messy.txt | tr -s '\n'
diff — 파일 비교
두 파일의 차이점을 줄 단위로 비교하여 보여준다. 설정 파일 변경 전후 비교, 코드 리뷰, 패치 생성 등에 사용한다.
# 기본 비교
diff file1.txt file2.txt
# 3c3 ← 3번째 줄이 변경(changed)됨
# < old line ← file1의 내용
# ---
# > new line ← file2의 내용
# Unified 형식 (-u) ★★★ — 가장 읽기 쉬움 (git diff와 동일한 형식)
diff -u file1.txt file2.txt
# --- file1.txt
# +++ file2.txt
# @@ -1,5 +1,5 @@
# unchanged line
# -deleted line
# +added line
# unchanged line
Chapter 11. 파이프 & 리다이렉션
파이프와 리다이렉션은 UNIX 철학의 핵심인 "작은 도구를 조합하여 복잡한 작업을 수행한다"를 실현하는 메커니즘이다.
리다이렉션 (Redirection)
리다이렉션은 표준 스트림의 방향을 바꾸는 것이다. 기본적으로 stdout은 화면에 출력되고 stdin은 키보드에서 입력받지만, 이를 파일이나 다른 스트림으로 변경할 수 있다.
출력 리다이렉션 (>, >>)
# stdout을 파일로 저장 (덮어쓰기)
echo "Hello" > output.txt
# stdout을 파일에 추가 (append)
echo "World" >> output.txt
# 명령어 결과를 파일로 저장
ls -la /var/log > file_list.txt
df -h > disk_status.txt
ps aux > process_list.txt
> vs >> — 중요한 차이:
>: 파일이 이미 존재하면 내용을 완전히 덮어쓴다. 파일이 없으면 새로 생성한다.>>: 파일이 이미 존재하면 기존 내용 뒤에 추가한다. 파일이 없으면 새로 생성한다.
# 파일 비우기 (truncate)
> /var/log/large_app.log # 파일 내용을 0바이트로 만듦 (파일은 유지)
# 이것은 rm 후 재생성보다 안전하다. 파일을 열고 있는 프로세스에 영향이 적다.
에러 리다이렉션 (2>, 2>>)
stderr(파일 디스크립터 2)를 별도로 제어할 수 있다.
# 에러만 파일로 저장
find / -name "*.conf" 2> errors.txt
# 에러를 버리기 (화면에 표시하지 않기) ★★★
find / -name "*.conf" 2>/dev/null
# 에러를 별도 파일에 추가
command 2>> error_log.txt
2.3 stdout과 stderr를 동시에 제어
# stdout은 파일로, stderr는 다른 파일로
command > output.txt 2> errors.txt
# stdout과 stderr를 같은 파일로 ★★★
command > all_output.txt 2>&1
# 해석: stdout(1)을 all_output.txt로 → stderr(2)를 stdout(1)이 가리키는 곳으로
# 위와 동일한 축약 문법 (bash 4.0+)
command &> all_output.txt
# stdout과 stderr를 모두 버리기 ★★★
command > /dev/null 2>&1
# 또는
command &> /dev/null
2>&1의 의미:
2>: stderr(fd 2)의 방향을 바꾼다&1: fd 1(stdout)이 현재 가리키고 있는 곳으로 보낸다- 따라서
> file 2>&1은 "stdout을 file로 보내고, stderr도 같은 곳(file)으로 보내라"는 뜻이다
⚠️ 순서가 중요하다:
> file 2>&1→ ✓ 올바름 (stdout이 먼저 file로 향하고, stderr가 따라감)2>&1 > file→ ✗ 의도와 다름 (stderr가 원래 stdout=화면으로 향하고, stdout만 file로 감)
입력 리다이렉션 (<)
# 파일을 stdin으로 전달
sort < unsorted.txt
# 실무에서는 파이프가 더 흔하지만, 일부 명령어에서 사용
mysql -u root -p database < schema.sql # SQL 파일 실행
wc -l < data.txt # 파일명 없이 줄 수만 출력
Here Document (<<)
여러 줄의 텍스트를 stdin으로 전달한다. 스크립트에서 파일을 생성하거나, 대화형 명령어에 입력을 전달할 때 사용한다.
# 파일 생성
cat << EOF > /etc/myapp/config.yaml
server:
host: 0.0.0.0
port: 8080
database:
host: ${DB_HOST}
port: 5432
EOF
# 변수 확장을 막으려면 구분자를 따옴표로 감싼다
cat << 'EOF' > script_template.sh
echo "Current user: $USER"
echo "Home: $HOME"
EOF
# 위 경우 $USER, $HOME이 확장되지 않고 그대로 기록된다
파이프 (Pipe, |)
파이프(|)는 앞 명령어의 stdout을 뒤 명령어의 stdin으로 연결한다.
명령어A | 명령어B | 명령어C
stdout ──→ stdin stdout ──→ stdin stdout → 화면
# 기본 예시
cat /etc/passwd | grep "bash" | wc -l
# 1) cat이 파일 내용을 stdout으로 출력
# 2) grep이 stdin에서 "bash" 포함 줄을 필터링
# 3) wc -l이 stdin에서 줄 수를 센다
파이프는 stdout만 전달한다
파이프는 stdout만 전달한다. stderr는 파이프를 통과하지 않고 화면에 그대로 출력된다.
# stderr는 파이프를 통과하지 않는다
find / -name "*.conf" | grep nginx
# Permission denied 에러 메시지(stderr)는 여전히 화면에 출력된다
# stderr도 파이프로 전달하려면
find / -name "*.conf" 2>&1 | grep nginx
# stderr를 stdout으로 합친 후 파이프 전달
자주 쓰는 파이프 패턴
# 1. 로그 분석: 에러 추출 → 빈도 분석
grep "ERROR" app.log | awk '{print $4}' | sort | uniq -c | sort -rn
# 2. 프로세스 찾기
ps aux | grep nginx | grep -v grep
# 3. 디스크 사용량 정렬
du -sh /var/*/ 2>/dev/null | sort -rh | head -10
# 4. 파일 내용을 클립보드에 복사 (xclip 설치 필요)
cat config.yaml | xclip -selection clipboard
# 5. JSON 응답 정리 (jq 설치 필요)
curl -s https://api.example.com/data | jq '.items[] | .name'
# 6. 실시간 로그 필터링
tail -f /var/log/syslog | grep --line-buffered "error"
tee — 출력을 파일과 화면 동시에
tee는 stdin을 받아서 파일과 stdout 양쪽에 동시 출력하는 명령어이다. 이름은 T자 모양 배관(pipe fitting)에서 유래했다.
┌──→ 파일
stdin ──→ tee
└──→ stdout (다음 파이프 또는 화면)
# 화면에 출력하면서 파일에도 저장
ls -la | tee file_list.txt
# 파일에 추가(append)하면서 화면에도 출력
echo "new entry" | tee -a log.txt
# 여러 파일에 동시 저장
echo "config" | tee file1.txt file2.txt file3.txt
# sudo와 함께 사용 — 권한이 필요한 파일에 쓰기 ★
echo "new line" | sudo tee -a /etc/hosts
# 주의: sudo echo "..." > /etc/hosts는 동작하지 않는다
# > 리다이렉션은 셸이 처리하므로 sudo의 권한이 적용되지 않기 때문이다
프로세스 치환 (Process Substitution)
프로세스 치환은 명령어의 출력을 임시 파일처럼 다른 명령어에 전달하는 기법이다.<(command) 형태로 사용한다.
# 두 명령어의 출력을 diff로 비교
diff <(ls dir1/) <(ls dir2/)
# 두 서버의 설정 파일 비교
diff <(ssh server1 cat /etc/nginx/nginx.conf) \
<(ssh server2 cat /etc/nginx/nginx.conf)
# 정렬된 두 결과를 comm으로 비교
comm -12 <(sort file1.txt) <(sort file2.txt)
diff나 comm 같은 명령어는 파일을 인자로 받는다. 파이프로는 하나의 stdin만 전달할 수 있으므로, 두 명령어의 출력을 동시에 전달할 수 없다. 프로세스 치환은 명령어의 출력을 임시 파일 디스크립터로 만들어 이 문제를 해결한다.
xargs vs 파이프
파이프는 데이터를 stdin 스트림으로 전달하지만, 일부 명령어는 stdin이 아닌 인자(argument)로 데이터를 받는다. 이때 xargs가 필요하다.
# rm은 stdin으로 파일명을 받지 않는다
find /tmp -name "*.tmp" | rm # ✗ 동작하지 않음
find /tmp -name "*.tmp" | xargs rm # ✓ xargs가 stdin을 인자로 변환
Chapter 12. vim 편집기
서버에 SSH로 접속했을 때, GUI 에디터는 없다. 사용할 수 있는 텍스트 편집기는 vi(또는 개선판인 vim)와 nano 정도이다. vim은 거의 모든 리눅스 시스템에 기본 설치되어 있으며, git commit, crontab -e, visudo 등 많은 시스템 도구가 기본 편집기로 vim을 호출한다.
vi와 vim의 관계
- vi: 1976년에 만들어진 원조 편집기이다. Bill Joy가 개발했다.
- vim: Vi IMproved의 약자이다. vi를 기반으로 구문 강조, 다중 되돌리기, 플러그인 등 수많은 기능을 추가한 개선판이다.
대부분의 최신 리눅스 배포판에서 vi를 실행하면 실제로는 vim이 실행된다.
# vim 설치 확인
vim --version | head -1
# 없으면 설치
sudo apt install vim # Debian/Ubuntu
sudo dnf install vim # RHEL/Fedora
vim의 모드
vim이 다른 편집기와 다른 점은 모드(mode) 개념이다.
i, a, o
┌──────── Normal ─────────┐
│ Mode │
│ (명령어 입력 모드) │
Esc │ │ Esc
│ : ──→ Command │
│ Mode │
│ (저장,종료 등) │
│ │
└────────→ Insert ────────┘
Mode
(텍스트 입력 모드)
| 모드 | 용도 | 진입 방법 | 빠져나가는 방법 |
|---|---|---|---|
| Normal | 커서 이동, 복사, 삭제, 검색 등 명령 | vim 시작 시 기본 | — |
| Insert | 실제 텍스트 입력 | i, a, o 등 |
Esc |
| Command | 저장, 종료, 치환 등 Ex 명령어 | Normal에서 : |
Enter 또는 Esc |
| Visual | 텍스트 선택 (블록 복사/삭제) | Normal에서 v |
Esc |
현재 어떤 모드인지 모르겠으면 Esc를 누른다. Esc는 항상 Normal 모드로 돌아간다.
파일 열기
vim filename.txt # 파일 열기 (없으면 새로 생성)
vim +10 filename.txt # 10번째 줄에서 열기
vim +/error filename.txt # "error"가 처음 나오는 줄에서 열기
vim -R filename.txt # 읽기 전용으로 열기
저장 & 종료 (Command 모드)
Normal 모드에서 :를 누르면 Command 모드로 진입한다.
| 명령 | 동작 |
|---|---|
:w |
저장 (Write) |
:q |
종료 (Quit) |
:wq |
저장 후 종료 ★ |
:q! |
저장하지 않고 강제 종료 ★ |
:wq! |
강제 저장 후 종료 (읽기 전용 파일도) |
ZZ |
:wq와 동일 (Normal 모드에서 바로 사용) |
:w newfile.txt |
다른 이름으로 저장 |
vim을 빠져나올 수 없다면?: Esc를 누른 후 :q!를 입력하면 무조건 나갈 수 있다.
Insert 모드 진입
Normal 모드에서 다음 키를 누르면 Insert 모드로 전환된다:
| 키 | 동작 |
|---|---|
i |
커서 앞에서 입력 시작 (가장 많이 사용) |
a |
커서 뒤에서 입력 시작 |
I |
줄의 맨 앞에서 입력 시작 |
A |
줄의 맨 뒤에서 입력 시작 |
o |
현재 줄 아래에 새 줄 추가 후 입력 |
O |
현재 줄 위에 새 줄 추가 후 입력 |
Normal 모드 — 커서 이동
기본 이동
| 키 | 이동 |
|---|---|
h / l |
좌 / 우 (한 글자) |
j / k |
아래 / 위 (한 줄) |
w |
다음 단어의 시작으로 |
b |
이전 단어의 시작으로 |
e |
현재 단어의 끝으로 |
0 |
줄의 맨 처음 |
$ |
줄의 맨 끝 |
^ |
줄의 첫 번째 비공백 문자 |
큰 이동
| 키 | 이동 |
|---|---|
gg |
파일의 맨 처음 |
G |
파일의 맨 끝 |
10G 또는 :10 |
10번째 줄로 이동 |
Ctrl + f |
한 페이지 아래 (forward) |
Ctrl + b |
한 페이지 위 (backward) |
Ctrl + d |
반 페이지 아래 |
Ctrl + u |
반 페이지 위 |
H |
화면 맨 위 (High) |
M |
화면 중간 (Middle) |
L |
화면 맨 아래 (Low) |
Normal 모드 — 편집 명령
삭제
| 명령 | 동작 |
|---|---|
x |
커서 위치의 한 글자 삭제 |
dd |
현재 줄 전체 삭제 ★ |
3dd |
현재 줄부터 3줄 삭제 |
dw |
커서부터 단어 끝까지 삭제 |
d$ 또는 D |
커서부터 줄 끝까지 삭제 |
d0 |
커서부터 줄 처음까지 삭제 |
dG |
커서부터 파일 끝까지 삭제 |
dgg |
커서부터 파일 처음까지 삭제 |
복사 & 붙여넣기
vim에서 복사는 yank라고 부른다.
| 명령 | 동작 |
|---|---|
yy |
현재 줄 복사 ★ |
3yy |
현재 줄부터 3줄 복사 |
yw |
단어 복사 |
p |
커서 아래/뒤에 붙여넣기 ★ |
P |
커서 위/앞에 붙여넣기 |
되돌리기 & 다시 실행
| 명령 | 동작 |
|---|---|
u |
되돌리기 (Undo) ★ |
Ctrl + r |
다시 실행 (Redo) |
. |
마지막 명령 반복 |
검색 & 치환
검색
/pattern → 아래 방향으로 검색
?pattern → 위 방향으로 검색
n → 다음 검색 결과로 이동
N → 이전 검색 결과로 이동
* → 커서 위의 단어를 아래 방향으로 검색
# → 커서 위의 단어를 위 방향으로 검색
치환 (Command 모드)
sed와 거의 동일한 문법이다.
# 현재 줄에서 치환 (첫 번째만)
:s/old/new/
# 현재 줄에서 모든 매치 치환
:s/old/new/g
# 파일 전체에서 치환 ★★★
:%s/old/new/g
# 파일 전체에서 치환 (확인하면서)
:%s/old/new/gc
# 각 매치마다 y(yes), n(no), a(all), q(quit)를 선택
# 특정 범위에서 치환 (10~20번째 줄)
:10,20s/old/new/g
# 대소문자 무시 치환
:%s/old/new/gi
~/.vimrc — 영구 설정
vim을 열 때마다 설정을 반복하고 싶지 않다면 ~/.vimrc 파일에 설정을 저장한다.
# 실무에 적합한 최소 .vimrc
cat << 'EOF' > ~/.vimrc
set number " 줄 번호 표시
syntax on " 구문 강조
set hlsearch " 검색 하이라이트
set incsearch " 입력하면서 검색
set tabstop=4 " 탭 너비
set shiftwidth=4 " 자동 들여쓰기 너비
set expandtab " 탭을 공백으로
set autoindent " 자동 들여쓰기
set showmatch " 괄호 매치 표시
set ruler " 커서 위치 표시
set encoding=utf-8 " UTF-8 인코딩
EOF
요약
| 상황 | 키 |
|---|---|
| 텍스트 입력하고 싶다 | i → 입력 → Esc |
| 새 줄 추가하고 싶다 | o → 입력 → Esc |
| 저장하고 종료 | Esc → :wq → Enter |
| 저장 안 하고 종료 | Esc → :q! → Enter |
| 줄 삭제 | dd |
| 되돌리기 | u |
| 검색 | /keyword → n(다음) |
| 전체 치환 | :%s/old/new/g |
| 줄 번호 표시 | :set number |
| 모르겠으면 | Esc 연타 |
Chapter 13. 프로세스 관리
프로그램 vs 프로세스
프로그램(Program)은 디스크에 저장된 실행 파일이다. /usr/bin/nginx, /usr/bin/python3 같은 바이너리 파일이 프로그램이다. 프로그램은 정적(static)이다 — 그냥 파일일 뿐이다.
프로세스(Process)는 프로그램이 메모리에 로드되어 실행 중인 인스턴스이다. 하나의 프로그램에서 여러 프로세스가 생성될 수 있다. 예를 들어 터미널 3개를 열면 bash 프로세스가 3개 존재한다.
프로세스는 다음을 가진다:
- PID (Process ID): 프로세스의 고유 식별 번호
- PPID (Parent PID): 자신을 생성한 부모 프로세스의 PID
- UID/GID: 프로세스를 실행한 사용자/그룹
- 메모리 공간: 코드, 데이터, 스택, 힙
- 파일 디스크립터: 열린 파일, 소켓, 파이프 등
- 환경변수: 프로세스에 전달된 설정값
PID 1 — init/systemd
리눅스 부팅 시 커널이 가장 먼저 실행하는 프로세스가 PID 1이다.
최신 배포판에서는 systemd가 PID 1이다.
모든 다른 프로세스는 PID 1의 자식(또는 자손)이다.
# PID 1 확인
ps -p 1 -o comm=
# systemd
# 프로세스 트리 확인
pstree -p | head -20
# systemd(1)─┬─sshd(1234)───sshd(5678)───bash(5679)
# ├─nginx(2345)─┬─nginx(2346)
# │ └─nginx(2347)
# └─dockerd(3456)
프로세스의 생성 — fork와 exec
리눅스에서 새 프로세스는 fork-exec 메커니즘으로 생성된다:
- fork(): 현재 프로세스(부모)의 복사본을 만든다 → 자식 프로세스 생성
- exec(): 자식 프로세스의 메모리를 새로운 프로그램으로 교체한다
셸에서 명령어를 실행하면 이 과정이 일어난다:
bash(PID 100) → fork() → bash(PID 200, 복사본) → exec("ls") → ls(PID 200)
(부모) (자식) (프로그램 교체)
좀비 프로세스와 고아 프로세스
좀비 프로세스(Zombie): 실행이 끝났지만 부모가 아직 종료 상태를 회수하지 않은 프로세스이다. ps에서 상태가 Z로 표시된다. 소량은 정상이지만, 대량 발생하면 PID가 고갈될 수 있다.
고아 프로세스(Orphan): 부모 프로세스가 먼저 종료된 자식 프로세스이다. PID 1(systemd)이 자동으로 입양(adopt)하여 관리한다.
# 좀비 프로세스 찾기
ps aux | awk '$8 == "Z" {print}'
프로세스 확인 명령어
ps — 프로세스 상태 스냅샷
ps는 실행 중인 프로세스의 현재 상태를 한 번 캡처하여 보여준다.
# 모든 프로세스 상세 정보 ★★★
ps aux
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# root 1 0.0 0.3 169484 12340 ? Ss Mar20 0:05 /usr/lib/systemd/systemd
# nginx 2345 0.1 0.5 45678 20480 ? S Mar20 1:23 nginx: worker process
# 특정 프로세스 검색 ★★★
ps aux | grep nginx
ps aux | grep -v grep | grep nginx # grep 자체 제외
# 프로세스 트리 형태로 보기
ps auxf
# 특정 필드만 출력 (-o: format)
ps -eo pid,ppid,user,%cpu,%mem,comm --sort=-%mem | head -15
# 특정 사용자의 프로세스
ps -u nginx
# 특정 PID의 상세 정보
ps -p 1234 -f
ps aux 출력 필드
| 필드 | 설명 |
|---|---|
| USER | 프로세스 소유자 |
| PID | 프로세스 ID |
| %CPU | CPU 사용률 |
| %MEM | 메모리 사용률 |
| VSZ | 가상 메모리 크기 (KB) |
| RSS | 실제 사용 중인 물리 메모리 (KB) — Resident Set Size |
| TTY | 연결된 터미널 (?면 터미널 없음 = 데몬) |
| STAT | 프로세스 상태 |
| START | 시작 시간 |
| TIME | CPU 사용 누적 시간 |
| COMMAND | 실행 명령어 |
STAT(프로세스 상태) 코드:
| 코드 | 의미 |
|---|---|
R |
Running — 실행 중 또는 실행 대기 |
S |
Sleeping — 이벤트 대기 중 (대부분의 프로세스) |
D |
Uninterruptible Sleep — I/O 대기 (디스크 등) |
T |
Stopped — 중지됨 (Ctrl+Z 등) |
Z |
Zombie — 종료되었지만 부모가 회수하지 않음 |
pgrep — 프로세스 이름으로 PID 찾기
# 프로세스 이름으로 PID 검색
pgrep nginx
# 2345
# 2346
# 상세 정보와 함께
pgrep -a nginx
# 2345 nginx: master process /usr/sbin/nginx
# 2346 nginx: worker process
# 특정 사용자의 프로세스만
pgrep -u www-data
프로세스 종료 — 시그널(Signal)
시그널이란
시그널은 프로세스에 보내는 비동기적 알림(notification)이다. "이 일을 해라", "종료해라", "멈춰라" 같은 메시지를 프로세스에 전달하는 메커니즘이다.
주요 시그널
| 번호 | 이름 | 기본 동작 | 설명 |
|---|---|---|---|
| 1 | SIGHUP |
종료 | 터미널 연결이 끊어질 때. 데몬에서는 설정 다시 읽기용으로 사용 |
| 2 | SIGINT |
종료 | Ctrl + C에 해당. 사용자가 인터럽트 요청 |
| 9 | SIGKILL |
강제 종료 | 프로세스가 무시/가로챌 수 없다. 최후의 수단 |
| 15 | SIGTERM |
종료 | 정상 종료 요청 (기본 시그널). 프로세스가 정리 작업 후 종료 가능 |
| 18 | SIGCONT |
재개 | 중지된 프로세스를 재개 |
| 19 | SIGSTOP |
중지 | 프로세스 일시 중지. 무시/가로챌 수 없다 |
| 20 | SIGTSTP |
중지 | Ctrl + Z에 해당 |
SIGTERM vs SIGKILL
- SIGTERM (15): "깔끔하게 종료해주세요"라는 요청이다. 프로세스는 이 시그널을 받으면 열린 파일을 닫고, 임시 파일을 정리하고, 연결을 해제한 뒤 종료할 수 있다. 항상 먼저 시도해야 한다.
- SIGKILL (9): "지금 즉시 죽어라"라는 명령이다. 커널이 프로세스를 즉시 종료시킨다. 프로세스는 어떤 정리 작업도 할 수 없다. 데이터 손실, 파일 손상, 잠금 미해제 등의 문제가 발생할 수 있다.
kill — 시그널 보내기
kill이라는 이름이지만, 실제로는 시그널을 보내는 명령어이다.
# SIGTERM 보내기 (기본, 정상 종료 요청) ★
kill 1234
kill -15 1234
kill -SIGTERM 1234
# 세 가지 모두 동일
# SIGKILL 보내기 (강제 종료, 최후의 수단)
kill -9 1234
kill -SIGKILL 1234
# 설정 다시 읽기 (SIGHUP) — 데몬을 재시작하지 않고 설정 반영
kill -1 $(pgrep nginx)
kill -HUP $(pgrep nginx)
# 여러 프로세스에 시그널 보내기
kill 1234 1235 1236
pkill / killall — 이름으로 종료
# 프로세스 이름으로 종료
pkill nginx
pkill -9 nginx # 강제 종료
# 특정 사용자의 프로세스만 종료
pkill -u testuser
# killall: 정확한 이름으로 종료
killall nginx
killall -9 hung_process
올바른 프로세스 종료 순서
# 1단계: SIGTERM으로 정상 종료 요청
kill 1234
# 2단계: 잠시 대기 (프로세스가 정리할 시간을 준다)
sleep 5
# 3단계: 아직 살아있는지 확인
ps -p 1234
# 4단계: 여전히 살아있으면 SIGKILL
kill -9 1234
포그라운드 & 백그라운드
포그라운드(Foreground): 터미널을 점유하고 있는 프로세스이다. 명령어를 실행하면 기본적으로 포그라운드에서 실행된다. 프로세스가 끝나거나 중지할 때까지 터미널에 다른 명령어를 입력할 수 없다.
백그라운드(Background): 터미널을 점유하지 않고 실행되는 프로세스이다. 명령어 끝에 &를 붙이면 백그라운드로 실행된다.
# 백그라운드로 실행
./long_running_task.sh &
# [1] 12345 ← 작업 번호 1, PID 12345
# 포그라운드 프로세스를 백그라운드로 전환
# Ctrl + Z (SIGTSTP → 일시 중지)
# bg (백그라운드에서 재개)
# 백그라운드 프로세스를 포그라운드로 전환
fg
fg %1 # 작업 번호 지정
# 백그라운드 작업 목록 확인
jobs
# [1]+ Running ./long_task.sh &
# [2]- Stopped vim file.txt
nohup — 터미널 종료 후에도 실행 유지
터미널(SSH 세션)을 닫으면 해당 터미널에서 실행한 프로세스는 SIGHUP을 받아 종료된다. nohup은 SIGHUP을 무시하여 터미널을 닫아도 프로세스가 계속 실행되게 한다.
# nohup + 백그라운드 실행
nohup ./long_running_task.sh &
# 출력은 nohup.out 파일에 저장된다
# 출력을 특정 파일로 지정
nohup ./task.sh > /var/log/task.log 2>&1 &
lsof — 열린 파일 확인
List Open Files의 약자이다. lsof는 파일뿐만 아니라 네트워크 소켓, 파이프, 디바이스 등 프로세스가 열고 있는 모든 것을 보여준다.
# 특정 프로세스가 열고 있는 파일
lsof -p 1234
# 특정 파일을 사용 중인 프로세스
lsof /var/log/syslog
# 삭제되었지만 아직 열려있는 파일 (디스크 공간 미해제 원인)
lsof +L1
# 특정 포트를 사용 중인 프로세스 ★★★
lsof -i :80
lsof -i :8080
# 특정 사용자가 열고 있는 파일
lsof -u nginx
# 특정 디렉터리의 파일을 사용 중인 프로세스
lsof +D /var/log/
요약
| 명령어 | 용도 | 핵심 |
|---|---|---|
ps aux |
프로세스 목록 | 가장 기본적인 확인 |
pgrep |
이름으로 PID 검색 | pgrep -a (상세) |
pstree |
프로세스 트리 | 부모-자식 관계 파악 |
kill |
시그널 보내기 | -15(정상) → -9(강제) 순서 |
pkill |
이름으로 종료 | pkill -u user |
jobs / fg / bg |
작업 제어 | 포그라운드/백그라운드 전환 |
nohup |
터미널 독립 실행 | SSH 끊어져도 생존 |
lsof |
열린 파일/포트 확인 | lsof -i :PORT |
Chapter 14. 시스템 모니터링
장애가 발생하면 "지금 서버에서 무슨 일이 일어나고 있는가"를 빠르게 파악해야 한다.
모니터링은 크게 네 가지 리소스를 추적한다:
| 리소스 | 핵심 질문 | 주요 도구 |
|---|---|---|
| CPU | 어떤 프로세스가 CPU를 많이 쓰는가? | top, htop, mpstat |
| 메모리 | 메모리가 부족한가? 스왑이 발생하는가? | free, top, vmstat |
| 디스크 | I/O 병목이 있는가? 용량이 부족한가? | iostat, df, iotop |
| 네트워크 | 트래픽이 과도한가? 연결 문제가 있는가? | ss, netstat, iftop |
top — 실시간 시스템 모니터링
top은 리눅스의 작업 관리자(Task Manager)에 해당한다. CPU, 메모리 사용량과 프로세스 목록을 실시간으로 보여준다.
출력 해석
top - 14:30:00 up 15 days, 3:22, 2 users, load average: 0.52, 0.38, 0.41
Tasks: 215 total, 1 running, 213 sleeping, 0 stopped, 1 zombie
%Cpu(s): 5.2 us, 1.8 sy, 0.0 ni, 92.1 id, 0.3 wa, 0.0 hi, 0.6 si, 0.0 st
MiB Mem : 7812.5 total, 1245.3 free, 3210.8 used, 3356.4 buff/cache
MiB Swap: 2048.0 total, 1792.0 free, 256.0 used. 4230.1 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2345 nginx 20 0 456780 98760 12340 S 3.2 1.2 5:23.45 nginx
3456 mysql 20 0 1234560 567890 45678 S 2.1 7.1 12:34.56 mysqld
헤더 영역 상세 해석:
Load Average (0.52, 0.38, 0.41):
- 1분, 5분, 15분 평균 시스템 부하이다
- CPU 코어 수와 비교해야 의미가 있다. 4코어 시스템에서 load 4.0이면 100% 활용, 8.0이면 과부하
- load average가 코어 수를 초과하면 프로세스가 CPU를 기다리는 대기열(queue)이 존재한다는 뜻이다
# CPU 코어 수 확인
nproc
# 또는
grep -c processor /proc/cpuinfo
htop — 향상된 대화형 모니터링
top의 개선판이다. 색상, 그래프, 마우스 지원 등 사용성이 훨씬 좋다.
htop의 장점:
- CPU, 메모리 사용률을 그래프 바로 시각화
- 화살표 키와 마우스로 탐색 가능
- 프로세스를 직접 선택하여 시그널 전송 가능 (F9)
- 트리 보기 (F5)
- 필터링 (F4) / 검색 (F3)
- 설정 커스터마이징 (F2)
uptime — 시스템 가동 시간 & 부하
$ uptime
14:30:00 up 15 days, 3:22, 2 users, load average: 0.52, 0.38, 0.41
간결하게 load average를 확인할 때 자주 사용한다.
iostat — 디스크 I/O 모니터링
# 설치 (sysstat 패키지)
sudo apt install sysstat
# 확장 통계, 2초 간격
$ iostat -x 2
Device r/s w/s rkB/s wkB/s await %util
sda 12.50 45.30 200.00 1024.00 2.50 15.20
nvme0n1 5.20 10.10 320.00 512.00 0.30 3.40
장애 대응 체크리스트
서버에 문제가 발생했을 때 순서대로 확인하는 체계적 접근법
# 1. 전체 상황 파악 (30초 안에)
uptime # load average 확인
free -h # 메모리 상태
df -h # 디스크 용량
top -bn1 | head -20 # CPU/프로세스 상위
# 2. CPU 문제 의심 시
top # CPU 점유율 높은 프로세스 확인 (P키)
mpstat -P ALL 1 # CPU 코어별 사용률
# 3. 메모리 문제 의심 시
free -h # available 확인
vmstat 1 5 # si/so (스왑 활동) 확인
ps aux --sort=-%mem | head -10 # 메모리 점유 상위 프로세스
dmesg -T | grep -i oom # OOM killer 동작 여부
# 4. 디스크 문제 의심 시
df -h # 용량 확인
df -i # inode 확인
iostat -x 1 5 # I/O 활용률 확인
iotop # 프로세스별 I/O (별도 설치)
# 5. 네트워크 문제 의심 시
ss -tlnp # 리슨 중인 포트
ping 8.8.8.8 # 외부 연결 확인
curl -v http://localhost # 웹 서비스 응답 확인
Chapter 15. 서비스 관리 & systemd 심화
systemd
init 시스템의 역할
리눅스가 부팅되면 커널은 PID 1 프로세스를 실행한다.
이 PID 1 프로세스가 init 시스템이며, 이후 모든 서비스(데몬)를 시작하고 관리하는 역할을 한다.
systemd의 특징
- 병렬 부팅: 서비스들을 동시에 시작하여 부팅 속도가 빠르다
- 의존성 관리: 서비스 간 의존 관계를 선언적으로 정의한다
- 소켓 기반 활성화: 소켓 요청이 들어올 때 서비스를 자동 시작한다
- cgroup 통합: 프로세스 그룹을 추적하여 자식 프로세스까지 관리한다
- 저널(journal): 통합 로그 시스템(journald)을 내장한다
- 타이머: cron을 대체할 수 있는 스케줄링 기능을 제공한다
데몬(Daemon)
데몬(Daemon)은 백그라운드에서 실행되는 프로세스로, 사용자와 직접 상호작용하지 않는다.
웹 서버(nginx), 데이터베이스(mysql), SSH 서버(sshd) 등이 데몬이다.
관례적으로 이름 끝에 d를 붙인다 (sshd, httpd, dockerd 등).
systemctl — 서비스 관리의 핵심 명령어
systemctl은 systemd를 제어하는 통합 명령어이다.
서비스 상태 확인
# 서비스 상태 확인 ★★★
systemctl status nginx
# ● nginx.service - A high performance web server and a reverse proxy server
# Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
# Active: active (running) since Mon 2025-03-20 10:00:00 KST; 3 days ago
# Main PID: 2345 (nginx)
# Tasks: 5 (limit: 4915)
# Memory: 12.3M
# CGroup: /system.slice/nginx.service
# ├─2345 nginx: master process /usr/sbin/nginx
# ├─2346 nginx: worker process
# └─2347 nginx: worker process
#
# Mar 20 10:00:00 web-server systemd[1]: Starting A high performance web server...
# Mar 20 10:00:00 web-server systemd[1]: Started A high performance web server...
# 간단히 활성 여부만 확인
systemctl is-active nginx
# active
# 부팅 시 자동 시작 설정 여부
systemctl is-enabled nginx
# enabled
| 필드 | 의미 |
|---|---|
| Loaded | unit 파일 경로, enabled/disabled 여부 |
| Active | 현재 상태 — active(running), inactive(dead), failed |
| Main PID | 메인 프로세스의 PID |
| Tasks | 서비스가 사용 중인 스레드/프로세스 수 |
| Memory | 서비스가 사용 중인 메모리 |
| CGroup | 서비스에 속한 프로세스 트리 |
서비스 시작/중지/재시작
# 시작
sudo systemctl start nginx
# 중지
sudo systemctl stop nginx
# 재시작 (stop + start)
sudo systemctl restart nginx
# 설정만 다시 읽기 (중단 없이) ★
sudo systemctl reload nginx
# 모든 서비스가 reload를 지원하지는 않는다
# reload를 시도하고, 안 되면 restart
sudo systemctl reload-or-restart nginx
restart vs reload
restart: 프로세스를 완전히 종료 후 다시 시작한다. 순간적으로 서비스가 중단된다.reload: 프로세스를 종료하지 않고 설정 파일만 다시 읽는다. 서비스 중단이 없다. Nginx, Apache, HAProxy 등이 지원한다.
부팅 시 자동 시작 설정
# 부팅 시 자동 시작 활성화
sudo systemctl enable nginx
# 활성화 + 즉시 시작을 한 번에
sudo systemctl enable --now nginx
# 부팅 시 자동 시작 비활성화
sudo systemctl disable nginx
서비스 목록 조회
# 모든 활성 서비스 목록
systemctl list-units --type=service
# 실행 중인 서비스만
systemctl list-units --type=service --state=running
# 실패한 서비스 확인 ★★★
systemctl --failed
# 모든 서비스 (enabled/disabled 포함)
systemctl list-unit-files --type=service
journalctl — 로그 조회
systemd는 journald라는 통합 로그 시스템을 내장한다. journalctl은 이 저널 로그를 조회하는 명령어이다.
# 전체 로그 (페이저로 열림)
journalctl
# 특정 서비스의 로그 ★★★
journalctl -u nginx
journalctl -u nginx --since "1 hour ago"
# 최근 N줄만 보기
journalctl -u nginx -n 50
# 실시간 로그 추적 (tail -f와 동일) ★★★
journalctl -u nginx -f
# 이번 부팅 이후 로그만
journalctl -b
# 시간 범위 지정
journalctl --since "2025-03-23 14:00" --until "2025-03-23 15:00"
# 특정 우선순위 이상만 (에러 이상)
journalctl -p err
# 우선순위: emerg(0), alert(1), crit(2), err(3), warning(4), notice(5), info(6), debug(7)
# 커널 메시지만 (dmesg와 유사)
journalctl -k
# 디스크 사용량 확인
journalctl --disk-usage
# 오래된 로그 정리
sudo journalctl --vacuum-time=7d # 7일 이전 삭제
sudo journalctl --vacuum-size=500M # 500MB 이하로 유지
Unit 파일 — systemd의 설정 단위
Unit이란
systemd가 관리하는 모든 리소스를 Unit이라고 한다.
서비스뿐만 아니라 마운트, 타이머, 소켓 등도 Unit이다.
| Unit 타입 | 확장자 | 설명 |
|---|---|---|
| Service | .service |
데몬 프로세스 |
| Timer | .timer |
스케줄링 (cron 대안) |
| Socket | .socket |
소켓 기반 활성화 |
| Mount | .mount |
파일시스템 마운트 |
| Target | .target |
Unit들의 그룹 (부팅 레벨과 유사) |
| Path | .path |
파일시스템 경로 감시 |
Service Unit 파일 구조
# 기존 서비스의 unit 파일 확인
systemctl cat nginx.service
systemctl cat cron.service
[Unit]
Description=A high performance web server and a reverse proxy server
Documentation=man:nginx(8)
After=network.target remote-fs.target nss-lookup.target
Wants=network-online.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
[Unit] 섹션 — 서비스의 메타데이터와 의존성
[Service] 섹션 — 서비스 실행 방식
[Install] 섹션 — enable/disable 시 동작
요약
| 명령어 | 용도 | 가장 많이 쓰는 형태 |
|---|---|---|
systemctl status |
서비스 상태 확인 | systemctl status nginx |
systemctl start/stop |
서비스 시작/중지 | |
systemctl restart |
서비스 재시작 | |
systemctl reload |
설정만 다시 읽기 | 서비스 중단 없음 |
systemctl enable --now |
자동 시작 + 즉시 시작 | |
systemctl --failed |
실패한 서비스 확인 | 장애 대응 시 필수 |
systemctl daemon-reload |
unit 파일 변경 반영 | unit 수정 후 필수 |
systemctl edit |
서비스 설정 오버라이드 | |
journalctl -u X -f |
실시간 서비스 로그 |
Chapter 16. 로그 관리
로그가 왜 중요한가
로그는 시스템과 애플리케이션이 "무슨 일이 일어났는지"를 기록한 텍스트이다.
- 애플리케이션이 죽었다 → 로그에서 에러 메시지 확인
- 보안 침해가 의심된다 → 인증 로그에서 비정상 접근 확인
- 성능이 저하되었다 → 로그에서 느린 쿼리, 타임아웃 패턴 확인
- 배포 후 문제가 생겼다 → 배포 시점 전후 로그 비교
리눅스 로그 체계
두 가지 로그 시스템
현대 리눅스에는 두 가지 로그 시스템이 공존한다:
① rsyslog (전통적 방식)
- 텍스트 파일로
/var/log/에 저장 - 오래된 표준이지만 여전히 널리 사용
cat,grep,tail등으로 직접 읽을 수 있다
② journald (systemd 방식)
- 바이너리 형식으로 저장 (직접 읽을 수 없음)
journalctl명령어로 조회- 구조화된 메타데이터(서비스명, PID, 우선순위 등) 포함
- systemd 서비스의 stdout/stderr를 자동 수집
대부분의 배포판에서 두 시스템이 동시에 동작한다.
journald가 수집한 로그를 rsyslog가 전달받아 /var/log/에 텍스트 파일로 저장하는 구조이다.
# 로그 디렉터리 구조 확인
ls -la /var/log/
로그 로테이션 (logrotate)
로그 파일은 시간이 지나면 계속 커진다. 방치하면 디스크를 가득 채운다. logrotate는 로그 파일을 주기적으로 회전(rotate)시켜 관리하는 도구이다.
access.log ← 현재 기록 중
access.log.1 ← 어제 (로테이션 됨)
access.log.2.gz ← 그저께 (압축됨)
access.log.3.gz ← 3일 전
...
access.log.7.gz ← 7일 전 (이후 삭제)
logrotate 테스트 및 수동 실행
# 설정 문법 검증 (실제로 실행하지 않음)
sudo logrotate -d /etc/logrotate.d/nginx
# 강제 수동 실행
sudo logrotate -f /etc/logrotate.d/nginx
# 전체 logrotate 수동 실행
sudo logrotate -f /etc/logrotate.conf
Chapter 17. 스케줄링
특정 시간 또는 주기적으로 명령어나 스크립트를 자동 실행하는 것이다.
- 매일 새벽 3시 데이터베이스 백업
- 매 5분마다 서비스 헬스체크
- 매월 1일 리포트 생성
리눅스에서는 cron(전통적)과 systemd timer(현대적), 두 가지 방식으로 스케줄링할 수 있다.
cron — 전통적 작업 스케줄러
cron은 유닉스 시대부터 사용되어 온 작업 스케줄러 데몬이다. 백그라운드에서 항상 실행되며, 설정된 시간이 되면 명령어를 자동 실행한다. 설정 파일을 crontab(cron table)이라고 한다.
crontab 시간 형식
┌───────── 분 (0-59)
│ ┌─────── 시 (0-23)
│ │ ┌───── 일 (1-31)
│ │ │ ┌─── 월 (1-12)
│ │ │ │ ┌─ 요일 (0-7, 0과 7은 일요일)
│ │ │ │ │
* * * * * command
| 문자 | 의미 | 예시 |
|---|---|---|
* |
모든 값 | * * * * * → 매분 |
, |
여러 값 | 1,15 * * * * → 1분, 15분 |
- |
범위 | 1-5 * * * * → 1~5분 |
/ |
간격 | */5 * * * * → 5분마다 |
자주 쓰는 스케줄 예시
# 매분 실행
* * * * * /path/to/script.sh
# 매시 정각
0 * * * * /path/to/script.sh
# 매일 새벽 3시
0 3 * * * /path/to/script.sh
# 매일 새벽 3시 30분
30 3 * * * /path/to/script.sh
# 5분마다
*/5 * * * * /path/to/script.sh
# 매주 일요일 자정
0 0 * * 0 /path/to/script.sh
# 매월 1일 새벽 2시
0 2 1 * * /path/to/script.sh
# 평일(월~금) 오전 9시
0 9 * * 1-5 /path/to/script.sh
# 매시 0분, 30분 (30분마다)
0,30 * * * * /path/to/script.sh
crontab 관리
# 현재 사용자의 crontab 편집 ★★★
crontab -e
# 현재 사용자의 crontab 보기
crontab -l
# 현재 사용자의 crontab 전체 삭제 (주의!)
crontab -r
# 특정 사용자의 crontab 편집 (root만 가능)
sudo crontab -u deploy -e
# 특정 사용자의 crontab 보기
sudo crontab -u deploy -l
crontab 작성 시 주의사항
① 환경변수가 다르다
cron은 사용자의 로그인 셸과 다른 환경에서 실행된다. PATH, HOME 등이 최소한으로 설정되어 있다. 그래서 crontab에서는 절대 경로를 사용해야 한다.
# ✗ 잘못된 예 — PATH에 /usr/local/bin이 없을 수 있음
* * * * * python3 script.py
# ✓ 올바른 예 — 절대 경로 사용
* * * * * /usr/bin/python3 /home/devops/script.py
# 또는 crontab 상단에 PATH를 직접 설정
PATH=/usr/local/bin:/usr/bin:/bin
* * * * * python3 /home/devops/script.py
② 출력을 반드시 처리하기
cron 작업의 stdout/stderr는 기본적으로 사용자에게 메일로 전송된다. 메일 시스템이 없으면 에러가 쌓인다.
# 출력을 로그 파일로 저장
0 3 * * * /opt/backup.sh >> /var/log/backup.log 2>&1
# 출력을 아예 버리기
0 3 * * * /opt/backup.sh > /dev/null 2>&1
# 에러만 로그로 남기기
0 3 * * * /opt/backup.sh > /dev/null 2>> /var/log/backup-error.log
③ 중복 실행 방지
작업이 오래 걸려서 다음 실행 시점에도 아직 실행 중이면 중복 실행된다. flock으로 방지할 수 있다.
# flock을 사용한 중복 실행 방지
*/5 * * * * flock -n /tmp/myjob.lock /opt/slow_task.sh
# -n: 이미 잠겨있으면 즉시 종료 (대기하지 않음)
systemd timer — 현대적 스케줄러
왜 systemd timer를 쓰는가
| 기능 | cron | systemd timer |
|---|---|---|
| 의존성 관리 | 없음 | 다른 서비스에 의존 가능 |
| 로그 | 직접 리다이렉션 필요 | journalctl로 통합 |
| 실행 기록 | 별도 관리 필요 | systemctl list-timers로 확인 |
| 놓친 실행 처리 | 서버 꺼져있으면 놓침 | Persistent=true로 보장 |
| 초 단위 스케줄링 | 불가 (분 단위) | 가능 |
| 리소스 제한 | 불가 | MemoryMax, CPUQuota 등 가능 |
timer 구조
systemd timer는 두 개의 unit 파일로 구성된다:
.timer— 언제 실행할지 (스케줄).service— 무엇을 실행할지 (작업 내용)
OnCalendar 시간 형식
# 형식: 요일 년-월-일 시:분:초
OnCalendar=*-*-* 03:00:00 # 매일 3시
OnCalendar=Mon *-*-* 09:00:00 # 매주 월요일 9시
OnCalendar=*-*-01 02:00:00 # 매월 1일 2시
OnCalendar=*-01-01 00:00:00 # 매년 1월 1일
OnCalendar=*-*-* *:*:00 # 매분
OnCalendar=*-*-* *:00/30:00 # 30분마다
OnCalendar=hourly # 매시간
OnCalendar=daily # 매일 자정
OnCalendar=weekly # 매주 월요일 자정
# 시간 표현식 검증
systemd-analyze calendar "*-*-* 03:00:00"
# Original form: *-*-* 03:00:00
# Next elapse: Tue 2025-03-24 03:00:00 KST
at — 일회성 예약 실행
특정 시간에 한 번만 실행할 작업을 예약한다. cron은 반복 실행, at은 일회성이다.
# 설치 (없는 경우)
sudo apt install at
# 10분 후에 실행
at now + 10 minutes
> /opt/scripts/deploy.sh
> [Ctrl+D]
# 특정 시간에 실행
at 3:00 AM
> /opt/scripts/maintenance.sh
> [Ctrl+D]
# 내일 오후 5시에 실행
at 5:00 PM tomorrow
> echo "Reminder" | mail -s "Task due" admin@example.com
> [Ctrl+D]
# 예약된 작업 목록
atq
# 예약 취소
atrm <작업번호>
'Learning Log' 카테고리의 다른 글
| [멋사 클라우드 5기] Day 39 - Docker Image & Container (0) | 2026.04.06 |
|---|---|
| [멋사 클라우드 5기] Day 37 & 38 - Linux (3) (0) | 2026.03.25 |
| [멋사 클라우드 5기] Day 33 & 34 - Linux (1) (1) | 2026.03.25 |
| [멋사 클라우드 5기] Day 31 & 32 - OSI 7 Layers (1) | 2026.03.16 |
| [멋사 클라우드 5기] Day 29 & 30 - 게시판 구현을 통해 확인한 Spring Data JPA (0) | 2026.03.12 |