전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]

티스토리 뷰

8. 파드 로드 밸런서 서비스

서비스 리소스는 파드 로드밸런서입니다.
서비스에 대해 여러분이 꼭 아셔야 할 것은 딱 두가지입니다.
파드안에서 다른 파드안의 어플리케이션을 서비스명으로 호출하는 방법과 목적별 네가지 서비스 유형입니다.

먼저 진짜 서비스가 파드 로드밸런싱을 해주는지는 확인해 보겠습니다.
두번째 파드 간 서비스명으로 통신하는 방법에 대해 배워 보겠습니다.
세번째로 목적별 네가지 서비스 유형에 대해 학습하겠습니다.
마지막으로 알면 유용할 내용들을 추가로 설명 하겠습니다.

8.1 파드 로드밸런싱 테스트

‘member’파드를 3개로 스케일링하고 ‘curl’파드에서 ‘member’파드의 ‘/hostname’이라는 API를 호출합니다.
이 API는 현재 파드의 호스트네임을 리턴합니다. 로드 밸런싱이 된다면 파드의 호스트명이 바뀌어야 겠죠 ?

현재 ‘member’파드가 스테이트풀셋으로 배포되었는지 확인하고 아니면 지우고 스테이트풀셋으로 배포하십시오.
이전에 만든 ‘sts-member.yaml’파일을 이용하시면 됩니다.
마지막 처럼 ‘member’파드가 일련번호가 붙어 3개 실행되게 해 주십시오.

[root@osboxes]# cd ~/k8s/yaml
[root@osboxes yaml]# k get po
NAME                        READY   STATUS    RESTARTS   AGE
curl                        1/1     Running   0          67m
member-8dfbb76f4-8dvfx      0/1     Pending   0          30s
member-8dfbb76f4-x9xl4      1/1     Running   0          30s
recommend-cd5454dc4-khkm5   1/1     Running   0          32h
[root@osboxes yaml]# k delete deploy member
deployment.apps "member" deleted
[root@osboxes yaml]# k apply -f sts-member.yaml
statefulset.apps/member created
[root@osboxes yaml]# k get po
[root@osboxes yaml]# k get po

NAME                        READY   STATUS    RESTARTS   AGE
curl                        1/1     Running   0          69m
member-0                    1/1     Running   0          94s
member-1                    1/1     Running   0          94s
member-2                    1/1     Running   0          94s
recommend-cd5454dc4-khkm5   1/1     Running   0          32h


‘curl’파드 안으로 진입합니다.

[root@osboxes yaml]# k exec curl -it -- sh


curl 명령을 이용하여 ‘member’서비스의 ‘/hostname’ API를 여러번 호출 합니다.
아래와 같이 호출할때 마다 호스트명이 달라질겁니다. 호스트명이 달라진다는 의미는 서비스 ‘member’가 로드밸런싱을 하여 매번 다른 파드로 연결해 준다는 의미입니다.

/ $ curl http://member:3001/hostname
Hostname=>member-2/ $
/ $ curl http://member:3001/hostname
Hostname=>member-1/ $
/ $ curl http://member:3001/hostname
Hostname=>member-0/ $


‘exit’를 입력하여 파드를 빠져 나옵니다.
간단하게 서비스가 파드 로드 밸런싱을 하는걸 테스트 해 봤습니다.

8.2 파드 간 서비스명으로 통신 방법

여러분이 개발하시는 어플리케이션을 크게 둘로 나누면 요청자와 제공자라고 할 수 있습니다.
요청자가 제공자의 API를 호출하고 그 결과를 받아 어떤 처리를 하게 되어 있습니다.
요청자와 제공자가 컨테이너가 아닌 경우 제공자 API를 호출하는 주소에는 DNS에 등록된 FQDN(Fully Qualified Domain Name)을 사용합니다.
예를 들어 ‘recommend.ott.com’과 같은 FQDN이 사용됩니다.
컨테이너인 경우에도 똑같이 DNS에 등록된 FQDN을 사용하게 됩니다. 쿠버네티스에서 사용하는 DNS는 CoreDNS이구요.
새로운 서비스 오브젝트나 스테이트풀 파드가 생성되면 CoreDNS에 FQDN이 등록됩니다.
CoreDNS에 등록되는 서비스 오브젝트의 FQDN은 ‘{서비스명}.{네임스페이스명}.svc.cluster.local’입니다.
스테이트풀 파드의 FQDN은 ‘{파드명}.{서비스명}.{네임스페이스명}.svc.cluster.local’이구요.

따라서 파드가 다른 파드를 호출할 때는 이 서비스 FQDN을 이용하면 됩니다. 호출 받는 파드가 스테이트풀 파드라면 서비스 FQDN 또는 파드 FQDN을 이용하면 되겠지요.

그런데 쿠버네티스에서는 호출 하는 대상 서비스 오브젝트가 같은 네임 스페이스에 있으면 편리하게 ‘{서비스명}’만을 FQDN으로 사용할 수 있습니다. 스테이트풀 파드의 주소는 네임 스페이스와 상관 없이 전체 FQDN을 사용해야 합니다.

