마참내!!!!

11월 중간에 급한 일도 있었고 조금 밀리고 실패도 많이 하다가 정말 긴 시간 끝에 완성했다!
홈서버 4대로 쿠버네티스 구축하기! 바로 가보자
ps. [25.12.02] 쿠버네티스 에러 포스팅을 정리하면서 필수인데 누락됐던 내용을 초반 부분에 추가했다
1. 사전 정리할 내용
- 아래는 이 포스팅에서 미리 알아야 이해하기 쉬운 내용들을 간단히 정리해뒀다
1) 노드는 서버를 의미한다
- 메인 노드 = 컨트롤 타워인 메인 서버
- 서브 노드 = worker에 해당하는 연결된 서브 서버
- 서버 구성은 분산 구조 구축했을 때의 기존의 홈서버 4대 그대로 사용했다

https://ratatou2.tistory.com/286
서버 부하 테스트 해보기 (feat. K6 스트레스 테스트 & 분산 구조 튜닝)
자 일해라 토끼같은 서버들아지난 분산 포스팅에 이어 오늘은 부하 테스트를 진행해볼 예정이다https://ratatou2.tistory.com/285 홈서버 4대로 분산 시스템 구축 해보기 (feat. 생각보다 겁나 쉬움!)분산
ratatou2.tistory.com
2) 쿠버네티스 쓰니까 Docker가 안됩니다
- 정상입니다!
- 이유를 한줄요약하면, 쿱네에서 컨테이너를 공식적으로 다루는 것은 'containerd'이기 때문이다
- containerd는 도커 내부의 컨테이너를 실행하는 엔진이다
- 즉, 쿠버네티스는 컨테이너를 도커가 아닌 '도커 알맹이'로 관리하는 것!!
- 쿠버네티스를 설치하면서 containerd가 기본 런타임이 되어버린 것이니 Docker가 안되는 것은 당연하다
2. 필수 사전 작업
- 미리 작업해야하는게 있다! 빼먹지 말자
- 참고로 아래 내용들은 모든 노드에서 진행하면 된다!
1) 모든 노드의 시간 동기화(NTP)
- 쿠버네티스는 토큰 검증, 인증서 만료, kubelet 통신 등 시간이 다르면 안되는게 많아서 미리 통일 시켜줘야한다
- 물론 우분투에도 기본적으로 systemd-timesyncd라는게 있긴 하다
- 다만, chrony가 훨씬 안정적인 NTP 동기화 제공해서 이것을 썼다
sudo apt install -y chrony
sudo timedatectl set-timezone Asia/Seoul
sudo systemctl enable --now chrony
chronyc sources
2) Swap 비활성화
- 이건 말 그대로 메모리 스왑에 관련된 옵션이다
- 메모리 스왑은 메모리가 부족하거나 메모리 확보가 필요할 때 발생한다
- 쿱네는 모든 리소스를 자기가 관리하는데, OS가 알아서 메모리 스왑을 하면? 메모리가 더 있는 줄 알게된다
- 당연히 램보단 성능 떨어지는 저장소를 램처럼 쓰니까 전체적인 성능 저하가 있을 수 있으므로 비활!
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab
3) 방화벽/필수 포트 개방
- 내가 띄운 앱서버, 쿱네 등등 필요한 포트가 있는데 닫혀있으면 에러 잘 뱉는다
- 근데 이건 각자 개발 환경마다 달라서 미리 어디를 열어두세요는 좀 어렵다
- 그래도 추천 드리면 6443(내가 띄운 앱서버 여기로 잡힘), 2379(etcd), 4789, 8285 정도?
- 내부통신을 열어둬야 노드간 양방향 통신이 가능하니까 통신관련 포트는 열어두자 (control 서버 & worker 서버)
sudo ufw allow 10250/tcp # Kubelet API
sudo ufw allow 30000:32767/tcp # NodePort Service 범위
sudo ufw allow 4789/udp # Flannel VXLAN
sudo ufw allow 8285/udp # Flannel host-gw (선택)
- 아래 표는 구성 요소에 따른 포트 번호를 정리해둔 표니까 참고하시길
| 구성요소 | 포트번호 | 프로토콜 | 방향 | 설명 |
| kube-apiserver | 6443 | TCP | Inbound | 클러스터 API 요청 |
| etcd server | 2379–2380 | TCP | Inbound | etcd 데이터 저장 |
| kube-scheduler | 10251 | TCP | Local | 스케줄러 |
| kube-controller-manager | 10252 | TCP | Local | 컨트롤러 |
| kubelet | 10250 | TCP | Control → Node | Pod 관리 |
| NodePort Services | 30000–32767 | TCP | 외부 → Node | 외부 서비스 |
| Flannel VXLAN | 4789 | UDP | Node ↔ Node | Pod 네트워크 |
| Flannel host-gw | 8285 | UDP | Node ↔ Node | 대체 네트워크 모드 |
4) 브릿지 네트워크 커널 설정 (br_netfilter + sysctl)
- 쿠버네티스 CNI 사용 시 커널에서 브릿지 트래픽이 iptables를 거쳐야 한다
- 즉 iptables에 미리 세팅을 안해두면 트래픽이 차단될 수 있음
sudo modprobe br_netfilter
sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system
5) containerd 기본 설정 생성
- 나는 Ubuntu + kubeadm 환경이었다
- 이 환경에서는 containerd를 설치하면 default config가 없음
- 그래서 아래 명령어로 미리 default 만들어둬야 함
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
- 그리고 파일 편집하기
sudo vim /etc/containerd/config.toml
- 아래 내용 true로 바꿔주기
- kubelet과 containerd가 같은 방식(systemd)로 자원 관리하도록 맞춰주는 과정이다
# Before
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
# SystemdCgroup = false
# After
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
- containerd 재시작
sudo systemctl restart containerd
sudo systemctl restart kubelet
3. 쿠버네티스 설치하기
1) 새로운 키 & 리포 추가
- 여기까지는 메인, 서브 노드 모두에서 실행해야한다
- 쿠버네티스가 설치가 되어있어야 연결할 수 있기 때문이다
sudo apt update
sudo apt install -y curl apt-transport-https ca-certificates gpg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/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.31/deb/ /" \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
2) 라우팅 기능 켜기
- 쿠버네티스도 결국 서버간의 '통신'이 필요하기 때문에 라우팅 기능을 켜둬야했었다
- 이 명령어는 모든 노드(서버)에서 실행한다
sudo sysctl -w net.ipv4.ip_forward=1
3) 실행
- 이거는 메인 노드에서만 실행하면 된다
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
4) 쿱네에 root 권한 주기
mkdir -p $HOME/.kube
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
5) 네트워크 플러그인 설치하기
- 그러고나면 네트워크 플러그인 설치하면 됐음
- 이건 메인 노드에서만 실행하면 된다
- 나머지 서브 노드엔 메인 노드가 알아서 세팅해준다
- 가장 편하고 많이 쓴다는 flannel 썼다
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
6) 서브 노드 연결하기
- 여기까지 진행하면 아래처럼 코드가 뜬다 이것들을 서브 노드에 붙여넣어 sudo 붙여 실행하면 된다
- 메인의 쿠버네티스에 연결하는 과정으로 이것이 마무리되면 정말로 worker 노드가 된다

