0. Contents


1. Overview

사용자레플리카 3개로 늘려줘Kukie 에이전트승인 카드 · kubectl 실행매니페스트 레포replicas: 11클러스터kube-apiserverDeployment webspec.replicas: 3 ← ② 가 쓴 값spec.replicas: 1 ← ④ 가 다시 쓴 값ArgoCDrepo-server레포를 읽어 원하는 상태를 만든다application-controller지금 상태와 비교하고 sync 한다2kubectl scale --replicas=3읽는다34Podweb-… (원래)web-… (늘어남)web-… (늘어남)④ 뒤 늘어난 Pod 는 사라지고원래 수로 돌아간다① 사람이 요청한다 — ② Kukie 가 kubectl 로 바꾼다 — ③ ArgoCD 가 Git 값과 다른 것을 찾는다 — ④ selfHeal 이면 Git 값으로 되돌린다kubectl 은 성공했고 채팅은 완료라고 답한다. 사용자가 다시 보면 값은 원래대로다
kubectl 은 ArgoCD 를 거치지 않고 API server 로 간다 — 그래서 변경은 성공하고, 그 뒤에 ArgoCD 가 Git 값으로 되돌린다

배경

  • Kukie 는 채팅으로 고객의 EKS 를 운영하는 서비스다
  • 변경 요청은 승인 카드를 거쳐 kubectl 로 실행한다
  • ArgoCD 를 쓰는 클러스터에서는 이 방식에 문제가 생긴다 — 클러스터의 원본이 Git 이기 때문이다

이 글의 질문

  • kubectl 변경은 언제 되돌아가고, 언제 남는가
  • 어떤 리소스를 ArgoCD 가 관리하는지 코드가 어떻게 아는가
  • ArgoCD 정보를 읽으려면 고객 클러스터에 어떤 권한이 필요한가
  • PR 을 올리기 전 단계에서 사용자에게 무엇을 보여 줄 수 있는가

범위

  • 매니페스트 레포에 PR 을 올리는 흐름과 Helm values 는 이 글에 없다
  • 시험은 ArgoCD 가 설치된 시험 전용 EKS 에서 했고, 화면 캡처는 앱의 픽스처 데이터로 띄운 것이다

2. Source of Truth — Git 과 클러스터

2.1 원하는 상태와 지금 상태

  • GitOps 에서는 레포에 적힌 YAML 이 원하는 상태다
  • 클러스터에 실제로 떠 있는 값이 지금 상태다
  • ArgoCD 는 이 두 값을 계속 비교한다 — Infra.05 4장의 sync 루프가 이 일을 한다
Synced두 값이 같다Gitreplicas: 1클러스터replicas: 1할 일 없음OutOfSync두 값이 다르다Gitreplicas: 1클러스터replicas: 3selfHeal 꺼짐: 그대로 둔다selfHeal 복원두 값이 달라 sync 한다Gitreplicas: 1클러스터replicas: 3 → 1selfHeal 켜짐: Git 값으로 쓴다kubectl get application 의SYNC STATUS 가 Synced화면에 OutOfSync 로 남고누가 sync 하면 Git 값이 된다kubectl 변경이 사라지고다시 Synced 가 된다Git 이 원본이다 — 클러스터에서 직접 바꾼 값은 기록이 없는 임시 변경으로 취급된다
ArgoCD 의 일은 두 값을 비교하는 것이다 — 달라졌을 때 무엇을 할지는 sync 정책이 정한다

2.2 Application 과 sync 정책

ArgoCD 는 앱 하나를 Application 이라는 쿠버네티스 리소스로 표현한다. 어느 레포의 어느 폴더를 어느 namespace 에 배포할지가 여기에 적혀 있다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: sandbox-plain
  namespace: argocd
spec:
  project: sandbox
  source:
    repoURL: git@github.com:example-org/kukie-gitops-sandbox.git
    targetRevision: main
    path: apps/plain                  # 이 폴더의 YAML 이 원하는 상태다
  destination:
    server: https://kubernetes.default.svc
    namespace: sandbox-plain
  syncPolicy:
    automated: {prune: true, selfHeal: true}   # 달라졌을 때의 동작

두 값이 달라졌을 때의 동작은 syncPolicy 가 정한다.

설정뜻kubectl 로 바꾼 값
automated 없음사람이 sync 를 눌러야 반영된다누가 sync 할 때까지 남는다
automatedGit 이 바뀌면 자동으로 반영한다다음 Git 변경이 sync 될 때 덮어써진다
automated.selfHeal클러스터가 달라져도 자동으로 sync 한다곧 되돌아간다
automated.pruneGit 에서 지운 리소스를 클러스터에서도 지운다값 변경과는 관계없다

ArgoCD 문서에 따르면 selfHeal 은 기본으로 꺼져 있다. 자동 sync 를 켜도 클러스터에서 직접 바꾼 값은 그것만으로는 자동 sync 를 일으키지 않는다.

