개요
Ubuntu가 설치된 Mac Mini에 kind 기반 k8s를 구축하는 과정을 소개한다.
(들어가며 - Local K8S Cluster 간략 소개)
로컬 K8S 도구는 kind를 선택했다. 대표적으로 kind, minikube, kubeads 등이 있는데 해당 글에서 자세히 소개하진 않겠다. [k8s docs] 나는 EKS와 동작이 가까워야 사내 인프라에 대한 학습 효과가 좋다고 판단해서 kind를 선택했다. kind는 노드를 docker 기반으로 띄운다.
1. Network 환경 구성
SRE가 된 지 어느덧 1년이 지났다. 그간 K8S 지식·운영에 대해 얼마나 성장했을까 돌이켜보면 부끄럽지만 아직 한참 부족한 점이 많았다. 리더와 이 고민을 공유했을 때 가지고 계신 구형 맥미니에 Ubuntu 서버를 설치해서 대여해주셨다. (최고의 리더 🥲)
이렇게 Ubuntu & Mac Mini 딱 기본 세팅이 되어있는 환경에 초기 네트워크 구축부터 진행해보았다.

📝 맥북에서 맥미니로의 SSH 접근은 공유기를 거친다
맥미니에 SSH로 붙으려면 맥북에서 맥미니까지 패킷이 흘러가야 하는데, 둘을 직접 케이블로 연결한 게 아니라면 이 트래픽은 공유기를 통과한다. 맥북은 무선(WiFi), 맥미니는 유선 LAN으로 공유기에 물려 있고, 두 장비 모두 공유기를 중심으로 한 동일 네트워크 안에 있기 때문이다.
여기서 짚고 넘어갈 점은, 가정용 공유기가 사실 한 박스 안에서 여러 역할을 동시에 수행하는 복합 장비라는 것이다. 일반적으로 공유기는 다음 역할을 한다.
- 무선 AP (Access Point): WiFi 신호를 LAN에 연결
- 스위칭 허브 (L2): LAN 포트 간 / WiFi–LAN 간 프레임을 MAC 주소 기반으로 포워딩
- 라우터 (L3): 내부망(LAN) ↔ 외부망(WAN) 간 IP 라우팅, default gateway 역할
- NAT: 사설 IP ↔ 공인 IP 변환 (외부로 나갈 때 SNAT)
- DHCP 서버: 내부망 장비에 사설 IP 자동 할당
1.1. 공유기와 맥미니를 유선(LAN)으로 연결하기
랜선을 연결한 뒤 ip -br link로 인터페이스 상태를 확인한다. 처음엔 NO-CARRIER가 떠 있는데, 이는 인터페이스는 켜졌지만 물리 링크(케이블 신호)가 없다는 뜻이다. 케이블을 제대로 연결해주면 UP으로 붙으면서 통신할 준비가 된다.
ip -br link
# link: L2 인터페이스 정보를 보여준다.
# -br 축약을 통해 이름 | 상태 | MAC | 플래그를 확인한다.

유선 LAN이 연결된 후, 상태는 DOWN → UP으로 변경되었고 NO-CARRIER → LOWER UP으로 변경된 걸 알 수 있다. 이제 인터넷 환경과 통신할 준비가 되었다는 의미다.
- lo: loopback interface. 호스트가 자기 자신과 통신할 때 쓰는 가상 인터페이스로, 127.0.0.1이 여기 붙는다. 항상 존재하고 현재 정상상태!
- enp2s0f0: 실제 이더넷 NIC(물리 랜 포트) PCI 버스 2번, 슬롯 0, function 0에 있는 이더넷 장치라는 의미다. (By. Predictable Network Interface)
- en = Ethernet
- p2 = PCI bus 2
- s0 = slot 0
- f0 = function
- UP = 관리상 활성화됨 (admin up)
- LOWER_UP = 물리 링크가 살아있음 (케이블 연결되어 캐리어 신호 있음)
- NO-CARRIER = admin은 up인데 물리 링크가 없음 (케이블 빠짐/죽은 링크)
- BROADCAST / MULTICAST = 해당 통신 방식 지원
1.2. ip 할당
IP를 할당하려면 먼저 공유기 대역과 게이트웨이(Router)를 알아야 한다. 같은 공유기에 연결된 핸드폰의 Wi-Fi 상세 정보에서 IP 주소와 라우터(게이트웨이)를 확인할 수 있다.

