Files
document-haness/docs/keycloak-session-store/tech-log-studio/losing-a-node-or-the-store/setup/setup-reproduce-a5-asymmetric-partition.md
T
DongHyeonkaandClaude Opus 5 2109f726fe feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만
있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문
종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다.

SSOT 결함 둘을 고쳤다.

- kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이
  「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽
  `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다.
- virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데
  원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다.

기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은
둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더
든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다).

계약을 셋 고쳤다.

- kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/**
  28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는
  것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온
  것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수
  대조했을 때 가장 가까운 커밋도 28개가 어긋났다).
- virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고
  재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다.
- kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고
  넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다.

style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져
(실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고,
engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고
있었다(Question 기록에서 11.94 → 3.86).

verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:01:55 +09:00

46 KiB

id, kind, slug, title, topic, topicName, project, status, studio, pinnedVersions, source, sourceRevision
id kind slug title topic topicName project status studio pinnedVersions source sourceRevision
b90d719f-39fb-4bab-a263-0e32eedb2b36 SETUP reproduce-a5-asymmetric-partition 한 방향만 끊어 보고 raw PREROUTING 까지 내려간다 losing-a-node-or-the-store PostgreSQL 을 내리고 노드 전원을 뽑았을 때 keycloak-session-store 게시 전 https://hyeonworks.com/studio/documents/b90d719f-39fb-4bab-a263-0e32eedb2b36/edit
name version
Keycloak 26.7.0
name version
curlimages/curl 8.11.1
final/document.md#a층-재현-절차-열-편을-직접-치는-순서-a-5
cdac9b8178391311d8eca1ebc6cac15bb62d79af

한 방향만 끊어 보고 raw PREROUTING 까지 내려간다

iptables 로 Keycloak 두 노드 사이의 JGroups 채널을 끊는 절차다. 주입 넷 중 앞의 둘은 일부러 실패시킨다 — 실패한 주입은 화면에 「아무 일도 없었다」로 보이므로 절차의 절반이 패킷 카운터를 읽는 일이다. 전 구간 약 30분.

관계

  • 노드를 잃는 두 가지 — 저장소가 같이 죽는 것과 들어갈 길이 없는 것 기계를 끄지 않고 네트워크만 끊었을 때 무엇이 다른지를 그 기록과 견주면 갈린다.
  • 주입이 아홉 번 조용히 실패했고 전부 아무 일도 없는 것처럼 보였다 여기서 일부러 밟는 실패 둘이 그 아홉 건에 들어 있다.
  • 예측을 먼저 적고, 주입이 걸렸는지 결과와 따로 확인하고, 대조군 없이 귀속하지 않는다 카운터로 주입을 먼저 판정하고 그다음에 클러스터를 보는 이 순서가 그 기준을 따른다.
  • readiness 가 깨진 노드를 시야에서 먼저 치운다 양방향 차단에서 keycloak-10/1 로 내려가고 정문이 계속 200 을 내는 상태를 이 절차가 만든다.
  • 7800 을 막고 디스커버리와 트랜스포트를 갈라 끊는다 앞 편이고, 거기서 NetworkPolicy 가 기존 연결을 못 끊은 것이 이 편의 출발 조건이다.

본문

읽기 전에 — 어디서 치는가

kubectlkc-lab-1 에서 친다. iptables 는 노드 자체를 건드리는 명령이라 게스트 셸이 필요하고, 두 노드에 각각 넣어야 하며 어느 노드에 넣느냐가 결과를 가른다. 이쪽 노드의 규칙은 [kc-lab-1] 에서 그대로 치고, 반대 노드의 규칙은 ssh kc-lab-2 로 붙어서 친다. 코드블록마다 어느 셸인지 붙여 두었다.

가이드는 터미널 둘을 권한다 — 하나는 상주 탐침 파드용, 하나는 관찰용이다. 다만 이 절차에는 탐침 파드의 셸 안에서 치는 명령이 하나도 없다. a5-probekubectl exec 으로만 쓰므로 명령은 전부 [kc-lab-1] 에서 치고, $K0$K1 도 그 셸의 변수다. 다른 터미널에서 관찰 §7 의 헬스체크를 치면 두 변수가 빈 문자열이라 양쪽 다 안 닿고, 그 화면을 「둘 다 DOWN」으로 읽게 된다. 실제로는 한쪽만 DOWN 이다.

무엇
네임스페이스 keycloak-lab · 관측 스택은 observability
막는 포트 7800(트랜스포트) 과 57800(FD_SOCK2 = bind_port + 50000)
주입 지점 raw PREROUTING. filter FORWARD 는 kube-router 와 경쟁한다
탐침 파드 a5-probecurlimages/curl:8.11.1, sleep 1800, --restart=Never
로그 시각 컨테이너는 UTC. KST 에서 9시간을 뺀다
걸리는 시간 전 구간 약 30분
도구 jq 가 이 실험대에 없다. Prometheus 출력은 trgrep 으로 자른다

이 실험이 가르는 것

A-1 이 답하지 못하고 넘긴 물음에서 출발한다. 가이드는 그 물음을 그대로 인용한다.

keycloak-1 은 멤버가 하나 줄어든 정상적인 사건이라 Ready 를 유지했고, keycloak-0 은 합류 자체를 못 해 DOWN 이 됐다. 양쪽이 동시에 DOWN 이 되는 경로가 있다면 전면 장애다.

A-1 은 도구도 하나 남겼다. NetworkPolicy 는 기존 연결을 못 끊는다 — conntrack 의 ESTABLISHED 가 먼저 통과시킨다. 그래서 이번에는 iptables 로 직접 간다.

그런데 iptables 에도 벽이 셋 있다. 이 절차는 그 셋을 일부러 다시 밟는다.

   실패 ①   filter FORWARD 최상단에 넣었는데 → CNI 가 밀어낸다
   실패 ②   raw 로 옮겼는데도 0 패킷        → 연결 방향을 잘못 짚었다
   성공     수신측 노드의 raw PREROUTING     → 19 패킷
   그런데   그래도 안 갈라진다               → 반대 방향으로 재연결한다

끝나면 이것들을 자기 화면에서 본다 — 규칙을 넣었는데 0 패킷인 상태를 iptables -L -n -v 의 카운터에서, kube-router 가 내 규칙을 아래로 밀어내는 것을 FORWARD 체인의 줄 번호에서, JGroups 연결 방향이 A-1 때와 반대인 것을 conntrack -L 에서, 단방향 차단이 스스로 낫는 것을 뒤집힌 연결에서, coord = t 가 둘인 split brain 을 PostgreSQL JGROUPS_PING 에서, 그런데도 한쪽만 DOWN 이고 외부는 200 인 것을 health/readyendpointslice 에서, MergeView 로 50초 만에 합쳐지는 것을 Keycloak 로그에서.

전제와 되돌리기

  • 05-keycloak06-observability 가 끝나 있다.
  • A-1 을 먼저 해 두면 훨씬 이해가 빠르다. 이 절차는 A-1 이 실패한 곳에서 시작한다.
  • kc-lab-2 에는 ssh kc-lab-2 로 붙는다. 앞 절의 표가 어느 명령을 어느 셸에서 치는지 적는다.

:::warning

이 절차는 Keycloak 클러스터를 실제로 분단시킨다. 실험대에서만 한다.

:::

1. 중단하는 방법을 먼저 확인한다

목적 — 어느 단계에서든 두 노드의 규칙을 한꺼번에 걷어낼 수 있게 해 둔다.

이 실험대는 두 줄로 쳤다. 둘째 줄에 SSH(Secure Shell, 원격 셸) 접속과 원격 셸의 인용과 세미콜론으로 이은 명령 둘이 한꺼번에 들어 있다.

sudo iptables -t raw -F PREROUTING ; sudo iptables -F FORWARD
ssh kc-lab-2 'sudo iptables -t raw -F PREROUTING ; sudo iptables -F FORWARD'

따라 하는 사람은 반대 노드 쪽을 나눈다. 먼저 붙고, 붙은 다음에 두 줄을 따로 친다. 행동 하나가 명령 하나가 된다. 이 나눈 형태는 이 실험대에서 치지 않았다.

이쪽 노드의 규칙을 먼저 걷어낸다.

sudo iptables -S FORWARD
sudo iptables -t raw -F PREROUTING
sudo iptables -F FORWARD

그다음 반대 노드에 붙는다.

ssh kc-lab-2

같은 두 줄을 원격 셸에서 친다.

sudo iptables -t raw -F PREROUTING
sudo iptables -F FORWARD
exit

예상 결과-F 는 아무것도 찍지 않는다. ① 에 무엇이 있었는지가 유일한 기록이므로 건너뛰지 않는다.

왜 필요한가-F FORWARD 는 그 체인 전체를 비운다. 이 실험대의 FORWARD 정책은 ACCEPT 이고 실제 규칙은 kube-router 와 kube-proxy 가 자기 체인에 두므로 잠시 뒤 스스로 복구된다. 그래도 무엇을 지웠는지 모르면 나중에 클러스터가 이상할 때 이 명령 탓인지 가릴 수 없다.

문제가 생기면 — 해제했는데 2~3분째 클러스터가 안 붙으면 반대 노드 규칙이 남아 있는 것이므로 두 노드 모두에서 -t raw -S PREROUTING 을 본다.

주입 전에 같은 명령으로 먼저 본다

주입이 걸리기 전과 후가 화면상 똑같이 보이는 절차다. 먼저 본 것이 없으면 실패를 성공으로 읽는다. 순서는 이렇다.

파드 IP·노드 → 디스커버리(DB) → 클러스터 뷰(로그) → 지표 → 연결 방향 → 밖

1. 파드 IP 와 노드 배치를 지금 다시 뽑는다

무엇을 확인하는가 — 어느 파드가 어느 노드에 있고 IP(Internet Protocol 주소)가 무엇인지.

kubectl -n keycloak-lab get pods -o wide
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
K1=$(kubectl -n keycloak-lab get pod keycloak-1 -o jsonpath='{.status.podIP}')
echo "K0=$K0 K1=$K1"

출력에서 답이 되는 것NODE 열과 파드 번호의 짝이다. 실측은 이렇다.

  keycloak-0=10.42.1.77 (kc-lab-2)   keycloak-1=10.42.0.42 (kc-lab-1)
keycloak-0                  1/1   Running   0               11m
keycloak-1                  1/1   Running   1 (2m48s ago)   155m
postgres-7b474b88c8-9cmsv   1/1   Running   0               14m

이 결과가 뜻하는 것NODE 와 파드 번호가 어긋나 있다. keycloak-0kc-lab-2 에 있다. iptables 를 어느 노드에 넣을지 정할 때 이걸 헷갈리면 규칙은 걸리는데 패킷은 안 걸린다. IP 도 A-1 때와 다르다(10.42.1.43 에서 10.42.1.77 로). 파드가 재시작되면 바뀌므로 여기 적힌 값을 쓰지 말고 ② 로 지금 뽑는다. keycloak-1RESTARTS 가 1 인 것은 A-4 에서 노드를 껐다 켠 흔적이다.

2. 디스커버리와 클러스터 뷰

무엇을 확인하는가 — 지금 코디네이터가 하나인지, 그리고 뷰 ID 가 몇인지.

kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
  -c "select name, ip, coord from jgroups_ping order by name"
kubectl -n keycloak-lab logs keycloak-0 | grep ISPN000094 | tail -1
kubectl -n keycloak-lab logs keycloak-1 | grep ISPN000094 | tail -1

출력에서 답이 되는 것 — ① 의 coord 열과 ② 의 뷰 ID 다. 모양은 이렇고 숫자는 환경마다 다르다.

       name       |       ip        | coord
------------------+-----------------+-------
 keycloak-0-24309 | 10.42.1.77:7800 | f
 keycloak-1-45480 | 10.42.0.42:7800 | t
(2 rows)
ISPN000094: [keycloak-0-24309(v=16.0.12)|13] (2) [keycloak-0-24309(v=16.0.12), keycloak-1-45480(v=16.0.12)]

이 결과가 뜻하는 것coord 열에 t 가 정확히 하나여야 한다. 둘이면 이미 갈라져 있고, 그 상태에서 주입해 봐야 아무것도 판정하지 못한다. 뷰 ID(|13)를 적어 둔다. 이 절차의 판정 기준이 그 숫자의 변화다.

3. 지표는 밖에서 Prometheus 에 묻는다

무엇을 확인하는가 — 두 노드가 보는 클러스터 크기.

Keycloak 컨테이너에는 curlwget 도 없다(exit 127).

kubectl -n observability exec deploy/prometheus -- \
  wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
kubectl -n observability exec deploy/prometheus -- \
  wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size' \
  | tr ',' '\n' | grep -E '"pod":|^"[0-9]'

출력에서 답이 되는 것value 배열의 두 번째 값이다. 모양은 이렇고 값은 환경마다 다르다.

{"status":"success","data":{"resultType":"vector","result":[
{"metric":{"__name__":"vendor_cluster_size","pod":"keycloak-1","node":"kc-lab-1"},"value":[1757037600.123,"2"]},
{"metric":{"__name__":"vendor_cluster_size","pod":"keycloak-0","node":"kc-lab-2"},"value":[1757037600.123,"2"]}]}}

이 결과가 뜻하는 것 — 두 줄이고 값이 둘 다 2 다. 원 실행에서는 이 값이 안 남았다. 값을 뽑으려고 붙인 파이썬 한 줄이 죽으면서 원본까지 같이 사라졌고, 화면에 남은 것은 스택트레이스뿐이다.

Traceback (most recent call last):
  File "<string>", line 3, in <module>
    for r in json.load(sys.stdin)["data"]["result"]: print(f"  cluster_size {r["metric"].get("pod"):12} = {r["value"][1]}")
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

wget 원문을 먼저 보고 나중에 자르면 같은 일이 안 생긴다.

4. 연결 방향 — 이 절차에서 가장 중요한 사전 관측이다

무엇을 확인하는가 — 두 파드 중 어느 쪽이 클라이언트이고 어느 쪽이 서버인지.

이 실험대는 두 줄로 쳤다.

sudo conntrack -L 2>/dev/null | grep 7800
ssh kc-lab-2 'sudo conntrack -L 2>/dev/null | grep 7800'

따라 하는 사람은 둘째 줄을 나눈다. 붙고 나서 원격 셸에서 친다. 이 나눈 형태는 이 실험대에서 치지 않았다.

ssh kc-lab-2
sudo conntrack -L 2>/dev/null | grep 7800
exit

출력에서 답이 되는 것dport=7800 인 쪽이 서버이고 src 가 클라이언트다.

ESTABLISHED src=10.42.1.77 dst=10.42.0.42 sport=60485 dport=7800
            ────────────            ────────────────────
            keycloak-0 가 클라이언트   keycloak-1 이 서버

이 결과가 뜻하는 것 — A-1 때와 방향이 반대다. A-1 에서는 10.42.0.35:40023 → 10.42.1.43:7800, 즉 keycloak-1 이 걸었다. 지금은 keycloak-0 이 건다. JGroups 의 TCP 연결 방향은 고정이 아니라 먼저 뜬 쪽과 먼저 JOIN 을 건 쪽에 따라 달라지고, 파드가 재시작될 때마다 바뀔 수 있다. 가정하지 말고 매번 conntrack -L 로 본다. 2>/dev/nullconntrack 이 stderr 로 찍는 「N flow entries have been shown」 요약을 지우는 것이므로, 처음에는 빼고 쳐서 그 줄도 한 번 본다.

5. 밖에서 보이는 상태

무엇을 확인하는가 — 정문이 지금 무엇을 답하는지.

curl -I --max-time 8 https://auth.hyeonworks.com/realms/master
curl -s -o /dev/null -w '%{http_code}\n' --max-time 8 https://auth.hyeonworks.com/realms/master

출력에서 답이 되는 것200 이어야 한다.

이 결과가 뜻하는 것 — 주입 뒤에도 이 값이 200 으로 남는 것이 이 절차의 결론 하나다. 지금 값을 안 잡아 두면 나중에 그것을 말할 수 없다.

주입

주입은 네 번이고 앞의 둘은 일부러 실패한다. 건너뛰지 않는다 — 이 실패의 모양을 봐 둬야 다음에 자기 규칙을 의심할 수 있다.

넷을 연달아 걸지 않는다. 하나를 걸고 같은 번호의 주입 검증을 친 뒤 그 규칙을 걷어내고 다음으로 간다. 넷이 같은 두 포트를 막으므로 걷어내지 않고 쌓으면 어느 규칙이 잡았는지 가릴 수 없다. 치는 순서는 이렇다.

   주입 ① ─▶ 검증 §1 ─▶ 관찰 §1 ─▶ 주입 ① 의 ④⑤ 로 철거 ─▶ 주입 ② ─▶ 검증 §2 ─▶ 주입 ② 의 ④⑤ 로 철거
                                                                                         │
        ┌────────────────────────────────────────────────────────────────────────────────┘
        ▼
   주입 ③ ─▶ 검증 §3 ─▶ 관찰 §2~§4 ─▶ 주입 ④ (③ 의 규칙은 그대로 둔다) ─▶ 검증 §4 ─▶ 관찰 §5~§7 ─▶ 복구

주입 ③ 의 규칙만 예외다. 네 번째 주입이 「kc-lab-1 의 규칙은 그대로 두고」 반대 방향을 더하는 것이라, 셋째만 걷어내지 않고 이어 간다.

1. 시도 ① — filter 테이블 최상단

목적FORWARD 최상단에 넣으면 conntrack 승인보다 먼저 평가되리라는 가설을 실제로 확인한다.

ssh kc-lab-2 "sudo iptables -I FORWARD 1 -p tcp -d $K0 --dport 7800  -j DROP"
ssh kc-lab-2 "sudo iptables -I FORWARD 1 -p tcp -d $K0 --dport 57800 -j DROP"
date '+%H:%M:%S 주입'

예상 결과iptables 는 아무것도 찍지 않는다. 주입: 12:28:23 만 남는다.

이 줄들은 「전제와 되돌리기」의 중단 절차처럼 나눠 치면 안 된다. 거기는 ssh kc-lab-2 로 붙고 원격 셸에서 쳤지만, 여기는 $K0 가 들어간다. $K0[kc-lab-1] 셸의 변수라 원격 셸에는 없고, 나눠 치면 빈 문자열이 들어가 -d 없는 규칙이 걸린다. 큰따옴표가 그 값을 [kc-lab-1] 에서 펴서 보내므로 한 줄 형태 그대로 친다.

왜 필요한가 — 57800 을 같이 막는 것은 FD_SOCK2(장애 감지 채널)가 bind_port + 50000 을 쓰기 때문이다. 7800 만 막으면 장애 감지는 계속 통해서 분단이 어정쩡해진다.

결과를 본 다음 반드시 걷어낸다. 주입 검증 §1 을 치고 여기로 와서 ④ 와 ⑤ 를 친다. 시도 ② 는 같은 두 포트를 같은 노드에서 다시 막으므로, FORWARD 에 남은 DROP 둘을 그대로 두면 어느 테이블이 패킷을 잡았는지 가릴 수 없다. 치우는 명령은 넣을 때와 인자가 같아야 한다.

ssh kc-lab-2 "sudo iptables -D FORWARD -p tcp -d $K0 --dport 7800  -j DROP"
ssh kc-lab-2 "sudo iptables -D FORWARD -p tcp -d $K0 --dport 57800 -j DROP"
ssh kc-lab-2 'sudo iptables -L FORWARD -n --line-numbers | head -5'

문제가 생기면 — ⑤ 에 DROP 이 남아 있으면 줄 번호로 지운다 — sudo iptables -D FORWARD 3.

2. 시도 ② — raw 테이블로 옮긴다

목적 — conntrack 조회보다 먼저 평가되는 체인에 같은 규칙을 넣는다.

netfilter 의 처리 순서가 그 근거다.

   패킷 도착
     │
     ├─▶ raw     PREROUTING     ← conntrack 보다 먼저. NOTRACK·DROP 용
     │
     ├─▶ conntrack 조회/생성    ← 여기서 ESTABLISHED 가 결정된다
     │
     ├─▶ mangle  PREROUTING
     ├─▶ nat     PREROUTING
     ├─▶ filter  FORWARD        ← NetworkPolicy·kube-router 가 여기 있다
     └─▶ 목적지 파드
어디에 넣는가 기존 연결을 끊는가 CNI 와 경쟁하는가
NetworkPolicy (filter) 못 끊는다 — conntrack 이 먼저 통과시킨다 (A-1) 없음
filter FORWARD 직접 순서에 따라 경쟁한다 (kube-router 가 밀어낸다)
raw PREROUTING 끊는다 없다 — CNI 가 안 쓰는 테이블
ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 7800  -j DROP"
ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 57800 -j DROP"
date '+%H:%M:%S 주입'

예상 결과 — 규칙이 목록에 보이고, 카운터는 0 으로 남는다. 왜 0 인지는 다음 절이 답한다.

왜 필요한가 — 목적지와 노드는 그대로 두고 테이블만 바꿔야 무엇 때문에 결과가 달라졌는지 하나씩 가릴 수 있다.

이 규칙도 반드시 걷어낸다. 주입 검증 §2 를 치고 여기로 와서 ④ 와 ⑤ 를 친다. 네 번째 주입이 kc-lab-2raw PREROUTING 에 글자까지 같은 두 줄을 다시 넣는다. 걷어내지 않으면 같은 규칙이 두 벌 쌓여 카운터가 네 줄로 갈리고, 검증 §4 의 예상 결과는 그 모양을 적어 두지 않았다. 지우기 전에 -L 로 무엇이 있는지 본다. -F 는 체인 전체를 비운다.

ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n --line-numbers'
ssh kc-lab-2 'sudo iptables -t raw -F PREROUTING'

문제가 생기면 — ④ 에 kube-router 규칙이 섞여 있으면 -F 대신 줄 번호로 내가 넣은 둘만 지운다.

3. 성공한 주입 — 수신측 노드의 raw PREROUTING

목적 — 7800 으로 실제로 들어가는 패킷을 잡는다. 목적지 파드가 있는 노드에서 잡아야 한다.

sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 7800  -j DROP
sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 57800 -j DROP
date '+%H:%M:%S 주입'

예상 결과

=== keycloak-1(수신측)으로 들어가는 7800/57800 만 DROP — kc-lab-1 에 넣는다 ===
  주입: 12:33:58

왜 필요한가 — 시도 ② 는 10.42.1.77(keycloak-0)을 목적지로 잡았는데 그것은 이 연결의 출발지다. 7800 으로 들어가는 패킷은 10.42.0.42(keycloak-1) 쪽으로 간다.

   내가 막은 것:  → 10.42.1.77:7800    (그런 패킷이 없다)
   실제 흐름:     → 10.42.0.42:7800    (여기를 막아야 한다)

문제가 생기면 — 앞의 conntrack -L 출력을 다시 본다. 방향이 바뀌었으면 목적지도 바뀐다.

4. 네 번째 주입 — 양방향

목적kc-lab-1 의 규칙은 그대로 두고 kc-lab-2 에 반대 방향을 더해 실제 분단을 만든다.

ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 7800  -j DROP"
ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 57800 -j DROP"
date '+%H:%M:%S 주입'

예상 결과

=== 양방향 차단 — 두 노드 모두에 raw DROP ===
  주입: 12:40:25

왜 필요한가 — 한 방향만 막으면 JGroups 가 열린 방향으로 다시 붙는다. 양쪽을 막아야 coord = t 가 둘이 된다.

문제가 생기면 — 양쪽 카운터를 다 본다. 한쪽만 걸리면 그것은 여전히 단방향이다.

주입 검증

카운터가 유일한 판정 기준이다. 규칙이 목록에 보이는 것은 검증이 아니다.

1. 시도 ① — 넣은 직후에는 맞게 보인다

ssh kc-lab-2 'sudo iptables -L FORWARD -n -v --line-numbers'

넣은 직후의 실측은 이렇다.

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination
1    DROP       6    --  0.0.0.0/0            10.42.1.77           tcp dpt:57800
2    DROP       6    --  0.0.0.0/0            10.42.1.77           tcp dpt:7800
  주입 시각: 12:28:23

여기서 만족하고 넘어가면 속는다. 1~2분 뒤 같은 명령을 다시 친다.

num   pkts bytes target
1      232  377K KUBE-ROUTER-FORWARD  /* kube-router netpol */   ← 다시 1번이 되었다
2        0     0 DROP  tcp dpt:57800
3        0     0 DROP  tcp dpt:7800                              ← 0 패킷

두 열을 동시에 본다.

무엇을 말하는가
num 내 규칙이 1번이 아니다. kube-router 체인이 위로 돌아왔다
pkts 0. 이 규칙에는 패킷이 단 한 개도 도달하지 않았다

kube-router 가 주기적으로 자기 체인을 FORWARD 최상단에 다시 삽입한다. 1번에 넣어도 곧 2번과 3번으로 밀려나고 kube-router 체인이 패킷을 먼저 처리한다. 직접 넣은 iptables 규칙은 CNI(Container Network Interface, 컨테이너 네트워크 플러그인 규격) 가 관리하는 체인과 경쟁하므로, 넣는 것으로 끝나지 않고 패킷 카운터로 확인해야 한다.

이 확인을 담았어야 할 02-injection-verify.txt 는 원 실험 시점에 0바이트로 저장됐다. 리다이렉션이 stdout 만 받았는데 출력이 stderr 로 갔던 것으로 보인다. 지금 그 파일에 들어 있는 것은 사후에 다시 수집한 것이고, 원 시점의 DROP 규칙은 이미 없어서 재현되지 않는다. 남아 있는 사실은 kube-router 체인이 FORWARD 1번을 차지하고 있다는 것 하나이고, 따라 하는 사람은 실제 카운터를 볼 수 있다.

2. 시도 ② — CNI 와 경쟁하지도 않는데 0 이다

ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n -v'
=== [검증] 이번엔 패킷이 걸렸는가 ===
  Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
   pkts bytes target     prot opt in     out     source               destination
      0     0 DROP       6    --  *      *       0.0.0.0/0            10.42.1.77           tcp dpt:57800
      0     0 DROP       6    --  *      *       0.0.0.0/0            10.42.1.77           tcp dpt:7800

앞에서 본 conntrack 이 답이다. -d 10.42.1.77 --dport 7800 은 존재하지 않는 패킷을 노린 규칙이었다. 규칙을 넣은 노드도 틀렸다.

3. 성공한 주입 — 처음으로 숫자가 올라간다

sudo iptables -t raw -L PREROUTING -n -v
   pkts bytes target     prot opt in     out     source               destination
      0     0 DROP       6    --  *      *       0.0.0.0/0            10.42.0.42           tcp dpt:57800
     19  2938 DROP       6    --  *      *       0.0.0.0/0            10.42.0.42           tcp dpt:7800

7800 규칙의 pkts 가 19 다. 57800 이 아직 0 인 것도 정보다 — FD_SOCK2 는 이미 붙어 있는 연결을 쓰고 있어서 새 연결을 시도하지 않았다. 조금 지나면 이쪽에도 숫자가 올라간다.

=== 차단 규칙 누적 카운터 ===
     19  1096 DROP       6    --  *      *       0.0.0.0/0            10.42.0.42           tcp dpt:57800
     21  3058 DROP       6    --  *      *       0.0.0.0/0            10.42.0.42           tcp dpt:7800
pkts 할 일
0 아무것도 측정하지 않았다 해석 금지. 방향과 테이블을 다시 본다
조금씩 는다 재연결 시도가 막히고 있다 관찰로 넘어간다
폭증한다 대상이 너무 넓다 -d--dport 를 좁힌다

4. 양방향 주입 — 양쪽 카운터를 다 본다

sudo iptables -t raw -L PREROUTING -n -v
ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n -v'

한쪽만 걸리면 그것은 여전히 단방향이고, 그 상태에서 클러스터를 봐도 앞 단계와 같은 답만 나온다.

관찰

1. 시도 ① 뒤 — 아무 일도 없다

시도 ① 의 규칙이 FORWARD 에 걸려 있는 동안 친다. 주입 §1 의 ④⑤ 로 걷어낸 뒤에 치면 규칙이 없는 상태를 재게 되는데, 화면은 규칙이 있든 없든 외부 200 이라 틀렸다는 신호가 안 나온다.

kubectl -n keycloak-lab get pods | grep keycloak
curl -s -o /dev/null -w '%{http_code}\n' --max-time 8 https://auth.hyeonworks.com/realms/master
  +25초   | keycloak-0:1/1 keycloak-1:1/1 | 외부 200
  +50초   | keycloak-0:1/1 keycloak-1:1/1 | 외부 200
  ...
  +200초   | keycloak-0:1/1 keycloak-1:1/1 | 외부 200

여기서 「비대칭 차단은 클러스터를 안 가른다」고 결론 내리면 틀린다. 결론이 우연히 맞더라도 근거가 없다 — 규칙에 패킷이 0 개 왔으니 이 관찰은 아무것도 측정하지 않았다.

2. 성공한 단방향 주입 뒤 — 흔들렸다가 스스로 낫는다

같은 두 줄이 다른 답을 낸다.

  +25초  - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200
  +50초  - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200
  +75초  - | keycloak-0:1/1 keycloak-1:0/1 | 외부 200
  +100초  - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200
  +125초  - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200

+75초keycloak-1 이 한 번 0/1 로 흔들렸다가 +100초 에 돌아온다. 주입이 닿기는 했고(시도 ① 의 아무 일 없음과 다르다) 스스로 나았다.

3. 로그 시각은 UTC 다

kubectl -n keycloak-lab logs keycloak-0 --since=20m | grep ISPN000094
kubectl -n keycloak-lab logs keycloak-1 --since=20m | grep ISPN000094

시각을 비교하려다 대부분 한 번은 틀린다.

   당신 셸의 date          12:33:58 KST
   컨테이너 로그의 시각    03:33:58        ← 같은 순간이다. UTC 다

Keycloak 컨테이너는 UTC(Coordinated Universal Time, 협정 세계시)로 찍는다. KST(Korea Standard Time, 한국 표준시)는 UTC+9 이므로 9시간을 빼서 맞춰 본다. 이걸 모르면 주입 전 로그와 주입 후 로그를 정반대로 가른다.

  2026-09-04 03:33:49 | MergeView::[keycloak-0-24309(v=16.0.12)|13] (2) [keycloak-0-24309(v=16.0.12), keycloak-1-45480(v=16.0.12)],

13, 멤버 (2), 그리고 MergeView 다. 해설 문서는 처음에 「주입 이후 뷰 변화가 하나도 없었다」고 썼고 그 자체는 맞는데, 「주입 전부터 그대로」의 「전」이 9초였다.

   03:33:49   MergeView 로 뷰 13 이 만들어짐   ← 그 직전에는 |12] (1), 즉 분단 상태였다
   03:33:58   내 주입                          ← 9초 뒤

앞선 실패한 주입 시도들이 만든 흔들림이 막 봉합된 직후였다. 로그 한 줄만 보고 「변화 없음」이라고 말하지 않고, 그 줄이 언제 생겼는지를 함께 본다. 결론 자체는 유지되지만 먼저 본 상태가 9초짜리였다는 사실을 같이 적어야 정직하다.

4. 왜 안 갈라졌나 — 연결이 뒤집혔다

앞에서 친 것과 똑같은 명령을 다시 친다. 그것이 대조하는 방법이다.

sudo conntrack -L 2>/dev/null | grep 7800
  tcp      6 299 ESTABLISHED src=10.42.0.42 dst=10.42.1.77 sport=48473 dport=7800 src=10.42.1.77 dst=10.42.0.42 sport=7800 dport=48473
  tcp      6 86232 ESTABLISHED src=10.42.0.42 dst=10.42.1.77 sport=44205 dport=57800 src=10.42.1.77 dst=10.42.0.42 sport=57800 dport=44205

srcdst 를 앞의 관측과 나란히 놓는다.

차단 전:  src=10.42.1.77 → dst=10.42.0.42:7800     ← 내가 막은 방향
차단 후:  src=10.42.0.42 → dst=10.42.1.77:7800     ← 열린 방향으로 다시 붙었다

JGroups 는 막힌 연결이 죽자 반대 방향으로 새로 연결했다. FD_SOCK2 가 상대를 의심하기 전에 복구가 끝났고, 의심 카운터가 그것을 뒷받침한다.

kubectl -n observability exec deploy/prometheus -- \
  wget -qO- 'localhost:9090/api/v1/query?query=vendor_jgroups_fd_sock2_get_num_suspected_members'
kubectl -n observability exec deploy/prometheus -- \
  wget -qO- 'localhost:9090/api/v1/query?query=vendor_jgroups_merge3_get_num_merge_events'
  keycloak-0  merge_events=1.0  suspected=0.0
  keycloak-1  merge_events=1.0  suspected=0.0

suspected = 0 이므로 아무도 상대를 의심하지 않았다. 끊긴 적이 없는 것과 같다. merge_events = 1 은 9초 전 병합의 값이다. 한 방향만 막는 것으로는 JGroups 를 가를 수 없다 — 두 노드는 서로에게 연결을 걸 수 있으므로 한쪽 길이 막히면 다른 길로 간다. 운영에서는 단방향 방화벽 오설정이 자가 치유된다는 뜻이고, 분단을 재현하려는 실험자에게는 함정이다.

5. 양방향으로 막으면 갈라진다

kubectl -n keycloak-lab get pods | grep keycloak
kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=keycloak \
  -o custom-columns=ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready
curl -s -o /dev/null -w '%{http_code}\n' --max-time 8 https://auth.hyeonworks.com/realms/master
  +75초  keycloak-0:1/1 keycloak-1:1/1 | ready=[10.42.0.42 10.42.1.77] 외부 200
  +100초  keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200
  +125초  keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200
  ...
  +225초  keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200

keycloak-10/1 로 내려가서 안 돌아오고(단방향 때와 다르다), ready 주소가 둘에서 하나로 줄었으며, 외부는 계속 200 이다. kubectl get endpoints 는 v1.33 부터 deprecated 라 경고가 뜨므로 endpointslice 를 본다.

뷰도 갈린다.

  keycloak-0  [keycloak-0-24309(v=16.0.12)|14] (1) [keycloak-0-24309(v=16.0.12)]
  keycloak-1  [keycloak-1-45480(v=16.0.12)|14] (1) [keycloak-1-45480(v=16.0.12)]

뷰 ID 는 둘 다 14 인데 멤버는 각자 1 명이다. 같은 번호의 다른 세계다.

6. split brain 은 DB 한 줄로 확인한다

kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
  -c "select name, ip, coord from jgroups_ping order by name"
       name       |       ip        | coord
------------------+-----------------+-------
 keycloak-0-24309 | 10.42.1.77:7800 | t
 keycloak-1-45480 | 10.42.0.42:7800 | t
(2 rows)

coord = t 가 둘이다. 앞에서 하나였던 것과 대조한다. 분단을 확인하는 가장 짧은 명령이 이 한 줄이고, 로그를 두 번 긁는 것보다 빠르며 지표보다 정확하다.

7. 그런데 한쪽만 DOWN 이다

Keycloak 컨테이너에 curl 이 없으므로 상주 파드를 띄운다.

kubectl -n keycloak-lab run a5-probe --image=curlimages/curl:8.11.1 \
  --restart=Never --command -- sleep 1800
kubectl -n keycloak-lab wait --for=condition=Ready pod/a5-probe --timeout=120s
kubectl -n keycloak-lab exec a5-probe -- curl -s "http://$K0:9000/health/ready"
kubectl -n keycloak-lab exec a5-probe -- curl -s "http://$K1:9000/health/ready"

일회용 파드를 안 쓰는 까닭은 원 실행이 --rm -it 로 했다가 붙지 못했기 때문이다.

warning: couldn't attach to pod/a5-h, falling back to streaming logs: Internal error occurred: error attaching to container: container is in CONTAINER_EXITED state

파드가 뜬 뒤 명령이 끝나 버리기 전에 붙어야 하는 경주가 된다. 관찰을 여러 번 반복할 것이라면 상주 파드가 항상 낫다.

상주 파드라서 --rm 이 없다. 그래서 이 절차를 두 번째 칠 때는 앞선 실행의 a5-probe 가 같은 이름으로 이미 있어 ① 이 AlreadyExists 로 거절된다. 아래 원상복구 확인표의 삭제 명령을 먼저 치고 ① 로 돌아온다.

--- keycloak-0 ---
{"status":"UP","checks":[{"name":"GracefulShutdown","status":"UP"}
{"name":"KeycloakInitialized","status":"UP"}
{"name":"Keycloakclusterhealthcheck","status":"UP"}

--- keycloak-1 ---
{"status":"DOWN","checks":[{"name":"GracefulShutdown","status":"UP"}
{"name":"Keycloakdatabaseconnectionsasynchealthcheck","status":"UP"}
{"name":"KeycloakInitialized","status":"UP"}

맨 앞의 "status" 를 본다. keycloak-0UP, keycloak-1DOWN 이고, keycloak-1 쪽에서 DB 체크는 UP 이다. DB 때문이 아니라 클러스터 때문이다.

A-1 의 열린 질문에 대한 답이 여기서 나온다.

무엇 keycloak-0 keycloak-1
분단 전 역할 코디네이터 (뷰 13 의 발행자) 일반 멤버
분단 후 자기 인식 「멤버가 하나 나갔다」 — 정상 사건 「코디네이터를 잃었다」 — 비정상
헬스체크 UP DOWN
Service 엔드포인트 남는다 빠진다

Keycloak 의 클러스터 헬스체크는 비대칭이다. 코디네이터였던 쪽은 자기가 정상이라고 보고 잃은 쪽만 DOWN 이 되므로, 완전 분단조차 용량 저하로 끝나고 전면 장애가 되지 않는다. A-2(DB 상실)에서는 양쪽이 동시에 DOWN 이었다. 차이는 DB 가 모두 의존하는 하나인 데 비해 클러스터 멤버십은 서로 상대적이라는 것이다.

복구와 원상복구 확인표

1. 두 노드의 규칙을 걷어낸다

목적 — 양쪽 raw PREROUTING 을 비워 JGroups 가 다시 붙게 한다.

지우기 전에 무엇이 있는지 본다.

sudo iptables -t raw -L PREROUTING -n -v --line-numbers
ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n -v --line-numbers'

그다음 해제 시각을 찍고 양쪽을 비운다.

date '+%H:%M:%S 해제'
sudo iptables -t raw -F PREROUTING
ssh kc-lab-2 'sudo iptables -t raw -F PREROUTING'

예상 결과해제: 12:44:37 이 남고, 25초 간격으로 파드를 보면 이렇다.

  +25초  keycloak-0:1/1 keycloak-1:0/1
  +50초  keycloak-0:1/1 keycloak-1:1/1
  → 복구 완료

50초, 사람 개입 없음. conntrack 은 건드리지 않아도 된다 — 차단이 풀리면 새 연결이 스스로 성립한다.

왜 필요한가 — 두 노드 중 한쪽만 비우면 여전히 단방향 차단이 걸려 있는 것이고, 클러스터는 열린 방향으로 붙어 겉보기에 복구된 것처럼 보인다.

여기서 비우는 것은 raw 뿐이다. 시도 ① 은 kc-lab-2filter FORWARDDROP 둘을 넣었는데 여기의 ④⑤ 는 그 체인을 안 본다. 주입 §1 의 ④ 를 그때 쳤으면 이미 없고, 건너뛰었으면 지금 남아 있다. 아래 원상복구 확인표의 filter 규칙 줄도 kc-lab-1 만 보므로 잡히지 않는다. 남아 있다면 「전제와 되돌리기」 §1 의 중단 절차를 친다 — 그쪽이 두 노드의 두 체인을 다 비운다.

문제가 생기면 — 2~3분째 안 붙으면 반대 노드 규칙이 남아 있다. 두 노드 모두에서 -t raw -S PREROUTING 을 본다.

2. 누가 붙였는지는 MergeView 가 말한다

kubectl -n keycloak-lab logs keycloak-0 | grep MergeView | tail -1
kubectl -n keycloak-lab logs keycloak-1 | grep MergeView | tail -1
  keycloak-0  MergeView::[keycloak-0-24309(v=16.0.12)|15] (2) [keycloak-0-24309(v=16.0.12), keycloak-1-45480(
  keycloak-1  MergeView::[keycloak-0-24309(v=16.0.12)|15] (2) [keycloak-0-24309(v=16.0.12), keycloak-1-45480(

뷰 ID 가 15, 멤버 (2), 양쪽이 같은 줄이다.

[keycloak-0-24309|13] (2)   ← 정상
[keycloak-0-24309|14] (1)   ← 분단. 양쪽이 각자 14 를 발행
MergeView::[...|15] (2)     ← 병합. 뷰 ID 는 계속 증가한다

뷰 ID 는 단조 증가하므로 언제 몇 번 갈라졌는지를 로그만으로 셀 수 있다.

kubectl -n keycloak-lab logs keycloak-0 | grep ISPN100007 | tail -6
[Context=work]            ISPN100007: After merge (or coordinator change) ...
[Context=clientSessions]  ISPN100007: After merge ...
[Context=offlineSessions] ISPN100007: After merge ...
[Context=loginFailures]   ISPN100007: After merge ...
[Context=actionTokens]    ISPN100007: After merge ...

증거 파일의 원문은 한 줄이 길어 잘려 있다. 그 모양도 한 번 본다.

  2026-09-04 03:32:59,874 INFO  [org.infinispan.CLUSTER] (non-blocking-thread--p2-t2) [Context=work] ISPN100007: After merge (or coo

ISPN100007 은 병합 또는 코디네이터 변경 후의 캐시별 토폴로지 재계산이다. 캐시가 여럿이므로 로그도 캐시 수만큼 나오고, 한 줄만 보고 한 번 재분배됐다고 세면 틀린다.

3. 원상복구 확인표

filter 규칙 줄은 kc-lab-1 만 본다. 시도 ① 이 kc-lab-2FORWARD 에 넣은 DROP 둘은 이 표로 안 잡히므로, 그쪽이 의심되면 「전제와 되돌리기」 §1 의 중단 절차를 친다. 반대 노드의 FORWARD 를 조회하는 명령은 원 가이드에 없다(unknown).

항목 명령 돌아왔을 때
raw 규칙 sudo iptables -t raw -S PREROUTING -P PREROUTING ACCEPT
filter 규칙 sudo iptables -S FORWARD | head -5 내가 넣은 DROP 이 없음
(반대 노드) ssh kc-lab-2 'sudo iptables -t raw -S PREROUTING' 같음
파드 kubectl -n keycloak-lab get pods keycloak 둘 다 1/1 Running
디스커버리 psql -c "select name, ip, coord from jgroups_ping order by name" coord = t 가 하나
logs keycloak-0 | grep ISPN000094 | tail -1 멤버 (2), 양쪽 동일
지표 vendor_cluster_size 양쪽 2
Service get endpointslice -l kubernetes.io/service-name=keycloak ready 주소 둘
탐침 파드 kubectl -n keycloak-lab get pod a5-probe 지웠으면 NotFound
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master 200
kubectl -n keycloak-lab delete pod a5-probe --ignore-not-found

막히면

아래는 이 실험대가 실제로 겪은 증상이다. 마지막 줄만 A-2·A-3 에서 겪은 것을 옮겼다 — 탐침 파드를 같은 방식으로 띄우므로 여기서도 그대로 걸린다.

증상 원인 확인
규칙을 넣었는데 아무 일도 없다 카운터가 0 이면 아무것도 측정 안 된 것 iptables -L -n -vpkts
내 규칙이 1번이 아니다 kube-router 가 자기 체인을 재삽입한다 --line-numbers 로 순서
raw 인데도 0 패킷 연결 방향을 잘못 짚었다 conntrack -L | grep 7800
conntrack 에 아무것도 안 보인다 반대 노드에서 봤다 두 노드 모두에서 본다
단방향인데 안 갈라진다 정상이다. 열린 방향으로 재연결한다 conntracksrc/dst 뒤집힘
로그에 변화가 없어 보인다 컨테이너 로그는 UTC. KST 와 9시간 차 logs 의 시각에서 9를 뺀다
「주입 전부터 그대로」인데 미심쩍다 그 「전」이 9초일 수 있다 앞 뷰가 언제 생겼는지 본다
-D 로 규칙이 안 지워진다 넣을 때와 인자가 다르다 줄 번호로 지운다: -D FORWARD 3
임시 파드에 attach 실패 --rm -it 는 경주가 된다 상주 파드를 쓴다
kubectl exec keycloak-0 -- curlexit 127 이미지에 curlwget 도 없다 탐침 파드나 Prometheus
지표를 파이썬으로 자르다 죽었다 원본까지 같이 사라진다 wget 원문을 먼저 본다
get endpoints 가 경고를 찍는다 v1.33 부터 deprecated get endpointslice -l kubernetes.io/service-name=...
57800 카운터만 0 이다 FD_SOCK2 가 아직 재연결을 안 했다 조금 기다렸다 다시 본다
해제했는데 2~3분째 안 붙는다 반대 노드 규칙이 남아 있다 두 노드 모두 -t raw -S PREROUTING
a5-probe 를 다시 못 만든다 앞선 실행의 파드가 그 이름으로 남아 있다 delete pod a5-probe --ignore-not-found

무엇이 관측이고 무엇이 아닌가

  • (observed) 파드 IP 10.42.1.7710.42.0.42 와 노드 배치, 뷰 ID 131415, 시도 ① 의 pkts 0 과 kube-router 가 되찾은 num 1, 시도 ② 의 pkts 0, 성공한 주입의 19 2938 과 이어서 19 109621 3058, 주입 12:28:2312:33:5812:40:25 와 해제 12:44:37, 단방향에서 +75초0/1+100초 의 복귀, 양방향에서 +100초 이후 0/1 고정과 ready 주소 하나, coord = t 둘, 양쪽 헬스체크의 UPDOWN, suspected=0.0merge_events=1.0, 복구 50초, MergeView15, ISPN100007 다섯 캐시, MergeView03:33:49 에 생기고 주입이 03:33:58 인 9초 간격.
  • (unknown) tr ',' '\n' | grep -E 로 자른 Prometheus 출력. 가이드가 미검증으로 표시했다. ssh kc-lab-2 로 들어가 원격 셸에서 conntrackiptables -F 를 따로 치는 두 단계 형태도 이 실험대에서 치지 않았다. 반대 노드의 filter FORWARD 를 조회하는 명령은 원 가이드에 아예 없어서 확인표에 넣지 못했다.
  • 증거가 비어 있는 곳 — 시도 ① 의 카운터를 담았어야 할 02-injection-verify.txt 가 원 시점에 0바이트로 저장됐다. 지금 그 파일에 있는 것은 사후 수집이고 원 시점의 DROP 규칙은 재현되지 않는다.
  • 원 실행에 안 남은 것 — 주입 전 vendor_cluster_size 값. 파이썬 한 줄이 죽으면서 Prometheus 원본까지 함께 사라졌다.
  • 이 절차가 재지 않은 것 — 분단 중에 세션이 어떻게 되는지는 재지 않았다(그것은 A-1 의 주제다). 여기서는 누가 살아남는가만 봤다.