2.3 kubectl 이 성공하는 이유

  • ArgoCD 는 API server 로 가는 요청을 가로채지 않는다
  • kubectl 요청은 평소처럼 API server 에 들어가고, 값은 정상으로 바뀐다
  • ArgoCD 는 그 뒤에 달라진 값을 발견한다
  • 그래서 명령은 성공으로 끝나고, 에이전트도 완료라고 답한다
  • 사용자는 나중에 값이 돌아간 것을 보게 된다. 오류가 없어서 원인을 찾기 어렵다

2.4 시험 환경

  • 실서비스 클러스터와 GPU 클러스터는 시험장으로 쓰지 않았다. 시험 전용 EKS kukie-gitops-test 를 따로 만들었다
  • 노드는 t3.medium 2대이고, 비용은 한 달 약 $160 이다. 시험이 끝나면 지우는 클러스터다
  • ArgoCD 는 Terraform 밖에서 helm 으로 설치했다. 지우면 “ArgoCD 없는 클러스터"도 시험할 수 있기 때문이다
  • 시험 앱의 원본은 별도 레포 kukie-gitops-sandbox 에 두고, ArgoCD 는 읽기 전용 deploy key 로 읽는다
  • 이 인프라 레포에는 Claude 리뷰 봇을 두지 않았다. 고객 클러스터를 흉내 내는 레포이기 때문이다

2.5 되돌림 시험

ArgoCD v3.5.3 에 형식이 다른 앱 셋을 올렸다.

앱형식selfHeal시험 전 값
sandbox-plainDirectory켜짐web 1개
sandbox-kustomizeKustomize켜짐api 2개
sandbox-helmHelm꺼짐worker 1개
kubectl config use-context kukie-gitops-test

# selfHeal 켜짐
kubectl -n sandbox-plain scale deploy web --replicas=3
kubectl -n sandbox-kustomize scale deploy api --replicas=5

# selfHeal 꺼짐
kubectl -n sandbox-helm scale deploy worker --replicas=2

# 잠시 뒤 확인
kubectl -n sandbox-plain get deploy web        # READY 1/1 로 돌아왔다
kubectl -n sandbox-kustomize get deploy api    # READY 2/2 로 돌아왔다
kubectl -n sandbox-helm get deploy worker      # READY 2/2 로 남아 있다
kubectl -n argocd get application sandbox-helm \
  -o jsonpath='{.status.sync.status}'          # OutOfSync
:26:30:32:3919:11 (UTC)selfHeal 켜짐sandbox-plain websandbox-kustomize apiweb 3 · api 5web 1 · api 21scale2자동 sync시작3sync 완료 — 약 6초selfHeal 꺼짐sandbox-helm worker자동 sync 만 켜짐worker 2 — Application 은 OutOfSync1scaleGit 이 바뀌어 sync 되기 전까지 남는다되돌림은 Git 을 다시 읽는 주기(기본 약 3분)와 별개로 일어났다시험 후 worker 는 손으로 1로 되돌렸고 Application 은 다시 Synced 가 됐다
2026-10-30 시험 기록 — 같은 시각에 scale 한 앱 셋의 결과가 selfHeal 설정으로 갈린다
  • ArgoCD 문서는 selfHeal 재시도 대기 기본값을 5초로 적는다. 시험의 되돌림도 같은 수 초 단위였다
  • Git 을 다시 읽는 주기는 기본 약 3분으로, 되돌림 시간과는 다른 값이다
  • 시험은 한 번이었고, 이 시간은 컨트롤러 설정에 따라 달라진다
  • 자동 sync 는 operationState.operation.initiatedBy.automated 값으로 사람이 시작한 sync 와 구분된다

3. Warn then Allow — 경고 후 허용

3.1 세 가지 선택지

선택좋은 점문제
kubectl 차단되돌림이 사라진다장애 대응처럼 PR 을 기다릴 수 없을 때 막힌다
아무것도 안 함구현할 것이 없다변경이 조용히 사라진다
경고 후 허용사용자가 결과를 알고 고른다경고를 보고 실행해도 되돌아간다

Kukie 는 경고 후 허용을 골랐다. 장애 중에 임시로 값을 바꾸는 일은 실제로 필요하고, 그 판단은 사용자가 할 수 있기 때문이다.

3.2 승인 카드

ArgoCD 관리 리소스에 kubectl 변경을 요청했을 때 나오는 승인 카드
selfHeal 이 켜진 앱의 승인 카드 — 경고가 노란 상자로 붙고 버튼은 "그래도 실행"이 된다. 픽스처 화면이며 이름은 예시로 바꿨다
  • kubectl 명령과 dry-run 결과는 일반 승인 카드와 같다
  • 그 위에 ArgoCD 경고와 관리하는 앱의 이름·경로가 붙는다
  • 버튼 문구는 “승인하고 실행"에서 “그래도 실행"으로 바뀐다
  • 승인하면 Action Plan 에 “ArgoCD 관리 리소스 직접 변경"이 남는다

3.3 Plan 에 싣는 값

변경 툴 4종(apply_manifest 등)은 실행 직전에 가드레일 훅을 지난다. 훅이 판별 결과를 승인 카드와 Plan 의 argocd 칸에 싣는다.

{
  "summary": "ArgoCD 관리 리소스 직접 변경",
  "level": "self_heal",
  "warning": "몇 분 안에 ArgoCD가 되돌려요. 유지하려면 sync를 멈추거나 PR을 같이 올리세요.",
  "applications": [{ "name": "guestbook", "path": "apps/guestbook", "self_heal": true }],
  "resources": [{ "kind": "deployment", "name": "guestbook-ui", "namespace": "study", "application": "guestbook" }]
}
  • applications 칸은 레포 주소와 sync 설정도 함께 담는다. 위 예시는 일부만 적었다
  • 서버는 Plan 의 payload 를 자유 형식으로 저장하므로, 카드에 이 칸을 더할 때 서버를 바꾸지 않았다
  • 앱은 이 칸을 api/argocd.ts 한 곳에서 검사한 뒤 읽는다
  • 리뷰에서 모양이 이상한 argocd 칸 때문에 화면 전체가 깨지는 문제가 나와, 배열이 아닌 값과 이름 없는 줄을 걸러내도록 고쳤다
  • 칸을 읽지 못해도 승인 카드는 그대로 떠야 한다

3.4 경고 수준

경고는 sync 정책에 따라 네 단계로 나뉜다. 대상 리소스가 여럿이면 가장 센 단계를 카드에 싣는다.

수준조건카드 문구
self_healselfHeal 켜짐몇 분 안에 ArgoCD가 되돌려요. 유지하려면 sync를 멈추거나 PR을 같이 올리세요.
auto_sync자동 sync 만 켜짐다음 git 변경이 반영될 때 덮어써져요.
unknown관리 표시는 있는데 앱을 못 읽음ArgoCD 앱 설정을 읽지 못해 되돌려질지는 알 수 없어요. 유지하려면 git 에도 같은 변경을 반영하세요.
manual자동 sync 꺼짐자동 sync 는 꺼져 있어요. ArgoCD 화면에 OutOfSync 로 보이고, 누가 sync 하면 git 내용으로 돌아가요.

문구는 에이전트가 만들고, 앱은 그대로 표시한다. 앱은 self_heal 만 노란 상자로 더 눈에 띄게 그린다.

3.5 남는 한계

  • selfHeal 이 켜져 있으면 경고를 보고 실행해도 값은 되돌아간다
  • 카드 문구가 말하는 “sync 멈추기"는 아직 버튼으로 구현되지 않았다
  • “같은 내용으로 PR 올리기” 선택지도 아직 구현 전이다
  • 지금은 사용자가 ArgoCD 쪽에서 직접 sync 를 멈추거나, 레포를 따로 고쳐야 한다

4. Ownership Detection — ArgoCD 관리 판별

4.1 판단 단위

  • 처음에는 클러스터마다 “변경 방식” 설정을 두려고 했다
  • 한 클러스터에도 ArgoCD 가 관리하는 리소스와 아닌 리소스가 섞여 있다
  • 대화 도중 설정이 바뀌면 그 대화를 어떻게 처리할지도 문제가 됐다
  • 그래서 설정을 없애고, 변경 직전에 리소스마다 판단하도록 바꿨다
대상 리소스kubectl get … -o json앞에서 5개까지1추적 표시어노테이션 먼저없으면 레이블2Applicationget applications -A이름으로 찾기3sync 정책automatedselfHeal4관리 아님표시가 없다표시가 다른 리소스를가리킨다→ 경고 없이 진행앱을 못 찾음어노테이션: 관리로 본다→ 수준 unknown레이블: Helm 레이블→ 경고 없이 진행경고 수준self_healauto_syncmanual(앱 못 읽음: unknown)Application 목록을 읽을 권한이 없으면(403) 판별 전체를 건너뛰고 지금처럼 동작한다kubectl 한 번 5초, 전체 10초 제한 — 변경 툴마다 도는 판별이 승인 카드를 늦추지 않게 한다
kubectl 조회만으로 판단하고, ArgoCD API 토큰은 쓰지 않는다 — 실패해도 실행을 막지 않는다

4.2 tracking-id 의 구조

ArgoCD 는 자기가 만든 리소스에 추적 표시를 남긴다. ArgoCD 문서의 기본 추적 방식은 argocd.argoproj.io/tracking-id 어노테이션이다.

kubectl -n sandbox-plain get deploy web \
  -o jsonpath='{.metadata.annotations.argocd\.argoproj\.io/tracking-id}'