IP를 직접 할당하는 이유는 이 환경에서 DHCP 클라이언트가 없어 IP가 자동으로 잡히지 않았다. 따라서 ip 명령어로 대역 안의 미사용 주소를 직접 할당해주었다.
# 공유기 LAN 대역(172.30.1.0/24) 중 미사용 주소를 맥미니에 할당
sudo ip addr add 172.30.1.200/24 dev enp2s0f0
📝 private IP
위에서 할당한 172.30.1.200은 사설(private) IP다. 사설 IP의 핵심 성질은 전 세계적으로 유일하지 않다는 점이다. 10.x, 172.16~31.x, 192.168.x 대역은 누구나 자기 망에서 자유롭게 쓰라고 비워둔 것이라, 같은 172.30.1.200이 수많은 네트워크에 동시에 존재하며 각각 다른 기기를 가리킨다. 즉 이 IP는 "내 LAN 안에서의 맥미니"일 뿐, 전역적으로 특정 기기를 의미하지 않는다. 그래서 인터넷은 사설 IP로 라우팅하지 못한다(어느 네트워크의 172.30.1.200인지 모호하므로).
그렇다면 어떻게 인터넷과 통신할까? 공유기가 경계에서 NAT(주소 변환)을 해주기 때문이다.
- 맥미니가 패킷을 보냄 (출발지 172.30.1.200) → 공유기 도착
- 공유기가 출발지 IP를 자기 public IP(ISP가 준 공인 주소)로 바꿔치기하고, 이 연결을 NAT 테이블에 기록
- 응답이 공유기 public IP로 돌아오면, NAT 테이블을 보고 다시 172.30.1.200으로 되돌려 맥미니에 전달
즉 LAN 안에선 다들 사설 IP를 쓰고, 밖으론 공유기 public IP 하나를 공유한다. 맥미니는 인터넷에 직접 노출된 것이 아니라 같은 LAN 안에서만 사설 IP로 접근 가능하다는 점이다.
1.3. Gateway (Default Route) 설정
서브넷을 통해 인터넷으로 나가려면 게이트웨이로 향하는 default route가 필요하다.
- 공유기 → 인터넷으로 가기 위한 L3 설정
# default route 추가 (AWS의 route table 0.0.0.0/0 엔트리에 해당)
sudo ip route add default via 172.30.1.254
- ip route add default via ...경로(routing) : 밖으로 나갈 땐 이 게이트웨이로
- default : 목적지가
0.0.0.0/0이라는 뜻. 즉 "다른 더 구체적인 경로에 안 걸리는 모든 목적지"를 가리킨다
즉, 로컬 서브넷 밖으로 나가는 모든 패킷은 일단 172.30.1.254(게이트웨이)한테 넘기는 것을 설정해준다.
1.4. DNS 설정
도메인 이름을 IP로 해석하려면 nameserver가 필요하다. 이 해석을 어느 DNS 서버에게 물어볼지 설정되어 있어야 하고, 그 설정 파일이 /etc/resolv.conf다. 이 환경에는 DHCP가 없어 DNS 서버가 자동으로 지정되지 않으므로, 외부 공개 DNS를 직접 작성한다.
# cloudflare의 public resolver (google의 8.8.8.8과 같은)
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
지정 후에 ping 1.1.1.1, ping ${domain}으로 ip 통신, dns 해석 여부를 확인한다.
ping -c2 1.1.1.1 # Ip로 직접 > 라우팅만 검증
ping -c2 archive.ubuntu.com # 도메인으로 라우팅 + dns 해석까지 검증
설정 과정에서 에러 메시지로 원인을 구분해보면..
Network is unreachable / No route to host: 라우팅 문제Temporary failure resolving: DNS Nameserver 관련 문제
2. SSH 원격 접속 환경 구성
2.1. opnessh-server 설치와 접근 설정
Mac Mini 콘솔에서 일일이 타이핑하는 게 번거로워서 노트북에서 복붙할 수 있도록 SSH 접속을 붙이기로 했다. openssh-server를 설치하고 서비스를 기동한다.
sudo apt update
sudo apt install -y openssh-server
sudo systemctl enable --now ssh
ss -tlnp | grep :22
ssh user@ip

