개발놀이터

쿠버네티스 이론 : 서비스 (Cluster IP, Node Port, LoadBalancer) 본문

배포/kubernetes

쿠버네티스 이론 : 서비스 (Cluster IP, Node Port, LoadBalancer)

마늘냄새폴폴 2024. 9. 2. 21:10

이번 포스팅은 쿠버네티스의 꽃인 파드와 짝을 이루는 서비스입니다. 서비스는 파드를 외부로 노출시켜주는 역할을 하게 되는데, 앞선 쿠버네티스 이론 : 파드 편에서 잠시 언급했지만 라벨을 이용해서 파드와 연결합니다. 

 

이는 노드에 들어있는 파드들 중에 노출되면 안되는 파드도 있기 때문에 (e.g. 데이터베이스) 보안상 지정된 파드만 외부와 연결할 수 있습니다. 

 

서비스에는 크게 Cluster IP, Node Port, LoadBalancer 이렇게 세 가지 타입이 있습니다. 각 타입은 특징이 뚜렷해서 어느 것이 어떤 상황에서 쓸지 명확하게 구분할 수 있습니다. 

 

그럼 본격적으로 시작해보죠!

 

Service (서비스)

서비스에는 Cluster IP, Node Port, LoadBalancer 이렇게 세 가지 타입이 있는데 하나씩 살펴보도록 하죠. 

 

Cluster IP

Cluster IP는 쿠버네티스 클러스터 내부에서만 접근이 가능한 타입입니다. 만약 Cluster IP 타입으로 서비스를 만든다면 외부에서는 어떤 방법을 동원해서도 연결될 수 없습니다. 

 

이런 특징 때문에 쿠버네티스를 관리하는 관리자만 접근할 수 있도록 열어두는 용도로 주로 사용합니다. 

 

서비스의 타입을 아무것도 설정하지 않으면 기본적으로 설정되는 타입이기도 한데, 사실 많이 사용될 것 같지는 않습니다. 

 

한번 yaml 설정파일 보시죠. 

 

apiVersion: v1
kind: Service
metadata:
  name: svc-1
spec:
  selector:
    app: pod
  ports:
  - port: 9000
    targetPort: 8080
  type: ClusterIP

 

apiVersion: v1
kind: Pod
metadata:
  name: pod-1
  labels:
    app: pod
spec:
  containers:
  - name: container
    image: tmkube/app
    ports:
    - containerPort: 8080

 

파드와 서비스 이렇게 두가지 설정이 있습니다. 파드의 라벨은 app: pod 이고 이를 서비스가 selector를 이용해 연결하고 있습니다. 

 

이때, 서비스의 IP로 접근을 요청하면 연결이 되지 않는 것을 알 수 있는데 이에 대한 자세한 실습 내용은 쿠버네티스 실습 : 서비스 편에서 제대로 다뤄보도록 하겠습니다. 

 

Node Port

Node Port는 기본적으로 Cluster IP의 성질을 가지고 있는 상태에서 모든 노드에 동일한 포트를 설정하고 해당 포트로 들어오는 모든 요청을 모두 분산시켜준다는 특징이 있습니다. 

 

Node Port에는 한가지 특징이 있는데 바로, 다른 노드로 들어오더라도 모든 요청을 분산한다는 특징이 있습니다. 즉, 서로 다른 노드에 서로 다른 파드가 배포되어 있다면 어떤 파드로 트래픽이 흐를지 모른다는 의미입니다. 

 

이 이얘기는 노드1에 파드1이 배포되어있고 노드2에 파드2가 배포되어있다면 노드1로 온 요청을 파드2가 처리할 수도 있다는 의미입니다. 

 

하지만 경우에 따라선 노드1로 들어온 트래픽 요청을 파드1에서 처리하고 싶을 수도 있습니다. 이럴 때는 externalTrafficPolicy를 local속성으로 설정하면 해결할 수 있습니다. 

 

apiVersion: v1
kind: Service
metadata:
  name: svc-2
spec:
  selector:
    app: pod
  ports:
  - port: 9000
    targetPort: 8080
    nodePort: 30000
  type: NodePort

 

Node Port의 또다른 특징은 Node Port에 해당하는 포트는 30000~32767중 하나로 설정된다는 것입니다. 때문에 만약 NodePort로 타입을 설정하고 nodePort를 설정하지 않는다면 30000~32767중 아무 포트가 하나 정해집니다. 

 

Node Port는 내부망에서 다른 클러스터끼리 소통하는 경우 사용할 수 있고, 데모 버전이나 임시 연결용으로 포트포워딩을 하여 외부로 잠시 연결해야 하는 경우에 사용할 수 있습니다. 

 

Load Balancer

Load Balancer타입은 외부로 노출된 IP를 이용해 외부 요청을 처리하는 타입으로서 기존 Node Port의 속성을 가지고 있는 상태에서 시작합니다. 

 

만약 이 타입을 설정하는 경우 모든 노드 포트와 연결되는 로드밸런서가 앞에 위치하게 됩니다. 이 로드밸런서는 노드로 들어오는 요청을 분산시키는데 Node Port와 다른 점은 내부망에서 사용했던 Node Port와 다르게 외부 인터넷으로 연결될 수 있다는 것이 특징입니다. 

 

주의) 이 로드밸런서의 External IP는 쿠버네티스 플러그인을 설치해야 자동으로 부여해줍니다. 하지만 만약 GCP나 AWS, Azure, OpenStack등을 사용해서 쿠버네티스를 사용한다면 이를 자동으로 부여해줍니다. 

 

apiVersion: v1
kind: Service
metadata:
  name: svc-3
spec:
  selector:
    app: pod
  ports:
  - port: 9000
    targetPort: 8080
type: LoadBalancer

 

로드밸런서는 실제 운영이 되어야하는 파드들을 연결할 때 사용할 수 있습니다. 

 

Service 심화

서비스에 대해서 간단하게 다시 짚어보고 서비스를 조금 더 깊이있게 파보겠습니다. 

 

서비스란?

서비스는 파드의 외부 접속을 도와주는 오브젝트로서 설저에 따라 접속할 수 있는 범위를 조절할 수 있습니다. 

 

Cluster IP는 외부에서 접속이 불가능하고 쿠버네티스 클러스터 내부에서만 접근이 가능한 Admin 전용 서비스입니다. 

 

Node Port는 내부망에서 접속이 가능하고 Node Port로 서비스를 설정하면 임의의 포트가 노드에 부여되고 노드의 IP와 포트를 알고 있으면 파드로 접근도 가능합니다. 

 

Load Balancer는 외부에서 접근할 수 있게 하는 상용서비스에서 주로 사용됩니다. 

 

DNS Server

만약 파드와 파드를 연결하고 싶은 경우엔 어떻게 해야할까요? 파드에서는 파드의 IP만 알고 있으면 연결이 가능하지만 현실적으로 이는 불가능합니다. 

 

왜냐하면 파드의 IP는 생성될 때마다 새롭게 동적으로 부여되기 때문에 만약 파드가 삭제되면 더는 해당 IP를 사용할 수 없기 때문이죠. 

 

이 때 서비스를 DNS 서버에 올려두면 서비스의 IP가 등록되고 이를 이용해서 서비스 이름으로 서비스를 찾아갈 수 있습니다. 

 

서비스와 파드는 생성과 동시에 IP를 부여받게 되는데 DNS서버는 서비스와 파드의 IP를 관리하고 있습니다. 우리가 흔히 아는 DNS 서버와 같은 역할을 하죠. 

 

쿠버네티스가 DNS서버를 운영할 때는 FQDN이라는 방식을 사용합니다. 

 

FQDN이란 DNS서버가 도메인을 관리하는 방식을 이야기하는 것입니다. 

 

서비스의 경우 {서비스명}:{네임스페이스명}.svc.{DNS서버이름} 이렇게 관리하죠. 이를 예시와 함께 살펴보면,

service1.default.svc.cluster.local 이렇게 짓는다는 의미입니다. 

 

파드의 경우는 {파드의 IP}.{네임스페이스명}.pod.{DNS서버이름} 이렇게 관리합니다. 이를 예시와 함께 살펴보면,

