나의 공부기록

[Kubernetes] 10. HPA(Horizontal Pod AutoScaler) & Control Plane 3중화(HAProxy) 본문

CS/Kubernetes

[Kubernetes] 10. HPA(Horizontal Pod AutoScaler) & Control Plane 3중화(HAProxy)

나의 개발자 2025. 4. 19. 17:35

HPA(Horizontal Pod Autoscaler)

  • 간단하게 쓰고 싶다면 Scale Up이 유리하고, 보통은 Scale Out이 많이 쓰임

HPA 실습

더보기

1. 디렉토리 생성

root@master-250410:~# cd mani/
root@master-250410:~/mani# mkdir hpa
root@master-250410:~/mani# cd hpa

 

2. 리소스 제한 manifest file 정의

root@master-250410:~/mani/hpa# vi res.yml

apiVersion: v1
kind: Pod
metadata:
  name: live-pod
  labels:
    app: live-nginx
spec:
  containers:
  - name: nginx
    image: 61.254.18.30:5000/nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

 

  • request : 최소 자원 / limits : 최대 자원
  • Pod(컨테이너)에 부여된 리소스에 임계값 정의를 통해, Pod의 갯수를 늘리거나 줄이고 싶음
  • AutoScaling을 위해, 자원을 모니터링할 메트릭서버를 설치해야 함

💡Metrics Server

✅ 헷갈렸던 부분 : Metrics Server도 CNI 플러그인 같은 역할을 해서, control-plane과 data-plane에 설치가 돼야 하는 것인 줄 알았음
➡️ 하지만, Metrics Server는 Control Plane에 설치된다기보다, 쿠버네티스 클러스터 안에 배포되는 Deployment
✅ 스케줄링되는 노드가 어디든 Pod 형태로 돌아감 ➡️ Worker Node에도 배포될 수 있음
✅ 역할 : kubelet으로부터 메트릭을 수집해서 API Server에 제공

 

3. 메트릭스 서버 설치 파일 다운로드

root@master-250410:~/mani/hpa# curl -Ls https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -o metric.yml
root@master-250410:~/mani/hpa# ls
metric.yml  res.yml

 

4. 메트릭스 서버 설치 파일 수정

  • kubelet-insecure-tls
    • 목적 : metrics-server가 각 노드의 kubelet에 매트릭 데이터를 요청할 대, TLS 인증서 검증을 무시하는 옵션
      ➡️ kubelet은 보안상 HTTPS로 메트릭을 제공
      ➡️ 대부분의 kubelete은 자체 서명된 인증서(self-signed cert)를 사용
      ➡️ metrics-server는 kubelet과 통신할 때, 인증서 유효성 검사를 진행
      👉 kubelet이 self-signed 인증서를 사용하면 "신뢰할 수 없는 인증서"라고 에러가 나서 메트릭을 가져올 수 없음
    • 그래서 인증서 검증을 생략해서 통신 에러 방지❗ 
  • --kubelet-preferred-address-types=InternalIP
    • 목적 : kubelet과 통신할 때, 어떤 IP 주소를 우선 사용해야 하는지를 정함
      ➡️ 쿠버네티스 노드는 여러 종류의 주소를 가지고 있음(ExternalIP, hostname 등...)
      ➡️ 실습이나 클라우드 환경에서는 노드의 hostname이 외부에서 해석되지 않을 수 있기 때문
      ➡️ metrics-server가 hostname을 먼저 사용하려고 시도하다가, 노드에 접근하지 못해서 메트릭 수집 실패❗
      👉 노드의 InternalIP를 우선 사용하도록 설정해서 오류 방지❗

 

5. 메트리스 서버 설치 

root@master-250410:~/mani/hpa# kubectl apply -f metric.yml 
serviceaccount/metrics-server unchanged
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader unchanged
clusterrole.rbac.authorization.k8s.io/system:metrics-server unchanged
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader unchanged
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator unchanged
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server unchanged
service/metrics-server unchanged
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

 

6. 메트릭스 서버 설치 확인

 

💡 리소스 제한 설정 이유

1️⃣ Pod가 자원을 과도하게 사용하는 것 방지

  • 컨테이너는 "호스트 OS 자원을 공유"하는 구조 ➡️ Pod에 리소스 제한을 주지 않으면, 프로세스가 무한정 CPU, 메모리를 쓸 수 있음
  • 만약 하나의 Pod가 과도하게 메모리를 먹으면, 노드 전체가 OOM(Out Of Memory) 크래시 날 수도 있음
  • 제한을 설정해서 과도한 자원 점유 방지
    ➡️ 다른 Pod들이 정상적으로 살아남을 수 있도록 보호

