Linux는 서버 개발에서 자주 사용되는 운영체제다. 웹 서버, 게임 서버, 데이터베이스, Docker, Kubernetes 같은 서버 기술을 다루다 보면 결국 Linux 환경을 만나게 된다.
Linux를 제대로 이해하려면 명령어를 많이 외우는 것보다 프로세스, 파일 시스템, 권한, 네트워크, 메모리, 서비스 관리가 서로 어떻게 연결되는지를 이해하는 것이 중요하다.
Linux의 기본 구조
Linux를 사용하는 프로그램은 운영체제의 자원을 직접 제어하지 않는다.
프로그램은 Kernel을 통해 CPU, 메모리, 디스크, 네트워크 같은 시스템 자원을 사용한다.
Application -> System Call -> Linux Kernel -> CPU / Memory / Disk / Network
Linux Kernel은 운영체제의 핵심으로 프로세스 관리, 메모리 관리, 파일 시스템, 네트워크, 디바이스 관리 등을 담당한다.
Shell은 Kernel과 애플리케이션 사이에서 사용자가 시스템을 제어할 수 있도록 명령 인터페이스를 제공한다.
User -> Shell -> System Call -> Kernel -> Hardware
대표적인 Shell로 Bash가 있다.
파일 시스템
Linux에서는 하드디스크의 드라이브를 C:\, D:\처럼 구분하는 Windows와 달리 하나의 루트 디렉터리 /를 기준으로 파일 시스템이 구성된다.
/ -> bin / boot / dev / etc / home / lib / opt / proc / root / tmp / usr / var
각 디렉터리는 용도가 다르다.
/etc에는 시스템 및 애플리케이션 설정 파일이 들어가는 경우가 많다.
/var에는 로그나 캐시처럼 실행 중 변경되는 데이터가 저장된다.
/home에는 일반 사용자의 홈 디렉터리가 위치한다.
/tmp는 임시 파일을 저장하는 용도로 사용된다.
/proc은 실제 디스크에 일반적인 파일을 저장하는 디렉터리라기보다 Kernel이 관리하는 프로세스와 시스템 정보를 파일 형태로 제공하는 가상 파일 시스템이다.
예를 들어:
cat /proc/cpuinfo -> CPU 관련 정보 확인
Linux에서는 장치 역시 파일 형태로 다루는 Unix 계열의 설계가 많이 반영되어 있다.
경로와 기본 명령어
Linux에서 자주 사용하는 기본 명령어는 파일과 디렉터리를 다루는 명령어다.
현재 디렉터리를 확인하려면:
pwd -> 현재 디렉터리 확인
파일 목록을 확인하려면:
ls -> 파일 목록 확인
디렉터리를 이동하려면:
cd /var/log -> /var/log 디렉터리 이동
디렉터리를 생성하려면:
mkdir test -> test 디렉터리 생성
파일을 복사하려면:
cp source.txt backup.txt -> source.txt 복사 -> backup.txt 생성
파일을 이동하거나 이름을 변경하려면:
mv source.txt target.txt -> source.txt 이동 / 이름 변경
파일을 삭제하려면:
rm target.txt -> target.txt 삭제
Linux에서는 명령어를 조합해서 사용하는 것도 중요하다.
cat app.log -> grep ERROR -> ERROR가 포함된 로그 필터링
cat의 출력이 grep의 입력으로 전달된다.
이런 방식으로 작은 명령어를 조합해 필요한 작업을 수행할 수 있다.
권한
Linux에서 파일과 디렉터리에는 권한이 존재한다.
대표적인 권한은 다음 세 가지다.
r -> read
w -> write
x -> execute
그리고 권한의 대상은 다음 세 그룹으로 나뉜다.
User -> Group -> Others
예를 들어:
-rwxr-x---
라면 다음과 같이 해석할 수 있다.
User -> rwx
Group -> r-x
Others -> ---
권한을 변경할 때는 chmod를 사용한다.
chmod 755 app.sh -> User rwx / Group r-x / Others r-x
755는 다음 권한을 의미한다.
User -> 7 -> rwx
Group -> 5 -> r-x
Others -> 5 -> r-x
파일의 소유자를 변경할 때는 chown을 사용한다.
chown user:user app.log -> app.log 소유자 변경
서버를 운영하다 보면 “Permission denied”가 상당히 자주 발생한다.
이때 무작정 sudo를 붙이는 것보다 어떤 사용자가 어떤 파일에 어떤 권한으로 접근하려는지부터 확인하는 것이 맞다.
사용자와 Root
Linux에는 사용자 계정과 그룹 개념이 있다.
현재 사용자를 확인하려면:
whoami -> 현재 사용자 확인
사용자가 속한 그룹을 확인하려면:
groups -> 사용자 그룹 확인
root는 시스템 전체에 대한 강한 권한을 가진 관리자 계정이다.
일반 사용자가 관리자 권한으로 명령을 실행해야 하는 경우 sudo를 사용할 수 있다.
sudo systemctl restart nginx -> 관리자 권한 -> nginx 재시작
하지만 root 권한은 필요한 작업에만 사용하는 것이 좋다.
권한을 잘못 가진 프로세스가 실행되면 시스템 전체에 영향을 줄 수 있기 때문이다.
Process
Linux에서 실행 중인 프로그램은 Process라는 실행 단위로 관리된다.
프로세스는 각각 PID(Process ID)를 가진다.
현재 실행 중인 프로세스를 확인할 때:
ps aux -> 실행 중인 Process 확인
실시간으로 프로세스를 확인할 때는:
top -> Process 실시간 모니터링
또는 htop을 사용할 수 있다.
특정 프로세스를 종료하려면:
kill 1234 -> PID 1234에 종료 Signal 전달
필요한 경우 강제 종료 신호를 사용할 수도 있다.
kill -9 1234 -> PID 1234에 SIGKILL 전달 -> 강제 종료
다만 SIGKILL은 프로세스가 정리 작업을 수행할 기회 없이 종료되므로 일반적인 종료 방법으로 남용하는 것은 좋지 않다.
Process와 Thread
프로세스는 독립적인 실행 환경을 가지고 있고 그 안에서 여러 Thread를 실행할 수 있다.
Process -> Thread / Thread / Thread
Thread들은 같은 프로세스의 메모리 공간을 공유한다.
반면 서로 다른 프로세스는 기본적으로 독립된 주소 공간을 가진다.
서버 개발에서는 이 차이가 중요하다.
프로세스 간 통신에는 IPC가 필요하고, Thread 간에는 공유 메모리를 사용할 수 있지만 동기화 문제가 발생한다.
대표적인 동기화 도구가 Mutex, Semaphore, Condition Variable 등이다.
Process A -> IPC -> Process B
Thread A -> Shared Memory -> Thread B -> Mutex / Semaphore / Condition Variable
Background Process와 Job
Shell에서는 명령어를 실행하면 기본적으로 해당 프로세스가 현재 터미널을 점유한다.
백그라운드로 실행하려면:
./server -> Background Process
실제 Shell에서는:
./server &
처럼 실행한다.
실행 중인 Job을 확인하려면:
jobs -> Background Job 확인
터미널 세션이 종료되어도 프로세스를 계속 실행해야 하는 경우에는 nohup, systemd, 컨테이너 등의 방법을 사용할 수 있다.
실제 서버 운영에서는 단순히 nohup으로 프로세스를 띄워놓기보다 서비스 관리 시스템을 사용하는 편이 적절하다.
Service와 systemd
Linux 서버에서 애플리케이션을 지속적으로 실행하고 관리하려면 Service 개념을 이해해야 한다.
최근 Linux 배포판에서는 systemd가 널리 사용된다.
서비스 상태 확인:
systemctl status nginx -> nginx Service 상태 확인
서비스 시작:
sudo systemctl start nginx -> nginx Service 시작
서비스 중지:
sudo systemctl stop nginx -> nginx Service 중지
서비스 재시작:
sudo systemctl restart nginx -> nginx Service 재시작
부팅할 때 자동으로 실행되도록 설정:
sudo systemctl enable nginx -> Boot 시 nginx 자동 실행 설정
로그는 journalctl을 통해 확인할 수 있다.
journalctl -u nginx -> nginx Service Log 확인
이런 구조를 사용하면 서버가 재부팅되거나 프로세스에 문제가 생겼을 때 서비스를 다시 실행하도록 관리할 수 있다.
Application -> systemd -> Service -> Process
Memory
Linux에서는 프로세스마다 가상 주소 공간을 사용한다.
Process -> Virtual Address Space -> Code / Data / Heap / Stack
실제 물리 메모리와 프로세스가 보는 가상 주소 공간은 동일한 개념이 아니다.
Linux Kernel은 Virtual Memory와 Page 단위의 메모리 관리 등을 통해 프로세스의 메모리 공간을 관리한다.
메모리 사용량을 확인할 때는:
free -h -> System Memory 확인
프로세스별 메모리 사용량은:
ps aux -> Process별 Memory 사용량 확인
또는 top 등을 이용해 확인할 수 있다.
서버 개발에서는 단순히 “RAM이 얼마나 남았는가”만 보는 것보다 프로세스별 메모리 사용량과 Swap 사용 여부까지 같이 보는 것이 좋다.
Disk와 I/O
서버에서 CPU보다 먼저 문제가 되는 것이 디스크 I/O인 경우도 많다.
디스크 용량을 확인하려면:
df -h -> Disk 사용량 확인
특정 디렉터리가 얼마나 공간을 사용하는지 확인하려면:
du -sh /var/log -> /var/log 사용량 확인
디스크 장치와 파티션 구조를 확인할 때는:
lsblk -> Disk / Partition 구조 확인
로그가 계속 쌓이는 서버에서는 디스크 용량이 부족해지는 문제가 발생할 수 있다.
그래서 로그의 보존 기간, Rotation, 압축 등을 같이 관리해야 한다.
Network
서버 개발에서 Linux 네트워크 명령어도 자주 사용한다.
네트워크 인터페이스를 확인하려면:
ip addr -> Network Interface / IP 확인
라우팅 정보를 확인하려면:
ip route -> Routing Table 확인
포트와 연결 상태를 확인할 때는:
ss -lntp -> Listen Socket / Port 확인
특정 서버에 HTTP 요청을 보내려면:
curl http://localhost:8080 -> HTTP Request 전송
DNS 확인에는:
nslookup example.com -> DNS 조회
또는:
dig example.com -> DNS 조회
등을 사용할 수 있다.
예를 들어 서버는 정상적으로 실행 중인데 외부에서 접근할 수 없다면 다음과 같은 순서로 확인할 수 있다.
Process 실행 여부 -> Port Listen 여부 -> Network Interface -> Routing -> Firewall -> External Network
서버 장애를 볼 때 단순히 애플리케이션 로그만 보는 것이 아니라 운영체제와 네트워크 계층까지 내려가서 확인할 수 있어야 한다.
File Descriptor
Linux 서버 개발에서 특히 중요한 개념이 File Descriptor다.
Linux에서는 파일뿐 아니라 Socket 같은 I/O 자원도 File Descriptor를 통해 접근할 수 있다.
Process -> File Descriptor Table -> File / Socket
대표적으로:
stdin -> FD 0
stdout -> FD 1
stderr -> FD 2
File -> FD 3
Socket -> FD 4
네트워크 서버에서 클라이언트 연결이 많아지면 Socket도 많이 생성된다.
따라서 서버에서 File Descriptor 제한이 문제가 될 수 있다.
현재 프로세스가 열어둔 FD를 확인하려면:
ls -l /proc/<pid>/fd -> Process의 Open File Descriptor 확인
현재 제한을 확인하려면:
ulimit -n -> File Descriptor Limit 확인
대규모 네트워크 서버를 운영한다면 이 개념을 알고 있어야 한다.
IOCP나 epoll 같은 고성능 네트워크 I/O 모델을 공부할 때도 결국 운영체제의 파일 디스크립터와 연결된다.
Signal
Linux 프로세스는 Signal을 통해 비동기적인 이벤트를 전달받을 수 있다.
대표적으로:
SIGINT -> Interrupt
SIGTERM -> Termination Request
SIGKILL -> Forced Termination
SIGHUP -> Hangup / Reload 등에 사용
터미널에서 Ctrl + C를 누르면 일반적으로 SIGINT가 전달된다.
프로세스를 정상적으로 종료시키려면 SIGTERM을 사용하는 경우가 많다.
kill -TERM <pid> -> SIGTERM -> Process
SIGKILL은 프로세스가 처리할 수 없는 강제 종료 신호다.
kill -KILL <pid> -> SIGKILL -> Process
Graceful Shutdown을 구현할 때는 이런 Signal을 받아 현재 작업을 정리하고 종료하는 구조를 만들 수 있다.
Process -> SIGTERM -> Graceful Shutdown -> Resource Cleanup -> Process Exit
Shell과 Bash
Shell은 사용자의 명령을 해석하고 실행하는 프로그램이다.
Bash는 대표적인 Shell 중 하나다.
User -> Bash -> Command -> Process
변수를 사용할 수도 있다.
PORT=8080 -> echo $PORT -> 8080
조건문과 반복문을 이용해 서버 관리 작업을 자동화할 수도 있다.
Shell Script -> Condition / Loop -> Server Management
이런 Shell Script는 배포, 백업, 로그 관리, 서버 초기화 등의 자동화에 사용할 수 있다.
Pipe와 Redirection
Linux Shell의 강력한 기능 중 하나가 명령어 조합이다.
Pipe를 사용하면 한 프로그램의 출력을 다른 프로그램의 입력으로 전달할 수 있다.
ps aux -> grep nginx -> nginx Process 필터링
파일로 출력을 저장할 수도 있다.
ls -al -> files.txt
실제로는:
ls -al > files.txt
파일의 내용을 명령어의 입력으로 전달할 수도 있다.
app.log -> grep ERROR -> Error 검색
실제로는:
grep ERROR < app.log
로그 분석 같은 작업에서는 이런 조합이 상당히 유용하다.
cat app.log -> grep ERROR -> wc -l -> ERROR 개수 확인
명령어 각각은 단순하지만 조합하면 서버 운영에 필요한 작업을 빠르게 수행할 수 있다.
Log
서버 개발에서 로그는 장애 원인을 추적하는 중요한 수단이다.
Linux에서는 애플리케이션 로그가 /var/log 아래에 저장되는 경우가 많다.
/var/log -> Application / System Log
특정 로그를 실시간으로 확인할 때:
tail -f app.log -> 실시간 Log 확인
에러만 찾을 때:
grep ERROR app.log -> ERROR Log 검색
로그를 단순히 남기는 것보다 중요한 것은 로그에 시간, 요청 정보, 프로세스 정보, 에러 원인 등을 추적할 수 있는 정보를 포함시키는 것이다.
운영 환경에서는 로그가 무한히 쌓이지 않도록 Log Rotation도 고려해야 한다.
Log -> Rotation -> Compression -> Retention
Linux에서 서버가 실행되는 과정
지금까지의 내용을 실제 서버 실행 관점에서 연결하면 다음과 같다.
사용자 -> Shell -> Process 실행 -> Linux Kernel -> CPU / Memory / Disk / Network -> Server Application -> Socket -> Network -> Client
예를 들어 게임 서버를 실행한다고 하면:
./game-server -> Process -> Socket -> Port 8080 -> Network -> Client
Shell이 프로세스를 생성하고 Linux Kernel이 CPU와 메모리를 할당한다.
게임 서버는 Socket을 생성하고 특정 Port에서 연결을 기다린다.
Game Server Process -> Socket -> Port 8080 -> Network -> Client
여기서 문제가 발생하면 Linux 명령어를 이용해 각 계층을 확인할 수 있다.
Process -> ps / top
Memory -> free / top
Disk -> df / du
Network -> ip / ss / curl
Log -> journalctl / tail / grep
Permission -> ls -l / chmod / chown
이런 식으로 Linux를 단순한 명령어 모음이 아니라 서버 프로그램이 실행되는 기반 환경으로 보는 것이 좋다.
Linux와 Docker
Docker를 사용하면 Linux의 기본 개념이 사라지는 것이 아니다.
Docker 컨테이너도 결국 Linux Kernel의 기능을 사용한다.
Docker Container -> Docker / Container Runtime -> Linux Kernel -> Hardware
컨테이너는 독립적인 가상 머신이 아니다.
Linux Kernel을 공유하면서 Namespace와 Cgroups 같은 기능을 이용해 프로세스와 리소스를 격리한다.
Linux Kernel -> Namespace / Cgroups -> Process / Resource Isolation
그래서 Docker를 제대로 이해하려면 Linux의 Process, Network, File System, Permission 같은 개념이 자연스럽게 연결된다.
Linux와 Kubernetes
Kubernetes를 사용하는 환경에서도 Linux는 기반이 된다.
Kubernetes -> Container Runtime -> Linux -> Hardware
Kubernetes가 Pod을 관리한다고 해도 실제 Node에서 프로세스와 네트워크, 메모리, 디스크를 관리하는 것은 결국 운영체제다.
그래서 Kubernetes에서 Pod이 실행되지 않는 문제를 해결하다 보면 결국 다음 영역까지 내려가게 된다.
Kubernetes -> Pod -> Container -> Process -> Linux -> CPU / Memory / Network / Disk
Kubernetes를 운영할수록 Linux에 대한 이해가 중요해지는 이유다.
정리
Linux를 서버 개발 관점에서 보면 다음 구조로 정리할 수 있다.
Application -> Process / Thread -> System Call -> Linux Kernel -> CPU / Memory / Disk / Network
그리고 실제 운영에서는 다음 개념들이 연결된다.
File System -> Permission -> Process -> Thread -> Memory -> File Descriptor -> Socket -> Network -> Signal -> Service -> Log -> Shell
Linux 명령어를 많이 외우는 것보다 문제가 발생했을 때 어느 계층을 확인해야 하는지 아는 것이 더 중요하다.
서버가 죽었다면 Process를 보고, 메모리가 부족하면 Memory를 보고, 연결이 안 되면 Socket과 Network를 보고, 파일 접근이 실패하면 Permission을 본다.
Server Problem -> Process -> Memory -> Disk -> File -> Permission -> Socket -> Port -> Network -> Service -> Log
Docker, Kubernetes, 웹 서버, 게임 서버, 데이터베이스 같은 기술도 결국 Linux 위에서 실행되는 경우가 많다.
그래서 Linux는 별도의 서버 기술이라기보다 서버 개발과 운영을 이해하기 위한 기본 실행 환경으로 보는 편이 맞다.