Azure Pipelines에서 timeout 안에 완료되지 않은 approval은 stage를 실패가 아니라 skipped로 표시한다. skipped stage는 대부분의 대시보드와 알림 템플릿에서 초록색이므로, 아무도 들여다보지 않은 모델 승격이 성공을 보고하는 pipeline 실행을 완료할 수 있다. 변경은 나가지 않았거나, 더 나쁘게는 앞선 stage가 이미 절반을 해 버린 채로 말이다. 8부는 게이트웨이 위에 애플리케이션을 올렸다. 이번 편은 게이트웨이 자체를 반복 가능하게 프로덕션에 넣고, 1부의 모델 은퇴 이메일을 데이터베이스 쿼리로 바꾼다.

실제로 배포되는 것은 무엇인가

세 가지 다른 속도로 변하는 네 개의 아티팩트이고, 그래서 하나가 아니라 세 개의 pipeline이다.

  • 플랫폼 인프라, 2부10-platform에서 온다. API Management, AKS, 네트워크, 관측성. 일 년에 몇 번 바뀌고, 사람의 approval과 함께 배포되며, 아무도 서두르지 않는다.
  • 모델 배포와 게이트웨이 설정, 20-models 레이어에 정책 XML과 alias 맵을 더한 것. 매주 바뀐다. eval gate가 필요한 pipeline이 바로 이것이다.
  • 테넌트 설정, 30-tenants 레이어. 온보딩 때 바뀌고, 소비자 팀의 셀프서비스 pull request가 말이 되는 단 하나의 자리다.
  • AKS 위의 컨트롤 플레인 컨테이너. 평범한 애플리케이션 CI/CD이고, 평범하다는 바로 그 이유로 넷 중 가장 재미없다.

정책 XML은 따로 언급할 가치가 있다. 모든 요청에서 실행되므로 프로덕션 코드이고, Terraform과 같은 리뷰를 거쳐 같은 pipeline으로 프로덕션에 도달한다. 포털은 읽기 위한 것이지 편집하기 위한 것이 아니며, 그것을 지키게 만드는 방법은 배포된 정책이 저장소와 다를 때 실패하는 pipeline 안의 drift check다.

service connection, 그리고 아무도 설정하지 않는 타이머

pipeline은 Azure Resource Manager service connection으로 Azure에 인증하고, 권장 구성은 app registration 또는 managed identity를 쓰는 workload identity federation인데, 이는 시크릿과 시크릿 관리를 그림에서 통째로 지운다. 5부의 논거 전체가 자격 증명 수명주기였던 플랫폼에서, 여기에 저장된 시크릿을 쓰는 것은 변호하기 어렵다.

두 가지 운영 세부 사항은 직접 겪기보다 runbook에 적어 둘 가치가 있다. Azure Pipelines는 100일 동안 사용되지 않은 service connection을 자동으로 비활성화하고, service connection 관리자나 Project Administrator가 다시 활성화해야 한다. 분기별 인프라 변경에만 도는 pipeline이 정확히 여기에 걸리는 pipeline이고, 실패는 최악의 시점에, 급한 무언가를 배포해야 할 때 도착한다. 그리고 시크릿에서 federation으로 전환한 service connection은 7일 동안 되돌릴 수 있다. 그 뒤에는 새 시크릿을 수동으로 만들어야 하고, 전환한 뒤 수동으로 변경한 connection은 도구로는 아예 되돌릴 수 없다.

eval gate는 job이 아니라 environment에 속한다

본능은 deploy job의 끝에 eval 단계를 추가하는 것이다. 그렇게 하면 eval은 pipeline이 하는 일이 되고, pipeline 작성자가 순서를 바꾸거나, condition으로 건너뛰거나, 게이트로 막았어야 할 배포 뒤로 옮길 수 있게 된다.

Azure Pipelines에는 올바른 원시 요소가 있다. environment, 또는 stage가 소비하는 service connection에 거는 check다. stage가 실행되기 전에 그 stage가 쓰는 모든 리소스의 모든 check가 충족되어야 하고, 부정 결정 하나면 stage는 거부된다. eval을 컨트롤 플레인의 eval 서비스를 호출하는 Invoke REST API check로 구성하면, 게이트는 pipeline 정의에 속하기를 멈추고 environment에 속하기 시작한다.

여기서 최종성 규칙이 중요하다. approval과 대부분의 check가 내린 결정은 최종이다. Invoke Azure Function과 Invoke REST API check는 예외라서 재실행될 수 있고, 0이 아닌 time between evaluations를 설정하면 check의 결정은 비최종이 되어 재평가된다. eval gate에는 이것이 세부 사항이 아니라 설계 선택이다. 계속 다시 도는 비최종 check는 "이 stage가 마침내 시작될 때에도 eval이 여전히 통과 중이어야 한다"에 맞고, 최종 check는 "이 빌드는 eval을 통과했으니 내보내라"에 맞다. 하나를 고르고, 권장 방식으로 구성한 비동기 형태는 최종이라는 것을 알아 두라.

eval 위에 사람의 approval을 프로덕션 environment에 더하고, 승인자가 자신의 실행을 승인하지 못하게 제한하는 옵션을 켜라. 그리고 timeout은 의도적으로 설정하라. 이 글의 첫 문장이 그 이유다. 만료된 approval은 stage를 skipped로 표시하고, skipped는 초록색으로 읽힌다. 완화책은 짧은 timeout, skipped stage를 특정해서 거는 알림, 그리고 초록색 pipeline을 배포가 일어났다는 증거로 절대 취급하지 않는 것이다.

eval이 실제로 검사하는 것

플랫폼의 소비자들이 신경 쓰는 케이스들의 golden set이다. 정책 XML 옆에 저장소에서 버전 관리되고, 모델 엔드포인트가 아니라 게이트웨이를 통해 후보 설정에 대해 실행된다. 마지막 지점이 이것을 모델 벤치마크가 아니라 플랫폼 eval로 만든다. alias 해석, 정책들, content safety 필터, retrieval 경로를 모두 통과시키는데, 이 모든 것은 모델이 전혀 바뀌지 않아도 응답을 망가뜨릴 수 있다.

임계값 세 개, 그리고 사람들이 건너뛰는 것은 세 번째다. golden set에 대한 종합 품질 점수. 지연 시간 예산. 약간 더 좋으면서 의미 있게 더 느린 모델은 대화형 어시스턴트에게 업그레이드가 아니기 때문이다. 그리고 회귀 목록. 평균과 무관하게 나빠져서는 안 되는 특정 케이스들이다. 평균은 개선하면서 고객 서비스 팀이 의존하는 프롬프트 세 개를 망가뜨리는 새 모델 버전이 나쁜 승격의 일반적인 모양이고, 회귀 목록만이 그것을 잡는다.

승격, canary, rollback은 모두 하나의 메커니즘이다

3부가 backend pool 앞에 alias를 두었기 때문에, 모델 버전 변경은 어떤 애플리케이션도 건드리지 않는다. 세 가지 모양을 가진 설정 변경일 뿐이다:

  • Canary: 같은 우선순위 그룹 안에서 낮은 가중치로 새 배포를 alias의 pool에 추가한다. 트래픽의 작은 몫이 그쪽으로 가고, 5부의 메트릭이 모델 alias별 지연 시간과 token 차이를 보여 주며, 아무것도 재배포할 필요가 없었다.
  • 승격: 가중치를 옮기고, 그다음 오래된 배포를 pool에서 제거한다.
  • Rollback: 반대 방향의 가중치 변경. 재배포가 아니라 몇 초이고, 이것이 alias가 존재하는 논거의 전부다.

이것이 해 주지 않는 한 가지는 canary 경계를 넘어 응답을 동일하게 만드는 일이다. 같은 alias를 서빙하는 두 모델 버전은 같은 프롬프트에 다른 출력을 내므로, 응답을 캐시하거나 비교하거나 재생하는 모든 것은 어느 버전이 답했는지 알아야 한다. 그것은 로그 필드이고, 나중이 아니라 canary와 같은 시점에 추가한다.

은퇴, 이제는 쿼리다

1부의 은퇴 이메일은 저장소를 grep하는 이틀을 비용으로 치렀다. 이제는 쿼리 하나면 되어야 하는데, 앞선 세 가지 결정이 답을 알 수 있는 것으로 만들었기 때문이다. 2부는 모델 버전을 고정하고 version_upgrade_option을 의도적으로 설정했으므로 어떤 배포도 스스로 움직이지 않는다. 3부는 모든 애플리케이션을 alias 뒤에 두었으므로 alias에서 배포로의 매핑은 데이터다. 5부는 모든 요청을 테넌트와 해석된 모델과 함께 로깅했으므로, 지난 30일 동안 그 배포를 실제로 쓴 테넌트 집합은 가정이 아니라 로그 쿼리다.

그러면 은퇴 runbook은 티켓 하나에 들어가는 네 단계다. 최근에 그 배포를 쓴 테넌트를 쿼리한다. 대체 배포를 세우고 그에 대해 eval gate를 돌린다. alias를 canary하고 회귀 목록을 지켜본다. 뒤집고 제거한다. 애플리케이션은 이 중 무엇이 일어났는지 끝내 알지 못하는데, 그것이 이 시리즈 전체가 목표한 결과다.

지켜봐야 할 실패 모드

  • 성공으로 읽힌 skipped. 만료된 approval은 stage를 건너뛴다. skipped 상태에 대해 명시적으로 알림을 걸어라. pipeline 알림에도, 팀이 실제로 보는 대시보드에도.
  • job 단계로서의 eval. 순서를 바꾸거나 condition으로 치워 버릴 수 있다면 그것은 문서다. environment check로서는 게이트다.
  • 가시성 없는 우회. check를 우회하려면 리소스에 대한 관리자 권한이 필요하고 누가 했는지와 함께 기록되는데, 이것은 좋은 일이고, checks 패널에 남겨 두기보다 주간 리뷰에서 드러낼 가치가 있다.
  • 100일째에 비활성화된 service connection. 분기별 인프라 pipeline은 인시던트 도중에 그것을 발견한다.
  • 포털에서의 정책 편집. drift check가 없으면 포털에서 만든 핫픽스는 다음 배포가 조용히 그것을 되돌릴 때까지 살아남는데, 이는 최악의 타이밍이다.
  • 아무도 갱신하지 않는 eval set. 일 년 동안 바뀌지 않은 golden set은 작년의 제품을 측정하고 있다.

10부가 물려받는 것

pipeline을 통해 나가는 게이트웨이, 게이트가 된 eval, 설정이 된 모델 변경이다. 시리즈의 모든 메커니즘이 이제 자리를 잡았고, 그 모두가 무언가를 내보낸다. 마지막 편은 그것으로 무엇을 할지에 관한 이야기다. trace, pipeline이 아니라 프로덕션에서의 eval, EU AI Act가 묻는 거버넌스 질문들, 그리고 1부가 약속한 숫자들이다.

다음으로 읽을 글

이것을 대규모로 운영할 때의 인프라와 플랫폼 쪽 이야기는 ercan.cloud에, 허브는 ercanermis.com에 있다.

참고 자료