콘텐츠로 이동

Linux Basics

분류: Layer 4 - CS 기초: 운영체제 & Linux

Linux는 서버에서 프로그램을 실행하고, 파일과 권한을 관리하고, 프로세스에 CPU·메모리·네트워크 같은 자원을 빌려주는 운영체제다.

CLI(Command Line Interface, 명령줄 인터페이스)는 그 운영체제와 대화하는 기본 언어다. tail, grep, ps, chmod, systemctl 같은 명령어 자체가 목적은 아니다. 명령어는 “지금 어떤 파일이 있고, 어떤 프로세스가 떠 있으며, 어떤 권한과 설정 때문에 실행이 막혔는가”를 관찰하는 도구다.

서버 장애를 처음 보면 증상은 대부분 앱 에러처럼 보인다. 하지만 실제 원인은 더 낮은 층에 있을 수 있다.

  • 포트가 이미 점유되어 서버가 시작하지 못한다.
  • .env가 없거나 환경변수가 서비스 프로세스에 전달되지 않았다.
  • 파일 권한이 맞지 않아 배포 스크립트나 SSH 키를 읽지 못한다.
  • 로그 파일은 계속 쌓이는데 디스크 또는 inode가 먼저 고갈된다.
  • 배포 중 SIGTERM을 처리하지 못해 in-flight 요청이 잘린다.
  • 서비스가 죽었는데 systemd가 재시작했는지, 컨테이너 런타임이 재시작했는지 구분하지 못한다.

Linux basics의 목표는 명령어를 많이 외우는 것이 아니라, 이런 증상을 파일, 프로세스, 권한, 환경변수, 시그널, 서비스 관리자 중 어느 문제로 봐야 하는지 나누는 것이다.

2.5 Linux가 등장한 이유 — Unix·MINIX의 한계와 해결 메커니즘

섹션 제목: “2.5 Linux가 등장한 이유 — Unix·MINIX의 한계와 해결 메커니즘”

1991년 Linus Torvalds가 Linux 커널을 공개했을 때의 빈틈은 “Unix 같은 개발·운영 모델을 개인 PC에서 자유롭게 쓰고 고칠 수 있는가”였다.

1991년 Linux가 비집고 들어온 빈틈

AT&T Unix

소스 라이선스 비용이 높고 학생·개인이 자유롭게 수정하기 어려웠다.

Unix의 파일·프로세스 모델은 배우고 싶지만 개인 PC에서 직접 고치며 쓰기 어려웠던 상황

MINIX

교육용 목적이 강했고, 당시에는 범용 서버 워크로드를 자유롭게 확장하기 어려웠다.

학습용 OS를 넘어 실제 워크로드를 돌릴 Unix 호환 커널이 필요했던 상황

386BSD / BSD

법적 분쟁과 출시 지연으로 x86에서 바로 쓸 수 있는 자유 Unix 계열 OS의 공백이 있었다.

386 PC에서 재배포 가능한 Unix 계열 OS가 필요했던 상황

Linux가 이 한계를 푼 방식은 지금 서버 운영의 기본 가정이 되었다.

  • GPL 라이선스: 커널을 수정·재배포할 수 있었기 때문에 하드웨어, 서버, 클라우드 환경으로 빠르게 확장됐다.
  • POSIX 호환 인터페이스: Unix의 파일, 프로세스, 시그널, 표준 입출력 모델을 유지해 도구와 스크립트가 이식되기 쉬웠다.
  • 실용적인 모놀리식 커널: 파일 시스템, 네트워크, 프로세스 관리 같은 핵심 기능을 같은 커널 안에서 빠르게 연결했다.

이 토픽이 다루는 inode, PID, signal, permission, systemd, SSH는 모두 이 운영체제 모델 위에 있다. Docker, ECS, Kubernetes도 별도 세계가 아니라 Linux 커널의 프로세스와 자원 경계를 빌려 쓴다. 단, cgroup, namespace, 스케줄러, system call 내부는 이 문서에서 깊게 들어가지 않고 뒤의 L4 문서로 넘긴다.

Linux를 처음 배울 때는 아래 다섯 질문으로 시작하면 된다.