# sandbox-plain:apps/Deployment:sandbox-plain/web
argocd.argoproj.io/tracking-idsandbox-plainapps/Deploymentsandbox-plain/web::① 앱 이름Application 이름이다목록에서 이 이름의 앱을 찾는다② group/Kind리소스 종류다리소스의 kind 와 같아야 한다③ namespace/name리소스의 위치와 이름이다리소스의 값과 같아야 한다② 나 ③ 이 리소스와 다르면 다른 곳에서 복사된 표시로 보고 관리 리소스에서 뺀다
표시 하나로 어느 앱의 리소스인지와, 표시가 이 리소스의 것이 맞는지를 함께 알 수 있다
  • 시험 앱의 Deployment 와 Service 에는 모두 이 어노테이션이 있었다
  • app.kubernetes.io/instance 레이블은 없었다
  • Service 처럼 core 그룹의 리소스는 sandbox-plain:/Service:sandbox-plain/web 처럼 group 자리가 비어 있다

4.3 복사된 표시와 Helm 레이블

추적 표시가 있다고 바로 ArgoCD 관리 리소스로 보면 두 경우에 틀린다.

복사된 어노테이션원본 Deployment web관리namespace: sandbox-plaintracking-id: …:sandbox-plain/web표시의 namespace 와 리소스 namespace 가 같다복사한 Deployment web관리 아님namespace: copy-testtracking-id: …:sandbox-plain/webYAML 을 복사할 때 어노테이션이 따라왔다Helm 이 붙인 레이블Deployment guestbook-ui관리instance: guestbookApplication guestbook 의 status.resources 에 있다앱이 있고 앱의 리소스 목록에도 있다Deployment prometheus관리 아님instance: prometheus같은 이름의 Application 이 없다helm install 이 같은 레이블을 붙였다ArgoCD 문서도 여러 Helm 차트가 같은 instance 레이블을 쓴다는 점을 레이블 추적의 문제로 적는다
표시가 있다는 사실만으로는 부족하다 — 어노테이션은 표시 내용을, 레이블은 Application 을 대조한다

레이블은 다른 도구도 붙이는 값이라 Application 까지 확인한다. 코드에서는 이렇게 갈린다.

# kukie_agent/argocd.py
item = find_application(items, tracking)
if tracking.source == "label" and (item is None or not manages(item, resource)):
    continue     # Helm 의 instance 라벨일 뿐이다

어노테이션은 ArgoCD 만 붙이는 값으로 본다. 그래서 어노테이션이 있는데 Application 을 못 찾으면 관리 리소스로 보고, 경고 수준을 unknown 으로 둔다. 다른 클러스터의 ArgoCD 가 이 클러스터를 관리하는 허브형 구성에서 이런 일이 생긴다.

4.4 읽기 실패의 구분

Application 목록을 못 읽는 이유는 세 가지로 나눈다.

상태뜻동작
absentapplications.argoproj.io CRD 가 없다ArgoCD 없음으로 처리
forbidden읽을 권한이 없다 (403)있는지 알 수 없어 판별을 건너뜀
error닿지 않음, 시간 초과, 응답 모양 이상연결 문제로 구분
  • 세 경우는 화면 안내가 다르다. “ArgoCD 가 없어요"와 “읽을 권한이 없어요"는 사용자가 할 일이 다르다
  • kubectl 은 API server 에 닿지 못할 때도 “그런 리소스 종류가 없다"처럼 보이는 오류를 낼 수 있다
  • 그래서 connection refused, unauthorized 같은 연결 실패 문구를 먼저 걸러낸다

4.5 시간 제한

  • 판별은 변경 툴을 부를 때마다 돈다. 오래 걸리면 승인 카드가 늦게 뜬다
  • kubectl 한 번은 5초, 판별 전체는 10초로 제한한다
  • 한 번의 제한은 남은 시간으로 줄인다. 끝 무렵에 시작한 조회가 전체 제한을 넘지 못하게 하기 위해서다
  • 대상 리소스는 앞의 5개까지만 본다
  • 어떤 실패도 위로 올리지 않는다. 판별은 경고를 붙이는 일이고 실행의 조건이 아니다

4.6 리뷰에서 바뀐 판별 코드

처음 코드문제바꾼 코드
표시의 앱 이름만 확인다른 namespace 로 복사한 리소스도 관리로 판단kind · namespace · name 대조
kubectl 호출마다 5초리소스가 많으면 전체 시간이 늘어남전체 10초 예산, 호출마다 남은 시간 사용

판별 코드는 kukie_agent/argocd.py 한 곳에 있고, 가드레일과 앱 목록 API, 앱 상세 API 가 함께 쓴다. 그래서 화면에 보이는 관리 여부와 승인 카드의 판단이 어긋나지 않는다.

읽기 툴의 결과에도 같은 판별로 만든 한 줄을 덧붙인다. 모델이 리소스를 바꾸기 전에 “ArgoCD 앱 guestbook 이 관리하는 리소스다"를 알게 하기 위해서다.

앱 이름이 <namespace>_<name> 꼴이면 ArgoCD 의 “apps in any namespace” 기능으로 만든 Application 으로 해석한다. 쿠버네티스 이름에는 _ 를 쓸 수 없어서 이 표기를 구분자로 쓸 수 있다.

5. Access Level — 연결 권한

5.1 연결 구조

Infra.07에서 만든 AWS 계정 연결은 영구 키를 받지 않는다. 고객 계정의 역할을 필요할 때만 빌려 쓴다.

Kukie 서버고객 역할을 잠깐 맡는다영구 키를 받지 않는다고객 AWS 계정KukieAccess 역할CloudFormation 스택이만든다EKS 클러스터접근 항목누가 들어오나접근 정책무엇을 할 수 있나kubectl토큰은 짧게 산다1AssumeRole+ ExternalId23전체argocdkube-systemkukie-demostudy모든 namespace제한argocdkube-systemkukie-demostudy허용 목록 안만① 역할을 맡는다 — ② 역할로 접근 항목과 정책을 다룬다 — ③ 정책이 허락한 범위에서 kubectl 이 돈다Application 은 보통 argocd namespace 에 있어 제한 수준에서는 읽을 수 없다. kube-* 는 제한 수준에서 템플릿이 거부한다
역할은 계정에 하나이고, 접근 항목과 정책은 클러스터마다 붙는다 — 권한 수준은 여기에 붙는 정책으로 갈린다
  • 역할(출입증)은 계정에 하나다. 들어오기 위한 조건은 팀마다 다른 ExternalId 다
  • 접근 항목(이름표)은 클러스터마다 하나다
  • 접근 정책은 접근 항목에 붙어서 그 클러스터 안의 권한을 정한다

5.2 템플릿 v4 의 한계

  • v4 는 AmazonEKSEditPolicy 를 kukie-* namespace 안에서만 줬다
  • kube-* namespace 는 템플릿이 거부했다
  • GPU 정보를 읽으려면 고객이 선택 그룹을 켜고 RBAC 파일을 직접 적용해야 했다
  • ArgoCD 의 Application 은 보통 argocd namespace 에 있어서 이 권한으로는 읽을 수 없다
  • 새 기능이 생길 때마다 고객에게 RBAC 파일을 하나씩 더 받아야 하는 구조였다

변경은 어차피 승인 카드를 거치므로, 복잡함에 비해 얻는 안전이 작다고 판단했다.

5.3 전체 권한과 제한 권한

템플릿 v5 는 AccessLevel 파라미터 하나로 두 수준을 고른다.

AWS 계정 연결 창의 전체 권한과 제한된 권한 선택
연결 창의 권한 선택 — 스택을 만들기 전에 고른다. 픽스처 화면이다
구분전체 full (기본)제한 limited
접근 정책AmazonEKSClusterAdminPolicyAmazonEKSEditPolicy
범위클러스터 전체AllowedNamespaces 안
kube-*열림 (변경은 승인 카드로만)거부
ArgoCD · GPU 화면쓸 수 있음쓸 수 없음
  • 수준은 계정마다 하나만 고른다. 역할 하나에 두 수준을 다 허락하면 제한의 의미가 없어지기 때문이다
  • 역할에는 둘 중 한 문장만 들어간다
# kukie-access-v5.yaml
- Fn::If:
    - FullAccess
    - Sid: ClusterAdminWholeCluster
      Effect: Allow
      Action: "eks:AssociateAccessPolicy"
      Resource: "*"
      Condition:
        StringEquals:
          "eks:policyArn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
          "eks:accessScope": "cluster"
    - Sid: EditAllowedNamespacesOnly
      # AmazonEKSEditPolicy, namespace 범위, AllowedNamespaces 조건

등록할 때는 서버가 수준에 맞는 정책과 범위를 접근 항목에 붙인다.

// kukie-server AwsEksAccessGateway.kt
val scope = when (accessLevel) {
    AccessLevel.FULL -> AccessScope.builder().type(AccessScopeType.CLUSTER).build()
    AccessLevel.LIMITED ->
        AccessScope.builder().type(AccessScopeType.NAMESPACE).namespaces(namespaces.distinct().sorted()).build()
}
클러스터 설정 화면의 GitOps 연결됨, 제한된 권한 표시
클러스터 설정 — 권한 수준과 GitOps 연결 여부가 클러스터마다 보인다. 픽스처 화면이며 이름은 예시로 바꿨다

제한된 권한의 클러스터는 설정 화면에 표시가 붙고, ArgoCD · GPU 화면에서는 “제한된 권한이라 쓸 수 없어요” 묶음으로 내려간다. 그 namespace 안의 대화와 변경은 그대로 쓸 수 있다.

