개발/backend

[Backend] 쿠버네티스(K8S)를 시작해보자(4) - 서비스 워크로드(workload)편(Service)

나인에스 2022. 4. 17. 18:53
해당 포스팅의 모든 example은 아래 github repository에서 다운받을 수 있습니다.
https://github.com/flyingcop/example-kubernates

개요

기존 포스팅에서 쿠버네티스에 대한 이론적인 내용, local host에서 테스트 혹은 트레이닝을 위한 환경  그리고 실제 쿠버네티스에서 구동되는 기본 요소들(Workload Resource)에 대해서 알아보고 간략하게 사용방법에 대해서 알아보았습니다.

이번 포스팅에서는 위의 구성요소들을 외부에 노출해서 사용자 혹은 client가 쿠버네티스에서 제공하는 기능을 사용할 수 있게 해주는 요소인 Service 에 대해서 알아보겠습니다. 이 챕터 이후에 각각의 Service들을 IP, Port를 이용한 접근이 아닌 URL을 이용한 접근 및 로드벨런싱 기능을 해주는 (Ingress) 와 그외의 persistent storage를 Local 에 생성할 수 있는 Volume 과 환경 변수를 관리할 수 있도록 해주는 ConfigMap, confidencial data의 관리를 위한 Secret에 대한 내용은 다음 번 포스팅에서 다루겠습니다.


Service

Service는 쿠버네티스의 Pod들에서 실행중인 애플리케이션을 외부 네트워크에 노출시키는 방법중 하나입니다.

Pod은 각각 unique한 자체 IP를 가지고 있고 이를 이용해서 외부 혹은 서로 통신할 수 있지만 Pod자체가 쿠버네티스 설정에 의해서 항상 동일하게 유지되는 unique한 자원은 아닙니다. Deployment에 의해서 동일한 서비스를 제공하는 pod가 여러개가 될 수도 있고 경우에 따라서 삭제, 재생성, 업데이트 등 유동적으로 관리되기 때문에 항상 내가 원하는 애플리캐이션이 동작하는 Pod의 IP를 지정해서 접근한다는 것은 매우 어려운 일입니다.

이를 해결하기 위해서 쿠버네티스는 Service라는 리소스를 제공하고, Service는 외부에서 접속할 수 있는 고정 IP를 가지고 있고 외부에서 Service를 통해서 내부의 다른 여러 Pod들에 접근할수 있도록 해주는 기능을 제공해줍니다. 예를들어 Deployment를 이용해서 3개의 replica 로 운영되는 Echo Backend를 운영한다고 했을때 Client는 세개의 replica의 어느곳에 접속을 해야할지 고려할 필요 없이 Service에 접속해서 Echo Backend 를 이용할 수 있습니다. 또 하나의 예로, 하나의 Service 아래 작은 단위 기능을 제공하는 다수의 Pod들을 배치해서 MSA(MicroServiceArchitecture) 구조도 만들 수도 있습니다.

제공하는 Service의 type에 따라서 ClusterIP, NodePort, LoadBalance, ExternalName 네가지 유형의 Service를 사용할 수 있습니다.

Service Type - ClusterIP

Service Type중 default 는 ClousterIP 입니다. cluster 내부에 IP를 할당후 접근할 수 있도록 해줍니다. 이는 클러스터 내부에서 접근을 허용해주며 내부에 있는 여러 Pod들 중 selector를 이용해서 선택하는 일종의 loadbalancer기능을 제공합니다.

example-service 폴더를 만들고 아래 두 yml파일을 생성합니다.

# deployment를 위한 yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: example-pod
      tier: app
  template:
    metadata:
      labels:
        app: example-pod
        tier: app
    spec:
      containers:
        - name: example-pod
          image: nginx:1.14.2		# 사용할 이미지
          ports:
            - containerPort: 80
              protocol: TCP
# Service 를 위한 yml
apiVersion: v1
kind: Service
metadata:
  name: example-service
spec:
  selector:
    app: example-pod
    tier: app
  ports:
    - port: 80
      protocol: TCP

실행 및 결과 확인

위의 그림과 같이 service가 생성되었고, deployment에 의해서 세개의 pod replica가 실행된것을 확인할 수 있다.

참고 : kubectl 명령어를 이용해서 folder내의 모든 yml 파일을 한번에 적용할때에는 아래 명령어를 사용한다.
$>kubectl apply -f {folder name}
또한 해당 folder의 하위 폴더에 있는 yml파일까지 적용 하려면 아래 명령어를 사용
$>kubectl apply -f {folder name} --recursive

물론 하나의 파일에 deployment, service모두를 정의 할 수 는 있으나 resource가 많아지면 file/folder로 구분하는 것이 관리하기 쉽습니다.

이제 같은 cluster 내에 생성된 Pod라면 위의 Service의 IP 혹은 domain name을 이용해서 example-pod에 접근할 수 있고, 위의 예제에서는 10.101.183.139 라는 IP 혹은 "example-service" 라는 donmain name을 이용할 수 있습니다. Service는 example-pod에 접근할 때 80 포트로 연결하게 됩니다. Service의 역활을 요약하면 cluster내부에서 example-pod 접근 요청에 대한 loadbalancer역활도 하고 example-pod라는 donmain을 관리하는 도메인서버로서의 역활도 하게 됩니다.