질문Linux 개념대표 증상
무엇이 실행 중인가?프로세스, PID, 부모/자식 관계포트 충돌, 좀비 프로세스, CPU 사용률 증가
데이터와 설정은 어디 있는가?파일 시스템, 경로, inode, /proc파일 없음, inode 고갈, 로그 누적
누가 접근할 수 있는가?사용자, 그룹, rwx 권한, 소유자Permission denied, SSH 키 거부
설정은 어떻게 주입되는가?환경변수, 셸, systemd unit, 컨테이너DB URL 누락, 포트 설정 불일치
어떻게 멈추고 다시 뜨는가?시그널, systemd, 컨테이너 런타임배포 중 5xx, exit 137, 서비스 재시작

이 표의 개념을 먼저 잡으면 명령어가 무작위 목록처럼 보이지 않는다. ps는 프로세스를 보는 도구이고, stat은 inode 메타데이터를 보는 도구이며, journalctl은 systemd가 수집한 서비스 로그를 보는 도구다.

4. CLI와 파이프 — 텍스트 도구를 조합하는 방식

섹션 제목: “4. CLI와 파이프 — 텍스트 도구를 조합하는 방식”

CLI는 명령어를 한 줄씩 입력하는 UI다. Linux 도구가 강한 이유는 각 명령이 보통 텍스트를 입력받고 텍스트를 출력하기 때문에, 파이프(|)로 작은 도구를 연결할 수 있기 때문이다.

세 가지 표준 통로를 먼저 기억한다.

이름번호의미
stdin(Standard Input)0명령어가 입력을 읽는 통로
stdout(Standard Output)1정상 결과를 쓰는 통로
stderr(Standard Error)2에러 메시지를 쓰는 통로

작은 예제로 보면 감이 잡힌다.

Terminal window
# app.log에서 ERROR가 포함된 줄 수 세기
grep "ERROR" app.log | wc -l
# 예상 출력: 42
# access.log의 첫 번째 컬럼(IP) 기준으로 요청 수 상위 10개 보기
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 예상 출력:
# 1523 203.0.113.42
# 892 198.51.100.7

여기서 핵심은 grep, awk, sort를 모두 외우는 것이 아니다. 각 도구가 작은 일을 하고, 파이프가 출력과 입력을 이어 주기 때문에 로그 파일을 별도 프로그램 없이 좁혀 볼 수 있다는 점이다.

실패 신호도 있다. 로그가 너무 크면 cat app.log | grep ...처럼 전체 파일을 먼저 쏟아내는 방식이 느릴 수 있다. 실시간 확인에는 tail -f, 압축 로그에는 zgrep, systemd 서비스 로그에는 journalctl처럼 “어떤 저장소를 읽는가”에 맞는 도구를 고른다.

5. 파일 시스템과 inode — 이름과 실체를 분리한다

섹션 제목: “5. 파일 시스템과 inode — 이름과 실체를 분리한다”

Linux 파일 시스템은 /에서 시작하는 트리 구조다. /etc에는 설정, /var/log에는 로그, /tmp에는 임시 파일, /home에는 사용자 파일이 주로 놓인다. 컨테이너 안에서도 이 트리 구조를 본다.

Linux에서 “모든 것이 파일이다”라는 말은 실제 세계의 모든 것이 일반 파일이라는 뜻이 아니다. 파일처럼 열고 읽고 쓰는 공통 인터페이스를 제공한다는 뜻이다. 예를 들어 /proc/1/status는 디스크에 저장된 일반 파일이 아니라, PID 1 프로세스 상태를 커널이 파일처럼 보여주는 가상 파일이다.

inode(index node)는 파일의 실체를 가리키는 메타데이터 구조다. inode에는 파일 이름을 제외한 많은 정보가 들어간다.

inode에 저장되는 것inode에 저장되지 않는 것
파일 크기, 권한, 소유자파일 이름
생성/수정/접근 시간파일 내용 자체
데이터 블록 위치디렉터리 경로
링크 카운트(하드 링크 수)

파일 이름은 디렉터리에 저장된다. 디렉터리는 대략 (파일 이름 -> inode 번호) 매핑을 담는 파일이다. 이 분리 덕분에 같은 inode를 여러 이름으로 가리키는 하드 링크가 가능하고, 파일 이동이 디렉터리 항목 변경만으로 끝날 수 있다.

