docs(guides): hands-on guides for the seven setup stages
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cdac9b8178
commit
88b7bd4bf0
@@ -0,0 +1,154 @@
|
||||
# 00 — lab host 준비
|
||||
|
||||
## 이 단계가 끝나면
|
||||
|
||||
`virsh list` 가 sudo 없이 돌고, VM 을 만들 수 있는 상태가 된다.
|
||||
|
||||
## 전제
|
||||
|
||||
물리 기계 한 대. 이 실험대는 Arch Linux 를 썼지만 배포판은 상관없다 —
|
||||
패키지 이름만 다르다.
|
||||
|
||||
---
|
||||
|
||||
## 1. CPU 가상화가 켜져 있는가
|
||||
|
||||
BIOS 에서 꺼져 있으면 아무것도 못 한다. 먼저 본다.
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
grep -Eo 'vmx|svm' /proc/cpuinfo | head -1
|
||||
```
|
||||
|
||||
`vmx`(Intel) 또는 `svm`(AMD) 이 나오면 된다. 아무것도 안 나오면 BIOS 에서
|
||||
Intel VT-x / AMD-V 를 켜야 한다.
|
||||
|
||||
**실측** — 이 실험대의 호스트는 16 코어 전부에서 지원한다.
|
||||
|
||||
## 2. KVM 모듈이 올라와 있는가
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
lsmod | grep kvm
|
||||
```
|
||||
|
||||
```
|
||||
kvm_intel ...
|
||||
kvm ...
|
||||
```
|
||||
|
||||
두 줄이 나오면 커널이 하드웨어 가상화를 쓸 준비가 됐다.
|
||||
|
||||
> **왜 이걸 먼저 보나.** KVM 없이도 QEMU 는 돌지만 **소프트웨어 에뮬레이션**이
|
||||
> 되어 수십 배 느리다. VM 두 대가 「뜨긴 뜨는데 느리다」면 대개 여기다.
|
||||
|
||||
## 3. 패키지 설치
|
||||
|
||||
**하기** (Arch)
|
||||
```bash
|
||||
sudo pacman -S --needed qemu-full libvirt virt-install dnsmasq
|
||||
```
|
||||
|
||||
Debian/Ubuntu 면 이름이 다르다.
|
||||
```bash
|
||||
sudo apt install qemu-system-x86 libvirt-daemon-system virtinst cloud-image-utils
|
||||
```
|
||||
|
||||
| 무엇 | 하는 일 |
|
||||
|---|---|
|
||||
| qemu | 실제로 가상 기계를 돌리는 것 |
|
||||
| libvirt | qemu 를 관리하는 층 (`virsh` 가 여기 붙는다) |
|
||||
| virt-install | VM 을 만드는 명령 |
|
||||
| dnsmasq | 가상 네트워크의 DHCP·DNS |
|
||||
|
||||
## 4. libvirt 를 띄우고 권한을 받는다
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
sudo systemctl enable --now libvirtd.socket
|
||||
sudo usermod -aG libvirt "$USER"
|
||||
```
|
||||
|
||||
그리고 **로그아웃했다 다시 들어온다.** 보조 그룹은 로그인할 때 정해지므로
|
||||
`usermod` 만으로는 지금 셸에 반영되지 않는다.
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
groups # libvirt 가 보여야 한다
|
||||
virsh list --all # sudo 없이 돌아야 한다
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
donghyeon libvirt wheel
|
||||
```
|
||||
|
||||
> **`libvirtd.service` 가 아니라 `.socket` 을 켠 이유.** 소켓 활성화라서
|
||||
> 데몬이 미리 떠 있지 않아도 `virsh` 가 접속하는 순간 systemd 가 띄운다.
|
||||
> 자원을 아끼고, 데몬을 재시작해도 클라이언트가 끊기지 않는다.
|
||||
|
||||
## 5. 연결 URI 를 고정한다
|
||||
|
||||
`virsh` 는 기본으로 `qemu:///session`(사용자 단위)에 붙는데, VM 은
|
||||
`qemu:///system`(시스템 단위)에 만들어야 한다. **이걸 안 맞추면 만든 VM 이
|
||||
안 보인다.**
|
||||
|
||||
**하기** — 셸 프로필에 넣는다
|
||||
```bash
|
||||
echo 'export LIBVIRT_DEFAULT_URI=qemu:///system' >> ~/.bashrc
|
||||
```
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
virsh uri
|
||||
```
|
||||
```
|
||||
qemu:///system
|
||||
```
|
||||
|
||||
## 6. 기본 네트워크
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
virsh net-list --all
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
Name State Autostart Persistent
|
||||
--------------------------------------------
|
||||
default active yes yes
|
||||
```
|
||||
|
||||
`inactive` 면 켠다.
|
||||
```bash
|
||||
virsh net-start default
|
||||
virsh net-autostart default
|
||||
```
|
||||
|
||||
이 네트워크가 `virbr0` 브리지와 `192.168.122.0/24` 대역을 만든다. VM 들이
|
||||
여기 붙는다.
|
||||
|
||||
---
|
||||
|
||||
## 막히면
|
||||
|
||||
| 증상 | 원인 | 확인 |
|
||||
|---|---|---|
|
||||
| `virsh list` 에 permission denied | 그룹 반영 안 됨 | 로그아웃/로그인 했는가. `groups` 에 libvirt 가 있는가 |
|
||||
| 만든 VM 이 목록에 없다 | URI 가 `session` | `virsh uri` |
|
||||
| VM 이 극단적으로 느리다 | KVM 미사용 | `lsmod \| grep kvm` · BIOS |
|
||||
| `net-start default` 실패 | dnsmasq 없음 | 패키지 설치 확인 |
|
||||
|
||||
---
|
||||
|
||||
## 이 단계의 실측값
|
||||
|
||||
돌고 있는 실험대에서 그대로 읽은 것이다.
|
||||
|
||||
```
|
||||
libvirt 12.7.0
|
||||
qemu QEMU emulator version 11.1.1
|
||||
그룹 donghyeon libvirt wheel
|
||||
네트워크 default / active / autostart yes
|
||||
```
|
||||
@@ -0,0 +1,232 @@
|
||||
# 01 — VM 두 대
|
||||
|
||||
## 이 단계가 끝나면
|
||||
|
||||
`kc-lab-1`·`kc-lab-2` 두 게스트가 뜨고, 호스트에서 SSH 가 키로 붙는다.
|
||||
|
||||
## 전제
|
||||
|
||||
[00](../00-lab-host/) 이 끝나 `virsh list` 가 sudo 없이 돈다.
|
||||
|
||||
## 왜 VM 두 대인가
|
||||
|
||||
이 실험대의 질문 전부가 **「인스턴스가 둘 이상이고 요청이 어느 쪽으로 갈지
|
||||
모른다」** 를 전제한다. 한 대면 세션 공유도 분단도 노드 상실도 실험이 되지
|
||||
않는다. 그리고 호스트에 직접 깔면 **되돌릴 수가 없다** — 이 실험대는 노드를
|
||||
죽이고 DB 를 crash 시키는 것이 목적이라 망가뜨릴 수 있는 층이 필요하다.
|
||||
|
||||
---
|
||||
|
||||
## 1. base 이미지를 받는다
|
||||
|
||||
OS 를 설치하지 않는다. 클라우드 이미지는 **이미 설치가 끝난 디스크**이고,
|
||||
첫 부팅에서 cloud-init 이 사용자·SSH 키·호스트명을 채운다.
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
cd /var/lib/libvirt/images
|
||||
sudo curl -LO https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
|
||||
sudo mv debian-12-genericcloud-amd64.qcow2 base.qcow2
|
||||
```
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
qemu-img info /var/lib/libvirt/images/base.qcow2
|
||||
```
|
||||
|
||||
## 2. cloud-init 을 쓴다
|
||||
|
||||
게스트마다 하나씩 만든다. 템플릿은
|
||||
[`deploy/lab/cloud-init/kc-lab.yaml.example`](../../../deploy/lab/cloud-init/kc-lab.yaml.example).
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
hostname: kc-lab-1
|
||||
fqdn: kc-lab-1
|
||||
manage_etc_hosts: true
|
||||
|
||||
users:
|
||||
- name: donghyeon
|
||||
groups: [sudo]
|
||||
shell: /bin/bash
|
||||
sudo: ['ALL=(ALL) NOPASSWD:ALL']
|
||||
lock_passwd: false
|
||||
plain_text_passwd: __CONSOLE_PW__
|
||||
ssh_authorized_keys:
|
||||
- __LAB_HOST_KEY__
|
||||
- __WORKSTATION_KEY__
|
||||
|
||||
ssh_pwauth: false
|
||||
package_update: true
|
||||
packages: [curl, nftables]
|
||||
```
|
||||
|
||||
### 세 값을 어디서 가져오나
|
||||
|
||||
자리표시자 셋을 실제 값으로 바꿔야 한다. **찾는 명령이 있다.**
|
||||
|
||||
```bash
|
||||
# ① lab host 공개키 — 없으면 만든다
|
||||
[ -f ~/.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519
|
||||
cat ~/.ssh/id_ed25519.pub
|
||||
|
||||
# ② 워크스테이션 공개키 — 워크스테이션에서
|
||||
cat ~/.ssh/id_ed25519.pub
|
||||
|
||||
# ③ 콘솔용 비밀번호 — 만들어서 보관한다
|
||||
openssl rand -base64 18
|
||||
```
|
||||
|
||||
셋을 넣는다.
|
||||
```bash
|
||||
sed -i \
|
||||
-e "s|__LAB_HOST_KEY__|$(cat ~/.ssh/id_ed25519.pub)|" \
|
||||
-e "s|__WORKSTATION_KEY__|<워크스테이션에서 복사해 온 값>|" \
|
||||
-e "s|__CONSOLE_PW__|$(openssl rand -base64 18)|" \
|
||||
kc-lab-1.yaml
|
||||
```
|
||||
|
||||
**확인** — 자리표시자가 남아 있으면 cloud-init 이 그대로 넣는다
|
||||
```bash
|
||||
grep -c '__' kc-lab-1.yaml # 0 이어야 한다
|
||||
grep -c 'ssh-ed25519\|ssh-rsa' kc-lab-1.yaml # 2 여야 한다
|
||||
python3 -c 'import yaml,sys; yaml.safe_load(open("kc-lab-1.yaml")); print("YAML OK")'
|
||||
```
|
||||
|
||||
마지막 줄이 중요하다. **cloud-init 은 파싱에 실패해도 아무 오류를 남기지
|
||||
않으므로**, 넣기 전에 여기서 걸러야 한다.
|
||||
|
||||
세 가지가 의도적이다.
|
||||
|
||||
| | 왜 |
|
||||
|---|---|
|
||||
| `NOPASSWD:ALL` | k3s 설치와 장애 주입이 비대화식으로 돌아야 한다. 비밀번호를 물으면 멈춘다 |
|
||||
| `plain_text_passwd` | **cloud-init 이 실패했을 때의 유일한 탈출구.** 키가 안 들어가면 SSH 가 막히므로 콘솔로 들어가 로그를 봐야 한다 |
|
||||
| 키 두 개 | lab host 에서 자동화가 돌고(에이전트 포워딩 없음), 워크스테이션에서는 ProxyJump 로 직접 붙는다 |
|
||||
|
||||
> **들여쓰기는 공백만.** YAML 은 탭을 금지하고, cloud-init 은 파싱에 실패해도
|
||||
> **아무 오류도 남기지 않는다.** 게스트가 `localhost` 로 뜨고 로그인이 안 되는
|
||||
> 것이 유일한 증상이다.
|
||||
|
||||
## 3. 시드 이미지를 만든다
|
||||
|
||||
cloud-init 은 `cidata` 라벨이 붙고 안에 `user-data`·`meta-data` 라는 **정확한
|
||||
이름**의 파일이 있는 볼륨을 찾는다.
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
printf 'instance-id: kc-lab-1-%s\nlocal-hostname: kc-lab-1\n' "$(date +%s)" > meta-kc-lab-1
|
||||
|
||||
xorrisofs -quiet -output seed-kc-lab-1.iso -volid CIDATA -joliet -rock \
|
||||
-graft-points /user-data=kc-lab-1.yaml /meta-data=meta-kc-lab-1
|
||||
|
||||
virsh vol-create-as default seed-kc-lab-1.iso "$(stat -c%s seed-kc-lab-1.iso)" --format raw
|
||||
virsh vol-upload --pool default seed-kc-lab-1.iso seed-kc-lab-1.iso
|
||||
```
|
||||
|
||||
`instance-id` 에 타임스탬프를 넣는 이유가 있다. cloud-init 은 **인스턴스마다
|
||||
한 번만** 초기화 모듈을 돌린다. id 가 같으면 이미 한 것으로 보고 건너뛰므로,
|
||||
user-data 를 고쳐도 반영되지 않는다.
|
||||
|
||||
## 4. VM 을 만든다
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
virt-install --name kc-lab-1 --memory 5120 --vcpus 2 \
|
||||
--disk size=20,backing_store=/var/lib/libvirt/images/base.qcow2 \
|
||||
--disk vol=default/seed-kc-lab-1.iso,device=disk,bus=virtio,readonly=on \
|
||||
--network network=default,mac=52:54:00:aa:bb:11 \
|
||||
--import --os-variant debian12 --noautoconsole
|
||||
```
|
||||
|
||||
`kc-lab-2` 는 이름·메모리(4096)·MAC(`...:12`)·시드만 바꾼다.
|
||||
|
||||
**★ 시드를 `--cloud-init` 으로 붙이지 않는다.** 그 옵션은 시드를 SATA CD-ROM
|
||||
으로 붙이는데, Debian `genericcloud` 이미지는 크기를 줄이려고 물리 하드웨어
|
||||
드라이버를 뺐다. **AHCI 장치가 보이지 않아** cloud-init 이 데이터소스를
|
||||
못 찾고 조용히 끝난다. `bus=virtio` 로 디스크로 붙여야 한다.
|
||||
|
||||
`--disk size=20,backing_store=...` 는 복사가 아니라 **오버레이**다. base 는
|
||||
읽기 전용으로 두고 변경분만 새 파일에 쌓이므로, 20GB 를 두 개 만들어도
|
||||
실제 디스크는 몇백 MB 만 쓴다.
|
||||
|
||||
## 5. DHCP 로 IP 를 고정한다
|
||||
|
||||
MAC 을 정해 두었으니 그 MAC 에 IP 를 예약한다.
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
virsh net-update default add ip-dhcp-host \
|
||||
"<host mac='52:54:00:aa:bb:11' name='kc-lab-1' ip='192.168.122.11'/>" \
|
||||
--live --config
|
||||
```
|
||||
|
||||
`--live --config` 둘 다 준다. `--live` 만 주면 재부팅에 사라지고, `--config`
|
||||
만 주면 지금 반영되지 않는다.
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
virsh net-dumpxml default | grep ip-dhcp-host -A3
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
<range start='192.168.122.2' end='192.168.122.254'/>
|
||||
<host mac='52:54:00:aa:bb:11' name='kc-lab-1' ip='192.168.122.11'/>
|
||||
<host mac='52:54:00:aa:bb:12' name='kc-lab-2' ip='192.168.122.12'/>
|
||||
```
|
||||
|
||||
## 6. 붙어 본다
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
virsh list --all
|
||||
ssh donghyeon@192.168.122.11 'hostname; cat /etc/os-release | head -1'
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
Id Name State
|
||||
--------------------------
|
||||
3 kc-lab-2 running
|
||||
4 kc-lab-1 running
|
||||
|
||||
kc-lab-1
|
||||
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 막히면
|
||||
|
||||
여기서 실제로 겪은 것들이다.
|
||||
|
||||
| 증상 | 원인 | 확인 |
|
||||
|---|---|---|
|
||||
| SSH `Permission denied (publickey)` · hostname 이 `localhost` | **cloud-init 이 안 돌았다.** 시드를 SATA 로 붙였거나 YAML 파싱 실패 | 아래 화면 캡처 |
|
||||
| user-data 를 고쳤는데 반영 안 됨 | `instance-id` 가 같아 건너뜀 | meta-data 의 id 확인 |
|
||||
| IP 가 매번 바뀐다 | DHCP 예약이 `--config` 없이 들어감 | `net-dumpxml` |
|
||||
| VM 이 느리다 | KVM 미사용 | [00](../00-lab-host/) 로 |
|
||||
|
||||
게스트에 못 들어갈 때는 **화면을 직접 뜬다.**
|
||||
|
||||
```bash
|
||||
virsh screenshot kc-lab-1 /tmp/kc1.ppm # 확장자와 무관하게 PNG 로 저장된다
|
||||
```
|
||||
|
||||
`localhost login:` 이면 cloud-init 미실행, `kc-lab-1 login:` 이면 실행된 것이다.
|
||||
이 한 장이 「SSH 가 안 되는 이유」를 절반으로 줄인다.
|
||||
|
||||
---
|
||||
|
||||
## 실측값
|
||||
|
||||
```
|
||||
kc-lab-1 vCPU 2 메모리 5120MB 192.168.122.11
|
||||
kc-lab-2 vCPU 2 메모리 4096MB 192.168.122.12
|
||||
게스트 OS Debian GNU/Linux 12 (bookworm)
|
||||
```
|
||||
|
||||
> 메모리가 처음 만들 때(3584MB)와 다르다. 호스트가 12GB 뿐이라 실험을 늘리며
|
||||
> 재배분했다. VM 을 다시 만들지 않고 바꾸는 방법은
|
||||
> [`session-lab-concepts.md`](../../session-lab-concepts.md) 13층에 있다.
|
||||
@@ -0,0 +1,136 @@
|
||||
# 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` 로 강제 |
|
||||
@@ -0,0 +1,170 @@
|
||||
# 03 — 호스트 nginx 라우팅
|
||||
|
||||
## 이 단계가 끝나면
|
||||
|
||||
밖에서 보낸 요청이 **nginx → Traefik → 파드**로 닿는다. 아직 TLS 는 없다.
|
||||
|
||||
## 전제
|
||||
|
||||
[02](../02-k3s/) 가 끝나 두 노드가 `Ready`.
|
||||
|
||||
## 왜 프록시가 두 겹인가
|
||||
|
||||
nginx 와 Traefik 이 하는 일이 다르다.
|
||||
|
||||
| | 맡는 것 |
|
||||
|---|---|
|
||||
| 호스트 nginx | 바깥세상과의 접점 — TLS 종단 · 인증서 · `X-Forwarded-*` |
|
||||
| Traefik | 클러스터 안의 동적 라우팅 — Ingress 를 보고 서비스를 고른다 |
|
||||
|
||||
**이 2홉이 운영 구조와 같다는 것이 이 배치의 핵심**이고, 동시에 B-4 의 헤더
|
||||
실험이 성립하는 이유다. 1홉을 가정하고 쓴 계약이 2홉에서도 유효한지를
|
||||
재려면 두 겹이 있어야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 설정을 쓴다
|
||||
|
||||
원본은 [`deploy/lab/host/nginx-keycloak-lab.conf`](../../../deploy/lab/host/nginx-keycloak-lab.conf).
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
sudo tee /etc/nginx/sites-available/keycloak-lab > /dev/null <<'EOF'
|
||||
upstream k3s_traefik {
|
||||
server 192.168.122.11:80;
|
||||
server 192.168.122.12:80;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl default_server;
|
||||
http2 on;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/auth.hyeonworks.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
|
||||
location / {
|
||||
proxy_pass http://k3s_traefik;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-Forwarded-Port 443;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
> **인증서 경로는 아직 없다.** [04](../04-tls/) 에서 만든다. 그전까지는 443
|
||||
> 블록을 주석 처리하고 80 만 `proxy_pass` 로 두면 이 단계를 먼저 확인할 수 있다.
|
||||
|
||||
Arch 는 `sites-available` 관례가 없다. 직접 만들고 `nginx.conf` 의 `http` 블록
|
||||
안에서 include 한다.
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /etc/nginx/sites-{available,enabled}
|
||||
sudo ln -s /etc/nginx/sites-available/keycloak-lab /etc/nginx/sites-enabled/
|
||||
# nginx.conf 의 http { } 안에: include /etc/nginx/sites-enabled/*;
|
||||
```
|
||||
|
||||
## 2. 문법을 보고 적용한다
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
**`&&` 가 중요하다.** 설정이 깨진 상태에서 reload 하면 nginx 가 새 워커를
|
||||
띄우지 못한다. `-t` 를 먼저 통과시키고 그때만 reload 한다.
|
||||
|
||||
## 3. 층별로 확인한다 — 아래에서 위로
|
||||
|
||||
한 번에 밖에서 치지 말고, **가까운 층부터** 본다. 어디서 끊겼는지가 바로 나온다.
|
||||
|
||||
**확인 ①** Traefik 이 듣고 있나 (nginx 를 건너뛴다)
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://192.168.122.11
|
||||
```
|
||||
```
|
||||
404
|
||||
```
|
||||
|
||||
**`404` 가 성공 신호다.** Traefik 까지 닿았는데 매칭되는 Ingress 규칙이 없다는
|
||||
뜻이다. `502` 나 연결 거부면 그 아래에서 끊긴 것이다.
|
||||
|
||||
**확인 ②** nginx 가 80 에서 리다이렉트하나
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://auth.hyeonworks.com
|
||||
```
|
||||
```
|
||||
301 https://auth.hyeonworks.com/
|
||||
```
|
||||
|
||||
**확인 ③** 끝까지 닿나 (TLS 이후)
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
|
||||
```
|
||||
```
|
||||
200
|
||||
```
|
||||
|
||||
## 4. upstream 이 둘인 이유
|
||||
|
||||
```
|
||||
upstream k3s_traefik {
|
||||
server 192.168.122.11:80;
|
||||
server 192.168.122.12:80;
|
||||
}
|
||||
```
|
||||
|
||||
두 노드 모두 Traefik 이 뜨므로 어느 쪽으로 보내도 된다. nginx 는 기본
|
||||
라운드로빈으로 번갈아 보내고, **한쪽이 죽으면 자동으로 뺀다.**
|
||||
|
||||
그 「빼는」 동작이 로그에 이렇게 남는다.
|
||||
|
||||
```
|
||||
connect() failed (113: No route to host) ← 호스트에 못 닿는다
|
||||
connect() failed (111: Connection refused) ← 포트에 아무도 없다
|
||||
no live upstreams ← 둘 다 죽었다고 판단
|
||||
```
|
||||
|
||||
**113 과 111 은 대응이 다르다.** 113 은 네트워크, 111 은 프로세스다.
|
||||
A-4 에서 노드를 잃었을 때 이 세 줄이 1분 안에 순서대로 나왔다.
|
||||
|
||||
---
|
||||
|
||||
## 막히면
|
||||
|
||||
| 증상 | 어디서 끊겼나 | 확인 |
|
||||
|---|---|---|
|
||||
| ① 이 연결 거부 | Traefik 이 안 떴거나 게스트가 죽음 | `kubectl get pods -n kube-system` |
|
||||
| ① 이 `502` | Traefik 은 떴는데 백엔드가 없음 | Ingress 확인 |
|
||||
| ② 가 응답 없음 | nginx 가 안 떴거나 방화벽 | `systemctl status nginx` |
|
||||
| ③ 이 `502` | 인증서 문제 또는 upstream 다운 | [04](../04-tls/) · 아래 로그 |
|
||||
|
||||
**로그를 볼 때** — 실무자가 치는 형태다.
|
||||
```bash
|
||||
journalctl -u nginx -p err -n 5 # 최근 에러만
|
||||
journalctl -u nginx -f # 지금 벌어지는 것
|
||||
```
|
||||
|
||||
**★ nginx 에러 로그는 2048바이트에서 잘린다.** 긴 URL 이 끝에서 단어 중간에
|
||||
끊겨 보이면 그것이다. 되찾으려면 **access 로그를 본다** — 거기엔 제한이 없다.
|
||||
|
||||
```bash
|
||||
grep oauth2/callback /var/log/nginx/access.log | tail -1
|
||||
```
|
||||
|
||||
이 실험대에서 B-7 의 502 원인이 error 로그에 있었는데 잘려 있었고, access
|
||||
로그에는 3492자로 온전히 남아 있었다.
|
||||
@@ -0,0 +1,227 @@
|
||||
# 04 — TLS
|
||||
|
||||
## 이 단계가 끝나면
|
||||
|
||||
`https://` 가 열리고 체인이 완전하며, 갱신이 자동으로 **서빙까지** 닿는다.
|
||||
|
||||
## 전제
|
||||
|
||||
[03](../03-nginx/) 이 끝나 nginx 가 Traefik 으로 프록시한다. 그리고 **공개
|
||||
DNS 에 이름 세 개가 이 호스트를 가리키고 있어야 한다** — Let's Encrypt 가
|
||||
HTTP-01 로 검증하러 오기 때문이다.
|
||||
|
||||
---
|
||||
|
||||
## 1. certbot 을 깐다
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
sudo pacman -S certbot certbot-nginx # Arch
|
||||
sudo apt install certbot python3-certbot-nginx # Debian/Ubuntu
|
||||
```
|
||||
|
||||
## 2. 인증서를 받는다
|
||||
|
||||
이름 세 개를 **한 인증서**에 넣는다.
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
sudo certbot certonly --webroot -w /var/www/html \
|
||||
-d auth.hyeonworks.com -d app1.hyeonworks.com -d app2.hyeonworks.com
|
||||
```
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
sudo certbot certificates
|
||||
```
|
||||
|
||||
> **이 실험대는 와일드카드를 쓰지 않았고, 그 비용이 나중에 청구됐다.**
|
||||
> B-7 에서 oauth2-proxy 를 올릴 네 번째 이름이 없어 Grafana 의 `app2` 를
|
||||
> 빌려야 했다. 와일드카드는 DNS-01 검증이 필요하고 그건 DNS 공급자 API 를
|
||||
> 붙여야 한다 — 그 절충을 안 한 결과다.
|
||||
|
||||
## 3. nginx 가 `fullchain` 을 보게 한다
|
||||
|
||||
[03](../03-nginx/) 의 설정에 이미 있다.
|
||||
|
||||
```
|
||||
ssl_certificate /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/auth.hyeonworks.com/privkey.pem;
|
||||
```
|
||||
|
||||
**`cert.pem` 이 아니라 `fullchain.pem`.** 서버 인증서만 보내면 중간 인증서가
|
||||
빠져 체인이 끊긴다. 브라우저는 대개 캐시나 AIA 로 보완해서 **정상으로 보이고**,
|
||||
캐시가 없는 클라이언트에서만 깨진다. 그래서 발견이 늦다.
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## 4. 확인 — 열리는가, 체인이 완전한가
|
||||
|
||||
**확인 ①** 열리나
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
|
||||
```
|
||||
```
|
||||
200
|
||||
```
|
||||
|
||||
**확인 ②** 체인 단계와 검증
|
||||
```bash
|
||||
echo | openssl s_client -connect auth.hyeonworks.com:443 \
|
||||
-servername auth.hyeonworks.com 2>/dev/null \
|
||||
| grep -E '^ *[0-9]+ s:|^ *i:|Verify return code'
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
0 s:CN = auth.hyeonworks.com
|
||||
i:C = US, O = Let's Encrypt, CN = YE2
|
||||
1 s:C = US, O = Let's Encrypt, CN = YE2
|
||||
i:C = US, O = ISRG, CN = Root YE
|
||||
2 s:C = US, O = ISRG, CN = Root YE
|
||||
i:C = US, O = Internet Security Research Group, CN = ISRG Root X2
|
||||
3 s:C = US, O = Internet Security Research Group, CN = ISRG Root X2
|
||||
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
Verify return code: 0 (ok)
|
||||
```
|
||||
|
||||
**단계가 1개면 `cert.pem` 을 쓴 것이다.**
|
||||
|
||||
**확인 ③** 이름 세 개가 한 인증서인가
|
||||
```bash
|
||||
for H in auth app1 app2; do
|
||||
echo | openssl s_client -connect $H.hyeonworks.com:443 -servername $H.hyeonworks.com 2>/dev/null \
|
||||
| openssl x509 -noout -serial
|
||||
done
|
||||
```
|
||||
|
||||
일련번호가 셋 다 같으면 SAN 하나에 들어 있는 것이다.
|
||||
|
||||
## 5. ★ 갱신이 서빙까지 닿게 한다 — 여기가 이 단계의 핵심이다
|
||||
|
||||
**타이머가 도는 것만으로는 부족하다.**
|
||||
|
||||
**확인** — 타이머
|
||||
```bash
|
||||
systemctl list-timers certbot-renew.timer
|
||||
```
|
||||
|
||||
이것이 `active` 여도 갱신된 인증서가 **서빙되지는 않는다.** nginx 는 인증서를
|
||||
기동 시점에 읽어 메모리에 들고 있고, certbot 은 `live/` 심볼릭 링크만 갈아
|
||||
끼운다. **경로는 그대로이고 내용만 바뀌므로 nginx 는 모른다.**
|
||||
|
||||
배포판 기본 유닛에는 reload 를 부르는 것이 없다.
|
||||
|
||||
```bash
|
||||
systemctl cat certbot-renew.service
|
||||
```
|
||||
```
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/certbot -q renew
|
||||
PrivateTmp=true
|
||||
```
|
||||
|
||||
`ExecStartPost` 도 `--deploy-hook` 도 없다.
|
||||
|
||||
**하기** — 훅 하나를 넣는다
|
||||
```bash
|
||||
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'EOF'
|
||||
#!/bin/sh
|
||||
nginx -t && nginx -s reload
|
||||
EOF
|
||||
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
|
||||
```
|
||||
|
||||
`deploy/` 에 넣는다. `post/` 는 갱신이 없어도 매번 돌아 하루 두 번 워커를
|
||||
갈아치운다. `deploy/` 는 **실제로 갱신됐을 때만** 실행된다.
|
||||
|
||||
**확인** — 실제로 도는지
|
||||
```bash
|
||||
# 강제 갱신 전에 워커 PID 를 적어 둔다
|
||||
ps -eo pid,lstart,args | grep 'nginx: worker' | grep -v grep
|
||||
|
||||
sudo certbot renew --force-renewal
|
||||
|
||||
# 워커 PID 가 바뀌었으면 reload 된 것이다
|
||||
ps -eo pid,lstart,args | grep 'nginx: worker' | grep -v grep
|
||||
```
|
||||
|
||||
**판정은 로그 문구가 아니라 워커 PID 로 한다.** certbot 이
|
||||
`Hook 'deploy-hook' ran with error output` 이라고 찍는데 **실패가 아니다** —
|
||||
nginx 의 `types_hash` 경고가 stderr 로 나갔을 뿐이고 내용은
|
||||
`test is successful` · `signal process started` 다.
|
||||
|
||||
> **로그에서 `error` 를 grep 하는 감시를 걸면 성공한 훅을 실패로 오독한다.**
|
||||
|
||||
**실측** — 이 실험대에서 잰 차이
|
||||
|
||||
| | 훅 없음 | 훅 있음 |
|
||||
|---|---|---|
|
||||
| 갱신 → 서빙 | **2305초 (38분 25초)** | **1~2초** |
|
||||
| 무엇이 reload 했나 | 사람이 직접 | certbot deploy 훅 |
|
||||
| 아무도 안 했다면 | 다음 nginx 재시작까지 = 사실상 무기한 | — |
|
||||
|
||||
**88일 동안 이 결함이 보이지 않는다.** 타이머는 정상이고 매번 `SUCCESS` 로
|
||||
끝나며, 만료 30일 전까지는 갱신 자체를 하지 않아 발현할 기회가 없다.
|
||||
발현하는 날의 증상은 **인증서 만료**이고, 그날에도 로그에는 `SUCCESS` 라고
|
||||
적혀 있다.
|
||||
|
||||
원문: [D-4](../../experiment-d4-certificate-renewal.md) ·
|
||||
[D-4a](../../experiment-d4a-deploy-hook.md) ·
|
||||
증거 [`evidence/d4-certificate-renewal/`](../../evidence/d4-certificate-renewal/)
|
||||
|
||||
## 6. reload 는 무중단인가 — 쟀다
|
||||
|
||||
궁금할 것이므로 결과만 적는다. **무중단이다.**
|
||||
|
||||
새 연결 8856건 전부 200, p95 는 205.7ms 대 204.3ms 로 변화 없음. 그리고
|
||||
845KB 를 20k/s 로 받는 중이던 요청이 **전송 12초째에 reload 를 맞고도**
|
||||
845361바이트를 온전히 받았다(연결수 1). 옛 워커가 그 요청을 끝까지 책임진다.
|
||||
|
||||
---
|
||||
|
||||
## 막히면
|
||||
|
||||
| 증상 | 원인 | 확인 |
|
||||
|---|---|---|
|
||||
| certbot 검증 실패 | DNS 가 이 호스트를 안 가리킴 · 80 이 막힘 | `dig +short <name>` · 밖에서 `curl http://<name>` |
|
||||
| 체인 단계가 1개 | `cert.pem` 을 씀 | 위 확인 ② |
|
||||
| 갱신은 됐는데 옛 인증서가 나감 | **deploy 훅 없음** | 워커 PID · `sudo ls /etc/letsencrypt/renewal-hooks/deploy/` |
|
||||
| 훅이 실패한 것처럼 보임 | stderr 경고를 error 로 표시 | 문구 말고 **워커 PID** |
|
||||
| 발급 한도 | 주당 중복 인증서 5장 | `--dry-run` 으로 먼저 시험 |
|
||||
|
||||
---
|
||||
|
||||
## 근거를 재려면 (선택)
|
||||
|
||||
평소에는 필요 없다. **문서에 남길 근거가 필요할 때만** 이렇게까지 한다.
|
||||
|
||||
갱신 중 가용성을 재려면 **주입 전에 대조군부터** 잡는다. 평시 오류율을 모르면
|
||||
갱신 중에 나온 실패 한 건을 해석할 수 없다.
|
||||
|
||||
```bash
|
||||
# 대조군 — 0.2초 × 900회 = 180초
|
||||
i=0; while [ $i -lt 900 ]; do
|
||||
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' --max-time 5 \
|
||||
https://auth.hyeonworks.com/realms/master
|
||||
i=$((i+1)); sleep 0.2
|
||||
done > /tmp/control.txt
|
||||
awk '{print $1}' /tmp/control.txt | sort | uniq -c
|
||||
```
|
||||
|
||||
이 실험대의 대조군은 **900건 전부 200, 오류 0** 이었다. 그래서 갱신 중 비200 이
|
||||
한 번이라도 나오면 갱신 탓으로 귀속할 수 있었다.
|
||||
|
||||
**그리고 시각을 비교할 때 시계를 확인한다.** 이 실험대는 test-server 가 NTP
|
||||
미동기로 106초 빨랐고, 보정하지 않은 첫 계산은 훅이 인증서 발급보다 104초
|
||||
먼저 실행된 것이 되어 물리적으로 불가능했다.
|
||||
|
||||
```bash
|
||||
A=$(date -u +%s.%N); B=$(ssh test-server 'date -u +%s.%N'); C=$(date -u +%s.%N)
|
||||
# 왜곡 ≈ B − (A+C)/2 , 어느 쪽이 맞는지는 외부 기준으로 가른다
|
||||
curl -sI https://www.google.com | grep -i '^date:'
|
||||
```
|
||||
@@ -0,0 +1,330 @@
|
||||
# 05 — Keycloak 2노드 + PostgreSQL
|
||||
|
||||
## 이 단계가 끝나면
|
||||
|
||||
`https://auth.hyeonworks.com` 에서 관리 콘솔에 로그인되고, 두 Keycloak 이
|
||||
하나의 클러스터로 보인다.
|
||||
|
||||
## 전제
|
||||
|
||||
[04](../04-tls/) 까지 끝나 `https://` 가 열린다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 매니페스트를 적용한다
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
kubectl create namespace keycloak-lab
|
||||
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml
|
||||
```
|
||||
|
||||
**확인** — 적용이 끝날 때까지 기다린다
|
||||
```bash
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=300s
|
||||
```
|
||||
|
||||
```
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
```
|
||||
|
||||
`rollout status` 는 **끝날 때까지 블록**한다. `get pods` 를 반복해서 보는 것보다
|
||||
이게 낫다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 리소스가 제대로 만들어졌는지 — 층별로 본다
|
||||
|
||||
`kubectl get pods` 만 보면 놓치는 것이 많다. **위에서 아래로** 확인한다.
|
||||
|
||||
### 2-1. 무엇이 만들어졌나
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
kubectl -n keycloak-lab get all
|
||||
```
|
||||
|
||||
`all` 은 이름과 달리 전부는 아니다 — Secret·ConfigMap·PVC·Ingress 는 안 나온다.
|
||||
|
||||
```bash
|
||||
kubectl -n keycloak-lab get secret,configmap,pvc,ingress
|
||||
```
|
||||
|
||||
### 2-2. Deployment → ReplicaSet → Pod 사슬
|
||||
|
||||
Deployment 는 파드를 직접 만들지 않는다. **ReplicaSet 을 만들고 그것이 파드를
|
||||
만든다.** 이 사슬 어디서 끊겼는지가 진단의 출발점이다.
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
kubectl -n keycloak-lab get deploy,rs,pod -l app=bff
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
replicaset.apps/bff-555df79c97 2 2 ← 지금 쓰이는 것
|
||||
replicaset.apps/bff-574c6d658b 0 0 ← 지난 배포
|
||||
replicaset.apps/bff-576d869c6d 0 0
|
||||
... (7개)
|
||||
pod/bff-555df79c97-6j86w 1/1 Running
|
||||
```
|
||||
|
||||
**ReplicaSet 이 여러 개인 것은 정상이다.** 배포할 때마다 새로 만들고 옛것은
|
||||
`0` 으로 남긴다 — 그래서 `kubectl rollout undo` 가 가능하다. 파드 이름의
|
||||
가운데 해시(`555df79c97`)가 어느 ReplicaSet 소속인지 말해 준다.
|
||||
|
||||
읽는 법:
|
||||
|
||||
| 보이는 것 | 뜻 |
|
||||
|---|---|
|
||||
| Deployment 는 있는데 RS 가 없다 | 컨트롤러가 못 돌았다 — RBAC·admission 확인 |
|
||||
| RS 는 있는데 DESIRED 만 있고 CURRENT 가 0 | 파드를 못 만든다 — 이벤트를 본다 |
|
||||
| Pod 은 있는데 `0/1` | 컨테이너가 안 뜬다 — 로그와 describe |
|
||||
|
||||
> StatefulSet 은 ReplicaSet 을 쓰지 않고 파드를 직접 만든다. 그래서
|
||||
> `keycloak-0`·`keycloak-1` 처럼 **이름이 고정**이고, A-4 에서 `Terminating`
|
||||
> 파드가 안 지워지면 대체 파드가 안 생기는 이유가 이것이다.
|
||||
|
||||
### 2-3. Secret 이 실제로 들어갔나 — 세 층으로 본다
|
||||
|
||||
값이 있는 것과 파드가 그 값을 받은 것은 다르다.
|
||||
|
||||
**확인 ①** Secret 에 키가 있나 — **값은 찍지 않는다**
|
||||
```bash
|
||||
kubectl -n keycloak-lab get secret keycloak-lab-secrets -o jsonpath='{.data}' \
|
||||
| tr ',' '\n' | grep -o '"[A-Z_]*"' | tr -d '"'
|
||||
```
|
||||
```
|
||||
KC_BOOTSTRAP_ADMIN_PASSWORD
|
||||
POSTGRES_PASSWORD
|
||||
```
|
||||
|
||||
**확인 ②** 값이 비어 있지 않나 — **길이만**
|
||||
```bash
|
||||
kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
-o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d | wc -c
|
||||
```
|
||||
```
|
||||
22
|
||||
```
|
||||
|
||||
**확인 ③** 파드 안에 주입됐나 — 여기가 진짜다
|
||||
```bash
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- \
|
||||
sh -c 'echo "길이=${#KC_BOOTSTRAP_ADMIN_PASSWORD}"'
|
||||
```
|
||||
```
|
||||
길이=19
|
||||
```
|
||||
|
||||
**어느 환경변수가 어느 Secret 에서 왔는지**도 볼 수 있다.
|
||||
```bash
|
||||
kubectl -n keycloak-lab get pod keycloak-0 \
|
||||
-o jsonpath='{range .spec.containers[0].env[*]}{.name}{"\t"}{.valueFrom.secretKeyRef.name}{"\n"}{end}'
|
||||
```
|
||||
```
|
||||
KC_DB
|
||||
KC_DB_URL
|
||||
KC_DB_USERNAME
|
||||
KC_DB_PASSWORD keycloak-lab-secrets ← Secret 에서 온 것만 오른쪽에 이름이 있다
|
||||
```
|
||||
|
||||
> **값을 그대로 찍지 않는 습관.** `-o yaml` 은 base64 를 그대로 보여 주고
|
||||
> 그건 암호화가 아니다. 터미널 스크롤백·화면 공유·로그에 남는다.
|
||||
> D-3 이 잰 것이 이것이다 — [`experiment-d3-secret-management.md`](../../experiment-d3-secret-management.md)
|
||||
|
||||
### 2-4. Service 가 파드를 잡고 있나 — Endpoints
|
||||
|
||||
Service 가 있어도 **셀렉터가 안 맞으면 뒤가 비어 있다.** 이때 증상은
|
||||
「연결은 되는데 응답이 없다」라 원인을 찾기 어렵다.
|
||||
|
||||
**확인** — 실무자가 가장 자주 쓰는 형태
|
||||
```bash
|
||||
kubectl -n keycloak-lab describe svc keycloak | grep -i endpoints
|
||||
```
|
||||
```
|
||||
Endpoints: 10.42.0.67:8080,10.42.1.155:8080
|
||||
```
|
||||
|
||||
목록으로 보려면 **EndpointSlice** 를 쓴다.
|
||||
```bash
|
||||
kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=keycloak
|
||||
```
|
||||
```
|
||||
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
|
||||
keycloak-xdph6 IPv4 8080 10.42.0.67,10.42.1.155 3d23h
|
||||
```
|
||||
|
||||
> **`kubectl get endpoints` 는 쓰지 않는다.** v1.33 부터 deprecated 이고
|
||||
> 실행하면 경고가 나온다.
|
||||
> ```
|
||||
> Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
|
||||
> ```
|
||||
> 옛 문서와 블로그에 이 형태가 많으니 주의한다.
|
||||
|
||||
준비 상태까지 함께 보려면 이렇게 뽑는다.
|
||||
```bash
|
||||
kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=keycloak \
|
||||
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"\t"}{.conditions.ready}{"\n"}{end}'
|
||||
```
|
||||
```
|
||||
10.42.0.67 true
|
||||
10.42.1.155 true
|
||||
```
|
||||
|
||||
`ready` 가 `false` 면 파드는 있는데 **readiness 프로브를 통과하지 못한** 것이라,
|
||||
Service 가 그 파드로 트래픽을 보내지 않는다.
|
||||
|
||||
**비어 있으면** 셀렉터와 파드 라벨이 안 맞는 것이다.
|
||||
```bash
|
||||
kubectl -n keycloak-lab get svc keycloak -o jsonpath='{.spec.selector}'; echo
|
||||
kubectl -n keycloak-lab get pods --show-labels
|
||||
```
|
||||
|
||||
### 2-5. PVC 가 실제로 붙었나
|
||||
|
||||
```bash
|
||||
kubectl -n keycloak-lab get pvc
|
||||
```
|
||||
|
||||
`Pending` 이면 StorageClass 가 없거나 노드에 자리가 없다. **`local-path` 는
|
||||
파드가 스케줄될 때까지 기다린다**(WaitForFirstConsumer)므로, 파드가 안 뜨면
|
||||
PVC 도 `Pending` 인 것이 정상이다. 둘이 서로를 기다리는 것처럼 보이지만
|
||||
파드 쪽 원인을 먼저 본다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 안 뜰 때 — 순서가 있다
|
||||
|
||||
**① 이벤트부터.** 로그보다 먼저다. 스케줄링·이미지·볼륨 실패가 여기 나온다.
|
||||
```bash
|
||||
kubectl -n keycloak-lab get events --sort-by=.lastTimestamp | tail -20
|
||||
```
|
||||
|
||||
**② describe.** 그 파드에 한정된 이벤트와 상태를 함께 본다.
|
||||
```bash
|
||||
kubectl -n keycloak-lab describe pod keycloak-0
|
||||
```
|
||||
|
||||
**③ 로그.** 컨테이너가 떴는데 죽는 경우다.
|
||||
```bash
|
||||
kubectl -n keycloak-lab logs keycloak-0
|
||||
kubectl -n keycloak-lab logs keycloak-0 --previous # 재시작 직전 로그
|
||||
```
|
||||
|
||||
`--previous` 가 중요하다. CrashLoopBackOff 면 지금 컨테이너는 방금 뜬 것이라
|
||||
**죽은 이유는 이전 컨테이너 로그에 있다.**
|
||||
|
||||
**④ 그래도 모르면 안에서 본다.**
|
||||
```bash
|
||||
kubectl -n keycloak-lab exec -it keycloak-0 -- sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 클러스터가 형성됐는지 확인한다
|
||||
|
||||
파드가 둘 다 `Running` 인 것과 **하나의 클러스터로 묶인 것**은 다르다.
|
||||
|
||||
**확인 ①** 로그
|
||||
```bash
|
||||
kubectl -n keycloak-lab logs keycloak-0 | grep ISPN000094 | tail -1
|
||||
```
|
||||
```
|
||||
ISPN000094: Received new cluster view for channel ISPN:
|
||||
[keycloak-0-10001|1] (2) [keycloak-0-10001, keycloak-1-52537]
|
||||
```
|
||||
|
||||
`(2)` 가 멤버 수다.
|
||||
|
||||
**확인 ②** 디스커버리 테이블
|
||||
```bash
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak -c 'select name, ip from jgroups_ping'
|
||||
```
|
||||
|
||||
**확인 ③** 지표
|
||||
|
||||
**★ Keycloak 컨테이너에는 `curl` 이 없다.** 공식 이미지가 최소 구성이라
|
||||
`wget` 도 `nc` 도 없다. 안에서 치면 이렇게 된다.
|
||||
|
||||
```
|
||||
sh: line 1: curl: command not found
|
||||
command terminated with exit code 127
|
||||
```
|
||||
|
||||
그래서 밖에서 물어본다. **Prometheus 에 묻는 것이 가장 짧다.**
|
||||
|
||||
```bash
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
|
||||
```
|
||||
|
||||
```
|
||||
keycloak-1 → 2
|
||||
keycloak-0 → 2
|
||||
```
|
||||
|
||||
Prometheus 가 아직 없다면 임시 파드를 띄운다.
|
||||
|
||||
```bash
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
kubectl -n keycloak-lab run m --rm -i --restart=Never \
|
||||
--image=curlimages/curl:8.11.1 --quiet --command -- \
|
||||
sh -c "curl -s http://$K0:9000/metrics | grep '^vendor_cluster_size'"
|
||||
```
|
||||
|
||||
```
|
||||
vendor_cluster_size{cache_manager="keycloak",node="keycloak-0-46674"} 2.0
|
||||
```
|
||||
|
||||
> **두 값이 다를 수 있다.** 각 노드가 자기가 아는 멤버 수를 보고하므로,
|
||||
> 분단되면 한쪽은 2 다른 쪽은 1 이 된다. **한 노드만 보면 분단을 놓친다.**
|
||||
|
||||
> **셋이 다른 것을 본다.** 로그는 「그때 그렇게 보였다」이고, 테이블은
|
||||
> 「지금 등록되어 있다」이며, 지표는 「지금 그 노드가 그렇게 안다」이다.
|
||||
> A-1 에서 이 셋이 갈렸다 — 테이블에는 둘 다 있는데 메시지는 안 갔다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 밖에서 닿는지
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
|
||||
```
|
||||
```
|
||||
200
|
||||
```
|
||||
|
||||
브라우저로 `https://auth.hyeonworks.com/admin` 에 들어가 관리자로 로그인한다.
|
||||
비밀번호는 위 2-3 의 Secret 에 있다.
|
||||
|
||||
---
|
||||
|
||||
## 막히면
|
||||
|
||||
| 증상 | 어디를 보나 |
|
||||
|---|---|
|
||||
| 파드가 `Pending` | `describe pod` 의 Events — 스케줄 불가 사유 |
|
||||
| `ImagePullBackOff` | 이미지 이름·태그. 자체 빌드면 두 노드 모두에 반입했는가 |
|
||||
| `CrashLoopBackOff` | `logs --previous` |
|
||||
| `Running` 인데 `0/1` | readiness 프로브 실패. `describe` 의 Conditions |
|
||||
| 밖에서 502 | Ingress → Service → Endpoints 순으로 뒤를 본다 |
|
||||
| 클러스터가 1로 보임 | 7800 이 막혔거나 디스커버리 실패. 위 4번 셋 다 확인 |
|
||||
|
||||
---
|
||||
|
||||
## 근거를 재려면 (선택)
|
||||
|
||||
세션이 실제로 어디 저장되는지는 DB 를 직접 본다.
|
||||
|
||||
```bash
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select offline_flag, count(*) from offline_user_session group by 1"
|
||||
```
|
||||
|
||||
`offline_flag='0'` 이 온라인 세션이다. 로그인하고 이 수가 늘면
|
||||
`persistent-user-sessions` 가 켜져 있는 것이고, 안 늘면 메모리에만 있는 것이다.
|
||||
그 차이가 A층 결론 전체를 뒤집는다 —
|
||||
[A-7](../../experiment-a7-volatile-comparison.md)
|
||||
@@ -0,0 +1,131 @@
|
||||
# 06 — 관측
|
||||
|
||||
## 이 단계가 끝나면
|
||||
|
||||
Prometheus 가 Keycloak 을 긁고, `vendor_cluster_size` 로 클러스터 상태를
|
||||
밖에서 볼 수 있다.
|
||||
|
||||
## 전제
|
||||
|
||||
[05](../05-keycloak/) 가 끝나 Keycloak 두 노드가 떴다.
|
||||
|
||||
## 왜 필요한가
|
||||
|
||||
실험의 판정을 **밖에서만** 하면 놓친다. A-1 에서 7800 을 끊었는데 외부 응답이
|
||||
전부 200 이었다 — 분단된 노드가 스스로 로드밸런서에서 빠졌기 때문이다.
|
||||
클러스터 안을 보는 눈이 따로 있어야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 적용
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
kubectl apply -f deploy/lab/k8s/observability.yaml
|
||||
kubectl -n observability rollout status deploy/prometheus --timeout=180s
|
||||
```
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
kubectl -n observability get pods
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
grafana-845b5678cf-b6gvc 1/1 Running
|
||||
node-exporter-9qk9w 1/1 Running
|
||||
node-exporter-c2mz4 1/1 Running
|
||||
prometheus-6774f94f7c-pzr2t 1/1 Running
|
||||
```
|
||||
|
||||
node-exporter 가 **둘**인 것은 DaemonSet 이라 노드마다 하나씩 뜨기 때문이다.
|
||||
|
||||
## 2. 무엇을 긁고 있나 — 여기가 중요하다
|
||||
|
||||
**확인**
|
||||
```bash
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- localhost:9090/api/v1/targets | grep -o '"job":"[^"]*"' | sort -u
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
"job":"keycloak"
|
||||
"job":"kubelet"
|
||||
"job":"node-exporter"
|
||||
"job":"prometheus"
|
||||
```
|
||||
|
||||
**★ Redis · BFF · PostgreSQL 이 없다.** 이 실험대는 그것들을 긁지 않는다.
|
||||
그래서 B층 실험 대부분에 Grafana 화면이 없는데, **안 찍은 것이 아니라 지표가
|
||||
없는 것**이다.
|
||||
|
||||
> 이것을 「스크린샷 누락」이 아니라 **측정된 공백**으로 기록했다.
|
||||
> [`evidence/followup/04-observability-gap.txt`](../../evidence/followup/04-observability-gap.txt)
|
||||
|
||||
## 3. 클러스터 상태를 본다
|
||||
|
||||
**하기**
|
||||
```bash
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
|
||||
```
|
||||
|
||||
**실측**
|
||||
```
|
||||
keycloak-1 → 2
|
||||
keycloak-0 → 2
|
||||
```
|
||||
|
||||
**두 노드가 각각 자기가 아는 멤버 수를 보고한다.** 분단되면 한쪽은 2,
|
||||
다른 쪽은 1 이 된다 — **한 노드만 보면 분단을 놓친다.**
|
||||
|
||||
자주 보는 지표들이다.
|
||||
|
||||
| 지표 | 무엇 |
|
||||
|---|---|
|
||||
| `vendor_cluster_size` | 이 노드가 아는 멤버 수 |
|
||||
| `vendor_jgroups_*` | JGroups 프로토콜별 카운터 |
|
||||
| `vendor_statistics_approximate_entries_unique{cache="sessions"}` | 이 노드의 세션 캐시 엔트리 수 |
|
||||
| `agroal_*` | JDBC 커넥션 풀 |
|
||||
| `up` | 스크레이프 성공 여부 |
|
||||
|
||||
## 4. `up` 을 믿지 않는다
|
||||
|
||||
**A-2 에서 503 이 나는 동안에도 `up` 은 1 이었다.** 프로세스가 살아 있고
|
||||
`/metrics` 가 응답하기만 하면 1 이므로 **「살아 있지만 쓸모없는」 상태를
|
||||
보지 못한다.**
|
||||
|
||||
```bash
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=up'
|
||||
```
|
||||
|
||||
경보를 걸 때는 `up == 0` 만으로 부족하고 **기능 지표**를 함께 본다.
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
|
||||
```
|
||||
|
||||
## 5. Grafana 를 볼 때
|
||||
|
||||
**하기** — 밖에 열지 않고 포트포워드로 본다
|
||||
```bash
|
||||
kubectl -n observability port-forward svc/grafana 3000:3000
|
||||
```
|
||||
|
||||
브라우저에서 `http://localhost:3000`.
|
||||
|
||||
> 실험 중에는 Grafana 보다 **Prometheus 쿼리 API** 가 편하다. 값을 그대로
|
||||
> 뽑아 비교할 수 있고 스크린샷보다 근거로 남기기 좋다.
|
||||
|
||||
---
|
||||
|
||||
## 막히면
|
||||
|
||||
| 증상 | 원인 | 확인 |
|
||||
|---|---|---|
|
||||
| Keycloak 지표가 안 보임 | 9000 이 안 열렸거나 스크레이프 설정 누락 | 위 2번 targets |
|
||||
| 값이 한 노드만 나옴 | 다른 노드 스크레이프 실패 | targets 의 `health` 필드 |
|
||||
| 컨테이너 안에서 curl 실패 | **Keycloak 이미지에 curl 이 없다** | 밖에서 Prometheus 로 묻는다 |
|
||||
| Grafana 에 데이터 없음 | 데이터소스 주소 오류 | Prometheus 서비스 이름 확인 |
|
||||
@@ -0,0 +1,73 @@
|
||||
# 실습 가이드 — 직접 쳐보면서 만드는 실험대
|
||||
|
||||
이 문서 묶음은 **읽는 문서가 아니라 따라 치는 문서**다. 기존
|
||||
[`experiment-*.md`](../) 가 「무엇을 발견했나」를 적었다면, 여기는
|
||||
「그 발견을 재현하려면 무엇을 어떤 순서로 치는가」를 적는다.
|
||||
|
||||
## 두 종류의 명령을 구별해 적는다
|
||||
|
||||
실무자가 터미널에서 치는 명령과, 근거를 남기려고 재는 명령은 길이도 목적도
|
||||
다르다. 이 가이드는 둘을 섞지 않는다.
|
||||
|
||||
| 표시 | 무엇인가 |
|
||||
|---|---|
|
||||
| **하기** · **확인** | 실무자가 실제로 치는 형태. 짧고, 한 번에 하나씩 |
|
||||
| **근거를 재려면** | 이 실험대가 문서에 남기려고 쓴 긴 형태. 평소에는 필요 없다 |
|
||||
|
||||
예를 들어 nginx 에러 로그가 잘렸을 때, 실무자는 잘린 걸 보고 access 로그로
|
||||
넘어간다. 길이를 재서 2048인지 확인하는 것은 **몰라서 재는** 것이고, 알면
|
||||
재지 않는다.
|
||||
|
||||
같은 이유로 `curl` 도 두 형태가 있다.
|
||||
|
||||
```bash
|
||||
curl -I https://auth.hyeonworks.com/realms/master # 한 번 볼 때
|
||||
curl -s -o /dev/null -w '%{http_code}\n' <url> # 여러 번 재서 비교할 때
|
||||
```
|
||||
|
||||
이 가이드의 **확인**은 값을 대조해야 해서 두 번째 형태를 자주 쓴다. 실제로
|
||||
터미널에서 눈으로 볼 때는 첫 번째로 충분하다.
|
||||
|
||||
## 자리표시자를 두지 않는다
|
||||
|
||||
`<토큰>` 처럼 적어 두면 그 값을 어디서 가져오는지가 문서 밖으로 나간다.
|
||||
이 가이드는 **값을 찾는 명령을 함께 적는다.**
|
||||
|
||||
```bash
|
||||
TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
|
||||
echo "${#TOKEN} 자" # 값이 아니라 길이만 확인한다
|
||||
```
|
||||
|
||||
비밀은 길이나 존재 여부만 확인하고 값을 찍지 않는다. 터미널 스크롤백과
|
||||
화면 공유에 남기 때문이다.
|
||||
|
||||
## 순서
|
||||
|
||||
앞 단계가 끝나야 다음이 된다. 각 단계 첫머리에 「이 단계가 끝나면」이 있고,
|
||||
그 상태를 확인하는 명령이 있다. **그것이 통과해야 다음으로 넘어간다.**
|
||||
|
||||
| 단계 | 무엇을 세우나 | 끝나면 확인되는 것 |
|
||||
|---|---|---|
|
||||
| [00](00-lab-host/) | lab host 가상화 준비 | `virsh list` 가 돈다 |
|
||||
| [01](01-vms/) | VM 두 대 | 두 게스트에 SSH 가 붙는다 |
|
||||
| [02](02-k3s/) | k3s server + agent | `kubectl get nodes` 에 둘 다 Ready |
|
||||
| [03](03-nginx/) | 호스트 nginx 라우팅 | 밖에서 요청이 파드까지 닿는다 |
|
||||
| [04](04-tls/) | Let's Encrypt | `https://` 가 열리고 체인이 4단계 |
|
||||
| [05](05-keycloak/) | Keycloak 2노드 + PostgreSQL | 관리 콘솔 로그인이 된다 |
|
||||
| [06](06-observability/) | Prometheus · Grafana | `vendor_cluster_size` 가 2 |
|
||||
| [experiments](experiments/) | 실험 26건 | 각 실험의 판정 기준 |
|
||||
|
||||
## 이 가이드가 검증된 방식
|
||||
|
||||
**읽기 전용 확인은 돌아가는 실험대에서 실제로 실행해 출력을 그대로 실었다.**
|
||||
버전·IP·메모리 같은 값은 지어내지 않았다.
|
||||
|
||||
**만드는 명령은 다르다.** VM 을 다시 만들거나 k3s 를 다시 깔면 지금 돌고 있는
|
||||
실험대가 없어지므로, 그 명령들은 **실제로 구축할 때 쓴 것을 그대로 옮겼고**
|
||||
결과 상태를 확인하는 것으로 대신했다. 어느 쪽인지 각 단계에 표시한다.
|
||||
|
||||
## 막혔을 때
|
||||
|
||||
각 단계 끝에 **「막히면」** 표가 있다. 거기 적힌 증상은 전부 이 실험대가
|
||||
실제로 겪은 것이고, 원문은 [`../evidence/`](../evidence/) 에 있다.
|
||||
지어낸 실패 사례는 없다.
|
||||
Reference in New Issue
Block a user