Azure VM의 OS에 Swap이 없는 이유 (그리고 이 블로그 서버가 종종 죽은 이유)

✍️

🛠️

👤

💥 가끔식 터지던 블로그 서버 (의문의 Out Of Memory Kill)

종종 몇몇 분들이 이 블로그에 접속 시도를 했을때, 접속이 안되고 클라우드플레어의 에러 페이지를 본 사람들이 있을 것이다. 죄송스럽게도, 모니터링 시스템을 제대로 구축 안해두고 내팽겨쳐두고, 서버가 터진걸 인지 못하고 있다가 어느날 접속이 안되는 걸 보고 뒤늦게 살리고 있는 상황이 반복되고 있었다.

지금 당장 서버 모니터링을 어떤식으로 해야할지가 아니라, 그 이전에 도대체 이 서버가 왜 터졌는지 분석하기로 해보았다.(물론, 서버부터 먼저 살려놓고)

이번에 서버가 터졌을 때, Auzre의 부트 진단에서 MariaDB가 Out of memory를 마지막 단말마로 남긴 로그를 확인했다. 이전에도 서버가 죽었을때도 콘솔 로그인 화면에서 같은 로그를 제대로 체크했어야 했는데, 그냥 아무 생각 없이 VM 정지시키고 재시작해서 살리는데 급급했던것 같다. 뒤늦게 kern.log 파일을 확인해보니 다음과 같이 남아있었다.

BASH

{username}@{hostname}:~$ sudo cat /var/log/kern.log | grep -i 'out of memory'

2025-08-03T{time}+00:00 {hostname} kernel: Out of memory: Killed process {PID} (mariadbd) total-vm:1112632kB, anon-rss:140116kB, file-rss:132kB, shmem-rss:0kB, UID:{num} pgtables:640kB oom_score_adj:0

2025-08-09T{time}+00:00 {hostname} kernel: Out of memory: Killed process {PID} (mariadbd) total-vm:1112632kB, anon-rss:145084kB, file-rss:80kB, shmem-rss:0kB, UID:{num} pgtables:636kB oom_score_adj:0

OOM Kill이 MariaDB의 과한 메모리 점유를 막다가 결국 OOM Kill마저 실패해서 서버가 통째로 Hang이 걸린 상태였다. 이걸 왜 파악하고 있냐면, 당시 서버가 죽었을 때, SSH도 제대로 작동이 안될정도이고, 정말 심각하게 Azure 직렬 콘솔 마저 연결하지 못하고 무한 루프 상태에 놓여져 있었다. 사실상 OOM Kill도 실패하여 커널패닉으로 시스템이 마비된 상태였다는 뜻이다.

🤔 그래서 정확히 뭐가 문제일까?

이렇게 주어진 상황만 보았을 때, MariaDB가 서버를 터트렸으니, 당연히 해당 DB 컨테이너를 의심할 것이다. 하지만, 방향을 바꿔서 먼저 이 블로그를 운영중인 환경을 먼저 진단 해보기로 했다.

현재 이 블로그는 Azure의 IaaS인 가상 머신 기능에서 Ubuntu 이미지를 선택하고, 여기에 Docker를 설치해 WordPress + nginx + MariaDB로 구성해서 운영하고 있다. Azure VM크기는 정말 작은 B1s(1 Core, MEM 1GiB, HDD)로 구성하여 이용하고 있다.1 심지어 블로그 서비스를 이용하기 이전에는 B1ls를 쓰고 있었다.

여기까지 내용을 읽었으면 ‘당연하게도, 서버가 터지는건 시간 문제였다.’라고 누구든지 말했을 것이다. 하지만 뭔가 이상하다. 아무리 메모리가 부족하다고 해도, DB가 사용하는 메모리가 많다고 해도 이정도일까? 어느정도 규모가 있는 트랜잭션을 처리하는 것이 아닌데, 메모리가 부족하다는게 이해가 안되는 상황이었다. 물론, 누군가는 이런 극한환경에서 서비스를 돌리는게 말이 되냐고 그냥 스케일 업(Scale-up)하는게 맞지 않냐고 반문 할 수 있다.

