Kubernetes

Service

서비스는 기본적으로 자신의 클러스터 IP
를 가지고 있다.
그리고 이 서비스를 파드에 연결을 시켜 놓으면 서비스의 IP를 통해서 파드에 접근이 가능하다.
그런데, 전 포스팅에서 살펴본 것처럼 파드도 똑같이 클러스터 내에서 접근할 수 있는 IP가 있었다.
그렇다면 굳이 왜 서비스를 이용하는 것일까?
사용 이유
Pod
라는 존재는 Kubernetes
에서 시스템 장애건, 성능 장애건 언제든지 죽을 수가 있다.
그리고 그때 다시 재생성 되도록 설계가 되어있는 오브젝트이다.
근데 이때, Pod
는 재생성시 IP가 변경된다. 따라서, 이 파드의 IP는 신뢰성이 떨어진다.
그러나, Pod
와는 별개로 서비스는 사용자가 직접 지우지 않는 한 삭제되거나 재생성되지 않는다.
그래서 해당 서비스의 IP로 접근하면 항상 해당 서비스와 연결되어 있는 Pod
에 접근이 가능하다.
위와 같은 이유로 서비스를 사용하는 것이고 서비스에는 몇 가지 종류가 존재한다.
서비스의 종류에 따라 파드의 접근을 도와주는 방식에 차이가 있는데 종류는 다음과 같다.
서비스의 종류
- 클러스터 IP
- NodePort
- Load Balancer
순서대로 알아보자.
클러스터 IP

클러스터 IP는 Kubernetes
클러스터 내에서만 접근이 가능한 IP이다.
따라서, Pod
에 있는 IP와 특징이 똑같다고 보면된다.(물론, 앞서 설명한 것처럼 Pod처럼 쉽게 재생성되진 않는다.)
해당 클러스터 IP는 클러스터 내의 다른 모든 오브젝트들이 접근을 할 수 있지만, 외부에서는 접근이 불가능하다.
그리고 파드를 하나만 연결하는 것이 아닌 여러 개의 파드를 연결할 수가 있는데, 파드를 여러 개 연결시키면
서비스가 알아서 트래픽을 분산하여 파드에 전달해준다.
실습에 앞서 서비스를 생성하는 YAML 파일을 살펴보면 다음과 같다.
apiVersion: v1
kind: Service
metadata:
name: svc-1
spec:
selector:
app: pod
ports:
- port: 9000
targetPort: 8080
type: ClusterIP
위의 YAML 파일에서 마지막 라인을 보면 type: ClusterIP
라고 되어있다.
해당 타입값은 옵션값이기에 생략을 하는 것이 가능하다.
만약, 생략을 하면 기본 값이 클러스터 IP이므로, 클러스터 IP를 사용할 때는 해당 타입을 넣지 않아도 된다.
그리고 위의 ports
를 살펴보면 9000
번 포트로 서비스에 요청을 하면 타겟이 되는 Pod
의 8080
포트로 연결이 된다.
NodePort

NodePort
타입으로 만들어도 서비스에는 기본적으로 클러스터 IP가 할당이 된다.
그렇기에 기본적으로 클러스터 IP 타입과 같은 기능이 포함되어 있다.
NodePort
타입만의 큰 특징은 Kubernetes
클러스터에 연결되어 있는 모든 노드한테 똑같은 포트가 할당이 된다.
따라서, 외부로부터 어느 노드던간에 그 IP의 포트로 접속을 하면 해당 서비스에 연결이 된다.
그러면 또 서비스는 기본 역할로 자신한테 연결되어 있는 Pod
에 트래픽을 전달해준다.
이때, 주의할 점은 해당 Pod
가 있는 노드에만 포트가 할당되는 것이 아닌 모든 노드에 포트가 만들어진다.
apiVersion: v1
kind: Service
metadata:
name: svc-2
spec:
selector:
app: pod
ports:
- port: 9000
targetPort: 8080
nodePort: 30001
type: NodePort
externalTrafficPolicy: Local
type
은 아까와 다르게 NodePort
타입이다. 이때 YAML 파일에 적혀있듯이 NodePort
의 포트를 지정할 수 있다.
포트 범위: 30001 ~ 32767(30000는 대시보드 포트이다)
포트값을 지정하는 것또한 옵셔널이기 때문에 생성시 작성하지 않으면 자동으로 포트 범위 내에서 할당이 된다.
그리고 NodePort
서비스에는 한가지 더 추가적인 속성이 존재한다.
만약, 위의 그림처럼 각 노드에 Pod
가 하나씩 올라가져 있다고 했을 때,
우리가 Node1의 IP로 접근을 하더라도 해당 서비스는 Node2에 있는 Pod
한테 트래픽을 전달할 수 있다.
그 이유는 앞서 설명했던 것처럼 NodePort
를 사용했을 때 특정 Node에 접근을 해도 결국 그것은 NodePort
서비스에 전달된다.
그렇기에 서비스 입장에서는 어떤 Node한테 온 트래픽인지 상관없이 그냥 자신한테 달려있는 Pod
들한테 트래픽을 전달하는
자신의 일을 하는 것이다.
근데 만약, YAML 파일 가장 아래에 있는 External Traffic Policy
라는 값을 Local
로 주게되면
특정 노드 포트로의 IP로 접근을 하는 트래픽은 서비스가 해당 노드에 위에 올려져 있는 Pod
한테만 트래픽을 전달해준다.
LoadBalancer

