Kind(Kubernetes in Docker)를 사용하여 macOS 환경에서 로컬 Kubernetes 클러스터를 생성하는 방법을 설명합니다.
Kind(Kubernetes in Docker)는 Docker 컨테이너를 Kubernetes 노드로 사용하는 도구로, 로컬 개발 및 테스트에 이상적입니다.
이 가이드에서 설정할 클러스터:
# Docker Desktop이 실행 중인지 확인
docker ps
# Kind 설치 (macOS)
brew install kind
# kubectl 설치
brew install kubectl
다음 내용으로 kind-cluster.yaml 파일을 생성합니다:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: myk8s
networking:
# API 서버가 모든 네트워크 인터페이스에서 수신하도록 설정
apiServerAddress: "0.0.0.0"
# 기본 PodCIDR (변경 가능)
# podSubnet: "10.244.0.0/16"
# 기본 ServiceCIDR (변경 가능)
# serviceSubnet: "10.96.0.0/12"
nodes:
# Control Plane 노드 (마스터)
- role: control-plane
# Kubernetes 버전 지정 (이미지 태그로 확인 가능)
image: kindest/node:v1.32.8
# Ingress Controller를 이 노드에 스케줄링하기 위한 레이블
labels:
app: ingress-controller
ingress-ready: "true"
# 컨테이너 포트를 호스트로 매핑
extraPortMappings:
# HTTP/HTTPS 포트 (Ingress Nginx용)
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
# NodePort 범위 (서비스를 외부에 노출할 때 사용)
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002 # ArgoCD용
hostPort: 30002
- containerPort: 30003 # Jenkins용
hostPort: 30003
- containerPort: 30004
hostPort: 30004
- containerPort: 30005
hostPort: 30005
# Worker 노드 1 (Jenkins 워크로드용)
- role: worker
image: kindest/node:v1.32.8
# NodeSelector를 사용하여 Jenkins Pod를 이 노드에만 스케줄링
labels:
app: jenkins
workload-type: ci
# 호스트 경로를 컨테이너 내부로 마운트 (영구 스토리지)
extraMounts:
- hostPath: /private/var/persist/jenkins
containerPath: /private/var/persist/jenkins
readOnly: false
# selinuxRelabel: false # Linux에서만 필요
# propagation: None # 마운트 전파 설정
# Worker 노드 2 (ArgoCD 워크로드용)
- role: worker
image: kindest/node:v1.32.8
labels:
app: argocd
workload-type: cd
extraMounts:
- hostPath: /private/var/persist/argocd
containerPath: /private/var/persist/argocd
readOnly: false
0.0.0.0으로 설정하면 모든 네트워크 인터페이스에서 접근 가능10.244.0.0/16)10.96.0.0/12)labels)노드 레이블은 Pod 스케줄링 시 nodeSelector 또는 affinity로 특정 노드 지정에 사용됩니다:
# Pod에서 노드 선택 예시
spec:
nodeSelector:
app: jenkins # app=jenkins 레이블이 있는 노드에만 스케줄링
extraPortMappings)Kind 컨테이너의 포트를 호스트 머신으로 매핑:
extraMounts)호스트의 디렉토리를 Kind 노드 내부로 마운트하여 영구 스토리지 제공:
# Jenkins용 디렉토리
sudo mkdir -p /private/var/persist/jenkins
sudo chmod 777 /private/var/persist/jenkins
# ArgoCD용 디렉토리
sudo mkdir -p /private/var/persist/argocd
sudo chmod 777 /private/var/persist/argocd
kind create cluster --config kind-cluster.yaml
예상 출력:
Creating cluster "myk8s" ...
✓ Ensuring node image (kindest/node:v1.32.8) 🖼
✓ Preparing nodes 📦 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-myk8s"
You can now use your cluster with:
kubectl cluster-info --context kind-myk8s
# 컨텍스트 확인
kubectl config current-context
# 출력: kind-myk8s
# 노드 확인
kubectl get nodes
예상 출력:
NAME STATUS ROLES AGE VERSION
myk8s-control-plane Ready control-plane 2m v1.32.8
myk8s-worker Ready <none> 2m v1.32.8
myk8s-worker2 Ready <none> 2m v1.32.8
# 모든 레이블 표시
kubectl get nodes --show-labels
# 특정 레이블만 확인
kubectl get nodes -L app,workload-type
예상 출력:
NAME STATUS ROLES AGE VERSION APP WORKLOAD-TYPE
myk8s-control-plane Ready control-plane 2m v1.32.8 ingress-controller
myk8s-worker Ready <none> 2m v1.32.8 jenkins ci
myk8s-worker2 Ready <none> 2m v1.32.8 argocd cd
Kind는 기본적으로 standard StorageClass를 제공합니다:
# StorageClass 목록 확인
kubectl get storageclass
예상 출력:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 2m
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard # Kind의 기본 StorageClass
resources:
requests:
storage: 1Gi
# API 서버 주소 및 CoreDNS 확인
kubectl cluster-info
# 모든 네임스페이스의 Pod 확인
kubectl get pods --all-namespaces
# 시스템 Pod 상태 확인 (CoreDNS, kube-proxy 등)
kubectl get pods -n kube-system
# Kind 노드들이 Docker 컨테이너로 실행 중인지 확인
docker ps --filter "name=myk8s"
예상 출력:
CONTAINER ID IMAGE NAMES
xxxxxxxxxxxx kindest/node:v1.32.8 myk8s-control-plane
xxxxxxxxxxxx kindest/node:v1.32.8 myk8s-worker
xxxxxxxxxxxx kindest/node:v1.32.8 myk8s-worker2
| 포트 | 서비스 | 설명 | 접속 방법 | 볼륨 마운트 |
|---|---|---|---|---|
| 80 | Ingress | HTTP | http://localhost | - |
| 443 | Ingress | HTTPS | https://localhost | - |
| 30000 | (예약) | 사용자 정의 서비스 | http://localhost:30000 | - |
| 30001 | (예약) | 사용자 정의 서비스 | http://localhost:30001 | - |
| 30002 | ArgoCD | ArgoCD UI/API | http://localhost:30002 | /private/var/persist/argocd |
| 30003 | Jenkins | Jenkins UI/API | http://localhost:30003 | /private/var/persist/jenkins |
| 30004 | (예약) | 사용자 정의 서비스 | http://localhost:30004 | - |
| 30005 | (예약) | 사용자 정의 서비스 | http://localhost:30005 | - |
NodePort를 사용하여 서비스를 외부에 노출하는 예:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80 # 클러스터 내부 포트
targetPort: 8080 # Pod 포트
nodePort: 30000 # 외부 노출 포트 (30000-32767 범위)
이렇게 설정하면 http://localhost:30000으로 서비스에 접근할 수 있습니다.
kind get clusters
kind delete cluster --name myk8s
kind delete cluster --name myk8s
kind create cluster --config kind-cluster.yaml
docker ps | grep myk8s
docker exec -it myk8s-control-plane bash
증상:
ERROR: failed to create cluster: failed to ensure docker network: ... address already in use
해결 방법:
# 포트 사용 확인 (macOS)
lsof -i :80
lsof -i :443
lsof -i :30000-30005
# 프로세스 종료 (필요한 경우)
kill -9 <PID>
# 또는 Docker Desktop 재시작
# Docker Desktop 앱 → Restart
증상:
Jenkins나 ArgoCD Pod가 CrashLoopBackOff 상태
해결 방법:
# 권한 확인
ls -la /private/var/persist/
# 권한 재설정
sudo chmod -R 777 /private/var/persist/jenkins
sudo chmod -R 777 /private/var/persist/argocd
# 소유자 확인 및 변경 (필요시)
sudo chown -R $(whoami) /private/var/persist/jenkins
증상: 클러스터 생성 중 멈추거나 실패
해결 방법:
# 1. 기존 클러스터 완전 삭제
kind delete cluster --name myk8s
# 2. Docker 네트워크 정리
docker network prune -f
# 3. Docker 컨테이너 정리
docker container prune -f
# 4. (선택) Docker 이미지 정리 (조심!)
docker image prune -a -f
# 5. Docker Desktop 재시작
# 6. 재생성
kind create cluster --config kind-cluster.yaml
증상:
NAME STATUS ROLES AGE VERSION
myk8s-worker NotReady <none> 2m v1.32.8
해결 방법:
# 노드 상태 상세 확인
kubectl describe node myk8s-worker
# CNI Pod 상태 확인
kubectl get pods -n kube-system | grep kindnet
# 문제가 있는 Pod 로그 확인
kubectl logs -n kube-system <pod-name>
# CNI 재시작
kubectl delete pod -n kube-system -l app=kindnet
증상: Pod가 Pending 상태로 남아있거나 Evicted 됨
해결 방법:
# Docker Desktop 설정에서 메모리 증가
# Docker Desktop → Settings → Resources → Memory (8GB 이상 권장)
# 노드 리소스 확인
kubectl top nodes # metrics-server 설치 필요
# Pod 리소스 사용량 확인
kubectl top pods --all-namespaces
증상:
The connection to the server localhost:8080 was refused
해결 방법:
# kubeconfig 파일 확인
echo $KUBECONFIG
# 기본 kubeconfig로 설정
export KUBECONFIG=~/.kube/config
# Kind 컨텍스트로 전환
kubectl config use-context kind-myk8s
# 컨텍스트 목록 확인
kubectl config get-contexts
증상:
ImagePullBackOff 또는 ErrImagePull
해결 방법:
# Pod 상세 정보 확인
kubectl describe pod <pod-name>
# Docker Hub 레이트 리미트 확인
# Docker 로그인 후 재시도
docker login
# Kind 클러스터에 직접 이미지 로드
docker pull <image-name>
kind load docker-image <image-name> --name myk8s
## 🚀 고급 설정
### 리소스 제한 설정
각 노드에 CPU와 메모리 제한을 설정할 수 있습니다:
```yaml
nodes:
- role: control-plane
# ...기존 설정...
# 이 기능은 아직 실험적 상태이며 cgroup v2가 필요할 수 있음
# kubeadmConfigPatches:
# - |
# kind: InitConfiguration
# nodeRegistration:
# kubeletExtraArgs:
# system-reserved: memory=1Gi,cpu=500m
# kube-reserved: memory=1Gi,cpu=500m
Kind는 기본적으로 containerd를 사용하지만, 설정을 커스터마이징할 수 있습니다:
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5000"]
endpoint = ["http://localhost:5000"]
Private container registry를 사용하려면:
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.configs."myregistry.com".auth]
username = "myuser"
password = "mypassword"
또는 더 안전하게 Docker config를 사용:
# Docker 로그인
docker login myregistry.com
# Kind 클러스터 생성 시 Docker config 사용
kind create cluster --config kind-cluster.yaml
여러 Kind 클러스터를 동시에 실행할 수 있습니다:
# 첫 번째 클러스터 (dev)
cat << EOF > kind-dev.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: dev-cluster
networking:
apiServerAddress: "0.0.0.0"
apiServerPort: 6443
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 8080
EOF
kind create cluster --config kind-dev.yaml
# 두 번째 클러스터 (staging)
cat << EOF > kind-staging.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: staging-cluster
networking:
apiServerAddress: "0.0.0.0"
apiServerPort: 6444 # 다른 포트
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 9080 # 다른 포트
EOF
kind create cluster --config kind-staging.yaml
# 컨텍스트 전환
kubectl config use-context kind-dev-cluster
kubectl config use-context kind-staging-cluster
Kubernetes의 실험적 기능을 활성화하려면:
featureGates:
"EphemeralContainers": true
"PodSecurity": true
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
"feature-gates": "EphemeralContainers=true,PodSecurity=true"
controller-manager:
extraArgs:
"feature-gates": "EphemeralContainers=true,PodSecurity=true"
scheduler:
extraArgs:
"feature-gates": "EphemeralContainers=true,PodSecurity=true"
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
"feature-gates": "EphemeralContainers=true,PodSecurity=true"
로컬에서 빌드한 이미지를 Kind 클러스터에 로드:
# 이미지 빌드
docker build -t myapp:latest .
# Kind 클러스터에 로드
kind load docker-image myapp:latest --name myk8s
# 로드된 이미지 확인
docker exec -it myk8s-control-plane crictl images | grep myapp
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /private/var/persist/jenkins
type: DirectoryOrCreate
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: app
operator: In
values:
- jenkins
클러스터가 정상적으로 생성되었다면: