Skip to content

[ICC-304] Google AI Studio → Vertex AI + GCS 전환 및 Cloudflare DDoS 방어 도입#166

Merged
lhoju0158 merged 16 commits intodevelopfrom
ICC-304-reversed-number-cloudfront
Apr 2, 2026
Merged

[ICC-304] Google AI Studio → Vertex AI + GCS 전환 및 Cloudflare DDoS 방어 도입#166
lhoju0158 merged 16 commits intodevelopfrom
ICC-304-reversed-number-cloudfront

Conversation

@GulSauce
Copy link
Copy Markdown
Member

@GulSauce GulSauce commented Apr 1, 2026

📢 설명

Google AI Studio → Vertex AI + GCS 전환, Cloudflare DDoS 방어 도입, 설정 파일 리팩토링 및 퀴즈 번호 경쟁상태 수정

변경 요약

# 변경 설명 영향 범위
1 Vertex AI + GCS 전환 API 키 → IAM 인증, Gemini Files API → GCS 직접 업로드 quiz-ai 모듈, 설정
2 Cloudflare DDoS 방어 Cloudflare 프록시 + Origin CA SSL + 실제 IP 복원 infra, nginx
3 설정 파일 리팩토링 역할 기반 파일명 + 파일 검증 프로퍼티 분리 config/, oci 모듈
4 퀴즈 번호 경쟁상태 수정 번호 할당을 소비자 측으로 이동 + ReentrantLock quiz-ai, quiz-make
5 CDN 네이밍 통일 AWS/CloudFront → 벤더 중립 CDN 네이밍 oci 모듈
6 GCP Terraform 구성 GCS, IAM, Vertex AI API를 IaC로 관리 infra/terraform
7 문서/기타 정리 CLAUDE.md 추가, 미사용 코드 제거 -

변경 1: Google AI Studio → Vertex AI + GCS 전환

Google AI Studio API는 GCP 프리 크레딧을 사용할 수 없어서 Vertex AI로 전환했다. Gemini 전용 PDF Context Caching은 Google만 제공하므로 Google 생태계를 유지해야 하고, 기존 Gemini 프롬프트의 콘텐츠 품질을 다른 AI로 옮기면 품질 저하를 예측할 수 없어 Vertex AI가 최선의 선택이었다.

기존 Gemini Files API의 resumable upload + 폴링 루프(initiateUpload → uploadBytes → waitForProcessing)가 GCS storage.create() 한 줄로 대체되어 GeminiFileRestClientConfig 전체를 삭제할 수 있었다.

// Before: Gemini Files API (RestClient 기반 resumable upload + 폴링)
String uploadSessionUrl = initiateUpload(fileSize, displayName);
GeminiFileUploadResponse response = uploadBytes(uploadSessionUrl, pdfFile, fileSize);
FileMetadata metadata = waitForProcessing(response.file().name()); // 최대 30초 폴링

// After: GCS 직접 업로드 (즉시 사용 가능)
BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, blobName)
    .setContentType("application/pdf").build();
storage.create(blobInfo, pdfBytes);
// Before: API 키 인증
Client.builder().apiKey(properties.getApiKey()).build();

// After: Vertex AI IAM 인증
Client.builder()
    .project(properties.getProjectId())
    .location(properties.getLocation())
    .vertexAI(true).build();
💡 의사 선택과정 (trade-off)

얻은 것

  • GCP 프리 크레딧 활용으로 비용 절감
  • IAM 기반 인증으로 API 키 관리 부담 제거
  • resumable upload + 폴링 제거로 코드 복잡도 대폭 감소

잃은 것

  • GCP 종속성 심화 (Google AI Studio는 클라우드 무관)

대안 검토

  • OpenAI 전환: Gemini 전용 PDF 캐싱 사용 불가, 프롬프트 재튜닝 필요, 품질 저하 리스크
  • AI Studio 유료 결제: 프리 크레딧 미적용으로 비용 비효율

변경 2: Cloudflare DDoS 방어 인프라