Terminal window
# inode 번호 확인
ls -li /etc/hostname
# 예상 출력:
# 131074 -rw-r--r-- 1 root root 12 Jan 15 09:00 /etc/hostname
# └────┘ inode 번호
# inode 메타데이터 확인
stat /etc/hostname
# 예상 출력 일부:
# File: /etc/hostname
# Size: 12
# Inode: 131074 Links: 1
# Access: (0644/-rw-r--r--) Uid: (0/root)

반례: 디스크 용량이 남아도 파일 생성이 실패할 수 있다. 작은 파일을 수백만 개 만들면 블록 공간보다 inode가 먼저 고갈될 수 있다. 이때 df -h는 여유가 있어 보이지만 df -iIUse%가 100%에 가까워진다.

Terminal window
df -h
# /dev/xvda1 20G 12G 8G 60% /
df -i
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/xvda1 1310720 1310700 20 100% /

운영 판단 기준은 단순하다. 대용량 파일 몇 개가 문제면 du -sh로 큰 디렉터리를 찾고, 아주 작은 파일이 폭증했다면 df -ifind로 파일 개수와 생성 위치를 본다. Docker 이미지·레이어도 결국 많은 파일과 overlay layer 메타데이터를 파일시스템 위에 쌓기 때문에 inode와 디스크 공간을 함께 소모할 수 있다. 컨테이너 파일시스템 내부 구조는 L5 Docker와 L4 cgroups/namespace 문서에서 더 깊게 다룬다.

6. 프로세스와 PID — 실행 중인 프로그램을 식별한다

섹션 제목: “6. 프로세스와 PID — 실행 중인 프로그램을 식별한다”

프로세스는 실행 중인 프로그램의 인스턴스다. 같은 node dist/main.js를 두 번 실행하면 프로그램 파일은 같아도 프로세스는 두 개다. 커널은 각 프로세스에 PID(Process ID)를 부여하고, 프로세스마다 메모리 공간, 열린 파일, 환경변수, 현재 작업 디렉터리 같은 실행 상태를 관리한다.

Terminal window
ps aux | grep node
# 예상 출력:
# deploy 1234 0.3 2.1 812340 87432 ? Sl 09:01 1:23 node dist/main.js

여기서 1234가 PID다. 포트 충돌, CPU 점유, 메모리 증가, 강제 종료 판단은 대부분 “어느 PID가 원인인가”를 찾는 일에서 시작한다.

Terminal window
# 3000번 포트를 듣고 있는 프로세스 확인
lsof -i :3000
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# node 1234 deploy 23u IPv4 12345 0t0 TCP *:3000 (LISTEN)
# 현대 Linux에서 소켓 상태 확인
ss -tlnp
# State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# LISTEN 0 511 0.0.0.0:3000 0.0.0.0:* users:(("node",pid=1234,fd=23))

여기서 FD(file descriptor)는 열린 파일이나 소켓을 가리키는 프로세스 안의 번호다. fd=23은 이 Node 프로세스가 23번 핸들로 listen socket을 들고 있다는 뜻이고, 유저 프로그램이 fd로 커널 자원에 접근하는 방식은 system-call-interrupt.mdx에서 더 깊게 다룬다.

자식 프로세스가 종료했는데 부모가 wait() 또는 waitpid()로 종료 상태를 수거하지 않으면 자식은 zombie(defunct) 상태로 남는다. zombie는 이미 실행을 끝냈기 때문에 CPU와 메모리는 거의 쓰지 않는다. 대신 process table의 PID 슬롯을 차지한다.

Terminal window
ps -eo pid,ppid,stat,cmd | grep -w Z
# 예상 출력:
# 5678 1234 Z+ [node] <defunct>
ps axo stat | grep -c Z
# 예상 출력: 12

이 장애가 어려운 이유는 조용히 누적되기 때문이다. CPU·메모리 대시보드에는 티가 잘 나지 않다가 PID를 더 만들 수 없을 때 fork(): Resource temporarily unavailable 같은 에러로 터진다. Linux의 kernel.pid_max 기본값은 환경마다 다르지만, 64bit 시스템에서는 최대 4,194,304까지 설정할 수 있다. 수치가 커도 무한하지 않다는 점이 핵심이다.

kill -9 <zombie_pid>는 효과가 없다. zombie는 이미 죽어서 시그널을 받을 실행 주체가 없다. 부모 프로세스가 종료되면 PID 1이 입양해 정리하거나, 부모 코드가 자식 종료를 수거하도록 고쳐야 한다. Docker 컨테이너에서 PID 1이 단순 셸 스크립트이고 자식 수거를 하지 않으면 zombie가 쌓일 수 있어 docker run --init 또는 tini 같은 init wrapper가 쓰인다.

