AWS - On-premises - VESSL AI를 연결한 하이브리드 인프라와 내부 LLM Agent 운영 환경
KKPP 인프라 레포지토리는 BNPL 서비스와 내부 LLM Agent 플랫폼을 위해 구성한 Terraform 기반 인프라 문서화 저장소입니다.
AWS의 public edge와 EKS 서비스 런타임, 온프레미스 Kubernetes의 private workload, VESSL AI의 GPU 기반 vLLM 서버를 연결하여 하이브리드 네트워크, 내부 LLM 추론, 관측성, DR, 비용 추정, 운영 문서화를 재현할 수 있도록 정리했습니다.
이 프로젝트는 BNPL 서비스에서 사용하는 서비스 API, 내부 금융 워크로드, PostgreSQL 데이터 계층, observability, 그리고 내부 LLM Agent를 하나의 하이브리드 인프라로 연결하기 위해 시작했습니다.
단일 클라우드에 모든 구성요소를 올리는 대신 다음 세 영역을 분리했습니다.
- AWS: public edge, EKS 기반 서비스 API, 정적 웹, RDS/DR 기록, observability, alerting, cost estimation
- 온프레미스 Kubernetes: 내부 금융 API, private workload, storage, CI/CD, PostgreSQL HA 담당자 작성 영역
- VESSL AI: H100 GPU를 임대해 vLLM 기반 내부 LLM 추론 서버 운영
이 저장소는 실제 애플리케이션 코드가 아니라, 위 인프라를 설명하고 검증하기 위한 Terraform, 운영 스크립트, observability dashboard, FinOps 문서를 관리합니다.
먼저 AWS, 온프레미스, VESSL AI가 각각의 역할을 유지하면서도 사설 네트워크로 연동될 수 있도록 설계했습니다.
User
-> CloudFront
-> S3 static web
-> Internal ALB / VPC origin
-> AWS EKS service APIs
-> On-premises private APIs
-> AIOps / MCP APIs
AWS EKS / On-prem Kubernetes
-> VESSL AI vLLM endpoint
결과적으로 public traffic은 CloudFront에서 통제하고, private workload와 LLM inference는 내부 네트워크 경계를 통해 접근하는 구조를 목표로 했습니다.
1) 네트워크 분리와 연결성
- public entry point는 CloudFront로 단일화했습니다.
- 정적 admin/user SPA는 S3 website origin에서 제공합니다.
- API 요청은 CloudFront VPC Origin을 통해 internal ALB로 전달합니다.
- AWS, 온프레미스, VESSL AI는 pfSense VPN 기반 연결을 전제로 분리했습니다.
- EKS와 온프레미스 Kubernetes가 private workload와 LLM endpoint를 호출할 수 있도록 설계했습니다.
2) pfSense 기반 하이브리드 VPN
담당자 작성 예정
- pfSense 구성 목적:
- AWS/온프레미스/VESSL AI 연결 방식:
- VLAN gateway / CARP VIP 구성:
- firewall rule / routing 정책:
- 장애 대응 또는 운영 확인 방법:
- 첨부할 스크린샷:
3) 내부 LLM Agent 구성
- VESSL AI의 H100 GPU에서 vLLM을 실행합니다.
- vLLM은 OpenAI-compatible API로 모델 추론만 담당합니다.
- MCP 서버는 LLM Agent가 사용할 수 있는 backend tool을 제한적으로 노출합니다.
- vLLM과 MCP를 분리해 모델 서버 장애와 도구 호출 권한의 blast radius를 줄였습니다.
4) PostgreSQL 이중화와 DR
- RDS primary / DR standby 구성을 기록했습니다.
- DMS full-load + CDC replication 흐름을 문서화했습니다.
- KMS key와 Secrets Manager record를 통해 암호화와 secret 관리 의도를 남겼습니다.
- 온프레미스 PostgreSQL 이중화 상세 구성은 담당자 작성 예정입니다.
아래 요구사항을 기준으로 인프라를 정리했습니다.
1) 하이브리드 환경에서 public/private traffic 분리
- 사용자는 CloudFront를 통해서만 public web/API endpoint에 접근합니다.
- private API와 내부 DB는 public internet에 직접 노출하지 않습니다.
- 내부 ALB와 VPN 연결을 통해 AWS와 온프레미스 사이의 경계를 관리합니다.
2) GPU 비용과 LLM serving 역할 분리
- GPU는 상시 보유하지 않고 VESSL AI에서 필요한 리소스를 임대합니다.
- vLLM은 모델 serving에 집중하고, MCP는 backend tool gateway 역할을 담당합니다.
- 개발 모델과 데모 모델 후보를 나누어 비용과 품질 사이의 선택지를 남겼습니다.
| 용도 | 모델 후보 |
|---|---|
| 개발 | Qwen/Qwen3-14B |
| 데모 | Qwen/Qwen3-32B |
3) 관측성과 장애 대응
- Kubernetes, PostgreSQL, pfSense, AIOps, autoscaling, FinOps dashboard를 Grafana 기준으로 정리했습니다.
- CloudWatch Alarm, SNS, Lambda, Slack notification 기록을 통해 alerting 흐름을 남겼습니다.
- pfSense와 PostgreSQL 이중화의 관측/장애 대응 상세는 담당자 작성 예정입니다.
4) 비용 추정과 공개 저장소 안전성
- Infracost 설정을 통해 Terraform record 기준 비용 추정을 수행할 수 있습니다.
- public release checklist를 두어 credential, kubeconfig, 실제 AWS account ID, 내부 IP, 운영 도메인 노출을 방지합니다.
- 예시 값은
000000000000,example.com,192.0.2.0/24,203.0.113.0/24같은 placeholder를 사용합니다.
온프레미스 영역은 vCenter/ESXi 기반 가상화 환경 위에 pfSense, TrueNAS, Kubernetes, PostgreSQL HA, CI/CD, observability workload를 역할별로 분리하고, 각 VM의 자원과 IP 대역을 사전에 규칙화해 배치했습니다.
온프레미스는 세 대의 ESXi 서버를 기반으로 구성했습니다.
| 호스트 | 관리 IP | 주요 역할 |
|---|---|---|
| ESXi Server 1 | 192.168.100.101 |
pfSense, TrueNAS, Kafka, PostgreSQL active, Kubernetes control/worker |
| ESXi Server 2 | 192.168.100.2 |
PostgreSQL standby, Kubernetes control/worker, AIOps/backend node |
| ESXi Server 3 | 192.168.100.3 |
GitLab, Jenkins, Kubernetes control/worker, monitoring/ELK node |
공통 관리 구성은 다음과 같습니다.
| 항목 | 값 |
|---|---|
| 공용 DNS | 192.168.100.100 |
| vCenter | 192.168.100.102 |
| 팀 관리 대역 | 192.168.100.1~99, 192.168.30.0/24 |
| 내부 서비스 대역 | 10.30.0.0/16 |
| 내부 도메인 | dev6.fisa |
| DNS/NTP 서버 | 10.30.0.10 |
온프레미스 내부망은 VLAN 300~399 범위에서 서비스 성격별로 분리했습니다.
| VLAN | 용도 | CIDR 예시 |
|---|---|---|
| 300 | 관리망 / pfSense / 공통 관리 | 10.30.0.0/24 |
| 301 | Storage / TrueNAS | 10.30.1.0/24 |
| 302 | Kubernetes workload | 10.30.2.0/24 |
| 303 | CI/CD / GitLab / Jenkins | 10.30.3.0/24 |
| 304 | Database / PostgreSQL HA | 10.30.4.0/24 |
IP는 운영 중 충돌을 줄이기 위해 규칙 기반으로 할당했습니다.
10.30.<VLAN 번호>.<서버 번호 / VM 번호>
서버 번호:
- server1 = 0
- server2 = 1
- server3 = 2
VM 번호:
- 일반 VM: 0~20번대
- 첫 번째 worker node: 30번대
- 두 번째 worker node: 40번대
- control plane node: 50번대
담당자 작성 예정
- pfSense active/standby 구성:
- VLAN gateway / CARP VIP:
- AWS VPN 연결:
- firewall rule:
- routing 정책:
- 운영 확인 화면:
TrueNAS는 온프레미스 storage VLAN에 배치해 내부 서비스의 공유 스토리지 역할을 담당하도록 구성했습니다.
- storage 전용 VLAN 분리
- Kubernetes, database, observability workload와 네트워크 경계 분리
- 장기 로그/백업/공유 볼륨 용도로 활용 가능한 구조
- 추후 NFS/iSCSI 기반 persistent volume 연동 여지를 남김
Kubernetes는 세 ESXi 호스트에 control plane과 worker node를 분산 배치했습니다.
- control plane node를 호스트별로 분산
- frontend/backend/AIOps/monitoring/CI/CD 성격의 worker node를 역할별로 분리
- 특정 ESXi 호스트 장애가 전체 Kubernetes workload 중단으로 이어지지 않도록 역할을 분산
CI/CD 영역은 별도 VLAN과 VM으로 분리했습니다.
| 구성요소 | 역할 |
|---|---|
| GitLab | source repository, merge request, container/build trigger 관리 |
| Jenkins | build/test/deploy pipeline 실행 |
| Kubernetes worker | 배포 대상 workload 실행 |
CI/CD 노드는 서비스 runtime node와 분리해, 빌드 작업이 운영 workload 자원을 과도하게 점유하지 않도록 했습니다. 또한 GitLab/Jenkins를 온프레미스에 배치해 내부망에서 소스, 빌드, 배포 흐름을 제어할 수 있도록 구성했습니다.
이 온프레미스 랩은 단순히 VM을 나열한 환경이 아니라, 실제 운영 환경에서 필요한 네트워크 분리, 자원 할당, storage, CI/CD, database HA, Kubernetes runtime을 한정된 물리 자원 안에서 재현하기 위한 구조입니다.
아래 다이어그램은 AWS, 온프레미스, VESSL AI를 연결한 전체 하이브리드 인프라 구성입니다.
요약 구조는 다음과 같습니다.
flowchart LR
user[User] --> route53[Route 53]
route53 --> cf[CloudFront + WAF + ACM]
cf --> s3[S3 static web]
cf --> alb[Internal ALB / VPC Origins]
subgraph AWS[AWS ap-northeast-2]
s3
alb
ecr[ECR]
sqs[SQS]
eks[EKS Cluster]
rds[RDS Primary]
dr[RDS DR Standby]
dms[DMS CDC]
obs[CloudWatch / Loki / Grafana]
alert[CloudWatch Alarm / SNS / Lambda / Slack]
alb --> eks
ecr --> eks
sqs --> eks
eks --> rds
rds --> dr
dms --> rds
eks --> obs
obs --> alert
end
subgraph ONPREM[On-premises]
pfsense[pfSense VPN / 담당자 작성 예정]
k8s[Private Kubernetes Cluster]
pg[PostgreSQL HA / 담당자 작성 예정]
k8s --> pg
end
subgraph VESSL[VESSL AI kr-west]
gpu[H100 GPU]
vllm[vLLM OpenAI-compatible API]
gpu --> vllm
end
alb <--> pfsense
eks <--> pfsense
pfsense --> k8s
eks --> vllm
k8s --> vllm
정적 웹, admin API, user API, AIOps API, MCP API가 서로 다른 실행 환경에 존재하기 때문에 사용자가 접근하는 public endpoint가 복잡해질 수 있었습니다.
CloudFront를 public edge로 두고, 정적 파일과 API 경로를 path pattern 기준으로 분리했습니다.
- 정적 admin/user SPA: S3 website origin
- admin API: internal ALB
- AIOps/MCP API: internal ALB / EKS service
- service catalog API: internal ALB / EKS service
- 사용자는 하나의 web edge를 통해 서비스에 접근합니다.
- public traffic 진입점에서 WAF, ACM, cache policy를 함께 관리할 수 있습니다.
- 내부 API는 public internet에 직접 노출하지 않고 VPC origin을 통해 접근합니다.
BNPL 서비스 특성상 public API, product/catalog API, observability는 cloud-native 환경이 적합하지만, 일부 내부 금융 API와 데이터 계층은 private network 안에서 운영하는 편이 안전했습니다.
AWS EKS와 온프레미스 Kubernetes의 역할을 분리했습니다.
| 영역 | 역할 |
|---|---|
| AWS EKS | 서비스 API, AIOps API, observability component |
| On-prem Kubernetes | 내부 금융 API, private workload |
| PostgreSQL HA | 담당자 작성 예정 |
| VPN / pfSense | 담당자 작성 예정 |
- public-facing workload와 private workload의 책임이 분리됩니다.
- 온프레미스 PostgreSQL HA 상세는 담당자가 보강할 수 있도록 별도 항목으로 분리했습니다.
- AWS와 온프레미스 사이의 VPN 상세는 담당자가 보강할 수 있도록 별도 항목으로 분리했습니다.
담당자 작성 예정
아래 항목을 채워 넣으면 됩니다.
| 항목 | 내용 |
|---|---|
| 구성 목적 | TODO |
| 네트워크 대역 | TODO |
| VLAN gateway | TODO |
| CARP VIP | TODO |
| AWS VPN 연결 | TODO |
| firewall rule | TODO |
| routing 정책 | TODO |
| 장애 대응 방식 | TODO |
| 운영 확인 방법 | TODO |
pfSense 구성은 실제 설정 화면을 함께 첨부하면 포트폴리오 신뢰도가 높아집니다.
아래 화면을 docs/images/에 추가한 뒤 본문에 삽입하는 것을 권장합니다.
| 파일명 예시 | 첨부하면 좋은 화면 | 보여줄 수 있는 내용 |
|---|---|---|
docs/images/pfsense-vpn-status.png |
pfSense VPN tunnel status | AWS/온프레미스 VPN tunnel 연결 상태 |
docs/images/pfsense-firewall-rules.png |
Firewall rules | 허용한 traffic 범위와 보안 경계 |
docs/images/pfsense-routing.png |
Static routes / gateway | AWS VPC와 온프레미스 대역 routing |
docs/images/pfsense-grafana-dashboard.png |
pfSense Grafana dashboard | gateway, firewall log, hybrid traffic 관측 |
<!-- 이미지 추가 후 예시 -->
<div align="center">
<img src="docs/images/pfsense-vpn-status.png" width="80%" />
</div>- TODO:
담당자 작성 예정
아래 항목을 채워 넣으면 됩니다.
| 항목 | 내용 |
|---|---|
| 구성 목적 | TODO |
| primary/replica 구성 | TODO |
| replication 방식 | TODO |
| failover 방식 | TODO |
| Patroni / etcd 사용 여부 | TODO |
| HAProxy / VIP routing | TODO |
| backup / restore 정책 | TODO |
| 장애 전환 테스트 결과 | TODO |
| 운영 확인 방법 | TODO |
Client / Service
-> TODO endpoint
-> TODO primary
-> TODO replica
Failure occurs
-> TODO detection
-> TODO promotion
-> TODO endpoint switch
PostgreSQL 이중화는 장애 전환 결과와 replication 상태가 보이는 화면을 첨부하면 단순 설명보다 훨씬 강하게 전달됩니다.
| 파일명 예시 | 첨부하면 좋은 화면 | 보여줄 수 있는 내용 |
|---|---|---|
docs/images/postgres-patroni-cluster.png |
patronictl list 결과 |
primary/replica 상태와 leader 확인 |
docs/images/postgres-replication-status.png |
replication status query | replica 지연, streaming 상태 |
docs/images/postgres-haproxy-stats.png |
HAProxy stats page | write/read endpoint routing 상태 |
docs/images/postgres-switchover-test.png |
switchover test 결과 | 장애 전환 후 새 primary 승격 |
docs/images/postgres-grafana-dashboard.png |
PostgreSQL Grafana dashboard | DB resource, connection, query 상태 |
<!-- 이미지 추가 후 예시 -->
<div align="center">
<img src="docs/images/postgres-patroni-cluster.png" width="80%" />
</div>- TODO:
LLM Agent 기능을 서비스에 붙이려면 GPU 인프라가 필요하지만, GPU를 직접 보유하거나 AWS GPU instance를 상시 운영하면 비용 부담이 큽니다. 또한 모델 추론과 backend tool 호출을 같은 서버에 두면 권한과 장애 범위가 커집니다.
GPU는 VESSL AI에서 임대하고, vLLM server와 MCP server를 분리했습니다.
- vLLM: OpenAI-compatible API 기반 모델 추론
- MCP: LLM Agent가 사용할 수 있는 backend tool gateway
- EKS/on-prem workload: 필요한 경우 vLLM endpoint 호출
- GPU 비용과 서비스 runtime 비용을 분리할 수 있습니다.
- 모델 serving 장애가 backend tool 권한 체계로 번지는 것을 줄입니다.
- LLM Agent가 실제 서비스 API를 호출할 때 MCP 계층에서 도구 범위를 제한할 수 있습니다.
VESSL AI에서 vLLM 기반 LLM serving을 운영하면서 토큰 사용량과 GPU 사용량을 함께 확인했습니다. 이를 통해 단순히 모델 endpoint를 띄우는 것뿐 아니라, 추론 요청이 들어올 때 GPU resource와 token throughput이 어떻게 변하는지 관측할 수 있도록 했습니다.
ai.mp4
모니터링 관점은 다음과 같습니다.
| 항목 | 확인 목적 |
|---|---|
| Token usage | 요청량과 응답 생성량 추적 |
| GPU utilization | 추론 부하에 따른 GPU 사용률 확인 |
| Memory usage | 모델 serving 중 GPU memory 여유 확인 |
| Request latency | vLLM endpoint 응답 지연 확인 |
일부 AWS 리소스는 콘솔에서 이미 생성된 상태였고, 저장소를 즉시 live provisioning source of truth로 전환하기에는 import/state/backend 정리가 필요했습니다.
현재 AWS Terraform은 record-only로 운영합니다.
이 저장소의 Terraform은 다음 목적을 가집니다.
- 아키텍처 문서화
- Infracost 기반 비용 추정
- CI에서
terraform fmt,terraform validate수행 - live resource 의도와 운영 정책 기록
- 공개 포트폴리오를 위한 안전한 인프라 설명
현재 운영 모델에서는 production 환경을 대상으로
terraform apply를 실행하지 않습니다.
- 실제 secret, state, account-specific value를 저장소에 두지 않습니다.
- 이미 존재하는 인프라의 설계 의도를 코드 형태로 설명할 수 있습니다.
- 나중에 source of truth로 전환할 때 import/state/backend 정리 범위를 명확히 가져갈 수 있습니다.
하이브리드 구조에서는 AWS, 온프레미스, LLM serving, DB HA 영역의 장애 지점이 나뉘기 때문에 단순 리소스 나열만으로는 운영 역량을 설명하기 어렵습니다.
Grafana dashboard, CloudWatch alarm, SNS/Lambda alerting, Infracost 설정, public release checklist를 함께 관리했습니다.
- Kubernetes, PostgreSQL, pfSense, AIOps, autoscaling, FinOps 관점의 dashboard를 정리할 수 있습니다.
- 비용 추정과 public release hygiene을 README와 별도 문서로 설명할 수 있습니다.
- 운영 관점의 포트폴리오 근거를 코드와 문서로 함께 남길 수 있습니다.
온프레미스 서비스 이미지는 Harbor에 저장하고, Trivy 스캔 결과를 기준으로 이미지 취약점을 확인했습니다. 이 작업의 목적은 단순한 이미지 경량화가 아니라, 취약점이 발생하는 계층을 분리하고 애플리케이션 의존성, runtime base image, CI/CD 운영 흐름을 단계적으로 개선하는 것이었습니다.
대상 서비스는 다음과 같습니다.
service-admin
service-auth
service-batch
service-core
service-payment
service-payment 이미지에서는 이전 태그에서 취약점이 다수 탐지되었고, 최종
이미지에서는 Harbor Trivy 기준 취약점이 없는 상태까지 개선했습니다.
Harbor의 Trivy 스캔 결과, service-payment 이미지의 이전 태그에서 다수의
취약점이 확인되었습니다.
| 태그 | 이미지 크기 | Trivy 결과 |
|---|---|---|
18a29dc |
138.36 MiB | 122건 |
aa587b4 |
134.94 MiB | 47건 |
9e51b49 |
119.20 MiB | 취약점 없음 |
세부 취약점에서는 org.apache.tomcat.embed:tomcat-embed-core 패키지의
Tomcat 관련 CVE가 확인되었습니다.
Trivy 결과를 단순히 취약점 개수로만 보지 않고, 다음 세 계층으로 나누어 확인했습니다.
1. Application dependencies
- Spring Boot, Tomcat, Netty, Kafka, PostgreSQL Driver, Logback 등
2. Runtime base image
- JRE, libc, zlib, expat, png library 등 OS/runtime 패키지
3. Build and delivery pipeline
- Docker build context, Harbor push, GitOps update, Jenkins workspace
초기에는 Java 애플리케이션 의존성 취약점과 OS/base image 취약점이 함께 섞여 있었습니다. 따라서 먼저 애플리케이션 의존성과 Dockerfile 구조를 정리한 뒤, 남은 취약점이 어느 계층에서 발생하는지 다시 확인했습니다.
대표적으로 tomcat-embed-core 10.1.30에서는 다음 Tomcat 관련 CVE가
탐지되었습니다.
| CVE | 심각도 | 패키지 | 기존 버전 | 수정 권장 버전 |
|---|---|---|---|---|
CVE-2025-24813 |
심각 | tomcat-embed-core |
10.1.30 |
10.1.35 이상 |
CVE-2026-41293 |
심각 | tomcat-embed-core |
10.1.30 |
10.1.55 이상 |
CVE-2026-43512 |
심각 | tomcat-embed-core |
10.1.30 |
10.1.55 이상 |
CVE-2026-43515 |
심각 | tomcat-embed-core |
10.1.30 |
10.1.55 이상 |
기존 단일 jar 실행 구조를 운영 배포에 맞게 정리했습니다.
| 항목 | 적용 내용 | 목적 |
|---|---|---|
| 멀티스테이지 빌드 | Gradle build stage와 runtime stage 분리 | 런타임 이미지에서 빌드 도구 제거 |
| root build context | Docker build context를 repository root로 변경 | 멀티모듈 Gradle 빌드 지원 |
.dockerignore |
.git, build output, IDE 파일 제외 |
build context 축소 |
| non-root 실행 | USER 65532:65532 |
컨테이너 권한 최소화 |
| exec-form ENTRYPOINT | JSON array 형태 유지 | signal 처리와 실행 안정성 확보 |
애플리케이션 의존성도 보안 패치 기준으로 정렬했습니다.
| 패키지 계열 | 조치 |
|---|---|
| Spring Boot BOM | spring-boot-dependencies:3.5.14 적용 |
| Spring Boot Gradle Plugin | 3.5.14로 정렬 |
| Tomcat | 10.1.55로 보정 |
| Netty | 4.1.135.Final BOM 적용 |
| Kafka Clients | 3.9.2로 보정 |
| PostgreSQL Driver | 42.7.11로 보정 |
| Logback | 1.5.25로 보정 |
| commons-lang3 | 3.18.0로 보정 |
1차 개선 후 Java 애플리케이션 의존성 취약점은 크게 줄었지만, 모든 서비스에서 공통적으로 일부 Critical/High 취약점이 남았습니다. 이 시점에서 남은 취약점은 서비스별 코드보다는 runtime base image 계층 문제로 좁혀졌습니다.
남은 취약점이 Debian OS 패키지 계층에 집중되어 있어 runtime base image를 교체했습니다.
| 후보 | 장점 | 판단 |
|---|---|---|
gcr.io/distroless/java21-debian12:nonroot |
shell 없음, non-root 기본 | Debian 계열 CVE 잔존 |
eclipse-temurin:21-jre-alpine |
Java 21 명확 | 상대적으로 큰 이미지 |
bellsoft/liberica-runtime-container:jre-21-slim-musl |
Java 21 유지, musl 기반, 크기 감소 | 최종 선택 |
cgr.dev/chainguard/jre:latest |
보안 최적화 강점 | Java major version 변경 위험으로 제외 |
최종적으로 musl 기반 Java 21 slim runtime을 사용하고, Dockerfile에서
USER 65532:65532를 유지했습니다.
Dockerfile 변경과 함께 Jenkins/GitOps 운영 이슈도 정리했습니다.
| 이슈 | 조치 |
|---|---|
| 오래된 base image cache | docker build --pull 적용 |
| GitOps main branch 동시 push 충돌 | git pull --rebase 후 retry 적용 |
| Jenkins disk 부족 | Docker build cache, dangling image, 오래된 image tag 정리 |
| 관측성 부족 | GitOps update attempt/failure/success 구조화 로그 추가 |
2차 runtime base image 교체 후 Harbor Trivy 재스캔에서 온프레미스 대상 서비스의 취약점이 0건으로 확인되었습니다.
| Service | 초기 Total | 1차 개선 후 Total | 최종 재스캔 Total |
|---|---|---|---|
service-admin |
128 | 47 | 0 |
service-auth |
123 | 47 | 0 |
service-batch |
58 | 47 | 0 |
service-core |
123 | 47 | 0 |
service-payment |
122 | 47 | 0 |
runtime base image 교체 후 이미지 크기도 서비스별로 약 80MB 수준 감소했습니다.
| Service | Distroless Debian | Musl slim runtime | 감소량 |
|---|---|---|---|
service-admin |
450 MB | 369 MB | 약 81 MB |
service-auth |
404 MB | 325 MB | 약 79 MB |
service-batch |
354 MB | 274 MB | 약 80 MB |
service-core |
422 MB | 342 MB | 약 80 MB |
service-payment |
425 MB | 345 MB | 약 80 MB |
추가 검증 결과:
- 5개 온프레미스 서비스 Docker build 성공
service-admin,service-auth,service-core,service-payment/healthHTTP 200service-batch컨테이너 실행 정상 종료- Java 21 유지
- non-root 사용자
uid=65532 gid=65532유지 - Jenkinsfile linter 통과
취약점 0건은 해당 시점의 Harbor Trivy DB 기준 결과입니다. 새로운 CVE DB가 반영되거나 base image가 갱신되면 결과는 달라질 수 있으므로, 정기 재스캔과 base image refresh 정책이 필요합니다.
AWS에 배포되는 service-catalog 이미지는 Harbor 보안 최적화와 목적이 달랐습니다.
이 서비스는 ECR/EKS에 배포되므로, 이미지 크기와 push/pull 레이어 크기를 줄이는
방향으로 최적화했습니다.
기존 Dockerfile은 fat jar를 그대로 복사해 실행하는 구조였습니다.
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY build/libs/*-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]이 구조에서는 소스 코드 한 줄만 변경되어도 전체 jar 레이어가 다시 생성되고, ECR에 새로 push/pull되는 레이어 크기가 커지는 문제가 있었습니다.
다음 세 가지 방향으로 최적화했습니다.
| 단계 | 적용 내용 | 목적 |
|---|---|---|
| 1 | Spring Boot layered jar 추출 | 의존성과 애플리케이션 코드 레이어 분리 |
| 2 | .dockerignore 최소화 |
Docker build context 축소 |
| 3 | jlink custom JRE 적용 |
Java runtime 크기 감소 |
최종 Dockerfile은 3-stage 구조로 구성했습니다.
jre-builder -> jlink custom Java 21 runtime 생성
extractor -> Spring Boot jar layer 추출
runtime -> alpine + custom JRE + application layer 실행
Jenkins 배포 흐름도 함께 조정했습니다.
docker build --pull적용으로 최신 base image digest 기준 빌드PUSH_LATEST파라미터로 불필요한latesttag push 제어common-core,common-security, Gradle 설정 변경 시에도 재빌드되도록 watch path 보완
| 항목 | 최적화 전 | 최적화 후 |
|---|---|---|
| 이미지 크기 | 484 MB | 340 MB |
| 감소량 | - | 144 MB |
| 감소율 | - | 약 29.8% |
| 서비스 코드 변경 시 주요 변경 레이어 | 약 103 MB jar layer | 약 0.5 MB application layer |
ECR 콘솔에서도 이미지 최적화 전후 크기와 스캔 결과를 확인했습니다.
최적화 전
|
최적화 후
|
스크린샷 기준 전후 비교는 다음과 같습니다.
| 항목 | 최적화 전 | 최적화 후 |
|---|---|---|
| ECR 이미지 크기 | 97.11 MB | 49.01 MB |
| 감소량 | - | 48.10 MB |
| 감소율 | - | 약 49.5% |
| Critical | 1 | 0 |
| High | 3 | 0 |
| Medium | 3 | 0 |
핵심은 단순히 최종 이미지 크기만 줄인 것이 아니라, 자주 변경되는 애플리케이션 코드 레이어를 작게 분리했다는 점입니다. 일반적인 코드 변경 배포에서는 ECR에 새로 올라가는 레이어 크기와 EKS 노드에서 pull해야 하는 변경량을 줄일 수 있습니다.
- ECR 저장 용량 감소
- Jenkins push 대상 레이어 감소
- 신규 노드 scale-out 시 image pull 부담 감소
- rolling update 시 Pod 준비 시간 감소 가능성
- 노드 디스크 사용량과 네트워크 사용량 감소
infra/
+-- aws/
| +-- networking/ # VPC, subnet, NAT, route table, VPN placeholder
| +-- eks/ # EKS cluster, node group, core add-ons, IRSA records
| +-- web-edge/ # CloudFront, VPC origin, internal ALB routing
| +-- edge-security/ # ACM, WAFv2 records
| +-- dns/ # Route 53 records
| +-- data/ # RDS, DR standby, DMS, KMS, Secrets Manager records
| +-- monitoring/ # Helm 기반 observability records
| +-- observability/ # Grafana dashboards
| +-- storage/ # S3 buckets
| +-- ecr/ # Container image repositories
| +-- messaging/ # SQS queues
| +-- alerting/ # CloudWatch alarm, SNS, Lambda, Slack alerts
| +-- bastion/ # RDS jump host records
| +-- msk/ # Kafka/MSK planning notes
+-- on-prem/
| +-- kubernetes/ # Private Kubernetes operations notes
| +-- postgres-ha/ # PostgreSQL HA 담당자 작성 영역
| +-- vpn/ # pfSense / VPN 담당자 작성 영역
+-- vessl-ai/
| +-- vllm/ # VESSL AI vLLM deployment notes
+-- docs/
+-- architecture.md
+-- finops-inform.md
+-- public-release-checklist.md





