Engineering/Kubernetes (K8S)

[Kubernetes] 쿠버네티스 서비스(Service) Deep Dive - (2) ClusterIP

[앙금빵] 2022. 5. 17.

리마인드 - 서비스 개념

(1) Pod는 고유한 IP주소가 존재하며 일시적인(ephemeral) 특성을 지니며 불안정한 리소스이다.

(2) 클라이언트 관점에서 서비스를 지원하는 Pod의 수와 IP에 상관하지 않아야 하며 모든 Pod는 단일 IP주소로 Access할 수 있어야 한다.

(3) 쿠버네티스는 이러한 문제점을 해결하기 위해 "Service" 리소스 유형을 제공한다.

(4) Service는 어떤 파드가 어디에서 존재하는지 (어떤 IP를 가지는지)에 관계없이 Pod 앞단에서 Pod 중 하나로 연결하여 요청을 처리하도록 한다.

(5) 서비스를 지원하는 Pod는 여러개일 수 있으며, 레이블 셀렉터(Label Selector)를 통해 서비스와 연결된 Pod를 정의한다.

 


리마인드 - 서비스 종류

 

① Cluster IP 타입
쿠버네티스 내부에서만 포드에서 접근할 때 사용한다. 외부로 Pod를 노출하지 않기 때문에 쿠버네티스 클러스터 내부에서만 사용되는 Pod에 적합하다.

② NodePort 타입
Pod에 접근할 수 있는 포트를 Cluster의 모든 Node에 동일하게 개방한다. 따라서 외부에 Pod에 접근할 수 있는 서비스 타입이다. 접근할 수 있는 포트는 랜덤으로 정해지지만 특정 포트로 접근하도록 설정할 수도 있다.

③ LoadBalancer 타입
클라우드 플랫폼에서 제공하는 Load Balancer를 동적으로 Provisioning하여 포드에 연결한다. NodePort 타입과 마찬가지로 외부에서 포드에 접근할 수 있는 서비스 타입이다. 일반적으로 AWS, GCP 등과 같은 클라우드 플랫폼 환경에서만 이용할 수 있다.

 


목표 - Architecture

각 서비스 타입별 (ClusterIP, NodePort, LoadBalancer) 실습 및 서비스 특성 파악

참고: Kubernetes-in-action 2nd Edition

 

사전준비

앞서, 실습환경을 구성하자.

gitclone https://github.com/luksa/kubernetes-in-action-2nd-edition.git

kubectl apply -f kubernetes-in-action-2nd-edition/Chapter11/SETUP/quote

③ PV, PVC, POD (더보기를 클릭 후 yaml파일 참조하여 각 오브젝트 생성)

pv.yaml

더보기

apiVersion: v1
kind: PersistentVolume
metadata:
  name: quiz-data
spec:
  capacity: 
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
    - ReadOnlyMany
  hostPath:
    path: /var/quiz-data
  storageClassName: ""

 

pvc.yaml

더보기

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: quiz-data
spec:
  resources:
    requests:
      storage: 5Gi
  accessModes:
  - ReadWriteOnce
  storageClassName: ""
  volumeName: quiz-data

 

pod.yaml

더보기

apiVersion: v1
kind: Pod
metadata:
  name: quiz
  labels:
    app: quiz
    rel: stable
spec:
  volumes:
  - name: initdb
    emptyDir: {}
  - name: quiz-data
    persistentVolumeClaim:
      claimName: quiz-data
  initContainers:
  - name: installer
    image: luksa/quiz-initdb-script-installer:0.1
    imagePullPolicy: Always
    volumeMounts:
    - name: initdb
      mountPath: /initdb.d
  containers:
  - name: quiz-api
    image: luksa/quiz-api:0.1
    imagePullPolicy: Always
    ports:
    - name: http
      containerPort: 8080
  - name: mongo
    image: mongo:4.4.6
    volumeMounts:
    - name: quiz-data
      mountPath: /data/db
    - name: initdb
      mountPath: /docker-entrypoint-initdb.d/
      readOnly: true

 

사전 준비 완료 시 아래와 같이 구성이 된다.

초기 환경 구성 완료시 모습


 

1. ClusterIP 

(1) ClusterIP는 서비스 리소스의 가장 기본이 되는 타입이다. 별다른 타입 지정을 하지 않으면 기본적으로 ClusterIP로 설정된다.

(2) 외부에서는 접근할 수 없으며 쿠버네티스 내부에서만 Pod에서 접근할 때 사용한다.

 

ClusterIP 사용 목적

외부와 통신이 불가능한데 굳이 왜 ClusterIP를 사용할까? 이유는 크게 2가지로 나뉜다.

 

첫째, 네트워크 보안 및 관리 목적

