본 글은 K8S를 처음 다루는 개발자의 테스트용 환경구축을 위한 글이다. 아직 K8S에 익숙하지 않은 개발자라면 여러 대의 인스턴스를 왔다 갔다 다루고 있으므로 반드시 본문을 꼼꼼히 읽으면서 흐름을 따라오길 바란다. 혹시라도 K8S 설정을 틀렸다 하더라도 오른쪽 북마크를 통해 “초기설정 돌아가기”로 가서 안내하는 명령을 따르면 된다.
또한, 이는 작성 당시 24년 11월 5일을 기준으로 작성된 글이다. 쿠버네티스나 기타 서드파티 라이브러리의 버전 차이로 스크립트가 동작하지 않을 수도 있으므로 공식문서를 확인하거나, 댓글로 알려주길 바란다. 문서를 수정하겠다.
아래 설정을 확인하자.
OS : Ubuntu 20.04 LTS(22.04 LTS 버전은 커널 드라이버 차이로 아래 전문의 방법이 안먹히는 것으로 안다. 추후 기회가 되면 22.04 버전도 가이드 포스팅을 작성해보겠다)
Instance : GCP VM Instance 3대
Region : US-West-1(오리건)-a (당연히 알겠지만 VM Instance의 리전이 다르면 설정이 불가능하다)
온프레미스 쿠버네티스 구성
환경 구성은 GCP의 VM 인스턴스 세 대(Master-1, Worker-1, Worker-2)를 이용했다. 현재 GCP는 등록된 새로운 계정에 한해 300$에 해당하는 토큰을 무료로 제공하고 있으니 이를 잘 활용하자.
swap 비활성화
K8S는 CPU와 메모리 리소스 할당을 매우 엄격하게 관리하는 시스템이며, 컨테이너가 요청한 리소스를 정확히 사용할 수 있도록 설계되어 있다. 따라서 모든 노드에서 운영체제가 기본적으로 시스템 효율의 위해 사용하고 있는 swap기능을 내려줘야 한다.
sudo swapoff -a # 현재 시스템에 적용(리부팅하면 재설정 필요)
sudo sed -i '/ swap / s/^\\(.*\\)$/#\\1/g' /etc/fstab # 리부팅 필수
swap이 활성화되어 있으면, 메모리가 부족할 때 디스크를 사용해 메모리처럼 동작하게 되는데 이 과정에서 세 가지 문제가 발생할 수 있다.
- 성능 예측 불가능: swap 메모리를 사용하게 되면 디스크 I/O가 증가하면서 성능이 크게 저하된다. Kubernetes는 컨테이너의 성능을 예측하고 일정하게 유지하려고 하는데, swap이 활성화되면 이러한 예측이 어려워진다.
- 오버커밋: 쿠버네티스는 메모리를 요청한 만큼 할당하려고 하지만, swap이 활성화되어 있으면 물리적인 메모리보다 더 많은 메모리를 할당할 수 있다. 따라서, 오버커밋으로 메모리 사용이 불안정해져 시스템 전체가 불안정해질 수 있다.
- OOM(Out of Memory) 상황 방지: 쿠버네티스는 특정 pod가 메모리를 과도하게 사용하면 OOM Killer를 통해 해당 pod를 종료시키는 방식으로 메모리 부족 문제를 해결한다. 하지만 swap이 활성화되면 시스템이 OOM 상황에 제대로 반응하지 못하고 성능이 저하될 수 있다.
컨테이너 런타임 구성
K8S의 pod 내의 컨테이너를 생성하고 실행하기 위해 컨테이너 런타임이 필요하다. 이를 위해서 쿠버네티스의 표준 컨테이너 런타임 중 하나인 containerd를 사용할 것이다.
원래는 Docker가 K8S의 대표적인 컨테이너 런타임이었지만 단순히 컨테이너를 관리하는 목적을 벗어나 도커만의 추가적인 기능이 더해져 복잡도가 올라가버리는 바람에 K8S는 1.20 버전부터 도커에 대한 직접적인 지원을 중단했다.
여러 표준 컨테이너 런타임이 있지만 그중, 컨테이너 생성과 관리에만 집중하는 가볍고 효율적인 containerd 런타임 환경을 선택하였다.
따라서 모든 노드에 다음 명령으로 구성을 수행한다.
# Using Docker Repository
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
# containerd 설치
sudo apt update
sudo apt install -y containerd.io
# sudo systemctl status containerd # Ctrl + C를 눌러서 나간다.
# Containerd configuration for Kubernetes
cat <<EOF | sudo tee -a /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
EOF
sudo sed -i 's/^disabled_plugins \=/\#disabled_plugins \=/g' /etc/containerd/config.toml
sudo systemctl restart containerd
# 소켓이 있는지 확인한다.
ls /var/run/containerd/containerd.sock
kubeadm, kubelet 및 kubectl 설치
모든 노드에서 다음 패키지들을 설치한다.
- kubeadm: 클러스터를 부트스트랩하는 명령이다. 클러스터를 초기화하고 관리하는 기능을 갖는다.
- kubelet: 클러스터의 모든 머신에서 실행되는 파드와 컨테이너 시작과 같은 작업을 수행하는 컴포넌트이다. 노드의 상태를 관리하고, 노드 내에서 컨테이너가 잘 실행되도록 pod의 라이프사이클을 제어한다.
- kubectl: 클러스터와 통신하기 위한 커맨드 라인 유틸리티이다. 클라이언트 전용 프로그램이다.
cat < kube_install.sh
# /etc/apt/keyrings 폴더 생성 및 권한 부여
sudo mkdir -p -m 755 /etc/apt/keyrings
# 1. apt 패키지 색인을 업데이트하고, 쿠버네티스 apt 리포지터리를 사용하는 데 필요한 패키지를 설치한다.
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
# 2. 구글 클라우드의 공개 사이닝 키를 다운로드 한다.
sudo curl -fsSL <https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key> | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
# 3. 쿠버네티스 apt 리포지터리를 추가한다.
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
# 4. apt 패키지 색인을 업데이트하고, kubelet, kubeadm, kubectl을 설치하고 해당 버전을 고정한다.
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
EOF
sudo bash kube_install.sh
설치가 끝나면 kubeadm 버전을 확인한다.
$ kubeadm version
넷필터 브릿지 설정
쿠버네티스가 브릿지 네트워크 인터페이스를 통해 이동하는 트래픽에 대해 방화벽 규칙을 적용할 수 있도록 모든 노드에서 다음 명령으로 넷필터 브릿지를 설정해야 한다.
$ sudo -i
$ modprobe br_netfilter
$ echo 1 > /proc/sys/net/ipv4/ip_forward
$ echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables # 브릿지 네트워크를 통해 전달되는 트래픽도 iptables 규칙을 거치도록 설정
$ exit
Kubernetes에서 pod 간의 통신은 주로 가상 네트워크를 통해 이루어지며, 이 네트워크는 브릿지 네트워크 인터페이스를 사용하여 구현된다. 하지만 리눅스에서는 기본적으로 브릿지 네트워크를 통해 전달되는 트래픽은 넷필터 규칙(예: iptables 규칙)에 의해 처리되지 않도록 설정되어 있다. 따라서, pod 간 네트워크 정책을 적용하고, iptables를 통해 트래픽을 제어하며, 네트워크 플러그인과의 호환성을 보장하기 위해 위 설정은 필수적이다.
클러스터 구성
이제 사전 준비 작업이 끝났다. 여기서부터는 본격적으로 쿠버네티스 클러스터를 구성할 것이다.
지금까지는 모든 노드에 실행되는 작업이 동일했다. 하지만 여기부터는 각 노드에서 작업하는 내용이 다르기 때문에 주의가 필요하다.
마스터 노드 초기화
쿠버네티스가 클러스터를 초기화하고 현재 노드를 마스터 노드로 설정할 것이다. 따라서 마스터 노드에만 init 작업을 수행한다.
sudo kubeadm init
에러를 마주친다면?
만약 명령어를 입력했는데 다음과 같은 에러가 발생한다면 참고하길 바란다.
$ sudo kubeadm init
I1104 13:17:20.297377 5134 version.go:256] remote version is much newer: v1.31.2; falling back to: stable-1.29
[init] Using Kubernetes version: v1.29.10
[preflight] Running pre-flight checks
error execution phase preflight: [preflight] Some fatal errors occurred:
[ERROR CRI]: container runtime is not running: output: time="2024-11-04T13:17:22Z" 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
[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables does not exist
[ERROR FileContent--proc-sys-net-ipv4-ip_forward]: /proc/sys/net/ipv4/ip_forward contents are not set to 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 github issue로 등록되어 있다.
해당 이슈는 containerd가 Kubernetes와 통신하기 위한 CRI (Container Runtime Interface) 플러그인을 비활성화하고 있어서 발생하는 문제이다. containerd.io 패키지를 설치할 때 기본 구성 파일인 /etc/containerd/config.toml에 disabled_plugins = ["cri"] 설정이 포함되어 있기 때문이다. Kubernetes는 pod을 생성하고 관리하는 데 CRI가 필요하기 때문에, CRI가 비활성화된 상태에서는 kubeadm init이 실패하게 된다.
따라서 이 문제를 해결하려면 문제의 Line을 주석하거나 해당 줄을 삭제, 또는 그냥 설정파일을 삭제하면 된다.
sudo rm /etc/containerd/config.toml
sudo systemctl restart containerd
sudo kubeadm init
master init 완료
master노드가 성공적으로 init 되면 아래와 같이 뜰 것이다. 안내하는 명령어를 그대로 따라 하자.
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
<https://kubernetes.io/docs/concepts/cluster-administration/addons/>
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 10.138.0.2:6443 --token dkzbq6.vnob8ho29wissqug \\
--discovery-token-ca-cert-hash sha256:146a02986d764f8b24d6b1c597d57af0364bb1c5c61d290d00cdd4e9f26b0db3
1. 마스터 노드에만 유저 설정
쿠버네티스 클러스터에 접근하려면 kubectl 명령어가 클러스터 정보가 담긴 kubeconfig 파일을 참조해야 한다. 따라서 admin.conf를 현재 사용자($HOME/.kube/config)가 접근할 수 있도록 복사하고, 해당 파일의 소유자를 현재 사용자로 변경한다. 이를 통해 kubectl이 클러스터에 접근할 때, 사용자 홈 디렉토리에 위치한 config 파일을 참조하게 된다.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
(optional) 만약 root 사용자라면
export KUBECONFIG=/etc/kubernetes/admin.conf
위 명령으로 환경 변수 KUBECONFIG에 admin.conf 파일 경로를 직접 지정해 사용할 수 있다.
2. 워커 노드에만 join설정
init시 콘솔에 출력된 토큰과 sha256 hash값을 이용해 워커 노드를 클러스터에 조인시켜야 한다.
kubeadm join 10.138.0.2:6443 --token dkzbq6.vnob8ho29wissqug \\
--discovery-token-ca-cert-hash sha256:146a02986d764f8b24d6b1c597d57af0364bb1c5c61d290d00cdd4e9f26b0db3
⚠️🚧 주의!! ⚠️🚧 당연히 내부 IP address와 토큰값, hash값은 개개인마다 설정이 다르다. 무지성으로 명령어를 복붙하면 안 된다. 반드시 본인의 콘솔에 출력된 명령어를 이용하자.
에러를 마주친다면?
워커노드가 join 할 때도 마스터 노드의 init과 동일한 문제 생길 수 있다. 원인도 동일하므로 아래 명령어를 워커노드에 입력하자.
sudo rm /etc/containerd/config.toml
sudo systemctl restart containerd
노드 목록 확인
마스터 노드에서 노드 목록을 확인한다. 모든 설정이 성공적으로 된다면 아래와 같은 콘솔출력을 확인할 수 있을 것이다.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 NotReady control-plane 7m55s v1.26.0
worker-1 NotReady <none> 14s v1.26.0
worker-2 NotReady <none> 23s v1.26.0
초기설정으로 돌아가기
init이나 join을 잘못 수행한 경우 sudo kubeadm reset을 사용해 초기 설정으로 돌아갈 수 있다.
$ sudo kubeadm reset
token 재발급받는 방법 (마스터 노드)
- 토큰 리스트 확인하기: sudo kubeadm token list
- 토큰 재발급하기: sudo kubeadm token create --print-join-command
Pod 네트워크 배포
기본적으로 쿠버네티스는 노드 간 통신을 위한 네트워크 모델을 정의하고 있지만, 실제 통신을 구현하기 위해서는 네트워크 플러그인을 설치해야 한다. 네트워크 플러그인은 쿠버네티스 클러스터에서 Pod 간 통신을 관리하는 데 반드시 필요한 서드파티 라이브러리이다. 네트워크 플러그인의 종류는 상당히 다양하므로 클러스터의 요구사항과 아키텍처에 따라 선택할 수 있다. 몇몇 네트워크 플러그인은 쿠버네티스 공식 docs에서 안내하고 있다.
만약 다른 플러그인을 사용하고 싶다면 해당 플러그인의 공식 docs를 이용해야 할 것 같다.
본 글에서는 Clilium을 선택했다. 마스터 노드에만 아래 명령어를 통해서 Clilium을 설치한다.
curl -LO <https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz>
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz
cilium install
설치가 잘 되었는지 확인한다.
$ cilium status
/¯¯\\
/¯¯\\__/¯¯\\ Cilium: OK
\\__/¯¯\\__/ Operator: OK
/¯¯\\__/¯¯\\ Hubble: disabled
\\__/¯¯\\__/ ClusterMesh: disabled
\\__/
Deployment cilium-operator Desired: 1, Ready: 1/1, Available: 1/1
DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3
Containers: cilium Running: 3
cilium-operator Running: 1
Cluster Pods: 2/2 managed by Cilium
Image versions cilium quay.io/cilium/cilium:v1.12.2@sha256:986f8b04cfdb35cf714701e58e35da0ee63da2b8a048ab596ccb49de58d5ba36: 3
cilium-operator quay.io/cilium/operator-generic:v1.12.2@sha256:00508f78dae5412161fa40ee30069c2802aef20f7bdd20e91423103ba8c0df6e: 1
2,3분 기다리면 노드의 상태가 레디로 변경되었는지 확인한다.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master-1 Ready control-plane 31m v1.26.0
worker-1 Ready <none> 24m v1.26.0
worker-2 Ready <none> 24m v1.26.0
(Test) 컨테이너 배포해보기
모든 환경 구축이 끝났으니 테스트로 톰캣 컨테이너를 배포하고 외부로 노출해보자. 쿠버네티스 환경에서 더 이상 워커 노드에 직접적으로 접근할 경우는 없다. 모든 작업은 마스터 노드에서 진행한다.
kubectl create deploy tc --image=consol/tomcat-7.0 --replicas=5
kubectl expose deploy tc --type=NodePort --port=80 --target-port=8080
1분 정도 지나 확인하면 모든 컨테이너가 Running 상태인 것을 확인할 수 있다.
$ kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/tc-685d6bd5d5-hdfx2 1/1 Running 0 35s
pod/tc-685d6bd5d5-n5pwq 1/1 Running 0 35s
pod/tc-685d6bd5d5-ng864 1/1 Running 0 35s
pod/tc-685d6bd5d5-nt66b 1/1 Running 0 35s
pod/tc-685d6bd5d5-nvcqj 1/1 Running 0 35s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 54m
service/tc NodePort 10.100.121.107 <none> 80:32023/TCP 34s
모든 노드에 위 결과에 표시된 3xxxx번대 포트로 서비스가 오픈된다. 이 포트로 접속을 시도한다.
$ curl 10.142.0.3:32023 | grep title
$ curl 10.142.0.4:32023 | grep title
$ curl 10.142.0.5:32023 | grep title
1. NodePort 포트 번호
서비스 타입이 NodePort로 설정되면, 쿠버네티스는 클러스터의 각 노드에서 사용 가능한 포트 범위(기본적으로 30000~32767) 중 하나를 할당하여 외부에서 해당 서비스를 프록시할 수 있게 한다.
여기서 80:32023/TCP는 아래와 같은 의미를 가진다.
- 80: 클러스터 내에서의 서비스 포트.
- 32023: 노드 포트로, 외부에서 해당 포트를 통해 서비스를 접근
2. 서비스 접근성(마스터 노드 접근)
쿠버네티스 클러스터는 서비스 디스커버리를 통해 내부적으로 서비스를 관리한다. NodePort 서비스는 클러스터 내 모든 노드에서 서비스에 접근할 수 있도록 설정되며, 마스터 노드도 클러스터의 한 부분에 속한다.
따라서, NodePort로 설정된 서비스는 서비스가 실행되고 있는 모든 노드(마스터 노드도 해당)에서 해당 노드의 IP 주소와 할당된 노드 포트를 사용하여 서비스에 접근할 수 있다.
'개발일기 > K8S' 카테고리의 다른 글
K8S Pod와 Deployment 간단 이해 (2) | 2024.11.28 |
---|
댓글