블로그를 Ubuntu 16.04에서 10년 동안 운영하다가 FreeBSD로 마이그레이션함
요약
Ubuntu 16.04 지원 종료에 따라 DigitalOcean에서 Hetzner VPS 및 FreeBSD 환경으로 마이그레이션한 사례를 다룹니다. Jails와 Bastille을 활용해 사이트별 격리 환경을 구축하고, ZFS와 Caddy를 통해 보안과 성능을 최적화했습니다.
핵심 포인트
- Ubuntu 16.04의 보안 위험을 해결하기 위해 FreeBSD로 전환
- Jails와 Bastille을 이용한 효율적인 서비스 격리 및 샌드박스 구현
- Hetzner VPS를 통해 기존보다 높은 사양을 절반 이하 비용으로 확보
- ZFS 도입으로 데이터 무결성 및 스냅샷 관리 효율성 증대
- 커널 파라미터 조정을 통해 요청 처리량 3~11배 향상
Ubuntu 16.04 LTS 기반 DigitalOcean VPS는 지원 종료와 보안 부담이 있었지만, 종료 시점까지 1491일 업타임을 유지함
- 새 서버는 독일
Hetzner VPS와 FreeBSD 14.3으로 옮겼고, 기존 월 $13 서버보다 강한 사양을 월 6유로 미만에 사용함
Jails와 Bastille로 사이트별 격리 환경을 만들고, Caddy Jail이 SSL과 리버스 프록시를 맡아 각 nginx Jail로 전달함 - 부하 테스트에서 새 FreeBSD 서버는 10,000 동시 연결을 위해
kern.ipc.somaxconn
을 조정한 뒤 요청 처리량이 3~11배 높게 나옴
- 전환에는
네트워킹과 FreeBSD 설정 학습이 필요했지만, 중앙화된 설정과 문서 품질 덕분에 예상보다 구성하기 쉬웠음
마이그레이션 배경
- 기존 블로그는
DigitalOcean VPS에서 10년 넘게 운영됐고, 뉴욕 데이터센터의 Ubuntu 16.04 LTS를 사용했음 - Ubuntu 16.04 LTS는 최소 5년 전부터 지원이 끝난 상태였고,
apt
패키지 저장소 업데이트를 받을 수 없었음
-
오래된 시스템은 보안상 불리하며, 과거 별도 WordPress 블로그가 오래된 VPS에서 카지노·도박 링크 삽입 공격을 겪은 적이 있음
-
기존 서버는 블로그 외에도 몇 개의 사이트를 함께 제공했지만 대부분
정적 사이트였음 -
가장 인기 있는 블로그도 보통 월 몇천 페이지뷰 수준이었고, Hacker News에서 몇몇 글이 바이럴된 경우를 제외하면 트래픽은 많지 않았음
-
서버는
nginx/1.10.3
로 정적 파일을 제공했고, 사이트별 설정은 /etc/nginx/sites-available
에 있었음
- 블로그는
Hugo로 생성됐고, 기존 배포 절차는 로컬 작성 → 저장소 커밋·푸시 → 서버 SSH 접속 → 저장소 pull →hugo
실행이었음
-
기존 VPS는 초기에 테스트와 프로그래밍 용도로도 쓰여 오래된 소프트웨어가 많이 남아 있었음
-
그래도 정상적으로 동작했고, 종료 시점의
업타임은 1491일로 약 4년 동안 재부팅 없이 운영됨 -
새 서버는 독일의
Hetzner VPS로 옮겼고, 기존보다 사양이 높으면서 월 비용은 절반 이하였음 -
기존 DigitalOcean 서버는 RAM 2GB, vCPU 1개, 디스크 50GB, 월 트래픽 2TB, 월 $13였음
-
Hetzner의 가장 저렴한 서버도 기존보다 메모리와 CPU가 두 배였고, 저장공간은 약간 적지만 트래픽은 10배였음
-
최종 선택한 Hetzner 구성은 월 6유로 미만의 더 강한 사양이었음
FreeBSD를 선택한 이유
FreeBSD는 새로운 시스템을 실제 운영 환경에서 시험해보고 싶어 선택됨
- 통합 설계, 안정성, 보안,
Jails가 장점으로 꼽힘
Jails는 FreeBSD에 25년 넘게 포함된 가상화·컨테이너화 기능임
- 호스트 시스템에 접근하지 못하는 “미니 시스템”을 샌드박스처럼 실행할 수 있음
- Docker 같은 컨테이너 솔루션은 프로그램 패키징에 더 적합하고 일시적·불변적인 성격을 갖는 반면, Jails는 같은 커널을 공유하는 서브시스템이나 미니 VM에 가깝게 다뤄짐
ZFS도 서버용 파일시스템으로 매력적인 선택지였음
-
데이터 무결성과 스냅샷 기능이 있고, Linux 쪽의 Btrfs와 유사한 면이 있음
-
ZFS는 Btrfs보다 훨씬 성숙한 것으로 평가됐고, 자주 스냅샷을 남기면 VPS 제공자의 유료 스냅샷·백업 시스템에 덜 의존할 수 있음
-
목표 구조는 사이트마다 하나의
Jail을 두고, 각 Jail 안에 필요한 빌드 도구와 nginx를 넣는 방식이었음 -
메인 웹 서버용 Jail은 외부와 연결되는 리버스 프록시를 맡음
-
특정 Jail이 침해되면 해당 Jail을 삭제하고 새로 만들 수 있는 구조를 의도함
FreeBSD 설치와 Bastille 도입
- Hetzner의 VM 생성 화면에는 기본 OS 이미지가 제한적이어서 BSD가 바로 보이지 않았음
Bastille은 Jails 관리를 쉽게 하기 위해 선택됨 - 수동 Jail 생성에 필요한 여러 단계를
bastille
명령으로 처리할 수 있음
- 예시 명령은
bastille list
, bastille create
, bastille console
임
- 설치와 활성화는 Bastille 시작 문서 기준으로 진행됨
pkg install bastille
sysrc bastille_enable="YES"
네트워크와 리버스 프록시 구조
-
전체 스택은
Caddy Jail 하나가 모든 도메인과 SSL 인증서를 처리하고, 사이트별 Jail로 트래픽을 리버스 프록시하는 구조임 -
각 사이트 Jail은 사이트를 빌드하고 제공하는 데 필요한 도구만 포함함
-
같은 네트워크 안의 여러 가상 머신과 비슷한 구조로 볼 수 있음
-
내부 가상 네트워크 인터페이스는
bastille0
로 만들었음
sudo sysrc cloned_interfaces+="lo1"
sudo sysrc ifconfig_lo1_name="bastille0"
sudo service netif cloneup
sudo sysrc ifconfig_bastille0="inet 10.0.0.1 netmask 255.255.255.0"
- 루프백 인터페이스를 복제해
bastille0
라는 이름을 붙이고 10.0.0.1/24
네트워크를 할당함
- Jail들은 이 네트워크 인터페이스에서 동작함
- 외부 HTTP·HTTPS 요청은
PF(Packet Filter) 규칙으로 Caddy Jail에 전달됨
/etc/pf.conf
에는 외부 인터페이스 vtnet0
, 내부 인터페이스 bastille0
, tailscale1
이 설정됐음
80
, 443
포트 트래픽은 Caddy 서버가 될 10.0.0.5
로 리다이렉트됨
ext_if = "vtnet0"
int_if = "bastille0"
vpn_if = "tailscale1"
set skip on $int_if
set skip on $vpn_if
nat on $ext_if from 10.0.0.0/24 to any -> ($ext_if)
rdr pass on $ext_if proto tcp from any to any port {80, 443} -> 10.0.0.5
block all
pass out quick on $ext_if keep state
sysrc pf_enable="YES"
service pf start
sysrc gateway_enable="YES"
Caddy와 사이트별 Jail 구성
- 기존 서버는
nginx를 오래 사용했지만, 새 구성에서는 SSL 인증서 관리를 자동화하기 위해 Caddy를 선택함 - 기존 nginx 환경에서는
certbot
으로 인증서를 주기적으로 갱신해야 했고, 갱신을 놓친 일이 여러 번 있었음
- Caddy Jail을 만들기 전에 FreeBSD 릴리스를 Bastille에 부트스트랩함
bastille bootstrap 14.3-RELEASE
- Caddy Jail은
10.0.0.5
IP로 생성됨
bastille create caddy 14.3-RELEASE 10.0.0.5 bastille0
bastille start caddy
- Jail 이름은
caddy
, 릴리스는 14.3-RELEASE
, 인터페이스는 bastille0
임
bastille list
로 실행 상태를 확인하고, bastille console caddy
로 Jail 내부 셸에 들어갈 수 있음
- Caddy 설치와 서비스 활성화는 Jail 내부에서 진행됨
pkg install caddy
sysrc caddy_enable="YES"
service caddy start
- Caddy 설정 파일은 Jail 내부의
/usr/local/etc/caddy/Caddyfile
에 있음
- 호스트에서 설정 파일을 관리하려면 호스트 디렉터리를 Jail 안으로 마운트할 수 있음
- 예시에서는
nullfs
와 읽기 전용 ro
옵션으로 Caddy가 설정을 변경하지 못하게 마운트함
bastille mount caddy /usr/local/etc/my-caddy-config /usr/local/etc/caddy nullfs ro 0 0
사이트와 블로그 배포
- 첫 번째 배포 대상은
es.cro.to
였고, 사이트 저장소는 GitHub 저장소로 관리됨
- 호스트의
/usr/local/www/escroto
에 저장소를 두고, 사이트 Jail에는 해당 디렉터리를 읽기 전용으로 마운트함
- 모든 사이트는 호스트의
/usr/local/www
아래에 두는 방식으로 정리됨
escroto
Jail은 nginx Bastille 템플릿으로 만들었음
bastille bootstrap https://github.com/bastillebsd/templates
bastille create escroto 14.3-RELEASE 10.0.0.11 bastille0
bastille template escroto www/nginx
- IP는
10.0.0.11
로 지정됨
- nginx 기본 페이지는 FreeBSD 관례대로
/usr/local/www/nginx
에서 제공됨
- 호스트의 사이트 디렉터리는 Jail에 읽기 전용으로 마운트됨
bastille mount escroto /usr/local/www/escroto /usr/local/www/escroto nullfs ro 0 0
- 저장소의
.git
디렉터리가 웹으로 제공되지 않도록 배포 스크립트를 사용함
rm -fr /usr/local/www/nginx/*
cp -R /usr/local/www/escroto/* /usr/local/www/nginx/
rm -fr /usr/local/www/nginx/.git
- 새 버전 배포는 호스트에서 저장소를 갱신한 뒤 Jail 안의 배포 스크립트를 실행하는 방식임
cd /usr/local/www/escroto
git pull
bastille cmd escroto /root/deploy.sh
- Caddy 설정은
cro.to
를 es.cro.to
로 영구 리다이렉트하고, es.cro.to
를 10.0.0.11
로 프록시함
cro.to {
redir https://es.cro.to{uri} permanent
}
es.cro.to {
reverse_proxy 10.0.0.11
}
- 블로그는
Hugo를 사용하며 GitHub 저장소로 관리됨 - 저장소는 호스트의
/usr/local/www/blog
에 클론됨
blog
Jail은 escroto
와 비슷하게 생성됐고, IP는 10.0.0.12
로 지정됨
bastille create blog 14.3-RELEASE 10.0.0.12 bastille0
bastille template blog www/nginx
bastille mount blog /usr/local/www/blog /usr/local/www/blog nullfs ro 0 0
bastille pkg blog update
bastille pkg blog install gohugo
- 블로그 배포 스크립트는 nginx 웹 루트를 비우고 Hugo 출력물을
/usr/local/www/nginx
에 생성함
rm -fr /usr/local/www/nginx/*
cd /usr/local/www/blog
hugo -d /usr/local/www/nginx
- DNS를 옮기기 전에는 기존 도메인 대신
crocidb.cro.to
를 새 블로그 서버에 연결해 테스트함
crocidb.cro.to {
reverse_proxy 10.0.0.12
}
벤치마크와 부하 테스트
- DNS 레코드를 바꾸기 전에 기존 서버
crocidb.com
과 새 서버 crocidb.cro.to
의 부하 처리 능력을 비교함
-
블로그 방문자는 주로 북미, 그다음 유럽과 남미에서 오기 때문에 새 독일 서버의 지연시간이 일부 사용자에게 약간 길어질 수 있음
-
핵심 관심사는 정적 사이트 제공 속도와 큰 부하를 견디는 능력이었음
-
GTMetrix, Pingdom, WebPageTest 같은 무료 온라인 도구도 사용했지만, 두 서버의 차이는 대부분 지연시간 정도였음
-
HTTP 부하 벤치마크 도구로 wrk와 hey를 사용함
-
두 도구는 대량의 동시 요청을 생성하고 요청 지연시간, 오류 응답, 초당 전송량 등을 수집함
-
같은 Hetzner의 다른 VPS에서
wrk
를 실행했을 때 새 서버가 크게 앞섰음
wrk -t4 -c100 -d30s --latency https://crocidb.com/
- 옵션은 4스레드, 100개 동시 연결, 30초 실행이었음
- 기존 서버는 평균 지연시간
89.63ms
, 초당 요청 833.41
, 전송량 8.29MB/s
였음
- 새 서버는 평균 지연시간
6.75ms
, 초당 요청 12260.10
, 전송량 130.80MB/s
였음
- 테스트 머신이 새 서버와 같은 데이터센터에 있어 공정한 비교는 아니었음
- Proton VPN을 이용해 여러 지역에서
wrk
를 돌리는 방식도 시도했지만 결과는 기대보다 낮았음
- 기존 서버 평균은 초당 약
300
요청, 새 서버 평균은 초당 약 800
요청으로 기록됨
- 최종적으로 일반 사용자용 VPN 대신 여러 지역의 실제 VPS를 만드는 방식으로 바뀜
Vultr VPS 기반 지역별 테스트
-
서버가 올라간 DigitalOcean·Hetzner와 다른 인프라를 쓰기 위해
Vultr가 선택됨 -
지역은 수동 작업 부담 때문에
London, São Paulo, Silicon Valley, Tokyo 네 곳으로 제한됨 -
각 지역에 가장 저렴한 Fedora VM을 만들고 테스트를 실행함
-
최종 테스트 도구는
hey
가 더 적합하다고 판단됨
./hey_linux_amd64 -n 1000000 -c 10000 -t 10 -z 5m -h2 https://crocidb.com/ > crocidb.com.log
./hey_linux_amd64 -n 1000000 -c 10000 -t 10 -z 5m -h2 https://crocidb.cro.to/ > crocidb.cro.to.log
- 설정은 총
1,000,000
요청, 동시 요청 10,000
, 타임아웃 10초
, 총 실행 시간 5분
, HTTP/2 사용이었음
- 현실적인 블로그 트래픽보다 훨씬 큰 부하였음
- 첫 실행에서 새 FreeBSD 서버는
10,000개 동시 연결을 감당하지 못해 초반에 실패함
netstat -Lan
으로 소켓 큐 크기를 확인하자 모두 128
로 나타남
- 기본값
kern.ipc.somaxconn
이 128
이어서 다음처럼 늘림
sysctl kern.ipc.somaxconn=16384
- São Paulo 테스트에서 두 서버 모두 상당한 오류를 반환했지만, FreeBSD 서버는 기대한
1,000,000
요청에 대응했고 Ubuntu 서버는 20,000
요청도 반환하지 못했음
- 기존 Ubuntu 서버는 전체 요청의 약
7%
만 완료함
- 새 FreeBSD 서버는 약
94%
를 완료함
-
Tokyo에서는 새 서버의 성공률이 약간 낮았지만, 크게 우려할 수준은 아니라고 판단됨
-
초당 요청 수 기준으로 새 서버는 기존 서버보다 최소
3배, 최대 11배 나았음 -
지연시간 백분위에서는 새 서버가 약
90%
지점까지 더 선형적으로 증가해 예측 가능성이 더 높았음
-
높은 부하에서도 전 세계 대부분 지역에서 블로그 메인 페이지 콘텐츠를
3.5초 미만에 받을 수 있는 결과였음 -
Tokyo 결과는 깊게 분석하지 않았음
hey
의 요청 단계별 분석에서는 일본으로 향하는 트래픽이 더 느릴 가능성이 나타남
- 두 번째 도메인의 DNS dial-up·lookup 값이 비정상적으로 낮아 보였고, CNAME 레코드 영향 가능성이 있었음
resp wait
와 resp read
값도 이상하게 높았는데, 성공한 요청만 집계했기 때문에 기존 서버가 초기에 빠르게 응답하다가 이후 새 요청을 사실상 막았을 가능성이 있었음
최종 전환과 핵심 교훈
-
벤치마크는 답하지 못한 부분이 많았지만, 결과에 만족해 DNS 레코드를 새 서버로 전환함
-
이 블로그는 이후 FreeBSD 기반 Hetzner 서버에서 공식적으로 운영됨
-
FreeBSD로 사이트 호스팅 머신을 구성하는 일은 여러 시간의 실험, 수정, 빌드, 실패를 거쳤지만 예상보다 복잡하지 않았음
-
요구 조건을 만족하는 웹 호스팅 서비스를 사용할 수도 있었고, 예시로 OpenBSD Amsterdam이 나옴
-
Proxmox로 컨테이너와 관리 대시보드를 사용할 수도 있었음
-
FreeBSD 쪽 대안으로 Sylve도 거론됨
-
직접 구성한 경로는 많은 학습을 제공했기 때문에 만족스러운 선택이었음
-
기존
Ubuntu 서버도 매우 견고했음 -
10년 동안 사이트 부하를 잘 처리했고, 마지막 4년은 재부팅 없이 운영됨
-
설정에 큰 노력을 들이지 않고도 안정적으로 동작함
FreeBSD 설정은 예상보다 쉬웠고, 시스템 설정을 한곳에 중앙화하는 방식과 온라인 문서 품질이 좋았음
- 직접 블로그 호스팅 머신을 구성하려면 게임 개발자가 아는 범위를 넘어서는
네트워킹 지식이 필요했음 - 다른 시스템을 배우는 과정이 즐거웠고, 다음에는 OpenBSD나 NetBSD를 시도할 수도 있음
- 블로그 트래픽 대부분이 AI 시스템의 크롤링에서 온다는 점에서, 이 모든 작업의 실용성은 제한적이라고 마무리됨
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기