20-109-5-11.default.pod.cluster.local 이렇게 관리한다는 의미입니다. 

 

DNS에 등록된 서비스에 접근할 때는 서비스명으로만 접근이 가능하지만 파드에 접근하기 위해서는 DNS에 적힌 FQDN의 모든 이름을 입력해야 접근이 가능합니다. 하지만 앞서 언급했듯이 파드의 IP는 지속적으로 변경되기 때문에 사실상 사용할 수 없습니다. 

 

Headless

headless는 파드와 파드를 연결할 때 서비스를 통하지 않고 직접 연결하고 싶은 경우에 사용할 수 있는 속성으로서 headless는 DNS서버를 이용합니다. 

 

headless는 서비스에 clusterIP속성을 None으로 주게 되면 자동으로 생성되는데 headless는 DNS에 다음과 FQDN으로 적습니다. 

{파드명}.{headless 서비스명}.{네임스페이스명}.svc.{DNS서버명}

 

그리고 접근할 때는 파드명과 headless 서비스명만 적으면 접근이 가능합니다. 즉, pod1.headless1 이런식으로 접근할 수 있다는 의미입니다. 

apiVersion: v1
kind: Service
metadata:
  name: headless1
spec:
  selector:
    svc: headless
  ports:
  - port: 80
    targetPort: 8080
  clusterIP: None

 

Endpoint

보통 서비스와 파드는 label과 selector로 연결됩니다. 하지만 실제로는 label과 selector만으로는 이 둘이 연결될 수 없습니다. 이는 사용자의 관점에서 쓰기 쉬우라고 만들어 놓은것이지 실제로 쿠버네티스는 label과 selector가 있다고 그래서 이 둘을 연결해주지 않습니다. 

 

쿠버네티스는 서비스와 파드를 실제로 연결하기 위해 Endpoint라는 것을 만듭니다. Endpoint는 서비스의 이름과 똑같은 이름으로 만들어지고 Endpoint에는 파드의 IP와 포트가 적혀있습니다. 

 

때문에, 이 Endpoint의 존재를 알고 있다면 실제로 selector가 없는 서비스와 label이 없는 파드도 연결할 수 있습니다. 

 

서비스의 이름과 똑같은 Endpoint오브젝트를 만들고 이 Endpoint에 파드의 IP와 포트를 입력하면 이 서비스와 파드는 실제로 서로 연결이 됩니다. 

 

하지만 앞서서 계속 언급했다시피 파드의 IP는 언제든 바뀔 수 있기 때문에 이렇게 사용할 수는 없습니다. 이런 Endpoint는 쿠버네티스가 서비스와 파드를 어떻게 연결하는지 정도만 알고 있으면 될 것 같습니다. 

 

쿠버네티스는 이런 Endpoint를 사용자 레벨에서 사용할 수 있도록 하는 오브젝트가 있는데 바로 ExternalName입니다. 

 

ExternalName

ExternalName은 Endpoint를 사용자가 쉽게 사용할 수 있도록 제공해주는 오브젝트입니다. 

 

서비스에서 externalName속성에 도메인 이름을 넣게 되면 쿠버네티스는 쿠버네티스 내부의 DNS를 먼저 뒤지고 없다면 사우이 DNS에 쿼리를 날려 도메인에 해당하는 IP를 가져옵니다. 

 

이렇게 ExternalName을 설정해주면 파드가 접속해야하는 곳의 IP주소가 변경되더라도 파드를 재생성하거나 설정을 변경해주지 않아도 된다는 장점이 있습니다. 

 

마치며

서비스는 파드와 짝을 이루는 쿠버네티스의 강력한 기능 중에 하나인 것 같은데... 사실 쿠버네티스를 공부하면 할수록 이런 오브젝트가 수십개가 있다고..? 싶은 생각이 듭니다. 

 

다음 포스팅은 볼륨입니다. 볼륨도 재미난 기능들이 많더라구요. 한번 제대로 파보겠습니다! 긴 글 읽어주셔서 감사합니다. 오늘도 즐거운 하루 되세요!