쿠버네티스 클러스터 내에서 유동성이 높은 파드에게 불변의 엔드포인트를 제공하는 것이 서비스의 역할이다.
ping
을 날릴 수 없다.쿠버네티스 static pod 중에 kube-proxy 가 있다. kube-proxy 의 역할은 자기가 떠 있는 노드에 들어오는 트래픽을 가로채서 알맞은 파드에 리다이랙션 해주는 프록시 역할이다. 하지만 이는 userspace 프록시 모드로, 현재는 성능이 훨씬 좋은 iptables 프록시 모드가 기본이다. userspace 프록시는 네트워크 트래픽 관련 작업을 거의 다 userspace에 있는 kube-proxy에서 작업한 후 네트워크 패킷들을 넘기는데, 이 때 userspace의 패킷들은 kernel space를 거쳐서 proxy가 이뤄지므로 큰 비용이 든다. 이와 반대로 iptables 모드는 kube-proxy가 했던 작업들을 kernel space의 netfilter라는 모듈이 하므로 비용이 훨씬 적게 든다.
그렇다면 서비스가 생성될 때 kube-proxy가 어떻게 동작하는지 살펴보자
API 서버에서 서비스를 생성하면 바로 가상 IP주소가 할당된다.
API 서버는 모든 kube-proxy에게 새로운 서비스가 생성됐음을 통보한다.
kube-proxy는 해당 노드에 iptables를 수정하여 서비스 IP주소로 온 패킷을 가로채서 알맞은 파드로 보내도록 한다.
서비스와 관련된 (pod IP, 포트) 쌍을 Endpoint 오브젝트가 모니터링하고 있으므로 pod가 변경되면 Endpoint도 바뀌고, kube-proxy가 이를 알아채 다시 iptables를 알맞게 수정한다.
여기서 iptables와 netfilter라는 개념이 헷갈릴 수 있다. 실제로 패킷을 룰을 기반으로 다루는 것은 kernel space에 있는 netfilter가 수행한다. iptables는 userspace에 있으며, netfilter에 룰을 넣기위한 interface가 된다.
원리를 보고나서 서비스의 특징 중에, 서비스 IP로 ping
을 왜 날릴 수 없는지 알 수 있다. 결국 서비스 IP는 어떠한 네트워크 인터페이스에도 할당되지 않고 그저 (서비스 IP, 포트) 쌍으로 구성되어 있기만 할 뿐이다.
3가지 모두 외부에 서비스를 노출시키는 방법이다. 하지만 분명 차이가 있으며 용도 또한 다르므로 알아두는 것이 좋다.
NodePort
모든 노드에 특정 포트를 열어서 외부로부터 트래픽을 수신하는 방식이다. 그러면 사용자는 노드 IP:NodePort
주소로 요청을 보낼 수 있다. 보통 on-premise에서 서비스를 노출시킬 때 많이 사용한다.
apiVersion: v1
kind: Service
metadata:
name: nodeport
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 30123
selector:
app: example
Load Balancer
NodePort의 확장 버전이며, gcp나 aws와 같은 클라우드 인프라에서만 사용할 수 있다. 기본적으로 서비스들은 노드포트처럼 특정 포트가 노드마다 열려있지만, 외부에 노출되는 포인트는 Load Balancer 하나이다. 따라서 사용자가 접근할 때는 Load Balancer의 IP주소를 가지고 접속할 수 있다.
하지만 Load Balancer는 하나의 서비스에만 적용할 수 있어서, 여러 서비스를 노출시키고 싶으면 그에 맞게 여러 Load Balancer를 생성해줘야 하고, 그렇게 되면 비용이 많이 발생하게 된다.
apiVersion: v1
kind: Service
metadata:
name: loadbalancer
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: example
Ingress
주로 production 환경에서 외부로 노출시키는 방법이다. 왜냐하면 위의 Load Balancer와 달리 Ingress 하나로 여러 서비스들을 노출시킬 수 있기 때문이다. 그만큼 사용하기 복잡하다는 단점이 있다.
우선 인그레스가 하나의 서비스를 노출시키는 방식부터 살펴보자. 인그레스 요청은 도메인과 uri 경로를 기준으로 서비스를 노출시킨다. 그리고 인그레스는 항상 인그레스 컨트롤러와 같이 배포되어야 한다. 인그레스는 여러 서비스를 노출시킬 때의 규칙을 정의한 것일 뿐, 실제 트래픽을 다루는 것은 인그레스 컨트롤러가 한다.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example
spec:
rules:
- host: example.com
http:
paths:
- path: /
backend:
service:
name: example-service
port:
number: 80
사용자는 example.com
도메인으로 요청을 보낸다.
DNS 서버에서 example.com
을 인그레스 컨트롤러 IP로 변경하여 요청을 보낸다.
인그레스 컨트롤러에 요청이 갈 때 헤더에 example.com을 담아서 보낸다.
인그레스 컨트롤러는 받은 요청의 데이터를 기반으로 인그레스 → 서비스 → 엔드포인트를 통해서 실제로 요청을 받아야 하는 파드IP를 얻어낸다.
얻어낸 파드IP로 인그레스 컨트롤러가 트래픽을 보낸다.
이번엔 여러 서비스를 노출시키는 경우를 보자 여러 서비스를 노출시킬 때는 다른 도메인 이름이나 uri 경로 기준으로 나눠서 노출시킨다. 아래 그림은 도메인 이름을 기준으로 여러 서비스를 노출 시키는 사례이다.
uri 경로로 노출시킬 땐 아래와 같이 인그레스를 정의하면 된다.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example
spec:
rules:
- host: example.com
http:
paths:
- path: /foo
backend:
service:
name: foo-example-service
port:
number: 80
- path: /bar
backend:
service:
name: bar-example-service
port:
number: 80
이 때 단순히 경로가 같을 때 서비스를 나누지 않고 prefix나 pathType등을 설정하여 다양하게 조건을 만들어서 생성할 수 있다.
[발번역] Kubernetes NodePort vs LoadBalancer vs Ingress?? 언제 무엇을 써야 할까??