지금까지는 파드로 컨테이너 기반 애플리케이션을 띄우고 디플로이먼트로 파드의 레플리카 수와 버전을 관리하였다. 하지만 이것만으로는 파드로 띄운 웹 애플리케이션을 쿠버네티스 클러스터 외부로 서비스할 수 없다. 왜냐하면 파드는 파드 고유의 IP를 가지긴 하지만 클러스터 내부용 IP이며, 파드는 클러스터의 상태에 맞게 생성되고 삭제되기 때문에 파드는 영구적인 리소스가 아니다. 즉, 파드가 장애가 났다고 판단하면 디플로이먼트가 파드를 재구동 시키면서 파드의 IP가 유지되지 않아서 파드가 클러스터 외부와 통신이 가능한다 하더라도 제대로된 서비스를 할 수가 없다.
이러한 파드의 특성 때문에 쿠버네티스의 클러스터 외부에서 파드와 통신할 수 있도록 하는 메커니즘이 필요하다. 필요한 기능은 먼저 파드가 영구적인 리소스가 아니기 때문에 재 구동된 파드들의 IP를 찾을 수 있도록 서비스 디스커버리 기능이 필요하고, 나머지 하나는 파드 레플리카 각각에 대해 트래픽을 골고루 전달할 수 있도록 로드밸런싱 기능이 필요하다.
쿠버네티스에서는 서비스 라는 오브젝트를 통해 서비스 디스커버리와 로드밸런싱 기능을 제공한다.
서비스
쿠버네티스에서 서비스는 일련의 파드로 구동 중인 애플리케이션을 네트워크 서비스로서 외부로 노출시키는 추상적인 방법이다. 쿠버네티스 서비스는 서비스 디스커버리 기능을 제공하므로 디스커버리 매커니즘을 사용하기 위해 애플리케이션을 변경할 필요가 없다. 그리고 서비스는 파드 집합에 대한 하나의 DNS 이름을 부여하고 파드 집합에 대해 로드밸런싱을 수행할 수 있다.
정의
쿠버네티스에서 서비스는 파드와 같은 쿠버네티스 오브젝트이다. 모든 쿠버네티스 오브젝트와 같이 서비스 정의를 apiserver에 POST 요청하여 새 인스턴스를 만들 수 있다. 서비스는 파드와 마찬가지로 쿠버네티스 매니페스트 파일로 정의될 수 있다.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
spec.selector
에서 서비스 디스커버리를 할 파드 집합을 셀렉터를 통해 결정한다.spec.type
: 서비스의 타입이다.spec.ports
: 서비스의 포트를 정의할 수 있다. 이 값은 오브젝트의 배열값이다.[].name
: 포트 이름이다. 포트를 여러 개 등록할 때 사용 용도를 구분(설명)하기 위해 값을 설정할 수 있다.[].protocol
: 기본 프로토콜은 TCP이며 다른 프로토콜(UDP 등) 을 사용할 수도 있다.[].port
: 서비스의 포트[].targetPort
:[].port
와 바인딩되는 파드의 포트
서비스 타입 (퍼블리싱)
어떤 파드 (애플리케이션)을 클러스터 외부로 노출시켜야 한다. 맨 앞에서 파드를 쿠버네티스 클러스터 외부로 노출시키기 위헤서 서비스를 사용한다고 하였다. 서비스의 spec에는 spec.type
이라고 서비스의 타입을 지정할 수 있는데, 이 타입에 따라 서비스가 쿠버네티스 클러스터 외부로 노출되는지 결정된다.
- ClusterIP (default): 서비스를 클러스터 내부 IP로 노출시킨다. 이 값을 선택하는 것은 오직 클러스터 내부로만 접근 가능하도록 하기 위해서이다.
- NodePort: 서비스를 각 노드의 IP에 정적 포트로 노출시킨다. NodePort 서비스가 라우팅하는 ClusterIP 서비스가 자동으로 생성된다. 이 서비스는
<NodeIP>:<NodePort>
로 요청함으로써 클러스터 외부로부터 서비스와 통신할 수 있다. - LoadBalancer: 클라우드 제공자의 로드 밸런서를 사용하여 서비스를 외부에 노출시킨다. 외부 로드밸런서가 라우팅되는
NodePort
와ClusterIP
서비스가 자동으로 생성된다. - ExternalName:
externalName
값과 함께CNAME
레코드를 반환함으로써 서비스와externalName
필드 컨텐츠를 매핑한다. 어떠한 프록시도 설정되지 않는다.
NodePort
type
필드를 NodePort
로 설정한 경우, 쿠버네티스 컨트롤 플레인은 --service-node-port-range
플래그 (default=30000-32767) 에 지정된 범위 내 포트번호를 할당한다. 각 노드는 해당 포트를 서비스에 프록시한다. 해당 서비스는 할당된 포트를 .spec.ports[*].nodePort
필드에 기록한다.
서비스 정의에서 .spec.ports[*].nodePort
가 명시되지 않은 경우에는 자동으로 할당되지만 값을 명시하면 명시한 값으로 포트가 할당된다. 할당된 .spec.ports[*].nodePort
값에 대하여 클러스터의 모든 노드에 해당 포트가 개방된다.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: MyApp
ports:
# By default and for convenience, the `targetPort` is set to the same value as the `port` field.
- port: 80
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
nodePort: 30007
서비스 디스커버리
쿠버네티스는 서비스를 통해 서비스 디스커버리 기능을 제공한다. 서비스에서 spec.selector
에 있는 레이블과 일치하는 파드에 대해서 서비스 디스커버리를 수행한다. 서비스 셀렉터에 대한 컨트롤러는 서비스에서 정의한 셀렉터와 일치하는 파드를 지속적으로 스캔한다. 그 후 모든 변경사항을 서비스와 같은 이름을 가진 엔드포인트 객체에 전달한다.
위의 예시에 있는 my-service
서비스를 띄우고 레이블이 app: MyApp
인 파드가 2개 떠있다고 가정해보자. kubectl로 서비스 이름과 동일한 이름을 가진 엔드포인트를 조회해보면 다음과 같은 결과를 얻을 수 있다.
$ kubectl get ep my-service
NAME ENDPOINTS AGE
my-service 10.244.141.76:80,10.244.207.132:80 2h
셀렉터가 없는 서비스
보통 서비스는 파드에 대한 접근을 추상화하지만, 다른 종류의 백엔드를 추상화하는 경우도 있다.
- 워크로드를 쿠버네티스로 전환하려는 경우, 쿠버네티스에 있지 않는 백엔드를 쿠버네티스로 실행하려는 경우
- 하나의 서비스에서 다른 네임스페이스의 서비스나 다른 클러스터로의 서비스를 지정하려는 경우
예를 들어, 기존 베어메탈 환경에서 쿠버네티스로 전환하려고 할 때 데이터베이스를 기존 환경에서 사용하던 것을 그대로 사용하려고 할 경우에 셀렉터가 없는 서비스를 이용할 수 있다. 셀렉터가 없는 서비스는 셀렉터가 없기 때문에 엔드포인트 객체를 자동으로 생성해주지 않는다. 따라서 개발자가 엔드포인트 객체를 직접 정의해야 한다.
apiVersion: v1
kind: Service
metadata:
name: db-service
spec:
clusterIP: None
internalTrafficPolicy: Cluster
ports:
- protocol: TCP
port: 3306
targetPort: 3306
name: mysql
---
apiVersion: v1
kind: Endpoints
metadata:
name: db-service
subsets:
- addresses:
- ip: 192.168.0.3
ports:
- port: 3306
가상 IP와 Service Proxy
클러스터 내 모든 노드들은 kube-proxy
를 구동한다. kube-proxy
는 ExternalName 타입이 아닌 서비스의 가상 IP 형식을 구현한다. 쿠버네티스에서 Service가 Service에 정의되어있는 레이블 셀렉터와 일치하는 레이블을 가진 파드를 대상으로 로드밸런싱을 한다고 했는데 실제 로드밸런싱은 kube-proxy
에서 구현한다. 이름에서 나와있듯이, kube-proxy
는 쿠버네티스 서비스로 오는 요청들을 관련된 엔드포인트로 전달하는 reverse proxy 역할을 하게된다.
kube-proxy mode
쿠버네티스에서는 3가지의 kube-proxy mode를 지원한다.
- User space proxy mode
- Iptables proxy mode (default)
- IPVS proxy mode
kube-proxy mode는 ConfigMap을 통해 설정할 수 있다. 일단 현재 쿠버네티스 클러스터에서 kube-proxy mode를 조회하는 방법을 알아보자. 아래 명령어를 입력하면 kube-proxy에 대한 설정을 볼 수 있다.
$ kubectl describe configmap -n kube-system kube-proxy
...
iptables:
masqueradeAll: false
masqueradeBit: null
minSyncPeriod: 0s
syncPeriod: 0s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
strictARP: false
syncPeriod: 0s
tcpFinTimeout: 0s
tcpTimeout: 0s
udpTimeout: 0s
kind: KubeProxyConfiguration
metricsBindAddress: ""
mode: ""
...
위의 내용은 kube-proxy 설정 중 일부인데 여기서 mode
가 kube-proxy의 모드이다. 빈 값으로 설정되어 있으면 기본값인 iptables
mode이다.
User space proxy mode
Userspace mode에서는 kube-proxy
는 서비스와 엔드포인트 객체가 추가되는지 삭제되는지 확인하기 위해 컨트롤 플레인을 지속적으로 확인한다. 그리고 Service의 ClusterIP와 Port로 향하는 트래픽을 캡쳐하는 iptables rule를 만든다. 이 rule은 트래픽을 kube-proxy로 전달하는 역할을 한다.
ClusterIP를 통해 요청이 들어오면 해당 요청은 iptables를 거쳐 kube-proxy로 요청이 전달되며 ClusterIP에 해당하는 서비스의 엔드포인트에 해당하는 파드로 연결해준다.
기본적으로 user space mode 내 kube-proxy는 Round-robin 알고리즘을 사용하여 백엔드를 선택한다.
Iptables proxy mode
Iptables mode에서는 kube-proxy는 서비스와 엔드포인트 객체가 추가되는지 삭제되는지 확인하기 위해 컨트롤 플레인을 지켜본다. 각 서비스에 대해 서비스의 clusterIP 및 port에 대한 트래픽을 캡처하고 해당 트래픽을 서비스의 백엔드 세트 중 하나로 리다이렉트(redirect)하는 iptables 규칙을 설치한다.
기본적으로 iptables 모드에서는 Round-robin이 아닌 임의의 백엔드를 선택한다.
iptables mode에서는 kernel space에 있는 netfilter에 의해 트래픽이 전달되기 때문에 user space mode와 같이 user space와 kernel space 사이를 스위칭 할 필요가 없어서 오버헤드가 줄어든다.
'DevOps > Kubernetes' 카테고리의 다른 글
쿠버네티스 Static Pods (0) | 2022.07.16 |
---|---|
[9] 쿠버네티스 볼륨 (0) | 2022.02.20 |
[7] 쿠버네티스 디플로이먼트 (0) | 2022.01.23 |
[6] 쿠버네티스 파드 (0) | 2022.01.05 |
[5] 쿠버네티스 오브젝트 (2) (0) | 2022.01.02 |
댓글