3개의 노드를 VM으로 띄워서 쿠버네티스 클러스터를 직접 구축하고 클러스터에 Pod를 생성하는 실습까지 해보기
전체적인 그림
쿠버네티스 구조
Master Node (= Control Plane)
•
API Server
◦
역할: 주요 통신 허브로, 클라이언트와 내부 컴포넌트 간의 API 요청을 처리한다.
◦
기능: 인증, 권한 부여, API 호출 및 클러스터 상태를 etcd에 저장한다.
•
Scheduler
◦
역할: 클러스터 내에서 새롭게 생성된 Pod를 적절한 Node에 할당한다.
◦
기능:
▪
리소스 요구 사항 분석 (CPU, RAM 등)
▪
정책 기반 노드 선택 (Ex: 장애 여부, 태그 등)
•
etcd
◦
역할: 분산형 Key-Value 저장소로, 클러스터의 모든 상태 데이터를 저장한다.
◦
기능: 클러스터 상태의 Source of Truth 역할을 한다.
•
Controller Manager
◦
역할: 다양한 컨트롤러(ReplicationController, NodeController 등)를 실행하여 클러스터 상태를 원하는 상태(Desired State)로 유지한다.
◦
기능:
▪
장애 복구 (Ex: 노드 다운 시, 재배치)
▪
Pod와 Service 상태 관리
▪
ReplicaSet 크기 조정 (= Pod 수 조정)
Worker Node
•
kubelet
◦
역할: API Server와 통신하며 Worker Node에서 발생하는 모든 활동을 제어한다. Pod와 컨테이너를 관리한다.
◦
기능:
▪
Pod 매니페스트를 받아 컨테이너 런타임을 통해 컨테이너 실행
▪
노드 상태 보고 및 컨테이너 재시작
•
kube-proxy
◦
역할: 네트워킹 구성 요소로, Service 간 트래픽을 라우팅한다.
◦
기능:
▪
클러스터 내 Pod 간 통신 지원
▪
클러스터 외부에서의 서비스 접근을 위한 로드 밸런싱
▪
iptables를 사용하여 네트워크 규칙을 구성
Ex) Pod 생성 과정
테스트 환경
•
Infra : On-Premise(VMWare)
•
Network : Local(192.168.0.0/24)
•
OS : Ubuntu 22.04 LTS 3대
◦
Master Node 1대, Worker Node 2대
•
Compute Resource :
◦
CPU : 2Core
◦
RAM : 4GB
클러스터 구축
1. 노드 세팅
1) VMWare로 3개의 노드 세팅
•
VMWare로 Ubuntu 환경의 노드를 3개 띄운다.
◦
1개는 Master Node, 2개는 Worker Node
2) netplan 설정
️
️
️
•
각 노드마다 netplan 디렉토리 안에 있는 YAML 파일에 들어간다. (모든 노드 다 적용해야 됨)
nano /etc/netplan/<~.yaml>
Bash
복사
•
그리고 아래와 같이 수정한다:
network:
ethernets:
ens160:
dhcp4: no
addresses: [<노드의 IP 주소>/24]
gateway4: <노드와 연결된 게이트웨이의 IP 주소>
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
version: 2
YAML
복사
◦
저 내용을 복붙하지 말고 각자 조금씩 다를 수 있으니(Ex: ens160이 아닐 수도?) 하나씩 확인하면서 수정하자.
◦
이걸 안 하면, API Server가 계속해서 죽는 경험을 할 수 있을 것이다. (내 경험은 그랬음)
◦
Gateway IP 확인 방법: ip route
⇒ default via <게이트웨이 IP 주소> dev eth0
•
수정된 Netplan 적용하기
sudo netplan apply
Bash
복사
3) SSH 설정
a.
SSH 서버 설치 및 활성화
sudo apt update
sudo apt install openssh-server
sudo systemctl enable ssh
sudo systemctl start ssh
Bash
복사
b.
노드의 IP 주소 확인
•
ip addr show
IP Address | |
Master Node | 192.168.74.129 |
Worker Node1 | 192.168.74.130 |
Worker Node2 | 192.168.74.131 |
c.
SSH 키 인증
•
ssh-keygen
: 로컬에서 SSH 키 생성
Tip) 명령어를 쳤을 때, 묻는 것들은 모두 Enter를 쳐서 넘어가면, 비밀번호 설정 없이 생성된다.
•
ssh-copy-id <user>@<node-ip>
: 각 노드에서 공개 키 복사
Tip) yes → Enter
•
비밀번호 없이 SSH 접속 가능
Hostname 변경 방법:
$ sudo hostnamectl set-hostname 새로운_서버_이름
$ sudo systemctl restart systemd-hostnamed
$ hostname
구별하기 편하기 위해서 k8s-master, k8s-worker1, k8s-worker2로 수정했다.
4) iTerm2로 노드 연결
a.
3개의 탭을 띄우기
b.
ssh <user>@<node-ip>
: 각 탭에서 SSH 명령으로 노드 접속
2. Docker 설치
sudo su
: 지금부터 모든 설치 과정은 편의를 위해 root 계정으로 진행할 것을 권장한다.
•
컨테이너를 실행시킬 런타임 환경이 필요하다.
◦
보통 Docker를 많이 쓰는데, 요즘에는 Docker에서 Runtime 환경으로 쓰는 ContainerD만 따로 떼어서 사용한다고 한다.
◦
우선, 여기서는 Docker를 설치하도록 하겠다.
•
Master Node도 kubeadm init 할 때, 컨테이너 런타임이 필요하므로 Master Node와 Worker Node 둘다 Docker 및 containerd를 설치한다.
1) Docker의 apt 레포지토리 세팅
a.
Docker의 공식적인 GPG Key 추가
apt-get update
apt-get install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
Bash
복사
b.
apt 리소스에 레포지토리 추가하기
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
Bash
복사
2) Docker 패키지 설치
apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Bash
복사
a.
설치 확인
systemctl enable docker
systemctl start docker
docker --version
# containerd 설치 확인
systemctl status containerd
Bash
복사
b.
containerd 추가 설정
cgroup 설정:
cgroup은 프로세스를 그룹화하고 그룹 단위로 시스템 리소스를 제한하거나 할당할 수 있는 기능을 제공하는데, 쿠버네티스에서는 Pod 단위로 cgroup을 생성하여 각 Pod에 리소스를 제한한다.
cgroup은 cgroupfs 방식과 systemd 방식으로 나뉘는데, 쿠버네티스의 kubelet이 systemd 방식을 사용하므로 containerd 역시 systemd를 CGroup 드라이버로 사용하는 것을 권장한다.
Pause 컨테이너 버전 일치:
Pause 컨테이너는 쿠버네티스에서 Pod의 네트워크 네임스페이스를 유지하기 위해 사용되는 가벼 운 컨테이너이다. 모든 Pod는 Pause 컨테이너를 먼저 생성하여, Pod 내의 다른 컨테이너는 이 Pause 컨테이너를 통해 네트워크 및 IP를 공유한다.
•
nano 패키지 설치 & Pause 이미지 수동 다운로드
apt update
apt install nano
Bash
복사
•
containerd의 config 수정
️
️
️
: 계속 API Server 죽고 Worker Node의 flannel이 안 붙는 것 해결
◦
Containerd의 기본 설정 파일 생성 및 저장 작업 수행
containerd config default > /etc/containerd/config.toml
Bash
복사
관련 내용 정리
◦
config.toml 파일 확인
nano /etc/containerd/config.toml
Bash
복사
◦
SystemdCgroup = true로 변경
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
Bash
복사
◦
nano 수정된 내용 저장 및 나가기
Ctrl + X
'Y'
Bash
복사
•
containerd 재시작
systemctl restart containerd
systemctl status containerd
Bash
복사
3. K8s 설치
•
쿠버네티스 클러스터의 모든 노드들은 kubeadm, kubectl, kubelet이 있어야 된다.
•
그러기 위해서는 약간의 환경설정이 먼저 필요하다.
1) 설치 전, 환경설정
a.
swap 메모리 비활성화
# swap 임시 비활성화
swapoff -a
# swap 영구 비활성화
sed -i '/swap/s/^/#/' /etc/fstab
# swap 메모리 상태 확인
swapon -s
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
swapon -s
Bash
복사
•
Swap은 임시 비활성화를 하고 영구 활성화를 해야 비활성화가 된다. (왜 그런지는 찾아봐야할 듯…)
•
swap -s를 쳤을 때, 출력값이 없으면 비활성화 상태이다.
b.
방화벽 비활성
# 방화벽 비활성화
apt-get install -y firewalld
systemctl stop firewalld
systemctl disable firewalld
# 열린 포트 확인
apt update
apt install net-tools
firewall-cmd --list-all
netstat -tlnp
Bash
복사
•
FirewallD is not running 이라고 뜨면, 방화벽이 잘 비활성화된 것이다.
c.
네트워크 설정
이 설정이 없으면 CNI(Container Network Interface)가 제대로 작동하지 않을 수 있다.
# /etc/modules-load.d/k8s.conf 파일 생성
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF
Bash
복사
•
이 명령은 br_netfilter 커널 모듈을 로드하도록 설정하는 파일을 생성한다:
◦
br_netfilter
▪
브릿지 네트워크 필터링을 활성화하는 커널 모듈
▪
쿠버네티스 클러스터에서 Pod와 Service 간의 트래픽을 관리하기 위해 브릿지 네트워크 인터페이스를 통해 트래픽을 필터링할 수 있어야 된다.
◦
/etc/modules-load.d/k8s.conf
▪
부팅 시, 자동으로 br_netfilter 모듈을 로드하도록 지정하는 설정 파일
# /etc/sysctl.d/k8s.conf 파일 생성
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# 시스템 재시작 없이 stysctl 파라미터 반영
sysctl --system
Bash
복사
•
이 명령은 네트워크 트래픽 처리와 관련된 sysctl 설정을 추가하는 파일을 생성한다:
◦
net.bridge.bridge-nf-call-ip6tables
▪
브릿지 네트워크를 통해 IPv6 트래픽이 ip6tables를 거쳐 필터링되도록 설정한다.
◦
sudo sysctl --system
▪
위에서 생성한 /etc/sysctl.d/k8s.conf 파일의 설정을 즉시 적용한다.
▪
시스템을 재부팅하지 않고도 새 설정이 반영된다.
2) kubeadm, kubectl, kubelet 설치
•
kubeadm : 클러스터를 쉽게 설정하고 초기화할 수 있도록 도와주는 도구
◦
kubeadm init : Control Plane을 초기화하고 클러스터를 부트스트랩시킴
◦
kubeadm join : Worker Node를 클러스터에 연결
•
kubectl : 클러스터와 통신하기 위한 CLI(명령줄 인터페이스) 도구
•
kubelet : 클러스터 내의 모든 노드에서 실행되는 에이전트
a.
apt 패키지 인덱스를 업데이트 & apt 레포지토리를 사용하는 데 필요한 패키지 설치
# apt-transport-https may be a dummy package; if so, you can skip that package
apt-get update
apt-get install -y apt-transport-https ca-certificates curl gpg
Bash
복사
b.
쿠버네티스 패키지 레포지토리에 대한 Public 서명 키를 다운로드
# If the directory `/etc/apt/keyrings` does not exist, it should be created before the curl command, read the note below.
# sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
Bash
복사
Note:
만약 Debian 12와 Ubuntu 22.04보다 버전이 낮다면, /etc/apt/keyrings 디렉토리는 기본값으로 존재하지 않아서 curl 명령을 치기 전에 저 디렉토리를 만들어야 된다.
c.
적절한 쿠버네티스 apt 레포지토리 추가
# This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
Bash
복사
d.
apt 패키지 인덱스를 업데이트 & kubelet, kubeadm 그리고 kubectl 설치 & 버전 고정
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
Bash
복사
e.
kubeadm을 실행하기 전, kubelet 서비스 활성화
systemctl enable --now kubelet
Bash
복사
3) control-plane 구성
Master Node에서만 실행하면 된다.
a.
kubeadm init 실행
kubeadm init \
--apiserver-advertise-address=<Master Node's IP> \
--pod-network-cidr=10.244.0.0/16
Bash
복사
•
--apiserver-advertise-address
: API Server가 어떤 IP 주소에서 요청을 받을지 지정
◦
특정 IP에서만 요청을 받도록 제한할 수 있다.
◦
이 IP 주소에 Master Node의 IP 주소를 적으면 된다.
•
--pod-network-cidr
: 클러스터에서 Pod 네트워크의 CIDR 범위를 정의
◦
네트워크 플러그인(CNI)과 호환되는 Pod 네트워크 범위를 설정해야 된다.
◦
Master Node의 IP 주소와 Pod 네트워크의 CIDR 범위는 반드시 달라야 된다.
▪
당연한 얘기일 수 있는데, Node가 쓰는 네트워크 범위와 Pod가 쓰는 네트워크 범위가 겹치면 통신이 원활히 이뤄질 수 없다.
◦
10.244.0.0/16 : CNI로 Flannel을 쓴다면, 이 IP 주소를 지정해야 된다.
CIDR(Classless Inter-Domain Routing; 사이더):
Class로 네트워크를 구분하는 것보다 더 유연하게 IP 주소를 여러 네트워크 영역으로 나눌 수 있는 라우팅 기법이다.
초기화를 성공하면, 이렇게 뜰 것이다. 항상 kubeadm init 에서 오래 걸리네…
•
kubeadm join 명령어 저장
Worker Node를 클러스터에 붙일 때, kubeadm join ~ 명령어를 써야 된다.
◦
cat > token.txt : 텍스트 파일을 열고 위의 내용을 붙여넣음
◦
ctrl + d : 저장 후, 나가기
◦
cat token.txt : 잘 저장되었는지 확인
b.
권한 설정
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Bash
복사
•
kubectl 명령어를 쓰도록 권한을 설정하는 작업이 필요하다.
c.
CNI 설치
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
Bash
복사
이런 에러가 뜰 수 있는데, Control Plane 컴포넌트들(특히, API Server)가 아직 덜 붙어서 생기는 에러일 수 있으니 조금 기다렸다가 다시 설치하면, 되더라…
<자꾸 CNI(flannel)이 CrashLoopBackOff가 뜬다면?>
sudo modprobe br_netfilter
lsmod | grep br_netfilter
sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
sudo sysctl --system
sysctl net.bridge.bridge-nf-call-iptables
Shell
복사
위와 같은 명령어를 Worker 노드에 적어준다.
위의 명령어들은 커널 모듈과 sysctl(시스템 네트워크 설정)을 조정해서 K8s에서 Pod 네트워크를 정상적으로 연결하기 위한 필수 조건을 만족시키는 과정이다:
1.
sudo modprobe br_netfilter
커널 모듈 중 하나인 br_netfilter를 로드한다.
이 모듈은 브리지(Bridge)로 패킷이 전달될 때, iptables에서 이를 제어할 수 있도록 만들어주는데, K8s CNI를 사용하기 위해서는 이 설정이 필요하다.
2.
lsmod | grep br_netfilter
모듈이 제대로 로드되었는지 확인하기 위해 현재 로드된 커널 모듈 목록을 필터링해 보여준다.
3.
sudo tee /etc/sysctl.d/k8s.conf <<EOF ... EOF
/etc/sysctl.d/k8s.conf 파일에 아래 세 가지 설정 값을 저장하는 단계:
•
net.bridge.bridge-nf-call-iptables=1:
브리지로 전달되는 IPv4 패킷을 iptables에서 필터링할 수 있도록 설정
•
net.bridge.bridge-nf-call-ip6tables=1:
브리지로 전달되는 IPv6 패킷을 ip6tables에서 필터링할 수 있도록 설정
•
net.ipv4.ip_forward=1:
IPv4 패킷을 라우팅할 수 있도록 허용(즉, 패킷 포워딩 활성화)
4.
sudo sysctl --system
방금 작성한 k8s.conf 등 sysctl 관련 설정을 시스템에 바로 적용
5.
sysctl net.bridge.bridge-nf-call-iptables
설정이 제대로 적용되었는지 확인하기 위해, bridge-nf-call-iptables의 현재 값을 출력
d.
컴포넌트들 확인
kubectl get pod --all-namespaces
kubectl get nodes
Bash
복사
•
자꾸 아래와 같은 에러가 뜬다?
그러면, 다시 kubeadm reset 하고 kubeadm init 하기
4) Worker Node 구성
a.
kubectl join
•
cat token.txt 에 저장한 명령어 복사해서 각 Worker Node에 적용하기
•
만약 Worker Node에서 kubeadm join 을 사용했던 적이 있는데, kubeadm join을 또 사용하려면 kubeadm reset을 꼭 해주자
b.
완료 
이렇게 세 개의 Node가 모두 Ready면, 클러스터 구축 완료
여전히 API Server가 자꾸 죽음… 리소스의 문제이려나?
그런데 Worker Node를 붙이면(join), 그 이후에는 잘 안 죽는 것 같던데 이건 연구의 대상이다.
⇒ netplan 설정과 containerd의 config를 수정하는 것 덕분에 해결 완료 