zombie 1~2개가 순간적으로 보였다가 사라지는 것은 배포나 배치 작업 종료 타이밍에서 볼 수 있다. 위험 신호는 같은 부모 아래에서 zombie 수가 계속 증가하거나, 트래픽이 없는데도 Z 상태가 누적되는 경우다. 이때는 zombie PID를 죽이는 것이 아니라 부모 프로세스가 자식 종료를 wait() 계열로 수거하는지 본다.

Orphan은 부모가 먼저 죽어 고아가 된 프로세스다. 이 경우 PID 1이 새 부모가 되어 관리한다. zombie와 orphan은 둘 다 부모/자식 관계 문제지만, zombie는 “죽었는데 수거되지 않은 자식”, orphan은 “살아 있는데 부모가 사라진 자식”으로 구분하면 된다.

7. 시그널 — 프로세스에게 보내는 제어 메시지

섹션 제목: “7. 시그널 — 프로세스에게 보내는 제어 메시지”

시그널(signal)은 커널이 프로세스에 보내는 짧은 제어 메시지다. “종료해라”, “중단됐다”, “자식이 종료됐다” 같은 이벤트를 프로세스에 알린다.

kill이라는 명령 이름 때문에 항상 죽이는 명령처럼 보이지만, 실제로는 시그널을 보내는 도구다.

시그널번호프로세스가 처리 가능한가보통의 의미
SIGTERM15가능정리하고 정상 종료하라는 요청
SIGKILL9불가능커널이 즉시 제거하는 최후 수단
SIGINT2가능터미널에서 Ctrl+C로 중단 요청
SIGCHLD17가능자식 프로세스 상태 변화 알림

운영에서 가장 중요한 판단은 SIGTERM과 SIGKILL의 차이다.

상황먼저 쓸 것이유
정상 배포, 서비스 재시작SIGTERM요청 처리, DB 연결 반납, 파일 플러시 기회를 준다
graceful shutdown이 끝나지 않음SIGTERM -> 대기 -> SIGKILL무한 대기를 막되 데이터 손상 가능성을 늦춘다
deadlock, 무한 루프, 응답 없음SIGKILLSIGTERM 핸들러가 실행되지 않거나 무시될 수 있다
zombie 프로세스zombie PID에 보내도 무의미이미 죽은 프로세스이므로 부모 수거 문제를 봐야 한다
Terminal window
kill 1234
# 기본값은 SIGTERM
sleep 15
kill -0 1234 2>/dev/null && kill -9 1234
# 아직 살아 있으면 SIGKILL

SIGKILL도 항상 “즉시 사라짐”을 보장하지는 않는다. 프로세스가 커널 안에서 uninterruptible sleep, 즉 D 상태로 디스크, NFS, device I/O를 기다리는 중이면 시그널 처리가 지연될 수 있다. 이때는 kill -9를 반복하기보다 I/O 경로, mount/device 상태, kernel wait channel을 의심한다.

컨테이너 환경도 이 추상화를 그대로 가져간다.

런타임SIGTERM -> SIGKILL 기본 간격자주 생기는 함정
Docker stop10초로컬에서는 빨리 죽어 prod와 다르게 보일 수 있다
Kubernetes pod30초(terminationGracePeriodSeconds)grace period 안에서 preStop hook과 종료 신호가 어떻게 처리되는지 확인해야 한다
AWS ECS task30초(stopTimeout, 최대 120초)시간 초과 시 exit code 137로 보일 수 있다
systemd보통 90초(TimeoutStopSec)unit 설정에 따라 더 짧거나 길 수 있다

Node.js/NestJS 서버라면 SIGTERM 핸들러에서 새 요청을 막고, 진행 중 요청을 기다리고, DB 커넥션을 닫는 흐름이 필요하다. 이 문서에서는 핸들러 구현보다 “왜 SIGKILL을 바로 쓰면 위험한가”를 이해하는 것이 우선이다. 프로세스와 스레드의 내부 실행 의미는 content/topics/L4/process-thread.mdx에서 더 깊게 다룬다.

