minikube 간단하게 사용해보기

반응형
 

무중단 배포를 적용해보자.

502 게이트웨이를 없애보자. (이론편)배포를 진행하게 되면 일정 시간 동안 딜레이가 발생합니다.현재 화면은 배포 전 상태이며, localhost인 이유는 로컬 환경에서 배포를 진행했기 때문입니다.문

b-programmer.tistory.com

이 글에서 저는 무중단 배포를 실습해 봤습니다. 로드벨런서(nginx)를 이용해서 rolling배포를 진행해 봤습니다. 그리고 쿠버네티스를 이용하면, rolling배포를 조금 더 편하게(?) 사용할 수 있다고 합니다. 그래서 쿠버네티스를 직접 사용해 보면 어떨까 싶었습니다. 하지만 요게 운영환경에서 사용이 되어서 로컬에서 사용하기에는 어렵다고 알고 있습니다. 그래서 찾다 보니 로컬에서는 minikube를 사용하면 쿠버네티스로 개발이 가능하다는군요. 이걸 통해서 실습을 진행해 볼 예정입니다.

minikube 실행

minikube를 실행시켜 보겠습니다.

minikube start --driver=docker

뜻은 minikube를 docker 드라이버로 실행하겠다는 뜻입니다.

이렇게 되면 로컬에서도 쿠버네티스를 사용할 수 있게 됩니다.

애플리케이션 배포

kubectl create deployment hello-node \
--image=registry.k8s.io/e2e-test-images/agnhost:2.53 \
-n test

테스트를 위해 hello-node라는 것을 배포한다고 하지만, 쿠버네티스에서 이것이 무엇을 의미하는지 알 필요가 있다고 생각합니다.
우리가 일반적으로 말하는 배포는 다음과 같습니다. 서버에 접속해서 jar 파일을 올리고, 프로세스를 실행합니다. 애플리케이션이 죽으면 직접 다시 실행하고, 버전이 바뀌면 기존 파일을 덮어씌워 재실행합니다. 즉, 우리가 말하던 배포는 "애플리케이션을 실행시키는 행위"에 가까웠습니다. 하지만 쿠버네티스에서 말하는 앱 배포는 조금 다릅니다.

이해를 위해 그림을 그려보았습니다.

그림에서 보면, 앱 배포가 쿠버네티스를 거쳐 여러 개의 파드로 이어지고, 그 파드들이 운영 서버 위에서 실행되는 구조처럼 표현되어 있습니다. 겉으로 보기에는 "파드를 만들고 그것을 운영 서버에 적절하게 배치하는 과정"처럼 보일 수 있습니다.
하지만 이것은 결과일 뿐입니다. 쿠버네티스에서 배포란 단순히 파드를 생성하는 행위가 아니라, 해당 애플리케이션이 어떤 상태로 존재해야 하는지를 선언하는 행위입니다.
예를 들어, hello-node는 항상 1개의 파드로 실행되어야 한다고 선언하면 쿠버네티스는 이를 클러스터의 목표 상태로 기록합니다.

목표 상태가 어떤지 현재 상태가 무엇인지 확인해보겠습니다.

목표 상태

spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: agnhost
        image: registry.k8s.io/e2e-test-images/agnhost:2.53
  • replicas: 1 → "파드 1개를 유지해줘"라는 선언
  • image: ... → "어떤 앱(이미지)으로 띄울지" 선언

현재 상태

status:
  replicas: 1
  readyReplicas: 1
  availableReplicas: 1
  • readyReplicas: 1 → 실제로 준비 완료된 파드가 1개
  • 즉, 목표(spec)와 현재(status)가 일치하고 있다는 증거

그 이후에는 시스템이 현재 상태와 비교합니다. 파드가 없으면 새로 생성하고 개수가 부족하면 추가로 만들며 필요하다면 적절한 노드에 다시 배치합니다.
따라서 쿠버네티스에서의 배포는 직접 애플리케이션을 실행하는 행위라기보다, 애플리케이션의 운영 상태를 시스템에 위임하는 과정이라고 볼 수 있습니다. 혹자는 쿠버네티스의 배포를 "상태를 변경한다"라고 표현합니다. 이는 단순 실행이 아니라, 클러스터가 유지해야 할 목표 상태를 정의하는 행위이기 때문입니다.

