Skip to content

docs(S15P11A705-96): define dev deployment approval contract - #32

Draft
tpals0409 wants to merge 1 commit into
mainfrom
docs/S15P11A705-96-dev-deployment-contract
Draft

docs(S15P11A705-96): define dev deployment approval contract#32
tpals0409 wants to merge 1 commit into
mainfrom
docs/S15P11A705-96-dev-deployment-contract

Conversation

@tpals0409

Copy link
Copy Markdown
Contributor

Summary

S15P11A705-96의 dev AI 배포를 위한 담당자 승인 요청 문서를 추가합니다.

  • source-verified 현재 동작과 owner 결정/TODO 분리
  • source-derived 환경변수 key 이름과 Secret-safe handoff 경계 기록
  • model/profile/judge compatibility ownership 및 fail-closed gate 명시
  • source-proven exact preset bootstrap command, 멱등성, provenance, retry/recovery 기록
  • 현재 /health 동작과 readiness/liveness 구분 및 /metrics 부재 기록
  • preset exact-set/SHA provenance와 외부 API retry/error classification의 known gap 공개
  • exact 8-key Secret 외 source-default 4개 설정의 ConfigMap/default owner 결정 요청
  • 이정헌 owner 체크리스트와 colosair reviewer 확인용 응답 표 추가

Scope

Docs-only입니다. 서비스 코드, 설정, 테스트, CI, Dockerfile, Kubernetes/GitOps/live 리소스는 변경하지 않습니다.

Review requested

AI owner 이정헌: 문서의 Owner 응답 표를 비밀값 없이 작성하고 각 gate를 승인/수정 필요/보류로 응답해 주세요.

Reviewer candidate colosair: owner 답변, source citation, fail-closed 조건을 확인해 주세요.

Activation safety

credential handoff, model/profile 합의, bootstrap provenance/성공 증거가 없으면 관련 Infra gate는 false를 유지합니다. 이 PR은 live 변경 승인이 아닙니다.

Verification

  • git diff --check
  • changed-file docs-only guard
  • added-line credential/token/password/secret-like value scan
  • 저장소에 명백한 Markdown 전용 validator가 없어 추가 validator는 실행/도입하지 않음

@tpals0409
tpals0409 requested a review from colosair July 29, 2026 00:05

@colosair colosair left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI owner 이정헌입니다. 소스 실측 기준으로 §4 응답 표를 채웠습니다.

기준 커밋은 971e515이고 문서의 감사 기준 85f02f7과 다르지만, 아래 인용한 파일·라인은 두 커밋에서 동일합니다. 시크릿 값은 이름·형식만 적었습니다.

먼저 문서 정정 1건배포를 막을 수 있는 결함 3건을 앞에 둡니다.


정정 — §2.4의 preset version 서술

"preset version은 YAML 항목의 version 또는 소스 기본 처리로 결정된다"

data/keyword_preset.yamlversion 필드가 존재하지 않습니다. 파일 단위도 항목 단위도 없고, 헤더 주석이 "embedding·embedding_profile·version은 여기서 다루지 않습니다"라고 명시합니다. loader는 int(preset.get("version", 1))이므로 모든 행의 version이 항상 1입니다. 캐시도 max(p.version, default=1)이라 ai.context_keyword.preset_version에 저장되는 값은 실질 상수입니다.

"YAML의 version을 쓸 수도 있다"로 읽히면 향후 그 필드를 신뢰하게 되니, **"version은 현재 상수 1이며 개정 추적 기능이 없다"**로 고쳐 주시면 좋겠습니다. §2.4의 gate false 판정 자체는 옳습니다.


배포 전에 반드시 짚어야 할 결함 3건

GMS_BASE_URL 형식 오류 시 비대칭 장애 — 형식 검증 코드가 없습니다

한 변수를 두 클라이언트가 다르게 소비합니다.

  • 임베딩: {GMS_BASE_URL}/embeddings (app/client/embedding_client.py:53)
  • 판정: GMS_BASE_URL.split("/gmsapi/")[0] + "/gmsapi"파생한 root + Gemini 경로 (app/client/llm_client.py:74,78-81)

/gmsapi/ 세그먼트가 빠지면 임베딩은 정상 동작하고 judge만 조용히 실패합니다. 형식을 검증하는 코드가 없고, 기동 시 GMS로 요청을 보내지 않으며(main.py:38-49 — 클라이언트 객체 생성만), /health는 상수 응답이라 틀린 endpoint·key로도 서버는 정상 기동하고 첫 실사용 요청에서야 실패합니다.

→ 배포 직후 임베딩·judge 양쪽을 각각 1회씩 실호출하는 스모크 절차를 활성화 조건에 넣어 주세요. 형식 검증은 AI 파트가 후속 티켓으로 추가하겠습니다.

/health를 readiness에 쓰면 죽은 인스턴스에 트래픽이 들어갑니다

핸들러가 return {"status": "ok"} 한 줄입니다(main.py:105-107). DB 세션 획득도 캐시 조회도 하지 않아 기동 후 DB가 끊기거나 커넥션 풀이 고갈돼도 계속 200입니다. lifespan이 보증하는 건 기동 시점 1회뿐입니다.

/healthliveness 전용으로만 쓰고, readiness는 /ready 신설 전까지 연결하지 말아 주세요. 신설은 AI 파트 작업이며 app.statedb·preset_cache가 이미 있어 DB ping + preset 건수 + 현재 profile 노출로 구현 가능합니다. 원하시는 경로명과 응답 스키마를 지정해 주시면 그 계약대로 만들겠습니다.

③ 배포 순서가 매니페스트 밖에서는 강제되지 않습니다

① back Flyway 마이그레이션 (V1 / V100 / V101 — ai.* 테이블, back 소관)
② python -m app.bootstrap.load_presets   ← 1회, 서버와 동일한 env 전체 필요
③ ai 앱 기동

②를 건너뛰면 ③이 preset 0건 RuntimeError크래시 루프합니다(main.py:51-60). 이건 사후 감지이지 순서 보장이 아닙니다.

init container 방식을 권장합니다. 같은 이미지 + command 오버라이드 + 같은 env면 순서가 매니페스트 수준에서 서고, 실패 시 Pod이 Ready로 넘어가지 않습니다. 이미지가 COPY data ./data를 하고 pyyamlrequirements.lock:55에 있어 런타임 이미지로 실행 가능합니다.


§4 Owner 응답 표

승인 항목 Owner 응답 비밀값 없는 결정·근거 요청 gate
dev-only, internal ClusterIP, no Ingress 승인 노출 라우트는 /health, POST /internal/v1/search, POST /internal/v1/context/process 3개뿐. 뒤 둘은 X-Internal-Secret 필수(app/core/security.py:12,23-30), /health만 무인증이라 프로브가 헤더 없이 호출 가능. 컨테이너 포트 8000 고정(PORT env 미지원, CMD 하드코딩) true
별도 pinlog_dev DB와 Secret-ref handoff 승인 ai는 마이그레이션을 실행하지 않습니다. 커넥션마다 SET search_path = ai, public을 걸고 core.* 접근이 없습니다(app/core/db.py). 롤 권한을 pinlog_devai 스키마로 한정해 주시면 됩니다. 단 ai.* 테이블은 back의 Flyway 소관이라 ①이 선행돼야 합니다 true (①선행 조건부)
source-derived env key names 승인 표의 12개가 완전합니다. app/ 전체에서 os.environ/getenv를 직접 읽는 코드가 0건이고 진입점은 app/core/config.py:Settings 한 곳뿐입니다. rename·prefix 차이 없음(.env.example:10-11, README.md:62, tests/conftest.py:37-38 교차 확인). Dockerfile에 ENV 지시어 0건이라 12개 전부 런타임 주입이며 시크릿이 이미지에 구워질 위험은 없습니다. tools/OPENAI_* 폴백은 .dockerignore로 이미지에서 제외돼 배포와 무관합니다 true
source-default 4개 설정의 ConfigMap/default 배치 수정 필요 4개를 둘로 나눠 주세요. 명시 주입 필요 2개PROCESSING_EXPIRY_SEC는 Spring 재스캔 만료와 반드시 동일해야 하므로(코드 주석·.env.example이 "임의값 금지" 명시) back과 같은 원본에서 주입해야 하고, PINLOG_JUDGE_MODEL은 유일하게 기본값이 있어 누락 시 조용히 구 모델로 되돌아갑니다(embedding 4종의 "기본값 없음 → 기동 실패" 원칙과 비대칭). source default 수용 2개KEYWORD_CANDIDATE_TOP_K, SIMILARITY_FLOOR는 dev에서 기본값으로 두셔도 됩니다 2개 true, 2개 ConfigMap 확정 후
GMS endpoint/API key handoff와 client 호환성 수정 필요 참조. 인증 키는 하나이고 헤더만 다릅니다 — 임베딩 Authorization: Bearer …(embedding_client.py:54), 판정 x-goog-api-key(llm_client.py:99). 임베딩용/판정용 키를 분리 주입하는 경로가 코드에 없습니다(main.py:40-48이 동일 settings를 두 클라이언트에 주입). egress는 GMS 게이트웨이 호스트 1개만 허용하면 됩니다. 프록시 미지원(HTTP_PROXY 처리 코드 없음, httpx 기본 동작 의존) 스모크 증거 확보 후
embedding model/dimension/distance/profile 승인 text-embedding-3-small / 1536 / cosine / profile openai-text-embedding-3-small-1536-cosine-v1. 시크릿이 아니므로 ConfigMap 가능합니다. 기동 시 Settings._profile_consistency가 세 토큰의 profile 문자열 포함 여부를 검사해 불일치 시 기동 실패시킵니다(config.py:45-62) — 다만 단순 substring 검사라 순서·구분자·벤더 접두어는 검증하지 않습니다 true
judge model 및 compatibility ownership 승인 gemini-2.5-flash. judge model은 embedding profile과 별개 축이며 ai.context_keyword_analysis.model_profile에 기록됩니다(keyword_service.py:170-175). 따라서 judge 모델 교체는 재임베딩을 유발하지 않습니다(docs/spec/model-profile.md §4). 호환성 승인 주체는 AI owner입니다 true
preset bootstrap 실행·retry·provenance 증거 수정 필요 command는 python -m app.bootstrap.load_presets가 맞습니다. 결정 요청 사항 — 실행 형태는 init container 권장(위 ③), retry는 backoffLimit ≥ 2 권장(로더에 retry/backoff 코드가 전혀 없어 재시도 책임이 오케스트레이터에 있습니다), 동시 실행 방어가 없으므로(advisory lock 없음) 단일 실행을 보장해 주세요. 중복 기동 시 데이터 손상은 없으나 임베딩 비용이 2배입니다. 성공 판정은 종료 코드 0 + stdout OK: 27 presets upserted. 부트스트랩 Job도 INTERNAL_SHARED_SECRET을 요구합니다(공용 Settings 강제) — dev에서는 감수하고 전용 설정 분리는 후속으로 남깁니다 실행 형태 확정 후
preset exact-set/version provenance gap 보류 (dev 수용) gate false 판정에 동의합니다. 위 정정대로 version은 상수 1이고, YAML에서 항목을 지워도 DB 행이 is_active=true로 남으며, is_active가 코드에 True 하드코딩이라 수동 비활성화도 재실행하면 되살아납니다. 추가 위험 — _UPSERT의 conflict target이 id인데 DDL에 code VARCHAR(50) NOT NULL UNIQUE가 있어, YAML에서 id를 바꾸고 code를 유지하면 트랜잭션 전체가 실패합니다. dev는 27건 고정으로 진행하고 provenance 설계는 AI 파트 후속 티켓(back 스키마 협의 필요)으로 분리하겠습니다 false 유지 동의
startup/readiness/liveness probe 계약 수정 필요 참조. startup은 GMS 호출 0건이고 preset 27행×1536 float32 ≈ 166KB를 SELECT 1회로 읽을 뿐이라 지배 요인은 Python import와 풀 생성입니다. 콜드 스타트 2~5초로 추정하나 레포에 실측치가 없습니다initialDelaySeconds/failureThreshold 확정 전 dev 컨테이너에서 1회 측정이 필요하며 AI 파트가 측정해 회신할 수 있습니다(요청 주세요). Dockerfile에 HEALTHCHECK 없음 /ready 신설 후
/metrics 부재 수용 또는 앱 변경 요청 보류 (dev 수용) dev는 로그 기반으로 가되 한계를 명시적으로 인지하고 진행해 주세요. embedding_client.py·llm_client.py로거 자체가 없어 GMS 성공률·지연·타임아웃 발생률을 어떤 경로로도 알 수 없고, stale PROCESSING 재선점이 무로그라 "신규 처리"와 "stale 회수"를 구분할 수 없습니다. ⚠️ app/ 전체에 log.error 호출이 0건이라 "ERROR 발생 시 알림" 룰을 걸면 아무것도 잡히지 않습니다 — dev 알림은 WARNING 기준으로 잡아 주세요. 계측 도입은 인프라 결정 후 AI가 구현하겠습니다 false 유지 동의
외부 API retry/error classification gap dev 수용 · 수정 선행 아님 S15P11A705-121 추적에 동의하며 수정을 배포 선행 조건으로 두지 않겠습니다. dev는 부하가 낮아 429 발생 가능성이 낮고, 일시 오류는 상태를 PROCESSING으로 둔 채 반환해 Spring 재스캔(만료 10분)이 회수하는 설계라 작업이 영구 유실되지 않습니다. 다만 관측 한계(위 항목) 때문에 발생 시 감지가 늦으니, dev 운영 중 judge 실패가 반복되면 즉시 알려 주세요 true (dev 한정)
resource/replica/rollout/rollback 운영값 수정 필요 — 값 제시 replica 1을 요청합니다. uvicorn 단일 워커(--workers 0건)이고 PresetCache가 프로세스당 1개라 수평 확장 시 인스턴스마다 startup에서 각자 적재합니다. graceful shutdown 30초 + terminationGracePeriodSeconds 40초를 제안합니다 — POST /internal/v1/context/process202를 즉시 반환하고 실제 처리를 같은 프로세스의 BackgroundTasks로 수행하므로(app/api/internal/v1/context.py:24-32), 롤아웃 시 작업이 끊기면 해당 Context가 PROCESSING으로 남아 10분 만료 후에야 회수됩니다. CPU/메모리 실측치는 레포에 없어 제시하지 못합니다 — dev 관측 후 조정 요청드리겠습니다. 롤백 대상 지정은 태그가 commit sha 전용(latest·semver 없음)이라 인프라 결정 사항입니다 값 확정 후
승인 6 · 수정 필요 5 · dev 수용 보류 2

추가 정보 2건

PresetCache는 런타임 갱신 경로가 없습니다. startup 1회 적재 후 재적재 엔드포인트·시그널이 없어(app/cache/preset_cache.py, 재적재 호출부 0건) preset 변경·profile 전환 반영은 프로세스 재기동이 유일한 경로입니다. 배포 계약에 명시해 주세요.

.env 파일 방식 주입 시 UTF-8 BOM 함정이 있습니다. 첫 줄 key가 인식되지 않아 "missing"으로 기동 실패하며 재발 이력이 있습니다(docs/troubleshooting/2026-07-23-fastapi-local-verification.md T16). k8s Secret env 주입이면 해당 없습니다.


AI 파트가 착수할 후속 작업

우선순위를 지정해 주시면 그 순서로 진행하겠습니다.

# 내용
A /ready 신설 — DB ping + preset 건수 + 현재 profile 노출 (우선순위 1 권장)
B GMS_BASE_URL/gmsapi/ 포함 형식 검증 추가
C /context/process 요청에 embeddingProfile 필드 추가 + 대조 (back 협의)
D 영구 오류 로그 레벨 WARNING → ERROR 정정 (스펙 정합)
E preset provenance 설계 — 적재 시각·source SHA (back 스키마 협의)

AB는 위 ①②를 직접 메우므로 dev 배포 전에 넣는 것이 안전합니다. 일정에 넣을지 판단 부탁드립니다.

@tpals0409

Copy link
Copy Markdown
Contributor Author

이정헌님, 소스 실측 기반 상세 답변 감사합니다. 정정과 결함 3건을 포함해 확인했습니다. 특히 preset version이 현재 상수 1이고 개정 추적 기능이 없다는 점은 문서에서 바로잡겠습니다.

Infra 쪽에서는 key/token 값과 앱 호환성 설계를 AI owner 소유로 두고, 값을 열람하지 않은 채 암호화 Secret과 컨테이너만 배포하겠습니다. 아래 항목을 다음 handoff로 요청드립니다.

AI 파트 요청사항

1. GitHub Secret → 암호화 Secret handoff

AI 소유 값은 Team-PinLog/ai GitHub Actions Secret에 등록·회전해 주세요. GitHub Secret은 Kubernetes Pod에 자동 전달되지 않으므로, plaintext를 출력하거나 commit하지 않고 SealedSecret encryptedData만 산출하는 경로도 함께 제공해 주세요.

  • AI owner 소유 Secret 리소스 제안: ai-owner-secrets
  • AI owner 소유 key: GMS_API_KEY, GMS_BASE_URL, INTERNAL_SHARED_SECRET
  • 실제 값은 PR, Actions log, artifact metadata에 노출 금지
  • cluster credential을 AI repo에 넣거나 workflow에서 kubectl apply하지 말고, 암호화 manifest/artifact만 Infra에 전달
  • Infra는 별도 ai-db-credentialsDATABASE_URL을 관리하고 두 Secret을 envFrom으로 연결

이 구조/이름을 수정하고 싶으면 AI owner 기준안을 비밀값 없이 회신해 주세요. Infra는 확정된 opaque Secret interface만 소비하겠습니다.

2. /ready와 GMS URL 검증 구현 요청

제안하신 후속 작업 A, B를 dev 배포 전 우선순위로 요청드립니다.

  • GET /health: process-only liveness 유지
  • GET /ready:
    • DB SELECT 1 성공
    • 현재 profile preset cache 1건 이상
    • 성공 200 {"status":"ready"}
    • 실패 503 {"status":"not_ready"}
    • GMS 호출은 readiness에서 제외
    • credential, endpoint, profile 값은 응답에 노출하지 않음
  • startup probe는 /health, readiness만 /ready 사용
  • GMS_BASE_URL은 embedding과 judge가 모두 요구하는 /gmsapi/ 형식을 startup validation에서 fail-fast

구현 PR과 merge 후 immutable image source SHA/digest를 전달해 주세요.

3. embedding·judge 양방향 smoke command 요청

배포 후 같은 컨테이너/환경에서 다음을 각각 1회 호출하고, 한쪽이라도 실패하면 non-zero exit하는 source-owned smoke command 또는 module을 제공해 주세요.

  • embedding 1회
  • judge 1회
  • credential/endpoint/request 원문을 stdout·stderr에 출력하지 않음
  • 성공 여부와 안전한 status만 출력
  • DB mutation 없이 실행 가능

Infra activation은 이 smoke가 둘 다 성공한 뒤 진행하겠습니다.

4. bootstrap 실행 방식

init container는 Pod 재시작과 rolling overlap마다 반복 실행돼, 요청하신 “단일 실행”과 충돌할 수 있습니다. Infra는 기존 chart의 versioned Argo PreSync Job으로 실행하겠습니다.

  • command: python -m app.bootstrap.load_presets
  • 동일 image와 env 사용
  • backoffLimit: 2
  • Deployment보다 선행
  • 성공 조건: exit 0 + OK: 27 presets upserted
  • 한 revision에서 Job 1개만 실행

현재 loader가 이 방식으로 실행 가능한지 확인 부탁드립니다. exact-set/version provenance는 dev에서 false gate로 남기고 후속 티켓으로 분리하겠습니다.

Infra에서 확정해 진행할 항목

  • replica: 1
  • 기존 CPU/memory 초기값으로 dev 배포 후 실측 조정
  • terminationGracePeriodSeconds: 40
  • internal ClusterIP:8000, no Ingress
  • non-secret ConfigMap 명시 주입: embedding model/dimension/distance/profile, PINLOG_JUDGE_MODEL, PROCESSING_EXPIRY_SEC
  • KEYWORD_CANDIDATE_TOP_K, SIMILARITY_FLOOR: dev source default 사용
  • Backend Flyway → PreSync bootstrap Job → Deployment 순서 유지
  • metrics/retry/error-classification/provenance 개선은 dev 배포 blocker에서 제외하고 기존 후속 티켓으로 추적

위 1~4의 PR/artifact와 회신을 주시면 Infra는 credential 값을 보지 않고 DB·Secret 참조·ConfigMap·컨테이너 배포를 진행하겠습니다.

@colosair

Copy link
Copy Markdown
Member

확인 감사합니다. 4번 지적이 맞습니다 — init container 제안을 철회합니다.

Pod 재시작과 rolling overlap마다 반복 실행된다는 점을 놓쳤습니다. 그러면 매번 27건을 GMS에 다시 임베딩하고(임베딩 캐시 없음), 롤링 중 old/new Pod이 겹치면 제가 요청한 "단일 실행 보장"과도 정면으로 충돌합니다. versioned Argo PreSync Job이 옳습니다.

4. PreSync Job 적합성 — 가능합니다

제시하신 조건 전부 현재 loader로 충족됩니다.

조건 확인
python -m app.bootstrap.load_presets 앱과 독립된 모듈. 서버 기동 경로를 타지 않습니다
동일 image 이미지가 COPY data ./data를 하고 WORKDIR /app이라 YAML 경로가 /app/data/keyword_preset.yaml로 해석됩니다. pyyamlrequirements.lock:55에 있어 런타임 이미지에 설치돼 있습니다
backoffLimit: 2 로더에 retry/backoff가 전혀 없어 재시도 책임이 오케스트레이터에 있습니다. 이 값이 그 공백을 메웁니다
Deployment 선행 필요합니다. 건너뛰면 서버가 preset 0건 RuntimeError로 크래시 루프합니다(main.py:51-60)
exit 0 + OK: 27 presets upserted main()이 예외를 잡지 않아 실패 시 traceback과 함께 비정상 종료합니다. 성공 시 stdout 문자열은 정확히 그 형식입니다
한 revision에 Job 1개 로더에 advisory lock이 없어 이 보장이 꼭 필요합니다. 중복 실행 시 데이터 손상은 없으나(최종 승자만 남음) 임베딩 비용이 2배입니다

두 가지만 유의해 주세요.

Job에도 서버와 동일한 env 전체가 필요합니다. get_settings()가 단일 모델을 강제하므로 부트스트랩이 쓰지 않는 INTERNAL_SHARED_SECRET까지 없으면 실패합니다. ai-owner-secretsai-db-credentials를 Job에도 envFrom으로 걸어 주세요. 부트스트랩 전용 설정 분리는 후속으로 남기겠습니다.

config만 바뀐 sync에서도 Job이 돌면 27건이 재임베딩됩니다. 멱등하므로 안전하지만 GMS 호출 비용이 발생합니다. revision 단위로 한 번이면 dev에서는 감수할 만하다고 봅니다 — 실행 빈도가 예상보다 높아지면 알려 주시면 loader 쪽에 스킵 조건을 넣겠습니다.

1. Secret handoff — 진행하되 cert가 필요합니다

ai-owner-secrets / ai-db-credentials 분리와 key 배치에 동의합니다. 수정 요청 없습니다.

GMS_API_KEY·GMS_BASE_URL·INTERNAL_SHARED_SECRETTeam-PinLog/ai Actions Secret에 등록하고, plaintext를 출력·커밋하지 않고 SealedSecret encryptedData만 artifact로 산출하는 workflow를 만들겠습니다. cluster credential은 레포에 넣지 않고 kubectl apply도 하지 않습니다.

요청: kubeseal 암호화에 sealed-secrets controller의 public cert가 필요합니다. 클러스터 접근 없이 오프라인 암호화가 가능한 형식(--cert로 넘길 PEM)으로 주시면 workflow에 넣겠습니다. 이건 공개 키라 레포에 커밋해도 무방한 것으로 압니다만, 별도 경로를 원하시면 지정해 주세요.

INTERNAL_SHARED_SECRETback과 같은 값이어야 합니다(/internal/* 요청의 X-Internal-Secret과 단순 문자열 완전 일치 비교). 두 파트가 각자 등록하면 어긋나므로, 값 생성·전달 주체를 한쪽으로 정해야 합니다. AI 파트가 생성해 back에 전달하는 방식으로 진행해도 될지 확인 부탁드립니다.

2. /ready와 GMS URL 검증 — 스펙 그대로 구현하겠습니다

제시하신 계약을 그대로 받겠습니다.

GET /health   정적 200 유지 · liveness/startup 전용
GET /ready    DB SELECT 1 + 현재 profile preset cache ≥ 1건
              200 {"status":"ready"} / 503 {"status":"not_ready"}
              GMS 호출 없음 · credential/endpoint/profile 값 미노출

app.statedb·preset_cache가 이미 붙어 있어(main.py:70-77) 추가 배선 없이 구현됩니다. /internal/ 밖이라 무인증 유지합니다.

GMS_BASE_URL 형식 검증에 한 가지 확인이 필요합니다. startup fail-fast로 넣으면 /gmsapi/ 형식을 만족하지 않는 기존 환경이 전부 기동 실패합니다. 로컬 개발자 환경과 현재 dev에 주입 예정인 값이 이 형식인지 확인해 주시면, 맞을 경우 그대로 fail-fast로 넣겠습니다. 아니라면 형식을 먼저 맞춘 뒤 검증을 켜는 순서로 가야 합니다.

3. smoke command — 만들겠습니다

embedding 1회 + judge 1회 · 한쪽이라도 실패 시 non-zero exit
credential/endpoint/request 원문 미출력 · 안전한 status만
DB mutation 없음

DB 접근 없이 두 클라이언트만 직접 호출하는 독립 모듈로 만들면 됩니다. 다만 get_settings()가 단일 모델이라 실행에는 env 전체가 필요합니다(4번과 동일 제약).

이게 ①에서 말씀드린 비대칭 장애의 유일한 사전 탐지 수단입니다 — 형식 검증이 잡지 못하는 인증 오류·네트워크 도달 실패까지 걸러냅니다.

Infra 확정 사항 — 전부 동의

replica: 1 · terminationGracePeriodSeconds: 40 · ConfigMap 명시 주입 6개 · source default 2개 · 배포 순서 · blocker 제외 범위 모두 이견 없습니다.

terminationGracePeriodSeconds: 40과 함께 **uvicorn --timeout-graceful-shutdown 30**도 컨테이너 command에 넣어 주시면 좋겠습니다. POST /internal/v1/context/process가 202를 즉시 반환하고 실제 처리를 같은 프로세스의 BackgroundTasks로 수행해서, 드레인 시간이 없으면 롤아웃마다 진행 중 Context가 끊깁니다(10분 만료 후 Spring 재스캔이 회수하므로 유실은 아니지만 지연됩니다).

정리

# 항목 주체 상태
1 SealedSecret 산출 workflow AI cert 수령 후 착수
2 /ready + GMS_BASE_URL 검증 AI 착수 (검증 fail-fast는 형식 확인 후)
3 smoke command AI 착수
4 PreSync Job Infra 가능 확인 완료

구현 PR이 나오면 merge 후 immutable image source SHA와 digest를 이 이슈에 전달하겠습니다.

요청 2건 회신 부탁드립니다 — sealed-secrets public cert, 그리고 dev에 주입 예정인 GMS_BASE_URL/gmsapi/ 형식을 만족하는지 여부(값은 알려주지 않으셔도 됩니다, 형식 만족 여부만).

컨테이너 런타임 변경 관련

docker 대신 다른 방식으로 전환하신다고 들었습니다. AI 파트 쪽 변경은 필요하지 않은 것으로 확인했습니다.

Dockerfile이 표준 OCI 빌드만 씁니다 — # syntax= 지시어, --mount=type=cache, heredoc, 멀티스테이지가 모두 없어 어떤 OCI 빌더로도 그대로 빌드됩니다. CI의 manifest 조회도 Accept 헤더에 OCI와 docker manifest를 함께 명시하고 있어 형식 제약이 없습니다. 빌드 자체는 GitHub Actions runner에서 수행되므로 클러스터 런타임과 독립적입니다.

다만 두 가지만 확인 부탁드립니다.

① 기존 이미지를 그대로 쓰시는지, 재빌드하시는지. 빌드 도구가 바뀌면 같은 소스라도 digest가 달라집니다. 저희 CI는 기존 태그가 있으면 HEAD manifest 프리플라이트로 덮어쓰기를 거부하므로, 동일 SHA를 다른 빌더로 재빌드하시면 push가 실패합니다. 이미 발행된 이미지를 그대로 배포하시면 문제없습니다.

imagePullSecret 경로. GHCR 패키지가 private이라 익명 pull이 되지 않습니다. 런타임 교체 시 인증 설정 경로가 달라지니 이 부분만 확인해 주세요.

@tpals0409

Copy link
Copy Markdown
Contributor Author

이정헌님, 확인했습니다. init container 철회와 PreSync Job 적합성 확인 감사합니다. 아래 두 요청과 런타임 질문에 회신드립니다.

1. sealed-secrets public cert 제공

현재 kube-system/sealed-secrets-controller에서 fetch한 public certificate입니다.

  • target namespace/name: pinlog-dev/ai-owner-secrets
  • scope: strict
  • SHA-256 fingerprint: 4A:C5:18:6C:57:94:76:F6:AA:E5:1C:C4:53:9D:4F:11:BB:C1:8F:3E:DF:8B:81:B8:1F:5E:A9:68:1C:5B:C6:A2
  • validity: 2026-07-20 02:16:49 UTC ~ 2036-07-17 02:16:49 UTC
  • private key 포함 없음
-----BEGIN CERTIFICATE-----
MIIEzDCCArSgAwIBAgIQX8b8DCwXjowmEYzMC1P4qjANBgkqhkiG9w0BAQsFADAA
MB4XDTI2MDcyMDAyMTY0OVoXDTM2MDcxNzAyMTY0OVowADCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBAKICruqzpPGWn7I3pQzv15MEvJ6U0uTR4rYFwwXM
ZczOXD+AgTt3mdrzoG2LkHuz/aX8g/ocYq7oh1FxFa40hN97kqwO3jtXy25ITWWn
UIzXjhVhisKuDqH20YHVpbFQ00Z7+sm32+0/zTY7W7DKn9n5kQHUhdZftOWCgRde
jTsukPjqsosWQtxUBvXphFI4aklRROfJQbo927PWGrAfcCWwrlpwFDPE5SZLBFhZ
u1cxT4xxHAquYYO+kGZHdbxA9iCs4Vir+LIMRVw6vqKhaxIOkpC1DTh3gnOyxzRH
2OOV0kn4mBCQetd0RdIm3a0WqKMPmoqTxq9KZODfxed6j+u1vHC0n/i13fgCjNX/
qY7q5SFq3pAeAIIw0uuE6TLHrIXY8PkA6E5GLWAPdPr2eptRQ7Pj8uAyx1CMW/7H
ffssvQhfGESJT8VRACL/n2dO2WYh1xjmXIVT5mvvWZ/Ncxz2oPDGS6w8+RJ6OPRe
YaJTayir2dK8LwbWPK6J2GbRcqBKDLRbrOurLz6srINHK7XDf4PV10uRkl12kDH8
G2mqZYwuk4LrBwEdJSLflh+8NjdfImZom40lL19p8Qf2M3bgX1ZjEkg1+u0UACsT
6x/XT9KcMmZdC5L5hb9ZE/iteSaNdSbACiEa3SBhUiJZMGf9yFaS3MeTN3MyyXjN
RyxnAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIAATAPBgNVHRMBAf8EBTADAQH/MB0G
A1UdDgQWBBRA60VWw2L97u77Z4glqgWOWdsgtjANBgkqhkiG9w0BAQsFAAOCAgEA
coUaIXwvlWpEWcGLCjF19W54uGF2V9JQ0jepOPUk1CE3Wc3ScIm1QMFFUXQMzRJV
hlDH45YgQ2mn+Qzwpf1fXPY4brmJ4ynrieUK8Ota7j/R530kW+DqprdOp4oJgrCh
2yRGtjaDy6bY4B9B9HugZSVhkQ1xWKNtI8D3eAne47YFG/Bq9Gv+tYzYFIeoQTrY
/0esmgPjPHHM8Cqz6BnfSuk7y7nHHCHsGlhJveYGI0t/ULbtwO1PgJAdWP/lnQC+
BeFXBE2D3oM4QucAA5bX4kBHexCebK/tKdPxEJTdLOD4cEA2AAhakhzh0UEKo3IO
TAsgh7A+lh0eMEiyDOdZxNLwMcPOaUH3CCT6Ji27jMZzs/iaoLKsibnyJhGPrTGi
wJBEXBy7ubLYUEzN05pFJFb0vNaZALD76GLq338ePgw4lHYEkmpD0a4FFNjlBZSS
2i0QU+vHG7z+yj2o9xLcsH+6F7KRYTBMFhk/uYNvxSkOv/7kSQNKJKtGFtPtJgM2
7wJUZ7+KlHrhlxoi3AMGwuTf2MvDf8tR+wep3m0nHlTFE6x+3CZPdgYRLdTQcw93
xZE2JXWZiNE4LcRE/Rl1E9Rso16oVk8eTGN0ZzJQAGnR+LgD2nbtEXNeb2913qQl
KXYgDi4sr1p+gCjGu06bmV2dW+CWAQO8v3QfVQGiqZY=
-----END CERTIFICATE-----

workflow에서 이 cert로 **strict scope의 pinlog-dev/ai-owner-secrets**를 생성해 주세요. artifact에는 full SealedSecret manifest 또는 encryptedData만 포함하고 plaintext Secret YAML, CLI trace, env dump는 남기지 않아야 합니다. Infra는 encrypted output만 소비합니다.

2. GMS_BASE_URL 형식

dev 계약은 /gmsapi/ 세그먼트를 포함하는 형식으로 확정합니다. 따라서 startup fail-fast validation을 바로 구현해 주세요.

실제 예정 값은 AI owner가 설계·GitHub Actions Secret에 등록하는 소유 경계라 Infra가 값을 열람해 확인하지 않습니다. 현재 live AI runtime Secret도 없으므로 기존 값과의 호환성 문제는 없습니다. AI owner가 형식에 맞는 값을 등록하고, 새 validation과 embedding+judge smoke 성공으로 증명하는 방식으로 진행하겠습니다.

3. INTERNAL_SHARED_SECRET 생성 주체

AI owner가 생성해 Backend owner에게 비공개 전달하는 방식으로 진행해도 됩니다. 현재 live Backend Secret에는 이 key가 없어 기존 값과의 충돌은 없습니다.

  • AI owner: 값 생성·AI GitHub Secret 등록·회전
  • Backend owner: 전달받은 동일 값을 Backend GitHub Secret에 등록
  • 각 owner workflow: 자신의 target Secret에 대해 별도로 encrypted handoff 생성
  • Infra: 값을 보지 않고 양쪽 encrypted Secret reference만 배포
  • 양쪽 smoke/E2E 불일치 시 activation 중단

PR, Actions log, Jira, Mattermost에는 값이나 부분값을 남기지 말아 주세요.

4. PreSync Job env와 graceful shutdown

확인해 주신 제약을 Infra contract에 반영합니다.

  • PreSync Job에도 ai-owner-secrets, ai-db-credentials, non-secret ConfigMap 전체를 동일하게 주입
  • revision당 Job 1개
  • backoffLimit: 2
  • 성공 exit 0 + OK: 27 presets upserted
  • Config-only sync 재실행 비용은 dev에서 수용하되, 불필요 재실행 빈도를 관찰
  • Deployment에 uvicorn --timeout-graceful-shutdown 30terminationGracePeriodSeconds: 40 적용

5. OCI image와 imagePullSecret

  • Infra는 이미지를 재빌드하지 않습니다.
  • /ready, URL validation, smoke module이 merge된 새 source SHA 이미지를 AI CI가 한 번만 build/publish해 주세요.
  • AI owner가 전달한 immutable source SHA + digest를 Infra가 그대로 pin합니다.
  • cluster runtime은 k3s embedded containerd이며 OCI image를 그대로 pull합니다.
  • private GHCR용 pinlog-dev/ghcr-ai-pull Secret은 live에 존재하고, AI workload에서 명시적으로 참조하겠습니다.

따라서 AI 파트의 다음 deliverable은 다음 3개입니다.

  1. /ready + GMS_BASE_URL fail-fast + 양방향 smoke 구현 PR
  2. merge 후 immutable source SHA와 digest
  3. public cert로 생성한 strict-scope ai-owner-secrets encrypted artifact

전달되면 Infra는 fresh DB backup → pinlog_dev/role/Flyway → PreSync bootstrap → Deployment → embedding/judge smoke → Backend E2E 순서로 진행하겠습니다.

@colosair

Copy link
Copy Markdown
Member

김세민님, 요청하신 §2 /ready+GMS_BASE_URL 검증§3 양방향 smoke를 구현했습니다 — #33 (Draft).

GET /health                        정적 200 유지 · liveness/startup 전용 (변경 없음)
GET /ready                         DB SELECT 1 + 현재 profile preset ≥1건
                                   200 {"status":"ready"} / 503 {"status":"not_ready"}
                                   GMS 미호출 · 무인증 · 값 미노출
GMS_BASE_URL                       /gmsapi/ 세그먼트 없으면 startup fail-fast
python -m app.smoke.gms_roundtrip  embedding 1회 + judge 1회 · 한쪽 실패 시 exit 1
                                   DB 접근 없음 · 안전한 status만 출력

형식 검증은 유예 없이 fail-fast로 넣었습니다. 현재 live AI runtime Secret이 없어 기존 값과의 호환성 문제가 없다고 확인해 주신 대로입니다.

로컬 실측 (실 GMS + 실 pgvector, preset 27건):

항목 결과
/ready 실기동 200 {"status":"ready"}
/health 200 {"status":"ok"} — 형태 불변
smoke 정상 embedding: ok / judge: ok / exit 0
smoke 비대칭 실패(판정 모델만 미존재) embedding: ok / judge: failed (TransientError) / exit 1
URL 세그먼트 누락 기동 중단, 에러 메시지에 값 없음

pytest -q 52 → 66 passed, ruff check . exit 0.

배선 요청

  • readiness probe → GET /ready, startup·liveness probe → GET /health
  • activation 전 컨테이너에서 python -m app.smoke.gms_roundtrip → exit 0 확인
  • smoke도 서버·PreSync Job과 동일하게 env 전체가 필요합니다(get_settings() 단일 Settings 제약, §4와 같은 조건)

주의 — smoke는 실호출이라 CI에 넣지 않았습니다. CI에서는 집계·종료 코드·값 미노출 규약만 스텁으로 검증합니다. GMS 가용성이 CI 성패에 들어오지 않게 하려는 판단이며, 실제 왕복 증명은 배포 절차의 이 명령이 담당합니다.

남은 deliverable 2건#33 병합 후 이 이슈에 전달하겠습니다.

  1. immutable source SHA + image digest
  2. 주신 public cert로 생성한 strict-scope pinlog-dev/ai-owner-secrets encrypted artifact

리뷰에서 특히 확인 부탁드릴 지점은 /ready의 preset 조건 해석입니다 — 캐시가 lifespan에서 현재 profile로 조회한 행만 담으므로 건수 ≥ 1을 곧 "현재 profile 기준 ≥ 1건"으로 판정했고 별도 재조회를 넣지 않았습니다. 계약 문구를 이렇게 읽은 것이 맞는지 봐 주세요.

@colosair

Copy link
Copy Markdown
Member

deliverable ①·② 전달

/ready · GMS_BASE_URL fail-fast · 양방향 smoke 구현이 main에 병합됐습니다 — ai#33.

immutable source SHA와 digest입니다.

source_repository  Team-PinLog/ai
source_sha         7e3139829f52b0ff400122e8b3661cf82c693878
image_repository   ghcr.io/team-pinlog/ai
digest             sha256:3110a5be3993f2d8a0cc1f33753d975b9bc4eda08e364fb685db510e184981ba

ai-image / publish 성공했고 ai-image-provenance 아티팩트(run 30414277607)에서 가져온 값입니다. 이 digest로 pin해 주세요.

구현 결과

GET /ready — 합의하신 계약 그대로입니다.

DB      커넥션 획득 후 SELECT 1
preset  현재 profile 기준 캐시 ≥ 1건
성공    200 {"status": "ready"}
실패    503 {"status": "not_ready"}
GMS     호출하지 않음

응답은 status 한 필드뿐이고 credential·endpoint·profile 값을 어떤 분기에서도 싣지 않습니다. /health는 정적 200 그대로 두었으니 startup·liveness에 쓰시면 됩니다.

GMS_BASE_URL 형식 검증/gmsapi/ 세그먼트가 없으면 기동을 중단합니다.

구현 중 값 노출 경로를 하나 발견해 막았습니다. pydantic의 ValueError로 올리면 ValidationErrorinput_value(원시 입력 dict)를 메시지에 실어 GMS_API_KEY·DATABASE_URL의 앞부분이 기동 로그와 traceback에 남습니다(pydantic 2.13.4 실측). 그래서 pydantic이 가로채지 않는 SettingsError를 별도로 두어 메시지를 완전히 통제했습니다. 배포 파이프라인 로그에 값이 남지 않습니다.

smokepython -m app.smoke.gms_roundtrip

embedding과 judge를 각각 1회 실호출하고 한쪽이라도 실패하면 non-zero exit합니다. 성공 여부와 안전한 status만 출력하며 credential·endpoint·request 원문을 stdout·stderr에 남기지 않습니다. DB mutation은 없습니다.

실행에는 서버와 동일한 env 전체가 필요합니다(get_settings()가 단일 모델을 강제하는 알려진 제약).

남은 것

encrypted artifact는 Actions Secret 등록 후 진행합니다. 주신 public cert로 strict scope pinlog-dev/ai-owner-secrets를 생성하는 workflow를 만들어 전달하겠습니다.

@colosair

Copy link
Copy Markdown
Member

handoff ① 완료 — SealedSecret 산출 경로

김세민님, 요청 ①(GitHub Secret → 암호화 Secret handoff)을 구현해 main에 병합했습니다 — #34, 299f6a6.

/ready·GMS_BASE_URL fail-fast, ③양방향 smoke는 앞서 #33으로 전달드렸으니 네 요청 중 ①②③이 모두 나갔습니다. ④ bootstrap PreSync Job 적합성도 확인 회신드린 상태입니다.

산출 방식

workflow    .github/workflows/seal-ai-secrets.yml   (workflow_dispatch 수동)
인증서       deploy/sealed-secrets/pinlog-dev-cert.pem
산출물       artifact `ai-owner-secrets-sealed` — encryptedData 만, 7일 보관

주신 인증서를 그대로 커밋했고 workflow가 매번 SHA-256 지문을 대조합니다(4A:C5:…:A2). 지문이 다르면 거기서 멈춥니다 — 엉뚱한 공개키로 봉인하면 복호화 실패가 배포 시점에야 드러나기 때문입니다. 만료 검사도 함께 합니다.

평문 Secret YAML은 만들지 않습니다. kubectl create secret --dry-run | kubeseal 경로는 중간 산출물이 평문 base64 YAML이라, kubeseal --raw로 값을 하나씩 봉인해 암호문만 조립했습니다. 평문은 셸 변수와 파이프에만 존재하고 파일·인자·로그 어디에도 남지 않습니다. set -x도 쓰지 않았습니다.

산출물은 업로드 전에 두 방향으로 검사합니다 — 모든 항목이 Ag로 시작하는 100자 이상 암호문인지, 그리고 평문이 새지 않았는지.

봉인 대상 7종 — PINLOG_EMBEDDING_* 넷을 포함했습니다

GMS_BASE_URL · GMS_API_KEY · INTERNAL_SHARED_SECRET
PINLOG_EMBEDDING_MODEL · PINLOG_EMBEDDING_DIMENSION
PINLOG_EMBEDDING_DISTANCE · PINLOG_EMBEDDING_PROFILE

앞선 코멘트에서 EMBEDDING 넷을 *"non-secret ConfigMap 명시 주입"*으로 분류하셨는데, 이번 요구 목록에는 Secret 쪽에 넣으셨습니다. 주입 경로를 하나로 두는 편이 낫다고 보아 Secret 경로로 통일했습니다 — 앱이 이 넷에 기본값을 두지 않아 누락 시 Pod이 아예 뜨지 않는데(app/core/config.py), 경로가 갈리면 그 실패가 두 곳에서 날 수 있습니다.

ConfigMap 쪽이 낫다고 판단하시면 말씀해 주십시오. 값 자체는 비밀이 아니라 어느 쪽이든 무방합니다. 확정 값은 이렇습니다 — 정본은 P32(spec/model-profile.md)입니다.

PINLOG_EMBEDDING_MODEL text-embedding-3-small
PINLOG_EMBEDDING_DIMENSION 1536
PINLOG_EMBEDDING_DISTANCE cosine
PINLOG_EMBEDDING_PROFILE openai-text-embedding-3-small-1536-cosine-v1
PINLOG_JUDGE_MODEL gemini-2.5-flash (코드 기본값 있음, 미주입 가능)

DATABASE_URL은 말씀대로 ai-db-credentials로 따로 두고 envFrom으로 합치는 것으로 이해했습니다.

배포 전 검사를 봉인 시점으로 앞당겼습니다

앱이 기동 시 fail-fast로 거르는 것과 같은 검사를 봉인할 때 미리 돌립니다. 배포하고 Pod이 죽어야 아는 상황을 줄이려는 것입니다.

  • GMS_BASE_URL/gmsapi/ 포함
  • PINLOG_EMBEDDING_DIMENSION이 정수
  • PINLOG_EMBEDDING_PROFILE이 나머지 셋을 부분 문자열로 포함

특히 세 번째가 중요합니다. profile이 어긋난 채 배포되면 기존 임베딩이 조회 대상에서 전부 빠지는데, 검색 결과만 조용히 비어서 배포 후에는 알아채기 가장 어렵습니다.

확인 요청 — PINLOG_AI_INFRA_PR_TOKEN

요구 목록에 주신 값 중 이것만 성격이 다릅니다. AI 앱이 읽지 않습니다ai 레포 전체에서 참조가 0건이고 config.py에도 없습니다.

이름으로 보면 workflow가 GitOps 레포에 PR을 여는 토큰으로 읽히는데, 그렇다면 전달 방식이 지금의 artifact가 아니라 PR이 됩니다. 토큰은 권한을 정하지 않고 발급할 수 없어 넷을 여쭙습니다.

  1. 대상 레포 — 어느 GitOps 레포에 PR을 여는지
  2. 권한 범위contents:write + pull_requests:write면 충분한지 (fine-grained PAT 기준)
  3. 만료·회전 — 기간과 회전 주체
  4. 커밋 경로 — SealedSecret manifest를 그 레포의 어느 경로에 넣는지

답을 주시면 workflow를 artifact 방식에서 PR 방식으로 바꾸겠습니다. 그 전까지는 artifact로 받아 가실 수 있습니다.

아직 확인되지 않은 것

실제 봉인은 아직 돌리지 않았습니다. Actions Secret 등록이 선행이고, 등록 후 수동 트리거로 산출합니다. 지문·인증서 유효기간·kubeseal 릴리스·workflow 문법까지는 확인했지만, 봉인 결과를 controller가 실제로 복호화할 수 있는지는 클러스터에 적용해 보셔야 확정됩니다. 첫 적용에서 실패하면 바로 알려 주십시오.

이 PR(#32)도 main 대비 뒤처져 있는데, 계약 문서 확정이 남으신 것인지 확인 부탁드립니다.

@tpals0409

Copy link
Copy Markdown
Contributor Author

AI owner Secret 7종 등록 후 봉인 workflow를 실제 실행했습니다. Secret 존재/형식 검증과 controller 인증서 지문 검증은 통과했지만, install 단계의 checksum 파일명 불일치로 실패했습니다.

curl -o 대상 파일명을 checksum manifest와 같은 kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz로 맞추고, tar xzf도 같은 변수를 사용한 뒤 workflow를 재실행해 주세요. Secret 값 문제는 아닙니다. Infra 쪽 pinlog_dev/pinlog_ai_dev와 strict ai-db-credentials 준비는 완료했습니다.

@colosair

Copy link
Copy Markdown
Member

고쳤습니다 — 재실행 부탁드립니다 (7d8776a)

김세민님, 정확한 진단 감사합니다. 지목하신 그대로였고 #35로 머지했습니다.

원인

sha256sum -cmanifest 줄에 적힌 이름으로 파일을 엽니다. 인자로 받은 파일을 검사하는 것이 아니라, 표준입력으로 들어온 <해시> <파일명> 줄에서 파일명을 읽어 그 이름을 엽니다. kubeseal.tar.gz로 받아 둔 탓에 그 이름의 파일이 없어 죽었습니다.

파일명을 변수 하나에 담아 다운로드·검증·해제가 같은 값을 쓰게 했습니다. 세 곳이 각자 이름을 적고 있던 것이 원인이라, 한 곳으로 모으는 쪽이 같은 실수를 다시 만들지 않습니다.

tarball="kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
curl -fsSL -o "${tarball}" "${base}/${tarball}"
grep "${tarball}" checksums.txt | sha256sum -c -
tar xzf "${tarball}" kubeseal

이번엔 실제로 돌려서 확인했습니다

다운로드   21,964,120 bytes
체크섬     kubeseal-0.27.1-linux-amd64.tar.gz: OK
해제       kubeseal 추출 성공

원 버전이 보고하신 것과 같은 메시지로 죽는 것도 재현했습니다(No such file or directory / FAILED open or read, exit 1).

놓친 이유를 적어 둡니다. #34 병합 전 검증이 YAML 파싱과 bash -n까지였고 둘 다 문법만 봅니다. sha256sum -c의 동작은 실행해야 드러나는데, 네트워크가 필요한 부분을 로컬에서 돌릴 수 있었음에도 하지 않았습니다. PR에 *"실제 봉인은 미실행"*이라고 한계를 적었지만 그 한계가 설치 단계까지 덮는다는 점을 인지하지 못했습니다.

재실행 부탁드립니다

main(7d8776a)에서 seal-ai-secrets workflow를 다시 돌려 주십시오. Secret은 이미 등록돼 계시니 그대로 통과할 것으로 봅니다.

다음에 막힐 만한 지점을 미리 적습니다. 설치 다음은 봉인이고, 거기서 실패한다면 인증서·--raw 사용법 쪽입니다. 그때도 로그와 함께 알려 주시면 같은 속도로 고치겠습니다. 봉인이 통과해도 controller가 실제로 복호화할 수 있는지는 클러스터 적용까지 가야 확정되므로, 첫 적용에서 문제가 생기면 그것도 알려 주십시오.

pinlog_dev/pinlog_ai_devai-db-credentials 준비 감사합니다.

남은 회신 요청

앞선 코멘트의 PINLOG_AI_INFRA_PR_TOKEN 용도 네 가지(대상 레포·권한 범위·만료·커밋 경로)가 아직 열려 있습니다. 지금은 artifact로 받아 가시면 되고, PR 방식이 필요하시면 그 넷을 알려 주시는 대로 workflow를 바꾸겠습니다.

PINLOG_EMBEDDING_* 넷에 대해 한 가지 예고드립니다. 그 값들은 비밀이 아니고 공용 계약 05_AI_설계.md 7.1 표에 이미 공개돼 있어, 정본을 코드로 옮기고 봉인 대상에서 빼는 계약 개정docs#27로 올려 뒀습니다. 머지되면 봉인 대상이 GMS_BASE_URL·GMS_API_KEY·INTERNAL_SHARED_SECRET 셋으로 줄고, EMBEDDING 넷은 이미지에 이미 들어 있어 주입할 것이 없어집니다. 등록해 두신 Secret은 지우지 않으셔도 되며 workflow가 읽지 않을 뿐입니다. 이 방향에 문제가 있으면 docs#27에 남겨 주십시오.

@colosair

Copy link
Copy Markdown
Member

⚠ 저희가 만든 충돌입니다 — ai-owner-secrets 키 집합

김세민님, infra#77 확인했습니다. 저희 workflow 산출물(run 30431247125)을 받아 적용해 주셨는데, 그 직후 저희가 봉인 대상을 줄여 계약이 어긋났습니다. 먼저 알려 드리지 못한 채 순서가 뒤집힌 점 사과드립니다.

무슨 일이 있었나

07:05  ai#32 에 EMBEDDING 축소를 "예고"만 남김
07:37  infra#77 병합 — ai-owner-secrets 7키로 확정, 저희 artifact 적용
07:52  docs#27 병합 — 05 §7.1 개정 (Profile 정본을 코드로)
07:59  ai#36 병합 — 봉인 대상 7종 → 3종

예고 시점에 이미 진행 중이셨습니다. 계약을 바꾸기 전에 합의를 받았어야 했는데 순서를 지키지 못했습니다.

현재 어긋난 지점

tests/test_ai_dev_prerequisites.pyREQUIRED_RUNTIME_KEYS7키를 정확히 요구합니다 — 누락도 추가도 거부하는 단언이 함께 있습니다.

GMS_API_KEY · GMS_BASE_URL · INTERNAL_SHARED_SECRET
PINLOG_EMBEDDING_MODEL · _DIMENSION · _DISTANCE · _PROFILE

저희 workflow를 지금 다시 돌리면 앞의 3키만 나옵니다. 그러면 그 테스트가 깨집니다.

왜 줄였는지

PINLOG_EMBEDDING_* 넷은 비밀이 아니고, 값이 공용 계약 05 §7.1 표에 이미 공개돼 있습니다. 그런데 배포 설정에만 두면 Profile 교체가 git 이력도 리뷰도 남기지 않습니다 — 그 변경은 기존 임베딩을 전부 조회 대상에서 빼는 결정인데(05 §9.3 검색 필터) 콘솔 편집 한 번으로 가능했습니다.

정본을 코드(app/core/config.py 기본값)로 옮기고 주입을 덮어쓰기로 낮췄습니다. 이제 그 넷은 이미지에 들어 있어 주입이 없어도 Pod이 뜹니다.

두 가지 중 택해 주십시오 — 어느 쪽이든 저희가 맞추겠습니다

(A) 저희가 7키로 되돌린다. workflow를 원복해 EMBEDDING 넷을 다시 봉인합니다. infra#77은 손대지 않으셔도 됩니다. 코드 기본값은 그대로 두므로 Secret 값이 이기고 동작은 같습니다. 가장 빠릅니다.

(B) 인프라가 3키로 좁힌다. REQUIRED_RUNTIME_KEYS에서 EMBEDDING 넷을 빼고, 필요하시면 그 넷을 ConfigMap으로 옮깁니다(암호화가 필요 없는 값입니다). 앞서 *"non-secret ConfigMap 명시 주입"*으로 분류하셨던 것과 같은 자리로 돌아갑니다.

저희 판단은 (B)가 구조상 맞다고 보지만, 지금 배포를 막고 있는 쪽은 저희이므로 (A)를 택하셔도 이견 없습니다. dev 배포를 먼저 띄우고 (B)를 후속으로 미루는 것도 방법입니다. 판단해 주시면 그대로 맞추겠습니다.

확인 부탁드릴 것

저희 workflow가 실제로 통과했는지infra#77 본문의 run 번호로 유추했을 뿐, 직접 확인하지 못했습니다. kubeseal 설치 단계 수정(#35, 7d8776a) 이후 봉인·artifact 산출까지 정상이었다면 그 사실만 알려 주시면 됩니다 — 저희 쪽 미검증 항목이 그것 하나 남아 있었습니다.

ai-db-credentials 분리와 /health·/ready 프로브 분리 반영 감사합니다. application.enabled·bootstrap.enabled·deployment.enabled가 아직 false인 것으로 보이는데, 활성화 전에 저희가 더 준비할 것이 있으면 알려 주십시오.

@colosair

Copy link
Copy Markdown
Member

ai 레포가 main · dev 2단 구조로 바뀌었습니다. 이 PR 에 영향이 있을 수 있어 사실만 전달합니다.

  • dev 브랜치 신설 — 518b2bf (현재 main 과 동일 지점)
  • dev 보호 설정은 main 과 동일: strict ai-ci / check · 미해결 대화 없음 · 관리자 포함 · 승인 0건
  • ai-cimain·dev 양쪽의 PR·push 에서 실행됩니다 (ci(S15P11A705-154): infra action pin·보존 문서·dev 준비를 정합화한다 #40, 518b2bf)
  • image publish 조건은 refs/heads/main 그대로입니다infraai-image-update.yamltest "$SOURCE_BRANCH" = main 으로 어서션하므로 이미지 경로와 GitOps 추적은 바뀌지 않습니다
  • 기본 브랜치를 dev 로 전환하는 것만 남았습니다

이 PR 의 base(main)는 저희가 바꾸지 않았습니다. 타 파트 소유 PR 이라 base 변경은 그쪽 판단입니다. main 대상으로 두어도 병합은 동작합니다.

AI 파트에 담당자가 추가 합류할 예정이라 통합 브랜치를 둔 것이고, 배포·봉인 경로는 변경하지 않았습니다.

@colosair

Copy link
Copy Markdown
Member

S15P11A705-96 은 오늘 완료로 닫았습니다 — 이 PR 은 블로커가 아닙니다

-96 의 완료 조건 5개가 전부 해소된 것을 확인하고 티켓을 닫았습니다. 대조 기록은 docs/implements/2026-07-31-ticket-audit-96-77.md(ai#53, dev 병합)에 있습니다.

계약은 이미 infra 쪽 문서로 실효화돼 있고 dev 배포도 열려 있습니다.

infra docs/ai-serving.md              bootstrap command · PreSync hook · backoffLimit · rollback
infra docs/ai-dev-prerequisites.md    runtime key 8개 · validator
apps/dev/ai/values.yaml               application · deployment · bootstrap  전부 enabled: true

따라서 이 PR 이 병합되지 않아도 AI 파트 쪽에서 막히는 것이 없습니다. 처분은 그쪽 판단에 맡기고 이 사실만 전합니다.

병합하실 경우 — base 를 봐 주십시오

현재  base = main

ai 레포는 CONTRIBUTING.md 기준 통합이 dev, 배포가 main 입니다. main 을 base 로 병합하면 maindev 보다 앞서게 됩니다.

07-30 에 ai#50 이 같은 조건으로 병합돼 main 1커밋 대 dev 7커밋으로 갈라졌고, ai#52 로 되돌리는 작업이 따로 필요했습니다(S15P11A705-183). 문서 한 편이라도 같은 결과가 납니다.

병합하신다면 base 를 dev 로 바꿔 주시면 됩니다.

참고 — 파일 위치

ai 레포 문서는 docs/ 아래 spec/ · proposals/ · implements/ · troubleshooting/ 으로 나뉘고 각 디렉터리에 README.md 색인이 있습니다. 현재 파일은 docs/ 루트에 티켓 번호로 놓여 있어 색인에 잡히지 않습니다. 필수 사항은 아니며, 옮기신다면 계약 문서라 spec/ 이 맞습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants