이번 시간에는 서비스가 무엇인지에 대해서 알아보고, 서비스의 종류에 대해서 알아보도록 하겠습니다.
서비스란?
서비스 API Resource는 쿠버네티스에서 가장 중요한 리소스 중 하나입니다. 저희가 외부로부터 요청을 받고, 외부 트래픽을 분산시키기 위해서 서비스라는 API Resource가 필요합니다. 저번시간에는 Deployment를 통해 파드를 수평확장하는 방법을 배웠었습니다. 수평확장도 중요하지만, 외부로 부터 들어오는 트레픽을 받고, 이 여러 파드들에다가 트레픽을 분산하는 역할도 매우 중요합니다.
이러한 서비스는 여러 파드에 대한 요청을 분산하는 로드 밸런서 기능을 수행하게 됩니다. 이 서비스에서 사용하는 로드 밸런서는 L4로 IP/PORT로 로드벨런서 기능을 수행합니다. 그리고 추후에 알아볼 Ingress라는 API Resource가 존재하는데요, 이를 이용하면 L7로드 벨런서의 기능도 수행할 수 있게 됩니다.
서비스의 종류
첫번쨰로 살펴볼 ClusterIP는 그림에서 보시다 싶이, 클러스터 안에서 놀고있음을 보실 수 있습니다. 이는 외부 트래픽을 받는것은 불가능하고, 클러스터 내에서 트레픽을 찔렀을 때, 파드들에게 로드를 분산시켜주는 역할을 하게됩니다.
그리고 다음으로는 NodePort입니다. 이는 외부로부터 트레픽 주입이 가능한 것을 보실 수 있습니다. 그리고 그 아래 Service -> Pod의 구성은 ClusterIP와 비슷하다는 것을 보실 수 있습니다. NodePort는 CluserIP를 Wrapping하는 구조여서 CluserIP의 모든 기능을 사용할 수 있습니다. 이에 더해서 클러스터를 구성하는 모든 노드의 같은 port를 열게 되는데요, 이 port를 통해서 외부 트래픽을 받고, CluserIP를 통해서 다시 Load를 분산하는 역할을 수행합니다.
3번쨰로는 LoadBalancer기능입니다. 이 경우에도 CluserIP의 모든 기능을 사용할 수 있음과 동시에, 외부에 존재하는 LoadBalancer를 동적으로 관리하게 됩니다. 그래서 대게 클라우드 프로바이더와 같이 사용됩니다.
마지막으로 ExternalName이 있는데, 위 3개와는 사뭇 다른 Service Resource라고 보시면 됩니다. 위에서 설명한 3가지는 외부에서의 트레픽을 관리하는 것 이었다면, ExternalName은 내부에서 외부로의 트레픽을 관리한다는 점에서 차이점이 있습니다. 예를 들어 a.b.com을 서비스 내부에서는 a.b로 호출을 할 수 있게끔 한다는 것이죠
ClusterIP와 서비스 디스커버리
ClusterIP는 Service API리소스의 가장 기본적인 타입이라고 할 수 있습니다. 그리고 중요한 점이 있는데, Pod에 부여되는 Pod IP를 위한 CIDR대역과 Service에 부여되는 Cluster IP CIDR대역이 독립적으로 존재한다는 점입니다.
위 그림과 같이 이전에 살펴보았던 것과같이 우리는 Cluster안에서 PodIP를 통해서 클러스터 내에서 통신하는 과정을 살펴본 바 있습니다. 이 PodIP는 내부에서만 사용되는 가상 IP이기 때문에 외부에서 사용할 수 없습니다. 이 PodIP말고도 우리는 위 그림과 같이 Service라는 객체를 만들게 될겁니다. 이는 ClusterIP라는 가상 IP를 가지게 됩니다. 이 가상 IP를 Cluster IP CIDR이라고 하는데, 이를 확인하기 위해서는 위 그림의 명령어를 쳐서 서비스가 어떤 CIDR IP대역을 잡았는지 확인할 수 있게 됩니다.
또한 서비스는 디플로이먼트와 비슷하게 Label Selector를 통해 서비스와 연결할 Pod들을 관리하게 됩니다. 또한 Cluster IP로 들어오는 요청에 대해서는 파드에 L4레벨의 로드벨런싱이 이루어지게 되고, 내부 DNS를 통해 서비스 이름을 이용한 통신이 가능해집니다. 이는 docker-compose와 매우 유사한데, 이도 서비스 내에서 서비스 이름으로 서비스 디스커버리가 가능했었습니다.
실습
위와같은 미리 작성된 매니페스트 파일을 확인합니다. 우선 deployment는 replicas를 3으로 했고 spec.containers.image를 nginxdemos/hello:plain-text로 지정하고 80번 포트를 열어놨습니다.
그리고 새로 작성한 service 매니페스트 파일을 보면, Core서비스라는 것을 확인할 수 있고, type은 ClusterIp이며, selector에 app: hello로 Service < ---- > Pod를 연결해 주었습니다. deployment.yaml에서 spec.matchLabels.app = hello이기 떄문입니다.
결과적으로 ClusterIP:port ---> PodIP:targetPort로 매핑되는 것입니다. 일단 이들을 모두 apply해보겠습니다.
service하나가 hello라는 이름으로 하나 생성된 것을 보실 수 있습니다. 이의 ClusterIP는 당연히 외부인 Host OS에서 접속이 불가능합니다. 왜냐하면 가상 IP이기 떄문이죠.
다만 minikube가 만든 클러스터 안에서는 잘 응답이 오는 것을 보실 수 있습니다.
그리고 우리는 이 과정에서 L4 로드벨런서가 동작하는 것을 이미 알고 있습니다. 이를 직접 확인해 보도록 하겠습니다.
계속해서 hello Service의 8080번 포트로 요청을 보내면 Server name에 계속 다른 이름이 뜨는데 이는 우리가 replica로 지정해준 3개의 pod와 service.yaml에 명시를 해주었기 떄문입니다.
뿐만 아니라 서비스 디스커버리 기능으로 제공하는 내부 DNS도 확인해 보도록 하겠습니다. test라는 pod를 하나 만들고 service에 hello라는 DNS로 요청을 보내보았습니다.
이와같이 ClusterIP를 활용하면 서비스 간의 이름과 Ip를 통해서 내부 통신이 가능해 집니다.
그리고 서비스가 사용하는 가상 IP대역도 위와같은 명령어를 통해 확인할 수 있습니다.
NodePort로 외부에 노출하기
NodePort는 위에서도 말 했지만, ClusterIP를 Wrapping하는 구조입니다. NodePort서비스를 이용한다 해도, ClusterIP의 기능을 모두 사용할 수 있는 겁니다. NodePort를 이용하면 모든 쿠버네티스 노드의 동일 포트를 개방하여 서비스에 접근하는 방식을 가지게 됩니다.
위에 해당하는 노드로 정해진 포트로 들어오면 이를 ClusterIP의 지정된 포트로 바인딩해주는 작업을 하게됩니다.
그리고 이전에 만들었던 ClusterIP의 port를 지정해 주는 방법은 spec.clusterIp:spec.ports[*].port로 지정해 주었었습니다.
하지만 NodePort의 설정은 사뭇 다릅니다.
차이점은 고작 type을 ClusterIP --> NodePort로 바꾸어 주었습니다. 그리고 # nodePort: 31000으로 Node의 포트를 지정해주었는데, 이를 지정해 주지 않는다면 사용 가능한 포트로 자동 매핑됩니다.
그리고 이를 apply하면, CLUSTER-IP는 그도래고, TYPE만 NodePort로 변경된 것을 보실 수 있습니다. 그리고 이전에는 PORT(S)에 8080만 적혀 있었는데 오른쪽에 30616이 추가되었는데, 이가 Node의 Port라고 보시면 될거 같습니다.
더 자세히 descibe해서 hello service의 정보를 보니 Port는 http 8080/TCP이고, NodePort는 http 30616/TCP가 잘 지정된 것을 확인할 수 있습니다.
그리고 node의 InternalIP주소를 알아낸다음에 30616번 포트로 요청을 보내게 되면, 응답이 잘 오는 것을 보실 수 있습니다.
그리고 당연하지만 minikube cluster안에서 hello service의 내부 가상 IP상의 8080번 포트로 요청을 잘 보내지는 것을 보실 수 있습니다.
LoadBalancer로 클라우드 프로바이더의 로드밸런서 연동
다음은 LoadBalancer입니다. 이는 아쉽게도 보통 클라우드 프로바이더의 로드밸런서와 연동되는 기능입니다. 보통 AWS의 경우 elb, alb를 지원합니다. 이 로드밸런서를 동적으로 관리할 수 이쎅 하는 기능이라고 보시면 됩니다.
하지만 우리는 minikube클러스터 상에서 진행되는데, 이는 테스트 환경입니다. 이러한 테스트 클러스터에서는 로드벨런서의 활용이 어렵습니다. 몇가지 추가적인 절차를 통해서 가능은 하지만, 매우 어려워서 추후에 포스팅 해보겠습니다.
바로 Load Balancer를 만들기 위한 매니페스트 파일을 보도록 하겠습니다. 이에 type만 LoadBalancer로 바뀐것을 확인할 수 있습니다.
그리고 이를 실제로 apply해보면, minikube환경에서 loadbalancer를 활용하지 못한다고 앞에서 말했는데, EXTERNAL-IP가 <pending>으로 뜨는것을 확인할 수 있습니다.
ExternalName로 외부로 요청 전달
ExternalName은 위 3개의 서비스와 성격이 매우 다릅니다. 이는 클러스터 외부에 존재하는 레거시 시스템을 쿠버네티스로 마이그레이션하는 과정에 활용 가능한 서비스입니다. 그리고 DNS설계를 해보신분이라면 이가 CNAME레코드와 동일한 역할을 수행하는 것이라고 느껴지실 겁니다.
이전과는 매니페스트 파일이 간단하다는게 느껴지실 겁니다. type으로 ExternalName을 지정해 주었습니다. 그리고 eternalName으로 httpbin.org를 가리키고 있습니다.
실제로 httpbin.org는 운영중인 서비스입니다.
실제로 apply하고, service목록을 확인해보면, httpbin이라는 이름으로 ExternalName Type의 서비스가 하나 만들어 지고 EXTERNAL-IP가 httpbin.org로 지정된 것을 확인해볼 수 있습니다.
그리고 우리가 이전에 만들었던 test pod를 가지고 진짜 httpbin이라는 DNS에 요청을 보내면 외부와 연결이 잘 되어 응답이 오는 것을 확인할 수 있습니다.
/get경로에 대해서도 잘 응답이 오는 것을 확인할 수 있습니다.