오토에버 클라우드 2기 42일차
Ubuntu 22.04에서 kubeadm으로 쿠버네티스 클러스터 구축
Ubuntu 22.04 서버 3대에 Docker와 Kubernetes를 설치하고, 클러스터 내부 노드 3개를 만든다 마스터노드 1개 워커노드 2개
환경 구성
- 마스터 노드 (Control-Plane):
192.168.56.10 - 워커 노드 1:
192.168.56.11 - 워커 노드 2:
192.168.56.12
구조
graph TD;
subgraph Kubernetes Cluster
subgraph "Control-Plane Node (Master)"
M[master<br>192.168.56.10]
end
subgraph "Worker Nodes"
W1[worker1<br>192.168.56.11]
W2[worker2<br>192.168.56.12]
end
M -- "kubeadm join" --> W1;
M -- "kubeadm join" --> W2;
end
style M fill:#f9f,stroke:#333,stroke-width:2px
style W1 fill:#bbf,stroke:#333,stroke-width:2px
style W2 fill:#bbf,stroke:#333,stroke-width:2px
1. [모든 노드] Docker 및 Kubernetes 패키지 설치
시스템 전체 업그레이드 및 필수 도구 설치
1
2
3
sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
Docker 공식 GPG 키 추가 및 저장소 설정
1
2
3
4
5
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Docker 엔진 설치
1
2
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Kubernetes GPG 키 추가 및 저장소 설정
1
2
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
kubeadm, kubelet, kubectl 설치 및 버전 고정
1
2
3
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
이유: apt-get upgrade 시 쿠버네티스 버전이 자동으로 변경되면 클러스터가 불안정해질 수 있다 이를 방지하기 위해 버전을 고정
2. [모든 노드] 클러스터 구성을 위한 시스템 공통 설정
Swap 비활성화 및 커널 설정
- 이유: 쿠버네티스는 자체적으로 메모리를 관리하며, 컨테이너 간 네트워크 트래픽을 올바르게 처리하기 위해 특정 커널 설정이 필요 이 설정이 없으면
kubeadm init시 오류가 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Swap 비활성화
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 커널 모듈 로드
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# sysctl 파라미터 설정
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
컨테이너 런타임(containerd) 설정
1
2
3
4
sudo rm -f /etc/containerd/config.toml
sudo systemctl restart containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
이유: containerd의 기본 설정을 쿠버네티스와 호환되도록 수정하여 [ERROR CRI] 오류를 사전에 방지
sudo kubeadm init –control-plane-endpoint=”192.168.56.10” –pod-network-cidr=192.168.0.0/16
I0701 01:43:14.782688 2003 version.go:256] remote version is much newer: v1.33.2; falling back to: stable-1.29
[init] Using Kubernetes version: v1.29.15
[preflight] Running pre-flight checks
error execution phase preflight: [preflight] Some fatal errors occurred:
[ERROR CRI]: container runtime is not running: output: time=”2025-07-01T01:43:15Z” level=fatal msg=”validate service connection: validate CRI v1 runtime API for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService”
, error: exit status 1
[preflight] If you know what you are doing, you can make a check non-fatal with
--ignore-preflight-errors=...To see the stack trace of this error execute with –v=5 or higher
containerd의 설정 파일이 쿠버네티스와 호환되도록 구성되지 않았기 때문
서비스 재시작
1
2
sudo systemctl restart containerd
sudo systemctl restart kubelet
이유: 위에서 변경한 모든 설정을 시스템에 최종적으로 적용하기 위해 관련 서비스를 재시작 모든 노드에서 아래 명령어를 실행
3. [마스터 노드] 클러스터 초기화
kubeadm 설정 파일 생성
이유: 각 노드가 여러 IP를 가진 환경에서는, API 서버와 kubelet이 어떤 IP로 통신해야 할지 명확하게 알려주어야 한다 설정 파일을 사용하면 이 모든 정보를 정확하게 지정할 수 있다
마스터 노드에서
kubeadm-config.yaml파일을 생성1
vi kubeadm-config.yaml
아래 내용을 그대로 복사하여 붙여넣기
1 2 3 4 5 6 7 8 9 10 11 12 13 14
apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration nodeRegistration: kubeletExtraArgs: node-ip: "192.168.56.10" localAPIEndpoint: advertiseAddress: "192.168.56.10" bindPort: 6443 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration controlPlaneEndpoint: "192.168.56.10:6443" networking: podSubnet: "192.168.0.0/16"
클러스터 초기화 및 설정
(권장) 이전 시도 초기화:
sudo kubeadm reset -f설정 파일을 사용하여 클러스터 초기화:
1
sudo kubeadm init --config kubeadm-config.yaml --upload-certs
kubectl환경 설정:init성공 후 화면 안내에 따라 아래 3개 명령어를 실행합니다.1 2 3
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
CNI(네트워크 플러그인) 설치:
이유: Pod들이 서로 통신할 수 있는 가상 네트워크를 만들어준다. 이것이 없으면 워커 노드는 NotReady상태로 남는다
1
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests
4. [워커 노드] 클러스터 참여
이제 워커 노드(192.168.56.11, 192.168.56.12) 각각에 접속하여 클러스터에 참여시킴
kubeadm 조인 설정 파일 생성
- 이유:
join시에도kubelet이 사용할 IP를 명확히 지정해주어야 한다
각 워커 노드에서
kubeadm-join-config.yaml파일을 생성아래 내용을 붙여넣되,
node-ip를 현재 접속한 워커의 IP로 반드시 수정1 2 3 4 5 6 7 8 9 10 11 12 13 14
apiVersion: kubeadm.k8s.io/v1beta3 kind: JoinConfiguration discovery: bootstrapToken: # 2단계에서 얻은 토큰과 해시값으로 수정하세요. apiServerEndpoint: "192.168.56.10:6443" token: "<your-token>" caCertHashes: - "<your-ca-cert-hash>" nodeRegistration: kubeletExtraArgs: # worker1 에서는 "192.168.56.11" # worker2 에서는 "192.168.56.12" node-ip: "<current-worker-ip>"
클러스터에 조인
(권장) 이전 시도 초기화:
sudo kubeadm reset -f설정 파일을 사용하여 조인:
1
sudo kubeadm join --config kubeadm-join-config.yaml
위 방법 처럼 ip주소를 명시하지 않는다면 서로 통신을 할 수 없어서 워커 노드들의 상태가 notReady상태임
Unable to create token for CNI kubeconfig error=Post "https://10.96.0.1:443/api/v1/.../token": dial tcp 10.96.0.1:443: connect: connection refused
결론적으로, 클러스터 내부의 네트워크 라우팅에 문제가 생겨 워커 노드의 Pod가 마스터 노드의 API 서버를 찾지 못하고 있는 상황이 발생했지만 위 방법처럼 주소를 명시해주니 해결
5. [마스터 노드] 클러스터 최종 확인
노드 상태 및 IP 확인:
1
kubectl get nodes -o wide
기대 결과: 모든 노드의 STATUS가 Ready로, INTERNAL-IP가 지정한 고유 IP로 표시되어야 한다

