HomeAbout
[Kubernetes] Kubernetes에서 Service Type 이해하기: 실전 예제로 ClusterIP부터 LoadBalancer까지
Docker & Kubernetes
[Kubernetes] Kubernetes에서 Service Type 이해하기: 실전 예제로 ClusterIP부터 LoadBalancer까지
NASA1515
NASA1515
June 24, 2021
3 min

목차

01
✔ Service
02
👍 Service session-affinity?
03
🐱‍🏍 Service Type

✔ Service

  • 이전에 Kubernetes Cluster안에 Controller들을 이용해서 POD를 정의했습니다.
    POD 특성상 생성 및 정의 될때 지정되는 IP가 랜덤하고 또한 리스타트 때마다 IP가 변동됩니다. 두개의 이유로 POD는 고정된 EndPoint로 호출이 어렵습니다.
    또한 여러 POD에 같은 Application을 운용할 경우 이 POD 간의 LoadBalancing을 지원해줘야 하는데 이러한 기능들을 수행하는게 Service(Service) 입니다.

간략한 Service들의 기능을 요약해보면 아래 4가지 정도입니다.

  • Service를 사용하게 되면 고정된 주소를 이용해서 접근이 가능해 집니다.
  • Service를 통해 Cluster 외부에서 POD에 접근하는것도 가능합니다.
  • 여러 POD를 묶어 로드 밸런싱이 가능합니다.
  • 고유한 DNS 이름을 가질 수 있습니다.

Service는 get service 명령을 통해 목록을 받아 올 수 있습니다

[root@nasa-master nasa]# kubectl get service
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   24d
  • 명령을 입력하면 default NameSpace에 항상 존재하는 Service가 보이네요, Master Node의 API로 접근하기 위한 Service입니다!!


Service 템플릿

  • Service는 다음과 같이 구성이 가능하며, 라벨 셀렉터(label selector)를 이용하여 관리하고자 하는 Pod 들을 정의할 수 있습니다.

Service 템플릿 기본 구조는 다음과 같습니다.

apiVersion: v1
kind: Service
metadata:
  name: hello-nasa-svc
spec:
  type: ClusterIP (LoadBalancer)
  clusterIP: 10.0.10.10
  selector:
    app: hello-nasa
  ports:
    - port: 80
    protocol: TCP
    targetPort: 8080

다른 부분은 일반적인 형태입니다

  • spec.type : Service 타입을 지정할수 있습니다. spec.type을 지정하지 않으면 기본 타입은 ClusterIP입니다.
  • spec.clusterIP : 사용하려는 ClusterIP를 직접 지정하는것도 가능합니다.
  • spec.selector : Service와 연결할 POD에 지정된 라벨을 지정합니다.
  • spec.ports : 배열 형태의 값입니다. Service가 포트를 외부에 제공 시 하나가 아니라 여러 개를 한번에 제공 할 수 있는데 spec.ports 하위에 값을 넣어주면 됩니다.

이런 형태의 멀티 포트 Service가 가능합니다

  • 예를 들어 웹서버의 HTTP와 HTTPS 포트가 대표적인 예인데 아래와 같이 ports 부분에 두개의 포트 정보를 정의해주면 됩니다.
apiVersion: v1
kind: Service
metadata:
  name: nasa-node-svc
spec:
  selector:
     app: nasa-node
  ports:
     - name: http
     port: 80
     protocol: TCP
     targetPort: 8080
     - name: https
     port: 443
     protocol: TCP
     targetPort: 8082

위의 템플릿으로 생성을 해보면 아래와 같이 멀티 포트로 생성이 됩니다

[root@nasa-master nasa]# kubectl apply -f nasa-svcm.yml 
service/nasa-node-svc created
[root@nasa-master nasa]# 
[root@nasa-master nasa]# kubectl get svc
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP          24d
nasa-node-svc   ClusterIP   10.96.147.197   <none>        80/TCP,443/TCP   8s


EndPoint

  • EndPoint란 Service의 Label Selector에 의해 연결된 POD의 IP 목록입니다. kube get endpoints 명령어로 확인 할 수 있습니다
[root@nasa-master nasa]# kubectl get endpoints
NAME            ENDPOINTS         AGE
kubernetes      10.146.0.6:6443   24d
nasa-node-svc   <none>            7m30s
  • 방금 만든 Service의 경우 연결되어있는 POD가 없기에 none으로 정의 되어있습니다.

label을 맞춰준 POD를 하나 생성해봅시다!

apiVersion: v1
kind: Pod
metadata:
name: nasa
labels: 
    app: nasa-node
spec:
containers:
    - name: nasa
    image: nginx:latest
    ports:
        - containerPort: 8080
        protocol: TCP
[root@nasa-master nasa]# kubectl apply -f nasa.pod-s.yml 
pod/nasa created
[root@nasa-master nasa]# kubectl get po -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP          NODE         NOMINATED NODE   READINESS G
ATES
nasa   1/1     Running   0          97s   10.32.0.2   nasa-node3   <none>           <none>

이렇게 label을 연결해준 POD가 생성되면 ENDPOINT가 생성됩니다.

[root@nasa-master nasa]# kubectl get endpoints
NAME            ENDPOINTS                       AGE
kubernetes      10.146.0.6:6443                 24d
nasa-node-svc   10.32.0.2:8082,10.32.0.2:8080   17m

Test POD를 하나 돌려서 Cluster끼리의 통신을 확인해봅시다!

[root@nasa-master nasa]# kubectl run nasatest -it --image=c1t1d0s7/network-multitool --generator=run-pod/v1 --rm=true bash
If you don't see a command prompt, try pressing enter.
bash-5.0# 
bash-5.0# curl http://10.32.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
  • 통신이 원활한 것을 확인 할 수 있습니다.

👍 Service session-affinity?

  • 위의 테스트처럼 1개의 POD가 아닌 [RS,RC,DS]처럼 여러개의 POD가 생성되면 당연히 LB로 POD를 묶어 여러개의 ENDPOINT를 가지고 있게 될 것이다
    이 경우 Client에서 요청을 보내면 요청은 LoadBalancing되어 매번 다른 파드로 연결된다.
    그러나 만약 특정 Client에서 요청이 들어오면 매번 특정 파드로 연결하고 싶은 경우 사용하는 것이 세션 어피니티입니다
apiVersion: v1
kind: Service
metadata:
name: mynapp-svc-ses-aff
spec:
sessionAffinity: ClientIP
ports:
- port: 80
    targetPort: 8080
selector:
    app: mynapp-rs
  • 세션 어피니티 구성은 None과 ClientIP가 있으며 디폴트는 None입니다.
    ClientIP를 설정하면 Kubernetes Cluster의 프록시(kube-proxy)는 Client의 IP를 보고 매번 같은 파드로 연결해줍니다

sessionAffinity 로 정의 할 수 있다

  • none : (기본) 세션 어피니티 없음
  • ClientIP : Client의 IP를 확인해 같은 파드로 연결됨


🐱‍🏍 Service Type

  • Service는 IP 주소 할당 방식과 연동 Service등에 따라 크게 4가지로 구별할 수 있습니다.

Cluster IP

  • Default 설정으로, Service에 Cluster IP (내부 IP)를 할당합니다.
    Kubernetes Cluster 내에서는 이 Service에 접근이 가능하지만 Cluster 외부에서는 외부 IP를 할당 받지 못했기 때문에, 접근이 불가능합니다.

Load Balancer

  • 보통 클라우드 벤더에서 제공하는 설정 방식으로 외부 IP 를 가지고 있는 Loadbalancer 를 할당합니다.
    외부 IP를 가지고 있기 때문에, Cluster 외부에서 접근이 가능합니다.

Node IP

  • Cluster IP로만 접근이 가능한것이 아니라 모든 노드의 IP와 포트를 통해서도 접근이 가능하게 됩니다.
    예를 들어 아래와 같이 hello-node-svc 라는 Service를 NodePort 타입으로 선언을 하고 nodePort를 30036으로 설정하면
    아래 설정에 따라 Cluster IP의 80포트로도 접근이 가능하지만 모든 노드의 30036 포트로도 Service를 접근할 수 있습니다.

hello-node-svc-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
name: hello-node-svc
spec:
selector:
    app: hello-node
type: NodePort
ports:
    - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
    nodePort: 30036

그림의 로직을 보면 이해가 쉽습니다.

스크린샷, 2020-09-18 13-43-43


External name

  • ExternalName은 외부 Service를 Kubernetes 내부에서 호출할 때 사용 할 수 있습니다.
    Cluster내의 Pod들은 Cluster IP를 가지고 있기 때문에 IP 대역 밖의 Service를 호출하려면 NAT 설정 등 복잡한 설정이 필요합니다.
    특히 클라우드 환경을 사용할 경우 DB 또는 Cloud 제공 SaaS Service (RDS, CloudSQL)등을 사용 할 경우 Kubernetes Cluster 밖이기 때문에
    호출이 어려운 경우가 있는데 이를 쉽게 해결할 수 있는 방법이 ExternalName 타입입니다.

아래와 같이 Service를 ExternalName 타입으로 설정하고 주소를 DNS로 my.database.example.com으로 설정합니다.

  • 이 my-service는 들어오는 모든 요청을 my.database.example.com 으로 포워딩 해주게 됩니다. (일종의 Proxy와 같은 역할)
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com

다음과 같은 구조로 Service가 배포됩니다.

스크린샷, 2020-09-18 14-06-37

  • (DNS가 아닌 직접 IP를 이용하는 방식) 위의 경우 DNS를 이용하였는데, DNS가 아니라 직접 IP 주소를 이용하는 방법도 있습니다.

Service ClusterIP Service로 생성을 한 후 Service Name만 정의하고 Service에 속해있는 Pod를 지정하지 않습니다.

apiVersion: v1
kind: Service
metadata:
name: nasa-svc-ext
spec:
ports:
- port: 80

다음으로, 아래와 같이 Service의 EndPoint를 별도로 지정해주면 됩니다.

apiVersion: v1
kind: Endpoints
metadata:
name: nasa-svc-ext
subsets:
- addresses:
    - ip: 35.225.75.124
    ports:
    - port: 80
  • 이 때 Service명과 Service EndPoints의 이름이 동일해야 합니다.
    위의 경우에는 nasa-svc-ext로 같은 Service명을 사용하였고 이 Service는 35.225.75.124:80 Service를 가르키도록 되어 있습니다.


