버그 재현 자동화에는 달콤한 유혹이 하나 있다.
“어차피 같은 API를 부르는 거니까, 중간 단계를 좀 건너뛰어도 되지 않을까?”
대부분의 경우 이 유혹은 꽤 합리적으로 들린다. 토큰을 직접 넣고, kubeconfig를 손으로 만들고, 마지막 API만 호출하면 빠르다. 결과도 나온다. 로그도 남는다.
그런데 접근 제어 제품이나 에이전트 기반 제품에서는 이 지름길이 가끔 위험해진다.
사용자가 실제로 밟는 길은 훨씬 길기 때문이다.
kubectl
-> kubeconfig exec plugin
-> qpctl
-> desktop Multi Agent
-> product API
-> proxy
-> Kubernetes API Server
이 경로에서 문제가 생기는데, 테스트는 중간을 건너뛴다면 어떻게 될까?
테스트는 초록색인데 사용자는 계속 빨간불을 본다.
이번 작업은 그 간극을 줄이는 이야기였다.
지름길은 빠르지만, 진짜 길은 다르다
Kubernetes 접근 문제를 재현할 때 가장 빠른 방법은 정적 토큰을 넣은 kubeconfig를 만드는 것이다.
kubectl
-> static token kubeconfig
-> proxy
-> Kubernetes API Server
이 방식은 좋다. 단순하고 빠르다. 프록시가 Kubernetes API와 어떻게 대화하는지 보기에는 충분하다.
하지만 실제 사용자가 데스크톱 에이전트를 통해 접근한다면 경로가 달라진다.
kubectl
-> exec kubeconfig
-> qpctl kube get-token
-> Multi Agent
-> product session / selected role
-> KAC token API
-> proxy
-> Kubernetes API Server
여기에는 정적 토큰 경로에는 없는 실패 지점이 많다.
- Multi Agent 앱이 설치되어 있는가
qpctl이 올바른 앱 번들을 가리키는가- 앱이 실행 중인가
- 로컬 제품 서버가 host로 등록되어 있는가
- 로그인 세션이 살아 있는가
- 현재 선택된 역할이 있는가
kubectl이 exec plugin을 통해 토큰을 받을 수 있는가
정적 토큰 테스트는 이 질문들을 대답하지 못한다.
그래서 이번에는 ReproKit에 “진짜 길”을 넣었다.
Multi Agent를 테스트의 승객이 아니라 등장인물로 만들기
처음에는 Multi Agent를 부가 조건처럼 생각하기 쉽다.
“앱이 설치되어 있으면 좋고, 아니면 수동으로 확인하면 되겠지.”
하지만 실제 제품 경로를 재현하려면 Multi Agent는 배경 소품이 아니다. 주인공 중 하나다.
그래서 ReproKit에 Multi Agent 관련 모듈을 나눠 넣었다.
multiagent.install
multiagent.runtime
multiagent.config
multiagent.auth
multiagent.kubeconfig
multiagent.verify
역할은 단순하다.
1. 설치 확인
앱이 있는지 본다.
/Users/.../Applications/QueryPieMultiAgent.app
/usr/local/bin/qpctl
여기서 중요한 건 “파일이 있다”만이 아니다. qpctl이 실제 앱 번들 안의 실행 파일을 가리키는지도 본다.
테스트 자동화에서 제일 억울한 실패는 이런 것이다.
“명령은 있는데, 내가 생각한 그 명령이 아니었다.”
2. 실행 상태 확인
데스크톱 앱은 라이브러리 함수가 아니다.
실행 중이어야 하고, 필요하면 다시 시작해야 한다. 그래서 runtime 모듈은 프로세스를 확인하고 앱을 열고, qpctl로 상태를 묻는다.
이 단계가 있으면 다음 사람이 “Multi Agent 켜져 있나요?”를 매번 물어보지 않아도 된다.
3. 로컬 host 설정
로컬 개발 서버는 보통 이런 주소로 떠 있다.
http://127.0.0.1:18080
그런데 Multi Agent가 이 서버를 모르면 qpctl은 엉뚱한 곳을 보거나 아예 실패한다.
여기서 재미있는 점은 설정 파일이 단순한 YAML이나 JSON이 아니었다는 것이다. 데스크톱 앱은 LiteDB에 host와 기본 서버 상태를 저장하고 있었다.
그래서 모듈은 먼저 DB를 백업한다. 그 다음 로컬 서버를 등록하고 현재 host로 선택한다.
좋은 자동화는 빠른 자동화가 아니라, 되돌릴 수 있는 자동화다.
4. 인증과 역할 선택
이번에 가장 흥미로웠던 함정은 “로그인했는데 역할이 없다”였다.
웹에서 역할을 선택했다고 해서, qpctl이 사용하는 에이전트 세션에도 같은 역할이 잡혀 있다고 보장할 수는 없었다.
겉으로는 이런 에러 하나로 보인다.
Cluster Role is not selected.
하지만 실제 의미는 더 구체적이다.
“웹 앱 세션 말고, Multi Agent가 쓰는 AGENT 세션에 선택된 KAC role이 없다.”
이 차이를 모듈로 만들었다.
qpctl auth status로 로그인 여부 확인- 필요하면 사용자가 직접 로그인할 명령 안내
- 로컬 재현 환경에서는
--allow-db-seed옵션으로 AGENT token의 selected role 보정 qpctl kube get-token이 실제 ExecCredential을 반환하는지 검증
토큰 값은 로그에 남기지 않는다. 필요한 것은 토큰의 내용이 아니라 “토큰을 정상적으로 받을 수 있는가”다.
5. exec-plugin kubeconfig 생성
마지막으로 진짜 kubeconfig를 만든다.
정적 토큰 kubeconfig가 아니라, kubectl이 실행 시점에 qpctl을 호출하는 kubeconfig다.
users:
- name: product-real-user
user:
exec:
command: /usr/local/bin/qpctl
args:
- kube
- get-token
- --server
- http://127.0.0.1:18080
- --cluster
- <cluster-id>
- --user-role
- <role-id>
- --output
- json
이 kubeconfig에는 bearer token이 저장되지 않는다.
kubectl이 실행될 때마다 qpctl이 Multi Agent에게 토큰을 받아온다. 즉 테스트가 사용자의 실제 길을 걷는다.
빠르게 만들었더니 더 정확해졌다
재미있는 점은 “진짜 경로”가 꼭 느리지만은 않았다는 것이다.
초기 setup은 할 일이 많다.
- minikube 준비
- KAC cluster 등록
- policy/role/grant 구성
- Multi Agent host 설정
- 앱 재시작
- 역할 동기화
- exec kubeconfig 생성
- ExecCredential 검증
그래도 로컬에서 약 40초 안팎이면 준비가 끝났다.
그 다음부터는 빨랐다.
verify --with-multiagent < 1초
kubectl direct probe 약 0.2초
kubectl via Multi Agent 약 0.2초
체감상 큰 차이는 이거다.
예전에는 사람이 체크리스트를 따라가며 물었다.
앱 설치되어 있나요?
qpctl 연결되어 있나요?
로그인되어 있나요?
role 선택되어 있나요?
kubeconfig 맞나요?
직접 kubectl과 제품 경유 kubectl 결과가 어떻게 다른가요?
이제는 명령 하나가 대답한다.
python3 dev-scripts/run-repro.py qcp-5498-kac-wide-output verify --with-multiagent
자동화의 가치는 단순히 “빠르다”가 아니다.
반복 질문을 없애고, 실패 지점을 이름 붙여주고, 증거를 같은 위치에 남기는 것이다.
kubectl get nodes -o wide가 알려준 것
이번 재현의 핵심 증상은 꽤 작아 보였다.
직접 Kubernetes에 붙으면 이렇게 나온다.
NAME STATUS ROLES VERSION INTERNAL-IP OS-IMAGE ...
그런데 제품 경로를 지나면 이렇게 줄어든다.
NAME AGE
처음 보면 권한 문제처럼 보일 수 있다.
하지만 여기서 중요한 단서는 kubectl의 출력 협상이다.
kubectl get nodes -o wide는 서버에게 “Table 형태로, wide 컬럼도 포함해서 주세요”라고 요청한다. 이 요청은 HTTP Accept header에 들어간다.
중간 프록시가 이 협상 정보를 너무 단순하게 바꾸면, Kubernetes API Server는 Table이 아니라 일반 JSON 목록을 돌려준다. 그러면 kubectl은 이렇게 말한다.
Unable to decode server response into a Table.
Falling back to hardcoded types.
즉 문제는 “클러스터에 못 들어간다”가 아니었다.
“들어가긴 들어갔는데, 대화 방식이 바뀌어서 wide 출력 정보를 잃었다”였다.
이 차이는 작지만 중요하다.
접근 제어 제품에서는 “통과했다”와 “원래 클라이언트 경험을 보존했다”가 다르기 때문이다.
좋은 재현 도구는 길을 짧게 만들지 않는다
이번 작업에서 얻은 가장 큰 교훈은 이것이다.
좋은 재현 도구는 실제 길을 지워버리지 않는다.
오히려 실제 길을 더 선명하게 만든다.
shortcut path
빠르다
단순하다
특정 레이어만 보기 좋다
real product path
조금 복잡하다
실제 사용자 경험과 가깝다
세션/역할/에이전트/프록시 문제까지 드러낸다
둘 중 하나만 있으면 부족하다.
정적 토큰 경로는 프록시와 Kubernetes 대화를 빠르게 본다. Multi Agent 경로는 실제 사용자가 밟는 길을 검증한다.
ReproKit의 역할은 이 둘을 싸우게 하는 것이 아니라, 나란히 세워 비교하게 하는 것이다.
direct kubectl
vs
product-real kubectl via Multi Agent
이렇게 보면 버그가 더 또렷해진다.
다음에 비슷한 일을 한다면
나는 이제 에이전트 기반 기능을 재현할 때 먼저 이 질문들을 던질 것 같다.
- 우리가 만든 테스트 경로가 실제 사용자 경로와 같은가?
- 중간에 토큰 발급, 역할 선택, 로컬 앱 상태 같은 숨은 문이 있는가?
- 실패했을 때 “마지막 에러”가 아니라 “어느 문에서 막혔는지” 말할 수 있는가?
- 결과가 스크린샷이나 기억이 아니라 transcript와 summary로 남는가?
- 반복 실행이 1분 안에 끝나는가?
이 다섯 질문에 답할 수 있으면, 재현 자동화는 단순한 스크립트가 아니라 작은 실험실이 된다.
그리고 좋은 실험실은 재미있다.
버그를 잡는 장소가 아니라, 시스템이 실제로 어떻게 움직이는지 보여주는 작은 무대가 되기 때문이다.
이번에는 그 무대 위에 Multi Agent, qpctl, kubectl, proxy, Kubernetes API Server가 차례로 올라왔다.
커튼이 열리자, 문제는 권한이 아니라 대화 방식이었다.
그걸 1초 안에 다시 볼 수 있게 된 것.
그게 이번 자동화의 가장 마음에 드는 부분이다.