Kubernetes/06. 쿠버네티스 네트워크

[쿠버네티스] 클러스터 내부 서비스 / clusterIP

Jaden Park 2021. 6. 18. 17:48

들어가며:
서비스 소개

서비스 생성

서비스 및 엔드포인트 확인

파드 생성 및 엔드포인트 연결

서비스 접근 테스트

서비스의 세션 어피니티 구성

서비스 다중 포트 구성

포트 이름 참조

 


들어가며

 

 

 

쿠버네티스의 네트워크 형태는 내부용 서비스, 외부 노출용 서비스, 특수한 형태(내부에서 외부로 나갈 때) 등이 있습니다.

db와 같이 외부로 노출되면 안되는 컨테이너가 사용하는 클러스터 내부 서비스에 대해 알아보도록 하겠습니다.

 

파드는 클러스터 외부의 요청이나 클러스터 내부의 다른 파드의 요청에 응답해야 합니다. 또한 파드가 다른 파드에서 제공하는 애플리케이션에 접근하기 위해서는 파드를 찾을 수 있어야 합니다.

 

쿠버네티스가 아닌 기존의 시스템은 애플리케이션이 동작하는 시스템의 호스트 이름이나 정적 IP를 할당하여 애플리케이션을 찾을 수 있지만, 쿠버네티스는 그렇게 할 수 없습니다.

 

이유는 다음과 같습니다.

  1. 파드는 일회성으로 동작하기 위해 설계되었습니다. 언제든지 제거될 수 있습니다.
  2. 특정 노드에 파드가 스케줄링 되고 IP 주소가 동적으로 할당됩니다. 클라이언트는 파드의 IP 주소를 미리 예측할 수 없습니다.
  3. 분산 아키텍처 및 수평 스케줄링의 경우 여러 파드가 같은 애플리케이션을 제공합니다. 각 파드마다 IP가 존재하고, 스케일링이 될 때마다 클라이언트가 해당 IP를 알 수 없습니다.

 

이를 해결하려면 파드는 단일 고정 IP를 통해서 서비스를 제공해야 합니다.


서비스 소개

서비스(Service)는 쿠버네티스 시스템에서 같은 애플리케이션을 실행하고 있는 컨트롤러의 파드 그룹에 단일 네트워크 진입 점을 제공하는 리소스 입니다. 서비스에 부여된 IP는 해당 서비스가 종료될 때까지 변경되지 않습니다.

클라이언트는 서비스가 제공하는 IP 및 포트를 통해 파드에 접근하게 됩니다. 또한 kube-dns(coredns) 가 적용되어 있다면 서비스의 이름에 기반한 고유한 FQDN이 부여됩니다.

서비스는 레이블 셀렉터를 이용해, 서비스의 대상(백엔드) 파드를 설정하는데 서비스에 선택된 파드의 목록은 앤드포인트(Endpoint) 리소스로 관리됩니다.

서비스 리소스는 여러 파드가 연결되어 있을 때 라운드 로빈(Round Robin)방식의 부하 분산이 제공합니다.

 

Kubernetes 서비스

이 서비스 및 엔드포인트는 쿠버네티스 아키텍처에서 살펴본 컨트롤 플레인의 API 서버(kube-apiserver)로 접근할 수 있는 서비스 입니다.

클러스터 내부의 파드가 API서버에 접근하기 위해 해당 서비스를 통해 접근합니다.


서비스 생성

서비스 생성은 두 가지 방법이 있습니다. 첫 번째는 kubectl expose 명령을 이용하는 방법이고, 두 번째는 YAML 파일로 리소스를 생성하는 방법입니다.

 

kubectl expose 명령을 이용하는 방법

$ kubectl expose <CONTROLLER_TYPE> <CONTROLLER_NAME> [--type=<SVC_TYPE] --name <SVC_NAME>
//예시
$ kubectl expose deployment myapp --port=80 --protocol=TCP --target-port=8080 --name myapp-svc --type=LoadBalancer

 

YAML 파일을 이용하는 방법

//myapp-svc.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: myapp-rs

.spec.ports.port: 서비스 리소스의 포트

.spec.ports.targetPort: 파드(대상) 리소스의 포트

.spec.selector: 파드 레이블 셀렉터, 엔드포인트 리소스 생성

 

서비스 오브젝트의 API는 v1을 사용하며 종류는 Service입니다.

 

.spec.ports.port 필드는 클라이언트가 접근 할 서비스의 포트이며, 해당포트로 접근하면 서비스는 .spec.ports.targetPort 필드에 지정된 파드의 서비스 포트로 포워딩하게 됩니다.

service.spec.selector 필드는 서비스에 연결할 파드를 레이블 셀렉터를 이용하여 매칭합니다. 레이블 셀렉터가 있는 경우 서비스 리소스 이름과 똑같은 엔드포인트 리소스가 자동으로 생성됩니다.


서비스 및 엔드포인트 확인

서비스 타입은 ClusterIP 이며, 이는 쿠버네티스 클러스터 내에서만 접근할 수 있는 타입입니다.

서비스에 할당된 클러스터 IP(CLUSTER-IP)는 10.105.202.176 입니다.

클러스터 외부에서 접근할 때는 외부 IP(EXTERNAL-IP)를 사용하며 이는 LoadBalancer 타입의 서비스를 선언 시에 할당됩니다.

즉, ClusterIP 서비스 타입은 클러스터 내부에서만 접근할 수 있는 서비스 입니다.

엔드포인트는 서비스의 레이블 셀렉터에 의해 연결된(포워딩 할) 파드의 IP 목록을 엔드포인트 리소스로 관리하고 있습니다.

엔드 포인트 이름은 서비스 이름과 같아야 합니다. 현재 app=myapp-rs 레이블을 가진 파드가 없기 때문에 엔드포인트 목록도 가지고 있지 않습니다. 즉, 서비스를 클러스터IP로 접속하더라도 포워딩 할 파드가 없습니다.

파드 생성 및 엔드포인트 연결

이전 포스트에서 만들었던 레플리카 컨트롤러로 파드를 생성해본 뒤 확인한 것 입니다.

3개의 엔드포인트가 구성되었으며 파드의 IP 포트를 파드 목록과 비교해볼 수 있습니다.

 


서비스 접근 테스트

서비스와 서비스가 포워딩 할 파드가 준비되었습니다. 이제 클러스터 내의 서비스에 접근하기 위한 테스트용 파드를 생성해 서비스 접근을 확인해보겠습니다.

임시 클라이언트용 파드를 생성해서, 서비스의 CLUSTER-IP에서 확인한 IP로 접근해보겠습니다.

kubectl run 명령으로 임시로 사용할 파드를 생성했습니다. (--rm 옵션은 bash 종료 시에 파드도 삭제하는 옵션입니다.)

파드를 실행 후 bash 쉘에서 curl 명령으로 서비스에 할당된 IP로 접근하면 각 파드에 부하 분산이 되는 것을 확인할 수 있습니다.

 

 

레플리카셋 컨트롤러에서 레플리카를 수평확장시키면 레이블과 api서버:포트가 일치하기에 서비스에 추가된 것을 확인할 수 있습니다.

 

 

오브젝트 edit 명령으로 replicas 를 4 → 5 수정한 뒤 확인해봐도 동일하게 추가되는 것을 확인할 수 있습니다.


서비스의 세션 어피니티(Session Affinity) 구성

앞서 살펴본 서비스 접근 테스트에서 특정한 하나의 클라이언트가 웹 요청을 할 떄마다, 부하 분산을 통해 다른 파드로 연결했습니다.

하지만, 세션 어피니티는 클라이언트 요청을 매번 똑같은 파드로 연결하고 싶은 경우나 그렇게 해야 할 경우에 사용하는 서비스입니다.

//myapp-svc-aff.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-ses-aff
spec:
  sessionAffinity: ClientIP
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: myapp-rs