Headless Service

  • Service는 접근을 위해서 Cluster IP 또는 External IP 를 지정받습니다.
    즉 Service를 통해서 제공되는 기능들에 대한 EndPoint를 Kubernetes Service를 통해서 통제하는 개념인데 MSA 에서는 기능 컴포넌트에 대한 EndPoint (IP 주소)를 찾는 기능을 Service 디스커버리 (Service Discovery) 라고 하고 Service의 위치를 등록해놓는 Service 디스커버리 솔루션을 제공합니다.
    Etcdhashcorp의 consul (https://www.consul.io/)과 같은 솔루션이 해당합니다. Kubernetes Service를 통해서 Service Componant를 관리하는 것이 아니라 Service 디스커버리 솔루션을 이용하기 때문에 Service에 대한 IP 주소가 필요 없습니다.
    이런 시나리오를 지원하기 위한 Kubernetes의 Service를 Headless라고 하는데 이러한 Headless Service는 Cluster IP 등의 주소를 가지지 않습니다. 단 DNS이름을 가지게 되는데 이 DNS 이름을 lookup 해보면 Service (Loadbalancer )의 IP 를 리턴하지 않고 이 Service에 연결된 Pod 들의 IP 주소들을 리턴하게 됩니다.

간단하게 테스트를 해보겠습니다.

  • RS로 여러개의 POD를 정의해놓은 상태에서 테스트 진행하겠습니다.
[root@nasa-master nasa]# kubectl get po -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP          NODE         NOMINATED NODE   READINESS GATES
nasatest-5bdd7d57f-s8b7d   1/1     Running   0          27m    10.32.0.4   nasa-node3   <none>           <none>
replicaset-nasa-47skg      1/1     Running   0          5m6s   10.46.0.2   nasa-node1   <none>           <none>
replicaset-nasa-7j58x      1/1     Running   0          5m6s   10.32.0.2   nasa-node3   <none>           <none>
replicaset-nasa-8ncc5      1/1     Running   0          5m6s   10.42.0.3   nasa-node2   <none>           <none>
replicaset-nasa-ktzpq      1/1     Running   0          5m6s   10.32.0.3   nasa-node3   <none>           <none>

여기에 다음과 같은 Headless Service를 하나 가동 시켜보겠습니다.

apiVersion: v1
kind: Service
metadata:
name: nasa-node-svc-headless
spec:
clusterIP: None
selector:
    app: nasa-nginx-pods-label
ports:
    - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
  • POD들의 레이블을 묶어준 뒤 Service를 정의하게되면

아래와 같이 ClusterIP가 할당되지 않는 것을 확인 할 수 있습니다.

[root@nasa-master nasa]# kubectl get svc
NAME                     TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes               ClusterIP   10.96.0.1    <none>        443/TCP   31m
nasa-node-svc-headless   ClusterIP   None         <none>        80/TCP    6m12s

그러나 다른 POD를 생성해서 NSLOOKUP을 날려 DNS를 조회해보면

[root@nasa-master nasa]# kubectl run nasatest -it --image=c1t1d0s7/network-multitool --generator=run-pod/v1 --rm=true bash
If you don't see a command prompt, try pressing enter.
bash-5.0# nslookup nasa-node-svc-headless
Server:         10.96.0.10
Address:        10.96.0.10#53
Name:   nasa-node-svc-headless.default.svc.cluster.local
Address: 10.46.0.2
Name:   nasa-node-svc-headless.default.svc.cluster.local
Address: 10.32.0.2
Name:   nasa-node-svc-headless.default.svc.cluster.local
Address: 10.32.0.3
Name:   nasa-node-svc-headless.default.svc.cluster.local
Address: 10.42.0.3
  • 위과 같이 Service에 의해 제공되는 pod 들의 IP 주소 목록이 나오는 것을 확인할 수 있


Loadbalancer

  • 현재 Cluster 환경은 GCP의 Instance에 KUBEADM으로 구성한 상태입니다. 현재 환경에서 GCP의 외부 IP로 LB를 이용해 URL을 받아와 보겠습니다.
    외부 IP를 가지고 있기 때문에, Cluster 외부에서 접근이 가능합니다. 방화벽 문제만 없다면요!

우선 다음과 같은 RS를 하나 정의합니다.

apiVersion: apps/v1 
kind: ReplicaSet 
metadata: 
name: rs-nasa 
spec: 
replicas: 4 
selector: 
    matchLabels: 
    app: nasa-rs-pod
template: 
    metadata: 
    name: nasa-rs
    labels: 
        app: nasa-rs-pod
    spec: 
    containers: 
    - name: rs-nasa 
        image: nginx:latest 
        ports: 
        - containerPort: 80

그리고 아래와 같은 LB Service를 하나 정의합니다.

apiVersion: v1
kind: Service
metadata:
name: nasa-node-lb
spec:
selector:
    app: nasa-rs-pod
ports:
    - name: http
    port: 80
    protocol: TCP
    targetPort: 80
type: LoadBalancer
externalIPs:
- 34.84.172.31

externalIPs의 경우 GCP 인스턴스에서 고정으로 할당한 IP입니다

스크린샷, 2020-09-18 16-01-07


위의 정의된 템플릿들을 생성하면 아래와 같이 정상적으로 생성됩니다!!

[root@nasa-master nasa]# kubectl get po -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP          NODE         NOMINATE
D NODE   READINESS GATES
rs-nasa-fvzm6              1/1     Running   0          64m   10.46.0.3   nasa-node1   <none>  
        <none>
rs-nasa-hqhjs              1/1     Running   0          64m   10.42.0.3   nasa-node2   <none>  
        <none>
rs-nasa-jn6jz              1/1     Running   0          64m   10.32.0.2   nasa-node3   <none>  
        <none>
rs-nasa-zfh2n              1/1     Running   0          64m   10.46.0.2   nasa-node1   <none>  
        <none>
[root@nasa-master nasa]# kubectl get svc -o wide
NAME                     TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE    SEL
ECTOR
kubernetes               ClusterIP      10.96.0.1      <none>         443/TCP        119m   <no
ne>
nasa-node-lb             LoadBalancer   10.101.13.59   34.84.172.31   80:30850/TCP   36m    app
=nasa-rs-pod
[root@nasa-master nasa]# 
[root@nasa-master nasa]# kubectl get endpoints
NAME                     ENDPOINTS                                            AGE
kubernetes               10.146.0.6:6443                                      120m
nasa-node-lb             10.32.0.2:80,10.42.0.3:80,10.46.0.2:80 + 1 more...   36m
  • POS 정상기동, Service 정상기동, EndPoint에 정상적으로 Pod가 동기화 됨을 확인합니다.

자 그럼 이제 Service를 위한 정의는 모두 끝났습니다!!

  • 테스트를 하기전 GCP 방화벽에서 HTTP에 대한 PORT를 허용해줍니다!

스크린샷, 2020-09-18 16-04-43


모두 확인이 완료 되었으면 외부 ubuntu os에서 curl로 요청해봅시다!

curl 34.84.172.31:30850

LB SVC에서 외부 PORT가 30850으로 설정되어있어 해당 포트로 요청해야합니다!!

스크린샷, 2020-09-18 16-06-31

  • 정상적으로 LB SVC의 외부IP로 URL을 받아오네요!! 성공!!

Tags

#Kubernetes
NASA1515

NASA1515

Data Engineer

Hello I'M Wonseok aka NASA1515

Expertise

Public Cloud
k8s/Docker
Python

Social Media

instagramwebsitelinkedingithub

Related Posts

[Kubernetes] VSCode (VisualStudio Code)로 Kubernetes 클러스터 관리 : 초보자를 위한 완벽 가이드
[Kubernetes] VSCode (VisualStudio Code)로 Kubernetes 클러스터 관리 : 초보자를 위한 완벽 가이드
2021-08-19
2 min

Topics

CloudDevelop

Social Media