target-tracking-service에 Dockerfile이 없던 상태에서 시작해, 로컬 docker compose 전체 스택 검증 → AI 프로바이더를 OpenAI에서 Gemini로 전환 → 기존 GitOps(K3s + ArgoCD) 파이프라인에 실제로 태우기까지 진행한 기록.
eclipse-temurin:21-jdk-alpine(빌드) → eclipse-temurin:21-jre-alpine(런타임) 2단계. non-root 유저(spring)로 실행, app.jar 하나만 최종 이미지에 남기는 방식으로 이미지 크기를 줄임.
기존 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 하나로는, 컨테이너로 뜨는 앱이 kafka:9092로 초기 접속한 뒤 브로커가 되돌려주는 주소가 localhost라 재연결이 끓기는 문제가 있었다. 이중화로 해결:
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:29092,PLAINTEXT_HOST://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
Dockerfile을 시도하기 전에 로컬 ./gradlew build부터 실패하는 걸 확인했다. Docker와 무관하게 특정 커밋(Spring AI 1.0.0 + pgvector 의존성 추가)부터 컴파일 자체가 안 되고 있던 상태.
원인: spring-ai-openai-spring-boot-starter, spring-ai-pgvector-store-spring-boot-starter는 Spring AI 1.0.0 마일스톤(M6 이전) 시절 아티팩트 이름이고, 1.0.0 GA부터는 spring-ai-starter-model-openai, spring-ai-starter-vector-store-pgvector로 개명되었다. 로컬 Gradle 캐시의 spring-ai-bom-1.0.0.pom을 직접 열어서 실제 아티팩트 목록을 확인해 검증.
Gemini Developer API(Google AI Studio)는 신용카드 없이 무료 티어 제공(gemini-2.5-flash 계열: 1,500 req/day, 1M TPM). OpenAI는 사실상 전액 유료. 포트폴리오 성격상 무료 티어가 있는 쪽이 합리적이라 판단해 전환.
Spring AI 1.0.0 GA에는 Gemini Developer API용 스타터가 없고(spring-ai-vertex-ai-gemini만 존재 — GCP 프로젝트/서비스어카운트가 필요한 Vertex AI 경로), API 키 기반 무료 티어를 쓰려면 spring-ai-starter-model-google-genai가 필요한데 이건 1.1.0부터 추가되었다는 걸 Maven Central의 spring-ai-bom 각 버전 POM을 직접 curl로 훑어서 확인. 최신 GA는 2.0.0이지만 기존 코드와의 호환 리스크를 줄이기 위해 같은 1.x 라인의 최신 GA인 1.1.8로 올림.