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 (4) | 2024.08.28 |
[Kubernetes] Pod - Container, Label, NodeSchedule (0) | 2024.08.12 |
[Kubernetes] 왜 쿠버네티스인가? (0) | 2024.08.05 |
[Kubernetes] 쿠버네티스 소개 (0) | 2024.08.05 |