본문 바로가기
DevOps/Kubernetes

[8] 쿠버네티스 서비스

by 비어원 2022. 2. 9.
728x90

지금까지는 파드로 컨테이너 기반 애플리케이션을 띄우고 디플로이먼트로 파드의 레플리카 수와 버전을 관리하였다. 하지만 이것만으로는 파드로 띄운 웹 애플리케이션을 쿠버네티스 클러스터 외부로 서비스할 수 없다. 왜냐하면 파드는 파드 고유의 IP를 가지긴 하지만 클러스터 내부용 IP이며, 파드는 클러스터의 상태에 맞게 생성되고 삭제되기 때문에 파드는 영구적인 리소스가 아니다. 즉, 파드가 장애가 났다고 판단하면 디플로이먼트가 파드를 재구동 시키면서 파드의 IP가 유지되지 않아서 파드가 클러스터 외부와 통신이 가능한다 하더라도 제대로된 서비스를 할 수가 없다.

이러한 파드의 특성 때문에 쿠버네티스의 클러스터 외부에서 파드와 통신할 수 있도록 하는 메커니즘이 필요하다. 필요한 기능은 먼저 파드가 영구적인 리소스가 아니기 때문에 재 구동된 파드들의 IP를 찾을 수 있도록 서비스 디스커버리 기능이 필요하고, 나머지 하나는 파드 레플리카 각각에 대해 트래픽을 골고루 전달할 수 있도록 로드밸런싱 기능이 필요하다.

쿠버네티스에서는 서비스 라는 오브젝트를 통해 서비스 디스커버리로드밸런싱 기능을 제공한다.

서비스

쿠버네티스에서 서비스는 일련의 파드로 구동 중인 애플리케이션을 네트워크 서비스로서 외부로 노출시키는 추상적인 방법이다. 쿠버네티스 서비스는 서비스 디스커버리 기능을 제공하므로 디스커버리 매커니즘을 사용하기 위해 애플리케이션을 변경할 필요가 없다. 그리고 서비스는 파드 집합에 대한 하나의 DNS 이름을 부여하고 파드 집합에 대해 로드밸런싱을 수행할 수 있다.

정의

쿠버네티스에서 서비스는 파드와 같은 쿠버네티스 오브젝트이다. 모든 쿠버네티스 오브젝트와 같이 서비스 정의를 apiserver에 POST 요청하여 새 인스턴스를 만들 수 있다. 서비스는 파드와 마찬가지로 쿠버네티스 매니페스트 파일로 정의될 수 있다.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  type: ClusterIP
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377
  • spec.selector 에서 서비스 디스커버리를 할 파드 집합을 셀렉터를 통해 결정한다.
  • spec.type: 서비스의 타입이다.
  • spec.ports: 서비스의 포트를 정의할 수 있다. 이 값은 오브젝트의 배열값이다.
    • [].name: 포트 이름이다. 포트를 여러 개 등록할 때 사용 용도를 구분(설명)하기 위해 값을 설정할 수 있다.
    • [].protocol: 기본 프로토콜은 TCP이며 다른 프로토콜(UDP 등) 을 사용할 수도 있다.
    • [].port: 서비스의 포트
    • [].targetPort: [].port 와 바인딩되는 파드의 포트

서비스 타입 (퍼블리싱)

어떤 파드 (애플리케이션)을 클러스터 외부로 노출시켜야 한다. 맨 앞에서 파드를 쿠버네티스 클러스터 외부로 노출시키기 위헤서 서비스를 사용한다고 하였다. 서비스의 spec에는 spec.type 이라고 서비스의 타입을 지정할 수 있는데, 이 타입에 따라 서비스가 쿠버네티스 클러스터 외부로 노출되는지 결정된다.

  • ClusterIP (default): 서비스를 클러스터 내부 IP로 노출시킨다. 이 값을 선택하는 것은 오직 클러스터 내부로만 접근 가능하도록 하기 위해서이다.
  • NodePort: 서비스를 각 노드의 IP에 정적 포트로 노출시킨다. NodePort 서비스가 라우팅하는 ClusterIP 서비스가 자동으로 생성된다. 이 서비스는 <NodeIP>:<NodePort> 로 요청함으로써 클러스터 외부로부터 서비스와 통신할 수 있다.
  • LoadBalancer: 클라우드 제공자의 로드 밸런서를 사용하여 서비스를 외부에 노출시킨다. 외부 로드밸런서가 라우팅되는 NodePortClusterIP 서비스가 자동으로 생성된다.
  • ExternalName: externalName 값과 함께 CNAME 레코드를 반환함으로써 서비스와 externalName 필드 컨텐츠를 매핑한다. 어떠한 프록시도 설정되지 않는다.

NodePort

type 필드를 NodePort로 설정한 경우, 쿠버네티스 컨트롤 플레인은 --service-node-port-range 플래그 (default=30000-32767) 에 지정된 범위 내 포트번호를 할당한다. 각 노드는 해당 포트를 서비스에 프록시한다. 해당 서비스는 할당된 포트를 .spec.ports[*].nodePort 필드에 기록한다.
서비스 정의에서 .spec.ports[*].nodePort가 명시되지 않은 경우에는 자동으로 할당되지만 값을 명시하면 명시한 값으로 포트가 할당된다. 할당된 .spec.ports[*].nodePort 값에 대하여 클러스터의 모든 노드에 해당 포트가 개방된다.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  selector:
    app: MyApp
  ports:
      # By default and for convenience, the `targetPort` is set to the same value as the `port` field.
    - port: 80
      targetPort: 80
      # Optional field
      # By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
      nodePort: 30007

서비스 디스커버리

쿠버네티스는 서비스를 통해 서비스 디스커버리 기능을 제공한다. 서비스에서 spec.selector에 있는 레이블과 일치하는 파드에 대해서 서비스 디스커버리를 수행한다. 서비스 셀렉터에 대한 컨트롤러는 서비스에서 정의한 셀렉터와 일치하는 파드를 지속적으로 스캔한다. 그 후 모든 변경사항을 서비스와 같은 이름을 가진 엔드포인트 객체에 전달한다.

위의 예시에 있는 my-service 서비스를 띄우고 레이블이 app: MyApp 인 파드가 2개 떠있다고 가정해보자. kubectl로 서비스 이름과 동일한 이름을 가진 엔드포인트를 조회해보면 다음과 같은 결과를 얻을 수 있다.

$ kubectl get ep my-service
NAME         ENDPOINTS                            AGE
my-service   10.244.141.76:80,10.244.207.132:80   2h

셀렉터가 없는 서비스

보통 서비스는 파드에 대한 접근을 추상화하지만, 다른 종류의 백엔드를 추상화하는 경우도 있다.

  • 워크로드를 쿠버네티스로 전환하려는 경우, 쿠버네티스에 있지 않는 백엔드를 쿠버네티스로 실행하려는 경우
  • 하나의 서비스에서 다른 네임스페이스의 서비스나 다른 클러스터로의 서비스를 지정하려는 경우

예를 들어, 기존 베어메탈 환경에서 쿠버네티스로 전환하려고 할 때 데이터베이스를 기존 환경에서 사용하던 것을 그대로 사용하려고 할 경우에 셀렉터가 없는 서비스를 이용할 수 있다. 셀렉터가 없는 서비스는 셀렉터가 없기 때문에 엔드포인트 객체를 자동으로 생성해주지 않는다. 따라서 개발자가 엔드포인트 객체를 직접 정의해야 한다.

 

apiVersion: v1
kind: Service
metadata:
  name: db-service
spec:
  clusterIP: None
  internalTrafficPolicy: Cluster
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306
      name: mysql
---
apiVersion: v1
kind: Endpoints
metadata:
  name: db-service
subsets:
  - addresses:
      - ip: 192.168.0.3
    ports:
      - port: 3306

가상 IP와 Service Proxy