예를 들어 Kubernetes rolling update 중 기존 pod가 SIGTERM을 받았는데 NestJS가 새 요청 차단과 in-flight 대기를 하지 않으면, grace period가 끝난 뒤 SIGKILL로 요청이 잘려 5xx가 생길 수 있다. 그래서 graceful shutdown은 “종료 핸들러가 있다”가 아니라 “로드밸런서에서 빠지고, 진행 중 요청을 마치고, 연결을 정리하는 순서가 맞다”는 계약이다.

8. 환경변수 — 프로세스 시작 시 주입되는 설정

섹션 제목: “8. 환경변수 — 프로세스 시작 시 주입되는 설정”

환경변수(environment variable)는 프로세스가 시작될 때 전달받는 문자열 key-value 설정이다. DB 주소, 포트, API key, 실행 모드처럼 배포 환경마다 달라지는 값을 코드 밖으로 빼기 위해 사용한다.

Terminal window
export PORT=3000
printenv PORT
# 3000
echo "$DATABASE_URL"
# postgresql://user:pass@host:5432/app

중요한 점은 환경변수가 실행 중인 프로세스의 설정 스냅샷이라는 것이다. 터미널에서 export DATABASE_URL=...을 새로 해도 이미 떠 있는 systemd 서비스나 컨테이너 프로세스의 환경이 자동으로 바뀌지는 않는다. 서비스를 재시작하거나, systemd unit의 EnvironmentFile, Docker/Kubernetes의 env 설정을 바꿔 새 프로세스를 띄워야 반영된다.

작은 실패 예시:

Terminal window
# 현재 셸에는 값이 있다.
echo "$DATABASE_URL"
# 그런데 systemd 서비스는 다른 환경으로 떠 있을 수 있다.
systemctl status my-nestapp
journalctl -u my-nestapp -n 50 --no-pager
# Error: DATABASE_URL is required

판단 기준은 다음과 같다.

상황먼저 볼 곳
로컬 터미널에서만 실행 실패현재 셸의 printenv, .env 로딩 여부
systemd 서비스에서만 실패unit 파일의 Environment, EnvironmentFile
컨테이너에서만 실패Docker/Kubernetes/ECS의 env 주입 설정
값은 있는데 권한 오류가 남음환경변수가 아니라 파일 권한이나 네트워크 접근성

9. 권한 — 누가 읽고, 쓰고, 실행할 수 있는가

섹션 제목: “9. 권한 — 누가 읽고, 쓰고, 실행할 수 있는가”

Linux 권한은 파일마다 소유자(owner), 그룹(group), 기타 사용자(others)에 대해 읽기(r), 쓰기(w), 실행(x) 권한을 따로 둔다.

Terminal window
ls -la deploy.sh
# -rw-r--r-- 1 deploy deploy 512 Jan 15 10:00 deploy.sh
# │││ │││ │││
# │││ │││ └┴┴ 기타 사용자: 읽기만
# │││ └┴┴ 그룹: 읽기만
# └┴┴ 소유자: 읽기/쓰기

숫자 권한은 r=4, w=2, x=1을 더한 값이다. 754는 소유자 7(rwx), 그룹 5(r-x), 기타 4(r--)를 뜻한다.

Terminal window
chmod +x deploy.sh
# 실행 권한 추가
chmod 600 .env
# 민감 파일: 소유자만 읽기/쓰기
chmod 400 my-key.pem
# SSH private key: 소유자 읽기만

반례: chmod 777은 문제 해결이 아니라 경계 해제다. 777은 모든 사용자에게 읽기·쓰기·실행 권한을 준다. “일단 되게 하자”는 임시 처방처럼 보이지만, 원인은 보통 소유자나 그룹이 잘못된 것이다.

문제 상황777의 위험더 나은 방향
배포 스크립트 실행 불가아무나 스크립트를 수정·실행 가능소유자/그룹 조정 후 750 또는 755
.env 읽기 실패비밀번호·API key 노출서비스 사용자 소유 + 600
업로드 디렉터리 쓰기 실패웹셸 업로드 같은 공격면 증가웹서버 사용자만 쓰기 허용
SSH 키가 거부됨키 파일을 넓게 열수록 SSH가 거부함chmod 400 my-key.pem

실패 신호는 Permission denied 하나로 뭉쳐 보인다. 그래서 먼저 “현재 사용자가 누구인가(whoami), 파일 소유자가 누구인가(ls -l), 필요한 권한이 읽기인지 실행인지”를 나눠야 한다.