이렇게 노트북으로 직접 붙어서 필요한 도구들(docker, kind, kubectl)을 설치해주었다. 같은 LAN 내부에서 여러 기기 통신을 직접 해보니 내가 구축한 환경에 대한 이해가 더 잘 되었고 Network 트러블슈팅의 80%는 in/out을 통해 해결할 수 있다는 점에도 조금은 공감할 수 있었다.
2.2. (트러블슈팅) ARP가 풀리지 않아 SSH 실패
증상: 노트북에서 ssh 접근 시도 시 No route to host.
같은 LAN(서브넷) 내부에서는 라우팅이 아니라 ARP로 상대를 찾는다. IP 패킷을 보내려면 상대의 MAC 주소가 필요한데, ARP가 "172.30.1.200을 가진 게 누구냐"를 브로드캐스트하고 그 응답으로 MAC 주소를 받는다. 이 응답을 못 받으면 OS가 No route to host를 낸다.
- ARP: 같은 LAN에서 IP → MAC을 매핑하는 Protocol
- No route to host (같은 서브넷 기준) : 라우트 문제가 아니라 ARP가 풀리지 않은 것 (vs Network is unreachable = 라우트 자체가 없음)
route -n get 172.30.1.200 # 이 IP로 갈 때 실제로 어느 인터페이스를 타는지
arp -a | grep 172.30.1.200 # ARP 캐시 상태 ((incomplete)면 미해석)
원인은 맥미니로 갈 트래픽이 LAN이 아니라 VPN 터널로 빠지면서 ARP 요청이 LAN에 도달하지 못했고, VPN이 설치되지 않은 다른 노트북으로 접근하니 바로 잘 됐다. 덕분에 ARP를 직접 이해해볼 수 있었던 트러블슈팅이었다.
3. kind 설치 후 Cluster 생성하기
kind 설치와 클러스터 생성은 생각보다 간단했다. 단일 바이너리를 받아 $PATH에 넣는 것으로 설치가 끝난다. cluster 조작을 위해 kubectl도 별도로 설치해주었다.
3.1. kind & kubectl 설치
# kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.32.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
# kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

kind의 핵심은 노드가 곧 Docker 컨테이너라는 점이다. 보통 노드는 EC2 같은 머신이지만, kind에서는 컨테이너 하나가 노드 역할을 하고 그 안에서 kubelet, containerd, control-plane 컴포넌트가 모두 돌아간다. 단일 노드 구성에서는 control-plane 노드의 taint가 제거되어 일반 워크로드도 이 노드에 스케줄된다고 한다.

이렇게 보니 단일 노드가 아니라 멀티 노드로 띄워야 하는데... 그건 config 파일로 노드 토폴로지를 정의하는 방식으로 할 수 있다고 한다. 집에 가서 다시 시도!
# kind-config.yaml 요론식...
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
kind create cluster --name k8s-lab --config kind-config.yaml
마치며
생각보다 클러스터 자체보다 그 앞단의 네트워크 환경을 잡는 데 시간이 더 들었지만, 덕분에 평소 추상화에 가려 잘 안 보던 L2/L3 레이어를 손으로 다뤄볼 수 있었다. 이제 로컬에서 마음껏 부수고 만들 수 있는 K8S 환경이 생겼으니, 다음 편에서는 이 클러스터 위에 Istio를 올려 본격적으로 메시 실습을 시작해보자
'DevOps > DevOps' 카테고리의 다른 글
| [DevOps] SSH 없이 안전하게 EC2 운영하기: SSM 기반 접근 제어와 전역 정책 설계 (0) | 2025.12.07 |
|---|---|
| [K8S] 자주 사용하는 Kubernetes CLI 정리 (1) | 2025.09.28 |
| [DevOps] 멀티모듈 프로젝트 CI/CD 적용 - 설계 구상 편 (3) | 2024.07.22 |