.spec.sessionAffinity 필드의 속성 값으로는 2가지 종류의 구성이 있습니다.

.spec.sessionAffinity:

  • None: (기본값) 세션 어피니티 없음
  • ClientIP: 클라이언트의 IP를 확인해 같은 파드로 연결

 

세션 어피니티 설정은 ClientIP로 설정하면 쿠버네티스 클러스터의 프록시(kube-proxy)는 클라이언트의 IP를 확인하여 매번 같은 파드로 연결해줍니다.

서비스를 생성 후 확인해보겠습니다.

 

아까와 동일하게 임시 클라이언트용 파드를 생성해서, 서비스의 클러스터IP에서 확인한IP로 접근해보겠습니다.

항상 같은 파드에 접근하는 것을 확인할 수 있습니다.


서비스 다중 포트 구성

파트의 애플리케이션은 항상 하나의 포트로만 서비스하는 것은 아닙니다. 즉 여러 포트를 사용하여 서비스를 할 수 있습니다. 서비스 역시 파드의 서비스 포트가 다중 포트인 경우, 노출할 서비스 포트 역시 다중 포트로 구성할 수 있습니다.

예를 들어 웹 서버인 경우 HTTP는 80, HTTPS는 443 다중 포트를 가진 파드 및 서비스를 구성해야 할 경우도 있습니다

다중 포트 구성은 이전 서비스 오브젝트 파일을 살펴보면 서비스 포트가 단일포트라고 하더라도 YAML 의 리스트로 구성된 것을 확인할 수 있습니다. 즉 다중포트는 리스트를 추가하여 구성할 수 있습니다.

다음은 다중 포트를 구성하기 위한 서비스 오브젝트 예제입니다.

//myapp-svc-multiport.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-multiport
spec:
  ports:
  - name: myapp-http
    port: 80
    targetPort: 8080
  - name: myapp-https
    port: 443
    targetPort: 8443
  selector:
    app: myapp-rs

다중 포트를 설정할 때는 반드시 포트의 이름을 부여해야 합니다.

.spec.ports.name: 포트의 이름

 


포트 이름 참조

서비스의 포트에 이름을 부여할 수 있습니다. 파드나 파드를 생성하는 컨트롤러의 파드 템플릿의 컨테이너 포트에도 이름을 부여할 수 있습니다.

이렇게 하면 파드(컨테이너)의 포트 이름을 이용하여 서비스 생성 시 파드의 대상포트(targetPort)에 이름을 이용하여 연결할 수 있습니다. 이는 이름으로 참조하기 때문에 파드의 포트 이름만 변경되지 않았다면, 실제 파드의 포트 번호가 변경되더라도, 스펙의 변경 없이 계속해서 연결할 수 있습니다. 즉, 엔드포인트도 변경됩니다.

 

포트 이름을 사용한 레플리카셋 및 서비스 생성

파드 및 컨트롤러에서 컨테이너의 포트에 이름을 부여하는 구성으로 오브젝트를 구성해보겠습니다.

//myapp-rs-named-port.yaml

---
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-rs-namedport
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-rs-namedport
  template:
    metadata:
      labels:
        app: myapp-rs-namedport
    spec:
      containers:
      - name: myapp
        image: ghcr.io/c1t1d0s7/go-myweb
        ports:
        - name: myapp-http
          containerPort: 8080

컨테이너의 8080 포트에 myapp-http 라는 이름을 부여했습니다. 이름은 서비스 생성 시 참조할 수 있습니다.

다음은 포트 이름을 참조하는 서비스 구성입니다.

//myapp-svc-named-port.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-namedport
spec:
  ports:
  - name: myapp-http
    port: 80
    targetPort: myapp-http
  selector:
    app: myapp-rs-namedport

.spec.ports.targetPort: 대상 파드의 포트 이름 지정

레플리카셋 컨트롤러와 서비스를 생성해보고 레플리카셋 컨트롤러를 확인해보겠습니다.

정확하게 8080 포트를 참조하고 있는 것을 확인할 수 있습니다.