그림으로 요약하면 아래와 같습니다.


위에서 로드밸런싱을 테스트 하기 위해 호출한 주소를 보면 ‘http://member:3001/hostname’처럼 서비스 오브젝트명인 ‘member’만으로 대상 서비스가 호출되는 걸 볼 수 있습니다.

한번 curl로 서비스의 전체 FQDN으로 호출 해 보십시오.

[root@osboxes temp]# k exec curl -it -- sh
/ $ curl http://member.ott.svc.cluster.local:3001/members/ondal
ondal => 온달,남자,hiondal@gmail.com/ $


또 파드 주소로도 호출해 보십시오.

/ $ curl http://member-0.member.ott.svc.cluster.local:3001/members/ondal
ondal => 온달,남자,hiondal@gmail.com/ $
/ $ curl http://member-1.member.ott.svc.cluster.local:3001/members/ondal
ondal => 온달,남자,hiondal@gmail.com/ $


대부분 서비스 오브젝트를 통해 같은 네임스페이스의 파드를 연결하기 때문에 FQDN으로 서비스명을 사용하는 경우가 많습니다.

8.3 목적별 서비스 유형

서비스 유형에는 아래와 같이 목적별로 총 4가지가 있습니다.
이번에는 각 서비스 유형별로 어떻게 서비스 오브젝트를 정의하는지 알아 보겠습니다.



1) 클러스터 내부 간 통신을 위한 ClusterIP 서비스
ClusterIP 서비스는 ❶ ‘type’을 ‘ClusterIP’라고 지정 합니다.
❷ 연결할 파드의 레이블을 지정 합니다. 이 레이블은 워크로드 컨트롤러의 파드 명세인 ‘spec.template.metadata’에 정의한 레이블과 동일해야 합니다.
❸ 서비스 오브젝트가 수신할 포트와 연결할 파드의 포트를 지정 합니다.
계속 말하지만 targetPort는 어플리케이션이 수신하는 포트이지 컨테이너의 포트가 아닙니다.
즉 ‘member’ 어플리케이션의 ‘server.port’에 지정한 포트를 지정해야 합니다. 워크로드 컨트롤러의 컨테이너 명세인 ‘spec.template.spec.containers.ports’에 지정한 ‘containerPort’는 컨테이너 간 통신에 쓰이는 포트이지 서비스 오브젝트가 연결하는 대상 포트가 아닙니다.

서비스 오브젝트를 생성하면 서비스에 가상IP가 부여되고 CoreDNS에 FQDN이 등록 됩니다.

2) 클러스터 외부와 내부 통신을 위한 NodePort 서비스
NodePort는 클러스터 외부와 내부를 연결해 주는 서비스입니다.
정의하는 방법은 ‘type’을 ‘NodePort’로 변경만 해주면 됩니다.
‘member’서비스를 ‘NodePort’서비스로 바꿔 보겠습니다.
일단 ‘ClusterIP’ 서비스 오브젝트 야믈 파일을 다운로드 하십시오.

[root@osboxes ~]# cd ~/k8s/yaml
[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.4/svc-member.yaml


현재 ‘member’서비스 오브젝트의 정보를 확인 하십시오.

[root@osboxes yaml]# k get svc
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
member      ClusterIP   10.109.187.33   <none>        3001/TCP   5d17h
recommend   ClusterIP   10.96.215.56    <none>        3002/TCP   32h


‘svc-member.yaml’파일을 열고 ‘type’을 ‘NodePort’로 변경하시고 배포하십시오.

apiVersion: v1
kind: Service
metadata:
  name: member
  namespace: ott
spec:
  type: NodePort
  selector:
    app: member
  ports:
  - name: port1
    port: 3001
    targetPort: 3001
  sessionAffinity: ClientIP

 

[root@osboxes yaml]# k apply -f svc-member.yaml
service/member configured
[root@osboxes yaml]# k get svc
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
member      NodePort    10.109.187.33   <none>        3001:30414/TCP   5d17h


뭐가 바뀌었나요 ? 네 ‘PORTS’컬럼을 보면 위 예처럼 ‘30414’라는 포트가 추가 되었습니다.
아마 여러분은 다른 숫자가 나올 겁니다.
이 포트가 클러스터 외부에 노출된 포트입니다.
이제 인그레스 오브젝트 없이도 클러스터 외부에서 서비스 오브젝트를 접근할 수가 있습니다.
당연히 포트는 클러스터 외부에 노출된 포트로 해야 하고 FQDN은 클러스터를 구성하는 아무 노드의 IP로 하시면 됩니다.
브라우저를 열고 http://{클러스터 노드 중 하나의 IP}:{서비스 외부 포트}/members/user1을 접근해 보십시오.

클러스터 외부에서 서비스 외부포트로 접근하면 그 포트에 바인딩 된 서비스 내부포트로 연결이 되는 방식 입니다.

‘어 ! 그럼 인그레스 만들지 말고 NodePort 서비스 만들면 되겠네요' 라고 생각하시는 분들이 있을 겁니다.
맞는 말이긴 한데 NodePort 서비스는 한 클러스터에 만들 수 있는 숫자 제한이 있습니다. ‘30000’번에서 ‘32767’번까지만 만들 수 있습니다.
뭐 그 정도면 충분할 수 있다고 생각할 수 있는데 다른 문제가 있습니다.
서비스 오브젝트를 삭제 했다가 다시 만들면 외부 노출 포트가 변합니다. 한번 ‘member’서비스를 삭제했다가 다시 만들어 보십시오. 외부 노출 포트 번호가 바뀌었을 겁니다.
이 외부 포트 번호를 고정 시킬 수는 있습니다.
‘svc-member.yaml’파일을 열고 ‘spec.ports’에 ‘nodePort: 30001’이라고 추가하고 서비스를 다시 만들어 보십시오.

...
spec:
  type: NodePort
  selector:
    app: member
  ports:
  - name: port1
    port: 3001
    targetPort: 3001
    nodePort: 30001

 

[root@osboxes yaml]# k delete -f svc-member.yaml
service "member" deleted
[root@osboxes yaml]# k apply -f svc-member.yaml
service/member created
[root@osboxes yaml]# k get svc
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
member      NodePort    10.104.189.73   <none>        3001:30001/TCP   3s

<팁>
쿠버네티스 오브젝트를 지울 때 오브젝트를 정의한 파일을 이용해 지울 수도 있습니다.
예) kubectl delete -f svc-member.yaml
</>
위와 같이 외부 노출 포트가 ‘30001’번이 되고 이 외부 포트는 서비스를 다시 만들어도 계속 유지 됩니다.
하지만 포트가 충돌 안나게 관리해야 하는 번거로움은 있습니다.

그렇다면 NodePort 서비스를 사용하는 경우는 언제일까요 ?
가장 대표적인 것은 데이터베이스 파드를 외부에서 접근해야 하는 경우입니다.
데이터베이스 접근은 HTTP가 아닌 TCP를 사용하기 때문에 인그레스로 해결이 안됩니다.
이때는 NodePort 서비스를 만들고 외부 포트를 고정해서 사용하시면 됩니다.

또 하나 별도의 웹서버를 세우고 웹서버에서 NodePort 서비스를 프락싱하는 아키텍처를 구현할때도 유용하게 사용됩니다. 이 방법은 인그레스편에서 더 자세하게 설명하겠습니다.

3) 클라우드 벤더의 로드밸런서 이용을 위한 LoadBalancer 서비스
LoadBalancer 서비스는 AWS의 EKS, 애저Azure의 AKS, 구글의 GKS, IBM의 IKS 등과 같은 클라우드 벤더가 제공하는 쿠버네티스 서비스를 사용할 때 그 클라우드 벤더의 로드밸런서를 사용하기 위해 만듭니다.
LoadBalancer 서비스를 만들면 자동으로 클라우드 벤더의 로드밸런서가 연결되어 서비스 오브젝트를 접근할 때 그 로드밸런서를 통해 파드가 연결 됩니다.
정의하는 방법은 ‘type’을 ‘LoadBalancer’하시고 클라우드 벤더에서 가이드하는 추가 설정을 하시면 됩니다.
바닐라 쿠버네티스에서는 NodePort 서비스와 동일하게 동작 합니다.

4) 클러스터 외부로의 프락시 역할을 하는 ExternalName 서비스
ExternalName은 클러스터 외부로 트래픽을 프락시Proxy 해주는 특수한 서비스 유형입니다.
그림으로 표현하면 아래와 같습니다.


대표적인 활용 케이스 하나를 들어 설명 하겠습니다.
여러분은 ‘OTT컨텐츠 정보 제공'을 기존에는 직접 개발한 어플리케이션을 통해 하고 있었습니다.
업데이트도 느리고 정보가 부정확한 경우도 많아서 외부의 유료 서비스를 사용하기로 결정했습니다.
요청 어플리케이션의 소스를 하나도 수정 안하고 할 방법이 있을까요 ?
기존에 ‘OTT컨텐츠 정보 제공' 파드로 연결해 주던 ClusterIP 서비스를 ExternalName 서비스로 바꾸시면 됩니다.

또 다른 활용 상황을 예제로 ExternalName 서비스를 만들어 보겠습니다.
여러분이 만든 ‘recommend’ 서비스는 서울 클러스터에서 제공 되고 있다고 가정하고 부산 클러스터에서 ‘recommend’서비스를 ExternalName서비스를 통해 이용하는 방법입니다.
물론 서울 클러스터에 있는 인그레스 오브젝트를 통해 부산 클러스터의 파드에서 ‘recommend’서비스를 접근할 수 있습니다. 하지만 두가지 이유로 ExternalName이 더 좋은 선택일 수 있습니다.
첫째, 부산 클러스터에 자체 ‘recommend’서비스가 만들어진다면 요청 어플리케이션을 수정하지 않고 쉽게 부산 클러스터의 ‘recommend’서비스로 바꿀 수 있습니다.
둘째, 서울 클러스터 ‘recommend’서비스의 변화가 생기면 각 외부 클러스터에서는 ExternalName 서비스만 바꾸면 됩니다. 예를 들어 ‘recommend’서비스를 더 이상 자체 운영하지 않고 클라우드 SaaS서비스로 바꾼다면 부산 클러스터의 ExternalName서비스에서 ‘externalName’만 바꾸면 됩니다.

부산 클러스터에 ExternalName 서비스 ‘recommend-ext’를 만듭니다.
클러스터가 하나 밖에 없으므로 현재 클러스터를 부산 클러스터라고 생각하고 만듭니다.

[root@osboxes yaml]# cd ~/k8s/yaml
[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.8/svc-recommend-ext.yaml


‘svc-recommend-ext.yaml’내용을 봅시다.
ExternalName 서비스의 ❶ type은 ‘ExternalName’입니다.
그리고 ❷ 프락시 할 대상 서버의 FQDN을 ‘externalName’에 지정 합니다. DNS서버가 없으므로 와일드카드 DNS형식으로 지정 합니다.
파드를 로드 밸런싱하는게 아니므로 ‘selector’도 없고 ‘ports’도 없습니다.

<주의>
externalName에 지정한 FQDN의 IP는 반드시 본인 워커노드의 퍼블릭 IP로 변경 하고 서비스 오브젝트를 생성하십시오.
</>

서울 클러스터의 ‘recommend’서비스를 NodePort서비스로 변경 합니다. 이때 외부 포트는 ‘30002’번으로 고정 합니다.

[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.8/svc-recommend.yaml
[root@osboxes yaml]# k delete svc recommend
[root@osboxes yaml]# k apply -f svc-recommend.yaml
[root@osboxes yaml]# k get svc
NAME            TYPE   …      EXTERNAL-IP                    PORT(S)  …

recommend       NodePort       <none>                         3002:30002/TCP …
recommend-ext   ExternalName   bestott.169.56.70.197.nip.io   <none>


위와 같이 부산 클러스터의 ExternalName 서비스 ‘recommend-ext’와 서울 클러스터의 NodePort 서비스 ‘recommend’가 준비 되었습니다.
이제 부산 클러스터의 ‘curl’파드에서 ‘recommend-ext’를 통해 서울 클러스터의 ‘recommend’서비스를 접근해 봅시다.
먼저 ‘recommend-ext’의 IP를 찾아 봅시다.
제 경우에는 아래와 같이 ‘externalName’에 지정한 ‘bestott.169.56.70.197.nip.io’으로 연결이 되고 그 FQDN의 IP인 169.56.70.197을 리턴 합니다. 여러분은 당연히 결과가 다를 겁니다.

[root@osboxes yaml]# k exec curl -it -- sh
/ $ nslookup recommend-ext
...
recommend-ext.ott.svc.cluster.local    canonical name = bestott.169.56.70.197.nip.io
Name:    bestott.169.56.70.197.nip.io
Address: 169.56.70.197


이제 ‘recommend-ext’로 ‘recommend’의 API를 호출해 봅시다.
아래 예제와 같이 결과가 잘 나올 겁니다.

/ $ curl http://recommend-ext:30002/bestott/user1
user1 => Disney+,https://www.disneyplus.comm/


FQDN인 ‘recommend-ext’는 여러분의 워커 노드 IP로 프락시 됩니다. 따라서 위 요청은 ‘http://{워커노드 IP}:30002/bestott/user1’으로 프락싱 되어 처리 됩니다.
‘recommend’ 서비스가 ‘30002’번으로 외부에 노출되어 있으므로 정상적으로 동작하게 됩니다.

이상으로 목적별 네가지 서비스 유형을 살펴 봤습니다.
이제 그 외 알면 유용한 내용을 마지막으로 학습하도록 하겠습니다.



8.4 그 외 유용한 내용들

서비스 리소스와 관련하여 나머지 유용한 내용들은 List Connection방식 로드밸런싱, 헤드리스 서비스, 클러스터 외부에 IP 노출하기, 엔드포인트EndPoint 리소스입니다.

1) List Connection 방식 로드밸런싱
서비스 리소스의 기본 로드밸런싱 방식은 라운드로빈RoundRobin 방식입니다. 라운드 로빈 방식은 요청이 올때마다 파드를 돌아가면서 연결해 주는 방식입니다.
하지만 어떤 요청자 어플리케이션은 한번 연결된 제공자 어플리케이션으로 계속 연결되어야 하는 경우가 있습니다.
이렇게 한번 붙은 서버로 계속 로드 밸런싱 하게 하는 방식을 List Connection이라고 합니다.
쿠버네티스 서비스 리소스는 클라이언트 파드의 IP를 이용하여 계속 동일한 서버 파드로 연결되게 하는 방법을 제공 합니다.
서비스 오브젝트 정의시 ‘spec.sessionAffinity: ClientIP’항목을 추가하시면 됩니다.
서비스 ‘member’의 정의에 이 항목을 추가하고 테스트 해 보겠습니다.

먼저 현재 라운드로빈 방식으로 연결되는걸 테스트 합니다.
아래와 같이 항상 달라지지는 않지만 연결되는 ‘member’ 파드가 달라 집니다.

[root@osboxes yaml]# k exec curl -it -- sh



/ $ curl http://member:3001/hostname
Hostname=>member-1/ $
/ $ curl http://member:3001/hostname
Hostname=>member-2/ $
/ $ curl http://member:3001/hostname
Hostname=>member-2/ $
/ $ curl http://member:3001/hostname
Hostname=>member-0/ $


‘svc-member.yaml’파일을 열고 ‘spec.sessionAffinity: ClientIP’를 추가하고 적용 합니다.

apiVersion: v1
kind: Service
metadata:
  name: member
  namespace: ott
spec:
  type: ClusterIP
  selector:
    app: member
  ports:
  - name: port1
    port: 3001
    targetPort: 3001
  sessionAffinity: ClientIP

 

[root@osboxes yaml]# k apply -f svc-member.yaml
service/member configured
[root@osboxes yaml]# k exec curl -it -- sh
/ $ curl http://member:3001/hostname
Hostname=>member-2/ $
/ $ curl http://member:3001/hostname
Hostname=>member-2/ $
/ $ curl http://member:3001/hostname
Hostname=>member-2


계속 동일한 파드로 로드밸런싱 되는것을 확인할 수 있습니다.


2) 헤드리스Headless 서비스 헤드리스 서비스는 IP가 없는 특수한 ClusterIP 서비스입니다.
‘Headless’에서 ‘Head’는 ‘머리'라는 뜻이 아니라 ‘특정 방향으로 향하다'라는 의미입니다.
즉 헤드리스 서비스는 IP가 없기 때문에 ‘향할 방향이 없는 서비스'라고 이해할 수 있습니다.

만드는 방법은 간단합니다.
ClusterIP 서비스 정의와 똑같이 하고 ‘clusterIP: None’만 추가하면 됩니다.
한번 만들어 볼까요 ?

[root@osboxes yaml]# cd ~/k8s/yaml
[root@osboxes yaml]# wget https://hiondal.github.io/k8s-yaml/3.8/svc-member-headless.yaml


야플 파일 내용을 보시면 ‘clusterIP: None’이 추가된 걸 알 수 있습니다.

apiVersion: v1
kind: Service
metadata:
  name: member-headless
  namespace: ott
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: member
  ports:
  - name: port1
    port: 3001
    targetPort: 3001


‘member-headless’서비스를 배포하고 그 결과를 확인해 보십시오.
아래와 같이 ‘CLUSTER-IP’에 ‘None’으로 나올 겁니다. IP가 없는 서비스라는 의미 입니다.

[root@osboxes yaml]# k apply -f svc-member-headless.yaml
service/member-headless created
[root@osboxes yaml]# k get svc
NAME              TYPE           CLUSTER-IP   …   PORT(S) … 

member            ClusterIP      10.108.203.129   3001/TCP …
member-headless   ClusterIP      None             3001/TCP …


왜 헤드리스 서비스를 만드는 걸까요 ?
서비스의 IP가 필요 없는 경우겠죠. 어떨때 서비스의 IP가 필요 없을까요 ?
헤드리스 서비스를 만드는 경우는 두가지입니다.

첫째, 스테이트풀셋으로 데이터베이스 파드를 생성해서 로드밸런싱이 불필요한 경우
둘째, 서비스 오브젝트를 이용하여 스테이트풀셋 파드의 주소를 얻어 다른 방법으로 로드밸런싱을 할 경우

한마디로 서비스 오브젝트를 이용한 로드밸런싱이 필요 없는 경우입니다.
예를 들어 두가지 경우를 좀 더 확실하게 설명하겠습니다.

첫번째 경우의 예는 파드 오피니티와 파드 안티 오피니티를 이용하여 아래와 같이 어플리케이션 파드와 레디스 파드를 스테이트풀셋으로 배포한 경우입니다.
이때는 각 ‘member’파드는 자신의 노드에 배포된 각 ‘redis’파드를 직접 접근하는게 좋습니다. 왜냐하면 다른 노드의 ‘redis’파드를 접근하면 불필요하게 네트워크를 타야 하니까요.
예를 들어 ‘member-0’파드에서는 아래와 같은 연결 문자열로 ‘redis-0’파드를 직접 연결하면 됩니다.
Server=redis-0.{redis 헤드리스 서비스명}.{네임스페이스}.svc.cluster.local;Database={DB명};User Id={유저명} Password={암호}


두번째 경우의 예는 스프링클라우드 로드밸런서같은 별도의 로드 밸런서를 이용하여 파드를 연결하는 경우입니다.
아래와 같이 헤드리스 서비스는 파드 로드 밸런싱이 아닌 파드의 주소를 제공하는 역할만 합니다.

