사내 Docker hub와 k8s 기반 오케스트레이션 도구를 사용한 배포 환경 설정기

2024.08.20

본 문은 필자가 사내에서 Docker 기반으로 서비스 배포를 배포하는 과정을 정리한 글이다.

들어가기 전에…

🐳 Docker

Docker는 애플리케이션을 더 쉽게 배포 및 실행할 수 있게 해주는 컨테이너 기반 가상화 플랫폼이다. Docker는 개발 환경과 운영 환경에서 일관성을 유지할 수 있도록 돕는 도구로, “컨테이너”라는 경량화된 가상 환경에서 애플리케이션과 그 의존성들을 격리된 상태로 실행할 수 있게 한다.

  • 컨테이너(Container): 애플리케이션과 그 실행 환경(라이브러리, 종속 패키지 등)을 포함하는 가상화된 패키지이다. 여러 컨테이너가 하나의 운영체제(OS)에서 독립적으로 실행되어 하나가 문제가 생기더라도 다른 곳에 영향을 미치지 않는다. 그리고 하나의 운영체제(OS)에서 실행되기 때문에 그 자원을 공유하여 성능이 더 뛰어나고 가볍다.

  • 이미지(Image): 실행 가능한 애플리케이션과 환경 설정이 포함된 불변의 파일 시스템 스냅샷이다. 이미지는 읽기 전용이며, 이를 기반으로 컨테이너를 생성하고 실행한다.

  • 도커 허브(Docker Hub): 다양한 애플리케이션의 Docker 이미지를 저장하고 공유하는 저장소이다. 공개된 이미지 외에도 사용자가 자신만의 이미지를 업로드할 수 있다.

도커를 사용하는 이유는 다음과 같다.

  • 이식성(Portability): 개발 환경과 운영 환경 간의 일관성을 유지하므로, “내 컴퓨터에서는 작동하는데 서버에서는 안 돼”와 같은 문제가 줄어든다.
  • 경량화(Lightweight): 가상머신(VM)보다 가벼워 리소스를 적게 사용하고, 더 빠르게 실행된다.
  • 확장성(Scalability): 마이크로서비스 아키텍처를 지원하여 애플리케이션을 다양한 서비스 단위로 나누어 관리할 수 있다.

Docker는 애플리케이션을 다양한 환경에서 동일하게 실행할 수 있도록 도와주는 도구이다.

🚢 Kubernetes

쿠버네티스(Kubernetes)는 컨테이너화된 애플리케이션의 배포, 확장 및 관리를 자동화하는 오픈소스 컨테이너 오케스트레이션 플랫폼이다. 구글에서 개발했으며, 현재는 클라우드 네이티브 컴퓨팅 재단(CNCF)에서 관리하고 있다.

  • 컨테이너 오케스트레이션: 다수의 컨테이너를 클러스터 환경에서 자동으로 배포하고, 확장하며, 관리를 수행하는 작업을 의미한다. 쿠버네티스는 이러한 작업을 효율적으로 처리해 준다.

  • 클러스터(Cluster): 쿠버네티스에서 클러스터는 컨테이너를 실행하는 노드(Node)들의 집합. 일반적으로 하나 이상의 마스터 노드와 여러 워커 노드로 구성된다.

  • 파드(Pod): 쿠버네티스에서 가장 작은 배포 단위로, 하나 이상의 컨테이너를 포함할 수 있다. 같은 파드 내의 컨테이너들은 네트워크 및 저장소 자원을 공유한다.

  • 서비스(Service): 파드들은 동적으로 생성되고 삭제될 수 있기 때문에, 파드들의 네트워크 경로를 관리하기 위해 사용된다. 서비스는 파드들의 집합에 대해 단일 IP 주소와 DNS 이름을 제공한다.

  • 디플로이먼트(Deployment): 애플리케이션의 상태를 원하는 방식으로 선언하는 구성이다. 쿠버네티스는 디플로이먼트를 사용해 파드를 관리하고, 필요에 따라 파드를 생성하거나 삭제한다.

쿠버네티스를 사용하는 이유는 다음과 같다.

  • 자동화된 배포 및 확장(Scaling): 애플리케이션을 자동으로 배포하고 확장할 수 있어 운영 부담이 줄어듭니다.
  • 자체 복구 (Self-healing): 문제가 발생한 컨테이너를 자동으로 재시작하거나, 특정 노드에 문제가 있을 때 다른 노드로 컨테이너를 이동시킵니다.
  • 확장성(Scalability): 많은 수의 컨테이너를 효과적으로 관리할 수 있어 대규모 애플리케이션 환경에서도 안정적인 운영이 가능하다.
  • 이식성(Portability): 다양한 클라우드 제공업체와 온프레미스 환경에서 동일한 방식으로 컨테이너를 관리할 수 있다.

간단히 말해, 쿠버네티스는 다수의 컨테이너를 효율적으로 관리하고 배포할 수 있게 해주는 강력한 도구이다. Docker가 개별 컨테이너를 실행하는 데 초점을 맞춘다면, 쿠버네티스는 이러한 컨테이너들이 대규모 환경에서 원활하게 동작하도록 오케스트레이션하는 데 중점을 둔다.

✅ Docker와 Kubernetes의 차이점

Docker와 Kubernetes는 모두 컨테이너와 관련된 기술이지만, 서로 다른 목적을 가지고 있으며, 서로를 보완하는 역할을 한다. Docker가 제공하는 기능만으로는 큰 규모의 애플리케이션을 안정적으로 운영하기에 부족할 수 있으며, 이때 Kubernetes를 추가적으로 사용하게 되면 다음과 같은 이점을 얻을 수 있다.

1. 컨테이너 오케스트레이션:

  • Docker: Docker는 개별 컨테이너를 생성, 실행, 관리할 수 있는 도구이다. 하지만 다수의 컨테이너를 관리하는 데는 한계가 있다.
  • Kubernetes: 수십 개에서 수천 개의 컨테이너를 효율적으로 배포, 확장, 관리할 수 있는 오케스트레이션 도구이다. Kubernetes는 애플리케이션의 복잡성을 관리하고, 다수의 컨테이너가 상호작용하며 동작할 수 있도록 도와준다.

2. 자동화:

  • Docker: 컨테이너 실행은 수동으로 수행해야 하며, 장애가 발생하면 수동으로 복구해야 한다.
  • Kubernetes: 컨테이너의 배포, 업데이트, 복구 과정을 자동화한다. 문제가 발생한 컨테이너를 자동으로 재시작하거나, 특정 노드에 문제가 생기면 컨테이너를 다른 노드로 옮긴다.

3. 스케일링(Scaling):

  • Docker: 개별 컨테이너의 스케일링은 가능하지만, 여러 개의 컨테이너를 수동으로 확장하기에는 비효율적이다.
  • Kubernetes: 트래픽이나 부하에 따라 컨테이너를 자동으로 확장(스케일 업/다운)할 수 있다. 예를 들어, 갑작스러운 사용자 증가로 인해 서버 부하가 증가하면 Kubernetes가 자동으로 더 많은 컨테이너를 생성하여 부하를 분산시킨다. (Auto Scaling, Manual Scaling, Cluster Autoscaler)

4. 셀프 힐링(Self-healing):

  • Docker: 컨테이너가 실패하면 수동으로 대응해야 한다.
  • Kubernetes: 컨테이너가 실패하면 Kubernetes는 자동으로 다시 시작하거나, 필요한 경우 대체 컨테이너를 실행하여 시스템의 가용성을 유지한다. (헬스 체크를 위한 Liveness Probe, Readiness Probe)

5. 서비스 디스커버리 및 로드 밸런싱:

  • Docker: 기본적으로 컨테이너 간의 네트워크 연결은 수동 설정이 필요하고, 로드 밸런싱도 직접 설정해야 한다.
  • Kubernetes: 서비스 디스커버리(컨테이너 간 자동 네트워크 연결)와 로드 밸런싱을 자동으로 관리합니다. 이로 인해 여러 컨테이너 간의 통신과 트래픽 분배가 효율적으로 이루어진다.

6. 복잡한 애플리케이션 관리:

  • Docker: 개별 컨테이너를 관리하는 데는 뛰어나지만, 복잡한 마이크로서비스 아키텍처를 관리하기엔 제한적이다.
  • Kubernetes: 여러 컨테이너로 구성된 복잡한 애플리케이션을 효과적으로 관리할 수 있으며, 이를 통해 마이크로서비스 아키텍처를 더 쉽게 구현하고 유지할 수 있다.

Docker는 컨테이너 기술의 핵심이며, 개별 애플리케이션을 컨테이너로 패키징하고 실행하는 데 강력하다. 하지만, 대규모의 복잡한 시스템에서 많은 컨테이너를 관리하고 자동화하는 데 있어서는 Kubernetes 같은 컨테이너 오케스트레이션 도구가 필수적이다. Kubernetes를 사용하면 운영의 복잡성을 줄이고, 더 높은 안정성, 가용성, 확장성을 가진 시스템을 구축할 수 있다.

🐳 사내 Docker Hub

애플리케이션을 클러스터의 컨테이너에 배포 및 테스트하고 운영하려면 도커 이미지가 필요하다.

이미지는 직접 만들어서 레포지토리에 등록해 사용할 수도 있고, 공개된 이미지를 가져올 수도 있다.