5.4 전체 권한의 실제 범위

처음 쓴 템플릿 설명은 “등록한 클러스터의 관리자"에 가까웠다. 리뷰에서 이 설명이 실제 권한보다 좁다는 지적이 나왔다.

역할의 정책 (전체 수준)Action: "eks:AssociateAccessPolicy"Resource: "*"Condition:eks:policyArn: …ClusterAdminPolicyeks:accessScope: "cluster"1Resource 를 특정 클러스터로 좁히지 않았다고객이 나중에 만든 클러스터까지 포함된다클러스터를 고르는 일은 Kukie 코드의 몫이다고객 AWS 계정의 EKS등록한shop-prod관리자 정책Kukie 가실제로 붙인다2미등록billing정책 없음역할은 붙일 수있지만 코드가붙이지 않는다새로 생긴new-team정책 없음역할은 붙일 수있지만 코드가붙이지 않는다① 역할이 할 수 있는 범위는 계정의 모든 EKS 다 — ② Kukie 가 실제로 붙이는 곳은 등록한 클러스터뿐이다일부 클러스터만 맡기려면 제한 수준을 쓴다. 클러스터 이름은 예시다
코드가 하는 일과 역할이 할 수 있는 일은 다르다 — 동의 문구는 역할이 할 수 있는 일을 기준으로 적었다

그래서 템플릿 설명을 “이 계정의 어느 EKS 클러스터에든 자기를 관리자로 붙일 수 있고, 실제로 붙이는 것은 등록한 클러스터뿐"으로 고쳤다. 고객은 코드의 동작이 아니라 역할의 권한을 보고 동의하기 때문이다.

얻는 것잃는 것
기능마다 RBAC 파일을 더 받지 않아도 된다Kukie 자격증명이 털리면 연결된 클러스터의 관리자 권한이 넘어간다
ArgoCD · GPU · 이후 기능이 바로 동작한다Kukie 쪽 역할 경계의 중요성이 더 커진다

보안 정책이 있는 고객을 위해 제한 수준을 남겨 두었다.

5.5 권한 전환 순서

스택을 업데이트해 수준을 바꾸면 서버가 스택 알림을 받아 등록된 클러스터의 정책을 교체한다.

스택 업데이트AccessLevelfull → limited서버스택 알림을받는다성공 경로Admin 만 붙음Admin + EditEdit 만 붙음① Edit 를 붙인다② Admin 을 뗀다실패 경로 — Edit 붙이기가 끝내 거부될 때Admin 만 붙음재시도 후 거부정책 없음③ Admin 을 떼고 실패로 기록제한으로 낮췄는데 관리자 권한이 남는 경우가 가장 나쁘므로, 실패하면 정책 없는 항목이 되는 쪽을 택한다
평소에는 끊김을 피하는 순서를, 실패했을 때는 넓은 권한이 남지 않는 쪽을 고른다
  • 새 정책을 먼저 붙이면 정책이 하나도 없는 순간이 생기지 않는다
  • 전환이 끝까지 실패하면 어느 쪽이 덜 나쁜지를 정해야 한다
  • 제한으로 낮추려는데 관리자 권한이 남는 경우를 더 나쁘게 봤다
  • IAM 변경이 퍼지는 데 시간이 걸려 첫 시도가 거부될 수 있으므로, 실패로 정하기 전에 재시도한다

5.6 전환 시험

  • full → limited 는 실제 스택 변경으로 시험했다
  • 서버가 알림을 받아 등록된 클러스터를 Edit 정책으로 바꿨다
  • 기본 namespace 가 default 인 시험 클러스터는 허용 목록 밖이라 Edit 정책 연결이 거부됐다
  • 허용 목록과 등록 namespace 가 어긋나면 실제 운영에서도 같은 실패가 생길 수 있다
  • limited → full 전환과 에이전트의 403 처리는 아직 시험하지 못했다
  • v4 고객이 v5 로 올리면 기본값 full 이 들어가 권한이 넓어진다. 갱신 화면에서 수준을 고르게 하는 작업은 남아 있다

6. Read Features — 화면과 읽기 도구

6.1 읽기 구조

클러스터kubectlApplication CRspec · statusGitHub 레포Contents API폴더 · 파일 · 렌더 재료레포 범위 설치 토큰Kukie 서버내부 APIAction Plan 기록에이전트kubectl 결과 10초 공유다시 읽기도 2초 하한커밋 id 로 내용 기억렌더 동시 실행 묶음Secret · 비밀 키 가림ArgoCD 앱 목록 · 상세sync · health · 이미지 · 오류레포 탭트리 · 파일 · 렌더 결과채팅 읽기 도구list · read · renderKukie 기록이 앱의 리소스를 건드린 Plan모두 읽기이므로 승인 카드가 없다 — 앱 목록은 레포 연결 전에도 쓸 수 있고, 레포 탭과 채팅 도구는 레포 연결이 필요하다
세 곳을 읽기만 한다 — 조회 비용은 나눠 쓰기와 크기 상한으로 묶는다

6.2 ArgoCD 앱 목록

ArgoCD 앱 목록 화면
ArgoCD 앱 목록 — 문제 있는 앱이 위로 올라오고, 제한된 권한 클러스터는 사이드바 아래에 흐리게 나온다. 픽스처 화면이다
  • 데이터는 ArgoCD API 가 아니라 클러스터의 Application 리소스에서 읽는다
  • 그래서 ArgoCD API 토큰을 따로 받지 않아도 된다
  • 사이드바에는 ArgoCD 가 있는 클러스터만 나온다
  • 카드에는 Synced · OutOfSync 수, selfHeal 앱 수, 연결한 레포를 보여 준다
  • 표에서는 문제가 있는 앱을 위로 올린다

처음 화면은 ArgoCD 앱이 가리키는 출처 레포를 보여 줬다. 사용자가 이것을 “Kukie 에 연결된 레포"로 착각해서, 카드와 요약줄을 Kukie 에 연결한 레포 기준으로 바꿨다. 두 레포가 다르면 “레포 미연결 → 연결하기"가 나온다.

6.3 앱 상세

앱 상세 패널의 왜 OutOfSync 칸
앱 상세 패널 — OutOfSync 인 리소스와 이유를 보여 준다. 픽스처 화면이다

앱 상세의 칸은 대부분 Application 의 status 에서 나온다.

화면 칸Application 필드
소스spec.source · status.sourceType
sync 설정spec.syncPolicy.automated
리소스 목록, 왜 OutOfSyncstatus.resources[]
마지막 syncstatus.operationState
배포 기록status.history[]
쓰는 이미지status.summary.images
오류와 경고status.conditions[]
  • “왜 OutOfSync"는 status.resources[] 에서 동기화 상태가 OutOfSync 인 줄만 고른 것이다
  • ArgoCD 의 condition 은 이름에 따라 나눈다. *Warning 은 경고로, *Error 는 오류로 표시한다
  • 지워진 namespace 의 앱은 클러스터 연결 실패(502)가 아니라 앱 없음(404)으로 돌려준다. 처음에는 502 로 나가 사용자가 연결 문제로 오해할 수 있었다

6.4 Kukie 기록

앱 상세 패널의 Kukie 기록 칸
Kukie 기록 — 채팅에서 한 변경과 OutOfSync 를 잇는다. 픽스처 화면이며 이름은 예시로 바꿨다
  • 앱의 배포 기록에는 Git 을 거친 변경만 있다. 채팅에서 kubectl 로 바꾼 값은 없다
  • Action Plan 목록에서 이 앱의 리소스를 건드린 것만 골라 함께 보여 준다
  • 그러면 OutOfSync 의 원인이 채팅에서 한 변경인지 바로 알 수 있다
  • 서버에 새 저장소를 만들지 않고 이미 받아 오는 Plan 을 걸러서 만든다
  • Kukie 밖에서 생긴 변경은 Plan 에 없다. 그래서 “Kukie 로 한 변경만 보여요” 문구를 함께 둔다

6.5 kubectl 조회 공유

  • 화면을 켜 둔 사람이 많으면 같은 클러스터에 같은 조회가 반복된다
  • 같은 연결의 앱 목록과 상세는 결과를 10초 동안 나눠 쓴다
  • 나눠 쓰려면 응답에 사람마다 다른 값이 없어야 한다
  • “다시 읽기"를 누르면 새로 읽되, 2초 안의 결과는 그대로 쓴다. 화면이 계속 새로 읽어도 kubectl 이 2초보다 자주 돌지 않는다

6.6 레포 보기

레포 탭의 렌더 결과와 Secret 가림
레포 탭 — 앱 기준 렌더 결과에서 Secret 값과 비밀로 보이는 ConfigMap 을 가린다. 픽스처 화면이다

레포를 연결하면 ArgoCD 화면에 레포 탭이 생긴다. 어느 폴더를 어느 앱이 쓰는지와, 앱이 클러스터에 실제로 넣는 YAML 을 볼 수 있다.

항목방식
폴더펼칠 때 그 폴더만 읽는다. 항목은 300개까지다
파일GitHub Contents API 로 읽는다. 1MB 를 넘으면 내용 없이 이유만 보여 준다
렌더 결과plain 은 그대로, Kustomize 는 kustomize build. 리소스 500개, 2MB 까지다
기억브랜치를 커밋 id 로 바꾼 뒤 (레포, 커밋, 경로)로 기억한다
미지원Helm 과 플러그인 앱은 지원 전으로 표시한다

