The experiment documents record what was found. These record what to type to
reproduce it, in folders per stage.
Two kinds of command are kept apart. 하기/확인 is what somebody actually types
at a terminal — short, one at a time. 근거를 재려면 is the long measuring form
this lab used to put evidence in a document, marked as not needed day to day.
The same split applies to curl: -I to look once, -w '%{http_code}' only when
comparing across repetitions.
No placeholders. Where a value is needed the command that produces it is
given, and secrets are checked by length rather than printed:
TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
echo "${#TOKEN} 자"
Stage 05 verifies resources in layers, because a Secret existing and a pod
having received it are different facts: keys, then length, then the value
inside the container, then which env var came from which Secret. Same for
workloads — Deployment to ReplicaSet to Pod, with the seven ReplicaSets this
cluster actually carries as the worked example.
Two commands were wrong and re-running them caught it. kubectl get endpoints
prints a deprecation warning on v1.33+, so the guide uses describe svc and
EndpointSlice. And the Keycloak image has no curl, so reading metrics from
inside the container fails with exit 127 — the guide asks Prometheus instead,
or runs a throwaway curl pod.
Read-only checks were executed against the running lab and their output is
quoted verbatim. Creating commands could not be re-run without destroying the
lab, so they are the ones used at build time; the README says which is which.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
137 lines
3.9 KiB
Markdown
137 lines
3.9 KiB
Markdown
# 02 — k3s 두 노드
|
|
|
|
## 이 단계가 끝나면
|
|
|
|
`kubectl get nodes` 에 두 노드가 `Ready` 로 나온다.
|
|
|
|
## 전제
|
|
|
|
[01](../01-vms/) 이 끝나 두 게스트에 SSH 가 붙는다.
|
|
|
|
---
|
|
|
|
## 1. server 를 깐다 (kc-lab-1)
|
|
|
|
**하기**
|
|
```bash
|
|
ssh kc-lab-1
|
|
curl -sfL https://get.k3s.io | sudo sh -s - server --node-ip 192.168.122.11
|
|
```
|
|
|
|
`--node-ip` 를 준다. 게스트에 인터페이스가 여럿이면 k3s 가 엉뚱한 것을 고를 수
|
|
있고, 그러면 두 노드가 서로를 다른 주소로 알게 된다.
|
|
|
|
**확인**
|
|
```bash
|
|
sudo kubectl get nodes
|
|
sudo systemctl is-active k3s
|
|
```
|
|
|
|
## 2. 토큰을 꺼낸다
|
|
|
|
**하기** — 화면에 찍어 눈으로 옮기지 말고 변수로 받는다
|
|
```bash
|
|
TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
|
|
echo "${#TOKEN} 자" # 값이 아니라 길이만 확인한다
|
|
```
|
|
|
|
**실측** — 이 실험대에서는 56자였다. 0 이면 server 가 아직 안 떴거나 경로가 다르다.
|
|
|
|
## 3. agent 를 붙인다 (kc-lab-2)
|
|
|
|
**하기** — 토큰을 그대로 넘긴다
|
|
```bash
|
|
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"
|
|
```
|
|
|
|
> 토큰을 셸 히스토리에 남기고 싶지 않으면 파일로 넘긴다.
|
|
> ```bash
|
|
> 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"
|
|
> ```
|
|
|
|
**확인** — server 쪽에서
|
|
```bash
|
|
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
|
|
```
|
|
|
|
`<none>` 은 오류가 아니라 **역할 라벨이 없다**는 뜻이다. agent 는 원래 그렇다.
|
|
|
|
## 4. 유닛 이름이 다르다
|
|
|
|
| 노드 | 유닛 |
|
|
|---|---|
|
|
| server | `k3s.service` |
|
|
| agent | `k3s-agent.service` |
|
|
|
|
**확인**
|
|
```bash
|
|
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'
|
|
```
|
|
|
|
> 이 차이가 A-4 에서 결과를 갈랐다. server 노드를 잃으면 `kubectl` 자체가
|
|
> 불통이 되고, agent 를 잃으면 `kubectl` 은 되지만 그 위의 워크로드가 사라진다.
|
|
|
|
## 5. k3s 가 기본으로 딸려 오는 것
|
|
|
|
따로 설치하지 않아도 이미 있다.
|
|
|
|
| | 무엇 |
|
|
|---|---|
|
|
| Traefik | 인그레스 컨트롤러. `:80` 을 듣는다 |
|
|
| servicelb (klipper-lb) | LoadBalancer 타입을 호스트 포트로 매핑 |
|
|
| local-path | 기본 StorageClass. **노드 로컬 디스크** |
|
|
| flannel | 파드 네트워크 (VXLAN) |
|
|
| kube-router | NetworkPolicy 집행 |
|
|
|
|
**확인**
|
|
```bash
|
|
sudo kubectl get pods -A
|
|
sudo kubectl get storageclass
|
|
```
|
|
|
|
> `local-path` 가 기본이라는 것이 A-4 에서 비용을 청구한다. PVC 가 **만들어진
|
|
> 노드에 묶여** 다른 노드로 재배치되지 않는다.
|
|
|
|
## 6. 워크스테이션에서 쓰려면
|
|
|
|
**하기**
|
|
```bash
|
|
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` 이다. **게스트 안에서만 맞는 주소**라
|
|
밖에서 쓰려면 바꿔야 한다.
|
|
|
|
---
|
|
|
|
## 막히면
|
|
|
|
| 증상 | 원인 | 확인 |
|
|
|---|---|---|
|
|
| agent 가 `NotReady` | 토큰·주소 오타 | `journalctl -u k3s-agent -n 30` |
|
|
| 노드 IP 가 예상과 다름 | `--node-ip` 없이 설치 | `kubectl get nodes -o wide` |
|
|
| 밖에서 kubectl 이 안 붙음 | kubeconfig 의 `127.0.0.1` | 위 6번 |
|
|
| 파드가 한 노드에만 몰림 | 스케줄러 판단 | `topologySpreadConstraints` 로 강제 |
|