2️⃣ 자원의 공정한 분배

  • 쿠버네티스는 여러 Pod를 한 노드에 여러 개 띄워서 자원을 나눠 사용
  • 제한을 걸어주면:
    • 특정 Pod가 CPU, 메모리를 독점하지 않음
    • 모든 Pod가 균등하게 자원을 쓰도록 보장 가능
  • 다같이 먹는 피자를 인원수대로 잘라주는 것 같은 개념

3️⃣ 스케줄링 최적화

  • Pod에 requests 값을 설정해두면
    • 쿠버네티스 스케줄러가 "이 Pod는 최소 얼마 자원이 필요하다"를 인식하고
    • 적절한 노드에 배치할 수 있음
  • 요청한 자원이 없는 노드에는 배포하지 않아서 과부화로 인한 충돌을 미리 방지

4️⃣ 예측 가능한 성능 보장

  • 리소스를 제한해두면, 시스템은 예측 가능한 자원 범위 내에서만 동작
  • 부하가 갑자기 몰려도, 다른 Pod 성능에 미치는 영향을 최소화 가능
  • 안정성 있는 서비스 운영이 가능

7. 리소스 제한 pod 설치 

7-1. Pod - manifest file 정의

root@master-250410:~/mani/hpa# vi res.yml 

apiVersion: v1
kind: Pod
metadata:
  name: live-pod
  labels:
    app: live-nginx
spec:
  containers:
  - name: nginx
    image: 61.254.18.30:5000/nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

 

7-2. Pod 생성

root@master-250410:~/mani/hpa# kubectl apply -f res.yml 
pod/live-pod created

 

8. 리소스 자원 사용량 조회

kubectl top pod
  • 메트릭스 서버가 설치되면 kubectl top pod 명령을 쓸 수 있음
    ➡️ 리소스의 자원 사용량 조회 가능

 

9. Pod 배포 설정 - Service + Deployment

  • 부하를 줘서 AutoScaling 되는 것을 확인하고자 함
  • 리소스를 낮춰서 Deploy & SVC 생성
root@master-250410:~/mani/hpa# vi hpa-deploy.yml

apiVersion: v1
kind: Service
metadata:
  name: svc-hpa
spec:
  selector:
    app: hpa-nginx
  ports:
  - port: 80
    targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-dep
spec:
  replicas: 3 # Pod 개수 : 3
  selector:
    matchLabels:
      app: hpa-nginx
  template:
    metadata:
      labels:
        app: hpa-nginx
    spec:
      containers:
      - image: 61.254.18.30:5000/nginx
        name: nginx-con
        resources:
          requests:   
            cpu: 10m
          limits:
            cpu: 20m

 

💡 HPA를 왜 Deployment로 구현하는가?

  • HPA는 Pod의 수를 자동으로 늘리고 줄이는 기능
    ➡️ Pod 개수를 관리하려면 기준이 필요한데, 이때 기준이 Deployment

 

왜 Deployment로 구현해야 할까?

1️⃣ Deployment는 ReplicaSet을 관리하는 상위 객체

  • Deployment는 Pod의 수(Replicas)를 선언하는 컨트롤러
  • Pod를 직접 관리하지 않고, 항상 ReplicaSet을 통해 Pod 개수를 관리
  • HPA는 Deployment.spec.replicas 값을 자동으로 수정해서 Pod를 늘리거나 줄임

✅ 여기서 질문! - Pod의 수 ➡️ 즉, ReplicaSet으로 Pod의 수를 선언하면 되는 거 아니야?

ReplicaSet은 단순히 Pod 개수 유지를 하는 역할로, HPA 대상 스케일링이 가능하지만, Pod 복제만 담당
➡️ 이중화, 복제, 전략적 배포(노드의 리소스에 따라 Pod 배포) 불가능

 

2️⃣ HPA는 Pod가 아니라 Controller를 대상으로 함

  • HPA는 Pod가 아니라 Deployment를 감시하고 스케일링함
  • Pod가 직접 생성된 경우 (kind:Pod)에는 HPA가 사용할 수 없음
  • Deployment, StatefulSet, ReplicaSet 같은 컨트롤러가 관리하는 자원만 스케일링 가능

3️⃣ Deployment의 목적 = 자동 복제 + 롤링 업데이트

  • Deployment는 Pod 개수를 유지하려고 노력함
  • HPA는 그 값을 동적으로 수정하는 역할을 함
    • Deployment : Pod 배포, 버전 관리, 고가용성 유지, replicas 선언
    • HPA : CPU/메모리 등 리소스 사용량을 기반으로 replicas 수 자동 수정

 

만약 Pod만 사용하면?

  • Kind: Pod로 직접 배포된 Pod는 HPA가 컨트롤할 수 있는 방법 ❌
  • Pod는 스스로 복제하거나 갯수 유지를 할 수 없음
  • Deployment가 있어야 kubectl scale도 되고, HPA도 자동으로 컨트롤 가능

10. Pod 배포 