예방 차원에서 Cloudflare Free 플랜을 도입했다. api.q-asker.com에 프록시를 활성화하여 DDoS 방어, SSL 터미네이션, CDN 캐싱을 무료로 확보했다.

Nginx에 Cloudflare IP 대역 set_real_ip_fromCF-Connecting-IP 헤더 설정을 추가하여 Rate Limiting이 실제 클라이언트 IP 기준으로 동작하도록 했다. 배포 파일도 .github/resources/infra/blue-green/으로 이동하여 인프라 관심사를 분리했다.

# Cloudflare 실제 클라이언트 IP 복원
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... (Cloudflare 전체 IP 대역)
real_ip_header CF-Connecting-IP;
💡 의사 선택과정 (trade-off)

얻은 것

  • DDoS 방어, SSL, CDN을 무료로 확보
  • 업계에서 가장 널리 사용되는 솔루션

잃은 것

  • Cloudflare 장애 시 서비스 접근 불가 (프록시 의존)

대안 검토

  • AWS Shield / OCI WAF: 유료, 현 규모에 과도한 비용

변경 3: 설정 파일 리팩토링 및 프로퍼티 분리

Vertex AI 전환과 함께 설정 파일을 역할 기반 네이밍으로 정리했다. OciObjectStorageProperties에서 파일 검증 필드를 FileValidationProperties로 분리하여, 스토리지와 무관한 비즈니스 검증 규칙을 독립시켰다.

Before After
server.yml database-config.yml
ai.yml ai-setting.yml
security.yml spring-security.yml
oci.yml oci-bucket-config.yml
q-asker.yml app-common.yml + resilience.yml
monitoring.yml actuator.yml
springdoc.yml spring-doc.yml
// Before: OCI 프로퍼티에 파일 검증이 혼재
@ConfigurationProperties(prefix = "oci.object-storage")
public record OciObjectStorageProperties(
    String namespace, String bucketName, String region,
    String configFilePath, String profile,
    long maxFileSize, int maxFileNameLength, String allowedExtensions) {}

// After: 파일 검증 프로퍼티 분리
@ConfigurationProperties(prefix = "oci.object-storage")
public record OciObjectStorageProperties(
    String namespace, String bucketName, String region,
    String configFilePath, String profile) {}

@ConfigurationProperties(prefix = "q-asker.file-validation")
public record FileValidationProperties(
    long maxFileSize, int maxFileNameLength, String allowedExtensions) {}

변경 4: 퀴즈 번호 경쟁상태 수정

기존에는 AI 오케스트레이션 레이어에서 AtomicInteger로 번호를 할당한 뒤 소비자에게 전달했는데, 번호 할당 시점과 SSE 전송 시점 사이에 다른 청크가 끼어들어 번호 역전이 발생할 수 있었다. 예: 1번 청크가 번호 15를 받았지만, 2번 청크(번호 610)가 먼저 전송되는 상황.

번호 할당을 소비자 측(GenerationCommandServiceImpl)으로 이동하고, ReentrantLock으로 번호 할당 → DB 저장 → SSE 전송을 하나의 임계 구역으로 묶어 순서를 보장했다. Virtual Thread 환경에서 synchronized는 캐리어 스레드 pinning 문제가 있어 ReentrantLock을 선택했다.

// 소비자 측에서 번호 할당 + 직렬화
AtomicInteger numberCounter = new AtomicInteger(1);
ReentrantLock consumerLock = new ReentrantLock();

// consumer 내부
consumerLock.lock();
try {
    for (QuizGeneratedFromAI quiz : problemSet.getQuiz()) {
        quiz.setNumber(numberCounter.getAndIncrement());
    }
    // DB 저장 → SSE 전송 (순서 보장)
} finally {
    consumerLock.unlock();
}
💡 의사 선택과정 (trade-off)

얻은 것

  • 클라이언트가 받는 퀴즈 번호가 항상 순차적으로 보장
  • 번호 역전으로 인한 프론트엔드 혼란 방지

잃은 것

  • consumer가 직렬화되어 병렬 처리 이점 일부 감소

수용 이유: lock 구간 내 작업이 DB 저장 + SSE 전송뿐이라 체류 시간이 짧고, 실제 병목인 AI 생성은 lock 밖에서 병렬로 돌아가므로 영향 미미


변경 5: AWS/CloudFront → CDN 네이밍 통일

Cloudflare 도입에 맞춰 AwsCloudFrontPropertiesCdnProperties, checkCloudFrontUrlWithThrowingcheckCdnUrlWithThrowing 등 벤더 특정 네이밍을 중립적인 CDN으로 변경했다.

// Before
@ConfigurationProperties(prefix = "aws.cloudfront")
public record AwsCloudFrontProperties(String baseUrl) {}

// After
@ConfigurationProperties(prefix = "cdn")
public record CdnProperties(String baseUrl) {}

변경 6: GCP 인프라 Terraform 구성

Vertex AI 전환에 필요한 GCP 리소스(GCS 버킷, 서비스 계정, API 활성화)를 Terraform으로 관리한다. 추후 계정 변경 시에도 동일 인프라를 재현할 수 있다. GCS 버킷은 1일 lifecycle 정책으로 임시 PDF를 자동 삭제한다.

resource "google_storage_bucket" "ai_files" {
  name     = var.gcs_bucket_name
  location = var.region
  lifecycle_rule {
    condition { age = 1 }
    action { type = "Delete" }
  }
}

변경 7: 문서 및 기타 정리

  • CLAUDE.md 추가 (프로젝트 개요, 기술 스택, 아키텍처 문서)
  • .gitignore에 Terraform state 파일 추가, CLAUDE.md 추적 해제 제거
  • 미사용 코드 제거: frontendDevUrl, AIProblem.number, AWS S3 BOM

코드 설명: 인라인 코멘트

PR의 주요 코드 라인에 인라인 코멘트가 등록되어 있습니다. Files changed 탭에서 확인하세요.

✅ 체크리스트

기본

  • 의사 선택 과정이 적절한지 확인
  • 코드 리뷰

GulSauce added 9 commits April 1, 2026 14:51
- Nginx: Cloudflare IP 복원(set_real_ip_from) 추가, Origin CA 인증서 경로 변경, ACME challenge 블록 제거
- docker-compose: Let's Encrypt 볼륨을 Origin CA 볼륨으로 교체
- init-ssl.sh: Let's Encrypt 초기화 스크립트 삭제 (Origin CA로 전환)
- Gemini File API → GCS 업로드로 교체 (GeminiFileServiceImpl)
- API Key 인증 → Vertex AI 모드 (project-id + location + ADC)
- GCS SDK 의존성 추가 (google-cloud-storage + libraries-bom)
- GeminiFileRestClientConfig 삭제 (REST 클라이언트 불필요)
- Context Cache URI: files/xxx → gs://bucket/path
- 설정값 Jasypt 암호화 (project-id, location, bucket-name)
- GCS 버킷 (q-asker-ai-files, 1일 수명주기)
- AI 서비스 계정 + IAM (Storage Object Admin, Vertex AI User)
- Vertex AI / Cloud Storage API 활성화
- .gitignore에 tfstate 제외, lock.hcl은 커밋 대상
- 설정 파일 이름 변경: ai.yml→ai-setting, security→spring-security 등
- q-asker.yml → app-common.yml (앱 공통 설정)
- server.yml에서 DB 설정 분리 → database-config.yml
- resilience4j.yml + rate-limit → resilience.yml 통합
- 파일 검증 설정을 OCI 프로퍼티에서 분리 → FileValidationProperties
- 배포 스크립트 .github/resources/ → infra/blue-green/ 이동
- docker-compose에 GCP ADC 볼륨 마운트 추가
- 기술 스택: Google Gemini via Vertex AI, Google Cloud Storage 추가
- 환경 변수: GCP project-id, location, GCS_BUCKET_NAME 추가
- 아키텍처: infra/terraform/gcp 디렉토리 추가
- config: ai.yml, monitoring.yml, oci.yml, security.yml, springdoc.yml
- deploy: .github/resources/ → infra/blue-green/ 이동 완료 후 원본 제거
- spring.security.oauth2, spring.security.jwt가 q-asker.security 하위에 있어
  Client id of registration 'google' must not be empty 에러 발생
- spring: 블록으로 올바르게 이동
- 멀티 도큐먼트 YAML에서 프로필 섹션이 common 섹션의 registration 맵을 덮어써
  client-id가 사라지는 문제 해결
- client-id, client-secret을 local/prod 각 프로필에 직접 포함
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 02509daf-42cb-4f99-a693-2d613af508fa

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ICC-304-reversed-number-cloudfront

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

GulSauce added 3 commits April 1, 2026 23:38
- 프로필별 분리 방식이 멀티 도큐먼트 YAML 병합에서 문제 발생
- client-id/secret을 공통 섹션에 두고 프로필에서는 redirect-uri만 오버라이드
- Nginx SSL 인증서 경로를 Cloudflare Origin CA → Let's Encrypt로 변경
- docker-compose에 /etc/letsencrypt 볼륨 마운트 추가
- Cloudflare Origin CA 설정은 주석으로 보존
- AwsCloudFrontProperties → CdnProperties (prefix: cdn)
- aws.cloudfront.base-url → cdn.base-url
- checkCloudFrontUrlWithThrowing → checkCdnUrlWithThrowing
- 변수명 s3Future/cloudFrontUrl → ociFuture/cdnUrl
- 미사용 AWS SDK BOM 의존성 제거
- 주석/DisplayName CloudFront/S3 참조 CDN/OCI로 변경
- CLAUDE.md 기술 스택 갱신 (AWS CloudFront → Cloudflare CDN)
@GulSauce GulSauce changed the title Icc 304 reversed number cloudfront [ICC-304] Google AI Studio → Vertex AI + GCS 전환 및 Cloudflare DDoS 방어 도입 Apr 1, 2026
Copy link
Copy Markdown
Member Author

@GulSauce GulSauce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 코드 설명 코멘트

return Client.builder()
.apiKey(properties.getApiKey())
.project(properties.getProjectId())
.location(properties.getLocation())
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google AI Studio(API 키) → Vertex AI(IAM) 전환 핵심. vertexAI(true)로 엔드포인트가 generativelanguage.googleapis.com에서 Vertex AI로 변경되며, project/location 기반 인증을 사용한다.

this.fileRequestRepeat =
Counter.builder("gemini.file.request")
Counter.builder("gcs.file.request")
.tag("type", "repeat")
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존 Gemini Files API의 3단계(initiateUpload → uploadBytes → waitForProcessing 폴링)를 GCS storage.create() 한 줄로 대체. GCS는 업로드 즉시 사용 가능하므로 폴링 루프가 불필요해졌다.

@@ -81,6 +82,8 @@ private void processAsyncGeneration(
String sessionId, Long problemSetId, GenerationRequest request) {

AtomicInteger atomicGeneratedCount = new AtomicInteger(0);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

퀴즈 번호 경쟁상태 수정: ReentrantLock으로 번호 할당 → DB 저장 → SSE 전송을 직렬화하여 번호 역전 방지. synchronized 대신 ReentrantLock을 쓴 이유는 Virtual Thread 환경에서 캐리어 스레드 pinning 문제를 회피하기 위함.

GulSauce added 4 commits April 2, 2026 01:54
- Nginx: Origin CA 인증서 경로 활성화, Let's Encrypt 주석 제거
- docker-compose: Let's Encrypt 볼륨 제거
Cloudflare IP 대역에서 API 서버(80/443), MON 서버(3000) 인바운드를
허용하는 NSG 규칙을 Terraform으로 관리하도록 IaC 구성
Copy link
Copy Markdown
Member

@lhoju0158 lhoju0158 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다. 수고하셨습니다

@lhoju0158 lhoju0158 merged commit cf3e70d into develop Apr 2, 2026
2 checks passed
@GulSauce GulSauce deleted the ICC-304-reversed-number-cloudfront branch April 3, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants