40 · 검증 매트릭스¶
이 도구가 "검증됐다"고 말할 때 그 말이 무엇을 뜻하는지 정의한다. 정의는 문서가 아니라
internal/matrix/matrix.go의 데이터이고, 이 문서는 그 데이터를 읽는 법과 돌리는 법을 적는다.
1. 왜 있는가¶
이 도구는 사람이 머릿속에 담을 수 있는 것보다 형태가 많다. 구축은 맨바닥에서 시작할 수도, 이미 있는 클러스터 위일 수도 있고, 노드는 1대이거나 여러 대이며, 데이터플레인 3종 · 인증서 모드 4종 · 게이트웨이 노출 2종 · 레지스트리 4종이 곱해진다. 전수 조합은 수백 가지고, 수백 번 구축하는 사람은 없다.
그래서 정직한 질문은 "다 돌렸는가"가 아니라 "몇 개를 돌리면 모든 값과, 실제로 깨졌던 모든 조합을 밟는가" 이다.
2026-08 에 시나리오 6개를 손으로 써서 돌렸더니 결함 13건이 나왔다. 그중에는
모든 신규 구축을 실패시키던 것(set -e 아래의 명령 치환)도 있었다. 결함들은
난해하지 않았다 — 아무도 걸어보지 않은 길이었을 뿐이다. 매트릭스는 그 길이
"아무도 안 걸어본 길"이 아니게 만드는 장치다.
2. 차원¶
| 차원 | 값 |
|---|---|
nodes |
1, 2 |
dataplane |
cilium-gw, cilium-traefik, canal-traefik |
pki |
none, private-ca, byo-cert |
exposure |
node-ips, lb-pool, none |
registry |
embedded, upstream |
gitops |
true, false |
network |
online, airgap |
operation |
build, grow, resume, reapply, upgrade |
operation 이 차원인 이유: 이번에 나온 결함 대부분은 첫 구축이 아니라 그 다음에
한 일에서 나왔다. 노드 증설, 중단 후 재개, 같은 문서 재적용은 각각 다른 코드
경로다.
network 가 차원인 이유: 문서에 단어 하나 바꿔 적는 것이 아니라 하네스가 실행
전에 노드의 egress 를 DROP 한다. 밖으로 나가려는 스텝은 고객사에서가 아니라
여기서 실패한다. 이 축 하나가 스토리지 이미지 다섯 개 누락, 프리플라이트가 자기
반입물을 거부하는 결함 등을 드러냈다 — 온라인에서는 전부 보이지 않는 것들이다.
3. 선정 규칙¶
케이스는 다음 두 조건을 만족해야 한다. 둘 다 internal/matrix 의 테스트가
하드웨어 없이 매 커밋 검사한다.
- 모든 차원의 모든 값이 최소 한 케이스에 등장한다 (
Uncovered) RequiredPairs의 모든 조합이 한 케이스 안에서 함께 등장한다 (UncoveredPairs)
2번이 핵심이다. 값별 커버리지만으로는 이번 교착을 못 잡는다 — 단일 노드도 통과하고 Gateway API 도 통과하는데, 둘이 만나면 cilium-operator 가 복제본 2를 한 노드에 놓지 못해 재시작이 영원히 안 끝났다. 그래서 실제로 깨졌던 조합은 목록으로 고정한다.
각 케이스에는 Why 가 필수다. 근거를 적지 않은 케이스는 다음에 누가 지운다.
4. 돌리는 법¶
scripts/build.sh # 운영자가 쓰는 그 바이너리
NODE_PASSWORD=... scripts/matrix.sh # 전 케이스
NODE_PASSWORD=... scripts/matrix.sh -run idc-single # 한 케이스
노드 주소 외에 지정하는 값들. 기본값이 문서용 대역(RFC 5737)인 것은 공개
저장소의 기본값이 실재하는 주소를 겨누지 않게 하기 위해서다 — 그대로 두면
kube-vip 이 붙을 인터페이스가 없어 VIP 케이스가 vip-interface 에서 멈춘다.
| 변수 | 뜻 |
|---|---|
MALMOK_LAB_VIP |
VIP 케이스가 쓸 주소. 노드와 같은 세그먼트의 빈 주소 |
MALMOK_LAB_LB_POOL |
로드밸런서 풀 CIDR |
MALMOK_LAB_AIRGAP_VERSION |
노드에 반입해 둔 RKE2 릴리스. 없으면 에어갭 케이스는 무엇을 놓아야 하는지 말하고 skip 한다 |
MALMOK_LAB_MIRROR |
pull-through 캐시 주소. 기본 꺼짐 — 캐시를 통과한 초록은 캐시 없는 고객의 설치를 증명하지 않는다 |
에어갭 케이스는 두 가지를 노드에 미리 놓아야 한다. RKE2 릴리스 아티팩트와,
플랫폼 이미지 번들이다. 번들은 MALMOK_IMAGE_ARCHES=amd64 scripts/airgap-images.sh <tag>
로 만들어 아티팩트 옆에 둔다 — 케이스가 와이프 뒤에 /var/lib/rancher/rke2/agent/images/
로 옮긴다. 미리 거기 넣어둘 수 없다: 와이프가 /var/lib/rancher 를 통째로 지운다.
- 케이스마다 두 노드를 완전 초기화하고 재부팅한다. 따라서 파괴해도 되는
구간에만 겨눈다 (
MALMOK_LAB_SERVER/MALMOK_LAB_AGENT로 지정). - 재부팅은 장식이 아니다.
rke2-uninstall은 Cilium 이 NIC 에 붙인 tc/eBPF 프로그램을 걷어내지 않고, 남은 프로그램이 다음 구축의 노드 간 통신을 떨군다 — 포트 검사가 그것을 방화벽으로 보고한다. - 하네스는 빌드된 바이너리를 실행한다. 엔진 패키지를 직접 부르면 아무도 쓰지 않는 경로를 검증하게 된다.
- RKE2 버전은 채널 서버에서 읽는다. 버전을 상수로 박은 스위트는 사람들이 실제로 설치하는 버전을 검증하지 않는다.
- 인증서 자재는 매번 생성한다 (root → intermediate → wildcard leaf). 디스크에 둔 자재는 만료되고, 아무도 안 건드렸는데 반년 뒤 실패하는 스위트는 사람들이 믿지 않게 된다.
5. 케이스가 통과했다는 것¶
작업별로 하는 일이 다르다.
| operation | 하는 일 |
|---|---|
build |
맨바닥에서 구축 |
grow |
1노드로 구축 후, 문서에 에이전트를 추가해 재적용 |
resume |
l1-bootstrap/service 시작 시점에 SIGKILL, 같은 명령으로 재실행 |
reapply |
구축 후 같은 문서를 다시 적용 — 바뀐 스텝이 0이어야 통과 |
upgrade |
이전 마이너로 구축한 뒤 현재 릴리스로 이동 — 전 노드의 kubelet 이 새 버전을 보고해야 통과. stable 에서 출발하면 stable 과 latest 가 같은 릴리스를 가리키는 동안 매번 skip 했고, 실제로 매트릭스 전 실행에서 한 번도 돌지 않았다 |
그 뒤 공통 확인 (전부 운영자 계정에서 sudo 없이):
- Ready 노드 수가 케이스가 말한 수와 같다
kubectl이 PATH 에 있다 (kubeconfig 만 있고 도구가 없으면 그 클러스터는 구축한 기계에서 못 쓴다)- cilium-gw 케이스는 GatewayClass 가 Accepted
- 모든 파드가 Running 또는 Completed 로 안정된다. Pending 은 통과가 아니다 — 이 도구가 요청했는데 클러스터가 주지 못한 파드다. 30초 간격으로 30회까지 기다린다: 세 초짜리 ContainerCreating 을 결함으로 보고하지 않기 위해서다
6. 매트릭스를 넓힐 때¶
Values()에 값을 추가한다 → 커버리지 테스트가 실패한다- 그 값을 밟는 케이스를
Cases()에 추가하고Why를 적는다 - 그 값이 다른 값과 만나야만 깨지는 성질이면
RequiredPairs()에 넣는다
이 순서를 지키면 "값을 추가했는데 아무도 안 돌리는" 상태가 존재할 수 없다.
7. 아직 매트릭스 밖인 것¶
정직하게 적는다. 이것들은 하드웨어나 외부 서비스가 없어서 빠졌다.
- 3서버 HA 조인 (
l1-join-server) — 세 번째 VM 필요 - 프록시 망 모드 — 프록시 필요. 폐쇄망은
network축으로 들어왔다 - acme-dns01 — 공인 DNS 와 ACME 계정 필요
- external / internal 레지스트리 — Harbor · Hauler 필요
byo-csi— 현장 CSI 필요. 이 단계는 StorageClass 존재만 확인한다- longhorn · nfs 스토리지 — 이번 릴리스가 설치하지 않는다. 문서가 지정하면 StorageClass 없는 클러스터를 만드는 대신 실행을 멈춘다