일반적으로 보안상 한 두개의 외부 서비스 외에 직접적으로 트래픽을 전달받는 경우는 드물다. 열린 서비스 끝점으로부터 트래픽을 전달받아 내부 파드와 소통하는데 이용한다.

둘째, 복잡한 네트워킹 수행 가능

쿠버네티스 서비스를 정의하는 메니페스트 파일을 통해 보다 정교하게 네트워킹을 정의하고 수행할 수 있다.

 

ClusterIP 서비스 생성

quote Pod 대상 manifest 파일을 생성하여 서비스를 생성해보자.

apiVersion: v1
kind: Service
metadata:
  name: quote
spec:
  type: ClusterIP
  selector:
    app: quote
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP

아래와 같이 yaml 파일을 작성하고 kubectl apply 명령어를 통해 서비스를 생성하자.

참고: Kubernetes-in-action 2nd Edition

① quote라는 서비스 이름으로 정의하며 포트 80의 연결을 허용한다.

② 각 연결을 포트 80으로 레이블 app=quote 와 일치하는 Pod로 포워딩한다.

 

Quiz Pod에 대해서 명령형 커맨드(Imperative Command)를 통해 서비스를 생성해 보자.

kubectl expose pod quiz --name quiz

 

여기서 quiz 파드에 적용된 레이블 app=quiz, rel=stable이 적용된 것을 확인할 수 있다.  그러나, quiz라는 서비스가 stable 레이블이 적용된 것이 아닌 조금 더 광범위적으로 적용하는 상황을 고려하여 app=quiz 레이블만 적용할 수 있도록 정책을 수정해보자.

# 레이블 설렉터를 app=quiz로 설정하도록 변경
root@k8s-m:~# kubectl set selector service quiz app=quiz
service/quiz selector updated

# 정책 반영 확인
root@k8s-m:~# kubectl get svc -o wide
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE     SELECTOR
quiz    ClusterIP   10.104.206.158   <none>        80/TCP    4m51s   app=quiz
quote   ClusterIP   10.97.190.49     <none>        80/TCP    5m42s   app=quote

 

추가적으로, 이미 생성한 서비스에서 포트 수정도 가능하다.

kubectl edit 기능을 활용하여 8080:8080 을 80:8080으로 수정해보자.

kubectl edit svc quiz

업데이트 된 quiz 서비스

 

생성한 서비스 확인

service를 svc로 줄여서 쓸 수 있다.

root@k8s-m:~# kubectl get svc -o wide
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE     SELECTOR
quiz    ClusterIP   10.104.206.158   <none>        80/TCP    4m51s   app=quiz
quote   ClusterIP   10.97.190.49     <none>        80/TCP    5m42s   app=quote

 

Pod로부터 Service 접근

이제 quote-001 파드로부터 일전에 생성한 quote, quiz 서비스에 대하여 접근이 가능한지 시도해보자.

나의 경우, quiz 서비스 ClusterIP는 10.104.206.158 quote 서비스 ClusterIP는 10.97.190.49 이다.

root@k8s-m:~# kubectl get svc -o wide
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE     SELECTOR
quiz    ClusterIP   10.104.206.158   <none>        80/TCP    4m51s   app=quiz
quote   ClusterIP   10.97.190.49     <none>        80/TCP    5m42s   app=quote

 

우선, quote-001 내부에서 Shell을 실행시켜보자. 그리고, quote-001로부터 quiz, quote 서비스에 접속이 되는지 확인해보자. 

#파드 내부 접속
kubectl exec -it quote-001 -c nginx -- sh
/ #

# quote-001(Pod) → quiz(Service) 접속
/ # curl http://10.104.206.158
This is the quiz service running in pod quiz

# quote-001(Pod) → quote(Service) 접속
/ # curl http://10.97.190.49
This is the quote service running in pod quote-003 on node k8s-w3

 

파드가 여러개인 경우, 서비스가 로드밸런서와 같이 다른 Pod들에 여러번 접근하는 것을 확인할 수 있다. 

/ # while true; do curl http://10.97.190.49; done
This is the quote service running in pod quote-001 on node k8s-w2
This is the quote service running in pod quote-002 on node k8s-w1
This is the quote service running in pod quote-001 on node k8s-w2
This is the quote service running in pod quote-003 on node k8s-w3
This is the quote service running in pod quote-003 on node k8s-w3
This is the quote service running in pod quote-canary on node k8s-w3
This is the quote service running in pod quote-002 on node k8s-w1
💡 기본적으로 서비스는 트래픽을 랜덤하게 파드로 분산시킨다. 만약, 특정 클라이언트와 특정 Pod를 지속적으로 연결이 되게 하려면 spec.sessionAffinity를 통해 구현할 수 있다.

SessionAffinity 타입은 None, Client IP 2가지만 지원한다. (None은 랜덤하게 파드로 로드밸런싱 한다.)

 


참고

  • kubernetes-2nd-edition

댓글