집에 둔 서버를 운영 환경으로 만들기까지
집에 둔 서버에 배포 기록, 상태 확인과 복구 절차를 더해 운영 환경으로 만든 과정
한강자리 서버는 FastAPI 백엔드, worker, PostgreSQL/PostGIS, Redis로 나뉘어 있었다. 이제 이 구성은 내 노트북 밖에서 계속 돌아야 했다.
그때부터 기준이 바뀌었다. “로컬에서 서버가 뜬다”만으로는 부족했다. 내가 자고 있을 때도 데이터 수집이 돌고, 배포는 매번 같은 순서로 진행되고, 문제가 생기면 어디부터 확인할지 바로 알 수 있어야 했다.
App Store에 올라간 앱이 호출하는 API는 내가 자리를 비운 동안에도 동작해야 했다. 오래된 값이나 빈 화면이 나타나면 서버와 수집 작업을 원격으로 확인할 방법도 필요했다.
운영 환경이 된 집 서버
한강자리 백엔드는 집에 있던 미니PC에 두고 배포, 모니터링, 장애 확인, 백업과 복구까지 직접 구성하기로 했다.
처음에는 Docker Compose에 가까운 단순한 구조로 시작했다. 로컬 개발 환경에서 Postgres, Redis, API, worker를 띄우고, 미니PC에도 비슷하게 배포하는 방식이었다.
외부 사용자가 계속 접속하는 서버로 바꾸려면 다음을 정해야 했다.
- 마이그레이션은 언제 돌릴 것인가
- API와 worker는 어떻게 나눠 배포할 것인가
- Postgres와 Redis는 어떻게 관리할 것인가
- 배포 뒤에는 무엇을 확인할 것인가
- 외부에서 API가 죽었을 때 Cloudflare Tunnel 문제인지, K3s 문제인지, 앱 문제인지 어떻게 구분할 것인가
- 로그와 메트릭은 어디서 볼 것인가
- 잘못된 배포를 어떻게 되돌릴 것인가
이 질문들이 쌓이면서 Cloudflare Tunnel, K3s, ArgoCD, local CI, 배포 상태 저장소가 붙었다.
외부 요청은 Cloudflare edge와 Tunnel을 지나 미니PC의 K3s ingress로 들어온다. 원본 서버를 직접 열어두지 않고 터널이 연결을 유지하게 했다. 사용자 요청이 들어오는 길과 운영자가 손보는 길도 섞지 않았다.
CI가 lint/test/build를 통과한 이미지 태그를 배포 상태 저장소에 적어둔다. 그러면 ArgoCD가 K3s cluster를 그 태그로 맞춘다. Prometheus와 Grafana로 메트릭을 보고, 로그 파이프라인과 백업/복구 절차도 같이 정리했다.
앱은 App Store에 올린 바이너리 하나로 끝나지 않았다. API, 데이터 수집, 캐시, 알림, 배포 경로, 운영 설정 관리, 헬스 체크, 로그, 메트릭, 백업까지 모두 제품의 일부였다.
전원 상태만으로 부족한 운영
미니PC를 쓰면 비용은 낮아지지만 내가 직접 정할 항목은 늘어난다. 디스크 부족을 어디서 확인할지, 재부팅 후 서비스가 올라오는지, DB 백업은 어디에 남는지, 잘못된 배포를 어떻게 되돌릴지 정해야 했다.
그래서 프로세스가 떠 있는지만 운영 기준으로 삼지 않았다. 내가 자리를 비운 동안에도 다음을 확인할 수 있어야 했다.
- API가 살아 있는가.
- worker가 정해진 시간마다 도는가.
- 마지막 수집 성공 시각이 너무 오래되지 않았는가.
- DB와 Redis가 정상인가.
- 배포한 이미지가 실제로 반영됐는가.
- 문제가 생겼을 때 어느 계층부터 볼지 정해져 있는가.
로그와 메트릭이 없으면 장애가 난 컴포넌트부터 추측해야 했다. API 응답, worker의 마지막 실행, DB, Redis와 배포 이미지를 각각 확인할 수 있게 했다.
실수를 줄이는 자동화
수동으로 이미지를 만들고 서버에 들어가 재시작할 때는 배포 단계를 빼먹을 수 있었다. 앱 공개 전에는 문구, API 응답과 worker 간격을 자주 고쳐 같은 절차를 반복했다.
lint, test, build, 이미지 빌드, 배포 상태 저장소 갱신과 ArgoCD 반영 순서를 CI에 넣었다. 작은 수정도 같은 순서로 배포하면서 누락을 줄였다.
한 번 성공한 배포를 다음에도 같은 순서로 실행하고 문제가 생기면 정해 둔 순서로 확인할 수 있게 만들었다.
서버를 다시 배포하고 상태를 확인할 수 있게 된 뒤 출시 준비로 넘어갔다.
App Store 공개와 줄어든 약속
여의도 주차장에서 시작한 첫 버전에 행사, 시설, 공지와 길찾기를 더했다. API와 worker를 분리하고 배포·관측 절차를 정한 뒤에는 번역, 스크린샷, 브랜드 사이트와 App Store 메타데이터를 준비했다.
앱 기능이 어느 정도 완성된 뒤에도 바로 출시할 수 있는 건 아니었다.
App Store Connect가 요구하는 일은 생각보다 많았다. 앱 이름, 부제, 설명, 키워드, 개인정보 처리방침, 지원 페이지, 연령등급, App Privacy, 리뷰 노트가 필요했다.
스크린샷, TestFlight 빌드, 실제 기기 검증, 다국어 메타데이터도 빠질 수 없었다.
이 과정에서 한강자리의 표현도 많이 바뀌었다.
초기에는 “주차”가 훨씬 앞에 있었다. 하지만 공개 메타데이터에서는 “한강공원 나들이 정보를 한눈에”로 정리했다. 앱 설명도 주차만 앞세우지 않고 11개 한강공원의 정보로 넓혔다. 주차 여유와 혼잡 흐름, 행사와 시설 안내를 함께 넣었다.
브랜드 사이트도 만들었다. 처음에는 지원 페이지와 개인정보 처리방침 정도면 충분할 거라고 생각했지만 앱을 모르는 사람이 들어왔을 때 “이 앱이 뭘 하는지”를 빨리 이해할 수 있는 페이지가 필요했다.
그래서 hangangjari.app에 다국어 브랜드 사이트를 만들고, 앱 화면과 위젯, 예측, 알림, 길찾기, 공식 데이터 출처가 보이게 했다.