필자가 재직중인 사내에서 자체적으로 Docker Hub 서비스를 운영하고 있다. 해당 서비스는 이미지 레지스트리로서, 이미지 저장, 조직별 이미지에 대한 인증/권한 관리, 자동 빌드 및 배포 등을 지원한다.

  • 자동 빌드 : 특정 브랜치 / 태그의 깃헙 이벤트 발생시 자동 빌드
  • 사내 K8S 오케스트레이션 도구에서 Deploy: 특정 태그의 이미지가 푸쉬되었을 때, 설정된 yaml 파일 자동 배포

이를 활용하여 서비스의 도커 이미지를 빌드/배포 자동화를 할 수 있었다.

🚢 사내 Kubernetes Cluster Orchestration

필자가 재직중인 사내에서는 쿠버네티스(kubernetes) 기반의 클러스터 오케스트레이션(Cluster Orchestration) 서비스를 운영하고있다.

해당 서비스에서는 아래 3가지 노드를 생성할 수 있다.

  • 마스터 노드(Master node): 클러스터를 관리.
  • 워커 노드(Worker node): 파드가 실행.
  • 인그레스 노드(Ingress node): 클러스터 외부에서 클러스터 내의 파드에 접근할때 로드밸런서의 역할을 수행. 인그레스 컨트롤러로 NGINX를 사용.

빌드/배포 Flow

인프라 도식도

1. 자동 빌드 (with Docker Hub)

  • Dockerfile: 애플리케이션의 환경과 실행 방법을 정의한 파일이다. Dockerfile을 기반으로 애플리케이션의 이미지를 빌드한다.

  • GitHub: 소스 코드가 관리되는 리포지토리로, 특정 브랜치나 태그에 변경이 발생할 때 이를 감지하여 자동으로 이미지를 빌드하도록 설정되어 있다.

  • Webhook: GitHub에서 특정 이벤트(예: 코드 푸시)가 발생할 때 Docker Hub에 알림을 보내 이미지를 빌드하도록 트리거한다.

  • Docker Hub: Dockerfile을 기반으로 이미지를 빌드하고, 이 이미지를 저장소에 저장한다. 이 저장된 이미지는 Kubernetes 클러스터에서 사용될 수 있다.

2. 자동 배포 (with K8S)

  • 쿠버네티스 클러스터: 클러스터 내의 여러 노드에서 컨테이너를 배포하고 실행하는 환경이다. 마스터 노드, 인그레스 노드, 워커 노드로 구성된다.

    • 마스터 노드: 클러스터를 관리하고 제어하는 역할을 담당한다. 실제 애플리케이션이 실행되지는 않으며, 클러스터의 상태를 관리한다.
    • 인그레스 노드: 외부 트래픽을 클러스터 내부로 라우팅하는 역할을 한다. 인그레스 컨트롤러를 통해 HTTP/HTTPS 요청을 적절한 서비스로 전달한다.
    • 워커 노드: 실제 애플리케이션 컨테이너(Pod)가 실행되는 노드이다. 배포된 애플리케이션은 여기서 실행됩니다.
  • 서비스 (Service):

    • Cluster IP: 클러스터 내부에서 서비스에 고정된 IP 주소를 제공하여, 클러스터 내의 다른 파드들이 이 서비스에 접근할 수 있게 한다.
    • 로드 밸런서 (VIP): 외부에서 서비스에 접근할 때 로드 밸런서를 통해 VIP(가상 IP)를 사용하여 트래픽을 분산한다.
  • Ingress: HTTP/HTTPS 트래픽을 관리하고 적절한 서비스로 요청을 전달한다. 이 과정에서 ingress.yaml 파일이 사용되어 설정이 정의됩니다.

  • Deployment: prod.yaml 파일을 통해 애플리케이션의 배포 설정이 정의됩니다. 이 파일을 기반으로 파드(Pod)가 생성되고, 워커 노드에서 실행됩니다.

3. 구성 파일들

  • prod.yaml: 실제 프로덕션 환경에 배포할 때 사용되는 Kubernetes의 배포 설정 파일이다. 이 파일을 사용하여 원하는 파드 수, 컨테이너 이미지, 환경 변수 등을 정의한다.

  • service.yaml: 서비스 설정 파일로, 서비스의 유형(예: ClusterIP, NodePort, LoadBalancer)과 해당 서비스가 제공하는 포트 및 관련 정보를 정의한다.

  • ingress.yaml: 인그레스 리소스를 정의하는 파일로, 외부 트래픽을 내부 서비스로 라우팅하는 규칙을 설정한다.

  • ingress-node-vip.yaml: VIP를 사용하여 특정 노드로 트래픽을 라우팅하는 설정을 정의하는 파일이다.