사실 Azure VM 사용을 그만하고 로컬 인프라(온프레미스)환경으로 옮기면 간단하게 해결되는 문제이긴하다. 과거에 유지보수 작업이나 테스트 작업을 하면서 이미 로컬 인프라로 환경을 그대로 마이그레이션해서 서비스를 이어나간적이 있었다. 그렇게 옮기고 서비스를 이어가는것 또한 서비스 중단 타임이 1분도 안되었으니 이전을 하는 것도 서비스에 영향이 크게 없을 것이다.2

그렇게 해결 끝! 하기에는 재미없다. 이러한 극한환경에서 서비스를 계속 굴려보는게 더 재미있다. 실제 프로덕션 환경도 아니고 내 학습과 그 기록을 기록하기 위한 서버이다보니 위의 방법으로 굳이 처리할 필요가 있을까 생각했다.

😦 ‘0B’로되어 있는 Swap 영역: “뭐야 내가 끈적 없는데?”

자, 그래서 VM에 접속하여 로그는 이미 체크했으니, 뭐가 문제일까하고 top이나 free부터 입력해서 분석해야겠다하고 free를 입력했다.

{username}@{hostname}:~$ free -h
        total     used      free   
Mem:    848Mi     813Mi     8Mi    
Swap:   0B        0B        0B

당시 너무 어이없어서 스크린샷을 찍어두지 않아 수치를 비슷하게 적었다.

해당 상황을 보고 뭐지 싶었다. 왜 swap이 0B인거지하고 한 10초간 그대로 멍때렸던것 같다. 바로 과거 기억을 뒤져보며 내가 언제 Swap 영역을 꺼뒀는지 한참 생각했다. 그리고 아무리 생각해도 Swap을 끌리 없다는 결론에 도착했다.

그래서 Azure VM Swap이 왜 없는지 구글링을 해보기 시작했다. 찾고 찾다보니 Azure 이외에도 AWS(Amazon Web Service)에서도 이러한 케이스를 다들 많이 겪고 있는 상황을 확인했다. 이상하다? 왜 AWS도 이러는거지? 생각해보는 과정중 더 찾기는 어렵다고 판단해 바로 Gemini 2.5 Pro 모델에 현 상황을 정리해 이유를 물어보기로 했다. 하지만 그 전에 먼저 작업 해야할게 있었다.

🔨 우선 스왑메모리부터 키고, 서비스부터 살리자

현재 스왑메모리가 문제인걸 인지 했으므로 스왑메모리부터 빠르게 켜야 된다.

BASH

# fallcoate 명령어로 2G의 스왑파일을 생성(용량이 더 필요할 경우 숫자를 바꾸자.)
sudo fallocate -l 2G /swapfile

# root 계정만 읽고 쓸 수 있도록 권한 설정
sudo chmod 600 /swapfile

# 스왑파일을 스왑 형식으로 포맷
sudo mkswap /swapfile

# 스왑 파일 활성화
sudo swapon /swapfile

이제 시스템이 재부팅 이후에도 해당 스왑 파일을 자동으로 불러와 마운트 해야되니, 다음과 같은 명령어를 입력하자.

BASH

# fstab 파일에 스왑파일을 자동으로 마운트 하도록 기록
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 제대로 등록이 되었는지 mount -a 명령어로 확인(마운트에 문제가 있을 경우 에러를 반환한다. 그럴 경우 fstab 파일을 다시 수정해야한다.)
sudo mount -a

# 최종적으로 스왑메모리가 잘 설정되어있는지 확인
sudo swapon -s

이와같이 작업을 완료했으면 스왑메모리가 시스템에 정상적으로 추가되었을 것이다. 만약, 웹 서비스에 필요한 WAS/WEB/DB 서비스가 이미 실행중일 경우 해당 서비스들을 한번 다시 시작해주자. 가끔 이미 서비스가 돌아가있는 상태에서 스왑메모리를 추가하면 바로 반영이 안될 경우가 있다.

☁️ ‘클라우드 네이티브’라는 새로운 철학

Gemini로 얻은 정보는 나에게 클라우드 네이티브라는 새로운 개념을 심어주었다. 처음 보면서도 어처구니 없었지만 곰곰히 생각해보면 납득이 가는 말뿐이었다.

🔥 느리게 죽을빠에(Slow Death) 빠르게 죽어서(Fail Fast) 다시 태어나는게 낫다.

해당 문장을 보자마자 ‘그렇다고 해도 이게 무슨 미친소리야’ 라고 생각했다. 느리게 죽을빠에 빠르게 죽어서 다시 시작한다고? 그렇다. 이 개념은 클라우드가 가진 강점을 제대로 이용하여 빠르게 안정적으로 서비스를 제공한다는 의미이다. 괜히 Swap 메모리로 문제가 있는 서버를 질질 끌고 가는 것보다 다시 빠르게 시작하는게 더 나은 선택이기 때문이다.

왜 클라우드 시스템은 이러한 아키텍처로 구성된 걸까?

🐖 클라우드의 세계에서 서버는 애완동물(Pets)이 아니라 가축(Cattle)이기 때문이다.

클라우드에서는 서버를 애완동물처럼 애지중지 하나하나 관리하면서 살려내야할 대상이 아닌, 문제가 생기면 서버를 가축마냥 도축(제거)하고 건강한 동물을 대려와서 키운다는 개념이다.

여기서 애완동물은 온프레미스 서버 관리가 해당된다 볼 수 있다. 위와 같이 느리게 죽을빠에 빠르게 죽어서 다시 시작한다는 개념을 적용하게되면… 주변에서 진짜 미쳤냐 소리를 들을 것이다. 물리적인 서버의 스케일 업과 스케일 아웃은 간단한 일이 아니다. 온프레미스를 구성한 아키텍처에 따라 다르겠지만, 일반적인 서버를 하나의 서비스를 담아 운영한다면 스케일 업과 스케일 아웃은 그리 쉬운 선택이 아니다.

하지만, 클라우드 환경에서는 다르다. 문제가 되는 VM 인스턴스가 있으면, 해당 인스턴스를 더 높은 사양으로 교체(스케일 업)하거나, 인스턴스를 더 늘려버리는(스케일 아웃) 클릭 단 한번(또는 미리 준비된 명령어)으로 아주 쉽게 수행 할 수 있다. 이렇게 인스턴스를 처리하는 것이 더 안정적이고 빠른 서비스를 제공 할 수 있기 때문이다.

🖇️ 스왑메모리를 끄는 근본적인 기술적 이유

이와같은 클라우드 IaaS의 VM OS 이미지에서 Swap 메모리가 꺼져있는 이유로는 클라우드 서버의 가상디스크 구조에서 출발한다.

일반적인 온프레미스 환경의 베어메탈 서버는 물리적인 HBA(또는 레이드)카드, FC(광) HBA카드, iSCSI 형태로 연결되어 있어도 SAN(iSCSI의 경우 별도의 L2/L3)스위치에 연결되어 있어, 논블로킹 아키텍처와 광활한 대역폭(단일 포트 16Gbps 이상)을 기반으로, 시스템 I/O가 예측불가능한 상황이 되고, 메모리 스왑이 과하게 일어나 성능 저하를 일으키는 스레싱(Trahsing)에 도달해도 위의 대역폭을 가지고 있기 때문에 스레싱 현상에 따른 성능 저하 영향이 클라우드 환경보다 덜하다.

하지만, 클라우드 환경은 이러한 베어메탈 구조와는 달리 얼핏 비슷하면서도 다른 부분이 있다. 클라우드의 가상디스크들은 기본적으로 네트워크로 연결되어, 여기에 가상화 계층이 추가되는 구조를 지니고 있다.