Pod 생성 실습
여러 방법을 Pod 만들기
선언형으로 Pod 만들기
kubectl run webserver --image=nginx:1.14 --port 80
kubectl describe pod webserver
kubectl get pods
kubectl get pods -o wide
Bash
복사
•
Web Server는 프로그램이 실행되면서 80번 포트를 열거 클라리언트 connection이 들어오기를 listen(대기)하는 프로그램
curl 10.244.2.4
Bash
복사
그 짧은 시간 안에 Pod가 여러 번 죽었다 살아나서 IP가 10.244.2.7
•
curl : Command Line의 웹 브라우저가 있음. (명령어 형식으로 쓸 수 있는 크롬)
apt-get update
apt-get install elinks
elinks <Pod's IP>
Bash
복사
•
elinks : 유닉스 기반 운영 체제를 위한 텍스트 기반 콘솔 웹 브라우저
Deployment & Replica 3개 배포
kubectl create deployment mainui --image=httpd --replicas=3
kubectl describe deployments.apps mainui
kubectl get pods -o wide
Bash
복사
•
kubectl run : Pod 1개 실행
•
kubectl create deployment ~ : Pod를 N개 실행
kubectl edit deployments.apps mainui
Bash
복사
•
replica를 3개에서 5개로 바로 늘려버리기~
kubectl run webserver --image=ngnix:1.14 --port 80 --dry-run=client -o yaml > webserver-pod.yaml
vi webserver-pod.yaml
Bash
복사
•
--dry-run=client : 실행 여부만 체크하겠어
•
-o yaml : yaml 포맷으로 보여줘
•
> webserver-pod.yaml : webserver-pod.yaml이라는 이름으로 파일 저장
kubectl run webserver --image=ngnix:1.14 --port 80 --dry-run=client -o yaml > webserver-pod.yaml
vi webserver-pod.yaml
YAML
복사
•
--dry-run=client : 실행 여부만 체크하겠어
•
-o yaml : yaml 포맷으로 보여줘
•
> webserver-pod.yaml : webserver-pod.yaml이라는 이름으로 파일 저장
kubectl delete pod webserver
kubectl create -f webserver-pod.yaml
YAML
복사
“50대의 추교현이 20대의 추교현에게 감사할 수 있도록 하루하루 최선을 다해 살고 있습니다.”
The End.