커밋 id 가 같으면 내용도 같으므로, 같은 커밋의 폴더와 렌더 결과는 다시 받지 않는다.

6.7 Secret 가림

레포에는 비밀값이 섞여 있을 수 있다. 화면과 채팅에 나가기 전에 내용을 가린다.

판단 기준예
파일 이름.env, *.pem, *.key, id_rsa, *.enc, 이름에 sops
리소스 내용kind: Secret, stringData, sops 칸
글자 패턴-----BEGIN … PRIVATE KEY-----, AWS 키, GitHub 토큰 형식
  • 이름과 위치는 보여 주고 값만 가린다. 그래야 레포 구조를 이해할 수 있다
  • 렌더 결과에서도 Secret 리소스의 값은 가린다

6.8 채팅 읽기 도구

레포 탭의 빠른 질문은 “이 파일이 무엇을 하는지 설명해 줘” 같은 문장을 입력창에 넣는다. 처음에는 에이전트에 레포를 읽는 도구가 없어서 클러스터 상태만 보고 짐작해 답했다. 그래서 읽기 도구 셋을 더했다.

도구하는 일
list_repo_dir폴더 목록과 그 경로를 쓰는 ArgoCD 앱
read_repo_file파일 내용 (가림과 크기 상한 적용)
render_appArgoCD 앱의 렌더 결과
  • 읽기 도구이므로 승인 카드 없이 돌고, Private · Shared 대화 모두에서 쓸 수 있다
  • 모델에 넘기는 결과는 24,000자로 자른다
  • 레포 연결 여부와 상관없이 도구 목록은 항상 같다. 프롬프트 캐시는 앞부분이 같아야 재사용되는데, 연결 상태에 따라 도구를 숨기면 앞부분이 달라진다
  • 레포가 연결돼 있지 않으면 “레포를 연결하면 볼 수 있어요"를 결과로 돌려준다

7. Discussion

7.1 확인 범위

내용확인 방법
selfHeal 되돌림과 OutOfSync시험 클러스터에서 실측
tracking-id 형식시험 클러스터에서 실측
ArgoCD 화면의 앱 표시시험 클러스터에서 확인
판별과 경고 카드단위 테스트와 픽스처. 실제 클러스터 시험 기록은 찾지 못했다
레포 보기와 채팅 도구단위 테스트와 픽스처. 실제 클러스터 시험 기록은 찾지 못했다
full → limited실제 스택 변경
limited → full, 403 처리아직 시험하지 않았다

7.2 경고 문구와 시험 결과

  • 카드는 “몇 분 안에 되돌려요"라고 적는다
  • 시험에서는 약 6초 만에 되돌아갔다
  • 사용자가 “몇 분은 유지된다"로 읽을 수 있어서, “곧 되돌려요"가 시험 결과에 더 가깝다

7.3 판별의 한계

  • 허브형 ArgoCD 는 Application 이 대상 클러스터에 없어 sync 정책을 읽지 못한다. 경고 수준이 unknown 에 머문다
  • Application 을 읽을 권한이 없는 클러스터는 판별을 건너뛴다. 이 경우 경고 없이 kubectl 로 실행된다
  • 레이블 추적 방식의 클러스터는 Application 까지 읽어야 판별할 수 있다

핵심 메시지

  • 판단 단위: 클러스터가 아니라 리소스
    • 한 클러스터에도 ArgoCD 가 관리하는 리소스와 아닌 리소스가 섞여 있다
  • 추적 표시: 믿기 전에 대조
    • 어노테이션은 복사될 수 있고, 레이블은 Helm 도 붙인다
  • 경고: 성공처럼 보이는 실패를 미리 알리는 장치
    • kubectl 은 성공하므로, 되돌림은 실행 전에 알려야 한다
  • 권한 설명: 코드의 약속과 역할의 범위 구분
    • 코드가 등록한 클러스터에만 붙여도, 역할이 할 수 있는 범위는 계정의 모든 EKS 다

8. References

selfHeal 이 기본으로 꺼져 있다는 점, 재시도 대기 기본 5초, Git 조회 주기와 prune. 2.2 와 2.4 의 근거다.
→ Argo CD — Automated Sync Policy
기본 추적 방식인 tracking-id 어노테이션과, 여러 Helm 차트가 같은 instance 레이블을 쓰는 문제. 4.2 와 4.3 의 근거다.
→ Argo CD — Resource Tracking
Access Entry 에 접근 정책을 연결하는 방식과 cluster · namespace 범위. 5.1 과 5.3 의 구성이다.
→ Amazon EKS — Associate access policies with access entries
AmazonEKSClusterAdminPolicy 와 AmazonEKSEditPolicy 의 권한 목록. 5.3 표의 근거다.
→ Amazon EKS — Access policy permissions
레포 폴더와 파일을 읽는 API. 6.6 의 레포 보기가 쓴다.
→ GitHub REST API — Repository contents