21 KiB
02 — k3s 두 노드
이 단계가 끝나면
lab host 에서 kubectl get nodes 를 치면 두 노드가 Ready 로 나온다.
sudo 도 ssh 도 붙이지 않는다.
전제
01 이 끝나 lab host 에서 두 게스트에 SSH 가 붙는다.
어디서 치는가
이 단계는 전부 [lab host] 에서 친다. 게스트에 로그인하지 않는다.
자세한 이유는 가이드 공통 규약 에 있고,
요점만 옮기면 이렇다.
- 게스트에는 lab host 의 개인키도
~/.ssh/config도 없다. 게스트 안에서ssh kc-lab-1을 치면Host key verification failed로 끝난다. - 그 실패를
TOKEN=$(...)로 감싸면 오류는 화면으로 새고 변수는 빈 채로 남는다. 셸은 불평하지 않는다. - 그래서 3번의 토큰이 비고, 4번의 agent 설치가
--token is required로 죽는다. 설치 스크립트는 그 전까지를 다 성공으로 찍고 끝나기 때문에 설치 출력만 보면 성공으로 읽힌다.
셸을 하나만 쓰면 이 문제가 통째로 없어진다.
1. server 를 깐다 (kc-lab-1)
하기 — [lab host]
ssh kc-lab-1 'curl -sfL https://get.k3s.io | sudo sh -s - server --node-ip 192.168.122.11'
게스트에 들어가지 않고 원격 실행한다. cloud-init 이 NOPASSWD:ALL 을
넣어 두었으므로 비대화식 sudo 가 멈추지 않는다.
--node-ip 를 준다. 게스트에 인터페이스가 여럿이면 k3s 가 엉뚱한 것을 고를 수
있고, 그러면 두 노드가 서로를 다른 주소로 알게 된다.
확인 — 서버가 떴고 자기 자신을 노드로 등록했는가
ssh kc-lab-1 'sudo systemctl is-active k3s; sudo kubectl get nodes'
어디를 봐야 하는가 — 유닛이 active 인가, 그리고 get nodes 에
kc-lab-1 한 줄이 Ready 로 있는가. 설치 직후 30초 남짓은 NotReady
이거나 아예 목록이 비어 있는 것이 정상이다 — CNI 가 아직 안 올라온
시간이다. 한 번 더 친다.
이 가이드에서 kubectl 을 게스트 쪽에서 돌리는 것은 여기 한 번뿐이다.
lab host 에는 아직 kubeconfig 가 없기 때문이고, 그것을 두는 것이 바로 2번이다.
이 결과가 의미하는 것 — active + Ready 면 API 서버가 살아 있고
kubeconfig 도 자리를 잡았다는 뜻이라 2번으로 간다. 유닛이 active 인데
get nodes 가 접속 오류를 내면 API 서버가 아직 기동 중이다. 유닛이
activating 에서 안 넘어가거나 failed 면 설치 자체가 실패한 것이니
로그를 본다.
ssh kc-lab-1 'sudo journalctl -u k3s -n 50 --no-pager'
2. lab host 에 kubeconfig 를 둔다
왜 여기서 하나 — 이 뒤로 kubectl 을 계속 쓴다. 지금 한 번 해두면
남은 단계에서 ssh 도 sudo 도 붙이지 않는다. 그리고 agent 노드에는
kubeconfig 가 없으므로(6번) 클러스터를 볼 자리를 먼저 정해 두는 편이 낫다.
하기 — [lab host]
mkdir -p ~/.kube
ssh kc-lab-1 'sudo cat /etc/rancher/k3s/k3s.yaml' \
| sed 's|127.0.0.1|192.168.122.11|' > ~/.kube/config
chmod 600 ~/.kube/config
세 줄 다 필요하다.
| 줄 | 빠뜨리면 |
|---|---|
mkdir -p ~/.kube |
> 는 파일을 열 뿐 경로를 만들지 않는다. cat 이 시작되기도 전에 No such file or directory 로 끝난다 |
sed |
k3s 가 쓴 주소는https://127.0.0.1:6443 이다. 게스트 안에서만 맞는 주소라 lab host 에서는 자기 자신의 6443 을 두드리게 된다 |
chmod 600 |
이 파일은 클러스터 admin 자격증명이다. 비밀번호 파일과 같은 급으로 다룬다 |
sudo는cat에만 걸리고>에는 걸리지 않는다. 리다이렉션은 셸이 명령보다 먼저 현재 사용자 권한으로 처리하기 때문이다. 그래서 출력 파일은 홈 아래(~/.kube/config)에 둔다.
확인 — 주소가 바뀌었고, 밖에서 붙는가
grep server: ~/.kube/config
kubectl get nodes
실측
server: https://192.168.122.11:6443
NAME STATUS ROLES AGE VERSION
kc-lab-1 Ready control-plane 47m v1.36.4+k3s1
어디를 봐야 하는가 — server: 값에 127.0.0.1 이 남아 있으면 sed 가
안 먹은 것이다. 그다음 get nodes 가 sudo 없이 도는가. 아직 노드는
한 줄뿐인 것이 정상이다 — agent 는 4번에서 붙인다.
이 결과가 의미하는 것 — 통과하면 이 뒤의 kubectl 은 전부 lab host 에서
친다. x509 오류가 나면 파일 안의 CA 와 서버가 지금 쓰는 CA 가 어긋난
것이다 — k3s 를 다시 깔았다면 이 복사도 다시 해야 한다. connection refused
면 주소는 맞는데 API 서버가 아직 안 뜬 것이다.
인증서 SAN 에 두 주소가 다 들어 있어서 이
sed가 통한다. 확인하려면ssh kc-lab-1 'sudo openssl x509 -in /var/lib/rancher/k3s/server/tls/serving-kube-apiserver.crt -noout -ext subjectAltName'을 친다.IP Address:127.0.0.1과IP Address:192.168.122.11이 둘 다 보인다. 8번의 SSH 터널이127.0.0.1로 붙을 수 있는 것도 같은 이유다.
3. 토큰을 꺼낸다
하기 — [lab host]. 화면에 찍어 눈으로 옮기지 말고 변수로 받는다
TOKEN=$(ssh kc-lab-1 'sudo cat /var/lib/rancher/k3s/server/node-token')
echo "${#TOKEN} 자" # 값이 아니라 길이만 확인한다
실측 — 이 실험대에서는 108자였다. (K10<해시>::server:<비밀번호> 형식이라
k3s 판올림에 따라 자릿수가 달라진다. 중요한 것은 값이 아니라 0 이 아니라는
것이다.)
어디를 봐야 하는가 — 찍히는 것은 자릿수 하나뿐이다. 값은 보지 않는다 — 화면에 띄우는 순간 터미널 스크롤백과 셸 히스토리에 남는다.
이 결과가 의미하는 것 — 세 자리 수가 나오면 토큰을 손에 쥔 것이니 4번으로
넘어간다. 0 이면 변수가 비었다는 뜻이고 원인은 셋 중 하나다.
0 인 이유 |
확인 |
|---|---|
| 게스트 안에서 쳤다 (가장 흔하다) | 프롬프트가kc-lab-1 이면 exit 로 lab host 로 나온다 |
| server 가 아직 안 떠서 파일이 없다 | ssh kc-lab-1 'sudo ls -l /var/lib/rancher/k3s/server/node-token' |
| 새 셸을 열어 변수가 사라졌다 | echo "${#TOKEN} 자" 를 4번을 칠 바로 그 셸에서 다시 친다 |
4번을 3번과 같은 셸에서 친다. 변수는 셸 밖으로 나가지 않는다. 창을 새로 열거나
ssh로 어딘가 들어갔다 나오면TOKEN은 없다.
4. agent 를 붙인다 (kc-lab-2)
하기 — [lab host]. 3번과 같은 셸에서 친다
[ ${#TOKEN} -ge 50 ] || echo "TOKEN 이 비었다 — 3번으로 돌아간다"
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"
첫 줄의 가드를 빼지 않는다. $TOKEN 이 비면 게스트에는
--token '' --node-ip ... 가 전달되고, agent 는 기동 즉시
level=fatal msg="Error: --token is required" 로 죽는다. 그런데 설치
스크립트는 내려받기·유닛 생성·enable 까지 다 성공으로 찍고 끝나고, 유닛은
Restart=always 라 5초마다 조용히 재시도한다. 가드 한 줄이 그 몇 분을 막는다.
토큰을 셸 히스토리에 남기고 싶지 않으면 파일로 넘긴다. 이러면 변수를 쓰지 않으므로 3번의 「같은 셸」 제약도 없어진다.
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 가 클러스터에 들어왔는가, 그리고 제 주소로 들어왔는가
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
어디를 봐야 하는가 — -o wide 를 준 이유가 마지막 열이다. INTERNAL-IP
두 개가 1번·4번에서 --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'
5. 유닛 이름이 다르다
| 노드 | 유닛 |
|---|---|
| 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 다」라는 답이다.
이 결과가 의미하는 것 — 4번의 get nodes -o wide 는 k3s 가 보고한
IP 이고, 이 줄은 우리가 준 IP 다. 둘이 다르면 옵션이 안 먹은 것이다.
그리고 뒤의 실험에서 노드를 멈출 때 칠 유닛 이름이 노드마다 다르다는 것을
여기서 확인해 둔다 — systemctl stop k3s 를 agent 노드에서 치면 아무 일도
일어나지 않고, 「주입했는데 증상이 없다」로 오독하게 된다.
이 차이가 A-4 에서 결과를 갈랐다. server 노드를 잃으면
kubectl자체가 불통이 되고, agent 를 잃으면kubectl은 되지만 그 위의 워크로드가 사라진다.
6. agent 노드에서는 kubectl 이 안 된다
kc-lab-2 에 들어가 sudo kubectl get pods -A 를 치면 이렇게 끝난다.
Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
kubectl 명령 자체는 있다. 설치 스크립트가
/usr/local/bin/kubectl -> k3s 심볼릭 링크를 만들기 때문이다. 없는 것은
붙을 곳을 알려 주는 파일, 곧 kubeconfig 다.
| 어디를 찾나 | kc-lab-1 | kc-lab-2 |
|---|---|---|
$KUBECONFIG |
(비어 있음) | (비어 있음) |
~/.kube/config |
없음 | 없음 |
/etc/rancher/k3s/k3s.yaml |
있음 | 없음 |
넷 다 못 찾으면 kubectl 은 오류를 내지 않고 하드코딩된 기본값
http://localhost:8080 으로 넘어간다. 쿠버네티스 1.20 이전 API 서버가
평문으로 열던 레거시 포트인데 지금은 아무도 열지 않는다.
localhost:8080이 보이면 네트워크 문제가 아니라 「설정을 하나도 못 찾았다」는 뜻이다. 이 주소는 어디에도 적혀 있지 않다. 방화벽이나 k3s 를 의심하기 전에 kubeconfig 부터 본다.
워커라서 파드가 안 보이는 것이 아니다. kubectl 은 그냥 HTTP 클라이언트라
어디서 실행하든 상관없다. k3s.yaml 을 kc-lab-2 로 복사해 넣으면 거기서도
전부 보인다. 그래도 복사하지 않는다 — 워커 한 대가 털리면 클러스터 전체가
털리는 구성이 된다. agent 가 가진 자격증명은 급이 다르다.
subject=O = system:nodes, CN = system:node:kc-lab-2
이 신원은 Node authorizer 와 NodeRestriction admission 이 자기 노드에
배정된 객체만 다루도록 제한한다. 게다가 그 자격증명은 kubelet 전용 경로
(/var/lib/rancher/k3s/agent/)에 있어 kubectl 이 읽지도 않는다. 그래서 실패가
「권한 없음(403)」이 아니라 「설정 없음(localhost:8080)」으로 나타난다.
그래서 클러스터는 2번에서 만든 lab host 의 kubeconfig 로 본다. 개념
설명은 session-lab-concepts.md 의
「agent 노드에는 kubeconfig 가 없다」에 있다.
7. k3s 가 기본으로 딸려 오는 것
따로 설치하지 않아도 이미 있다.
| 무엇 | |
|---|---|
| Traefik | 인그레스 컨트롤러.:80 을 듣는다 |
| servicelb (klipper-lb) | LoadBalancer 타입을 호스트 포트로 매핑 |
| local-path | 기본 StorageClass.노드 로컬 디스크 |
| flannel | 파드 네트워크 (VXLAN) |
| kube-router | NetworkPolicy 집행 |
확인 — [lab host]. 무엇이 이미 돌고 있고, 기본 저장소가 무엇인가
kubectl get pods -A
kubectl get storageclass
실측
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-54996dc9b4-x5bsz 1/1 Running 0 49m
kube-system helm-install-traefik-cnv8z 0/1 Completed 2 (48m ago) 48m
kube-system helm-install-traefik-crd-c6vft 0/1 Completed 0 48m
kube-system local-path-provisioner-77b9867795-5k8d2 1/1 Running 0 49m
kube-system metrics-server-6dc596dfb8-nzwt2 1/1 Running 0 49m
kube-system svclb-traefik-a18ee1fc-2s9rl 2/2 Running 0 48m
kube-system svclb-traefik-a18ee1fc-xmrrt 2/2 Running 0 22m
kube-system traefik-59b7647586-rsrbc 1/1 Running 0 48m
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 49m
어디를 봐야 하는가 — get pods -A 에서는 NAMESPACE 열이 kube-system
인 줄들의 STATUS. Running 과 Completed 가 섞여 있는 것이 정상이다 —
helm-install-traefik-* 는 일회성 잡이라 Completed 로 남는다.
svclb-traefik-* 가 두 줄인 것도 봐 둔다. DaemonSet 이라 노드마다 하나씩
뜨는 것이고, 이 두 줄이 4번의 join 이 실제로 먹었다는 또 하나의 증거다.
get storageclass 에서는 이름 뒤의 (default) 표시가 어디 붙어 있는가.
이 결과가 의미하는 것 — 여기 뜬 것들은 우리가 안 깔았는데 있는 것이고,
뒤 단계에서 「왜 80 포트가 이미 잡혀 있지」·「왜 PVC 가 이 노드에만 묶이지」의
답이 전부 이 목록에 있다. local-path 에 (default) 가 붙어 있으면
05 의 PVC 는 StorageClass 를 안 적어도 이것으로 만들어진다. Pending 이나
CrashLoopBackOff 가 섞여 있으면 그 파드부터 describe 로 본다.
local-path가 기본이라는 것이 A-4 에서 비용을 청구한다. PVC 가 만들어진 노드에 묶여 다른 노드로 재배치되지 않는다.
8. 워크스테이션에서 쓰려면
2번의 kubeconfig 를 그대로 가져와도 안 된다. 게스트는 libvirt NAT 안에
있어서 워크스테이션에서 192.168.122.11 로 가는 경로가 없다.
[워크스테이션] $ ping -c1 192.168.122.11
1 packets transmitted, 0 received, 100% packet loss
lab host 를 거치는 터널을 뚫는다.
하기 — [워크스테이션]
# 1) 터널. 이 창은 열어 둔다
ssh -N -L 6443:192.168.122.11:6443 test-server
# 2) 다른 창에서 — 게스트 원본을 그대로 가져온다. sed 가 필요 없다
mkdir -p ~/.kube
ssh test-server "ssh kc-lab-1 'sudo cat /etc/rancher/k3s/k3s.yaml'" > ~/.kube/kc-lab.yaml
chmod 600 ~/.kube/kc-lab.yaml
export KUBECONFIG=~/.kube/kc-lab.yaml
2번과 달리 sed 를 치지 않는다. k3s 원본이 이미
https://127.0.0.1:6443 이고, 터널 덕에 워크스테이션에서는 그 주소가 맞기
때문이다. 2번이 192.168.122.11 로 바꿨던 것은 lab host 에서 볼 때
127.0.0.1 이 lab host 자신을 가리키기 때문이었다. 같은 파일이라도 어느
기계에서 읽느냐에 따라 맞는 주소가 다르다.
확인
grep server: ~/.kube/kc-lab.yaml
kubectl get nodes
어디를 봐야 하는가 — server: 가 https://127.0.0.1:6443 인가. 그다음
get nodes 가 4번과 같은 두 줄을 내놓는가.
이 결과가 의미하는 것 — 터널 덕에 워크스테이션의 6443 이 kc-lab-1 의
6443 이다. API 서버 인증서 SAN 에 127.0.0.1 이 들어 있어서(2번의 각주)
인증서 검증도 통과한다. 타임아웃이면 1)의 터널 창이 닫힌 것이고,
connection refused 면 터널은 살아 있는데 반대편 API 서버가 죽은 것이다.
터널이 닫히면
kubectl이 통째로 멎는다. 이 실험대의 A층 실험은 노드를 죽이고 살리는 것이 목적이라, 터널 상태와 클러스터 상태가 섞여 오독하기 쉽다. A층 실험은 lab host 에서 치는 것을 권한다.
막히면
| 증상 | 원인 | 확인 |
|---|---|---|
echo "${#TOKEN} 자" 가 0 |
게스트 안에서 쳤거나, 셸이 바뀌었다 | 프롬프트가kc-lab-1 이 아닌지. 3번 표 |
| agent 설치는 성공했는데 노드가 안 보임 | --token 이 빈 문자열 |
ssh kc-lab-2 'sudo journalctl -u k3s-agent -n 30 --no-pager' 에 --token is required |
agent 가NotReady |
토큰·주소 오타 | ssh kc-lab-2 'sudo journalctl -u k3s-agent -n 30 --no-pager' |
| 노드 IP 가 예상과 다름 | --node-ip 없이 설치 |
kubectl get nodes -o wide |
lab host 에서kubectl 이 No such file |
mkdir -p ~/.kube 를 빠뜨렸다 |
위 2번 |
lab host 에서connection refused |
kubeconfig 의127.0.0.1 을 안 바꿨다 |
grep server: ~/.kube/config |
kc-lab-2 에서 localhost:8080 |
agent 에는 kubeconfig 가 없다.정상이다 | 위 6번 |
| 워크스테이션에서 타임아웃 | 터널이 없다 | 위 8번 |
x509 오류 |
k3s 재설치로 CA 가 바뀌었다 | 2번의 복사를 다시 한다 |
| 파드가 한 노드에만 몰림 | 스케줄러 판단 | topologySpreadConstraints 로 강제 |