안녕하세요! 저는 최근 온프레미스 환경에서 Kubernetes 클러스터를 직접 구축하고 운영하면서 많은 것을 배우고 있습니다. 오늘은 그 여정의 첫 번째 날, "클러스터 기초와 아키텍처 이해"에 대해 이야기해보려 합니다.
처음 Kubernetes를 접할 때 가장 막막했던 부분이 "도대체 이 많은 컴포넌트들이 뭐하는 건데?"였습니다. Pod, Service, Deployment, etcd, CoreDNS... 용어만 들어도 머리가 복잡해지더라고요.
하지만 실제로 클러스터를 직접 만들고, 각 컴포넌트가 어떻게 동작하는지 하나씩 확인하면서 "아, 이래서 이렇게 설계했구나!" 하는 순간들이 있었습니다. 오늘 그 경험을 공유하고자 합니다.
저는 총 3개의 노드로 클러스터를 구성했습니다:
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE CONTAINER-RUNTIME
cpu1 Ready control-plane 46h v1.31.13 172.30.1.43 Ubuntu 22.04.5 LTS containerd://1.7.28
cpu2 Ready <none> 17h v1.31.13 172.30.1.80 Ubuntu 22.04.5 LTS containerd://1.7.28
gpu1 Ready <none> 17h v1.31.13 172.30.1.38 Ubuntu 22.04.5 LTS containerd://1.7.28
| 노드 | 역할 | 스펙 | 특징 |
|---|---|---|---|
| cpu1 | Master + Worker | 12코어, 7.5GB | 마스터 노드지만 taint 제거로 워커로도 사용 |
| cpu2 | Worker | 8코어, 16GB | 일반 워크로드 실행 |
| gpu1 | Worker | 12코어, 16GB | GPU 워크로드용 (향후 활용 예정) |
여기서 포인트!
보통 Master 노드는 Control Plane 컴포넌트만 실행하고 일반 Pod는 실행하지 않습니다. 하지만 저는 리소스 활용을 위해 cpu1의 taint를 제거했습니다.
$ kubectl describe node cpu1 | grep Taints
Taints: <none>
<none>이 보이시나요? 이제 cpu1에도 일반 애플리케이션 Pod를 배포할 수 있습니다!
가장 먼저 해본 명령어는 이거였습니다:
$ kubectl cluster-info
Kubernetes control plane is running at https://172.30.1.43:6443
CoreDNS is running at https://172.30.1.43:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
간단하지만 중요한 정보:
https://172.30.1.43:6443 - 모든 kubectl 명령어가 여기로 갑니다Kubernetes 클러스터를 사람에 비유하면, Control Plane은 "뇌"입니다. 모든 결정이 여기서 이루어지죠.
Control Plane은 4개의 핵심 컴포넌트로 구성됩니다:
$ kubectl get pods -n kube-system -o wide | grep cpu1
etcd-cpu1 1/1 Running 172.30.1.43 cpu1
kube-apiserver-cpu1 1/1 Running 172.30.1.43 cpu1
kube-controller-manager-cpu1 1/1 Running 172.30.1.43 cpu1
kube-scheduler-cpu1 1/1 Running 172.30.1.43 cpu1
모두 cpu1 (마스터 노드)에서 실행되고 있습니다. 각각의 역할을 알아봅시다:
$ kubectl get componentstatuses
NAME STATUS MESSAGE ERROR
etcd-0 Healthy ok
팁: 프로덕션 환경에서는 etcd를 반드시 백업하세요. 이게 바로 클러스터의 "두뇌 백업"입니다.
내가 깨달은 것:
kubectl get pods
↓
API Server (172.30.1.43:6443)
↓
etcd에서 Pod 정보 조회
↓
결과 반환
모든 작업이 API Server를 거친다는 게 핵심입니다!
실제로 본 예시:
제가 테스트로 Pod를 하나 삭제했을 때:
$ kubectl delete pod coredns-76b86bc878-5v86m -n kube-system
pod "coredns-76b86bc878-5v86m" deleted
$ kubectl get pods -n kube-system | grep coredns
coredns-76b86bc878-5v86m 1/1 Running 0 5s <- 자동 재생성됨!
coredns-76b86bc878-pnpjq 1/1 Running 0 46h
5초 만에 새 Pod가 생성되었습니다. 이게 바로 Controller Manager의 마법입니다! ✨
재미있는 발견:
$ kubectl get pods -A -o wide | grep -c cpu1
12
$ kubectl get pods -A -o wide | grep -c cpu2
3
cpu1에 Pod가 더 많은 이유? Control Plane Pod 4개 + CoreDNS 2개 + 기타 시스템 Pod들이 모두 cpu1에 있기 때문입니다!
$ kubectl get pods -A | wc -l
20 # 헤더 포함
처음엔 "19개나?"라고 놀랐습니다. 하지만 알고 보니 모두 필요한 시스템 Pod들이었어요.
1. Control Plane Pod (4개) - Static Pod
etcd-cpu1
kube-apiserver-cpu1
kube-controller-manager-cpu1
kube-scheduler-cpu1
여기서 발견! 이들은 /etc/kubernetes/manifests/에 YAML 파일로 정의되어 있습니다:
$ ls /etc/kubernetes/manifests/
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
kubelet이 이 디렉토리를 감시하다가 파일이 있으면 자동으로 Pod를 실행합니다. API Server가 없어도 실행된다는 점이 신기했어요!
2. DaemonSet Pod (9개) - 각 노드마다 1개씩
$ kubectl get daemonset -A
NAMESPACE NAME DESIRED CURRENT READY
calico-system calico-node 3 3 3
calico-system csi-node-driver 3 3 3
kube-system kube-proxy 3 3 3
각 노드마다 정확히 1개씩! 이게 DaemonSet의 핵심입니다.
calico-node: 네트워크 에이전트kube-proxy: 서비스 라우팅csi-node-driver: 스토리지 드라이버3. Deployment Pod (6개) - 복제 가능
$ kubectl get deployment -A
NAMESPACE NAME READY UP-TO-DATE AVAILABLE
calico-system calico-kube-controllers 1/1 1 1
calico-system calico-typha 2/2 2 2
kube-system coredns 2/2 2 2
tigera-operator tigera-operator 1/1 1 1
CoreDNS가 2개인 이유? 고가용성(HA)을 위해서입니다. 하나가 죽어도 다른 하나가 서비스를 계속합니다!
$ kubectl get namespaces
NAME STATUS AGE
default Active 46h <- 기본 네임스페이스
kube-system Active 46h <- Kubernetes 시스템
calico-system Active 46h <- Calico 네트워크
tigera-operator Active 46h <- Calico 운영자
kube-public Active 46h <- 공개 리소스
kube-node-lease Active 46h <- 노드 하트비트
네임스페이스를 "아파트의 각 집"으로 생각하면 이해하기 쉽습니다:
실용 팁:
# 특정 네임스페이스 조회
$ kubectl get pods -n kube-system
# 모든 네임스페이스 조회
$ kubectl get pods -A
# 기본 네임스페이스 변경 (매번 -n 안 써도 됨)
$ kubectl config set-context --current --namespace=kube-system
처음 Pod IP를 봤을 때 혼란스러웠습니다:
$ kubectl get pods -A -o wide
NAMESPACE NAME IP NODE
kube-system coredns-... 10.244.184.81 cpu1 <- Pod IP
kube-system kube-proxy-... 172.30.1.43 cpu1 <- Node IP
왜 IP가 다른가요?
우리 클러스터에는 3가지 네트워크가 있습니다:
1. Node Network: 172.30.1.0/24
- 실제 서버들의 IP
- 예: 172.30.1.43 (cpu1)
2. Pod Network: 10.244.0.0/16
- Calico가 할당하는 Pod 전용 IP
- 예: 10.244.184.81 (coredns)
3. Service Network: 10.96.0.0/12
- 가상 IP (실제로는 존재하지 않음!)
- 예: 10.96.0.10 (CoreDNS Service)
Calico 네트워크 확인:
$ kubectl get ippool
spec:
cidr: 10.244.0.0/16
blockSize: 26
vxlanMode: CrossSubnet
natOutgoing: true
핵심 설정:
vxlanMode: CrossSubnet - 같은 서브넷은 직접, 다른 서브넷은 VXLAN 터널 사용blockSize: 26 - 각 노드에 /26 블록 할당 (62개 IP 사용 가능)가장 신나는 순간이었습니다. DNS가 정말 작동하는지 직접 테스트했어요:
$ kubectl run test-dns --image=busybox:1.28 --rm -i --restart=Never -- sh -c "
nslookup kubernetes.default &&
nslookup google.com
"
결과:
Server: 10.96.0.10
Address 1: 10.96.0.10
Name: kubernetes.default
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local
Name: google.com
Address 1: 142.250.206.238
✅ 성공!
무슨 일이 일어난 걸까요?
1. Pod 생성됨
└─ /etc/resolv.conf에 nameserver 10.96.0.10 자동 설정
2. "kubernetes.default" 조회 요청
└─ CoreDNS (10.96.0.10)가 받음
└─ "kubernetes" Service를 찾아서 10.96.0.1 반환
3. "google.com" 조회 요청
└─ CoreDNS가 받음
└─ 클러스터 내부에 없으니 상위 DNS (168.126.63.1)로 포워딩
└─ Google IP 반환
DNS Search Domain의 마법:
Pod의 /etc/resolv.conf를 보면:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
이 설정 덕분에:
nginx → nginx.default.svc.cluster.local 자동 확장kube-dns.kube-system → kube-dns.kube-system.svc.cluster.local 자동 확장실용 예시:
같은 네임스페이스의 서비스 호출:
curl nginx # ✅ 작동
curl nginx:80 # ✅ 작동
다른 네임스페이스의 서비스 호출:
curl nginx.default # ✅ 작동
curl nginx.default.svc # ✅ 작동
curl nginx.default.svc.cluster.local # ✅ 작동 (전체 FQDN)
Kubernetes는 선언적(Declarative) 시스템이다
모든 것은 API Server를 거친다
네트워크는 3개 레이어로 분리
DNS는 Kubernetes의 핵심
# 1. 전체 리소스 한눈에 보기
kubectl get all -A
# 2. 노드별 Pod 개수 확인
kubectl get pods -A -o wide | awk '{print $8}' | sort | uniq -c
# 3. 특정 Label을 가진 Pod만 조회
kubectl get pods -l app=nginx
# 4. 리소스 상세 정보 (문제 해결에 필수!)
kubectl describe pod <pod-name> -n <namespace>
# 5. 실시간 로그 확인
kubectl logs -f <pod-name> -n <namespace>
Kubernetes, 처음엔 정말 어려웠습니다. 용어도 생소하고, 개념도 복잡하고...
하지만 직접 클러스터를 만들고, 하나씩 확인하면서 점점 이해가 되기 시작했어요. 특히 DNS 테스트가 성공했을 때의 그 기쁨이란!
여러분도 처음엔 막막하실 수 있습니다. 하지만 포기하지 마세요. 하나씩 차근차근 따라가다 보면 어느새 "아, 이래서 Kubernetes를 쓰는구나!"하는 순간이 올 겁니다.