오토에버 클라우드 2기 42일차
Ubuntu 22.04에서 kubeadm으로 쿠버네티스 클러스터 구축
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