root@master-250410:~/mani/hpa# kubectl apply -f hpa-deploy.yml 
service/svc-hpa created
deployment.apps/hpa-dep created

 

Deployment로 Pod 배포 설정 상황

 

 11. 명령어로 PHA 배포

  • CPU 사용량이 50 % 이상일 때, 스케일링을 할 것이고, 최소 1 ~ 최대 5개로 구성
root@master-250410:~/mani/hpa# kubectl autoscale deploy hpa-dep --min=1 --max=5 --cpu-percent=50

 

12. manifest file로 PHA 정의 - Pod 개수 자동조절 정책

root@master-250410:~/mani/hpa# vi as.yml

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-nginx
spec:
  maxReplicas: 5
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-dep
  targetCPUUtilizationPercentage: 50

 

13.  HPA 생성

root@master-250410:~/mani/hpa# kubectl apply -f as.yml 
horizontalpodautoscaler.autoscaling/hpa-nginx created
리소스 생성 흐름

 

14. 부하 테스트

  • 부하를 줄 ClusterIP
  • 부하 부여
 i=1; while true; do sleep 0.001; echo $((i++)) `curl -s <svc의 ClusterIP>`;done

 

15. 부하테스트 결과 확인

  • Pod 개수가 최대 개수(max replicas)로 늘어난 것을 확인 가능
부하 테스트 진행 & 결과
부하테스트 흐름

 

16. 부하 중단

  • Pod 개수 : 5 ➡️ 1 수정 (Min Replicas)

 

17. hpa 상태 확인

kubectl get hpa
부하 테스트 중단 흐름

18. HPA 삭제

  • hpa에서 최소 1개의 Pod라고 정의했기 때문에, 1개만 남았음
    ➡️ spec.replicas=3 자체가 spec.replicas=1로 변경된 걸 확인 가능
    👉 Deployment이 replicas = 1로 변경되어 HPA가 삭제되어도 Pod는 1개로 유지됨을 의미
  • kubectl delete hpa hpa-nginx ➡️ 오토스케일러 삭제

 

  • 만약에 노드를 오토스케일링하고 싶다면, AWS에는 카펜터(karpenter)를 통해 가능하다.

실습 - 01

문제

https://github.com/pcmin929/sb_code 이 스프링부트 앱을 여러분들의 쿠버네티스 클러스에 배포하여
ilove.k8s.com 으로 접속했을 때 앱이 뜨도록 해보세요.

또한, ilove.k8s.com/web 으로 접속했을 때, readinessProbe를 통해
/healthz 경로로 헬스체크를 하는 간단한 httpd 웹서버를 띄워보세요.

위 두 개의 앱은 오토스케일링 되어야 한다.

풀이

더보기

1. sb_code 소스코드 다운로드

root@master-250410:~/mani/hpa# mkdir exam01
root@master-250410:~/mani/hpa# cd exam01/

root@master-250410:~/mani/hpa/exam01# git clone https://github.com/pcmin929/sb_code
Cloning into 'sb_code'...
remote: Enumerating objects: 531, done.
remote: Counting objects: 100% (352/352), done.
remote: Compressing objects: 100% (96/96), done.
remote: Total 531 (delta 170), reused 298 (delta 151), pack-reused 179 (from 1)
Receiving objects: 100% (531/531), 35.92 MiB | 9.07 MiB/s, done.
Resolving deltas: 100% (215/215), done.

 

2. 이미지 정의 & 빌드 & Push

root@master-250410:~/mani/hpa/exam01# vi Dockerfile 

FROM openjdk:8
RUN apt update -y && apt install -y maven
WORKDIR /
COPY sb_code sb_code
RUN cd sb_code && mvn clean package
WORKDIR /sb_code/target
CMD ["java","-jar","springbootApp.jar"]

root@master-250410:~/mani/hpa/exam01# docker build -t etoile0320/spring:1 .
[+] Building 1.1s (10/10) FINISHED                                         docker:default
 => [internal] load build definition from Dockerfile                                 0.0s
 => => transferring dockerfile: 227B                                                 0.0s
 => [internal] load metadata for docker.io/library/openjdk:8                         1.1s
 => [internal] load .dockerignore                                                    0.0s
 => => transferring context: 2B                                                      0.0s
 => [1/6] FROM docker.io/library/openjdk:8@sha256:86e863cc57215cfb181bd319736d0baf6  0.0s
 => [internal] load build context                                                    0.0s
 => => transferring context: 5.41kB                                                  0.0s
 => CACHED [2/6] RUN apt update -y && apt install -y maven                           0.0s
 => CACHED [3/6] COPY sb_code sb_code                                                0.0s
 => CACHED [4/6] RUN cd sb_code && mvn clean package                                 0.0s
 => CACHED [5/6] WORKDIR /sb_code/target                                             0.0s
 => exporting to image                                                               0.0s
 => => exporting layers                                                              0.0s
 => => writing image sha256:982d6b1b28163235a15ec2e20d08452a12b747f435166feb5ef529e  0.0s
 => => naming to docker.io/etoile0320/spring:1                                       0.0s