서비스 ‘member’와 헤드리스 서비스 ‘member-headless’의 차이를 nslookup으로 확인해 보겠습니다.
아래와 같이 ‘member’서비스는 로드밸런싱된 파드의 FQDN과 IP를 리턴하지만 ‘member-headless’서비스는 각 파드의 FQDN과 IP를 리턴 합니다.

[root@osboxes yaml]# k exec curl -it -- sh
/ $ nslookup member
...
Name:    member.ott.svc.cluster.local
Address: 10.108.203.129

/ $ nslookup member-headless
...
Name:    member-headless.ott.svc.cluster.local
Address: 192.168.235.166
Name:    member-headless.ott.svc.cluster.local
Address: 192.168.235.139
Name:    member-headless.ott.svc.cluster.local
Address: 192.168.235.156


<팁>
첫번째 경우는 헤드리스 서비스로 만들지 않고 ClusterIP 서비스를 만들어도 됩니다.
서버 파드 연결할 때 각 파드의 FQDN으로 직접 연결이 가능하면 되기 때문입니다.
다만 굳이 필요 없이 서비스에 IP를 만들지 않기 위해 헤드리스로 만드는 겁니다
두번째 경우는 꼭 헤드리스 서비스를 만들어야 서비스 오브젝트를 통해 파드들의 FQDN과 IP를 구할 수 있습니다.
</>

3) 클러스터 외부에 IP 노출 하기
NodePort 서비스는 클러스터 외부에 포트를 노출하여 외부에서의 접근을 허용하였습니다.
ClusterIP 서비스도 클러스터 외부에 IP를 노출하여 외부에서 접근하게 할 수 있습니다.
ClusterIP 서비스인 ‘member’의 IP를 노출하여 외부에서 접근해 보겠습니다.
배천노드의 ‘~/k8s/yaml/svc-member.yaml’파일을 열고 아래 ‘externalIPs’항목과 여러분의 워커노드 IP를 추가 하십시오.

apiVersion: v1
kind: Service
metadata:
  name: member
  namespace: ott
spec:
  type: ClusterIP
  selector:
    app: member
  externalIPs:
  - 169.56.70.197
  ports:
  - name: port1
    port: 3001
    targetPort: 3001

<팁>
External IP는 여러개 추가할 수 있습니다.
</>

변경 사항을 적용하고 ‘member’서비스를 보면 ‘EXTERNAL-IP’가 적용된 것을 볼 수 있습니다.

[root@osboxes yaml]# k apply -f svc-member.yaml
service/member configured
[root@osboxes yaml]# k get svc member
NAME     TYPE        CLUSTER-IP       EXTERNAL-IP     PORT(S)    AGE
member   ClusterIP   10.108.203.129   169.56.70.197   3001/TCP   85m


이제 아래와 같이 웹브라우저에서 ‘{External IP}:3001’로 접근할 수 있습니다.

컨트롤 플레인의 IP로도 될까요 ? 한번 해 보십시오.
컨트롤 플레인의 IP는 외부에 노출 시키지 않았기 때문에 접근이 되지 않습니다.

야믈 파일을 다시 열고 컨트롤 플레인의 IP를 External IP로 추가하고 적용한 후 다시 해 보십시오.
이젠 당연히 될 겁니다.


4) 엔드포인트EndPoint 리소스
엔드포인트 리소스는 파드의 IP와 포트 정보를 갖고 있는 리소스입니다.
‘End Point’라는 말은 우리말로 ‘접점'이라고 번역 되는데 어떤 것을 접근할 때 제일 앞단의 접점을 의미 합니다.
예를 들어 쿠버네티스 클러스터의 엔드 포인트는 API Server입니다.

서비스 리소스가 파드를 로드 밸런싱 하려면 각 파드들의 IP와 포트를 알아야 합니다. 이걸 제공해 주는 것이 엔드포인트 리소스입니다. 파드 연결을 위한 접점 정보를 갖고 있다는 뜻에서 엔드포인트 리소스라고 합니다.

여러분이 서비스 오브젝트를 만들면 자동으로 서비스 오브젝트의 ‘selector’에 정의된 파드들의 IP와 포트를 수집하여 엔트포인트 오브젝트가 만들어집니다. 그래서 서비스 오브젝트로 요청이 오면 서비스 오브젝트는 엔트포인트 오브젝트에서 연결할 파드들의 IP를 얻고 로드밸런싱한 파드로 연결해 주는 겁니다.
<팁>
엔트포인트를 자동으로 만들어주는 쿠버네티스 컴포넌트는 Controller Manager입니다.
</>

진짜 엔드포인트 오브젝트가 각 서비스에 대해 만들어졌는지 확인해 보겠습니다.
아래와 같이 엔트포인트를 통해 각 파드의 IP와 포트를 확인할 수 있습니다.

