서버 운영의 언어, Linux 기본기
Linux 기본기는 서버 로그 확인, 프로세스 진단, 파일 권한, 컨테이너 디버깅을 이어 주는 공통 언어다. 이 스크립트는 inode, 프로세스와 시그널, 권한, systemd, 보안 하드닝까지 실무 장애 대응 관점에서 정리한다.
Script Companion
오디오와 함께 스크립트 보기
- 01
AWS든 GCP든 서버에 들어가면 결국 마주치는 환경은 Linux다. 로그를 보고, 프로세스를 확인하고, 파일을 찾고, 네트워크를 진단하는 작업이 모두 CLI 위에서 이루어진다. Docker 컨테이너 안도 Linux이기 때문에, 서버 문제가 생겼을 때 CLI를 다루지 못하면 확인할 수 있는 것이 크게 줄어든다. 이 문서의 핵심은 명령어를 많이 외우는 것이 아니라, 파일, 프로세스, 권한, 서비스 관리가 어떤 원리로 이어지는지 잡는 것이다.
- 02
Linux 파일 시스템의 중요한 출발점은 inode, 즉 index node다. 도서관 비유로 보면 파일 데이터는 책이고, inode는 책의 위치와 메타데이터가 적힌 카탈로그 카드에 가깝다. inode에는 파일 크기, 권한 rwx, 소유자, 생성·수정·접근 시간, 데이터 블록 포인터, 링크 카운트가 들어간다. 하지만 파일 이름과 파일 내용은 inode에 직접 저장되지 않는다. 파일 이름은 디렉토리에 있고, 디렉토리는 파일 이름에서 inode 번호로 가는 매핑 테이블처럼 동작한다.
- 03
파일 이름과 inode가 분리되어 있기 때문에 같은 실체를 여러 이름으로 가리키는 하드 링크가 가능하고, 파일을 이동할 때도 inode 자체가 아니라 디렉토리 항목만 바꾸면 된다. 이 구조는 번호나 식별자로 실체를 특정한다는 점에서 DB 인덱스 B-Tree, Primary Key와 Row ID, 컨테이너 레이어 SHA256 해시와도 연결된다. 문서는 Docker overlay2가 Linux overlayfs 위에서 동작하고, 컨테이너 수정 시 copy-on-write로 upper 레이어에 새 inode를 만든다고 설명한다.
- 04
inode 구조를 모르면 디스크 장애를 잘못 읽기 쉽다. 디스크 블록 용량이 남아 있어도 inode가 모두 소진되면 새 파일을 만들 수 없고, 이때 일반적인 디스크 사용량만 보면 여유가 있는 것처럼 보일 수 있다. 반대로 inode 사용률을 보면 100퍼센트에 도달해 있다. 소용량 로그 파일, 임시 파일, PHP 세션 파일이 수백만 개 쌓이는 환경에서 이런 일이 생긴다. Docker 컨테이너가 임시 파일을 무한히 만들거나, 이미지와 레이어를 정리하지 않는 경우에도 inode와 디스크가 함께 고갈될 수 있다.
- 05
Linux에서는 모든 것이 파일이라는 관점도 중요하다. 루트에서 시작하는 트리 구조 아래에 사용자 디렉토리, 로그, 설정, 임시 파일이 놓이고, 디바이스나 프로세스 정보도 파일처럼 표현된다. 예를 들어 PID 1 프로세스의 상태도 proc 가상 파일시스템에서 읽을 수 있다. 그래서 파일 목록 보기, 내용 확인, 끝부분 추적, 문자열 검색, 파일 찾기, 프로세스 보기, 디스크 확인, HTTP 요청 같은 CLI 조작은 따로 떨어진 기술이 아니라 서버 상태를 읽는 기본 감각에 가깝다.
- 06
프로세스는 실행 중인 프로그램의 인스턴스이며, 같은 프로그램을 두 번 실행하면 두 개의 프로세스가 생긴다. 각 프로세스는 커널에서 고유한 PID, 즉 Process ID를 받고 독립된 메모리 공간을 가진다. 프로세스 조회 명령은 proc 가상 파일시스템에서 정보를 읽어 온다. 여기서 조용한 실패 모드가 zombie 프로세스다. 자식 프로세스가 종료했는데 부모가 wait나 waitpid로 종료 코드를 수거하지 않으면 defunct 상태로 process table에 남는다.
- 07
zombie 프로세스는 CPU와 메모리를 거의 쓰지 않기 때문에 대시보드에서 잘 보이지 않고, PID 슬롯만 차지한다. kernel.pid_max에 닿아야 fork가 Resource temporarily unavailable로 실패하며 드러나므로 인지하기 어렵다. 이미 죽은 프로세스라 zombie PID에 SIGKILL을 보내도 효과가 없다. 부모를 종료해 init, 즉 PID 1이 입양하고 정리하게 해야 한다. Docker 컨테이너에서 PID 1이 시그널 핸들링 없는 단순 스크립트인 경우, 또는 Node.js child_process.spawn 뒤에 exit 처리를 등록하지 않는 경우 같은 결과가 생길 수 있다.
- 08
프로세스를 종료할 때는 SIGTERM과 SIGKILL의 차이를 알아야 한다. SIGTERM은 정리하고 종료하라는 요청이고, SIGKILL은 커널이 즉시 제거하는 신호라 프로세스가 핸들러를 등록할 수 없다. 정상 배포나 서비스 재시작에서는 진행 중 요청 완료, DB 커넥션 반납, 파일 플러시를 위해 SIGTERM을 먼저 쓰고 기다린 뒤 필요할 때 SIGKILL로 올린다. systemd는 기본 90초, Kubernetes pod와 AWS ECS task는 30초, Docker stop은 10초라는 기본 간격이 있어 로컬과 운영 결과가 달라질 수 있다.
- 09
컨테이너 환경에서도 Linux 시그널 추상화는 그대로 전이된다. 문서의 사례처럼 NestJS 앱을 Kubernetes에 롤링 업데이트할 때 SIGTERM 핸들러에서 app.close를 호출하지 않으면 진행 중인 HTTP 요청이 30초 뒤 SIGKILL로 잘려 5xx가 될 수 있다. 핸들러를 추가하고 진행 중 처리를 기다린 뒤 종료하도록 바꾸면 배포 중 5xx를 없앨 수 있다. Kubernetes, ECS, Docker 모두 세부 기본값은 다르지만, graceful shutdown의 공통 해법은 SIGTERM을 받고 정리하는 패턴이다.
- 10
환경변수와 권한은 운영 문제의 또 다른 중심이다. 서버와 컨테이너의 DB 주소, API Key 같은 설정값은 코드에 하드코딩하지 않고 환경변수로 분리한다. 파일 권한은 소유자, 그룹, 기타 사용자에게 읽기·쓰기·실행을 어떻게 줄지 정하는 체계다. chmod 777은 모든 사용자에게 모든 권한을 주기 때문에 빠른 해결처럼 보이지만, 웹서버 업로드 디렉토리, 설정 파일, 배포 스크립트에서 보안 사고로 이어질 수 있다. 최소 권한 원칙은 필요한 최소한의 권한만 주는 것이다.
- 11
systemd와 journalctl은 EC2에 직접 서비스를 배포하거나 서버 수준 디버깅을 할 때 만나는 표준 도구다. ECS나 Docker에서는 컨테이너 런타임이 수명 관리를 대신하지만, 서버에 직접 배포된 서비스나 nginx, 크론 같은 데몬을 다룰 때는 systemd가 부팅 순서, 재시작, 상태 관리를 맡는다. 운영 보안에서는 SSH root 로그인 비활성화, 키 기반 인증, 불필요한 서비스 비활성화, Fail2Ban, root가 아닌 전용 deploy 사용자 실행이 기본 점검 항목이다.
- 12
트러블슈팅은 증상과 원인을 나누어 읽어야 한다. Permission denied는 실행 권한이 없거나 현재 사용자에게 읽기·실행 권한이 없는 경우가 많고, No space left on device는 오래된 로그나 Docker 이미지·레이어 누적으로 디스크가 찬 상황일 수 있다. EADDRINUSE는 이전 프로세스가 포트를 계속 점유한다는 신호다. systemd 서비스가 failed로 끝나면 환경변수 누락, 실행 경로 오류, 포트 충돌, 코드 에러를 확인해야 한다. SSH 접속 실패는 22번 포트, Private Subnet, 키 파일 권한, known_hosts 문제로 나뉜다. 정리하면 Linux 기본기는 서버, Docker, ECS Exec, K8s exec에서 같은 문제를 읽게 해 주는 공통 기반이다.
같은 레이어