LabHub

CAPA — Argo 프로젝트 인증 어소시에이트 · AppProject·ApplicationSet·멀티테넌시 · 이론

화이트리스트와 블랙리스트는 방향이 반대다

LabHub 에서 이어서 보기

한 줄 요약

AppProject 는 "이 팀이 어디서 가져와 어디에 놓을 수 있는가" 를 정하는 경계이고, ApplicationSet 은 그 경계 안에서 Application 을 대량으로 찍어내는 공장이다. AppProject 에서 가장 많이 틀리는 지점은 클러스터 리소스는 화이트리스트(적힌 것만 허용)이고 네임스페이스 리소스는 블랙리스트(적힌 것만 금지)라는, 방향이 반대인 두 필드다.

왜 이게 필요했나

Argo CD 에 팀이 하나뿐일 때는 default 프로젝트로 충분하다. 팀이 셋이 되는 순간 질문이 생긴다. A팀이 실수로 B팀 네임스페이스에 배포하면? 누군가 개인 GitHub 저장소를 소스로 등록하면? 앱 하나가 ClusterRoleBinding 을 만들어 스스로 cluster-admin 이 되면? RBAC 만으로는 이걸 못 막는다. Argo CD 의 서비스어카운트는 어차피 강한 권한을 갖고 있고, 사용자는 Argo CD 를 통해 그 권한을 빌려 쓰는 구조이기 때문이다. 그래서 Argo CD 는 자기 계층에서 한 번 더 거른다. 그 거름망이 AppProject 다.

ApplicationSet 은 다른 종류의 고통에서 나왔다. 클러스터 다섯 개에 서비스 스무 개면 Application 이 백 개다. 손으로 관리할 수 없고, 새 클러스터가 붙을 때마다 스무 개를 복사해야 한다. 제너레이터는 이 반복을 데이터로 바꾼다.

어떻게 동작하나

AppProject 의 핵심 필드를 방향까지 함께 기억해야 한다.

| 필드 | 방향 | 뜻 |
| --- | --- | --- |
| sourceRepos | 허용 목록 | 여기 적힌 패턴의 저장소만 소스로 쓸 수 있다 |
| destinations | 허용 목록 | 여기 적힌 클러스터·네임스페이스에만 배포할 수 있다 |
| clusterResourceWhitelist | 허용 목록 | 여기 적힌 클러스터 스코프 종류만 만들 수 있다 |
| namespaceResourceBlacklist | 금지 목록 | 여기 적힌 네임스페이스 스코프 종류만 못 만든다 |

세 번째와 네 번째가 반대다. 클러스터 리소스는 기본이 전면 금지이므로 Namespace 하나만 적으면 그것만 열린다. 네임스페이스 리소스는 기본이 전면 허용이므로 ResourceQuota 를 적으면 그것만 닫힌다. 이 방향을 헷갈리면 "막았다고 생각한 것이 열려 있는" 상태가 되는데, 정책이 없는 것보다 나쁘다.

syncWindows 는 시간대로 동기화를 열고 닫는다. 규칙 하나만 기억하면 된다. deny 가 allow 를 이긴다. 평일 업무시간 allow 와 주말 deny 가 겹치면 겹친 구간은 금지다. manualSync: true 를 붙이면 그 창에서 자동 동기화는 막고 사람이 누르는 것만 허용한다.

ApplicationSet 의 제너레이터는 list(정적 목록), clusters(등록된 클러스터), git(디렉터리 스캔 또는 파일 읽기), pullRequest(PR 프리뷰), scmProvider(조직의 저장소 자동 발견) 가 기본이고, 여기에 두 종류의 조합기가 붙는다. matrix 는 두 제너레이터의 곱집합이라 클러스터 셋과 앱 넷이면 열둘이 나온다. merge 는 mergeKeys 로 조인해서 같은 키를 가진 항목의 값을 덮어쓴다. 전 클러스터 공통값을 깔고 특정 클러스터만 다르게 줄 때 쓰는 것이 merge 다.

템플릿 엔진의 기본은 fasttemplate 이고 이것은 변수 치환만 한다. 조건이나 반복이 필요하면 spec.goTemplate: true 를 켜야 하며, 켜는 순간 참조 문법도 점이 붙은 형태로 바뀐다. goTemplateOptions 에 missingkey=error 를 주면 없는 키를 조용히 빈 문자열로 만드는 대신 에러를 내는데, 이름이 빈 Application 이 생기는 사고를 막아 준다.

app-of-apps 는 Application 이 다른 Application 들을 담은 디렉터리를 가리키는 패턴이다. 부트스트랩 한 번으로 전체 플랫폼이 올라오는 대신, 부모를 지우면 자식이 전부 prune 대상이 되므로 부모의 prune 설정을 특히 조심해야 한다.

현장에서 만나는 모습

저자 홈랩은 클러스터가 하나지만 네임스페이스로 테넌트를 나눈다. 여기서 배운 것은 AppProject 가 그리는 경계와 실제 자원 경계가 다른 물건이라는 점이다. AppProject 의 destinations 는 "capa-team-alpha 에 배포해도 된다" 는 문법적 허가일 뿐, 그 네임스페이스가 노드 자원을 얼마나 먹을지는 전혀 통제하지 못한다. 자원 경계는 ResourceQuota 와 LimitRange 가 만든다. 그런데 AppProject 의 namespaceResourceBlacklist 에 ResourceQuota 를 넣는 것이 정석인 이유가 여기서 나온다. 테넌트가 자기 쿼터를 스스로 늘려 버리면 쿼터가 아니기 때문이다. 쿼터는 플랫폼 팀이 만들고 테넌트는 못 만지게 하는 것이 이 두 필드의 조합이다.

GPU 노드가 섞인 클러스터에서는 한 걸음 더 나갔다. GPU Feature Discovery 가 붙여 주는 라벨은 문자열이라 "24GB 이상" 같은 비교 셀렉터가 안 되기 때문에, 노드에 gpu.homelab/tier 같은 의미 기반 라벨을 직접 붙여 워크로드가 자기 체급을 고르게 했다. ApplicationSet 의 list 제너레이터에 테넌트별로 이런 라벨 값을 실어 보내면, 하나의 템플릿으로 서로 다른 노드 그룹에 앉는 앱들을 찍어낼 수 있다.

다음 실습에서 할 것

/root/capa-project/ 에 AppProject 하나와 ApplicationSet 두 개(list 제너레이터, matrix 제너레이터)를 작성하고, 그 프로젝트가 허가하는 테넌트 네임스페이스 3개와 각각의 ResourceQuota 를 실제 클러스터에 올려 문법적 경계와 자원 경계를 나란히 세워 본다.