AT&T Unix
소스 라이선스 비용이 높고 학생·개인이 자유롭게 수정하기 어려웠다.
Unix의 파일·프로세스 모델은 배우고 싶지만 개인 PC에서 직접 고치며 쓰기 어려웠던 상황분류: Layer 4 - CS 기초: 운영체제 & Linux
Linux는 서버에서 프로그램을 실행하고, 파일과 권한을 관리하고, 프로세스에 CPU·메모리·네트워크 같은 자원을 빌려주는 운영체제다.
CLI(Command Line Interface, 명령줄 인터페이스)는 그 운영체제와 대화하는 기본 언어다. tail, grep, ps, chmod, systemctl 같은 명령어 자체가 목적은 아니다. 명령어는 “지금 어떤 파일이 있고, 어떤 프로세스가 떠 있으며, 어떤 권한과 설정 때문에 실행이 막혔는가”를 관찰하는 도구다.
서버 장애를 처음 보면 증상은 대부분 앱 에러처럼 보인다. 하지만 실제 원인은 더 낮은 층에 있을 수 있다.
.env가 없거나 환경변수가 서비스 프로세스에 전달되지 않았다.SIGTERM을 처리하지 못해 in-flight 요청이 잘린다.Linux basics의 목표는 명령어를 많이 외우는 것이 아니라, 이런 증상을 파일, 프로세스, 권한, 환경변수, 시그널, 서비스 관리자 중 어느 문제로 봐야 하는지 나누는 것이다.
1991년 Linus Torvalds가 Linux 커널을 공개했을 때의 빈틈은 “Unix 같은 개발·운영 모델을 개인 PC에서 자유롭게 쓰고 고칠 수 있는가”였다.
소스 라이선스 비용이 높고 학생·개인이 자유롭게 수정하기 어려웠다.
Unix의 파일·프로세스 모델은 배우고 싶지만 개인 PC에서 직접 고치며 쓰기 어려웠던 상황교육용 목적이 강했고, 당시에는 범용 서버 워크로드를 자유롭게 확장하기 어려웠다.
학습용 OS를 넘어 실제 워크로드를 돌릴 Unix 호환 커널이 필요했던 상황법적 분쟁과 출시 지연으로 x86에서 바로 쓸 수 있는 자유 Unix 계열 OS의 공백이 있었다.
386 PC에서 재배포 가능한 Unix 계열 OS가 필요했던 상황Linux가 이 한계를 푼 방식은 지금 서버 운영의 기본 가정이 되었다.
이 토픽이 다루는 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가 수집한 서비스 로그를 보는 도구다.
CLI는 명령어를 한 줄씩 입력하는 UI다. Linux 도구가 강한 이유는 각 명령이 보통 텍스트를 입력받고 텍스트를 출력하기 때문에, 파이프(|)로 작은 도구를 연결할 수 있기 때문이다.
세 가지 표준 통로를 먼저 기억한다.
| 이름 | 번호 | 의미 |
|---|---|---|
| stdin(Standard Input) | 0 | 명령어가 입력을 읽는 통로 |
| stdout(Standard Output) | 1 | 정상 결과를 쓰는 통로 |
| stderr(Standard Error) | 2 | 에러 메시지를 쓰는 통로 |
작은 예제로 보면 감이 잡힌다.
# 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처럼 “어떤 저장소를 읽는가”에 맞는 도구를 고른다.
Linux 파일 시스템은 /에서 시작하는 트리 구조다. /etc에는 설정, /var/log에는 로그, /tmp에는 임시 파일, /home에는 사용자 파일이 주로 놓인다. 컨테이너 안에서도 이 트리 구조를 본다.
Linux에서 “모든 것이 파일이다”라는 말은 실제 세계의 모든 것이 일반 파일이라는 뜻이 아니다. 파일처럼 열고 읽고 쓰는 공통 인터페이스를 제공한다는 뜻이다. 예를 들어 /proc/1/status는 디스크에 저장된 일반 파일이 아니라, PID 1 프로세스 상태를 커널이 파일처럼 보여주는 가상 파일이다.
inode(index node)는 파일의 실체를 가리키는 메타데이터 구조다. inode에는 파일 이름을 제외한 많은 정보가 들어간다.
| inode에 저장되는 것 | inode에 저장되지 않는 것 |
|---|---|
| 파일 크기, 권한, 소유자 | 파일 이름 |
| 생성/수정/접근 시간 | 파일 내용 자체 |
| 데이터 블록 위치 | 디렉터리 경로 |
| 링크 카운트(하드 링크 수) |
파일 이름은 디렉터리에 저장된다. 디렉터리는 대략 (파일 이름 -> inode 번호) 매핑을 담는 파일이다. 이 분리 덕분에 같은 inode를 여러 이름으로 가리키는 하드 링크가 가능하고, 파일 이동이 디렉터리 항목 변경만으로 끝날 수 있다.
# 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 -i의 IUse%가 100%에 가까워진다.
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 -i와 find로 파일 개수와 생성 위치를 본다. Docker 이미지·레이어도 결국 많은 파일과 overlay layer 메타데이터를 파일시스템 위에 쌓기 때문에 inode와 디스크 공간을 함께 소모할 수 있다. 컨테이너 파일시스템 내부 구조는 L5 Docker와 L4 cgroups/namespace 문서에서 더 깊게 다룬다.
프로세스는 실행 중인 프로그램의 인스턴스다. 같은 node dist/main.js를 두 번 실행하면 프로그램 파일은 같아도 프로세스는 두 개다. 커널은 각 프로세스에 PID(Process ID)를 부여하고, 프로세스마다 메모리 공간, 열린 파일, 환경변수, 현재 작업 디렉터리 같은 실행 상태를 관리한다.
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가 원인인가”를 찾는 일에서 시작한다.
# 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 슬롯을 차지한다.
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은 “살아 있는데 부모가 사라진 자식”으로 구분하면 된다.
시그널(signal)은 커널이 프로세스에 보내는 짧은 제어 메시지다. “종료해라”, “중단됐다”, “자식이 종료됐다” 같은 이벤트를 프로세스에 알린다.
kill이라는 명령 이름 때문에 항상 죽이는 명령처럼 보이지만, 실제로는 시그널을 보내는 도구다.
| 시그널 | 번호 | 프로세스가 처리 가능한가 | 보통의 의미 |
|---|---|---|---|
| SIGTERM | 15 | 가능 | 정리하고 정상 종료하라는 요청 |
| SIGKILL | 9 | 불가능 | 커널이 즉시 제거하는 최후 수단 |
| SIGINT | 2 | 가능 | 터미널에서 Ctrl+C로 중단 요청 |
| SIGCHLD | 17 | 가능 | 자식 프로세스 상태 변화 알림 |
운영에서 가장 중요한 판단은 SIGTERM과 SIGKILL의 차이다.
| 상황 | 먼저 쓸 것 | 이유 |
|---|---|---|
| 정상 배포, 서비스 재시작 | SIGTERM | 요청 처리, DB 연결 반납, 파일 플러시 기회를 준다 |
| graceful shutdown이 끝나지 않음 | SIGTERM -> 대기 -> SIGKILL | 무한 대기를 막되 데이터 손상 가능성을 늦춘다 |
| deadlock, 무한 루프, 응답 없음 | SIGKILL | SIGTERM 핸들러가 실행되지 않거나 무시될 수 있다 |
| zombie 프로세스 | zombie PID에 보내도 무의미 | 이미 죽은 프로세스이므로 부모 수거 문제를 봐야 한다 |
kill 1234# 기본값은 SIGTERM
sleep 15kill -0 1234 2>/dev/null && kill -9 1234# 아직 살아 있으면 SIGKILLSIGKILL도 항상 “즉시 사라짐”을 보장하지는 않는다. 프로세스가 커널 안에서 uninterruptible sleep, 즉 D 상태로 디스크, NFS, device I/O를 기다리는 중이면 시그널 처리가 지연될 수 있다. 이때는 kill -9를 반복하기보다 I/O 경로, mount/device 상태, kernel wait channel을 의심한다.
컨테이너 환경도 이 추상화를 그대로 가져간다.
| 런타임 | SIGTERM -> SIGKILL 기본 간격 | 자주 생기는 함정 |
|---|---|---|
Docker stop | 10초 | 로컬에서는 빨리 죽어 prod와 다르게 보일 수 있다 |
| Kubernetes pod | 30초(terminationGracePeriodSeconds) | grace period 안에서 preStop hook과 종료 신호가 어떻게 처리되는지 확인해야 한다 |
| AWS ECS task | 30초(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은 “종료 핸들러가 있다”가 아니라 “로드밸런서에서 빠지고, 진행 중 요청을 마치고, 연결을 정리하는 순서가 맞다”는 계약이다.
환경변수(environment variable)는 프로세스가 시작될 때 전달받는 문자열 key-value 설정이다. DB 주소, 포트, API key, 실행 모드처럼 배포 환경마다 달라지는 값을 코드 밖으로 빼기 위해 사용한다.
export PORT=3000printenv PORT# 3000
echo "$DATABASE_URL"# postgresql://user:pass@host:5432/app중요한 점은 환경변수가 실행 중인 프로세스의 설정 스냅샷이라는 것이다. 터미널에서 export DATABASE_URL=...을 새로 해도 이미 떠 있는 systemd 서비스나 컨테이너 프로세스의 환경이 자동으로 바뀌지는 않는다. 서비스를 재시작하거나, systemd unit의 EnvironmentFile, Docker/Kubernetes의 env 설정을 바꿔 새 프로세스를 띄워야 반영된다.
작은 실패 예시:
# 현재 셸에는 값이 있다.echo "$DATABASE_URL"
# 그런데 systemd 서비스는 다른 환경으로 떠 있을 수 있다.systemctl status my-nestappjournalctl -u my-nestapp -n 50 --no-pager# Error: DATABASE_URL is required판단 기준은 다음과 같다.
| 상황 | 먼저 볼 곳 |
|---|---|
| 로컬 터미널에서만 실행 실패 | 현재 셸의 printenv, .env 로딩 여부 |
| systemd 서비스에서만 실패 | unit 파일의 Environment, EnvironmentFile |
| 컨테이너에서만 실패 | Docker/Kubernetes/ECS의 env 주입 설정 |
| 값은 있는데 권한 오류가 남음 | 환경변수가 아니라 파일 권한이나 네트워크 접근성 |
Linux 권한은 파일마다 소유자(owner), 그룹(group), 기타 사용자(others)에 대해 읽기(r), 쓰기(w), 실행(x) 권한을 따로 둔다.
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--)를 뜻한다.
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), 필요한 권한이 읽기인지 실행인지”를 나눠야 한다.
systemd는 Linux 서버에서 서비스를 시작하고, 중지하고, 부팅 시 자동 실행하고, 실패 시 재시작 정책을 적용하는 서비스 관리자다. EC2 같은 서버에 직접 앱을 배포하면 nginx, sshd, my-nestapp 같은 서비스가 systemd unit으로 관리되는 경우가 많다.
컨테이너 환경에서는 Docker, ECS, Kubernetes가 서비스 생명주기를 더 바깥에서 관리한다. 그래도 systemd를 알면 “서비스 프로세스는 누가 띄우고, 어떤 환경변수와 사용자로 실행하며, 죽었을 때 누가 다시 띄우는가”라는 기본 질문을 배울 수 있다.
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 <서비스명>이 맞다. 둘은 비슷해 보여도 읽는 저장소가 다르다.
SSH(Secure Shell)는 원격 서버에 암호화된 터미널 세션을 여는 프로토콜이다. EC2에 직접 들어가거나, 배포 서버에서 로그를 확인하거나, 응급 진단을 할 때 만난다.
기본 모델은 세 가지다.
| 방식 | 의미 | 주의점 |
|---|---|---|
| 비밀번호 SSH | 사용자 비밀번호로 로그인 | 무차별 대입에 약해 프로덕션에서는 피한다 |
| 공개키 SSH | private key로 본인을 증명 | 키 파일 권한이 넓으면 SSH가 거부할 수 있다 |
| AWS SSM Session Manager | SSH 포트 없이 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 권한과 감사 로그 경로로 접속하므로, 같은 “서버 접속”이라도 노출되는 위험과 통제 지점이 다르다.
Permission denied가 났다증상:
./deploy.sh# bash: ./deploy.sh: Permission denied개념으로 쪼개면 다음 순서다.
ls -l deploy.sh에서 x가 있는지 본다.whoami, id, ls -l을 본다.x 권한과 인터프리터 접근 권한이 모두 필요하다.chmod 777 대신 소유자·그룹을 맞추고 필요한 권한만 준다.No space left on device가 났다증상:
touch /tmp/a# touch: cannot touch '/tmp/a': No space left on device두 가지 공간을 나눠 본다.
df -h# 블록 공간 확인
df -i# inode 공간 확인df -h가 100%면 큰 로그, 캐시, 이미지 파일을 의심한다. df -i가 100%면 작은 파일이 너무 많이 생긴 것이다. 둘은 원인도 해결책도 다르다.
증상:
Error: listen EADDRINUSE: address already in use :::3000포트는 커널이 관리하는 네트워크 자원이고, 하나의 IP:port 조합은 보통 하나의 프로세스가 listen한다.
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 지연을 먼저 의심한다.
증상:
systemctl status my-nestapp# Active: failed (Result: exit-code)이때 볼 개념은 네 가지다.
ExecStart가 실제 파일을 가리키는가?EnvironmentFile이 존재하고 서비스 프로세스에 전달되는가?User=로 지정된 사용자가 파일과 포트를 쓸 수 있는가?명령은 이 질문을 확인하는 도구다.
journalctl -u my-nestapp -n 100 --no-pagersystemctl cat my-nestappSSH 장애는 “서버가 죽었다”가 아니라 네트워크 경로, 인증, 로컬 known_hosts 문제 중 하나일 수 있다.
| 증상 | 의미 | 먼저 할 일 |
|---|---|---|
Connection timed out | TCP 22까지 도달하지 못함 | 보안 그룹, subnet, 방화벽, SSM 가능성 |
Permission denied (publickey) | 서버는 닿았지만 인증 실패 | 사용자명, 키 파일, chmod 400 |
Host key verification failed | 이전 서버의 host key와 현재 서버가 다름 | 서버 재생성 여부 확인 후 known_hosts 정리 |
| 개념 A | 개념 B | 차이점 |
|---|---|---|
| 절대경로 | 상대경로 | /var/log는 루트 기준, ./log는 현재 디렉터리 기준 |
> | >> | >는 덮어쓰기, >>는 이어쓰기 |
kill | kill -9 | kill 기본값은 SIGTERM 요청, kill -9는 SIGKILL 강제 종료 |
| zombie | orphan | zombie는 죽었지만 수거 안 된 자식, orphan은 부모가 사라진 실행 중 자식 |
tail -f | journalctl -f | 파일을 직접 보느냐, systemd journal을 보느냐가 다르다 |
sudo | su | sudo는 한 명령을 권한 상승 실행, su는 사용자 세션 전환 |
| 환경변수 수정 | 실행 중 프로세스 반영 | 새 셸 값과 이미 떠 있는 서비스 프로세스 환경은 별개다 |
이 문서는 Linux를 서버 실행 표면으로 처음 읽는 문서다. 그래서 다음 내용은 짧은 포인터만 남긴다.
content/topics/L4/process-thread.mdx에서 다룬다.content/topics/L4/system-call-interrupt.mdx에서 다룬다.content/topics/L4/memory-management.mdx에서 다룬다.content/topics/L4/cpu-scheduling.mdx에서 다룬다.content/topics/L4/cgroups-namespace.mdx와 L5 Docker 문서에서 다룬다.아래 절차는 실무에서 유용하지만, 본문 개념을 이해하는 데 반드시 펼쳐야 하는 내용은 아니다.
# 실시간 파일 로그에서 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/메모리 상위 프로세스 관찰topdf -hdf -i
# 큰 디렉터리 후보 보기du -sh /var/log/* 2>/dev/null | sort -rh | head -10
# 오래된 로그 후보 찾기. 실제 삭제 전에는 목록을 먼저 확인한다.find /var/log -name "*.log" -mtime +30 -printsystemctl status my-nestappjournalctl -u my-nestapp -n 100 --no-pagersystemctl cat my-nestapp
# unit 파일을 바꾼 뒤에는 데몬 리로드가 필요하다.sudo systemctl daemon-reloadsudo systemctl restart my-nestapp# /etc/ssh/sshd_config 예시. 운영 서버에서는 백업과 접속 유지 세션을 먼저 확보한다.PermitRootLogin noPasswordAuthentication noPubkeyAuthentication yes
# private key 권한chmod 400 my-key.pem
# 서비스 전용 유저 생성 예시sudo useradd --no-create-home --shell /usr/sbin/nologin deployroot 직접 로그인과 비밀번호 SSH는 공격면이 크다. AWS에서는 SSH 포트 22를 열지 않는 SSM Session Manager도 선택지가 된다.
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