[Kind] macOS 로컬 k8s 설치 가이드
- 로컬 맥북(Mac) 환경에서
kind(Kubernetes IN Docker)를 이용해 멀티 노드 클러스터를 띄우고, 실제 네트워크와 구성 소개.

Kind는 도커 컨테이너를 노드처럼 쓰기 때문에 도커 데몬이 살아있는지 먼저 확인해야 한다.
# 도커 데몬 상태 및 실행 중인 컨테이너 확인
docker info
docker ps

설정 파일(Config)을 넣어서 Control Plane 1개 + Worker 1개로 구성한다.
특히 extraPortMappings 설정을 넣어줘야 나중에 NodePort 서비스 띄웠을 때 로컬(localhost)에서 접속 가능하다.
# kind-config.yaml 내용을 인라인으로 바로 적용해서 클러스터 생성
kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- role: worker
EOF

클러스터가 떴으니 노드 리스트와 이미지를 확인해 본다.
# Kind가 다운로드 받은 노드 이미지 확인
docker images
# kind 명령어로 노드 목록 확인 (myk8s-control-plane, myk8s-worker)
kind get nodes --name myk8s
# 현재 컨텍스트 및 네임스페이스 확인 (kubectx/kubens 설치 시)
kubens default


"로컬에서 어떻게 k8s에 붙는 걸까?"
Kind는 전용 도커 브리지 네트워크를 만들어서 그 안에서 노드(컨테이너)들을 묶는다.
# 도커 네트워크 리스트 확인 ('kind' 라는 네트워크가 생겼을 것임)
docker network ls
riverjin@gangjin-ung-ui-Macmini k8sDeploy % docker network ls
NETWORK ID NAME DRIVER SCOPE
09c6a26e9d6d bridge bridge local
0cb0081feebe host host local
468c485492a0 kind bridge local
1f0321e31b81 none null local
# kind 네트워크 상세 정보 확인 (서브넷 대역 확인용)
# -> docker desktop 기본값은 172.18.0.0/16, OrbStack 사용 시 192.168.97.0/24 등으로 다를 수 있음
docker inspect kind | jq
[
{
"Name": "kind",
"Id": "468c485492a0cb548c5d14218b609d97d20ec668dd70a3040651a6989741d8c7",
"Created": "2025-09-07T12:40:56.569091094Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv4": true,
"EnableIPv6": true,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
},
{
"Subnet": "fc00:f853:ccd:e793::/64",
"Gateway": "fc00:f853:ccd:e793::1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"a5d274543bba8a024b0902c9cc34fb860bb3304648dd113dafafbb942827c0ac": {
"Name": "myk8s-control-plane",
"EndpointID": "d28e2ba27ce7d8240cd5d2a06f2b8c650131c4deea60c3ad33c6aada06cb981a",
"MacAddress": "52:67:17:11:19:a9",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": "fc00:f853:ccd:e793::3/64"
},
"db37299466b8149864a6be3e555d3eadb9030309f590e9ae4505f98851936083": {
"Name": "myk8s-worker",
"EndpointID": "4a0770fc4b783374e6fec315e9c0f0f74da0a820920b57d9d82138ef979ae62d",
"MacAddress": "32:7b:d9:65:1f:78",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": "fc00:f853:ccd:e793::2/64"
}
},
"Options": {
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.driver.mtu": "65535",
"com.docker.network.enable_ipv4": "true"
},
"Labels": {}
}
]
kubectl이 어떻게 도커 컨테이너 안에 있는 API 서버랑 통신하는지 확인.
도커가 호스트의 포트를 컨테이너의 API 서버 포트(6443)로 포워딩해주고 있다.
# 클러스터 정보 및 API 서버 주소 확인 (https://127.0.0.1:xxxxx 형태)
kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:49674
CoreDNS is running at https://127.0.0.1:49674/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
# 도커 포트 매핑 확인 (PORTS 컬럼 확인)
riverjin@gangjin-ung-ui-Macmini k8sDeploy % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
db37299466b8 kindest/node:v1.32.8 "/usr/local/bin/entr…" 4 minutes ago Up 4 minutes myk8s-worker
a5d274543bba kindest/node:v1.32.8 "/usr/local/bin/entr…" 4 minutes ago Up 4 minutes 0.0.0.0:30000-30001->30000-30001/tcp, 127.0.0.1:49674->6443/tcp myk8s-control-plane
실제 쿠버네티스 내부 컴포넌트들이 뭘 쓰고 있는지 확인.
# 노드 정보 상세 확인
# -> OS-Image, Kernel, 그리고 **Container Runtime(CRI)**이 containerd 인지 확인
riverjin@gangjin-ung-ui-Macmini k8sDeploy % kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
myk8s-control-plane Ready control-plane 5m2s v1.32.8 172.18.0.3 <none> Debian GNU/Linux 12 (bookworm) 6.10.14-linuxkit containerd://2.1.3
myk8s-worker Ready <none> 4m48s v1.32.8 172.18.0.2 <none> Debian GNU/Linux 12 (bookworm) 6.10.14-linuxkit containerd://2.1.3
# 파드 정보 확인 (모든 네임스페이스)
# -> **CNI(네트워크 플러그인)**가 kindnet으로 동작 중인지 확인
riverjin@gangjin-ung-ui-Macmini k8sDeploy % kubectl get pod -A -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-668d6bf9bc-45vwj 1/1 Running 0 5m23s 10.244.0.3 myk8s-control-plane <none> <none>
kube-system coredns-668d6bf9bc-kdzvm 1/1 Running 0 5m23s 10.244.0.2 myk8s-control-plane <none> <none>
kube-system etcd-myk8s-control-plane 1/1 Running 0 5m31s 172.18.0.3 myk8s-control-plane <none> <none>
kube-system kindnet-mll82 1/1 Running 0 5m23s 172.18.0.3 myk8s-control-plane <none> <none>
kube-system kindnet-rd7g2 1/1 Running 0 5m19s 172.18.0.2 myk8s-worker <none> <none>
kube-system kube-apiserver-myk8s-control-plane 1/1 Running 0 5m31s 172.18.0.3 myk8s-control-plane <none> <none>
kube-system kube-controller-manager-myk8s-control-plane 1/1 Running 0 5m32s 172.18.0.3 myk8s-control-plane <none> <none>
kube-system kube-proxy-gr796 1/1 Running 0 5m19s 172.18.0.2 myk8s-worker <none> <none>
kube-system kube-proxy-w9l6d 1/1 Running 0 5m23s 172.18.0.3 myk8s-control-plane <none> <none>
kube-system kube-scheduler-myk8s-control-plane 1/1 Running 0 5m31s 172.18.0.3 myk8s-control-plane <none> <none>
local-path-storage local-path-provisioner-7dc846544d-sc2nc 1/1 Running 0 5m23s 10.244.0.4 myk8s-control-plane <none> <none>
k8s의 네임스페이스와 리눅스(Docker)의 네임스페이스는 개념이 다르다.
실제로 노드 역할을 하는 것은 도커 컨테이너다.
# k8s 논리적 네임스페이스 확인
kubectl get namespaces
NAME STATUS AGE
default Active 5m57s
kube-node-lease Active 5m57s
kube-public Active 5m57s
kube-system Active 5m57s
local-path-storage Active 5m53s
# 실제 물리적(?) 노드 역할을 하는 도커 컨테이너 확인
# 이름이 myk8s-control-plane, myk8s-worker 로 되어 있음
docker ps
docker images
# 컨트롤 플레인 컨테이너 내부로 들어가서 포트 리슨 상태 확인 (ss 명령)
docker exec -it myk8s-control-plane ss -tnlp
kubectl 명령어가 느리거나 이상할 때 디버깅하는 법.
# API 호출 과정을 상세하게 출력 (-v6 ~ -v9)
# -> 인증 정보 로드 과정, API 요청 URL 등이 다 보임
kubectl get pod -v6
# kubeconfig 파일 까보기 (인증서, 클러스터 주소, 유저 정보)
cat ~/.kube/config
# 또는 환경변수 확인
cat $KUBECONFIG
[Kind] 네트워크 패킷 여행기: 내 맥북에서 컨테이너까지
방금 ss -tnlp로 확인한 건 "컨테이너 내부 상황"이었다.
이제 "내 맥북(Host)에서 쏜 명령어가 어떻게 저 깊은 곳에 있는 6443 포트까지 닿는지" 그 연결 고리를 파헤쳐 본다.
이걸 이해해야 나중에 "왜 로컬에선 되는데 배포하면 안 되지?" 같은 네트워크 문제를 풀 수 있다.
kubectl 명령 한 번 칠 때 일어나는 일이다.
생각보다 과정이 많다.

