한 줄 정의
서비스 장애의 상당수는 애플리케이션 코드가 아니라 계정 권한, 프로세스 상태, 디스크, 파일 디스크립터 같은 OS 수준의 한계에서 시작합니다.
쉽게 말하면
서버를 여러 사람이 함께 쓰는 공용 작업실 이라고 생각해 봅시다.
작업실에는 모든 문을 여는 마스터키(root)가 있고, 이건 관리인만 갖습니다. 나머지 사람은 자기 사물함 키(일반 계정)만 받고, 잠깐 마스터키가 필요할 때는 관리인이 “이 문만, 이 사람에게” 열어 줍니다(sudo). 마스터키를 아무에게나 복사해 주면 편하긴 하지만, 누가 무엇을 부쉈는지 알 수 없게 됩니다.
작업실이 멈추는 이유도 대체로 정해져 있습니다. 지금 안에 누가 앉아 있는지 모르거나(프로세스), 창고가 짐으로 꽉 차서 아무것도 들여놓을 수 없거나(디스크), 콘센트 개수가 모자라 장비를 더 꽂을 수 없거나(파일 디스크립터), 벽시계가 어긋나 옆 작업실과의 약속 시간이 틀어집니다(시간 동기화).
이 장이 다루는 명령어들은 전부 “작업실 안에서 지금 무슨 일이 벌어지고 있는지 눈으로 확인하는 방법” 입니다. 코드를 아무리 잘 짜도 작업실 상태를 볼 줄 모르면, 문제가 내 코드 때문인지 작업실 때문인지 구분할 수 없습니다.
왜 중요한가?
규모가 큰 회사는 인프라·플랫폼 팀과 서비스 개발 팀이 분리되어 있습니다. 덕분에 백엔드 개발을 시작하면서 OS를 직접 설치하고 설정하고 관리하는 경험을 아예 못 하고 넘어가기도 합니다.
문제는 장애가 났을 때 드러납니다. 서버 OS 경험이 없으면 애플리케이션 문제인지 OS 문제인지 가려내지 못합니다. 인프라 엔지니어 수준의 전문성은 아니더라도, 기본적인 서버 관리는 할 수 있어야 하는 이유입니다.
책이 드는 세 가지 사고가 이를 잘 보여 줍니다.
| 증상 | 실제 원인 |
|---|---|
| 프로세스를 재시작했는데 구동되지 않고 비정상 종료 | 직전에 root로 띄운 프로세스가 만든 파일을, 일반 계정이 수정할 권한이 없었음 |
| 서버에 아예 접속이 안 되어 결국 OS를 재설치 | 디스크 사용률 100%, 재부팅으로도 복구 불가 |
| 결제 승인 API가 특정 서버에서만 간헐적으로 실패 | 그 서버 시간이 실제보다 30초 이상 느려, 15초 내 요청만 받는 API가 거부 |
셋 다 코드에는 문제가 없었습니다. 그런데 원인을 찾기까지 오래 걸렸다는 점이 공통점입니다. 어디를 봐야 할지 모르면 시간은 그만큼 더 걸립니다.
서버 관리 연습은 가상화 도구로
예전에는 실제 컴퓨터에 OS를 직접 설치하며 연습해야 했고, 잘못 설치하면 다시 설치하느라 시간을 날렸습니다. 지금은 버추얼박스(VirtualBox)나 베이그런트(Vagrant) 같은 가상화 도구로 OS 설치부터 각종 설정까지 부담 없이 반복해 볼 수 있습니다.
핵심 내용
계정과 권한
리눅스 계정 관리의 출발점은 root 계정에 접근할 수 있는 인원을 제한하는 것 입니다. root는 OS를 설치하면 기본 생성되는 모든 권한을 가진 관리자 계정이라, 무엇이든 할 수 있습니다. 보통 인프라 담당자만 root 권한을 갖고 개발자는 별도로 생성한 사용자 계정으로 서버에 접속합니다. OS 관리에 익숙하지 않은 사람에게 root를 주면 재앙에 가까운 사고가 벌어질 수 있습니다.
계정 여러 개를 하나의 그룹으로 묶어 권한을 관리할 수도 있지만, 한 서버에 개발자용 계정을 여러 개 만들어 쓰는 경우가 드물어 그룹 수준의 권한 관리를 실제로 쓸 일은 많지 않습니다.
파일 권한 읽기
파일을 실행했는데 허가 거부(Permission denied)가 뜬다면 읽기 권한이나 실행 권한이 없다는 뜻입니다. 권한은 ls -l로 확인합니다.
-rw-r--r--. 1 vagrant vagrant 7 11월 10 08:24 README.md
-rwxr--r--. 1 vagrant vagrant 28 11월 10 08:26 run.sh
└ 파일 권한 └ 소유자 └ 소유 그룹 └ 크기 └ 마지막 수정 시간 └ 파일명
권한 문자열은 세 글자씩 끊어 읽습니다. rwxr--r--이면 소유자는 읽기·쓰기·실행, 그룹은 읽기, 다른 계정도 읽기입니다.
| 대상 | 첫 3글자 | 중간 3글자 | 마지막 3글자 |
|---|---|---|---|
| 누구의 권한인가 | 소유자 | 그룹 | 다른 사용자 |
| 문자 | 의미 | 숫자 |
|---|---|---|
| r | 읽기(Read) | 4 |
| w | 쓰기(Write) | 2 |
| x | 실행(eXecute) | 1 |
chmod로 권한 바꾸기
숫자는 합으로 조합합니다. 읽기+쓰기는 6, 전체 권한은 7(4+2+1)입니다. 소유자·그룹·다른 계정 순으로 세 자리를 지정합니다.
$ chmod 754 run.sh # 소유자 전체, 그룹 읽기·실행, 다른 계정 읽기숫자 대신 항목별로 지정할 수도 있습니다. 대상 × 동작 × 권한의 조합입니다.
| 대상 | 동작 | 권한 |
|---|---|---|
u 소유자 / g 그룹 / o 다른 사용자 / a 모두 | + 추가 / - 제거 / = 지정 | r 읽기 / w 쓰기 / x 실행 |
$ chmod u+x run.sh # 소유자에게 실행 권한 추가
# g-x : 그룹에서 실행 권한 제거
# o=rx : 다른 사용자에게 읽기와 실행 권한만 지정
# a+r : 모두에게 읽기 권한 추가습관적인 chmod 777 금지
777은 읽기·쓰기·실행 권한을 모두에게 부여하므로 보안에 취약해집니다. 권한 문제가 났을 때 777로 뭉개는 것이 가장 빠른 해결처럼 보이지만, 딱 필요한 만큼만 부여하는 습관을 들여야 합니다.
sudo로 권한 빌리기
개발자도 systemctl로 서비스를 재시작하는 등 root 권한이 필요할 때가 있습니다. 그때마다 인프라 담당자에게 요청하면 업무 효율이 떨어지고, 그렇다고 root를 넘겨주면 보안이 위험해집니다. 이 사이를 메우는 것이 sudo 입니다.
허용 범위는 /etc/sudoers 파일이나 /etc/sudoers.d 디렉터리에서 관리하며, 수정은 visudo 명령어로 합니다. sudoers는 읽기 전용 파일이라 vi로 고치려면 권한을 앞뒤로 바꿔야 하지만, visudo는 그 과정을 알아서 처리합니다.
설정은 계정 호스트=(실행할 사용자) 명령어 순으로 읽습니다.
root ALL=(ALL) ALL # root는 모든 호스트에서 모든 사용자로 모든 명령어 실행 가능
user1 ALL=(ALL) ALL # user1도 동일 (sudo 실행 시 user1 암호 입력)
권한을 좁히는 방향은 두 축입니다.
| 설정 | 의미 | 위험도 |
|---|---|---|
user1 ALL=(ALL) ALL | 암호를 물어본 뒤 모든 명령어 허용 | 높음 |
user1 ALL=(ALL) NOPASSWD: ALL | 암호 없이 모든 명령어 허용 — root 암호를 알려준 것과 사실상 같음 | 매우 높음 |
user1 ALL=(ALL) NOPASSWD: /usr/bin/systemctl | 특정 명령어만 허용 | 보통 |
user1 ALL=(ALL) NOPASSWD: /usr/bin/systemctl start docker | 특정 인자를 사용한 명령까지만 허용 | 낮음 |
개발·임시 테스트 서버가 아니라면 NOPASSWD: ALL은 쓰지 않고, 실제로 필요한 명령어만 열어 주는 것이 원칙입니다.
root로 서버 프로그램을 띄우지 않습니다
일반 계정으로 실행해야 하는 서버 프로그램을 root로 실행하면, 그 프로세스가 만든 파일을 나중에 일반 계정이 수정하지 못해 권한 문제를 겪습니다. root는 모든 권한을 가지므로 실행 자체는 성공하는 것이 함정입니다. 또한
rm -rf처럼 되돌릴 수 없는 명령어는 root 권한이 붙는 순간 사고 여파가 훨씬 커지므로, 삭제 전에는 현재 디렉터리와 대상 목록을 반드시 확인합니다.
프로세스 다루기
클라이언트가 서버에 연결되지 않으면 가장 먼저 서버 프로세스가 정상 동작 중인지 확인해야 합니다. 재시작 과정에서 이전 프로세스가 남아 있거나, 시작 중 오류로 구동에 실패한 경우가 흔하기 때문입니다.
| 목적 | 명령어 |
|---|---|
| 프로세스 ID·소유자·명령행 인자 확인 | ps aux, ps -eaf |
| CPU·메모리 사용량 실시간 확인 | top, htop |
| 메모리 점유 상위 5개 확인 | ps aux --sort -rss | head -n 6 |
프로세스 종료
종료가 되지 않을 때는 kill 옵션 PID로 직접 끝냅니다. 두 옵션의 차이가 핵심입니다.
| 옵션 | 시그널 | 동작 |
|---|---|---|
-15 (기본값) | SIGTERM | 프로세스에 종료 신호를 보내면, 프로세스가 임시 파일 삭제나 스프링 빈 소멸 처리 같은 정리 작업을 수행한 뒤 종료 |
-9 | SIGKILL | 강제 종료. 정리 작업을 할 기회를 주지 않음 |
처음부터 -9로 죽이지 말고, -15로 여러 차례 시도했는데도 종료되지 않을 때만 -9를 씁니다. 이 순서를 지키지 않으면 정리되지 않은 임시 파일이나 잠금이 남아 다음 기동을 방해합니다.
포그라운드와 백그라운드
tail, top, vi처럼 터미널에서 실행하는 프로그램은 기본적으로 포그라운드 프로세스 입니다. 터미널에 연결되어 키보드·화면으로 사용자와 상호작용하는 대신, 터미널과의 연결이 끊기면 함께 종료됩니다.
톰캣 같은 서버 프로세스는 항상 떠 있어야 하므로 터미널과 연결되지 않는 백그라운드 프로세스 로 실행합니다. 명령어 뒤에 &를 붙이면 됩니다.
$ java -Dserver.port=9090 -jar server.jar &그런데 &만으로는 부족합니다. 사용자가 로그아웃할 때 전달되는 HUP 시그널에 프로세스가 함께 종료될 수 있기 때문입니다. nohup(No Hang Up)을 함께 쓰면 HUP 시그널이 프로세스에 전달되지 않습니다.
flowchart TD L["로그아웃 · 터미널 연결 끊김"] --> H{HUP 시그널} H -->|"포그라운드 (tail -f)"| A["종료"] H -->|"명령어 &"| B["함께 종료될 수 있음"] H -->|"nohup 명령어 &"| C["계속 실행"]
출력을 어디에 남길 것인가
nohup으로 실행한 프로세스의 콘솔 출력은 기본적으로 nohup.out에 기록됩니다. 원하는 파일로 보내려면 리디렉션을 씁니다.
$ nohup java -Dserver.port=9090 -jar server.jar > server.log 2>&1 &2>&1
2는 표준 오류,>는 리디렉션 연산,&1은 표준 출력을 뜻합니다. 즉 표준 오류를 표준 출력과 동일한 경로로 보내라는 의미이고, 위 명령어에서는 결국 둘 다server.log에 쌓입니다. 이걸 빼먹으면 정작 급할 때 필요한 에러 스택 트레이스만 로그에서 사라집니다.
디스크 용량 관리
디스크가 100%가 되면 아무 작업도 할 수 없는 상태가 되고, 재부팅으로도 복구되지 않아 OS를 다시 설치해야 하는 상황까지 갑니다. 그래서 모니터링 도구에 사용률 기반 알림을 두 단계로 걸어 둡니다. 보통 80%를 넘으면 경고, 90%를 넘으면 위험으로 잡습니다.
알림을 받으면 범인을 찾는 순서는 정해져 있습니다.
$ df -h # 어떤 파티션의 사용률이 높은지
$ du -sh ./* # 어떤 디렉터리·파일이 용량을 차지하는지
$ du -sh */ # 파일 제외, 하위 디렉터리 용량 합만-h는 바이트 대신 K·M·G 단위로 표시하고, -s는 하위 디렉터리 용량을 합산해 보여 줍니다.
디스크를 채우는 두 가지
서버 프로그램에서 디스크 용량과 관련된 파일은 사실상 로그 파일 과 임시 파일 둘뿐입니다.
로그는 문제 원인을 분석하려면 반드시 남겨야 하지만, 남기는 만큼 계속 쌓입니다. 그래서 관리 전략이 필요합니다.
- 보관 기간을 정해 오래된 로그를 삭제합니다(예: 로컬은 최대 90일).
- 보관이 필요하면 NAS나 S3 같은 별도 저장소로 백업한 뒤 삭제합니다.
- 로그와 텍스트는 압축 효율이 높으므로, 일정 기간 지난 파일은 압축해 보관합니다.
$ find ./logs -mtime +29 -type f -delete # 30일 이전 파일 삭제find는 -exec 옵션과 함께 쓰면 삭제뿐 아니라 압축·이동 등으로도 확장됩니다.
임시 파일도 같습니다. 클라이언트가 올린 파일을 로컬에 임시 보관했다가 파일 서버로 업로드하는 기능은 로컬에 잔여 파일을 남기므로, 일정 시간이 지나면 지워야 합니다.
로그 로테이션과 널 카피
로그 파일을 일자별이나 크기별로 잘라서 교대해 가며 보관하는 것을 로그 로테이션(rotation) 이라고 합니다. 로테이션을 걸어 두지 않으면 파일 하나가 수십 GB까지 커지는데, 이 상태에서는 로그를 검색하거나 복사·압축하는 작업 자체가 운영 중인 서비스에 부담이 됩니다.
서비스를 중지할 수 없는데 디스크가 이미 95%를 넘겼다면 응급 처방으로 널 카피(null copy) 를 씁니다.
$ cp /dev/null out.log/dev/null은 쓰는 데이터가 모두 버려지는 널 장치 파일이라, 이를 복사하면 파일 크기가 0이 됩니다. 프로세스를 건드리지 않고 즉시 공간을 확보할 수 있지만 이전에 쌓인 로그는 전부 유실됩니다.
한 디렉터리의 파일 개수
용량과 별개로 파일 개수 도 관리 대상입니다. 한 디렉터리에 파일이 많아지면 어느 시점부터 탐색 속도가 급격히 느려집니다. 흔한 대응은 날짜·시간 기준으로 디렉터리를 나누는 것으로, /files/2025/07/31/ 같은 구조를 씁니다. 순간 생성량이 많다면 일자 아래를 해시 값으로 한 단계 더 쪼갭니다.
파일 디스크립터 제한
프로세스는 파일 입출력이나 소켓 통신을 할 때 OS로부터 파일 디스크립터(File Descriptor, FD) 를 할당받습니다. OS는 이 개수를 제한하므로, 제한이 1024라면 파일과 소켓을 합쳐 1024개를 넘길 수 없습니다. 동시 접속 1024개를 넘기는 순간 Too Many Open Files와 함께 소켓 생성에 실패합니다.
트래픽 증가에 맞춰 미리 확인하고 늘려야 하는 값이라는 점이 중요합니다. 장애가 난 다음에 확인하면 이미 요청을 받지 못하고 있는 상태입니다.
어느 계층의 제한인가
FD 제한은 여러 계층에 걸쳐 있어 헷갈리기 쉽습니다. 확인과 변경 위치를 정리하면 다음과 같습니다.
| 계층 | 확인 | 변경 |
|---|---|---|
| 현재 터미널 세션 | ulimit -a의 open files, ulimit -n | ulimit -n 개수 (해당 세션 한정, 재접속하면 기본값) |
| 사용자 기본값 | — | /etc/security/limits.conf |
| systemd 서비스 기본값 | systemctl show -p DefaultLimitNOFILE | /etc/systemd/system.conf의 DefaultLimitNOFILE → systemctl daemon-reload |
| 개별 systemd 서비스 | — | 서비스 파일의 [Service] LimitNOFILE= |
| 프로세스 하나의 상한 | sysctl fs.nr_open | /etc/sysctl.conf |
| 시스템 전체 | sysctl fs.file-max | /etc/sysctl.conf → sysctl -p |
| 실행 중인 프로세스에 적용된 값 | cat /proc/PID/limits, prlimit --pid PID | — |
| 실제 사용 중인 개수 | lsof -p PID | wc -l | — |
limits.conf에 넣는 설정은 다음 형태입니다.
* soft nofile 100000
* hard nofile 100000
맨 앞 *은 모든 사용자에 적용한다는 뜻이고, nofile이 FD 개수입니다. soft 는 기본값, hard 는 사용자가 올릴 수 있는 최대값입니다. 프로세스는 soft 값을 기본으로 쓰다가 부족하면 hard까지 올릴 수 있고, hard를 넘지는 못합니다.
systemd 서비스는 ulimit·limits.conf를 보지 않습니다
여기서 가장 자주 헛발질합니다.
limits.conf를 고치고ulimit -n으로 확인까지 했는데 서비스에는 반영되지 않는 이유는, systemd로 실행되는 서비스가/etc/systemd/system.conf의DefaultLimitNOFILE값을 사용하기 때문입니다. 서비스로 띄우는 서버라면systemctl show -p DefaultLimitNOFILE또는/proc/PID/limits로 실제 프로세스에 적용된 값 을 확인해야 합니다.
fs.nr_open, fs.file-max는 기본값이 충분히 크므로 대개 손댈 일이 없습니다. 조정할 곳은 거의 항상 사용자·서비스 계층입니다.
시간 동기화
컴퓨터가 관리하는 시간과 실제 시간 사이에는 오차가 생기고, 이 오차는 시간이 갈수록 누적되어 수 초 이상 벌어지기도 합니다. 서버가 여러 대면 서버마다 시간이 제각각인 상황이 됩니다.
시간에 의존하는 기능에서 이 차이는 곧바로 버그가 됩니다. 카산드라 클러스터처럼 각 노드가 서버 시간으로 데이터 생성 시각을 기록하는 경우, 시간 차이가 크면 어느 것이 최신 데이터인지 판단이 틀어집니다. 앞서 본 결제 승인 API 사고도 같은 종류입니다.
해법 자체는 간단합니다. chrony나 ntp 같은 서비스로 주기적으로 서버 시간을 맞추면 됩니다. 문제는 설정을 빠뜨리는 것이므로, 서버 구성 체크리스트에 시간 서비스 설정을 필수 항목으로 넣어 최초 구성 시점에 활성화하는 방식으로 방지합니다.
크론으로 스케줄링
서버를 운영하면 “매일 0시 5분에 90일 지난 로그 삭제”, “매주 일요일 4시에 DB 풀백업” 같은 주기 작업이 생깁니다. 크론(cron) 은 유닉스 계열 OS의 시간 기반 스케줄러로, 크론탭(crontab) 에 정의된 스케줄에 맞춰 작업을 실행합니다.
$ crontab -l # 등록된 작업 조회
$ crontab -e # 편집 (기본 편집기는 보통 vi)크론탭은 OS 계정별로 존재하므로, 각 계정에 필요한 작업을 따로 설정합니다.
시간 패턴
* * * * * 실행 명령어
│ │ │ │ └ 요일 (0-6, 0이 일요일, 6이 토요일)
│ │ │ └── 월 (1-12)
│ │ └──── 월의 일 (1-31)
│ └────── 시 (0-23)
└──────── 분 (0-59)
*은 “매 시간”을 의미합니다. 분 위치의 *은 매분, 월 위치의 *은 매월입니다.
| 표기 | 의미 |
|---|---|
0 0 * * * | 매일 0시 0분 |
30 1 1 * * | 매월 1일 1시 30분 |
5 4 * * 0 | 매주 일요일 4시 5분 |
0 1,2,3 * * * | 콤마로 개별 값 지정 — 매일 1시, 2시, 3시 정각 |
30 1-3 * * * | 구간 지정 — 매일 1시·2시·3시 30분 |
*/10 1 * * * | 간격 지정 — 매일 1시 0분부터 50분까지 10분 간격 |
0 */2 * * * | 0시부터 2시간 간격으로 0분에 |
출력은 반드시 파일로
0 4 * * * /data/project/run.sh > /data/project/out.log 2>&1
크론으로 실행한 명령어의 콘솔 출력을 별도 파일에 남겨 두어야 나중에 문제가 생겼을 때 원인을 분석할 수 있습니다. 크론 작업은 사람이 보고 있지 않은 시각에 실행되므로, 출력을 남기지 않으면 실패했다는 사실조차 모르고 지나갑니다.
크론탭이 설정되지 않을 때
실행 권한이 없으면
/etc/cron.deny(또는/etc/cron.d/cron.deny)에 계정이 등록되어 있는지 확인합니다. 반대로/etc/cron.allow가 존재하면 그 파일에 계정이 등록되어 있어야만 크론탭을 설정할 수 있습니다. 한편 반복이 아니라 일회성 작업이라면 cron이 아니라at명령어를 씁니다.
alias 등록하기
로그를 보려고 긴 디렉터리 경로로 이동하는 것처럼 자주 쓰는 명령어는 별칭으로 등록해 두면 편합니다.
$ alias cdweb='cd /var/www/html' # 현재 터미널 세션에만 적용
$ alias # 현재 정의된 모든 별칭 조회매번 등록하지 않으려면 홈 디렉터리의 .bashrc 하단에 같은 설정을 추가합니다. 로키 리눅스, CentOS, 우분투 모두 기본 셸이 bash입니다.
| 파일 | 실행 시점 | 주로 넣는 것 |
|---|---|---|
.bash_profile | 로그인할 때 한 번 | PATH처럼 전체 세션에 필요한 환경 변수 |
.bashrc | 새 bash 셸을 시작하거나 터미널 창을 열 때마다 | alias처럼 세션마다 적용할 설정 |
.bash_profile은 내부에서 .bashrc를 실행하는 코드를 포함하고 있어, 로그인 시점에 .bashrc 설정도 함께 적용됩니다.
네트워크 확인
IP 정보
ifconfig는 네트워크 인터페이스별 정보를 보여 줍니다. eth0은 일반적인 네트워크 인터페이스, lo는 로컬 루프백 인터페이스입니다.
| 항목 | 의미 |
|---|---|
inet | IP 주소 |
inet6 | IPv6 주소 |
ether | MAC 주소 |
RX / TX | 수신/전송한 패킷 수와 바이트 크기 |
nc로 연결 확인
외부·내부 서비스와의 연결이 불안정할 때 가장 먼저 확인할 것은 해당 서버의 특정 포트로 연결이 되는지 입니다. nc를 쓰면 애플리케이션을 띄우지 않고도 확인할 수 있습니다.
$ nc -z -v www.daum.net 443 # TCP 443 포트 연결 확인
$ nc -z -u -v localhost 6100 # UDP 포트 확인
$ nc -l -v -p 1234 # 리스닝(서버) 모드로 대기-z는 데이터 전송 없이 포트가 열려 있는지만 확인하고, -v는 추가 정보를 출력합니다. -v가 없으면 연결에 성공해도 아무 메시지가 나오지 않아 답답하므로 붙이는 편이 낫습니다.
-l 리스닝 모드는 실제 서버 프로세스를 만들기 전에 두 노드 간 통신이 되는지 미리 검증할 때 유용합니다. 다만 클라이언트가 연결하면 nc가 종료되므로 확인할 때마다 다시 실행해야 합니다. HTTP API 응답까지 확인하려면 curl이나 wget을 씁니다.
netstat으로 포트 확인
서버 프로세스는 떠 있는데 연결이 안 된다면, 실제로 그 포트에서 클라이언트 연결을 기다리고 있는지 확인해야 합니다.
$ netstat -lputn # 열려 있는 서버 포트 목록
$ netstat -anp | grep 12931 # 특정 포트 사용 여부| 옵션 | 의미 |
|---|---|
-l | 리스닝 중인 서버 소켓만 |
-p | 소켓을 사용하는 PID/프로그램 이름 |
-t / -u | TCP / UDP 소켓 |
-n | 포트·주소를 이름 대신 숫자로 |
-a | 사용 중인 전체 포트 |
포트 충돌
“이미 포트가 사용 중”이라는 오류로 서버 기동에 실패하면 netstat으로 그 포트를 쓰는 프로그램을 확인합니다. 간혹 다른 서버가 아니라 클라이언트 소켓 이 같은 포트 번호를 잡아 충돌하기도 합니다. 이를 막으려면 서버 포트는 9,999번 이하만 쓰고, OS가 클라이언트 소켓에 할당하는 임시 포트는 10,000번 이상만 쓰도록 범위를 분리합니다.
비교 / 트레이드오프
디스크가 꽉 찼을 때 쓸 수 있는 카드는 세 가지이고, 안전한 순서대로 시도해야 합니다.
| 수단 | 확보량 | 데이터 손실 | 언제 |
|---|---|---|---|
오래된 로그 삭제(find -mtime) | 보관 정책만큼 | 정책상 불필요한 것만 | 평상시 자동화 |
| 압축 보관 | 큼(로그·텍스트는 압축률 높음) | 없음 | 보관 의무가 있을 때 |
널 카피(cp /dev/null) | 즉시, 파일 크기 전부 | 해당 파일 전체 유실 | 서비스 중지 불가 + 임계 상황 |
프로세스 실행 방식도 “터미널과의 연결”이라는 하나의 축으로 정리됩니다.
| 방식 | 터미널 연결 | 로그아웃 시 | 용도 |
|---|---|---|---|
| 포그라운드 | 있음 | 종료 | 로그 확인, 편집 등 상호작용 |
명령어 & | 없음 | 함께 종료될 수 있음 | 잠깐 돌리는 작업 |
nohup 명령어 & | 없음 | 계속 실행 | 서버 프로세스 |
내 생각
- 이 장의 명령어들은 “장애 대응 시나리오”로 묶어서 외워야 실전에서 나옵니다. 연결이 안 되면
ps→netstat→nc, 서버가 이상하면df -h→du -sh */, 소켓이 안 열리면/proc/PID/limits→lsof순서로 손이 가야 명령어가 아니라 절차가 됩니다. ulimit과 systemd의 불일치는 반드시 한 번은 당합니다. 서비스로 띄운 서버의 FD 제한은 셸에서 확인한 값과 다르므로, 확인은 언제나 실행 중인 프로세스 기준(/proc/PID/limits)으로 해야 합니다.kill -9를 먼저 치는 습관은 정리 로직을 무력화합니다. 스프링의 graceful shutdown이나 커넥션 반환이 전부 SIGTERM 핸들러에 걸려 있으므로,-9는 컨테이너 환경에서도 마지막 수단이어야 합니다.2>&1을 빼먹는 것은 사실상 에러 로그를 버리는 것과 같습니다. 백그라운드 실행과 크론 양쪽에서 동일하게 적용되는데, 정작 필요한 순간에 스택 트레이스만 없어서 원인 추적이 막힙니다.- 디스크 관리는 “알림 임계치를 걸었는가”가 실제 방어선입니다. 로그 로테이션과 보관 정책은 서버 구성 시점에 같이 넣어야 하고, 널 카피는 응급 처방이지 운영 방식이 아닙니다.
- 시간 동기화는 코드로는 절대 못 잡는 종류의 버그를 만듭니다. 결제·토큰 만료·분산 데이터 최신성 판단이 전부 시간에 의존하므로, 서버 프로비저닝 체크리스트에 chrony 설정을 필수 항목으로 박아 두는 편이 훨씬 쌉니다.
- sudo 설정의 기본값은
NOPASSWD: ALL이 아니라 명령어 화이트리스트여야 합니다. 개발자에게 서비스 재기동 권한을 주는 목적이라면systemctl start/stop <서비스>까지 좁히는 것이 최소 권한 원칙에 맞습니다.
관련 개념
- Ch02 느려진 서비스, 어디부터 봐야 할까 — 자원 사용량 모니터링과 병목 지점 판단
- Ch07 IO 병목, 어떻게 해결하지 — 소켓과 파일 디스크립터 소비
- Ch08 실무에서 꼭 필요한 보안 지식 — 최소 권한 원칙과 운영 계정 관리