root@master-250410:~/mani/hpa/exam01# docker push etoile0320/spring:1
The push refers to repository [docker.io/etoile0320/spring]
5f70bf18a086: Mounted from etoile0320/mynginx 
e555f105c919: Pushed 
258fa15dce80: Pushed 
5b357aae65d8: Pushed 
6b5aaff44254: Mounted from library/openjdk 
53a0b163e995: Mounted from library/openjdk 
b626401ef603: Mounted from library/openjdk 
9b55156abf26: Mounted from library/openjdk 
293d5db30c9f: Mounted from library/openjdk 
03127cdb479b: Mounted from library/openjdk 
9c742cd6c7a5: Mounted from library/openjdk 
1: digest: sha256:5d2e896da6f7f9a777a89da72d65ffed30b5aebaad988c5c18e388a6f2948957 size: 2638

 

3. Spring - Pod

3-1. Spring - Pod 정의

root@master-250410:~/mani/hpa/exam01# vi spring-dep.yml 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-spring-dep
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hpa-spring
  template:
    metadata:
      labels:
        app: hpa-spring
    spec:
      containers:
      - image: 61.254.18.30:5000/bo-spring:1
        name: spring-con
        readinessProbe:
          httpGet:
            path: /
            port: 8085
          initialDelaySeconds: 3
          periodSeconds: 5
        resources:
          requests:
            cpu: 100m
          limits:
            cpu: 200m

 

3-2. Spring - Pod 배포 

root@master-250410:~/mani/hpa/exam01# kubectl apply -f spring-dep.yml

 

3-3. Spring - Pod 정상 작동 확인

 

4.  Spring - Service

4-1. Spring - Service 정의

root@master-250410:~/mani/hpa/exam01# vi spring-svc.yml 

apiVersion: v1
kind: Service
metadata:
  name: svc-spring-hpa
spec:
  selector:
    app: hpa-spring
  ports:
  - port: 80
    targetPort: 8085

 

4-2. Spring Service 배포

root@master-250410:~/mani/hpa/exam01# kubectl apply -f spring-svc.yml

 

4-3. Spring - Service 정상 작동 확인

 

5.  Spring - HPA

5-1. Spring - HPA 정의

root@master-250410:~/mani/hpa/exam01# vi spring-as.yml 

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-spring-sa
spec:
  maxReplicas: 5
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-spring-dep
  targetCPUUtilizationPercentage: 50

 

5-2. Spring - HPA 배포 

root@master-250410:~/mani/hpa/exam01# kubectl apply -f spring-as.yml

 

5-3. Spring - HPA 정상 작동 확인

 

6. nginx - Pod

6-1. nginx - Pod 정의

root@master-250410:~/mani/hpa/exam01# vi nginx-dep.yml 

apiVersion: v1
kind: ConfigMap
metadata:
  name: index
data:
  index.html: |
    i love k8s:)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-nginx-dep
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hpa-nginx
  template:
    metadata:
      labels:
        app: hpa-nginx
    spec:
      containers:
      - image: 61.254.18.30:5000/nginx
        name: nginx-con
        volumeMounts:
          - name: vol-index
            mountPath: /usr/share/nginx/html/healthz
        readinessProbe:
          httpGet:
            path: /healthz
            port: 80
          initialDelaySeconds: 3
          periodSeconds: 5
        resources:
          requests:
            cpu: 100m

 

6-2. nginx - Pod 배포

root@master-250410:~/mani/hpa/exam01# kubectl apply -f nginx-dep.yml

 

6-3. nginx - Pod 작동 확인

 

7. nginx - Service

7-1. nginx - Service 정의

root@master-250410:~/mani/hpa/exam01# vi nginx-svc.yml 

apiVersion: v1
kind: Service
metadata:
  name: svc-nginx-hpa
spec:
  selector:
    app: hpa-nginx
  ports:
  - port: 80
    targetPort: 80

 

7-2. nginx - Service 배포

root@master-250410:~/mani/hpa/exam01# kubectl apply -f nginx-svc.yml

 

7-3. nginx - Service 정상 작동 확인

 

8. nginx - HPA

8-1. nginx - HPA 정의

root@master-250410:~/mani/hpa/exam01# vi nginx-as.yml 

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-nginx-sa
spec:
  maxReplicas: 5
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-nginx-dep
  targetCPUUtilizationPercentage: 50

 

8-2. nginx - HPA 배포

root@master-250410:~/mani/hpa/exam01# kubectl apply -f nginx-as.yml

 

8-3. nginx - HPA 정상 작동 확인

 

9. ingress

9-1. ingress 정의

root@master-250410:~/mani/hpa/exam01# vi ingress.yml 

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: ilove.k8s.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: svc-spring-hpa
                port:
                  number: 80 #서비스의 포트
          - path: /web
            pathType: Prefix
            backend:
              service:
                name: svc-nginx-hpa
                port:
                  number: 80 #서비스의 포트

9-2. ingress 배포

root@master-250410:~/mani/hpa/exam01# kubectl apply -f ingress.yml

 

9-3. ingress 생성 확인

 

10. 작은 DNS 수정

root@master-250410:~/mani/hpa/exam01# vi /etc/hosts

127.0.0.1 localhost
127.0.1.1 ubuntu-tem

# kubectl get svc -n ingress-nginx에서 나왔던 주소
# ingress-controller 주소
211.183.3.150 ilove.k8s.com

 

11. 결과 확인

 

nginx와 spring 앱 접속 가능 확인

control plane 삼중화

  • etcd를 동기화하기 위해서, 메인 etcd를 선출
    ➡️ 홀수 개 만큼 control plane이 존재해야 동률이 나오지 ❌
    ➡️ Contorl Plane은 홀수 개로 구성되어야 함

HAproxy 실습

더보기

1. HAproxy 서버 생성 - haproxy

  • 기존 우분투 템플릿 복제
  • IP : 211.183.3.50/24
  • 호스트명 수정

 

2. haproxy 패키지 설치 - haproxy

root@haproxy:~# apt update -y
root@haproxy:~# apt install -y haproxy

 

3. haproxy 설정 - haproxy

3-1. haproxy 설정 파일 백업

root@haproxy:~# cp /etc/haproxy/haproxy.cfg .

 

3-2. haproxy 설정파일 수정 - 타겟 그룹 & 주소 설정

root@haproxy:~# vi /etc/haproxy/haproxy.cfg 

frontend kubernetes-master-lb
bind 0.0.0.0:6443
option tcplog
mode tcp

default_backend kubernetes-master-nodes

backend kubernetes-master-nodes
mode tcp
balance roundrobin
option tcp-check
option tcplog
server k8s-master1 211.183.3.101:6443 check
server k8s-master2 211.183.3.102:6443 check
server k8s-master3 211.183.3.103:6443 check
haproxy 설정 내용

 

4. m1 생성 & 설정 - m1

4-1. Worker2 노드 복제

  • master / worker1 노드 ➡️ suspend, worker2 노드 ➡️ shut down

4-2. IP 설정

 

4-3. 호스트 네임 설정

root@m1-250418:~# hostnamectl set-hostname m1-250418

 

4-4. fstab 마운트 해제

root@m1-250418:~# vi /etc/fstab 

# /etc/fstab: static file system information.
# 
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/ubuntu-vg/ubuntu-lv during curtin installation
/dev/disk/by-id/dm-uuid-LVM-FbCcNiwxfzYCT4sOGiAUnUv9tzOJziSZZFLADjshFr2WZanOkLoQpew9zAZmxLoj / ext4 defaults 0 1
# /boot was on /dev/sda2 during curtin installation
/dev/disk/by-uuid/ac919480-e104-499d-ae25-8ce7c4a5965e /boot ext4 defaults 0 1
#/swap.img      none    swap    sw      0       0
# 211.183.3.100:/shared /shared nfs defaults 0 0

 

4-5. 클러스터 초기화 - kubeadm reset

  • 기존 클러스터(worker2에서 포함되어 있던 클러스터)에서 탈퇴
root@m1-250418:~# kubeadm reset --cri-socket unix:///run/containerd/containerd.sock

# 원본
kubeadm reset --cri-socket unix:///run/containerd/containerd.sock

 

5. m2, m3 생성 ➡️ m1 복제 후 IP & 호스트명 수정 - m2, m3

  • m2 : 211.183.3.102/24
  • m3 : 211.183.3.103/24
m2, m3의 IP 수정

 

6. haproxy 재시작 - haproxy

  • 설정 파일(haproxy.cfg)을 바꾸거나 환경을 수정 ➡️ 변경사항 반영을 위해 haproxy 재시작
root@haproxy:~# systemctl restart haproxy

 

7. kubeadm 초기화 - m1

  • contorl-plane-endpoint=211.183.3.50
    ➡️ haproxy의 주소
root@m1-250418:~# kubeadm init --pod-network-cidr=10.244.0.0/16 --upload-certs --kubernetes-version=v1.30.2  --cri-socket unix:///run/containerd/containerd.sock --ignore-preflight-errors=all --control-plane-endpoint=211.183.3.50

# 원본
kubeadm init --pod-network-cidr=10.244.0.0/16 --upload-certs --kubernetes-version=v1.30.2  --cri-socket unix:///run/containerd/containerd.sock --ignore-preflight-errors=all --control-plane-endpoint=211.183.3.50

 

  • 클러스터 초기화 ➡️ 2개의 토큰 생성

💡왜 m1에서만 kuubeadm 초기화를 진행하는가?

  • 해당 명령어는 쿠버네티스 클러스터의 "초기 설정"으로, /etc/kubernetes 안에 인증서, 설정 파일이 생성됨
    ➡️ etcd 클러스터의 첫 멤버(m1)가 생성됨 ➡️ Control Plane 구성요소가 기동됨
    👉 초기화용 Token, Certificate Key를 생성함
  • 초기화를 통해 생성한 Token을 가지고, 다른 Control Plane(m2, m3)를 클러스터에 Join함

8. 마스터용 토큰 ➡️ m2와 m3에 실행

  • 마스터용 토큰 + --cri 옵션 입력
    • --cri 옵션 : --cri-socket unix:///run/containerd/containerd.sock
root@m3-250418:~# kubeadm join 211.183.3.50:6443 --token 38c2k5.mjfc8sh6slx1mar2 --discovery-token-ca-cert-hash sha256:4cbdafa947854b1826d7a53a169ed55b280ac79a81444f600c9d5e97f33f2b3c --control-plane --certificate-key 99997a7109e61bedf3d8b96eb3d060d0d9c91037cfbba2043d354e31717ddcea --cri-socket unix:///run/containerd/containerd.sock

 

토큰 적용 후, 결과 화면

 

✅ haproxy 서버에서 스스로에게 kubectl명령을 치기 위해 kubectl 명령어 설치 + 클러스터의 config 파일 필요

  • 클러스터의 config 파일 ➡️ m1의 config 파일(/etc/kubernetes/amdin.conf)

9. 패키지 다운로드 - haproxy

  • HTTPS 기반 저장소에서 안전하게 패키지를 다운로드하고 curl을 사용하기 위한 패키지 설치
root@haproxy:~# apt-get install -y apt-transport-https ca-certificates curl

 

10. GPG Key(무결성 검증) 생성 - haproxy

10-1. GPG Key 저장 디렉토리 생성

root@haproxy:~# mkdir -p /etc/apt/keyrings

 

10-2. GPG Key 공개키 다운로드 & 바이너리로 변환 ➡️ 저장

root@haproxy:~# curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

 

11. Kubernetes 저장소 패키지 설치 - haproxy

  • GPG Key로 서명된 것만 Kubernetes 저장소 패키지에서 다운로드
root@haproxy:~# echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /

 

12. APT 저장소 목록 업데이트

root@haproxy:~# apt-get update

 

13. kubectl 명령어 설치 - haproxy

13-1. kubectl 명령어 설치

root@haproxy:~# apt-get install -y kubect

 

13-2. kubectl 업그레이드 제외

root@haproxy:~# apt-mark hold kubectl

 

14. 클러스터의 config 파일 설정 - haproxy

14-1. m1의 config 파일 복사

 

14-2. 설정 파일 생성

root@haproxy:~# mkdir -p $HOME/.kube
root@haproxy:~# vi ~/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJZjY0M2ZkLzhjaHN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBME1UZ3dPREk1TUROYUZ3MHpOVEEwTVRZd09ETTBNRE5hTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUURDTWt0T2pBNjJvVklkWEFseGRZK3RXUmN5ODRxY0VMeVk5dm5DWm1ISzZJTjVjM2VZOTdSVkc3THAKV2tMUDhtMzlrd0xpSldzWVJzY3gwK3ZnMkt1RlZTcGJkZjRhVDgydHBKRldSWmNLOThUS251M2QzQUl1cEd6YQp2SlhBZmRtcHNkNm5FaXN4dkJXS08zUXBLWkMycHlEakFUb2JoeVl4dEFkWVJrT2ticlRpREM4SE5LdnBWSGQrCnJaRDFseFVicmRLTkxtUXhKdWpHRVdWZXArN3EzZ1VPWmFPMS9CZXBHbUd4RktyMHU5QjUreEd2Z2dEanJBNGgKL3Z0UTIxN0xUNGN1S05HeU9ldG9kWEdWd0hGalNGdjZrdXNkS1pKZk4vQTBnVVlLOG1ydVFrSzRVN0NZTEJ5MgpEbWgzWms3RWkzdTBEVXl1cVBuSEVkbU9OTktiQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJTZllBcW53YitQMlFzU3hVODd0Q2R5dDhiQVdEQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQUdEbTNZMGVWYQpuaUM0VzE2RUkyci9OWU5PYlhZckd4dVJxVkhEeDlvSTRKMnVwc1E5UjNkRTZxeFZ5dWcrSjY3N0JPTU5SQ0wvCjFoaDV4ZzlWZzhRYWUvb0hLR05SbExweTZEMUJXZGQwNXVFc2VtdlRUVUxCa2N2aDFxaXg5cmJNWW9uQ1pRK3YKVnV5N1doTFM4OEJGNTBmOG01MW5LWUNIcEh3cFVGRTdwWVM3b243UHlvbEtoZzlVS3prbHpmcmdKc3cyeDluOQo1c3pHY0tpV2NxTVI5dnN5TmhHM0lmaWRxbnZqWEZJZ0lneHE0MWdEQTFueWFJdFdqbytIWFovcEgvVFFrbkJNCnpxYVZCeGt4R0pCbTZnZ3dib2RCZDVXSTVFSTZpMXE0eWlhdXRuTkk3MzVJQkUxYWc3bnlYeFpyMVdSOXovL2oKa1RIMGtqbms4M0dZCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    server: https://211.183.3.50:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURLVENDQWhHZ0F3SUJBZ0lJVEhRN3NSem9mVGt3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TlRBME1UZ3dPREk1TUROYUZ3MHlOakEwTVRnd09ETTBNRFJhTUR3eApIekFkQmdOVkJBb1RGbXQxWW1WaFpHMDZZMngxYzNSbGNpMWhaRzFwYm5NeEdUQVhCZ05WQkFNVEVHdDFZbVZ5CmJtVjBaWE10WVdSdGFXNHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDY2Z4dUQKTzFFSjZjWmxuYUFTdVNCbmhZaUkwTnl1QjBEVFF4aHZLdk01ZVU1d2tUM09RK0c2aWFHZUhtSEQ5MThxY0R5YwpreFJzdFk1UnB6UjNWS09WN1Jvby8zNHhoUVhaOXJJM3FEWjJZUFJ2eDI2YklycFNwd01aZEpyVGNIRU1maHNHCkNyLzJMUUZYeTZnMVk1N2RScjl1MkhtNmxYQXdQZUdMemNvUm50UDBrWEkvTFErQ3Vac01rV2NjbmtXUndmOWQKMVpuSkhsS3FvNi80M2ZsVzBaYUVPcWlxZlJuc0ovWWorMjFPSGhZLytDK0l3dmlUSnFFSHlHVGdRUkpDMHY4MQp1N2JXOXRzVmxOcTJveXlzQUJnRXQrWktiSGUwMXRSY0VjSU1UeU14cTVTVEhwME96Nyt4YlhvM1N2TUE1aFRHCmE2L1YyZU1jdHdqejljQ0JBZ01CQUFHalZqQlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUsKQmdnckJnRUZCUWNEQWpBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRko5Z0NxZkJ2NC9aQ3hMRgpUenUwSjNLM3hzQllNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJZNnJNUVBOVjBSb2I4bzMxSnB5NnFwMUQ0Cm9WeXRHcnVabWVsbncwUWM2N241aURFQUFHU3MzTUtTOXVlekRxM1Z5THl5OGsyTFYzWXFVanprcGoxVE1ZOEQKa3NwTlhtOXZBelVCbHlXYTk0b3JvajV0UDl1Nk9mRFQ3V2RuMTJ5b3NQeFgxWU11MXYrVDF4L29udy9xbUxyYQpHRTRrRU5XaCs5N1BhYS82Q3BtT3MzeEpkamJJd2NVM1hNaEtjZkxaMVNaNUY0SGpUNEhIaTIrcG9YcEtXc053CjlYNjdid3JMWTVIT3UvVGttaHNFTkorUlN0cDNiNGNmZ2YxWkE0VWIyTVdkQ3dOaWptZDNLcWltNXBrS0JRalgKei91enFPR293RmFwQks0R3Y5K09sY1VsWnVvM3FaaEl1RnVkU21GczNWWE15WEhZUDhLQ3U1N256QldXCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
    client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBbkg4Ymd6dFJDZW5HWloyZ0Vya2daNFdJaU5EY3JnZEEwME1ZYnlyek9YbE9jSkU5CnprUGh1b21obmg1aHcvZGZLbkE4bkpNVWJMV09VYWMwZDFTamxlMGFLUDkrTVlVRjJmYXlONmcyZG1EMGI4ZHUKbXlLNlVxY0RHWFNhMDNCeERINGJCZ3EvOWkwQlY4dW9OV09lM1VhL2J0aDV1cFZ3TUQzaGk4M0tFWjdUOUpGeQpQeTBQZ3JtYkRKRm5ISjVGa2NIL1hkV1p5UjVTcXFPditOMzVWdEdXaERxb3FuMFo3Q2YySS90dFRoNFdQL2d2CmlNTDRreWFoQjhoazRFRVNRdEwvTmJ1MjF2YmJGWlRhdHFNc3JBQVlCTGZtU214M3ROYlVYQkhDREU4ak1hdVUKa3g2ZERzKy9zVzE2TjByekFPWVV4bXV2MWRuakhMY0k4L1hBZ1FJREFRQUJBb0lCQUVHaVZZbWVjUWgxVVU0QQo0OGUyZU02eUJHZE5JYUNqVGg3TWZ3end1SDJjVUxlSlVxQ1ZlN0JlVkxnYUNlckVidGMvcDB3THEzOUVUZUlVCi9ENG44MHZIMnpiaW9LeG9HK0lrUXU1dzBCYXA1eHFTUytNb2wzaGVyMEFYMVc5a015V0lKaEZNcE9HOFVsbmUKQWpnU0Jlakw2VEcxL0tleVVSRUtndk85WlVncDZ0Yk8rc2FLZmZhclh6WUFZMW5xTzd1QUVlMWJNaEJRdUs5ZwpjY0taT1RhUkxkbGNlUVFCVEM0b04zU3RoR2lzT2VkWGVFd0d4SDB4WExIcGFnOEwxcjdHRWtvSzNFUnRtUkEvCmlyaThYSHd1eER5Z1F0NzUydXhYME9xZ1YwRDA2UkdsQTI3cGpBcGlreGJHcUNoWHdaaEVVUmtSczdTWlFiNHAKQy81SUhJRUNnWUVBeXNKUlFVT1MzMEJ6N1hyOWJxTVc4RWwrSk9vYTFWNVczcW1GRkM4Vjc1VHFWN0xHanRzZAplanBuK1h0NFJGcFhsMUNncWNSV0dHbTU0N0xsZkduaUZLT3Foa25aa2FKOEFETmdUajBxSmNZU1crb0JDNVdNCk4yOVZPQVlQNnFjM0gxRjRDejh4RE9vVEVzKzNYd1A3N0NBRmx1VnJHSlNKaEpsVG1xemE3d2tDZ1lFQXhaYjQKNDNWOWJEWGtGcnBJZjFGTCtGVXRCV210eFVLSk9qaHU1dDNhVlExcWpFUllrTXlUa3h0cnhsNXdqemRqVEZQYwowMUdtZkhlSUxkNDZKRVdDK3phSm14Q2JQdHltMXZSaFZ0ZkdTSHNwYnJzT2U4bDljMnZWeThiSk51TUlyekJ5CjByMTdXUlNzbXNOVS9kVjdZVUozMUNzQXFIdWcwYU03L3B4MHE3a0NnWUVBeDBkTXpUUTQraXRDeFRtSU5HRWUKZm5QOUF4TUQ1SW1nSDUvRktCMVBGZlhxak44c3YrTWppYW8xM3NJM1poYlUxK2Rxd1BBekhqTnJmQnVyNlNlNApDRWtEcEpDWXdjWk0wWFd0UmJoZjRGaFlXdllXcU9nR3M4VThvSjkwclZCRG9RaFZUOWgvd1EyZVNYTFY2eERqClhTUHIybThKWDFNK0JaRWMxbnpsZ09FQ2dZRUFnMFh6WTROS2FkdEFCNDJKM3ZTUEtaVEZUWVJSaSszUnFCbVkKTE5BV1gwMkRqVjlYREJTdXN4eVR0UDVIZ0E4SGJNMkd0K3JXVm5rL3cyR1NkVWl0WmVOczl0WElucklRTWwyNApVZThYY0U1TFQ2TDlVMmFoYjA2a2d6YzF0YjZPcFgzbHUzZGgvT1FNYk9IN2xJMEI1TE01VTMraWQwMXpvZlAyClJ3bUJSaGtDZ1lCQVByYTlkRTVIRy9IVjI5UXhNQ0ZyRm1hWG03MWNEM0RQamEzOTFKQW9tTThUK0hoQ3c4K2gKcXpBUGs0Rno2ZGVuOHJxSDVYZmdoMllGbXkyMm8yNm0xZnY2SDBLOW9JL21jaGhEd3Z3MlBuTmhqM0RobWZPYwpaVmMrWW05Y3BUc2ZqbjMrbFdvR3hibksrT3dZME1EZy96ZDhtRWYrdDZzakYzdlF3N2RtbWc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=

root@haproxy:~# sudo chown $(id -u):$(id -g) $HOME/.kube/config

 

15. 마스터용 클러스터에 포함된 거 확인 - haproxy

root@haproxy:~# kubectl get nodes
NAME        STATUS   ROLES           AGE     VERSION
m1-250418   Ready    control-plane   11m     v1.30.11
m2-250418   Ready    control-plane   6m46s   v1.30.11
m3-250418   Ready    control-plane   5m55s   v1.30.11

 

16. m1 노드 Spuspend

 

17. 마스터용 클러스터 확인 - m1 노드 비활성화(Suspend)

  • 일정 시간이 지나면, 메인 Control Plane을 선출

👉 Control Plane의 HAproxy 구성할 때는 반드시 Control Plane의 개수를 홀수개로 가져가야 한다.