[Kubernetes] 워커 노드 내 Pod와 클러스터 외부 DB와의 통신

mrcocoball·2024년 6월 23일
0

Kubernetes

목록 보기
4/8

워커 노드 내 Pod와 클러스터 외부 DB의 통신

기존 DB를 StatefulSet으로 배포하던 것을 별도의 VM으로 이관하면서, 마이크로서비스 Pod가 VM에 설치된 DB와 연결되도록 설정해야 했다. (VPC는 동일)
보통, 같은 VPC 내에 있는 VM이라면 내부 통신이 가능하기 때문에 VM의 Public IP를 알고 있으면 연결이 가능하다. (다른 VPC라면 VPC 피어링이 필요하다)

예를 들어, 마이크로서비스 (Spring Boot)의 application.yml 내 DB 연결 정보가 다음과 같을 경우

  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://${POSTGRES_URI}:${POSTGRES_PORT}/${POSTGRES_DB}
    username: ${POSTGRES_USERNAME}
    password: ${POSTGRES_PASSWORD}

Deployment 명세 내에서 환경 변수를 다음과 같이 지정하면 연결이 가능하다.

spec:
  # ...
  template:
	# ...
    spec:
	  # ...
      containers:
        - name: ....
          # ...
          env:
            - name: POSTGRES_URI
              value: "VM의 Public IP"
            - name: POSTGRES_DB
              value: "VM 내 설치된 DB의 데이터베이스명"
            - name: POSTGRES_PORT
              value: "VM 내 설치된 DB의 포트 번호"

다만 이렇게 환경 변수에 VM의 Public IP를 직접 입력해서 넣을 경우 다음과 같은 문제가 발생한다.

  • VM의 Public IP를 직접 입력한 경우가 많아질 수록 관리 지점이 늘어난다
    -> 가령, 같은 Public IP를 공유하는 Deployment가 6개일 경우 Public IP가 바뀌면 6개의 Deployment의 설정을 모두 바꿔야 한다
  • VM의 Public IP를 바꿀 때마다 Pod의 재배포가 발생된다

MSP 측에선 단순히 IP 정보를 환경 변수로 직접 넣어도 된다고 하였지만 위와 같은 이유로 보다 효과적인 방법이 없는지 찾았고,
다행히 이전에 잠깐 배웠었던 Headless Service와 Endpoints를 활용하여 관리 지점과 Pod의 재배포를 최소화하는 방법을 찾았다.

Headless Service, Endpoints

Headless Service는 Kubernetes의 Service의 ClusterIP를 None으로 지정한 경우를 의미하며 공식 문서에서는 다음과 같이 소개하고 있다.

Sometimes you don't need load-balancing and a single Service IP. In this case, you can create what are termed headless Services, by explicitly specifying "None" for the cluster IP address (.spec.clusterIP).
때로는 로드 밸런싱과 단일 서비스 IP가 필요하지 않을 때가 있다. 이 경우, 클러스터 IP 주소(.spec.clusterIP)에 "None"을 명시적으로 지정하여 헤드리스(Headless) 서비스를 만들 수 있다.

You can use a headless Service to interface with other service discovery mechanisms, without being tied to Kubernetes' implementation.
헤드리스 서비스는 Kubernetes의 구현에 얽매이지 않고 다른 서비스 디스커버리 메커니즘과 인터페이스할 수 있게 해준다.

요약하자면 Kubernetes의 구현을 사용하지 않고 다른 외부 서비스와의 통합을 할 수 있도록 만들어진 것이 Headless Service라고 하는데,
대표적인 경우가 내가 마주한 상황이었던 Kubernetes 클러스터 내의 노드 -> 클러스터 외부의 VM 간의 통신이다.

여기서 Service 내에 Selector를 명시한 경우와 명시하지 않는 경우에 따라 처리 방법이 달라지는데, VM의 경우 클러스터 외부에 존재하여 Selector로 감지가 불가능하다. 이 경우에 공식 문서에 따르면 다음과 같이 처리된다.