출시 전 마지막 며칠은 코드보다 검증과 정리가 더 많았다. 다국어 스크린샷을 만들고, App Store Connect에 올리고, 빌드 번호를 올려가며 TestFlight 후보를 만들었다.
최종 후보를 정한 뒤에는 릴리즈 태그와 공개 메타데이터를 맞췄다. App Store에 보이는 이름, 설명, 개인정보 고지가 실제 기능과 어긋나지 않는지도 다시 확인했다.
이름, 설명, 스크린샷과 브랜드 사이트는 사용자가 앱을 켜기 전에 먼저 접하는 정보였다.
공개 전에 덜어낸 설명
출시 막바지에는 기능을 더 넣는 것보다 설명을 줄이고 서로 맞추는 일이 많았다. App Store 설명은 길게 쓰면 오히려 흐려졌고, 스크린샷 문구는 짧아야 했다. 개인정보 처리방침과 지원 페이지는 과장 없이 정확해야 했다. 공식 앱이 아니라는 점도 숨기면 안 됐다.
개발자 입장에서는 이미 다 아는 내용이라 가볍게 넘기고 싶어진다. 하지만 사용자는 앱을 설치하기 전에 이름, 아이콘, 스크린샷, 설명, 개인정보 고지를 먼저 본다.
서버에서 확인한 실제 동작을 App Store 설명, 개인정보 안내와 지원 페이지에도 같은 범위로 적었다.
끝까지 공개하고 싶었던 앱
한강자리는 무료 앱이다. 로그인 없이 쓸 수 있고 즐겨찾기는 기기에 저장된다. 위치 정보는 가까운 공원과 주차장을 정렬할 때만 기기 안에서 쓴다. 제품 품질 확인을 위한 익명 사용 이벤트는 있지만 처음부터 불필요한 계정과 개인정보를 요구하지 않는 쪽으로 만들었다.
수익 모델은 두지 않았다. 내가 겪은 불편을 고치고 같은 정보가 필요한 사람도 쓸 수 있게 공개하는 데 집중했다.
한강은 내게 익숙한 장소였지만 차를 가져가거나 처음 가는 공원을 고를 때는 여러 사이트에서 정보를 따로 찾았다. 아이와 주말에 나가거나 한강공원을 처음 찾는 사람에게도 주차, 혼잡도와 시설 위치는 출발 전에 필요한 정보였다.
어느 공원에 갈지.
차를 가져가도 될지.
지금 사람이 많은지.
오늘 행사가 있는지.
화장실이나 편의시설은 어디 있는지.
길찾기는 어떤 앱으로 열면 되는지.
한강자리는 이 질문에 필요한 공공데이터와 직접 정리한 정보를 한곳에 모았다. 적어도 한강에 갈 때마다 주차 웹페이지를 열던 내 습관은 앱과 위젯으로 바뀌었다.
App Store 공개 뒤에는 다른 사람의 휴대폰에서도 이 기능이 계속 동작하는지 확인해야 했다. 미니PC의 배포 상태, 데이터 수집 시각, 백업과 복구 절차, App Store 설명을 함께 관리하는 일이 그때부터 남았다.
링크
Comments
아직 댓글이 없습니다. 첫 댓글을 남겨주세요.
검토 대기 중