콘텐츠로 이동
Study Notekagent · kmcp

GitOps PR 승인 배포

결론부터
  • kagent의 배포 단위는 전부 Kubernetes manifest라서 “Git에 manifest를 넣으면 배포된다”는 GitOps 흐름에 그대로 올라간다.
  • backend는 workflow를 트리거만 하고 클러스터에 쓰지 않는다. 클러스터에 쓰는 것은 merge된 Git을 따라가는 GitOps 도구뿐이다.
  • PR의 diff가 Agent의 능력 변화다. 승인자는 image digest, toolNames, requireApproval, allowedHeaders, Secret 참조를 본다.
  • 승인의 강도는 kagent가 아니라 저장소 보호 규칙이 정한다. merge 권한과 workflow 파일을 보호하지 않으면 승인은 형식이 된다.
  • 배포 결과는 리소스의 status.conditions로 읽어 portal에 되돌린다.
이 장에서 처음 나오는 말5개
GitOps
Git 저장소의 내용을 클러스터의 원하는 상태로 삼고, 전용 도구가 클러스터를 Git에 계속 맞추는 운영 방식이다.
GitHub App
사람 계정 대신 저장소에 접근하는 app 신원이다. 저장소별·권한별로 좁힌 짧은 수명의 token을 발급받는다.
workflow_dispatch
GitHub Actions workflow를 API 호출로 직접 실행시키는 trigger다. 입력값을 함께 넘길 수 있다.
image digest
image 내용의 hash(sha256:...)다. tag와 달리 같은 digest는 항상 같은 내용을 가리킨다.
drift
Git에 적힌 상태와 클러스터의 실제 상태가 어긋난 것이다.

이 덱이 가정하는 배포 방식은 이렇다. 임직원이 portal에서 Agent 배포를 요청하면 우리 backend가 GitOps 저장소의 workflow를 트리거한다. workflow는 개발자 저장소 읽기 권한을 가진 GitHub App으로 source를 읽고, manifest를 만들어 PR을 올린다. 그 PR이 merge되면 승인된 것으로 보고 배포가 진행된다.

이 페이지는 그 흐름의 각 단계가 kagent의 무엇에 대응하는지, PR에 무엇이 담기고 승인자가 무엇을 봐야 하는지를 정리한다.

portal 요청부터 backend의 workflow 트리거, manifest PR 생성, 승인자 merge, GitOps 도구의 sync, kagent controller의 배포, status 회수까지의 순서
단계누가kagent·kmcp와의 관계
① ②backend없음. 요청을 기록하고 GitHub API로 workflow를 실행시킨다
③ ④ ⑤workflowsource를 읽고(③) manifest를 만들어 검사한 뒤(④) PR을 올린다(⑤). manifest 생성에 kagent·kmcp CLI의 --dry-run을 쓸 수 있다
⑥승인자없음. 저장소의 branch 보호 규칙이 승인 조건을 정한다
⑦ ⑧GitOps 도구merge된 commit을 감지해(⑦) Agent·MCPServer 등을 Kubernetes API에 적용한다(⑧) — 선언 경로
⑨controller리소스를 Deployment·Service로 바꾸고 status.conditions를 쓴다
⑩backendstatus를 읽기 전용으로 조회해 portal에 표시한다. 이후 호출은 A2A — 호출 경로

이 방식의 핵심은 쓰기 권한이 한 줄로만 이어진다는 점이다.

주체가진 권한갖지 않는 권한
backendworkflow 실행 요청. 클러스터의 Agent·MCPServer 읽기. A2A 호출GitOps 저장소 쓰기, 클러스터 쓰기
workflow개발자 저장소 읽기(GitHub App). GitOps 저장소에 branch와 PR 생성보호된 branch에 직접 push, merge
승인자PR merge—
GitOps 도구클러스터 쓰기Git에 없는 내용의 적용
kagent controller감시하는 namespace 안의 Deployment·Service 생성—

backend가 침해되어도 공격자가 얻는 것은 “PR을 만들게 하는 능력”까지다. 승인 없이는 클러스터가 바뀌지 않는다. 사내 Agent 배포 플랫폼 덱의 연결 설계는 backend가 Kubernetes API에 직접 apply하는 구조였다. 그 구조에서는 backend의 ServiceAccount가 곧 배포 권한이었다.

workflow의 ④ 단계는 Agent의 작성 방식에 따라 다르다.

대상개발자 저장소에 있는 것workflow가 하는 일
Declarative Agent지시문, 원하는 tool 이름, 설명우리 template에 값을 채워 Agent manifest를 만든다
BYO Agentsource와 Dockerfileimage를 build하거나 이미 build된 image의 digest를 확인해 Agent manifest를 만든다
MCP 서버 (kmcp)kmcp projectimage digest를 확인해 MCPServer manifest를 만든다

