Search

[K8s] 쿠버네티스 클러스터 구축 및 실습 (Feat. Ubuntu, kubeadm)

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에 리소스를 제한한다. cgroupcgroupfs 방식과 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.