모든 팀에게 AWS 키를 나눠줄 뻔했습니다. 그래서 대신 LLM 게이트웨이를 구축했습니다.
요약
팀 내 LLM 접근 권한 관리의 위험성을 지적하며, AWS 키 직접 공유 대신 LLM 게이트웨이를 구축해야 하는 이유를 설명합니다. 보안, 비용 추적, 권한 제어 측면에서 게이트웨이가 제공하는 이점을 다룹니다.
핵심 포인트
- AWS IAM 키 직접 공유는 보안 및 키 교체 관리의 어려움을 초래함
- 공유 키 방식은 개별 사용자의 사용량 추적 및 비용 통제가 불가능함
- LLM 게이트웨이는 토큰 단위의 제어와 상세한 사용 기록을 제공함
- 데이터 보안이 중요한 경우 SaaS 대신 자체 구축형 게이트웨이가 권장됨
몇 달 전, 현재 많은 인프라 담당자들이 겪고 있는 요청이 제 책상에 놓였습니다: "XYZ (비기술) 팀들이 우리 LLM을 기반으로 무언가를 만들고 싶어 합니다. 그들에게 Bedrock에 대한 액세스 권한을 줄 수 있나요?"
서류상으로는 충분히 간단해 보였습니다. AWS Bedrock은 이미 우리 계정에 있었고, 모델들은 활성화되어 있었으며, IAM을 통해 권한을 부여할 수 있었습니다. 10분이면 끝날 작업이었죠.
하지만 실제로는 10분짜리 작업이 아니며, 만약 그렇게 생각하고 접근한다면 약 3주 후에 후회하게 될 것입니다.
이를 수행하는 두 가지 명백한 방법
기본적으로 두 가지 방법이 있는데, 첫 번째 사용자 이후를 생각하면 둘 다 좋지 않습니다.
첫 번째 옵션은 Bedrock을 호출할 수 있는 IAM 자격 증명(credentials) 또는 역할(role)을 나눠주는 것입니다. 한 사람에게는 괜찮습니다. 하지만 이를 몇 번 반복하면, .env 파일에 AWS 키가 살아있고, 여기에는 CI 변수가, 저기에는 누군가의 Postman 컬렉션이, 어딘가에는 당신이 절대 알 수 없는 노트북(notebook)이 존재하게 됩니다. 이 중 하나라도 교체(rotating)하려면 그것이 떨어진 모든 곳을 찾아내야 합니다. 또한 IAM 권한은 의도했던 것보다 더 광범위해지는 경향이 있습니다. 누군가 Bedrock을 호출할 수 있게 하는 정책은 보통 다른 몇 가지 기능에도 접근할 수 있게 합니다. 왜냐하면 아무도 첫날에 이 권한을 아주 좁게 설정하지 않으며, 그 '첫날'이 1년 전의 일이 되어버리기 때문입니다.
두 번째 옵션은 내부 엔드포인트(internal endpoint) 뒤에 하나의 공유 키를 두고, 모두가 그곳을 가리키게 하는 것입니다. 구축하기는 더 빠릅니다. 하지만 이제 모든 요청이 동일하게 보이기 때문에, 누구의 코드가 무엇을 소모하고 있는지 알 수 없습니다. 단 하나의 잘못된 루프(loop)가 점심시간 전에 한 달 예산을 다 써버릴 수 있으며, 당신은 어느 팀이 그것을 배포했는지 알 수 없습니다. 그리고 모든 사람을 위해 키를 교체하지 않고서는 특정 한 사람의 접근만 차단할 수 없습니다.
두 방법 모두 두세 명 이상의 사람이 관여하는 순간 무너집니다. 그리고 가장 뼈아픈 부분은, 결국 재무팀에서 왜 AI 비용 항목이 급증했는지 물었을 때, 당신이 그들에게 보여줄 수 있는 것이 아무것도 없다는 점입니다.
이것은 대부분 해결된 문제입니다
이제는 사람들이 AI 게이트웨이(AI gateways) 또는 LLM 게이트웨이(LLM gateways)라고 부르는 도구들이 하나의 카테고리로 자리 잡았습니다. LiteLLM, Portkey, Helicone, Cloudflare의 AI Gateway, 그리고 그 외 수많은 도구들이 있습니다. 이들은 모두 여러분의 애플리케이션과 모델 제공자(model provider) 사이에 위치하며, 여러분이 원하는 두 가지를 제공합니다. 바로 누가 무엇을 호출할 수 있는지에 대한 제어권(control)과 모든 사용자가 무엇을 소비했는지에 대한 기록입니다. 일반적인 API 게이트웨이(API gateway)와 동일한 개념이지만, 토큰(tokens)을 대상으로 한다는 점이 다릅니다.
저는 한동안 호스팅형 서비스들을 살펴보았고, 대부분의 팀에게는 하나를 선택해서 바로 진행하라고 권하고 싶습니다. 하지만 저희는 그렇게 하지 않았습니다. 그 이유는 그리 화려하지 않습니다. 저희는 핀테크(fintech) 기업이며, 사용량 그래프를 얻기 위해 프롬프트(prompts)와 컴플리션(completions)—그중 일부는 저희 네트워크를 벗어나기를 원치 않는 데이터와 접촉하는 것들—를 타사의 SaaS를 통해 라우팅하는 것은 제가 감수할 수 없는 거래였습니다. 저는 이 모든 것이 저희만의 경계(perimeter) 내부에 있고, Bedrock과 직접 통신하며, 로그는 저희가 소유한 데이터베이스에 저장되기를 원했습니다. 그래서 저는 작은 버전을 직접 구축했습니다.
한 가지 규칙: 아무도 AWS 자격 증명을 받지 않습니다
전체 설계는 단 하나의 결정으로 요약되며, 이는 매우 단호합니다. 아무도 AWS 자격 증명(AWS credentials)을 받지 않습니다. 개발자도, 서비스도, 게이트웨이 자체를 제외한 그 무엇도 받지 않습니다.
대신 사람들이 받는 것은 게이트웨이가 만들어낸 접두사가 붙은 토큰(prefixed token)입니다. 예를 들어 llmkey_9f3c…와 같이 AWS는 들어본 적도 없는 형태입니다. 이를 애플리케이션에 넣고, 클라이언트가 Bedrock 엔드포인트(endpoint) 대신 게이트웨이의 URL을 가리키도록 설정하면 끝납니다. 실제 AWS 자격 증명이 존재하는 유일한 곳은 게이트웨이 프로세스의 환경 변수(environment variables)뿐이며, 이는 절대 외부로 복사되지 않습니다.
그 외의 모든 것은 그 하위 단계의 문제입니다. 여러분이 발급한 키를 지닌 요청이 나타나면, 누가 호출하고 있는지 알 수 있습니다. 그리고 누가 호출하는지 알게 되면, 요청을 거부하기 시작할 수 있습니다.
키별로 제한할 수 있는 것들
몇 가지 사항이 있으며, 이는 키별로 설정됩니다.
특정 키가 접근할 수 있는 모델을 고정할 수 있습니다. 대부분의 내부 도구는 최상위 모델(top-tier model)을 사용할 필요가 없습니다. 티켓 요약기(ticket summarizer)는 작고 빠른 모델로도 충분히 잘 작동하며, 실수로 혹은 누군가 어느 날 오후에 호기심이 생겼다는 이유로 해당 키가 몇 배나 더 비싼 모델을 호출하도록 허용할 이유는 없습니다.
각 키에 토큰 예산(token budget)을 할당하고 모든 호출에 대해 이를 차감할 수 있습니다. 거창한 것은 아니며, 단순히 숫자와 누적 합계만 관리합니다. 키가 한계치에 다다르고 있으면 문제가 되기 전에 대시보드에서 확인할 수 있고, 한계치에 도달하면 중단됩니다. 이 기능 하나만으로도 밤잠을 설치게 만드는 원인인 '폭주하는 스크립트(runaway-script)' 시나리오를 제거할 수 있습니다.
또한 키에는 이름과 소유자가 부여됩니다. 따라서 사용량을 살펴볼 때, 밤 11시에 추적해야 하는 무작위 문자열이 아니라 "내부 분석 작업" 또는 실제 인물을 보게 됩니다.
요청 경로 (The request path)
호출이 이루어지는 과정은 다음과 같습니다. 그리 대단한 것은 아닙니다:
- 게이트웨이 키(gateway key)와 함께 요청이 들어옵니다.
- 키를 조회합니다. 활성화 상태인가? 요청한 모델을 사용할 권한이 있는가? 남은 예산이 있는가?
- 위 사항 중 하나라도 '아니오'라면, AWS에 단 1센트도 쓰기 전에 그 자리에서 즉시 거부합니다.
- 모든 확인이 완료되면, 실제 인증 정보(credentials)를 사용하여 Bedrock으로 전달합니다.
- 응답이 돌아오면, 응답에서 토큰 수(token counts)를 추출하여 사용 로그에 행을 기록하고, 해당 키의 누적 합계를 업데이트합니다.
5단계는 나중에 모든 보고(reporting)가 가능하게 만드는 핵심 이유입니다. 모든 호출은 입력 토큰, 출력 토큰, 지연 시간(latency), 모델, 비용을 포함한 하나의 행이 됩니다. 앱의 모든 화면은 단순히 그 하나의 테이블에 대한 쿼리일 뿐입니다.
핵심만 요약하자면, 중간의 확인 과정은 다음과 같이 간단합니다:
def handle_request(gateway_key, model_id, prompt):
key = keys.lookup(gateway_key)
...
비용 계산하기 (Working out the cost)
비용(Cost)은 사람들이 까다로울 것이라고 예상하는 부분입니다. 하지만 가격을 코드에 직접 박아넣지만(bake) 않는다면 까다롭지 않습니다. Bedrock은 입력(input)에 대해 1K당 요율을, 출력(output)에 대해 또 다른 1K당 요율을 부과하며, 이 요율은 모델마다 다르고 시간이 지남에 따라 변합니다. 따라서 이 값들은 설정(settings)에 저장하며, 비용 함수(cost function)는 단순히 이를 읽어옵니다:
def price(model_id, input_tokens, output_tokens):
rate = pricing[model_id] # 1K당 요율, 설정에서 수정 가능
return (input_tokens / 1000 * rate.input_price
...
이 작업은 매 요청(request)마다 실행되어 저장되기 때문에, 나중에 아무도 지출 내역을 재구성할 필요가 없습니다. 모델별, 키(key)별, 혹은 원하는 기간별로 데이터를 분리하여 분석할 수 있습니다. 처음 이 작업을 수행했을 때 한 개의 키가 한 달 비용의 대부분을 차지하고 있다는 것을 발견했는데, 이는 일주일 동안 추측하는 대신 해당 키를 소유한 팀과 5분간 대화하는 것으로 끝났습니다.
대시보드(Dashboards), 그리고 누가 이를 만질 수 있는가
해당 로그 테이블(log table) 위에 있는 부분은 모두가 가장 먼저 확인하는 부분입니다. 즉, 시간에 따른 사용량, 어떤 모델이 집중적으로 사용되는지, 기간별 비용, 지출액 기준 상위 키 순위 등입니다. 관측성(observability) 대시보드를 본 적이 있다면 그와 비슷해 보일 것입니다. 다만 축(axes)이 토큰(tokens)과 돈(money)일 뿐입니다.
한 가지 예상보다 더 중요했던 점이 있습니다. 이 데이터를 확인해야 하는 사람들이 항상 이를 변경할 권한이 있는 사람들은 아니라는 사실입니다. 재무팀 담당자나 팀 리더는 수치를 확인하고 싶어 합니다. 하지만 그들이 키를 생성하거나 가격 테이블을 수정할 이유는 없습니다. 그래서 역할(roles)을 나누었습니다. 모든 것을 할 수 있는 전체 관리자(full admins)와, 오직 보기만 할 수 있는 읽기 전용 계정(read-only accounts)입니다. 작은 기능이었지만, 한 번 이상 어색한 상황을 모면하게 해주었습니다.
내가 실수했던 것들
예상치 못했거나, 예상했지만 무시했던 몇 가지 사항들입니다:
- 스트리밍(Streaming)은 깔끔한 버전을 망쳐놓습니다 Completion을 스트리밍하면 사전에 대기 중인 깔끔한 Usage 객체가 없습니다. 토큰이 도착할 때마다 개수를 세거나, Bedrock이 맨 마지막에 보내주는 메타데이터를 가져와야 합니다. 저는 이를 사후에 수정(retrofit)했는데, 처음부터 제대로 만드는 것보다 훨씬 더 고통스러웠습니다.
- 키당 단일 수명 예산(lifetime budget)은 괜찮아 보이지만, 그렇지 않습니다 누군가 실제로 더 많은 예산이 필요해지는 순간까지는 버팁니다. 하지만 그 시점이 되면 며칠마다 수동으로 제한을 수정해야 하며, 처음부터 월간 초기화(monthly resets) 기능이나 충전(top-up) 경로를 구축해두지 않은 것을 후회하게 됩니다.
- 로그 테이블은 예상보다 빠르게 커집니다 요청당 한 행(row)은 수백만 행이 되기 전까지는 아무것도 아닙니다. 급하게 처리하기 전에 오래된 데이터를 아카이빙(archiving)하거나 롤업(rolling up)할 대략적인 계획을 세워두세요.
- 비용은 전체 정밀도(full precision)를 유지하세요 요청당 비용은 1센트의 아주 작은 조각입니다. 중간에 반올림을 해버리면 월간 총액이 틀려지는데, 그 차이가 누군가 결국 눈치채고 시스템 전체를 신뢰하지 않게 될 정도로 딱 미묘한 수준이 됩니다.
이 중 어려운 것은 하나도 없습니다. 이들은 처음에 조금 잘못 다뤄봄으로써만 진정으로 배울 수 있는 종류의 것들입니다.
구축할 가치가 있었을까요?
저희에게는, 네, 있었습니다. 똑똑해서가 아니라(사실 똑똑하지도 않습니다), 데이터베이스가 결합된 리버스 프록시(reverse proxy)일 뿐이니까요. 가치가 있는 이유는 매일매일 변한 모습 때문입니다. 누군가 Bedrock 접근 권한을 원하면, 저는 키를 생성해줍니다. 끝입니다. IAM 티켓은 필요 없습니다. 실제 자격 증명(credential)은 단 한 번도 게이트웨이를 벗어난 적이 없습니다. 키 하나가 오작동하면, 그 키만 취소(revoke)하면 되고 다른 사람들은 계속 작업을 이어갈 수 있습니다.
그리고 이제는 끊임없이 발생하는 AI 지출 문제가 거론될 때, 저는 스프레드시트 대신 페이지 하나를 엽니다.
마지막 항목이 제가 가장 강력하게 밀어붙이고 싶은 부분입니다. 제가 대화하는 모든 회사가 지금 비슷한 종류의 고민을 하고 있습니다. 누가 이 모델들을 사용하는지, 무엇에 사용하는지, 비용은 얼마인지, 그리고 이 모든 것이 거버넌스(governed) 하에 있는지 말입니다. 여러분은 작성하자마자 일주일 만에 구식이 되어버릴 정책 문서로 이에 답할 수도 있고, 인프라가 스스로 답하게 할 수도 있습니다. 저는 어느 쪽이 최신 상태를 유지할지 알고 있습니다.
만약 여러분도 방금 "모든 팀을 우리 LLM(Large Language Models)에 연결하라"는 요청을 받았다면, 제가 드릴 수 있는 조언은 단 하나입니다. 키(keys)를 함부로 배포하지 마세요. 먼저 Bedrock 앞에 작은 무언가를 구축하세요. 주말 정도의 작업 시간이 걸리겠지만, 나중에 정리하기 정말 고통스러운 난장판으로부터 여러분을 구해줄 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기