CLI는 클러스터에 적용하지 않고 manifest만 출력하는 옵션을 제공한다. 공식 CLI 문서가 GitOps 용도라고 명시한 옵션이다(kagent deploy, kagent mcp deploy).

터미널 창
# 설명용 — workflow 안에서 MCPServer manifest만 만든다
kmcp deploy --file ./mcp/kmcp.yaml --image "$IMAGE@$DIGEST" --transport http \
--namespace agents --dry-run --output mcpserver.yaml

kagent deploy --dry-run도 Agent manifest를 출력하지만 --env-file이 필수이고, 그 파일의 값으로 Secret manifest까지 함께 만든다. BYO Agent의 manifest는 필드가 적으므로 우리 template으로 직접 만드는 편이 Secret이 섞일 위험이 없다.

지켜야 할 것이 셋 있다.

  • image는 digest로 고정한다. tag는 같은 이름으로 다른 내용을 가리킬 수 있어서, 승인된 PR이 바뀌지 않았는데 실행되는 내용이 바뀔 수 있다. source는 요청 시점의 commit SHA로 읽는다.
  • Secret 값을 PR에 넣지 않는다. manifest에는 Secret의 이름만 두고, 값은 secret 저장소에서 클러스터로 직접 가져오게 한다.
  • 개발자의 입력을 그대로 manifest로 옮기지 않는다. 개발자가 정할 수 있는 필드(지시문, 설명, 요청하는 tool)와 플랫폼이 정하는 필드(namespace, modelConfig, runtime, label, 자원 한도)를 template에서 나눈다.
  • 디렉터리gitops/
    • 디렉터리platform/
      • 디렉터리kagent/
        • values.yaml kagent chart values
        • 디렉터리model-configs/
          • litellm-default.yaml 플랫폼 팀이 소유
    • 디렉터리agents/ Agent namespace
      • 디렉터리hr-helper/
        • agent.yaml Agent
        • mcpserver.yaml MCPServer — Agent 전용 tool 서버가 있을 때
        • externalsecret.yaml secret 저장소에서 가져올 값의 이름
        • kustomization.yaml
      • 디렉터리contract-reviewer/
        • agent.yaml

플랫폼 설정과 Agent를 다른 경로에 두면 승인자를 경로별로 나눌 수 있다. platform/은 플랫폼 팀만, agents/는 Agent 승인자가 merge하게 한다.

# 설명용 예제 — agents/hr-helper/agent.yaml
apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: hr-helper
namespace: agents
labels:
platform.example.com/agent-id: "a-1042"
platform.example.com/source-commit: "3f9c2e1"
spec:
description: 휴가·근태 규정을 안내하고 본인 신청 내역을 조회한다
type: Declarative
declarative:
runtime: go
modelConfig: litellm-default
systemMessage: |
너는 인사 규정을 안내하는 Agent다.
tools:
- type: McpServer
mcpServer:
name: portal-api-mcp
kind: MCPServer
toolNames:
- get_my_leave_requests
allowedHeaders:
- Authorization

label에 portal의 Agent ID와 source commit을 남기면 backend가 status를 읽을 때 “어느 요청의 결과인지”를 리소스에서 바로 대조할 수 있다. portal DB의 배포 기록에는 PR 주소와 merge commit을 함께 저장한다.

merge가 승인이므로 승인자는 이 diff가 Agent에게 무엇을 허용하는가를 읽어야 한다.

바뀐 곳뜻확인할 것
image의 digest실행되는 코드가 바뀐다어느 저장소의 어느 commit에서 만들어졌는가
toolNames 추가Agent가 할 수 있는 일이 늘어난다변경·삭제 tool인가. requireApproval이 필요한가
requireApproval에서 삭제사람 승인 없이 실행된다의도된 변경인가
allowedHeaders 추가임직원 토큰이 그 tool 서버로 전달된다그 서버를 누가 운영하는가. 토큰을 받아도 되는 서버인가
RemoteMCPServer의 urltool 호출이 그 주소로 나간다사내 주소인가, 외부인가
mcpServer.namespace다른 namespace의 tool 서버를 쓴다그 서버가 이 namespace를 허용했는가
headersFrom · secretRefs · env의 Secret 이름그 자격을 Agent·서버가 쓴다그 Secret의 권한 범위
modelConfig다른 모델·key를 쓴다허용된 ModelConfig인가
systemMessage행동 규칙이 바뀐다안전 규칙 조각이 빠지지 않았는가

사람이 매번 다 볼 수는 없으므로 workflow의 ④ 단계에서 기계가 먼저 거른다. 예를 들면 다음과 같다.

  • CRD schema에 맞는 manifest인가(kubectl apply --dry-run=server 등).
  • image가 digest로 고정되어 있고 허용된 registry인가.
  • namespace와 modelConfig가 허용 목록 안에 있는가.
  • allowedHeaders를 쓰는 tool 서버가 승인된 서버 목록에 있는가.
  • Secret 값이 포함되지 않았는가.

검사를 통과하지 못한 PR은 merge할 수 없게 branch 보호 규칙에 묶는다.

GitOps 도구가 manifest를 적용하면 controller가 reconcile하고 결과를 status에 쓴다. backend는 이 값을 읽어 portal의 상태를 바꾼다.

portal에 보일 상태근거
승인 대기PR이 열려 있다
배포 중PR이 merge됐다. 리소스가 아직 없거나 Ready가 아니다
호출 가능Agent의 Accepted·Ready가 True이고 status.observedGeneration이 최신이다
배포 실패Accepted가 False(참조·spec 문제)이거나 Ready가 오래 False(image·Secret 문제)

MCPServer는 Accepted·ResolvedRefs·Programmed·Ready 네 condition을 쓴다(kmcp). Agent가 참조하는 ModelConfig·Secret·MCPServer가 먼저 준비되도록 GitOps 도구의 적용 순서를 정해 두면 중간의 실패 상태가 줄어든다. controller가 만드는 Deployment에 annotation이 필요하면 deploymentAnnotations를 쓴다.

backend에 필요한 클러스터 권한은 대상 namespace의 agents·mcpservers 리소스에 대한 get·list·watch뿐이다.

  • 되돌리기: merge commit을 revert하는 PR을 만든다. GitOps 도구가 이전 manifest를 적용하고 controller가 Agent Pod를 이전 설정으로 다시 만든다. 되돌리기도 승인을 거친다.
  • 삭제: manifest를 지우는 PR을 merge한다. GitOps 도구가 리소스를 지우면(prune 설정 필요) controller가 Deployment와 Service를 지우고 호출 주소가 닫힌다.
  • 남는 것: 대화 이력은 kagent의 PostgreSQL에 남는다. Agent 삭제와 별개로 session 보존 기간이 정리한다.

급한 차단은 PR을 기다리지 않는다. 호출은 backend를 거치므로 backend가 그 Agent의 호출을 먼저 막고, 배포 제거는 PR로 따라간다.

kagent UI는 Agent를 만들고 고치는 화면을 제공한다. UI로 만든 Agent는 Git에 없고, UI로 고친 값은 Git과 다르다. GitOps 도구가 자동 복구를 켜 두었다면 Git의 값으로 되돌리고, 꺼 두었다면 어긋난 채로 남는다.

어느 쪽이든 승인을 거치지 않은 변경이 잠시라도 실행될 수 있다. 그래서 다음을 함께 둔다.

  • kagent UI는 운영자만 접근하게 하고, 임직원의 Agent 변경은 portal → PR 경로로만 받는다.
  • Agent namespace에 대한 쓰기 RBAC는 GitOps 도구의 ServiceAccount에만 준다.
  • GitOps 도구의 자동 복구와 prune을 켜서 Git에 없는 리소스가 남지 않게 한다.
질문왜 정해야 하나
image build는 누가 하는가 — 개발자 저장소의 CI인가, 우리 workflow인가읽기 전용 GitHub App만으로는 build 결과를 registry에 올릴 수 없다. build 주체와 registry 권한을 정해야 한다
승인자는 누구인가 — Agent 소유 부서인가, 플랫폼 팀인가임직원이 Agent를 자주 만들면 리뷰가 병목이 된다
위험이 낮은 변경은 자동 merge하는가조회 tool만 쓰는 Declarative Agent의 지시문 수정까지 사람이 보면 대기 시간이 길어진다. 자동 merge 조건은 검사 규칙으로 명시해야 한다
요청부터 호출 가능까지 얼마나 걸려도 되는가build + 리뷰 + sync 시간이 portal의 대기 화면에 그대로 드러난다
portal DB와 Git 중 무엇이 원본인가Agent의 정체성·권한은 portal DB, 배포된 구성은 Git으로 나누고 둘을 Agent ID와 commit으로 연결한다
  • backend의 GitHub 자격 증명이 유출됐다. 공격자가 승인 없이 Agent의 tool을 늘릴 수 있는가? → backend 권한이 workflow 실행 요청뿐이면 PR이 만들어질 뿐이다. merge 권한과 workflow 파일이 보호되어 있어야 이 답이 성립한다.
  • 승인된 PR의 image가 :latest였다. 무엇이 문제인가? → 같은 manifest인데 Pod가 재시작될 때 다른 내용이 실행될 수 있다. 승인한 것과 실행되는 것이 달라진다. digest로 고정한다.
  • kagent UI에서 운영자가 Agent의 toolNames를 고쳤다. 어떻게 되는가? → Git과 어긋난다. 자동 복구가 켜져 있으면 되돌아가고, 꺼져 있으면 승인되지 않은 구성이 계속 실행된다.