
담당자가 전원 퇴직한 '용도 불명 서버'를 발굴 조사하기 ― 서버 고고학의 권유
요약
인수인계 자료가 없는 용도 불명의 Linux 서버를 체계적으로 조사하는 '서버 고고학' 방법론을 소개합니다. 시스템에 부하를 주지 않는 읽기 전용 원칙을 바탕으로 포트, 프로세스, cron, 디렉토리 등을 분석하는 절차를 다룹니다.
핵심 포인트
- 조사 시 설정 변경이나 서비스 재시작을 금지하는 읽기 전용 원칙 준수
- 시스템 부하 방지를 위해 find, du 명령어 사용 시 타임아웃 및 범위 제한 필수
- LISTEN 포트, 프로세스 트리, cron 설정을 통해 서버의 주요 용도 파악
- Ansible을 활용한 조사 자동화 및 AI를 통한 용도 추측 가능성 제시
IP 주소와 SSH 로그인만이 단서인, 용도를 알 수 없는 Linux 서버. 구축자는 이미 퇴직했고, 인수인계 자료도 없다. 업무상 이런 머신을 여러 대 마주하게 되어, 조사 절차를 체계화했다. 아무도 당시를 알지 못하는 정체를 남겨진 흔적을 통해 추측하는 것 ― 이 행위가 유적 발굴과 닮았기에, 개인적으로 "서버 고고학"이라고 부르고 있다.
이 기사에서는 확립된 조사 절차(무엇을 어떤 순서로 볼 것인가)와, 그것을 Ansible로 자동화하여 AI에게 용도 추측을 시키는 부분까지 작성한다.
조사 대상은 단지 "용도 불명"일 뿐, 실제 운영 중일 수도 있다. 그래서 철칙이 두 가지 있다.
읽기 전용 (설정 변경·파일 생성·서비스 재시작은 일절 하지 않는다. 조사 자체가 장애로 이어지면 본말전도임)
부하를 주지 않는다 (find나 du에는 timeout과 범위 제한을 붙인다. 특히 NFS나 s3fs 등 네트워크 마운트 너머의 스캔은 끝나지 않음·스톨(Stall) 발생·과금 발생이라는 삼중고를 겪으므로 제외한다)**
이후의 커맨드는 모두 이 원칙을 따르고 있다.
시행착오 끝에 다음 순서로 정착했다. 앞쪽의 질문일수록 적은 커맨드로 큰 단서를 얻을 수 있다. 실제로 질문 1과 2에서 대략적인 윤곽이 잡히고, 3 이후는 뒷받침 근거와 영향 범위의 확인이 되는 경우가 많았다.
무엇이 돌아가고 있는가 (포트·프로세스·cron) -
무엇이 놓여 있는가 (앱 디렉토리·설정·인증서·패키지) -
누가 언제 사용했는가 (로그인 이력·커맨드 이력) -
외부와 무엇을 주고받는가 (확립된 연결·마운트) -
언제 만들어졌는가 (구축 시기의 흔적)
처음은 LISTEN 포트. 대기 포트는 용도의 가장 유력한 단서이며, 어떤 프로세스가 열고 있는지까지 한 번에 알 수 있다.
ss -tlnp # TCP
ss -ulnp # UDP
80/443이라면 Web, 3306이라면 MySQL 계열, 10050이라면 Zabbix 에이전트, 하는 식이다. 다음은 프로세스 트리와 "의도적으로 상시 동작하도록 설정된 것"의 확인.
ps auxf
systemctl list-unit-files --state=enabled
systemctl --failed # "과거에 동작했으나 고장 난" 기능을 찾을 수 있음
--failed는 은근히 중요하다. 기동에 실패한 채 방치된 유닛을 통해 "본래 있었어야 할 기능"이 보일 때가 있다.
그리고 cron. 야간 배치나 월간 집계 전용 서버는 낮에 보면 거의 무풍 상태이며, LISTEN 포트에도 나타나지 않는다. 이런 서버의 정체는 포트가 아니라 cron에 나타난다.
cat /etc/crontab
ls /etc/cron.d/ && cat /etc/cron.d/*
for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u "$u" 2>/dev/null; done
...
사용자별 crontab의 전체 사용자 스캔을 잊기 쉬우므로 주의해야 한다. /var/spool/cron/ 직하를 ls 하는 것만으로도 "crontab을 가진 사용자"는 알 수 있다.
다음은 "물건". 앱이 놓이기 쉬운 디렉토리를 갱신 일시와 함께 살펴본다.
for d in /opt /usr/local /srv /var/www /data /app /home; do
echo "=== $d ==="; ls -lat "$d" 2>/dev/null | head -50
done
...
디렉토리 이름과 갱신 일시만으로도 "언제쯤까지 손이 닿았던 무언가"인지 꽤 파악할 수 있다.
Web 서버가 동작하고 있다면 설정을 읽는다. 설정 내의 도메인 이름과 proxy_pass의 전송 대상은 그대로 용도 설명문이 되는 경우가 많다.
find /etc/nginx -maxdepth 4 -type f \( -name '*.conf' -o -path '*sites-enabled*' \) 2>/dev/null
find /etc/apache2 /etc/httpd -maxdepth 4 -type f 2>/dev/null | head -200
SSL 인증서는 CN/SAN과 유효 기간을 확인한다. 유효 기간이 몇 년 전에 만료된 채라면, 그 무렵부터 사용되지 않았다는 방증이 된다.
for f in $(find /etc/letsencrypt/live /etc/ssl/certs /etc/pki/tls/certs -type f \
\( -name 'cert.pem' -o -name '*.crt' -o -name 'fullchain.pem' \) 2>/dev/null | head -50); do
echo "=== $f ==="
...
패키지는 RedHat 계열이라면 rpm -qa --last가 우수하며, 설치된 날짜 순으로 나열된다. "구축 직후에 무엇을 설치했고, 언제까지 업데이트했는가"라는 역사를 읽을 수 있다.
rpm -qa --last | head -300
마지막으로 선배들의 메모 찾기. 원시적이지만, 이것으로 단번에 해결된 적이 실제로 있다.
timeout 60 find /root /home /opt /usr/local /srv /var/www /etc -xdev -maxdepth 6 -type f \
\( -iname 'README*' -o -iname 'NOTES*' -o -iname '*.md' \
-o -iname '手順*' -o -iname 'メモ*' -o -iname 'INSTALL*' \) 2>/dev/null | head -200
-xdev를 사용하여 파일 시스템 경계를 넘지 않는(=네트워크 마운트로 내려가지 않는) 것이 포인트다.
last -F | head -100
lastlog | awk 'NR==1 || $0 !~ /Never logged in/'
최종 로그인이 몇 년 전이라면, 적어도 사람의 손에 의한 운영은 멈췄을 가능성이 높다. 그리고 bash_history. 전임자의 작업이 그대로 남아 있는, 고고학적으로 가장 "육성"에 가까운 사료라고 생각한다.
for d in /root /home/*; do
for h in .bash_history .zsh_history; do
[ -f "$d/$h" ] && { echo "=== $d/$h ==="; tail -n 300 "$d/$h"; }
...
함께 /etc/passwd, /etc/sudoers (및 /etc/sudoers.d/), 각 사용자의 authorized_keys를 통해 "누가 접근할 수 있는 상태였는가"를 파악한다.
지금까지의 가설을 다른 시스템과의 관계를 통해 뒷받침한다.
ss -anp state established # 현재 연결되어 있는 상대
cat /etc/hosts # db-prod-01 같은 수기 엔트리는 많은 것을 말해준다
cat /etc/fstab # NFS 마운트 등
...
여기서 얻을 수 있는 정보는 용도 추정뿐만 아니라, "중단했을 때 어디에 영향이 가는가"에 대한 추산으로 직결된다.
구축 시기를 알게 되면, "그 무렵에 시작된 프로젝트는 무엇인가"라는 조직 측의 기록과 대조할 수 있다. 기술적인 흔적과 인간의 기억을 잇는 가교가 된다.
stat /etc/ssh/ssh_host_*key # 최초 셋업 시 생성됨
stat /
ls -la /var/log/installer/ # Debian/Ubuntu 계열
...
확인할 항목과 순서를 고정할 수 있었기에, Ansible의 role로 구현했다. 구성은 다음과 같다.
archaeology/
├── playbooks/audit.yml # 실행 파일 (Python 탐지 등의 pre_tasks 포함)
├── roles/server_audit/
...
모든 태스크에 changed_when: false / failed_when: false를 붙여, read-only이면서 "개별 명령어가 실패하더라도 조사 전체는 멈추지 않는" 구조로 만들었다. 태스크는 전부 이 형태의 반복이다.
- name: "02. TCP listening (ss -tlnp)"
ansible.builtin.shell: "ss -tlnp 2>/dev/null || ss -tln"
register: out_02_tcp
...
섹션 단위로 태그를 붙여두었으므로, --tags listening_ports,cron과 같은 필터링 실행도 가능하다.
레거시 서버 특유의 노하우도 두 가지 포함했다.
Python 인터프리터 (Python interpreter) 자동 검출. 레거시 환경은 Python 경로가 제각각(/usr/libexec/platform-python이거나 python2만 있는 등)이므로, gather_facts: false로 설정해 두고 Python이 필요 없는 raw 모듈로 먼저 검출한 뒤 ansible_python_interpreter를 동적으로 설정한다.
- name: "Python interpreter 검출 (raw, Python 불필요)"
ansible.builtin.raw: |
for p in /usr/bin/python3 /usr/local/bin/python3 \
...
네트워크 마운트 제외. du나 find를 s3fs/NFS/CIFS에 실행하면 지옥을 맛보게 되므로, pre_tasks에서 /proc/self/mounts로부터 네트워크 계열 마운트 포인트를 추출하여 후속 태스크의 스캔 대상에서 제외한다.
- name: "(safety) 네트워크/FUSE 계열 마운트 검출"
ansible.builtin.shell: |
awk '$3 ~ /^(fuse|fuse\.|s3fs|sshfs|nfs|cifs|smb|davfs|gcsfuse|fuseblk)/ {print $2}' \
...
실행하면 대상 서버 1대당 1개의 Markdown 리포트(reports/<호스트명>.md)가 생성된다.
이 부분이 개인적으로 가장 마음에 드는 지점이다. 리포트는 Jinja2 템플릿으로 Markdown화하는데, 도입부의 YAML frontmatter에 분석 지시 사항 그 자체를 심어두었다.
---
report_kind: legacy_server_audit
goal: |
...
즉, 생성된 파일을 Claude와 같은 AI에 그대로 첨부하기만 하면, 프롬프트를 따로 작성하지 않아도 분석이 시작된다. 돌아오는 결과는 다음과 같이 근거를 갖춘 추측이다.
"포트 443에서 대기 중이며, 인증서의 도메인 이름은 사내 포털임. 단, 최종 로그인은 3년 전이며 인증서도 만료됨. 사내 포털의 구 환경으로, 현재는 휴면 상태인 것으로 추측됨."
인간은 이 추측을 원본 데이터와 대조하며 리뷰하고, 중지 또는 이관 판단에 집중하면 된다.
용도 추정은 '여러 흔적의 교차 검증'을 통해 정확도가 높아진다. 포트만 보거나 이력만 봐서는 판단을 그르치기 쉽다. 독립된 여러 단서가 같은 방향을 가리킬 때 비로소 확신도 높은 추측이 된다. 이는 인간이든 AI든 마찬가지이며, 그렇기에 노하우를 개인의 직관에 맡기지 않고, 확인할 항목과 순서를 정해 누락 없이 수집하는 것에 가치가 있었다. AI 추측의 질은 제공하는 재료의 망라성에 의해 결정된다.
'없다'는 사실도 단서가 된다. cron이 비어 있음, 로그인 이력이 끊김, 인증서가 만료된 채 방치됨 — 이러한 '없음'의 축적이 휴면 상태의 증거가 된다.
수집한 정보의 취급은 사전에 결정해야 한다. 리포트에는 내부 IP, bash_history, authorized_keys, 인증서의 CN/SAN 등 기밀성이 높은 정보가 한데 모여 들어간다. 효율적으로 수집할 수 있다는 것은, 유출되었을 때의 영향도 크다는 뜻이다. 보관 장소와 공유 범위(그리고 AI에게 전달해도 되는지 여부)는 조사를 시작하기 전에 결정해 두어야 했다. 리포지토리에서는 reports/를 .gitignore에 추가하고, 실제 IP를 포함하는 inventory도 템플릿만 Git 관리 대상으로 삼고 있다.
"건드리면 안 될 것 같아서 아무도 손대지 못하는 서버"는 어느 조직에나 있을 것이다. Read-only 방식의 발굴 조사를 통하면, 그 정체는 의외로 쉽게 파악할 수 있다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기