클러스터 내 모든 노드들은 kube-proxy를 구동한다. kube-proxy는 ExternalName 타입이 아닌 서비스의 가상 IP 형식을 구현한다. 쿠버네티스에서 Service가 Service에 정의되어있는 레이블 셀렉터와 일치하는 레이블을 가진 파드를 대상으로 로드밸런싱을 한다고 했는데 실제 로드밸런싱은 kube-proxy에서 구현한다. 이름에서 나와있듯이, kube-proxy는 쿠버네티스 서비스로 오는 요청들을 관련된 엔드포인트로 전달하는 reverse proxy 역할을 하게된다.

kube-proxy mode

쿠버네티스에서는 3가지의 kube-proxy mode를 지원한다.

  • User space proxy mode
  • Iptables proxy mode (default)
  • IPVS proxy mode

kube-proxy mode는 ConfigMap을 통해 설정할 수 있다. 일단 현재 쿠버네티스 클러스터에서 kube-proxy mode를 조회하는 방법을 알아보자. 아래 명령어를 입력하면 kube-proxy에 대한 설정을 볼 수 있다.

$ kubectl describe configmap -n kube-system kube-proxy
...
iptables:
  masqueradeAll: false
  masqueradeBit: null
  minSyncPeriod: 0s
  syncPeriod: 0s
ipvs:
  excludeCIDRs: null
  minSyncPeriod: 0s
  scheduler: ""
  strictARP: false
  syncPeriod: 0s
  tcpFinTimeout: 0s
  tcpTimeout: 0s
  udpTimeout: 0s
kind: KubeProxyConfiguration
metricsBindAddress: ""
mode: ""
...

위의 내용은 kube-proxy 설정 중 일부인데 여기서 mode가 kube-proxy의 모드이다. 빈 값으로 설정되어 있으면 기본값인 iptables mode이다.

User space proxy mode

Userspace mode에서는 kube-proxy는 서비스와 엔드포인트 객체가 추가되는지 삭제되는지 확인하기 위해 컨트롤 플레인을 지속적으로 확인한다. 그리고 Service의 ClusterIP와 Port로 향하는 트래픽을 캡쳐하는 iptables rule를 만든다. 이 rule은 트래픽을 kube-proxy로 전달하는 역할을 한다.

ClusterIP를 통해 요청이 들어오면 해당 요청은 iptables를 거쳐 kube-proxy로 요청이 전달되며 ClusterIP에 해당하는 서비스의 엔드포인트에 해당하는 파드로 연결해준다.

기본적으로 user space mode 내 kube-proxy는 Round-robin 알고리즘을 사용하여 백엔드를 선택한다.

유저스페이스 프록시에 대한 서비스 개요 다이어그램

Iptables proxy mode

Iptables mode에서는 kube-proxy는 서비스와 엔드포인트 객체가 추가되는지 삭제되는지 확인하기 위해 컨트롤 플레인을 지켜본다. 각 서비스에 대해 서비스의 clusterIP 및 port에 대한 트래픽을 캡처하고 해당 트래픽을 서비스의 백엔드 세트 중 하나로 리다이렉트(redirect)하는 iptables 규칙을 설치한다.

기본적으로 iptables 모드에서는 Round-robin이 아닌 임의의 백엔드를 선택한다.

iptables 프록시에 대한 서비스 개요 다이어그램

iptables mode에서는 kernel space에 있는 netfilter에 의해 트래픽이 전달되기 때문에 user space mode와 같이 user space와 kernel space 사이를 스위칭 할 필요가 없어서 오버헤드가 줄어든다.

728x90

'DevOps > Kubernetes' 카테고리의 다른 글

쿠버네티스 Static Pods  (0) 2022.07.16
[9] 쿠버네티스 볼륨  (0) 2022.02.20
[7] 쿠버네티스 디플로이먼트  (0) 2022.01.23
[6] 쿠버네티스 파드  (0) 2022.01.05
[5] 쿠버네티스 오브젝트 (2)  (0) 2022.01.02

댓글