[root@osboxes yaml]# k get ep
NAME              ENDPOINTS                              
member            192.168.235.139:3001,192.168.235.156:3001,192.168.235.166:3001
member-headless   192.168.235.139:3001,192.168.235.156:3001,192.168.235.166:3001
recommend         192.168.235.144:3002      


이 엔드포인트를 없애면 어떻게 될까요 ? 파드 주소 정보를 모르니 당연히 연결이 안될까요 ?
‘member’ 엔드포인트를 삭제해 보겠습니다.
다시 생성이 됩니다. 컨트롤 플레인의 Controller Manager가 자동으로 다시 만들어 주는 겁니다.

[root@osboxes yaml]# k delete ep member
endpoints "member" deleted
[root@osboxes yaml]# k get ep member
NAME              ENDPOINTS                                             
member            192.168.235.139:3001,192.168.235.156:3001,192.168.235.166:3001


엔드포인트를 삭제 하지는 못하지만 재정의할 수는 있습니다.
‘member’ 엔드포인트를 편집하여 ‘member-0’만 남기고 나머지를 지워 보십시오.
아래와 같이 member-0에 대한 IP와 포트만 나오게 하시면 됩니다.

[root@osboxes yaml]# k edit ep member

...
subsets:
- addresses:
  - hostname: member-0
    ip: 192.168.235.156
    nodeName: worker1
    targetRef:
      kind: Pod
      name: member-0
      namespace: ott
      resourceVersion: "3025501"
      uid: a4e900ae-c7e9-4516-a74c-432fb6f34236


[root@osboxes yaml]# k get ep member
NAME     ENDPOINTS              AGE
member   192.168.235.156:3001   2m46s


curl로 정말 member-0 파드로만 연결되는지 테스트 해 보겠습니다.
이상하게 안되네요. 여전히 다른 파드로도 연결이 됩니다.

[root@osboxes yaml]# k exec curl -it -- sh
/ $ curl http://member:3001/hostname
Hostname=>member-0/ $
/ $ curl http://member:3001/hostname
Hostname=>member-2/ $
/ $ curl http://member:3001/hostname
Hostname=>member-2/ $
/ $ curl http://member:3001/hostname
Hostname=>member-1/ $


원하는대로 하려면 ‘member’서비스를 헤드리스 서비스로 만들고 ‘selector’도 지우셔야 합니다.
아래와 같이 ‘svc-member.yaml’파일을 수정 하십시오. ‘externalIPs’도 지우십시오.

apiVersion: v1
kind: Service
metadata:
  name: member
  namespace: ott
spec:
  type: ClusterIP
  clusterIP: None
  ports:
  - name: port1
    port: 3001
    targetPort: 3001


기존 ‘member’서비스를 지우고 다시 생성 하십시오.

[root@osboxes yaml]# k delete svc member
service "member" deleted
[root@osboxes yaml]# k apply -f svc-member.yaml
service/member created

엔드포인트를 확인해 보면 ‘member’ 엔드포인트가 없습니다.
왜냐하면 ‘selector’가 없어서 대상 파드를 못 찾기 때문입니다.

[root@osboxes yaml]# k get ep
NAME              ENDPOINTS                                                        AGE
member-headless   192.168.235.139:3001,192.168.235.156:3001,192.168.235.166:3001   150m
recommend         192.168.235.144:3002                                             3h31m


엔드포인트 ‘member’를 수동으로 만들기 위해 ‘member-0’파드의 IP를 먼저 구하십시오.

[root@osboxes yaml]# k get po member-0 -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP  …
member-0   1/1     Running   0          36h   192.168.235.156


엔드포인트 ‘member’ 야믈 파일을 위에서 구한 ‘member-0’파드의 IP를 추가하여 아래와 같이 만드십시오.

apiVersion: v1
kind: Endpoints
metadata:
  name: member
  namespace: ott
subsets:
- addresses:
  - ip: 192.168.235.156
  ports:
  - port: 3001

<주의>
엔드포인트 오브젝트의 이름은 반드시 서비스 오브젝트의 이름과 같아야 합니다.
</>

엔드 포인트 야믈을 적용하고 오브젝트가 잘 생성되었는지 확인 합니다.

[root@osboxes yaml]# k apply -f ep-member.yaml
endpoints/member created
[root@osboxes yaml]# k get ep
NAME              ENDPOINTS      …
member            192.168.235.156:3001


이제 다시 curl로 ‘member-0’파드로만 연결되는지 확인 합니다.
드디어 원하는대로 한 파드로만 연결이 됩니다.

[root@osboxes yaml]# k exec curl -it -- sh
/ $ curl http://member:3001/hostname
Hostname=>member-0/ $
/ $ curl http://member:3001/hostname
Hostname=>member-0/ $
/ $ curl http://member:3001/hostname
Hostname=>member-0/


이와 같이 엔드포인트를 이용하면 특정 파드로만 강제로 연결하게 할 수 있습니다.
여러분이 기존 서비스는 건드리지 않고 테스트를 위해 특정 파드로만 연결하고 싶다면 ‘selector’가 없는 헤드리스 서비스를 만든 후 연결할 파드 IP와 포트만 가지고 있는 엔드 포인트를 만들면 됩니다.