배포를 위한 yaml 설명

1. deploy/prod.yaml

클러스터에서 서버를 운영하는 방법을 명시한다. 이를 통해서 애플리케이션을 배포(클러스터의 워커노드 위에서 실행)할 수 있다.

  • 실행시킬 컨테이너의 기반이되는 이미지의 경로,
  • 서버가 실행되는 워커노드의 파드를 관리하는 controler에 대한 명세,
  • 환경변수,
  • CPU나 메모리 자원에 대한 스펙
  • 컨테이너가 정상적으로 실행되고 있는지 확인할 기준 (readinessProbe, livenessProbe)

아래의 명령어를 통해서 deployment를 클러스터에 생성할 수 있다.

# 클러스터에 템플릿 내용 적용
kubectl apply -f prod.yaml

# 파드 생성 내역을 확인
kubectl get deploy,rs,pod --show-labels

# 파드의 특정 로그에 접속
kubectl logs -f {파드 이름}

2. deploy/service.yaml

서비스(컨트롤러)에 대한 스펙을 명시한다.

  • 서비스: 고정된 단일 엔드포인트를 제공해주는 역할. 마스터 노드의 컨트롤러에 해당됨. (한 워커 노드 안에서만 유효)
  • 파드: 워커노드 내에서 실제 아스트로 서버가 실행되는 곳을 파드라고함. 파드들은 새로 생성되고 삭제되고를 반복하기 때문에 IP가 계속 바뀜. 때문에 고정된 IP를 설정하기 위해서는 서비스를 사용해야함. 아스트로 서버가 실행중인 파드에 접근하고 싶다면 고정된 IP(클러스터 IP) 를 통해서 접근 가능. (단, 클러스터 내부에서 한 워커 노드 안에서만 서비스가 요청을 분배해줌.)

아래와 같이 kubectl(클러스터의 마스터노드에서 워커노드의 파드들을 관리하는 API를 제공하는 CLI)를 통해서 서비스를 클러스터에 생성할 수 있다.

# 클러스터에 템플릿 내용 적용
kubectl apply -f service.yaml

# 생성된 서비스를 확인한다.
kubectl get service

# 레이블을 기준으로 자원을 조회한다.
kubectl get all -l app={앱 이름}

3. deploy/ingress.yaml

인그레스에 대한 스펙 명시

  • 인그레스: 클러스터 외부에서 클러스터 내부로 접근하는 요청을 어떻게 처리할지를 정의한 규칙 모음.
  • 인그레스 컨트롤러: 인그레스를 실제로 동작시키는 노드

클러스터 외부에서 아스트로 서버에 접근하기 위해서는 위에서 설정한 서비스만으론 불가능하다. 서비스는 클러스터 외부로 노출된게 아니기 때문에 외부에서 접근할 수 없다. 때문에 인그레스 노드가 서비스와 연결되어 외부에서 내부의 워커노드의 파드에 접근할 수 있게 도와준다.

사내 쿠버네티스 오케스트레이션 서비스에서는 nginx 기반의 ingress-nginx 컨트롤러를 사용중이기 때문에 ingress-nginx 컨트롤러는 ingress.yaml에 정의한 인그레스를 nginx 환경 설정으로 변경해서 적용된다.

아래 명령어로 인그레스를 클러스터에 생성할 수 있다.

# 클러스터에 템플릿 내용 적용
kubectl apply -f ingress.yaml

# 생성된 인그레스 확인
# Address에 있는 ip를 통해 브라우저에서 직접 접근 가능
kubectl get ingress

4. deploy/ingress-node-vip.yaml

인그레스 노드에 로드밸런서 VIP 연결

  • 로드밸런서: 대규모 실 서비스의 트래픽 제어, 가용성 확보와 성능 향상을 돕는다.
  • 실제 서비스에서는 수십대의 인그레스 노드가 있기 때문에 이를 로드밸런서로 묶어서 서비스한다.

인그레스 노드를 묶는 로드밸런서 VIP를 생성해서 서비스 인프라를 완성할 수 있다.

# 로드밸런서 서비스를 생성
kubectl apply -f ingress-node-vip.yaml

# 네임스페이스가 ingress-nginx인 서비스를 조회함.
# 최종적으로는 여기서 조회되는 External IP 로 외부에서 접근 가능하다.
kubectl get -n ingress-nginx service

도메인 연결

사내에서 자체 도메인 리소스를 생성할 수 있는 서비스를 사용하여 도메인을 생성했다.

  • 레코드 타입: A (도메인에 IPv4 주소를 연결하는 방식)
  • 레코드 값: 로드밸런서 VIP 를 연결하여 얻은 외부 IP(External IP)