Post

오토에버 클라우드 2기 42일차

오토에버 클라우드 2기 42일차

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로 통신해야 할지 명확하게 알려주어야 한다 설정 파일을 사용하면 이 모든 정보를 정확하게 지정할 수 있다

  1. 마스터 노드에서 kubeadm-config.yaml 파일을 생성

    1
    
    vi kubeadm-config.yaml
    
  2. 아래 내용을 그대로 복사하여 붙여넣기

    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"
    

 

클러스터 초기화 및 설정

  1. (권장) 이전 시도 초기화: sudo kubeadm reset -f

  2. 설정 파일을 사용하여 클러스터 초기화:

    1
    
    sudo kubeadm init --config kubeadm-config.yaml --upload-certs
    
  3. 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를 명확히 지정해주어야 한다
  1. 각 워커 노드에서 kubeadm-join-config.yaml 파일을 생성

  2. 아래 내용을 붙여넣되, 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>"
    

 

클러스터에 조인

  1. (권장) 이전 시도 초기화: sudo kubeadm reset -f

  2. 설정 파일을 사용하여 조인:

    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

기대 결과: 모든 노드의 STATUSReady로, INTERNAL-IP가 지정한 고유 IP로 표시되어야 한다

전체 시스템 Pod 확인:

1
kubectl get pods --all-namespaces

kube-system과 calico-system 네임스페이스의 모든 Pod들이 Running상태로 보인다면, 쿠버네티스 클러스터가 성공적으로 구축된 것

 

Kubernetes 고급 활용법 (kubectl)

1. 리소스 상태 체크와 대기 (kubectl wait)

스크립트 등에서 리소스가 특정 상태가 될 때까지 기다려야 할 때 유용한 명령어임

  • sample-podReady 상태가 될 때까지 대기

    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
    

 

This post is licensed under CC BY 4.0 by the author.