이걸 조금 더 응용하면 ExternalName 서비스를 대체할 수도 있습니다.
ExternalName서비스는 FQDN이 필요한데 그 대신에 프락시할 대상 서버의 IP를 이용하고 싶다면 엔드포인트를 이용할 수 있습니다.
‘recommend’서비스를 엔드 포인트를 이용하여 프락싱 해 보겠습니다.
먼저 ‘selector’가 없는 새로운 헤드리스 서비스를 만들겠습니다.
‘svc-recommend-proxy.yaml’파일을 아래 내용으로 작성하여 배포해 주십시오.

apiVersion: v1
kind: Service
metadata:
  name: recommend-proxy
  namespace: ott
spec:

  type: ClusterIP
  clusterIP: None
  ports:
  - port: 80

 

[root@osboxes yaml]# k apply -f svc-recommend-proxy.yaml
service/recommend-proxy created

[root@osboxes yaml]# k get svc recommend-proxy
NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
recommend-proxy   ClusterIP   None         <none>        80/TCP    85s
[root@osboxes yaml]# k get ep recommend-proxy
Error from server (NotFound): endpoints "recommend-proxy" not found


당연히 ‘recommend-proxy’ 엔드 포인트는 자동으로 만들어 지지 않습니다.
‘recommend-proxy’서버의 엔드 포인트를 만듭니다.
‘ep-recommend-proxy.yaml’파일을 아래 내용으로 만들고 배포 하십시오.
헤드리스 서비스 ‘recommend-proxy’를 IP ‘169.56.70.201’로 프락싱 하는 설정입니다.
프락시할 IP는 여러분의 워커 노드나 컨트롤 플레인 IP로 반드시 바꾸셔야 합니다.

apiVersion: v1
kind: Endpoints
metadata:
  name: recommend-proxy
subsets:
- addresses:
  - ip: 169.56.70.201

 

[root@osboxes yaml]# k apply -f ep-recommend-proxy.yaml
endpoints/recommend-proxy created
[root@osboxes yaml]# k get ep recommend-proxy
NAME              ENDPOINTS       AGE
recommend-proxy   169.56.70.201   27s


이제 ‘curl’파드에서 nslookup으로 recommend-proxy 서비스의 IP를 찾아 보십시오.
아래와 같이 엔드포인트에서 지정한 IP로 연결이 됩니다.

[root@osboxes yaml]# k exec curl -it -- sh
/ $ nslookup recommend-proxy
...
Name:    recommend-proxy.ott.svc.cluster.local
Address: 169.56.70.201


이제 헤드리스 서비스 ‘recommend-proxy’로 서비스 ‘recommend’의 API를 호출해 봅니다.
이때 포트는 ‘recommend’서비스에서 외부로 노출한 ‘30002’번을 사용해야 합니다.

/ $ curl http://recommend-proxy:30002/bestott/user1
user1 => Disney+,https://www.disneyplus.comm


잘 되시죠 ? 이렇게 ExternalName서비스 대신에 헤드리스 서비스와 엔드포인트를 이용하여 클러스터 외부로 프락싱할 수 있습니다.

 

쿠버네티스 쉽게 이해하기 시리즈 목차

[쿠버네티스 쉽게 이해하기 1] 쿠버네티스 설치하기
[쿠버네티스 쉽게 이해하기 2] 쿠버네티스 아키텍처
[쿠버네티스 쉽게 이해하기 3] 한장으로 이해하는 쿠버네티스 리소스
[쿠버네티스 쉽게 이해하기 4] 쿠버네티스 개발에서 배포까지 실습
[쿠버네티스 쉽게 이해하기 5] 쿠버네티스 오브젝트 정의 파일 쉽게 만들기
[쿠버네티스 쉽게 이해하기 6] 꼭 알아야 할 쿠버네티스 주요 명령어
[쿠버네티스 쉽게 이해하기 7] 파드 실행 및 통제를 위한 워크로드 컨트롤러
[쿠버네티스 쉽게 이해하기 8] 파드 로드 밸런서 서비스
[쿠버네티스 쉽게 이해하기 9] 서비스 로드 밸런서 인그레스
[쿠버네티스 쉽게 이해하기 10] 환경변수 컨피그맵과 시크릿
[쿠버네티스 쉽게 이해하기 11] 데이터 저장소 사용을 위한 PV/PVC
[쿠버네티스 쉽게 이해하기 12] 헬스 체크를 위한 스타트업 프로브, 라이브니스 프로브, 레디니스 프로브
[쿠버네티스 쉽게 이해하기 13] 통합 로깅을 위한 EFK 스택
[쿠버네티스 쉽게 이해하기 14] 인증Authentication과 알백RBAC 방식의 인가Authorization
[쿠버네티스 쉽게 이해하기 15] 더 알면 좋을 주제들: 무중단 배포, 모니터링, HPA

댓글

전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]