10. systemd와 journalctl — 서버 프로세스의 생명주기 관리자

섹션 제목: “10. systemd와 journalctl — 서버 프로세스의 생명주기 관리자”

systemd는 Linux 서버에서 서비스를 시작하고, 중지하고, 부팅 시 자동 실행하고, 실패 시 재시작 정책을 적용하는 서비스 관리자다. EC2 같은 서버에 직접 앱을 배포하면 nginx, sshd, my-nestapp 같은 서비스가 systemd unit으로 관리되는 경우가 많다.

컨테이너 환경에서는 Docker, ECS, Kubernetes가 서비스 생명주기를 더 바깥에서 관리한다. 그래도 systemd를 알면 “서비스 프로세스는 누가 띄우고, 어떤 환경변수와 사용자로 실행하며, 죽었을 때 누가 다시 띄우는가”라는 기본 질문을 배울 수 있다.

Terminal window
systemctl status my-nestapp
# Active: active (running) 또는 failed 확인
journalctl -u my-nestapp -n 50 --no-pager
# 해당 서비스의 최근 로그 50줄
journalctl -u my-nestapp -f
# 실시간 서비스 로그

systemd 장애를 볼 때는 명령어 순서보다 이 네 가지를 확인한다.

확인할 것왜 보는가실패 신호
ExecStart실제 실행 파일과 경로가 맞는가No such file, Cannot find module
User어떤 Linux 사용자 권한으로 실행되는가설정 파일/로그 파일 Permission denied
EnvironmentFile필요한 환경변수가 서비스에 들어갔는가DB URL, PORT, API key 누락
restart 정책죽은 뒤 자동 재시작되어 증상이 숨는가로그에 같은 실패가 반복되고 PID가 계속 바뀜

서비스 로그가 파일에 직접 쓰이면 tail -f /var/log/...가 맞고, systemd journal로 수집되면 journalctl -u <서비스명>이 맞다. 둘은 비슷해 보여도 읽는 저장소가 다르다.

11. SSH와 서버 접근 — 접속은 운영 권한의 시작점이다

섹션 제목: “11. SSH와 서버 접근 — 접속은 운영 권한의 시작점이다”

SSH(Secure Shell)는 원격 서버에 암호화된 터미널 세션을 여는 프로토콜이다. EC2에 직접 들어가거나, 배포 서버에서 로그를 확인하거나, 응급 진단을 할 때 만난다.

기본 모델은 세 가지다.

방식의미주의점
비밀번호 SSH사용자 비밀번호로 로그인무차별 대입에 약해 프로덕션에서는 피한다
공개키 SSHprivate key로 본인을 증명키 파일 권한이 넓으면 SSH가 거부할 수 있다
AWS SSM Session ManagerSSH 포트 없이 IAM 권한으로 세션을 연다AWS 에이전트/IAM 설정이 필요하다

작은 실패 기준:

메시지먼저 의심할 것
Connection timed out보안 그룹, 방화벽, private subnet, 라우팅
Permission denied (publickey)잘못된 키, 잘못된 사용자명, 키 파일 권한
Host key verification failed서버 재생성/IP 재사용으로 known_hosts 불일치

운영 원칙은 “접속할 수 있느냐”보다 “누가 어떤 권한으로 접속할 수 있느냐”가 더 중요하다. 가능하면 root 직접 로그인과 비밀번호 로그인은 피하고, 공개키 또는 SSM처럼 접근 주체와 감사 기록을 남길 수 있는 방식을 쓴다.

SSH는 보통 22번 포트, 네트워크 도달성, 키 파일 관리가 운영 표면이 된다. SSM Session Manager는 SSH 포트를 열지 않고 IAM 권한과 감사 로그 경로로 접속하므로, 같은 “서버 접속”이라도 노출되는 위험과 통제 지점이 다르다.

12. 작은 장애 시나리오로 묶어 보기

섹션 제목: “12. 작은 장애 시나리오로 묶어 보기”

증상:

Terminal window
./deploy.sh
# bash: ./deploy.sh: Permission denied

개념으로 쪼개면 다음 순서다.

  1. 실행 파일인가? ls -l deploy.sh에서 x가 있는지 본다.
  2. 현재 사용자가 소유자나 그룹에 포함되는가? whoami, id, ls -l을 본다.
  3. 읽기 오류인지 실행 오류인지 구분한다. 스크립트 실행에는 파일의 x 권한과 인터프리터 접근 권한이 모두 필요하다.
  4. 무작정 chmod 777 대신 소유자·그룹을 맞추고 필요한 권한만 준다.

