The guides were written before the skill existed and a scored audit found
three gaps. Fixed by a subagent running under the skill, with measured
values, versions, IPs and quoted output declared off limits.
The big one: every step showed a command and its output, and almost none
said which line to look at or what it meant. 64 interpretation pairs added
across the seven files, weighted where the reading is hardest — 20 in the
Keycloak stage, where a Secret existing and a pod having received it are
different facts.
Only the extracting form of curl appeared. Where the reader meets a response
for the first time the guides now open with curl -I or curl -v and name the
lines worth reading; -w '%{http_code}' survives only where the code is a
value being compared — two upstream nodes against each other, or the 900-run
control loop.
Listing Secret keys went from a three-stage pipe to kubectl describe secret,
which prints the key names and their byte counts in one native command
without exposing a value.
And a tool assumption: jq and yamllint are installed on neither the lab host
nor the guests. The guides now say so where JSON is read by eye, rather than
sending the reader to install something mid-diagnosis. cloud-init schema is
on the guests and is now the guest-side check.
Also removes a stray Playwright screenshot committed at the repository root
in 919547a; the evidence copy under docs/evidence/b7a-orphan-session/ is the
one the document references.
Four things the audit left standing are recorded in the agent's report rather
than papered over — notably that 04's reload measurements are stated without
a reproduction procedure, and that 05 and 06 reference each other as
prerequisites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.8 KiB
02 — k3s 두 노드
이 단계가 끝나면
kubectl get nodes 에 두 노드가 Ready 로 나온다.
전제
01 이 끝나 두 게스트에 SSH 가 붙는다.
1. server 를 깐다 (kc-lab-1)
하기
ssh kc-lab-1
curl -sfL https://get.k3s.io | sudo sh -s - server --node-ip 192.168.122.11
--node-ip 를 준다. 게스트에 인터페이스가 여럿이면 k3s 가 엉뚱한 것을 고를 수
있고, 그러면 두 노드가 서로를 다른 주소로 알게 된다.
확인 — 서버가 떴고 자기 자신을 노드로 등록했는가
sudo systemctl is-active k3s
sudo kubectl get nodes
어디를 봐야 하는가 — 유닛이 active 인가, 그리고 get nodes 에
kc-lab-1 한 줄이 Ready 로 있는가. 설치 직후 30초 남짓은 NotReady
이거나 아예 목록이 비어 있는 것이 정상이다 — CNI 가 아직 안 올라온
시간이다. 한 번 더 친다.
이 결과가 의미하는 것 — active + Ready 면 API 서버가 살아 있고
kubeconfig 도 자리를 잡았다는 뜻이라 2번으로 간다. 유닛이 active 인데
get nodes 가 접속 오류를 내면 API 서버가 아직 기동 중이다. 유닛이
activating 에서 안 넘어가거나 failed 면 설치 자체가 실패한 것이니
로그를 본다.
sudo journalctl -u k3s -n 50 --no-pager
2. 토큰을 꺼낸다
하기 — 화면에 찍어 눈으로 옮기지 말고 변수로 받는다
TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
echo "${#TOKEN} 자" # 값이 아니라 길이만 확인한다
실측 — 이 실험대에서는 56자였다.
어디를 봐야 하는가 — 찍히는 것은 자릿수 하나뿐이다. 값은 보지 않는다 — 화면에 띄우는 순간 터미널 스크롤백과 셸 히스토리에 남는다.
이 결과가 의미하는 것 — 두 자리 수가 나오면 토큰을 손에 쥔 것이니 3번으로
넘어간다. 0 이면 변수가 비었다는
뜻이고 원인은 둘 중 하나다: server 가 아직 안 떠서 파일이 없거나, sudo 가
비대화식 SSH 에서 비밀번호를 물어 실패했거나. 어느 쪽인지는 파일부터 본다.
ssh kc-lab-1 'sudo ls -l /var/lib/rancher/k3s/server/node-token'
3. agent 를 붙인다 (kc-lab-2)
하기 — 토큰을 그대로 넘긴다
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 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"
확인 — agent 가 클러스터에 들어왔는가, 그리고 제 주소로 들어왔는가
sudo kubectl get nodes -o wide
실측
kc-lab-1 Ready control-plane v1.36.4+k3s1 192.168.122.11
kc-lab-2 Ready <none> v1.36.4+k3s1 192.168.122.12
어디를 봐야 하는가 — -o wide 를 준 이유가 마지막 열이다. INTERNAL-IP
두 개가 1번·3번에서 --node-ip 로 준 값과 같은가. 그다음이 STATUS 두 줄
Ready, 그다음이 ROLES 열이다. <none> 은 오류가 아니라 역할 라벨이
없다는 뜻이다 — agent 는 원래 그렇다.
이 결과가 의미하는 것 — 두 줄이 Ready 이고 IP 가 맞으면 이 단계는 끝났다.
IP 가 다른 대역(예: flannel 이나 다른 인터페이스 주소)으로 잡혀 있으면
지금은 아무 증상이 없다가 03 의 nginx upstream 과 A층의 노드 상실
실험에서 어긋난다 — 그때 고치는 것보다 지금 재설치가 싸다. kc-lab-2 가
아예 안 보이면 join 이 실패한 것이니 agent 쪽 로그를 본다.
ssh kc-lab-2 'sudo journalctl -u k3s-agent -n 30 --no-pager'
4. 유닛 이름이 다르다
| 노드 | 유닛 |
|---|---|
| server | k3s.service |
| agent | k3s-agent.service |
확인 — 어느 노드가 무슨 이름으로, 어떤 인자로 돌고 있는가
ssh kc-lab-1 'systemctl cat k3s | grep -A3 ExecStart='
ssh kc-lab-2 'systemctl cat k3s-agent | grep -A4 ExecStart='
실측
ExecStart=/usr/local/bin/k3s server '--node-ip' '192.168.122.11'
ExecStart=/usr/local/bin/k3s agent '--node-ip' '192.168.122.12'
어디를 봐야 하는가 — ExecStart= 줄의 부분명령(server/agent)과
그 뒤의 인자. 설치 스크립트에 준 옵션이 여기 그대로 굳어 있다. 유닛 이름을
틀리면(systemctl cat k3s 를 agent 노드에서) No files found 가 나오는데,
그것 자체가 「이 노드는 agent 다」라는 답이다.
이 결과가 의미하는 것 — 3번의 get nodes -o wide 는 k3s 가 보고한
IP 이고, 이 줄은 우리가 준 IP 다. 둘이 다르면 옵션이 안 먹은 것이다.
그리고 뒤의 실험에서 노드를 멈출 때 칠 유닛 이름이 노드마다 다르다는 것을
여기서 확인해 둔다 — systemctl stop k3s 를 agent 노드에서 치면 아무 일도
일어나지 않고, 「주입했는데 증상이 없다」로 오독하게 된다.
이 차이가 A-4 에서 결과를 갈랐다. server 노드를 잃으면
kubectl자체가 불통이 되고, agent 를 잃으면kubectl은 되지만 그 위의 워크로드가 사라진다.
5. k3s 가 기본으로 딸려 오는 것
따로 설치하지 않아도 이미 있다.
| 무엇 | |
|---|---|
| Traefik | 인그레스 컨트롤러. :80 을 듣는다 |
| servicelb (klipper-lb) | LoadBalancer 타입을 호스트 포트로 매핑 |
| local-path | 기본 StorageClass. 노드 로컬 디스크 |
| flannel | 파드 네트워크 (VXLAN) |
| kube-router | NetworkPolicy 집행 |
확인 — 무엇이 이미 돌고 있고, 기본 저장소가 무엇인가
sudo kubectl get pods -A
sudo kubectl get storageclass
어디를 봐야 하는가 — get pods -A 에서는 NAMESPACE 열이 kube-system
인 줄들의 STATUS. Running 과 Completed 가 섞여 있는 것이 정상이다 —
helm-install-traefik-* 는 일회성 잡이라 Completed 로 남는다.
get storageclass 에서는 이름 뒤의 (default) 표시가 어디 붙어 있는가.
이 결과가 의미하는 것 — 여기 뜬 것들은 우리가 안 깔았는데 있는 것이고,
뒤 단계에서 「왜 80 포트가 이미 잡혀 있지」·「왜 PVC 가 이 노드에만 묶이지」의
답이 전부 이 목록에 있다. local-path 에 (default) 가 붙어 있으면
05 의 PVC 는 StorageClass 를 안 적어도 이것으로 만들어진다. Pending 이나
CrashLoopBackOff 가 섞여 있으면 그 파드부터 describe 로 본다.
local-path가 기본이라는 것이 A-4 에서 비용을 청구한다. PVC 가 만들어진 노드에 묶여 다른 노드로 재배치되지 않는다.
6. 워크스테이션에서 쓰려면
하기
ssh kc-lab-1 'sudo cat /etc/rancher/k3s/k3s.yaml' > ~/.kube/kc-lab.yaml
sed -i 's|127.0.0.1|192.168.122.11|' ~/.kube/kc-lab.yaml
export KUBECONFIG=~/.kube/kc-lab.yaml
kubeconfig 안의 서버 주소가 127.0.0.1 이다. 게스트 안에서만 맞는 주소라
밖에서 쓰려면 바꿔야 한다.
확인 — 밖에서 붙는가
grep server: ~/.kube/kc-lab.yaml
kubectl get nodes
어디를 봐야 하는가 — 첫 줄의 server: 값이 https://192.168.122.11:6443
인가(127.0.0.1 이 남아 있으면 sed 가 안 먹은 것이다). 그다음 get nodes
가 3번과 같은 두 줄을 내놓는가. 이제부터는 sudo 도 ssh 도 붙이지
않는다.
이 결과가 의미하는 것 — 통과하면 워크스테이션에서 곧장 클러스터를 볼 수
있고, 05 이후의 kubectl 은 전부 여기서 친다. connection refused 면 주소는
맞는데 API 서버가 안 뜬 것이고, 타임아웃이면 워크스테이션에서
192.168.122.0/24 로 가는 경로가 없는 것이다(호스트를 거치는 SSH 터널이나
ProxyJump 가 필요하다). x509 오류면 파일 안의 CA 와 서버가 지금 쓰는 CA 가
어긋난 것이다 — k3s 를 다시 깔았다면 kubeconfig 도 다시 복사해야 한다.
막히면
| 증상 | 원인 | 확인 |
|---|---|---|
agent 가 NotReady |
토큰·주소 오타 | journalctl -u k3s-agent -n 30 |
| 노드 IP 가 예상과 다름 | --node-ip 없이 설치 |
kubectl get nodes -o wide |
| 밖에서 kubectl 이 안 붙음 | kubeconfig 의 127.0.0.1 |
위 6번 |
| 파드가 한 노드에만 몰림 | 스케줄러 판단 | topologySpreadConstraints 로 강제 |