그렇다면, 현재 상태를 목표 상태로 변경하는 작업이 필요할 것이라는 생각이 들 수 있습니다.
하지만 쿠버네티스는 현재 상태를 기준으로 동작하지 않습니다. 항상 "내가 선언한 목표 상태"를 기준으로 삼고, 현재 상태를 그에 맞추는 방식으로 동작합니다. 그렇다면 목표 상태는 어떻게 설정할 수 있을까요?
방법은 여러 가지가 있지만, 가장 단순하게 생각하면 “다시 선언하면 된다”는 말로 정리할 수 있습니다.

대시보드를 확인하면 다음과 같이 확인할 수 있습니다.

현재 hello-node 이미지는 기본적으로 정식 HTTP 서버로 동작하지 않습니다.
따라서 HTTP 요청을 확인하기 위해 실행 옵션을 추가해주어야 합니다.

아래 명령은 디플로이먼트에 실행 인자를 추가하여, 컨테이너가 HTTP 테스트 서버 모드로 동작하도록 설정하는 과정입니다.

kubectl patch deployment hello-node --type='json' -p='[
  {"op":"add","path":"/spec/template/spec/containers/0/args","value":["netexec","--http-port=8080"]}
]'
deployment.apps/hello-node patched

변경했습니다. 이제 hello-node는 port가 8080입니다. 그러니까 http라는거죠

뭐지 왜 레플리케이션이 왜 두 개지?

kubectl patch 명령을 실행한 이후, ReplicaSet이 두 개로 보이는 현상이 발생했습니다.
그 이유는 Deployment의 파드 템플릿(spec.template) 이 변경되었기 때문입니다.

쿠버네티스는 Deployment의 스펙이 달라지면 "새로운 버전이 배포되어야 한다"고 판단합니다.
즉, 단순히 값을 수정하는 것이 아니라 새로운 버전으로 다시 배포하는 과정이 시작됩니다.

내부적으로 일어나는 일

스펙이 변경되면 쿠버네티스는 다음과 같은 과정을 수행합니다.

  1. 새로운 ReplicaSet을 생성한다.
  2. 새 Pod를 생성한다.
  3. 기존 Pod를 점진적으로 종료한다.
  4. 최종적으로 목표 개수만 유지한다.

이 과정을 롤링 업데이트(Rolling Update) 라고 합니다.

서비스의 등장

현재 디플로이먼트를 만들었고 파드가 정상적으로 떠있는것을 확인했습니다. 잠시 위의 그림을 소환해보겠습니다.

그림을 보면 마치 Pod가 곧바로 운영 서버에 배포되어 바로 외부 요청을 처리하는 것처럼 보일 수 있습니다.
하지만 실제로는 그렇지 않습니다. 파드는 단지 "실행 단위"일 뿐이며, 외부에서 직접 접근하기 위한 고정된 접점이 아닙니다.
파드는 언제든지 재 생성될 수 있고, IP 또한 변경될 수 있습니다.

따라서 Pod가 떠 있다고 해서 곧바로 외부 요청을 안정적으로 받을 수 있는 상태는 아닙니다.

여기서 등장하는 개념이 바로 서비스입니다. 서비스는 파드 앞에 위치하는 고정된 접속 지점으로, 외부 요청을 받아 내부의 파드로 전달하는 역할을 합니다. 즉, 디플로이먼트는 애플리케이션을 실행시키는 역할을 하고, 서비스는 실행된 애플리케이션에 접근할 수 있도록 연결해주는 역할을 합니다. 따라서 서비스를 생성하는 이유는 단순히 리소스를 하나 더 만드는 것이 아니라,
실제로 애플리케이션에 접근할 수 있는 통로를 마련하는 과정이라고 볼 수 있습니다.
결국 서비스는 외부 요청이 파드까지 도달할 수 있도록 연결해주는 통로라고 이해하면 됩니다. 파드는 언제든지 재생성될 수 있고, IP 또한 변경될 수 있기 때문에 안정적인 접점이 아닙니다. 따라서 이러한 변화를 중앙에서 흡수하고, 항상 동일한 방식으로 접근할 수 있도록 해주는 역할이 필요하며, 그 역할을 서비스가 담당합니다.

한번 실행해보겠습니다.

kubectl expose deployment hello-node --type=LoadBalancer --port=8080

