Rust 코드를 더 빠르게 배포하기
원문은 Matthias Endler님이 에 게재했습니다. 이 블로그 구독하기
기술 여정을 돌아보면 베어메탈 서버를 다루던 시절부터 클라우드 컴퓨팅의 세계를 탐험하기까지 먼 길을 걸어왔다. 처음엔 모든 게 정말 단순해 보였다. 서버 하나 띄우고, 컨테이너 하나 배포하면 끝이라고. 하지만 깊이 파고들수록 인프라의 편리함이란 겉보기만큼 단순하지 않다는 걸 깨달았다.
클라우드 제공업체들은 수많은 도구를 제공하는데, 각각 익혀야 할 것이 따로 있다:
- Google Cloud / AWS
- Kubernetes
- Helm
- Docker
- Terraform
- GitHub Actions
모험심이 있다면 EKS나 GKE 같은 관리형 Kubernetes 서비스에 도전해 볼 수도 있다. 클릭 몇 번으로 애플리케이션이 바로 실행된다니 매력적이다. 하지만 모니터링, 로깅, 보안, 스케일링 등을 동시에 챙기기 시작하면 현실이 다가온다.
어느새 제품에 집중하는 대신 의도치 않게 DevOps 팀을 이끌고 있는 자신을 발견하게 된다. 인프라를 관리하려고 인력을 더 뽑는 사이 경쟁사들은 기능을 계속 출시하며 사용자 기반을 키워나간다.
나의 좌절
클라우드는 인프라를 쉽게 만들어주겠다고 약속했지만, 쏟아지는 도구와 서비스는 오히려 압도적일 수 있다. 전부 사용하지 않더라도 존재를 알고 기본은 익혀둬야 한다. 결과는? 제품에 대한 집중력이 떨어진다.
인프라를 다루는 일 자체는 좋아하지만, 제품을 만들어 내는 일도 그만큼 좋아한다. 안타깝게도 많은 기업이 인프라에 소중한 시간과 돈을 낭비하며 같은 실수를 반복한다.
인프라 고민을 아예 없앨 방법은 없을까?
서버리스의 매력
서버리스 아키텍처는 매력적으로 보인다. 서버도 없고, 컨테이너도 없이 순수한 비즈니스 로직만 있으면 된다. 하지만 여기에도 과제는 있다:
- 콜드 스타트 시간
- Lambda 크기 제한
- 메모리 문제
- 장시간 실행 프로세스
- 디버깅의 복잡성
- 로컬 테스트 부족
서버리스는 특정 사용 사례에서는 장점이 있지만, 규모가 큰 애플리케이션에서는 여전히 서버가 필요할 수 있다.
Platform-As-A-Service (PaaS)
Heroku나 Netlify 같은 플랫폼은 세 번째 선택지를 제시했다. 모든 인프라를 대신 처리해주는 관리형 서비스다. 더 이상 인프라를 걱정할 필요 없이 코드를 푸시하기만 하면 배포된다. 이런 솔루션의 장점은 특정 프로그래밍 언어 생태계와 깊게 통합되어 있다는 점이다.
나는 Rust 개발자를 위해 만들어져 최고의 개발 경험을 제공하는 플랫폼을 찾고 있었다. Rust 생태계(serde, sqlx, axum 등)와의 깊은 통합을 원했다.
얼마 전 Rust 개발 워크플로를 조금 더 매끄럽게 만들 방법을 찾다가 Shuttle을 알게 됐다. 기존 Rust 생태계에 자연스럽게 녹아드는 도구로, 평소대로 cargo를 사용하면서 인프라 관련 무거운 작업을 덜어준다. 물론 모든 문제를 해결해주는 마법 지팡이는 아니지만, Shuttle에서 마음에 드는 점은 단순함이다. 가파른 학습 곡선을 가진 완전히 새로운 환경에 던져지는 게 아니다. 대신 익숙한 Rust 코드에 집중하면 Shuttle이 뒤에서 서버 측 복잡성을 관리해준다. 요컨대 아는 것에 충실하면서 배포와 서버 관리가 조금 더 수월해지는 셈이다. 코딩 방식 자체를 혁명적으로 바꾸는 것이 아니라, 때로는 골칫거리가 되는 백그라운드 프로세스 관리를 미묘하게 바꿔주는 것이다.
지금까지의 Shuttle 경험
지금까지 Shuttle로 Zerocal과 Readable이라는 두 개의 작은 Rust 서비스를 만들었다.
Shuttle은 아주 적은 어노테이션만으로 Rust 코드를 클라우드에 배포할 수 있게 해준다. 프로비저닝과 배포가 보통 서비스 구축에서 가장 고통스러운 부분임을 감안하면, 개발 경험은 거의 이상에 가깝다.
대신 몇 줄의 코드만 추가하면 된다. 직접 확인해보라. 보일러플레이트는 사라지고 비즈니스 로직만 남는다.
Zerocal - Stateless한 캘린더 마법
Zerocal은 Shuttle에 처음 배포한 프로젝트였다. 원리는 매우 단순하면서도 혁신적이었다. 캘린더 데이터를 URL에 직접 인코딩하는 것이다. 즉, 이벤트 생성은 다음과 같이 간단했다:
curl https://zerocal.shuttleapp.rs?start=2023-11-04+20:00&duration=3h&title=Birthday&description=paaarty이 요청은 캘린더에 추가할 수 있는 iCal 파일을 반환한다. 브라우저에서 이벤트를 만드는 방법은 다음과 같다:

이 프로젝트를 Shuttle에서 만들 당시에는 아직 몇 가지를 수정하고 API를 조금씩 바꾸고 있던 때였다. 이런 자잘한 문제들에도 불구하고 경험은 좋았다. 단 몇 분 만에 앱을 실행할 수 있었다.
axum 라우트를 포함해 서비스를 시작하는 코드는 다음과 같다:
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {
// just normal axum routes
let router = Router::new()
.route("/", get(calendar))
.route("/", post(calendar));
Ok(router.into())
}이제 Zerocal은 개인적으로 더 이상 필요하지 않아서, 다른 누군가 이어받아 주었으면 한다. GitHub나 Discord 같은 곳에서 초대장을 공유할 때 정말 유용할 수 있다고 생각한다. Zerocal에 대해 더 알고 싶다면 자세한 설명을 읽어보라.
비슷한 프로젝트를 Zerocal에서 영감을 받아 만든 사람도 있다는 점을 언급하고 싶다. Mahesh Sundaram이 Deno로 작성한 kiwi다. 정말 멋진 결과물이다.
내 E-리더를 위한 리더 모드
Firefox 리더 뷰에 대한 애정이 특히 e-리더에 최적화된, 미니멀하고 JavaScript 없는 웹 읽기 경험을 위한 Reader Mode Proxy를 만들게 했다. 장황한 웹사이트를 방해받지 않고 읽기 쉬운 형태로 바꾸려는 의도였다.
이 프로젝트는 문제를 해결하는 단순한 앱을 좋아하는 내 취향을 고스란히 반영했다. 약간의 어노테이션만으로 코드가 Shuttle 환경에 매끄럽게 적응했다. 처음에는 테스트를 위해 내 컴퓨터에서 앱을 실행할 수 있게 자체 로컬 모드를 만들어 두었지만, Shuttle 자체의 로컬 모드가 충분히 잘 작동해서 더 이상 유지할 필요가 없었다.
앱을 개발하는 동안 몇 가지 우여곡절도 있었다. 서비스 다운타임 때문에 코드를 일부 손봐야 했다. 하지만 Shuttle이 발전하면서, 특히 네이티브 정적 파일 처리를 도입한 뒤로는 과정의 일부가 훨씬 단순해졌다.
이전에는 이렇게 생겼다:
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {
let router = Router::new()
// Previously, I needed to manually serve static files
.route(
"/static/Crimson.woff2",
get(|| async {
static_content(
include_bytes!("../static/fonts/Crimson.woff2",),
HeaderValue::from_static("text/woff2"),
)
}),
)
.route(
"/static/JetBrainsMono.woff2",
get(|| async {
static_content(
include_bytes!("../static/fonts/JetBrainsMono.woff2",),
HeaderValue::from_static("font/woff2"),
)
}),
)
.fallback(readable);
Ok(router.into())
}이제 이렇게 바뀌었다:
#[shuttle_runtime::main]
async fn axum() -> shuttle_axum::ShuttleAxum {
let router = Router::new()
.nest_service("/static", ServeDir::new(PathBuf::from("static")))
.fallback(readable);
Ok(router.into())
}이 프로젝트의 세부 내용을 이해하려면 더 자세한 내용을 참고하라.
제어와 안전성
처음에는 인프라를 위해 코드에 어노테이션을 추가하면 벤더 종속이 생기지 않을까 걱정했다. 프로젝트에 대한 완전한 통제권을 유지하고 싶었다. 떠나고 싶다면? Shuttle 매크로가 보일러플레이트를 없애주기 때문에 추가했던 2개의 어노테이션만 제거하면 원래 코드로 돌아갈 수 있다. Shuttle 코드 자체도 오픈소스라 셀프 호스팅 인스턴스를 직접 구축할 수도 있다. 물론 굳이 그렇게 하고 싶지는 않지만.
직접 구축하는 인프라의 진짜 비용
인프라는 표면적으로는 쉬워 보이지만, 유지 관리에는 다양한 복잡성과 비용이 따른다. 업데이트, 배포, 가용성 등 감당하기 벅찰 수 있다. 이런 작업에 쏟는 매 시간은 직접 비용과 기회비용을 모두 수반한다.
인프라는 미로 같고, Rust로 작업하는 사람들에게 Shuttle은 잘 맞는 듯하다. 이제 Shuttle이 할 수 있는 일과 없는 일을 꽤 이해했으니, 조만간 Shuttle에서 더 큰 프로젝트를 시도해 볼 생각이다. 한번 시도해보려 한다면, 필요에 맞는지 확인하기 위해 가격 정책을 확인하는 것이 좋다.
인프라의 진짜 비용을 명심하라!
앞서 말했듯이, 비용은 단순한 서버 비용만이 아니라 그 이상이다. 가장 큰 요인은 인프라 유지보수와 디버깅에 드는 인건비일 것이고, 이는 비용이 많이 든다. 인프라를 코드로 관리한다면 인프라를 설정하는 데만 수 시간을 쓰고, 유지 관리에는 그보다 더 많은 시간을 쏟아야 할 것이다. 요즘 연봉을 생각하면 그 비용은 상당하다.
취미 프로젝트라 하더라도 나에게는 그 수고를 감수할 가치가 없다. 모든 것을 구동하는 코드보다는 기능 작업에 집중하고 싶다.
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기