증상:

Terminal window
touch /tmp/a
# touch: cannot touch '/tmp/a': No space left on device

두 가지 공간을 나눠 본다.

Terminal window
df -h
# 블록 공간 확인
df -i
# inode 공간 확인

df -h가 100%면 큰 로그, 캐시, 이미지 파일을 의심한다. df -i가 100%면 작은 파일이 너무 많이 생긴 것이다. 둘은 원인도 해결책도 다르다.

증상:

Error: listen EADDRINUSE: address already in use :::3000

포트는 커널이 관리하는 네트워크 자원이고, 하나의 IP:port 조합은 보통 하나의 프로세스가 listen한다.

Terminal window
lsof -i :3000
# 또는
ss -tlnp | grep ':3000'

바로 kill -9를 치기 전에, 해당 PID가 내가 죽여도 되는 서비스인지 확인한다. 정상 배포 중이면 SIGTERM으로 graceful shutdown을 먼저 시도하고, 로컬 개발 중 남은 프로세스라면 빠르게 종료해도 된다. 연결 수가 비정상적으로 많아 보일 때는 포트 하나만 보지 말고 established connection 규모도 같이 본다. 예를 들어 ss -tn state established | wc -l이 평소 100 안팎이던 서비스에서 갑자기 5,000으로 뛰면, 포트 충돌보다 연결 누수나 downstream 지연을 먼저 의심한다.

12.4 systemd 서비스가 시작되지 않는다

섹션 제목: “12.4 systemd 서비스가 시작되지 않는다”

증상:

Terminal window
systemctl status my-nestapp
# Active: failed (Result: exit-code)

이때 볼 개념은 네 가지다.

  • 실행 경로: ExecStart가 실제 파일을 가리키는가?
  • 환경변수: EnvironmentFile이 존재하고 서비스 프로세스에 전달되는가?
  • 권한: User=로 지정된 사용자가 파일과 포트를 쓸 수 있는가?
  • 포트: 이전 프로세스가 같은 포트를 잡고 있지 않은가?

명령은 이 질문을 확인하는 도구다.

Terminal window
journalctl -u my-nestapp -n 100 --no-pager
systemctl cat my-nestapp

SSH 장애는 “서버가 죽었다”가 아니라 네트워크 경로, 인증, 로컬 known_hosts 문제 중 하나일 수 있다.

증상의미먼저 할 일
Connection timed outTCP 22까지 도달하지 못함보안 그룹, subnet, 방화벽, SSM 가능성
Permission denied (publickey)서버는 닿았지만 인증 실패사용자명, 키 파일, chmod 400
Host key verification failed이전 서버의 host key와 현재 서버가 다름서버 재생성 여부 확인 후 known_hosts 정리
개념 A개념 B차이점
절대경로상대경로/var/log는 루트 기준, ./log는 현재 디렉터리 기준
>>>>는 덮어쓰기, >>는 이어쓰기
killkill -9kill 기본값은 SIGTERM 요청, kill -9는 SIGKILL 강제 종료
zombieorphanzombie는 죽었지만 수거 안 된 자식, orphan은 부모가 사라진 실행 중 자식
tail -fjournalctl -f파일을 직접 보느냐, systemd journal을 보느냐가 다르다
sudosusudo는 한 명령을 권한 상승 실행, su는 사용자 세션 전환
환경변수 수정실행 중 프로세스 반영새 셸 값과 이미 떠 있는 서비스 프로세스 환경은 별개다

이 문서는 Linux를 서버 실행 표면으로 처음 읽는 문서다. 그래서 다음 내용은 짧은 포인터만 남긴다.

  • process vs thread의 실행 의미, 주소 공간, context switching은 content/topics/L4/process-thread.mdx에서 다룬다.
  • system call, interrupt, file descriptor, epoll처럼 앱과 커널의 경계는 content/topics/L4/system-call-interrupt.mdx에서 다룬다.
  • memory, OOM, RSS, V8 heap은 content/topics/L4/memory-management.mdx에서 다룬다.
  • CPU scheduler, run queue, event loop delay는 content/topics/L4/cpu-scheduling.mdx에서 다룬다.
  • cgroups, namespace, 컨테이너 격리는 content/topics/L4/cgroups-namespace.mdx와 L5 Docker 문서에서 다룬다.