# 예시
sudo kubeadm join 192.168.0.201:6443 --token ign2mp.k3afmzdtrtjwvs3e \
--discovery-token-ca-cert-hash sha256:804369d46362266526d6d328d4f35ce843bd4369353b0d5a860229f8eff4f12b
- 실행하면 아래처럼 좔좔좔 뜬다

- 연결이 잘 됐는지는 아래 명령어로 메인 노드에서 확인하면 된다
kubectl get nodes
- 연결이 잘 됐다면 아래처럼 뜬다
NAME STATUS ROLES AGE VERSION
macmini-main Ready control-plane 5m v1.31.0
n100-node Ready <none> 2m v1.31.0
lg-node Ready <none> 2m v1.31.0
samsung-node Ready <none> 2m v1.31.0
- 특히 메인 노드는 아래처럼 control-plane이라고 뜨는 것을 볼 수 있음

4. 쿠버네티스 초기화(리셋)하기
- 엄청 꼬였을 때, 뭐가 잘 안될 땐 역시 삭제하고 하나씩 다시하는게 나았다
- 이건 그냥 메인, 서브 노드 모두에 해주면 된다
1) 전부 리셋(삭제) 하기
sudo kubeadm reset -f
2) 잔여 파일 삭제
sudo systemctl stop kubelet
sudo systemctl stop containerd
sudo rm -rf /etc/cni/net.d
sudo rm -rf /etc/kubernetes
sudo rm -rf /var/lib/cni
sudo rm -rf /var/lib/kubelet
sudo rm -rf /var/lib/etcd
sudo rm -rf ~/.kube
sudo systemctl restart containerd
5. 테스트 앱 빌드하기
- 앞서 미리 말했듯이 쿠버네티스는 컨테이너를 containered로 관리한다
- 즉, 도커를 쓰지 못하니 빌드를 할 때도 다른 방법이 필요하다는 것이다
- 아무튼 쿠버네티스 환경에서 앱 빌드할 땐 nerdctl이란 도커 호환툴을 써야한다
https://github.com/containerd/nerdctl
GitHub - containerd/nerdctl: contaiNERD CTL - Docker-compatible CLI for containerd, with support for Compose, Rootless, eStargz,
contaiNERD CTL - Docker-compatible CLI for containerd, with support for Compose, Rootless, eStargz, OCIcrypt, IPFS, ... - containerd/nerdctl
github.com
1) nerdctl, git에서 다운 받기
- 아직 리눅스에서 배포하지 않아서 git에서 다운받아 써야했다
# (1) 최신 릴리스 버전 확인 및 다운로드
VERSION=$(curl -s https://api.github.com/repos/containerd/nerdctl/releases/latest | grep tag_name | cut -d '"' -f 4)
wget https://github.com/containerd/nerdctl/releases/download/${VERSION}/nerdctl-${VERSION#v}-linux-amd64.tar.gz
# (2) 압축 해제 및 설치
sudo tar -C /usr/local/bin -xzf nerdctl-${VERSION#v}-linux-amd64.tar.gz
# (3) 설치 확인
nerdctl --version
2) buildctl 설치
- nerdctl 설치하면 끝인줄 알았는데 하나 더 필요하다고 해서 진행함

# (1) 최신 BuildKit 다운로드 (v 포함)
VERSION=$(curl -s https://api.github.com/repos/moby/buildkit/releases/latest | grep tag_name | cut -d '"' -f 4)
wget https://github.com/moby/buildkit/releases/download/${VERSION}/buildkit-${VERSION}.linux-amd64.tar.gz
# (2) 압축 해제 후 설치
sudo tar -C /usr/local -xzf buildkit-${VERSION}.linux-amd64.tar.gz
# (3) 로그 디렉토리 권한 확인 및 생성
sudo mkdir -p /var/log
sudo touch /var/log/buildkitd.log
sudo chmod 666 /var/log/buildkitd.log
# (4) 백그라운드 실행 (이제 권한 문제 없음)
sudo nohup /usr/local/bin/buildkitd >> /var/log/buildkitd.log 2>&1 &
# (5) 설치 확인
ps aux | grep buildkitd
3) 앱 파일 빌드하기
- 앱 파일들이 있는 곳에 가서 아래 명령어로 빌드하면 된다
sudo nerdctl build -t myapp:latest .

- 그러고나면 이미지 리스트에서 확인할 수 있다
nerdctl images
- 재밌는게 일반 빌드와 달리 nerdctl로 빌드하면 디렉토리에 빌드 파일로 남지 않는다
- 바로 컨테이너 이미지에 자동으로 올라가는데 Docker나 nerdctl은 일반 빌드가 아닌 '컨테이너 빌드' 시스템이라서 그렇다고 한다
6. 앱 빌드 배포하기
- 보통 배포 전에 이미지 빌드한 것을 레지스트리 등록한다
- 메인에 등록해두면 서브에서 땡겨 쓸 수 있는 편리한 구조
1) 로컬 레지스트리에 앱 이미지 업로드하기
sudo nerdctl run -d -p 5001:5000 --restart=always --name registry registry:2
sudo nerdctl tag myapp:latest localhost:5001/myapp:latest
sudo nerdctl push localhost:5001/myapp:latest
- 그러고나면 뭔가 잔뜩 업로드하고 푸시하고 그런다

