-
CI / CDProjects/약품고 (스마트 의약품냉장고) 2026. 2. 10. 16:43
편했던 점, 어려웠던 점, 그리고 처음 체감한 키워드들
이번 프로젝트에서 CI/CD는
각자의 분야에서 빠르고 쉽게 배포하고 자기것과 연동해서 테스트해 볼 수 있도록하는게 목표였다.개발 서버를 굴리고, 실제 보드에 올려보고,
원격으로 붙어서 디버깅까지 하다 보니
CI/CD가 잘 되어있으면, 팀의 개발속도가 빨라진다고 느꼈다. 다만, 셋팅이 너무 어렵다... 그리고 팀원들의 로컬 <-> 원격 서버 <-> 실제 개발 아키텍처 에 대한 이해 없이는 정상적으로 흘러가지 않았다.
개발 서버 운영 구성 요약
- Docker / Docker Compose: 서비스 단위 격리
- Jenkins: 빌드 & 배포 자동화
- Nginx Proxy Manager(NPM): 도메인/HTTPS/프록시 관리
- Tailscale: 서버·개발 PC·임베디드 보드 간 사설 네트워크
- 실제 보드(Raspberry Pi / Jetson): 로컬 + 원격 실기 테스트
이 조합이 좋았던 이유는
*“어디서든 같은 방식으로 접속하고, 같은 방식으로 올릴 수 있었기 때문”*이다.
편했던 점들
1. Tailscale 하나로 네트워크 스트레스가 사라짐
가장 체감이 컸다.
- 포트 포워딩 고민 없음
- 공인 IP 신경 쓸 필요 없음
- 개발 PC ↔ 서버 ↔ 보드가 같은 네트워크처럼 보임
특히 임베디드 보드 테스트할 때,
- SSH
- 카메라 스트리밍
- 내부 API 호출
이 전부 사설 IP로 자연스럽게 이어지는 경험이 꽤 좋았다.
2. Jenkins 덕분에 “배포가 이벤트가 아니게 됨”
- git push = 배포 준비
- 빌드 실패 → 배포 안 됨
- 성공 → 자동 반영
배포가 쉬워져서 업무가 줄어서 좋았다. 다만, 초기에 젠킨슨 세팅과 젠킨슨, 도커 빌드 세팅하느라 시간을 많이 썼다.
환경변수 관리도 젠킨슨에서 주입 받을지 직접 서버에 넣어두고 참조하게 할지 등의 선택도 필요하다.
3. Docker 덕분에 “환경 차이” 얘기가 줄어듦
- 로컬에서 되던 게 서버에서 안 되는 문제
- 서버 재부팅 후 뭐가 안 올라오는 문제
이런 게 눈에 띄게 줄었다.
서비스 단위로 컨테이너를 나누고,
- 환경 변수
- 포트
- 네트워크
를 명시적으로 관리하니까
“왜 안 되지?”보다 “어디서 안 됐지?”로 생각이 바뀌었다.
대신, 컨테이너 끼리의 네트워크 연결, 로그 보는건 조금더 복잡해졌다.
어려웠던 점들
1. Jenkins + Docker + 권한 이슈
- Jenkins 컨테이너에서 Docker 제어
- 볼륨 마운트 권한
- 빌드 중 생성된 파일 소유권 문제
2. Nginx Proxy Manager는 편하지만, 이해없이 쓰면 확장이 어렵다.
NPM은 정말 편하다.
- 도메인 연결
- HTTPS
- 리버스 프록시
다 UI로 해결된다.
하지만 문제 생겼을 때는:
- 실제 Nginx 설정이 어딨는지
- 내부적으로 어떤 포트로 흘러가는지
모르니 너무 막막하다.
자동화가 많이 이루어진 도구일수록,
문제 상황에서는 내부 동작을 아는 게 중요하다는 걸 느꼈다./서비스명 기반 포트 포워딩을 선택한 이유
개발 서버에서는 하나의 host 아래에서
/서비스명 형태로 포트 포워딩하는 방식을 사용했다.이 방식에서 가장 중요한 전제는,
각 서비스의 root_path를 host 경로와 함께 맞춰주는 것이다.이유는 단순했다.
리버스 프록시를 거치면 응답의 기준이 결국 host 주소가 되기 때문에,
서비스 입장에서는 자신이 /에 떠 있는지 /api, /cam 아래에 떠 있는지
명확히 알고 있어야 했다.그래서 FastAPI 같은 경우도
처음부터 root_path를 명시적으로 맞춰두는 게 훨씬 편했다.
Nginx Proxy Manager의 대안 설정은 어색했다
물론, 이 문제를 Nginx Proxy Manager 쪽 설정으로 해결할 수도 있다.
- rewrite 규칙을 추가하거나
- 내부 경로를 바꿔서 /처럼 보이게 만들 수도 있다
하지만 이 방식은 항상 어딘가 어색했다.
- 설정이 UI 안쪽에 숨겨지고
- 서비스 코드만 봐서는 실제 접근 경로를 알기 어렵고
- 나중에 다시 봤을 때 “왜 이렇게 돼 있지?”가 된다
그래서 나는
프록시가 아니라 서비스가 자기 위치를 아는 구조를 선호하게 됐다.
결국 Supabase는 별도 도메인을 파게 됐다
이 방식이 항상 통하는 건 아니었다.
Supabase처럼
- 내부적으로 여러 엔드포인트를 가지거나
- 프록시 경로 변경을 전제로 설계되지 않은 서비스는
/서비스명 아래에 억지로 넣는 순간부터
설정이 꼬이기 시작했다.그래서 Supabase는 결국
별도의 도메인을 파서 host 자체를 분리하는 쪽을 선택했다.지금 다시 봐도 이 선택은 합리적이었다.
모든 서비스를 한 host 아래에 우겨 넣는 것보다,
경로를 이해하지 못하는 서비스는 도메인으로 분리하는 게 낫다.- /서비스명 방식은 내가 만든 서비스에는 매우 잘 맞는다
- 그 대신 root_path를 처음부터 설계에 포함해야 한다
- 외부 서비스나 범용 플랫폼은 경로보다 도메인 분리가 깔끔하다
이 경험 이후로는
“경로로 나눌지, 도메인으로 나눌지”를
서비스 성격에 따라 먼저 결정하게 됐다.
몰랐는데, 이번에 제대로 체감한 키워드들
- Single Source of Truth
→ 설정은 코드나 한 곳에만 둬야 한다 - Observability
→ 안 죽는 것보다, 왜 죽었는지 보이는 게 중요 - Network is part of the system
→ 네트워크도 코드만큼 중요하다
'Projects > 약품고 (스마트 의약품냉장고)' 카테고리의 다른 글
실사, 발표, 그리고 전체 회고 (0) 2026.02.10 구현, 아키텍처 (0) 2026.02.10 설계 회고 (0) 2026.02.10 API 설계 (0) 2026.02.10 데이터 모델링 (0) 2026.02.10