자, 스왑이 어떻게 작동되는지 기억해내보자.

스왑이 작동하는 방식은 실제 RAM(메모리)영역에 올라가 있는 일부 데이터를 디스크에 있는 스왑 공간(Swap Space)로 옮기는 과정을 거친다. 이 과정은 당연히 디스크의 I/O를 과하게 점유하게 되며, 당연히 네트워크로 연결된 클라우드의 가상디스크들은 이 네트워크 대역폭(IOPS)을 그대로 잡아먹게 되고, 가상화로 인한 오버헤드까지 보너스로 가지게 되어 전체 시스템을 불안정하게 만든다.

추가적으로, 클라우드의 VM은 공유자원으로서 각 베어메탈 서버를 클러스터 단위로 묶어 서비스를 제공하고 있어 결국 물리적인 I/O를 점유한다는 뜻이고, 이를 과하게 사용하게 될 경우 해당 노드를 공유자원으로 나누어 가진 VM들 또한 전부 영향을 받을 수 있다는 의미이다.

그래서 각 IaaS 서비스를 제공하는 많은 클라우드 회사들의 OS 이미지는 기본적으로 Swap이 꺼져있는 것이다.

❓ 하지만, 이 클라우드 네이티브 개념을 지금 내 환경에서 왜 적용해야하는데?

이러한 클라우드 네이티브의 철학은 ‘대규모 분산 시스템’에서 빛을 발하는 메타이다. 현재 내가 운영하고 있는 저사양 VM을 운영하고, 최소한의 서비스로 유지중인 상태에서는 스왑메모리는 이 시스템을 유지하기 위한 필수적인 생명줄이다.

OOM Kill이 내 서비스 프로세스를 찾아 Kill을 시도 했던건 이 블로그의 서비스가 스왑메모리를 적극적으로 쓰지 못했기에 메모리 부족 현상이 잦아졌기 때문이다. 그렇다고 스케일업 하기 어려운 상황에서 내가 선택할 수 있는건 Swap을 활성화하거나 DB 튜닝을 해야하는 선택지가 남게 되었고, 나는 DB 튜닝을 해서 성능을 손실보는 것보단 당분간 Swap 활성화를 통한 서비스 제공과 이후 서비스 모니터링을 주기적으로 하는 선택을 하기로 했다.

그러므로, 클라우드 네이티브라는 트렌드는 지금 내게 주어진 환경에서는 적용하기 어려운 개념이었다.

📝 결론: 무조건적인 최신 트렌드를 따라가는 것은 생각보다 위험할 수 있다.

클라우드 네이티브라는 최신 트렌드는 어떻게 보면 현대 클라우드 아키텍처를 구성하는데 있어 정말 중요한 개념일지 모른다. 하지만, 이러한 개념은 결국 거대한 분산 구조라는 맥락(Context)으로부터 시작한다. 이러한 맥락을 이해하지 못하고 최신 트렌드를 무조건 따라가는 선택지는 이번과 같이 비슷한 케이스를 리턴할게 될 가능성이 높아진다.

또한, 과거 내가 작성한 ‘어느날 Docker 내부 컨테이너에 접속이 안되면’ 이라는 글 또한, 최신 트렌드(업데이트)를 그대로 따라가서 발생한 사고의 흔적이기도 하다.

그러므로 무조건 최신 트렌드를 따라서 적용하기에 앞서서 본인이 관리하는 전체적인 시스템의 워크로드와 환경을 이해하고 제어할 수 있어야하며, 적절한 아키텍처를 시스템에 적용해야한다.

  1. 적격 무료서비스 750시간 때문에 이전부터 사용하고 있다. ↩︎
  2. DNS 레코드만 딸깍 수정하여, 온프레미스로 바로 서비스가 연결되도록 서비스가 가능한 구조를 미리 준비했기 때문이다. ↩︎
목차