- 이제 각 서브 노드에서 배포하면 된다
2) 서브 노드에 배포하기
- 메인 노드에서 .yaml 파일이 하나 필요하다
- 아까 배포한 'myapp'이란 서비스를 어떻게 띄울 것인지에 대한 주문서(order) 같은 것이다
- 파일부터 생성
sudo vim myapp-deployment.yaml
- 파일 내용 기입
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: localhost:5001/myapp:latest
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 5000
type: NodePort
- 위에 보면 replicas라는 옵션이 있는데 이게 pod를 몇개까지 띄울 것인지에 대한 명시다
- 아래 명령어를 통해 각 노드에 App 서버 pod를 띄울 수 있다
kubectl apply -f myapp-deployment.yaml
- 띄우고 나면 아래 명령어를 통해 띄워져 있는 pod들을 확인할 수 있음
kubectl get pods -A -o wide

번외
- 정말 많은 실패와 에러가 있었다..
- 그것들을 전부 여기에 적기엔 너무 많아서 따로 빼서 포스팅할 예정
- 튜닝 과정 및 테스트도 따로 포스팅할 것이다!
'Infra > DevOps' 카테고리의 다른 글
| 쿠버네티스(Kubernetes, K8s) 실험과 배운 것들에 대한 회고록 (0) | 2025.12.06 |
|---|---|
| 쿠버네티스(Kubernetes, K8s) 구축 과정에 마주한 에러들 (0) | 2025.12.03 |
| Prometheus, Out of Bound 해결방법 (feat. 서버 시간 통일 & TSDB 초기화) (0) | 2025.11.28 |
| Fail2Ban, 다시 iptables-nft 체제로 전환하기 (feat. 최최최종ver) (0) | 2025.11.24 |
| Fail2Ban, nftables 체제로 완전 전환하기 (feat. 눈물겨운 이유와 Ubuntu 22.04) (0) | 2025.11.15 |