LoadBalancer
타입의 서비스도 일단 기본적으로 앞에서 배운 NodePort
의 성격을 또 그대로 가지고 있다.
그리고 추가적으로, 로드 밸런서라는게 생겨서 각각의 로드의 트래픽을 분산시켜주는 역할을 한다.
근데 한가지 문제는 해당 로드 밸런서에 접근을 하기 위한 외부 접속 IP 주소는 개별적으로 Kubernetes
를 설치했을 때
기본적으로 생성되지 않는다. 별도로 외부 접속 IP를 할당을 해주는 플러그인이 설치가 되어 있어야 한다.
만약, GCP이나 아마존에서 제공하는 Kubernetes
플랫폼을 사용하면 자체적으로 플러그인이 설치가 되어 있어
LoadBalancer
타입으로 서비스를 만들면 알아서 외부에서 접속할 수 있게 IP를 만들어준다.
그럼 해당 IP를 통해 외부에서 접근이 가능하다.
LoadBalancer YAML 파일
apiVersion: v1
kind: Service
metadata:
name: svc-3
spec:
selector:
app: pod
ports:
- port: 9000
targetPort: 8080
type: LoadBalancer
내용은 별거 없이 type: LoadBalancer
를 지정해주면 된다.
여기까지 3가지 서비스 타입에 관해 알아보았는데 서비스는 이외에도 여러 용도로 더 쓰인다.
(해당 내용은 추후에 알아보자)
그러면 각각의 서비스 타입들을 어떤 상황에 적용해야 할까?
첫째로 클러스터 IP
클러스터 IP는 외부에서 접근할 수가 없고 클러스터 내에서만 사용되는 IP이다.
그렇기 때문에 해당 IP에 접근할 수 있는 대상은 클러스터 내부에 접근을 할 수 있는 운영자와 같은 인가된 사람일 수 밖에 없다.
주된 작업은 Kubernetes
대시보드를 관리하거나 각 Pod
의 서비스 상태를 디버깅하는 작업을 위해 사용된다.
둘째로 NodePort
NodePort는 특징이 물리적인 호스트의 IP를 통해 Pod
에 접근이 가능하다는 것인데,
대부분 호스트 IP는 보안적으로 내부망에서만 접근을 할 수 있게 네트워크를 구성한다.
따라서, 이 NodePort는 클러스터 밖에는 있지만 그래도 내부망 안에서 접근을 해야 될 때 사용된다.
그리고 일시적인 외부 연동용으로도 사용이 되는데 우리가 내부 환경에서 시스템 개발을 하다가
외부에 간단한 데모를 보여줘야 할 때 네트워크 중계기에 포트포워딩을 해서 NodePort를 잠깐 뚫어놓고 사용이 가능하다.
마지막으로 LoadBalancer
실제적으로 외부에 서비스를 노출시키려면 로드밸런서를 이용을 해야 된다.
그래야 내부 IP가 노출되지 않고 외부 IP를 통해 안정적으로 서비스를 노출시킬 수 있기 때문이다.
그렇기에 로드밸런서는 외부의 시스템을 노출하는 용으로 사용된다.
실습
몇 가지의 간단한 실습을 진행해보자.
우선, Pod
가 죽어서 재생성 되더라도 서비스의 IP를 통해 접근하는 예제이다.
apiVersion: v1
kind: Pod
metadata:
name: pod-1
labels:
app: pod
spec:
nodeSelector:
kubernetes.io/hostname: k8s-node1
containers:
- name: container
image: kubetm/app
ports:
- containerPort: 8080
우선, 위의 YAML로 Pod
를 하나 생성한다. 이때, 라벨은 app: pod
로 지정을 해두었다.
그 후, 아래 YAML로 서비스를 하나 생성한다.
apiVersion: v1
kind: Service
metadata:
name: svc-1
spec:
selector:
app: pod
ports:
- port: 9000
targetPort: 8080
YAML 파일을 보면 알 수 있듯이 셀렉터로 방금 생성한 Pod
의 라벨을 지정해주었다.
그렇게 서비스를 생성하면 다음과 같이 Pod
와 연결된 상태로 잘 생성이 된다.

이때, 타입은 Cluster IP로 잘 생성이 되는데 type
값을 넣지 않아도 기본값이 클러스터 IP이기 때문이다.
그리고 이 서비스에 9000
번 포트로 접근을 하면 서비스가 타겟이 되는 컨테이너의 8080
포트로 연결을 해준다.
한번 서비스가 잘 동작하는지 확인하기 위해 Kubernetes
클러스터 내부의 k8s-master
에 접속해 curl
을 전송하자.