~/.kube/config)kubectl은 무작정 통신하는 게 아니다. 먼저 설정 파일을 본다.
# cat ~/.kube/config 예시
clusters:
- cluster:
server: https://127.0.0.1:51234 # <--- 여기를 주목!
name: kind-myk8s
server 주소가 kind-control-plane 같은 도메인이 아니라 127.0.0.1(내 컴퓨터)로 되어 있다.6443이 아니라 51234 같은 임의의 5자리 포트가 적혀 있다. (Kind가 클러스터 만들 때 랜덤으로 배정함)내 맥북의 51234 포트는 누가 듣고 있을까? 바로 도커(Docker Desktop or OrbStack)다.
51234 포트를 리스닝(LISTEN)하고 있다가, 여기로 패킷이 들어오면 낚아챈다.myk8s-control-plane 컨테이너의 6443으로 갈 녀석이구나" 하고 토스해준다.# 맥북 터미널에서 확인해보면
docker ps
# 출력 예시:
# 0.0.0.0:51234->6443/tcp
# (내 맥북 51234로 오면 -> 컨테이너 6443으로 보내라)
도커는 패킷을 들고 가상 다리(Bridge Network)를 건넌다.
아까 docker network ls로 봤던 kind 네트워크다.
127.0.0.1에서 172.18.0.x(컨테이너 내부 IP)로 바뀐다. (DNAT)드디어 패킷이 myk8s-control-plane 컨테이너에 도착했다.
아까 ss -tnlp로 확인했던 그 프로세스가 맞이한다.
kube-apiserver 프로세스*:6443 (누구든 환영)127.0.0.1)에 말을 걸었지만, 사실은 도커가 중간에서 전화 내용을 받아서 컨테이너(172.18.x.x)로 전달해주고 있었다.kubectl이 안 된다면?docker ps 확인)~/.kube/config의 포트와 docker ps의 포트가 다른지 확인)