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에 무엇이 담기고 승인자가 무엇을 봐야 하는지를 정리한다.
전체 흐름
섹션 제목: “전체 흐름”| 단계 | 누가 | kagent·kmcp와의 관계 |
|---|---|---|
| ① ② | backend | 없음. 요청을 기록하고 GitHub API로 workflow를 실행시킨다 |
| ③ ④ ⑤ | workflow | source를 읽고(③) manifest를 만들어 검사한 뒤(④) PR을 올린다(⑤). manifest 생성에 kagent·kmcp CLI의 --dry-run을 쓸 수 있다 |
| ⑥ | 승인자 | 없음. 저장소의 branch 보호 규칙이 승인 조건을 정한다 |
| ⑦ ⑧ | GitOps 도구 | merge된 commit을 감지해(⑦) Agent·MCPServer 등을 Kubernetes API에 적용한다(⑧) — 선언 경로 |
| ⑨ | controller | 리소스를 Deployment·Service로 바꾸고 status.conditions를 쓴다 |
| ⑩ | backend | status를 읽기 전용으로 조회해 portal에 표시한다. 이후 호출은 A2A — 호출 경로 |
누가 무엇을 쓸 수 있는가
섹션 제목: “누가 무엇을 쓸 수 있는가”이 방식의 핵심은 쓰기 권한이 한 줄로만 이어진다는 점이다.
| 주체 | 가진 권한 | 갖지 않는 권한 |
|---|---|---|
| backend | workflow 실행 요청. 클러스터의 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가 곧 배포 권한이었다.
manifest를 만드는 방법
섹션 제목: “manifest를 만드는 방법”workflow의 ④ 단계는 Agent의 작성 방식에 따라 다르다.
| 대상 | 개발자 저장소에 있는 것 | workflow가 하는 일 |
|---|---|---|
| Declarative Agent | 지시문, 원하는 tool 이름, 설명 | 우리 template에 값을 채워 Agent manifest를 만든다 |
| BYO Agent | source와 Dockerfile | image를 build하거나 이미 build된 image의 digest를 확인해 Agent manifest를 만든다 |
| MCP 서버 (kmcp) | kmcp project | image 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.yamlkagent 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 저장소의 모양
섹션 제목: “GitOps 저장소의 모양”디렉터리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
- agent.yaml
디렉터리contract-reviewer/
- agent.yaml
플랫폼 설정과 Agent를 다른 경로에 두면 승인자를 경로별로 나눌 수 있다. platform/은 플랫폼 팀만,
agents/는 Agent 승인자가 merge하게 한다.
PR 하나에 담기는 것
섹션 제목: “PR 하나에 담기는 것”# 설명용 예제 — agents/hr-helper/agent.yamlapiVersion: kagent.dev/v1alpha2kind: Agentmetadata: 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: - Authorizationlabel에 portal의 Agent ID와 source commit을 남기면 backend가 status를 읽을 때 “어느 요청의 결과인지”를 리소스에서 바로 대조할 수 있다. portal DB의 배포 기록에는 PR 주소와 merge commit을 함께 저장한다.
승인자가 PR에서 보는 것
섹션 제목: “승인자가 PR에서 보는 것”merge가 승인이므로 승인자는 이 diff가 Agent에게 무엇을 허용하는가를 읽어야 한다.
| 바뀐 곳 | 뜻 | 확인할 것 |
|---|---|---|
image의 digest | 실행되는 코드가 바뀐다 | 어느 저장소의 어느 commit에서 만들어졌는가 |
toolNames 추가 | Agent가 할 수 있는 일이 늘어난다 | 변경·삭제 tool인가. requireApproval이 필요한가 |
requireApproval에서 삭제 | 사람 승인 없이 실행된다 | 의도된 변경인가 |
allowedHeaders 추가 | 임직원 토큰이 그 tool 서버로 전달된다 | 그 서버를 누가 운영하는가. 토큰을 받아도 되는 서버인가 |
RemoteMCPServer의 url | tool 호출이 그 주소로 나간다 | 사내 주소인가, 외부인가 |
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 보호 규칙에 묶는다.
merge 이후 — sync와 상태 회수
섹션 제목: “merge 이후 — sync와 상태 회수”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로 따라간다.
drift — Git 밖에서 바뀌는 경우
섹션 제목: “drift — Git 밖에서 바뀌는 경우”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과 어긋난다. 자동 복구가 켜져 있으면 되돌아가고, 꺼져 있으면 승인되지 않은 구성이 계속 실행된다.