여기서 ClusterIP type의 Service는 cluster 내부에서만 접근할 수 있는데 굳이 이게 필요할까요? 라는 의문이 들수 있습니다. 대답은 필요하다입니다. MSA를 적용하게 되면 무수히 작은 단위의 pod들이 배포가 되고, 이 각각의 작은단위의 pod들중 외부에 노출되어서는 안되는 것들도 매우 많이 있을겁니다.(ex. DB에 직접 연결해서 read/write 역활만 수행하는 pod) 또한 이 Pod들은 cluster 내부 혹은 서비스 전체 영역에서 common 하게 사용될 수도 있기 때문에 Service를 이용한 loadbalancing, domain name을 이용한 이용 편의성등은 필수가 됩니다.

 

 

 

Service Type - NodePort

이제는 NodePort type의 Service를 만들어 보겠습니다. NodePort는 cluster 외부의 Node에서 특정 pod으로 접근을 할수 있도록 해주는 서비스 입니다. cluster내의 모든 Node에 설정한 Port(기본값은 30000 ~ 32767중 자동 할당)를 연결 및 open하고 해당 port로의 모든 요청 혹은 접근은 지정되어 있는 pod로 라우팅하게 됩니다.

신규 폴더를 생성해서 아래와 같이 service.yml파일을 생성 합니다. 그리고, deployment는 위에서 사용했던 파일을 그대로 복사해서 사용하면 됩니다.

apiVersion: v1
kind: Service
metadata:
  name: example-service
spec:
  type: NodePort
  selector:
    app: example-pod
    tier: app
  ports:
    - port: 80
      protocol: TCP
      nodePort: 30010

실행 및 결과 확인

NodePort type의 Service 실행 결과

위와같이 NodePort가 실행된것을 확인 할 수 있고,아래 command를 이용해서 생성된 cluster NodePort에 접근할 수 있습니다.

#외부 접속 ip 확인
$>minicube ip

#curl을 이용해서 생성된 NodePort에 접근
$>curl 192.168.64.2:30010 # ip는 위의 command의 결과를 확인후 입력

NodePort로 접근한 결과

 

Service Type - LoadBalancer

LoaderBalancer는 Node가 Dynamic하게 destroy되었을때에 동일한 역활을 해주는 Node혹은 Pod로 라우팅해줄수 없다는 NodePort의 단점을 보완합니다. 예를 들어, 2개의 Node가 있고 이중 어디로 접근을 한다고 해도 NodePort에 의해서 target이 되는 Pod가 있는 Node로 접근을 하게 되지만 만약 현재 접근하려고 하는 Node가 시스템에 의해서 Destory가 되어 존재 하지 않는 Node일 수도 있습니다.

위와 같은 단점을 보완하기 위해서 현재 Active되어 있는 Node에 접근 할 수 있게 해주는 Load Balance가 있습니다. client에서의 접근은 Load Balancer에 접근요청을 하고 Load Balancer가 현재 active되어 있는 노드에 접근을 하도록 라우팅을 하게 됩니다.

Local host에서는 Load Balancer를 테스트 하기 위해서 가상의 Load balancer 역활을 해주는 addon을 생성해야 합니다. AWS와 같은 cloud 환경인 경우 해당 cloud service에서 load balancer를 제공해주지만 로컬에서는 존재 하기 않기 때문입니다.

먼저 아래의 command를 이용해서 metallb라는 가상의 환경을 만들어주는 addon을 활성화 해줍니다.

# MetalLB 활성화
$>minikube addons enable metallb

# MetalLB 설정
$>minikube addons configure metallb

-- Enter Load Balancer Start IP: {minikube ip 실행 결과 IP입력}
-- Enter Load Balancer End IP: {minikube ip 실행 결과 IP입력}
    ▪ Using image metallb/speaker:v0.9.6
    ▪ Using image metallb/controller:v0.9.6
✅  metallb 이 성공적으로 설정되었습니다

metallb addon활성화 및 설정

이후 아래와 같이 load balancer를 생성하는 yml 파일을 생성 후 적용합니다.

apiVersion: v1
kind: Service
metadata:
  name: example-service-loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: example-pod
    tier: app
  ports:
    - port: 30000
      protocol: TCP
      targetPort: 80

실행 및 결과 확인

위와 같이 LoaderBalancer가 추가 되었습니다. 이제 minikube IP 로 확인된 IP와 Load Balancer의 포트 30000번으로 아래와 같이 접속 테스트를 할수 있습니다.

 

Load Balancer는 기본적으로 NodePort의 기능을 포함하고, NodePort는 ClusterIP를 포함하고 있습니다. 따라서, Load Balancer만 생성하면 외부에서 특정 Pod에 접근할 수 있도록 할 수있습니다.