프론트엔드 개발자 관점에서 인프라를 뜯어보고 이해하기
2026-04-21 · INFRA
CI/CD 흐름을 직접 정리하면서 Kubernetes 구성요소가 어떻게 연결되고, 내가 서비스를 왜 이렇게 나눠 설계했는지 이해한 내용을 기록했어요.
왜 이 글을 정리하게 되었는가
이번에 배포 구조를 만지면서 느낀 점은, 막연하게 "CI/CD를 붙였다" 정도로만 알고 있으면 실제로 문제가 생겼을 때 아무것도 못 한다는 점이었어요.
저도 처음에는 아래 정도로만 이해하고 있었어요.
- 코드를 올리면 어딘가에서 빌드가 된다.
- 이미지가 올라간다.
- 쿠버네티스가 배포해준다.
그런데 실제로 인프라를 만지기 시작하니까 그 정도 이해로는 부족했어요.
- 어떤 도구가 빌드를 담당하는지
- 어떤 시점에 이미지 태그가 바뀌는지
- Kubernetes에서 Ingress, Service, Deployment가 각각 무슨 역할인지
- 내가 왜 front / admin / api / db를 이런 구조로 나눴는지
이걸 정확히 알아야 다음에 제가 인프라를 다시 만질 때도 덜 헷갈리겠다는 생각이 들었습니다.
그래서 이 글은 단순한 배포 후기보다는, 제가 나중에 다시 봐도 이해할 수 있는 인프라 정리 문서에 가깝게 써보려고 했어요.
1. 먼저 CI/CD 흐름부터 제가 이해한 방식으로 정리해봤어요
이번 구조를 한 줄로 요약하면 이렇습니다.
코드를 푸시하면 CI가 이미지를 만들고, 그 이미지 정보가 Git에 기록되고, Argo CD가 그 선언된 상태를 읽어서 Kubernetes에 반영하는 구조예요.
조금 더 풀어서 보면 아래 흐름이에요.
개발자 push
-> Gitea Workflow 실행
-> 애플리케이션 이미지 빌드
-> private registry push
-> k8s manifest의 image tag 갱신
-> 변경된 manifest를 다시 Git에 반영
-> Argo CD가 변경 감지
-> Kubernetes에 sync
여기서 제가 중요하게 느꼈던 건 빌드와 배포가 분리되어 있다는 점이었어요. 기존의 github workflow 파일을 작성할 때는 build 과정 이후에 deploy 과정이 있었음을 알고 있었기에 차이점을 발견하기 쉬웠어요.
CI가 하는 일
- 코드를 받아서 빌드해요.
- 이미지를 만들어요.
- 레지스트리에 push해요.
- 어떤 이미지가 배포되어야 하는지 manifest에 남겨요.
CD가 하는 일
- Git에 적혀 있는 원하는 상태를 읽어요.
- 현재 클러스터 상태와 비교해요.
- 차이가 있으면 Kubernetes 리소스를 맞춰요.
즉 이번에 제가 이해한 CI/CD는 단순히 "자동 배포", "지속가능한 배포"의 추상적인 개념에서 좀 더 확장해서
- CI는 결과물을 만든다
- CD는 선언된 상태를 실제 환경으로 맞춘다
이렇게 역할을 나눠 보게 되었어요.
2. 이 서비스는 어떤 구조로 설계했는가
제가 이번에 이해한 서비스 구조는 크게 4개 축으로 보면 정리가 쉬웠어요.
- 사용자 웹(front)
- 관리자 웹(admin)
- API 서버(api)
- 데이터베이스(postgres)
이걸 Kubernetes 위에 올리면 아래처럼 생각할 수 있어요.
Internet
-> Ingress
-> Service/front -> app Pod(front)
-> Service/admin -> app Pod(admin)
-> Service/api -> api Pod
-> Service/postgres -> postgres Pod
전체 아키텍처를 ASCII로 그려보면 이런 그림이에요
CI / CD + GitOps flow
+----------------------+
| Developer |
+----------------------+
|
| git push
v
+----------------------+
| Gitea Repository |
+----------------------+
|
| trigger workflow
v
+----------------------+ +----------------------+
| Gitea Actions CI |------->| private-registry |
+----------------------+ push +----------------------+
|
| update image tag in manifest
v
+----------------------------------+
| k8s manifest in Git repository |
+----------------------------------+
|
| watched by
v
+----------------------+
| Argo CD |
+----------------------+
|
| sync desired state
v
Kubernetes runtime flow
+------------------------------------------------------------------+
| Kubernetes Cluster |
| |
| +----------------------+ |
| | Ingress Controller | |
| +----------------------+ |
| | | | |
| | / | /admin-base-path| /api/... |
| v v v |
| +-------------+ +-------------+ +-------------+ |
| |Service/front| |Service/admin| | Service/api | |
| +------+------+ +------+------+ +------+------+ |
| | | | |
| | | v |
| | | +---------------+ |
| +--------+ +-----+ | api Pod | |
| v v | - api:8000 | |
| +----------------------+ +---------------+ |
| | app Pod | | |
| | - front:3000 | v |
| | - admin:3001 | +----------------+ |
| +----------------------+ |Service/postgres| |
| +----------------+ |
| | |
| v |
| +----------------+ |
| | postgres Pod | |
| +----------------+ |
| | |
| v |
| +----------------+ |
| | PVC | |
| +----------------+ |
+------------------------------------------------------------------+
이 그림을 기준으로 보면, 위쪽은 CI/CD와 GitOps 흐름, 아래쪽은 실제 Kubernetes 런타임 구조라고 이해하면 쉬워요.
겉으로 보기에는 단순하지만, 저는 여기서 설계 의도를 이렇게 정리했어요.
제가 나눈 기준
1) front와 admin은 같은 app Pod 안에 두었어요
이 둘은 배포 단위가 완전히 따로 놀기보다는 함께 관리해도 괜찮다고 판단했어요.
- 같은 서비스 묶음 안에 있고
- 함께 배포되는 흐름이 자연스럽고
- 운영 객체를 너무 많이 늘리지 않고 싶었어요
대신 네트워크 진입점은 분리했어요.
- front는
Service/front - admin은
Service/admin
즉 실행 단위는 같이 가져가되, 라우팅은 나눠서 관리하는 구조예요.
2) api는 별도 Pod로 분리했어요
API는 웹과 성격이 다르다고 생각했어요.
- 환경변수도 다르고
- DB 연결도 필요하고
- 장애 포인트도 다르고
- 나중에 스케일링 기준도 달라질 수 있어요
그래서 api는 웹과 분리하는 게 더 이해하기 쉬웠고, 운영적으로도 깔끔하다고 느꼈어요.
3) DB는 StatefulSet + PVC처럼 상태가 유지되는 쪽으로 봤어요
웹과 API는 교체 가능한 실행 단위로 봐도 되지만, DB는 데이터가 핵심이기 때문에 접근 방식이 달라요.
- Pod가 교체되어도 데이터는 유지되어야 하고
- 저장소가 분리되어야 하고
- 연결 대상도 안정적이어야 해요
그래서 DB는 그냥 "하나의 컨테이너"가 아니라, 상태를 가진 리소스로 이해하는 게 맞다고 느꼈어요.
3. Kubernetes 구성요소를 이번 구조에 맞춰서 이해해봤어요
쿠버네티스를 처음 보면 리소스 이름이 많아서 어렵게 느껴지는데, 이번 프로젝트 기준으로 보면 생각보다 역할이 선명했어요.
3-1. Deployment: 어떤 Pod를 어떤 상태로 유지할지 정하는 것
이번 구조에서 Deployment는 크게 두 개로 보면 됐어요.
app Deploymentapi Deployment
제가 이해한 Deployment는 아래 역할이에요.
- 어떤 이미지를 쓸지 정해요.
- Pod를 몇 개 유지할지 정해요.
- 환경변수와 probe를 붙여요.
- 새 버전이 오면 롤링 업데이트를 해요.
즉 Deployment는 트래픽을 직접 받는 역할이 아니라, Pod를 만들고 유지하는 관리자에 가까웠어요.
3-2. Service: Pod 앞에 두는 안정적인 진입점
Service는 이번에 제가 가장 중요하게 이해한 개념이었어요.
Pod는 교체될 수 있고 IP도 바뀔 수 있는데, 그럼 다른 서비스가 어디로 붙어야 하는지가 애매해져요.
그 문제를 해결하는 게 Service였어요.
예를 들면 이렇게요.
Service/front는 front 트래픽의 진입점Service/admin은 admin 트래픽의 진입점Service/api는 API 트래픽의 진입점Service/postgres는 DB 연결의 진입점
즉 실제로는 Pod가 바뀌더라도, 위에서 보는 입장에서는 Service 이름만 알고 있으면 돼요.
이 부분이 제가 쿠버네티스를 이해할 때 가장 크게 체감한 포인트였어요.
Kubernetes에서는 Pod 자체보다 Service가 더 안정적인 기준점이다.
3-3. Ingress: 외부 요청을 어느 Service로 보낼지 정하는 것
Ingress는 외부에서 들어온 요청을 나눠주는 역할이에요.
이번 구조에서는 대략 이렇게 이해하면 됐어요.
/요청은 front로 보낸다./admin-base-path요청은 admin으로 보낸다./api/...요청은 api로 보낸다.
즉 Ingress는 외부 공개 라우터에 가까웠어요.
이걸 보면서 정리가 됐던 건,
- Ingress는 외부 요청을 받는다.
- Service는 내부 진입점이다.
- Deployment는 Pod를 유지한다.
세 개가 서로 비슷해 보여도 완전히 다른 계층이라는 점이었어요. (물론 요청 처리를 Ingress가 직접 하지 않고 Ingress Controller가 진행을 해요. 대표적인 Ingress Controller는 nginx Controller가 있다는 것을 배웠고, 사용자가 URL을 입력해 집입을 하게 되었을 때 DNS에서 URL -> IP로 변경하는 과정에서 IP는 Ingress Contoller의 External IP를 바라보도록 하면 된다는 것도 알게 되었어요.)
3-4. Secret / Config / PVC도 결국 역할이 분명했어요
Secret
민감한 값은 코드에 직접 넣는 게 아니라 Secret으로 분리해야 했어요.
예를 들면:
- DB 연결 정보
- JWT 관련 값
- 기타 민감한 환경변수
PVC
DB처럼 데이터가 남아야 하는 경우에는 저장소가 분리되어야 했어요.
즉 Pod는 바뀌어도 괜찮지만, 데이터는 그대로 있어야 하니까 PVC가 필요했어요.
이렇게 보니까 각 리소스가 왜 존재하는지도 조금씩 납득이 됐어요.
4. 요청이 실제로 어떻게 흐르는지 정리해봤어요
이번에 제가 가장 오래 붙잡고 있었던 건 "그래서 요청이 실제로 어디를 거쳐 가는가"였어요.
이걸 이해하고 나니까 트러블슈팅도 훨씬 쉬워졌어요.
4-1. 사용자 웹 요청
사용자 요청 /
-> Ingress
-> Service/front
-> app Pod의 front 프로세스
4-2. 관리자 웹 요청
사용자 요청 /admin-base-path
-> Ingress
-> Service/admin
-> app Pod의 admin 프로세스
여기서 중요한 건 front와 admin이 같은 Pod를 볼 수는 있지만, Service와 포트는 다르다는 점이에요.
즉 같은 실행 단위를 공유하더라도, 네트워크 경로는 분리해서 관리할 수 있었어요.
4-3. API 요청
사용자 요청 /api/...
-> Ingress
-> Service/api
-> api Pod
4-4. API에서 DB로 가는 요청
api Pod
-> Service/postgres
-> postgres Pod
-> PVC
여기서도 제가 다시 정리한 포인트는 이거예요.
애플리케이션은 DB Pod IP를 직접 아는 게 아니라, postgres Service를 통해 연결한다.
이 개념이 잡히니까 Pod IP가 바뀌더라도 왜 서비스가 계속 동작하는지 이해가 됐어요.
5. 제가 이 구조를 괜찮다고 느낀 이유
이 구조를 정리하면서 단순히 "되니까 쓴다"가 아니라, 왜 이렇게 설계했는지도 같이 정리해봤어요.
5-1. 역할이 너무 섞이지 않아요
- 웹은 웹대로
- API는 API대로
- DB는 DB대로
- 외부 라우팅은 Ingress가
- 내부 연결은 Service가
각자 맡는 역할이 분리되어 있어서 나중에 봐도 덜 헷갈릴 것 같았어요.
5-2. 운영할 때 추적 순서가 명확해요
문제가 생겼을 때 아래 순서로 보면 돼요.
Ingress -> Service -> Pod -> Application log
DB 문제라면:
api -> Service/postgres -> postgres Pod -> storage / readiness
즉 어디부터 봐야 할지가 비교적 분명했어요.
5-3. 나중에 변경하기도 쉬운 구조예요
예를 들어 나중에 이런 변경이 들어올 수 있어요.
- api만 따로 scale out 하고 싶다.
- admin만 분리 배포하고 싶다.
- DB를 외부 관리형으로 바꾸고 싶다.
- Ingress 규칙을 더 세분화하고 싶다.
이럴 때도 처음부터 완전히 엉킨 구조가 아니면 변경 포인트를 찾기가 쉬워요.
그래서 이번 구조는 "지금 당장만 되는 구조"라기보다, 다음 수정도 생각한 구조로 이해하게 됐어요.
6. 이번에 제가 이해한 핵심: Pod는 바뀌어도 진입점은 유지되어야 한다
쿠버네티스를 보면서 가장 크게 남은 문장은 이거였어요.
Pod는 교체될 수 있지만, 서비스의 진입점은 안정적이어야 한다.
이걸 이번 구조에 대입하면 이렇게 볼 수 있었어요.
- Pod는 새 이미지 배포 때 바뀔 수 있어요.
- Pod IP도 바뀔 수 있어요.
- 하지만 Service 이름은 유지돼요.
- Ingress 규칙도 유지돼요.
즉 상위 계층은 개별 Pod를 직접 알 필요가 없어요.
이 구조 덕분에 Kubernetes가 "서버 한 대를 오래 관리하는 방식"보다 "필요한 상태를 계속 맞춰가는 방식"에 가깝다는 것도 조금 더 실감하게 됐어요.
7. 나중에 제가 다시 인프라를 만질 때 볼 체크리스트
이 부분은 거의 저를 위한 메모에 가까워요.
7-1. 배포가 안 되는 것 같을 때
먼저 이 순서로 볼 것 같아요.
- Gitea Workflow가 정상 실행됐는가
- 이미지가 registry에 정상 push됐는가
- manifest image tag가 갱신됐는가
- Argo CD가 변경을 sync했는가
- Deployment가 새 Pod를 정상적으로 올렸는가
즉 "쿠버네티스 문제"라고 단정하기 전에, CI 구간과 GitOps 구간을 분리해서 봐야 해요.
7-2. 웹이 안 열릴 때
- Ingress path가 맞는가
- 연결되는 Service가 맞는가
- app Pod가 Ready 상태인가
- 실제 front/admin 프로세스가 맞는 포트에서 뜨는가
7-3. API가 안 될 때
/api라우팅과 rewrite가 맞는가Service/api가 정상인가- api Pod가 Ready 상태인가
- 환경변수와 Secret이 정상 주입됐는가
- API 로그에 에러가 없는가
7-4. DB 연결이 안 될 때
DATABASE_URLhost가 Service 이름 기준인가Service/postgres가 정상인가- postgres Pod가 Ready 상태인가
- PVC나 DB 초기화 과정에 문제가 없는가
이렇게 적어두니까 다음에 다시 볼 때 훨씬 덜 막막할 것 같아요.
8. 결국 이 글에서 제가 남기고 싶은 정리
이번 작업을 하면서 제가 얻은 건 "쿠버네티스 문법을 외웠다"보다는, 서비스를 어떤 기준으로 나누고, 그걸 어떤 리소스로 연결해야 하는지 감이 생겼다는 점이었어요.
정리하면 이렇습니다.
- CI는 이미지를 만들고 배포할 버전을 기록해요.
- CD는 Git에 있는 선언 상태를 실제 클러스터에 반영해요.
- Ingress는 외부 요청을 Service로 나눠줘요.
- Service는 Pod 앞의 안정적인 진입점이에요.
- Deployment는 Pod를 생성하고 유지해요.
- DB는 상태를 가지는 자원이기 때문에 다른 앱 리소스와 다르게 봐야 해요.
- 그리고 저는 이 구조 안에서 front/admin/api/db를 각 역할에 맞게 나누어 이해했어요.
저는 원래 프론트엔드 개발자 관점에서 인프라를 보는 편이라, 너무 추상적으로 설명된 구조보다 "그래서 이 요청이 어디로 가는가", "내가 왜 이렇게 나눴는가" 같은 설명이 훨씬 도움이 됐어요.
그래서 이 글도 그런 기준으로 정리해봤습니다.
나중에 제가 다시 인프라를 수정하거나 구조를 바꿀 일이 생기면, 그때는 이 글을 기준으로 지금 구조를 먼저 이해하고 나서 수정하면 좋겠다는 생각이 들어요.
긴 글 읽어주셔서 감사합니다.