정상적으로 올라갔다는것이 보여집니다.

그렇다면, Type은 무엇이며, ClusterIP, 내부 엔드 포인트는 무엇을 말하는 걸까요? 그리고 외부 엔드 포인트는 아무것도 없을까요?

Type

Type은 서비스의 접근 방식을 의미합니다. 대표적으로 ClusterIP, NodePort, LoadBalancer가 존재합니다.

  • ClusterIP 외부에 노출할 필요가 없는 내부 서비스 용도로 사용됩니다.
  • NodePort 간단한 외부 접근 테스트 용도로 사용됩니다.
  • LoadBalancer 운영 환경에서 외부에 공개해야 하는 서비스에 사용됩니다.

Minikube는 클라우드 환경이 아니기 때문에, 실제 외부 LoadBalancer는 존재하지 않습니다.
따라서 서비스의 Type을 LoadBalancer로 설정하더라도 EXTERNAL-IP는 <pending> 상태로 표시됩니다.
이는 오류가 아니라, 외부 LoadBalancer를 생성할 수 없는 로컬 환경의 특성 때문입니다.

ClusterIP

서비스가 가지는 고정된 내부 IP입니다. 그림으로 그려보면 대략적으로 다음과 같은 그림 같습니다.

그러니까 현재 실행 중인 파드로 트래픽을 전달하는 역할을 합니다.

Endpoints

서비스가 실제로 연결하고 있는 파드 목록입니다. 
하지만 여기서 조금 이상하게 느껴질 수 있습니다. 저는 분명 파드를 하나만 실행했고, 실행 포트도 8080으로 변경했습니다.

현재 실행 중인 파드도 하나뿐입니다. 그렇다면 서비스 쪽에서는 이전 파드의 기록을 제거하지 않고 남겨두는 것처럼 보일 수 있습니다.
하지만 실제로는 그렇지 않습니다. 서비스는 파드를 기록하는 것이 아니라, 현재 라벨 조건에 맞는 파드를 실시간으로 조회하여 연결 대상을 구성합니다. 즉, Endpoints에 표시되는 값은 과거의 기록이 아니라, 지금 이 순간 실제로 연결 가능한 파드의 정보(IP:Port) 입니다.
파드가 재생성되거나 IP가 변경되면 Endpoints 또한 자동으로 갱신됩니다.
따라서 Endpoints는 누적되는 기록이 아니라, 현재 상태를 반영한 연결 대상 목록이라고 이해하면 됩니다.

외부 Endpoints가 존재하지 않는 이유

minikube 환경에서는 진짜 클라우드 LoadBalancer가 존재하지 않기 때문에 EXTERNAL-IP가 비어있거나 <pending>으로 나옵니다.
그렇다면, 외부로 접근하려면 어떻게 해야 할까요?

여러가지 방법이 있겠지만 여기에서는 포트포워딩 방법을 소개하려고 합니다.

포트 포워딩이란?

  • 포트 포워딩은 쿠버네티스 내부에서 실행 중인 애플리케이션을 로컬 환경에서 직접 접근할 수 있도록 임시로 연결해주는 기능
  • 애플리케이션이 로컬로 이동하는 것이 아니라, 로컬과 클러스터 사이에 통로를 만들어주는 방식

다음과 같은 명령어로 사용이 되어집니다.

kubectl port-forward service/hello-node 18080:8080

그러면 다음과 같이 동작이 되었다는 것을 알 수 있습니다.

이제 이렇게 만든 앱을 로컬에서도 사용할 수 있습니다.

저는 분명 18080을 배포한적이 없지만 쿠버네티스에서 포트포워딩한 결과로 사용할 수 있었습니다.

결론

간단하게 minikube를 사용해봤습니다. 쿠버네티스는 어떻게 앱을 배포하고 그것을 어떻게 로컬에서 사용하는거까지 사용해본느낌입니다. 
이제 본견적으로 디플로이먼트, 파드, 레플리케이션등 기본적인 개념도 알아보고, 제가 직접 개발하고 있는 앱도 올려보는 시간도 가져보겠습니다. 회사에서 일했을때 alroCD를 이용했던적이 있었는데 그것은 쿠버네티스를 사용해야지 이용할 수 있다고 합니다. 이것도 추후에 다뤄보겠습니다. 

반응형

댓글

Designed by JB FACTORY