Files
document-haness/docs/virtualization/tech-log-studio/build-completion-judgment/case/case-an-empty-token-installed-the-agent-anyway.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

9.7 KiB

kind, slug, title, topic, topicName, project, status, sourceRevision, source
kind slug title topic topicName project status sourceRevision source
CASE an-empty-token-installed-the-agent-anyway 빈 토큰이 조용히 흘러갔다 — 설치 출력은 성공이었고 agent 만 5초마다 다시 죽었다 build-completion-judgment 끝났다는 판정 virtualization 게시 전 9465582b5d1630eb4ae7c4e078021486919bf6b6
final/document.md#185-가이드-묶음이-스스로-정한-규약
final/document.md#188-단계-02-k3s-server-와-agent
final/document.md#184-이-부의-출처와-범위

빈 토큰이 조용히 흘러갔다 — 설치 출력은 성공이었고 agent 만 5초마다 다시 죽었다

k3s agent 설치는 오류 한 줄 없이 끝났는데 kubectl get nodes 에는 노드가 하나뿐이었다. 게스트 안에서 친 ssh 가 실패해 토큰이 빈 문자열로 넘어갔고, 설치 스크립트는 그 전까지를 성공으로 찍었다. 값을 찍지 않고 길이만 재는 한 줄을 설치 앞에 두면 잡힌다.

관계

  • 도구가 낸 출력은 대상의 상태가 아니다 — 그 명령이 무엇을 세는지부터 가른다 설치 스크립트의 출력을 노드가 붙었다는 뜻으로 읽은 사건이라 그 규칙의 사례가 된다.
  • 가까운 층부터 한 층씩 건너뛰며 확인한다 — 층마다 성공 신호가 다르다 설치가 끝났다는 출력 대신 각 층에서 무엇이 성공 신호인지로 판정을 옮기는 절차다.
  • cloud-init 이 안 도는 원인은 넷인데 증상은 「SSH 가 안 붙는다」 하나였다 같은 구축의 한 단계 앞에서 벌어진 일이고, 거기서도 실패가 아무 오류 없이 지나갔다.
  • 가이드대로 다시 쳐서 이 실험대가 같은 상태로 서는가 여기 적은 설치 명령을 다시 쳐서 같은 상태가 되는지는 확인된 적이 없다.
  • 단계별 구축 문서는 작성 시점이 아니라 실행 순서로 검증한다 명령을 어느 셸에서 치는지를 단계마다 확인하라는 규칙이고, 그 확인이 빠졌을 때 나온 실패가 이것이다.

문제

k3s agent 설치가 끝까지 돌았고 출력에 오류가 없었다. 그런데 두 노드가 Ready 로 나와야 할 kubectl get nodes 에 두 번째 노드가 나타나지 않았다.

설치 출력의 오류 : x kubectl get nodes 의 노드 수 : 1 k3s-agent 유닛 : 5초마다 재시작 journalctl -u k3s-agent : level=fatal msg="Error: --token is required"

실패가 화면에 나오지 않아 설치는 끝난 것으로 읽힌다.

결론

토큰을 꺼내는 ssh 를 게스트 안에서 쳤고 그 명령이 Host key verification failed. 로 끝났다. 명령 치환으로 감싸면 오류는 stderr 로 흘러가고 변수에는 빈 문자열이 담긴다. 셸은 아무 불평도 하지 않는다.

agent 는 --token '' 을 받아 level=fatal msg="Error: --token is required" 로 죽지만, 설치 스크립트는 그 전까지를 다 성공으로 찍고 끝난다. 그래서 설치 출력만 보면 성공이고, 유닛이 Restart=always 라 5초마다 조용히 재시도한다.

해결 : 게스트에 들어가지 않고 lab host 한 셸에서 ssh kc-lab-1 '...' 형태로 친다 가드 : 값을 쓰기 전에 길이로 가른다. 이 실험대의 토큰은 108자였다 히스토리에 남기지 않으려면 : --token-file 로 넘긴다. 토큰을 꺼낸 셸과 같은 셸에서 쳐야 한다는 제약도 없어진다

검증 환경

호스트 : test-server, Arch Linux CPU : i5-1135G7, 논리 코어 8 RAM : 11,648MiB QEMU : 11.1.1 libvirt : 12.7.0 게스트 : Debian 12 genericcloud k3s : v1.36.4+k3s1 server 노드 : kc-lab-1, 192.168.122.11 agent 노드 : kc-lab-2, 192.168.122.12 토큰 길이 : 108자 토큰 형식 : K10<해시>::server:<비밀번호>

재현 조건

  1. lab host 에서 게스트 세 대를 세우고 kc-lab-1 에 k3s server 를 깐다.
  2. lab host 에서 ssh kc-lab-1 로 게스트에 로그인한다.
  3. 게스트 프롬프트에서 TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token') 을 친다.
  4. echo "${#TOKEN} 자" 가 0 을 낸다.
  5. 같은 셸에서 agent 설치 명령에 --token "$TOKEN" 을 넘긴다. 설치 출력은 오류 없이 끝난다.
  6. lab host 로 돌아와 kubectl get nodes 를 친다. 노드가 하나뿐이다.
  7. ssh kc-lab-2 'journalctl -u k3s-agent' 에서 --token is required 를 찾는다.

본문

토큰이 지나는 길과 명령을 치는 셸

k3s server 는 kc-lab-1 에, agent 는 kc-lab-2 에 깔고 둘 다 lab host 에서 ssh 로 원격 실행한다. 가이드가 코드 블록마다 어느 기계에서 치는지를 붙여 둔 까닭이 여기에 있다 — 게스트는 libvirt NAT(192.168.122.0/24) 안에 있어 워크스테이션에서 직접 닿지 않고, ssh kc-lab-1 이라는 별칭도 lab host 의 ~/.ssh/config 에만 있다.