셀렉터가 정의되지 않은 헤드리스 서비스의 경우, 컨트롤 플레인은 EndpointSlice 객체를 생성하지 않는다. 그러나 DNS 시스템은 다음 중 하나를 찾고 구성한다.

  • type: ExternalName 서비스에 대한 DNS CNAME 레코드.
  • ExternalName을 제외한 모든 서비스 타입에 대해, 서비스의 준비된 엔드포인트의 모든 IP 주소에 대한 DNS A / AAAA 레코드.
    • IPv4 엔드포인트의 경우, DNS 시스템은 A 레코드를 생성
    • IPv6 엔드포인트의 경우, DNS 시스템은 AAAA 레코드를 생성
  • 셀렉터 없이 헤드리스 서비스를 정의할 때는 포트가 targetPort와 일치해야 한다

EndpointSlice를 만들지 않고 위의 작동 방식에 따라 서비스를 연결한다고 하는데, 이 때 Endpoints 리소스를 직접 생성할 경우 위의 작동 방식이 아닌, Endpoints에서 명시한 주소와 포트로 연결을 해준다고 한다.

따라서 외부 VM의 Public IP + 포트로 Headless Service, Endpoints를 만들면 클러스터 내의 Pod들은 이 Service, Endpoints를 통해 클러스터 외부의 VM과 통신을 Kubernetes Service DNS Name으로 할 수 있게 된다.

Headless Service와 Endpoints 명세 예시는 다음과 같다.

# 헤드리스 서비스
apiVersion: v1
kind: Service
metadata:
  name: db-service
spec:
  clusterIP: None
  ports:
    - protocol: TCP
      port: 5432 # 외부로 노출된 포트
      targetPort: 5432 # 연결하려는 포트
  
# 헤드리스 서비스의 Endpoints
apiVersion: v1
kind: Endpoints
metadata:
  name: db-service
subsets:
  - addresses:
      - ip: 192.168.0.70 # VM의 Public IP
    ports:
      - port: 5432 # VM의 포트

이렇게 될 경우 클러스터 내의 Pod들은 생성된 서비스의 DNS Name, 즉 db-service.${namespace}.svc.cluster.local 을 사용하여 클러스터 외부의 VM의 특정 포트와 연결할 수 있게 된다. (이 경우 같은 VPC 내 192.168.0.70:5432와 통신이 가능해진다)
여기서는 네임스페이스를 따로 명시하지 않고 kubectl apply -f 를 사용해서 배포한다고 가정하면 위의 서비스는 default 네임스페이스에 배포되어 db-service.default.svc.cluster.local이 될 것이다.

그리고 이를 Deployment 명세에 반영한다면

spec:
  # ...
  template:
	# ...
    spec:
	  # ...
      containers:
        - name: ....
          # ...
          env:
            - name: POSTGRES_URI
              value: db-service.default.svc.cluster.local # 네임스페이스가 둘 다 default로 동일하다면 db-service만 입력해도 됨
            - name: POSTGRES_DB
              value: "VM 내 설치된 DB의 데이터베이스명"
            - name: POSTGRES_PORT
              value: "VM 내 설치된 DB의 포트 번호"

이렇게 바뀌게 된다.

이렇게 해서 Headless Service를 만들고 연결시킴으로서 VM의 Public IP를 직접 등록했을 때와의 비교해서 장점은 다음과 같다.

  • 연결하려는 VM의 Public IP + Port를 이미 Service로 배포했기 때문에 Public IP가 변경되더라도 Service 내부 설정만 바꾸면 된다.
  • 위의 내용과 이어지는데 Service 내부 설정만 바뀌면(=Service만 재배포되면) 다른 Pod들은 설정을 바꿀 필요가 없으므로 재배포가 필요 없어진다.

물론 장점만 있는 것은 아닌데, 아무래도 연결된 Pod의 환경변수로 IP를 직접적으로 알아볼 수 없다는 단점이 있다. 그러나 Headless Service, Endpoints 명세 파일의 위치만 알고 있다면 큰 단점은 되지 않을 것이라 생각한다.

이번 사례에서는 Public IP, 즉 IPv4 엔드 포인트에 대해 다뤘는데 DNS CNAME 레코드를 사용해야 하는 경우엔 externalName을 사용하면 되고 원리는 비슷할 것으로 추측된다.

Reference

https://kubernetes.io/docs/concepts/services-networking/service/#headless-services

profile
Backend Developer

0개의 댓글