서비스와 잘 통신이 되는 것을 확인할 수 있다.
(해당 curl
명령어를 사용시 Hostname
을 반환해주는 건 인프런의 일프로님의 이미지에 관련 설정이 되어있기 때문이다.)
만약, 해당 주소로 내부망인 데스크탑에서 접근을 하면 어떻게 될까?

내부망인 데스크탑이라도 Kubernetes
클러스터가 아니기 때문에 접근이 불가능한 것을 확인할 수 있다.
그럼 본론으로 돌아와 우리의 처음 목표였던 Pod
를 삭제하고 다시 만들었을 때도 서비스와 연결이 되는지 확인해보자.

서비스의 Pod
를 삭제시킨 뒤 재생성해보자.

Pod
를 재생성해도 알아서 서비스가 라벨을 통해 인식하여 연결이 된 것을 확인할 수 있다.
다음으로는 NodePort
를 만들어보자.
apiVersion: v1
kind: Service
metadata:
name: svc-2
spec:
selector:
app: pod
ports:
- port: 9000
targetPort: 8080
nodePort: 30001
type: NodePort
우선, 앞서 만들었던 Cluster IP 타입의 서비스를 삭제하고 위 YAML 파일로 서비스를 생성해보자.
nodePort
는 30001
번으로 지정을 해주었다.

생성된 서비스를 보면 클러스터 IP가 자동으로 생성이 되었고 사용되는 내부 엔드포인트 포트는 2개가 할당이 되었다.
그 중 30001
번 포트가 우리가 생성한 nodePort
이다.
생성된 nodePort
로 각 노드에 curl
을 보내면 모든 노드에 적용되어 다음과 같이 응답이 잘 오는 것을 확인할 수 있다.

이 상태에서 pod-2
를 하나 더 생성해보자.
apiVersion: v1
kind: Pod
metadata:
name: pod-2
labels:
app: pod
spec:
nodeSelector:
kubernetes.io/hostname: k8s-node2
containers:
- name: container
image: kubetm/app
ports:
- containerPort: 8080
이때는 2번 노드를 선택해주었다. 그 후, 서비스를 다시 확인하면 Pod
가 2개 연결된 것을 확인할 수 있다.


그리고 nodePort
로 접근을 시도하면 서비스가 각각이 연결되어 있는 Pod
들한테 트래픽을 분산해서 전달을 해준다.
(위의 사진을 보면 트래픽이 분산되어 가는 것을 확인할 수 있다.)
다음으로는 아까 설명했던 externalTrafficPolicy
옵션을 사용했을 때의 예시이다.
우선, 기존의 서비스를 지우고 해당 옵션을 달아서 서비스를 다시 생성해보자.
apiVersion: v1
kind: Service
metadata:
name: svc-2
spec:
selector:
app: pod
ports:
- port: 9000
targetPort: 8080
nodePort: 30001
type: NodePort
externalTrafficPolicy: Local
서비스를 생성한 뒤 Node1
에 접근하면 Node1
을 선택한 Pod
1번에만 접근하는 것을 볼 수 있다.

Pod
2번도 마찬가지이다.

그럼 만약, Node1
에는 Pod
가 없는데 Node1
의 IP를 계속 호출한다면 어떻게 될까?
Pod
1번을 삭제한 뒤 접근하면 아래와 같이 계속 기다리고만 있게 된다.

따라서, externalTrafficPolicy
옵션을 사용할 때는 해당 부분에 유의해야 한다.
마지막으로, LoadBalancer
타입의 서비스를 생성해보자.
apiVersion: v1
kind: Service
metadata:
name: svc-3
spec:
selector:
app: pod
ports:
- port: 9000
targetPort: 8080
type: LoadBalancer

사진을 보면 클러스터 IP도 정상적으로 잘 생성이 되었고 NodePort
도 자동으로 생겼다.
하지만 계속 Pending
상태로 생성이 되지 않는다.
그 이유는 LoadBalancer
타입은 외부에서 접근이 가능하도록 External IP를 자동으로 할당해주는 플러그인이 있어야 한다.
하지만, 기본적으로 Kubernetes
를 설치할 때는 그게 없기 때문에 External IP가 보이지 않는다.
실제로 클러스터 Master에서 아래 명령어를 입력하면,

다음과 같이 EXTERNAL-IP가 계속 Pending
상태인걸 확인할 수 있다. 이는 플러그인이 없기 때문이다.
'Infra > Kubernetes' 카테고리의 다른 글
[Kubernetes] ConfigMap, Secret - Env, Mount (0) | 2024.09.02 |
---|---|
[Kubernetes] Volume - emptyDir, hostPath, PV/PVC (0) | 2024.08.28 |
[Kubernetes] Pod - Container, Label, NodeSchedule (0) | 2024.08.12 |
[Kubernetes] 왜 쿠버네티스인가? (0) | 2024.08.05 |
[Kubernetes] 쿠버네티스 소개 (0) | 2024.08.05 |