agent 가 server 에 붙으려면 server 가 만든 node-token 이 필요하고, 그 값을 lab host 에서 꺼내 같은 셸에서 설치 명령에 넘긴다.

TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
echo "${#TOKEN} 자"        # 값이 아니라 길이만 확인한다

비밀은 길이나 존재 여부만 확인하고 값을 찍지 않는다. 가이드가 먼저 정한 표기 규약이고, 값을 찍으면 터미널 스크롤백과 화면 공유에 그대로 남기 때문이다.

ssh kc-lab-2 "curl -sfL https://get.k3s.io | sudo sh -s - agent \
  --server https://192.168.122.11:6443 \
  --token '$TOKEN' \
  --node-ip 192.168.122.12"

게스트 안에서 친 ssh 는 빈 문자열이 된다

토큰을 꺼내려고 게스트에 먼저 들어가면 같은 명령이 다르게 끝난다. 게스트에는 lab host 의 개인키도 ~/.ssh/config 도 없다.

[kc-lab-1] $ ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token'
Host key verification failed.

여기까지는 오류 문구가 찍힌다. 문제는 이 명령을 TOKEN=$(...) 로 감쌌을 때다. 명령 치환은 표준 출력만 변수에 담으므로 오류는 stderr 로 흘러가고 TOKEN 에는 빈 문자열이 담기며, 셸은 아무 불평도 하지 않는다. 뒤이어 치는 설치 명령은 --token '' 을 넘긴 것과 같아진다.

설치 출력이 성공으로 끝나는 경로

빈 문자열을 받은 설치도 끝까지 돈다. agent 가 --token '' 을 받아 level=fatal msg="Error: --token is required" 로 죽는데, 설치 스크립트는 그 전까지를 다 성공으로 찍고 끝나므로 설치 출력만 보면 성공이다. 성공으로 찍힌 것은 내려받기와 유닛 생성과 enable 까지다. 유닛은 Restart=always 라 5초마다 조용히 재시도하고, 그래서 실패가 화면이 아니라 journalctl -u k3s-agent 안에서만 되풀이된다.

k3s server 와 agent 를 세우는 단계가 끝났다는 판정은 lab host 에서 노드 목록을 쳐서 두 노드가 Ready 로 나오는 것이다.

kubectl get nodes -o wide
NAME       STATUS   ROLES           AGE   VERSION        INTERNAL-IP
kc-lab-1   Ready    control-plane   47m   v1.36.4+k3s1   192.168.122.11
kc-lab-2   Ready    <none>          21m   v1.36.4+k3s1   192.168.122.12

빈 토큰으로 깔린 agent 는 이 목록에 올라오지 않는다.

값을 찍지 않고 길이만 잰다

토큰을 화면에 찍어 눈으로 대조하는 방법은 표기 규약이 막아 두었다. 그래서 설치 앞에 길이를 재는 한 줄을 둔다.

[ ${#TOKEN} -ge 50 ] || echo "TOKEN 이 비었다 — 3번으로 돌아간다"

이 실험대의 토큰은 108자였고 형식이 K10<해시>::server:<비밀번호> 라 k3s 판올림에 따라 자릿수가 달라진다. 그래서 이 가드는 값을 맞춰 보지 않고 길이가 0 이 아닌지만 본다.

길이가 0 으로 나오는 길은 셋이다. 가이드는 게스트 안에서 친 경우를 그중 가장 흔하다고 적어 두었고, 나머지 둘은 server 가 아직 안 떠서 토큰 파일이 없거나 새 셸을 열어 변수가 사라진 경우를 가리킨다. 가드 한 줄은 그 몇 분을 막는다 — 설치가 성공으로 끝나고, 노드 목록에서 한 줄이 빠진 것을 알아채고, journalctl 까지 가는 동안이다.

토큰을 파일로 넘기면 같은 셸 제약이 없어진다

위 가드는 TOKEN 이 셸 변수라 토큰을 꺼낸 셸과 같은 셸에서 쳐야 하고, 다른 창에서는 비어 있다. 토큰이 명령줄에 들어가는 것도 걸리면 파일로 넘긴다.

ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token' \
  | ssh kc-lab-2 'sudo tee /tmp/token >/dev/null'
ssh kc-lab-2 "curl -sfL https://get.k3s.io | sudo sh -s - agent \
  --server https://192.168.122.11:6443 --token-file /tmp/token \
  --node-ip 192.168.122.12; rm -f /tmp/token"

토큰이 셸 히스토리에 남지 않고, 변수를 쓰지 않으니 셸이 달라도 된다.

확인하지 못한 것

설치 출력도 journalctl 출력도 final/evidence/ 에 남기지 않았다. level=fatal msg="Error: --token is required"Host key verification failed. 는 SSOT 본문에서 옮긴 것이고 명령 출력 파일이 근거가 아니다. 토큰 108자도 같다.

이 실패를 일부러 다시 만들어 본 기록도 없어서, 빈 토큰으로 설치하면 설치 출력이 성공으로 끝난다는 것은 한 번의 관측이다. 재현이 쉽지 않은 까닭은 이 부의 검증 방식에 있다 — 만드는 명령은 다시 치면 돌고 있는 실험대가 없어지므로 구축할 때 쓴 것을 옮기고 결과 상태를 확인하는 것으로 대신했다. 재현 없이 남길 수 있는 것은 journalctl 쪽이고, 그 출력을 final/evidence/raw/ 에 남기면 진단 절에 증거가 붙는다.

가드 한 줄이 실제로 빈 토큰을 잡아 본 기록도 없다.