전체 시스템 Pod 확인:
1
kubectl get pods --all-namespaces
kube-system과 calico-system 네임스페이스의 모든 Pod들이 Running상태로 보인다면, 쿠버네티스 클러스터가 성공적으로 구축된 것

Kubernetes 고급 활용법 (kubectl)
1. 리소스 상태 체크와 대기 (kubectl wait)
스크립트 등에서 리소스가 특정 상태가 될 때까지 기다려야 할 때 유용한 명령어임
sample-pod가Ready상태가 될 때까지 대기1
kubectl wait --for=condition=Ready pod/sample-pod
모든 Pod 즉시 삭제 (완료를 기다리지 않음)
1
kubectl delete pod --all --wait=false
2. 매니페스트(Manifest) 파일 관리
하나의 파일에 여러 리소스 정의
---구분자를 사용하여 하나의 YAML 파일에 여러 리소스를 함께 정의할 수 있음. 연관성이 높은 리소스(Deployment와 Service 등)를 함께 관리하기에 편리함1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# deployment-and-service.yaml apiVersion: apps/v1 kind: Deployment metadata: name: order1-deployment spec: replicas: 3 # ... (생략) ... --- apiVersion: v1 kind: Service metadata: name: order2-service spec: type: LoadBalancer # ... (생략) ...
여러 매니페스트 파일을 디렉토리로 동시 적용 특정 디렉토리 안의 모든 YAML 파일을 한 번에 적용할 수 있음. 파일명 순서대로 적용됨
1 2 3 4 5
# ./dir 디렉토리 내의 모든 yaml 파일 적용 kubectl apply -f ./dir # 하위 디렉토리까지 모두 재귀적으로 적용 kubectl apply -f ./dir -R
3. 어노테이션(Annotation)과 레이블(Label)
두 메타데이터 모두 리소스를 설명하지만, 용도가 다름
어노테이션: 주로 시스템 구성 요소나 도구가 사용하는 메타데이터. 사람을 위한 메모나 설명, 도구의 설정값 등을 저장.
레이블: 리소스를 선택(Selector)하거나 그룹화하기 위한 메타데이터. Deployment가 어떤 Pod를 관리할지 결정하거나, Service가 어떤 Pod로 트래픽을 보낼지 결정할 때 사용됨
레이블을 이용한 리소스 필터링
1 2
# label1=val1 레이블을 가진 모든 파드 조회 kubectl get pods -l label1=val1
리소스에 레이블 추가/변경
1 2 3 4 5 6 7 8
# sample-pod에 label3=val3 레이블 추가 kubectl label pods sample-pod label3=val3 # 기존 레이블 값 덮어쓰기 kubectl label pods sample-pod label3=val3_new --overwrite # 레이블 삭제 kubectl label pods sample-pod label3-
4. 리소스 정리 (kubectl apply --prune)
GitOps 등에서 매니페스트 파일 관리를 자동화할 때, Git 저장소에서 삭제된 YAML 파일에 해당하는 리소스를 클러스터에서도 자동으로 삭제하는 기능임
문제 상황: 디렉토리에서
sample-pod2.yaml파일을 삭제하고 다시kubectl apply -f ./prune을 실행해도sample-pod2는 삭제되지 않음해결:
--prune옵션과 특정 레이블 셀렉터(-l)를 함께 사용하면,apply명령어는 지정된 레이블을 가진 리소스 중 현재 디렉토리에 없는 리소스를 자동으로 삭제해 줌1 2 3
# prune 디렉토리의 파일들을 적용하되, system=a 레이블을 가진 리소스 중 # 현재 디렉토리에 없는 것은 삭제함 kubectl apply -f ./prune --prune -l system=a
5. 리소스 조작 및 확인
리소스 직접 편집:
kubectl edit deployment my-deployment리소스 일부 정보 업데이트:
kubectl set image pod/sample-pod nginx-container=nginx:1.17파일과 실제 리소스 차이 비교:
kubectl diff -f my-pod.yaml사용 가능한 리소스 종류 확인:
kubectl api-resources리소스 상세 정보 확인:
kubectl describe pod my-pod리소스 사용량 확인 (metrics-server 애드온 필요)
- 노드 사용량:
kubectl top node - 파드 사용량:
kubectl top pod -n kube-system
- 노드 사용량:
컨테이너에서 명령어 수행
1 2 3 4 5
# sample-pod에서 /bin/ls 명령어 실행 kubectl exec -it sample-pod -- /bin/ls # sample-pod의 셸에 접속 kubectl exec -it sample-pod -- /bin/bash
로컬 포트 포워딩: 로컬 PC의 포트를 파드의 포트로 전달하여 임시로 접근
1
kubectl port-forward pod/sample-pod 8080:80
로그 확인:
-f옵션으로 실시간 로그 확인 가능1
kubectl logs -f sample-pod