15. 선택 부록 — 명령어는 개념 확인용으로만 쓰기

섹션 제목: “15. 선택 부록 — 명령어는 개념 확인용으로만 쓰기”

아래 절차는 실무에서 유용하지만, 본문 개념을 이해하는 데 반드시 펼쳐야 하는 내용은 아니다.

로그와 프로세스 빠른 관찰
Terminal window
# 실시간 파일 로그에서 ERROR만 보기
tail -f /var/log/app.log | grep "ERROR"
# 최근 1000줄 중 ERROR 최신 50줄
tail -n 1000 app.log | grep "ERROR" | tail -50
# 실행 중인 node 프로세스 확인
ps aux | grep node
# CPU/메모리 상위 프로세스 관찰
top
디스크와 inode 확인
Terminal window
df -h
df -i
# 큰 디렉터리 후보 보기
du -sh /var/log/* 2>/dev/null | sort -rh | head -10
# 오래된 로그 후보 찾기. 실제 삭제 전에는 목록을 먼저 확인한다.
find /var/log -name "*.log" -mtime +30 -print
systemd 서비스 확인
Terminal window
systemctl status my-nestapp
journalctl -u my-nestapp -n 100 --no-pager
systemctl cat my-nestapp
# unit 파일을 바꾼 뒤에는 데몬 리로드가 필요하다.
sudo systemctl daemon-reload
sudo systemctl restart my-nestapp
SSH 접근 기본 하드닝 확인
Terminal window
# /etc/ssh/sshd_​config 예시. 운영 서버에서는 백업과 접속 유지 세션을 먼저 확보한다.
PermitRootLogin​ no
Password​Authentication no
Pubkey​Authentication yes
# private key 권한
chmod 400 my-key.pem
# 서비스 전용 유저 생성 예시
sudo use​radd --no-create-home --shell /usr/sbin/nologin deploy

root 직접 로그인과 비밀번호 SSH는 공격면이 크다. AWS에서는 SSH 포트 22를 열지 않는 SSM Session Manager도 선택지가 된다.

Linux Basics 복습 체크

  • Linux를 서버 프로세스, 파일, 권한, 환경변수, 시그널을 관리하는 실행 표면으로 설명할 수 있다.
  • CLI 명령어를 외우는 대신 어떤 개념을 관찰하는 도구인지 연결할 수 있다.
  • 파일 이름과 inode가 분리되어 있고, 디스크 공간과 inode 공간이 따로 고갈될 수 있음을 설명할 수 있다.
  • PID로 프로세스를 식별하고, zombie와 orphan의 차이를 말할 수 있다.
  • SIGTERM과 SIGKILL의 차이를 graceful shutdown 관점에서 설명할 수 있다.
  • 환경변수가 실행 중 프로세스에 자동 반영되지 않는 이유를 이해한다.
  • `chmod 777` 대신 소유자, 그룹, 최소 권한을 먼저 봐야 하는 이유를 설명할 수 있다.
  • systemd 서비스 실패를 실행 경로, 환경변수, 권한, 포트 문제로 나눠 볼 수 있다.
  • SSH 접속 실패를 네트워크 도달성, 인증, host key 문제로 구분할 수 있다.

bash scripting, stdin/stdout/stderr, inode, hard link, symbolic link, process table, PID 1, zombie process, signal, graceful shutdown, environment variable, file permission, systemd unit, journalctl, SSH, SSM Session Manager

  1. Linux는 서버에서 코드가 실행되는 표면이며, CLI는 그 표면을 관찰하고 제어하는 언어다.
  2. 파일 이름과 inode, 프로세스와 PID, 권한과 사용자, 환경변수와 실행 프로세스를 분리해서 봐야 장애 원인이 좁혀진다.
  3. SIGTERM은 정리할 기회를 주는 종료 요청이고, SIGKILL은 커널이 즉시 제거하는 최후 수단이다.
  4. systemd와 journalctl은 EC2 같은 서버에서 서비스 생명주기와 로그를 보는 기본 도구다.
  5. 컨테이너와 클라우드 서비스도 Linux의 프로세스·파일·권한·시그널 모델을 바탕으로 동작하므로, 이 문서는 L4의 첫 실행 감각을 만든다.