쿠버네티스 교과서 - 03 - 네트워크를 통해 서비스에 파드 연결하기

jjunhwan.kim·2024년 5월 7일
0

쿠버네티스

목록 보기
2/2
post-thumbnail

쿠버네티스 내부의 네트워크 트래픽 라우팅

  • 파드는 쿠버네티스에서 부여한 IP 주소를 가진다.
  • 파드가 다른 파드와 통신할 때 IP 주소가 필요하다.
  • 파드가 새로운 파드로 교체될 때 IP 주소가 바뀐다. 새로운 IP 주소는 쿠버네티스 API를 통해서만 파악할 수 있다.
# 두 개의 디플로이먼트를 실행한다.
kubectl apply -f sleep/sleep1.yaml -f sleep/sleep2.yaml

# 파드가 완전히 시작될 때까지 기다린다. 
kubectl wait --for=condition=Ready pod -l app=sleep-2

# 두 번째 파드의 IP 주소를 출력한다.
kubectl get pod -l app=sleep-2 --output jsonpath='{.items[0].status.podIP}'

# 첫 번째 파드에서 두 번째 파드로 ping을 2번 보낸다.
kubectl exec deploy/sleep-1 -- ping -c 2 $(kubectl get pod -l app=sleep-2 --output jsonpath='{.items[0].status.podIP}')
  • 아래는 ping의 결과이다. 정상적으로 동작한 것으로 보면 파드는 IP 주소를 통해 서로 통신한다.
PING 10.1.0.22 (10.1.0.22): 56 data bytes
64 bytes from 10.1.0.22: seq=0 ttl=64 time=0.976 ms
64 bytes from 10.1.0.22: seq=1 ttl=64 time=0.135 ms

--- 10.1.0.22 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.135/0.555/0.976 ms
  • 쿠버네티스가 만든 가상 네트워크는 클러스터 전체를 커버한다. 따라서 IP 주소만 있으면 서로 다른 노드에서 실행 중인 파드끼리도 통신이 가능하다.
  • 위의 파드는 디플로이먼트가 관리한다. 파드를 삭제하면 디플로이먼트가 새로운 파드를 생성한다. 이 때 IP 주소가 변경될 수 있다.
# 파드의 IP 주소를 확인한다.
kubectl get pod -l app=sleep-2 --output jsonpath='{.items[0].status.podIP}'

# 파드를 삭제한다.
kubectl delete pod -l app=sleep-2

# 파드의 IP 주소를 확인한다. 디플로이먼트가 삭제된 파드를 다시 생성하였으므로 새로운 IP 주소가 부여된다.
kubectl get pod -l app=sleep-2 --output jsonpath='{.items[0].status.podIP}'
  • IP 주소는 언제든지 바뀔 수 있다. 인터넷에서는 IP 주소에 이름을 붙이는 도메인 네임을 도입하여 이 문제를 해결했다.
  • 쿠버네티스에도 전용 DNS 서버가 있다. 서버가 서비스 이름과 IP 주소를 대응시켜준다.
  • 서비스는 파드가 가진 네트워크 주소를 추상화한 것이다. 디플로이먼트가 파드와 컨테이너를 추상화한 것과 같다. 서비스는 자신만의 IP 주소를 갖는다.
  • 이 주소로 요청을 보내면 쿠버네티스가 서비스와 연결된 파드의 실제 IP 주소로 요청을 연결한다.
  • 서비스와 파드의 연결 관계는 디플로이먼트와 파드의 연결 관계와 마찬가지로 레이블 셀렉터를 사용하여 느슨한 연결을 갖는다.
  • 아래는 서비스에 필요한 최소한의 YAML 정의이다.
# API 버전
apiVersion: v1
kind: Service

# 서비스 이름이다. 도메인 네임으로 사용된다.
metadata:
  name: sleep-2

# 서비스 정의에는 셀렉터와 포트 목록이 포함되어야 한다.
spec:
  selector:
    app: sleep-2 # app 레이블의 값이 sleep-2인 모든 파드가 대상이다.
  ports:
    - port: 80 # 80번 포트를 주시하다가 파드의 80번 포트로 트래픽을 전달한다.
  • 서비스는 kubectl apply 명령을 사용하여 배포한다.
kubectl apply -f service.yaml
# 서비스의 상세 정보를 출력한다. 
kubectl get svc sleep-2

NAME      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
sleep-2   ClusterIP   10.108.29.118   <none>        80/TCP    11s
  • 서비스 이름(도메인 네임)으로 ping을 보내본다. 도메인 네임은 IP로 잘 바뀌지만 ping은 실패한다. 이전 예제에서는 파드 IP로 ping을 성공했었다. 지금은 서비스 IP 주소로 ping이 실패한다. 그 이유는 ping 명령이 ICMP 프로토콜을 사용하는데 서비스 리소스가 지원하지 않는 프로토콜이다.
# sleep-1 파드에서 sleep-2 파드로 ping을 보낸다.
kubectl exec deploy/sleep-1 -- ping -c 1 sleep-2

# sleep-2 서비스의 클러스터 IP 주소와 같다.
PING sleep-2 (10.108.29.118): 56 data bytes

# 실패한다.
--- sleep-2 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss
command terminated with exit code 1

파드와 파드 간 통신

  • 서비스를 yaml에 정의할 때 유형(type)을 정의할 수 있다. 기본 값은 클러스터IP(ClusterIP)이다.

  • 클러스터IP는 클러스터 전체에서 사용되는 IP 주소를 생성한다. 이 IP 주소는 파드가 어떤 노드에 있더라도 접근 가능하다. 하지만 클러스터 내에서만 유효하다.

  • 따라서 클러스터IP는 파드와 파드 간 통신에서만 쓰인다.

  • 웹 애플리케이션과 API 서버로 이루어진 예제를 아래와 같이 실행해본다.

# 웹 애플리케이션과 API 서버 두 개의 디플로이먼트를 실행한다.
kubectl apply -f numbers/api.yaml -f numbers/web.yaml

# 준비가 끝날 때까지 기다린다.
kubectl wait --for=condition=Ready pod -l app=numbers-web

# 웹 애플리케이션에 포트 포워딩을 적용한다.
kubectl port-forward deploy/numbers-web 8080:80
  • localhost:8080에 접속하여 버튼을 클릭하면 아래와 같이 오류가 발생한다. 웹 애플리케이션에서 numbers-api 도메인으로 API를 호출하는데 오류가 발생한 것이다.

  • 쿠버네티스 내부 DNS 서버에 numbers-api 서비스를 등록한다.

kubectl apply -f numbers/api-service.yaml
  • 아래는 numbers-api 서비스의 yaml 파일이다. 아래 서비스는 도메인 네임이 numbers-api이고 해당 도메인으로 요청시 app 레이블의 값이 numbers-api 인 모든 파드의 80번 포트로 요청을 전달한다.
apiVersion: v1
kind: Service

metadata:
  name: numbers-api # 서비스 이름이 도메인 네임이 된다.

spec:
  ports:
    - port: 80
  selector:
    app: numbers-api
  type: ClusterIP
  • 다시 포트포워딩을 시작하고 localhost:8080에 접속하여 버튼을 클릭하면 아래와 같이 랜덤 숫자가 잘 출력된다.

  • 웹 애플리케이션에서 numbers-api로 요청을 보내면 위에서 정의한 서비스에 의해 API 서버 파드로 요청이 전달되는 것이다.

  • API 파드는 디플로이먼트가 관리한다. 수동으로 파드를 지워도 대체 파드가 생성된다. 생성된 파드 역시 API 서비스에 정의된 레이블 셀렉터와 일치하므로 웹 애플리케이션에서 API 서버에 요청을 보내면 기존처럼 잘 전달된다.

# API 파드의 이름과 IP 주소를 확인한다.
kubectl get pod -l app=numbers-api -o custom-columns=NAME:meta
data.name,POD_IP:status.podIP

# 아래와 같이 출력된다.
NAME                           POD_IP
numbers-api-545b9d9ccd-nmp2c   10.1.0.26

# API 파드를 수동으로 삭제한다.
kubectl delete pod -l app=numbers-api

# 아래와 같이 출력된다.
pod "numbers-api-545b9d9ccd-nmp2c" deleted

# API 파드의 이름과 IP 주소를 확인한다.
kubectl get pod -l app=numbers-api -o custom-columns=NAME:metadata.name,POD_IP:status.podIP

# 아래와 같이 출력된다.
NAME                           POD_IP
numbers-api-545b9d9ccd-9cg8k   10.1.0.27
  • API 파드의 IP 주소가 바뀌어도 서비스 IP는 변경되지 않았다.
  • 웹 애플리케이션은 API 파드의 IP 주소로 요청하는게 아니라 서비스 도메인 네임으로 요청한다.
  • 서비스의 IP 주소는 그대로 이므로 기존처럼 잘 동작한다.
  • 결론적으로 서비스는 웹 애플리케이션 파드와 API 파드의 결합을 분리하므로 API 파드가 대체되어도 웹 애플리케이션 파드는 영향을 받지 않는다.

외부 트래픽을 파드로 전달하기

로드 밸런서

  • 쿠버네티스에는 클러스터 외부에서 들어오는 트래픽을 파드에 전달하는 여러 가지 방법이있다.
  • 간단한 방법으로 로드밸런서라는 유형의 서비스를 사용한다.
  • 로드밸런서는 트래픽을 받은 노드에서, 다른 노드에서 실행되는 파드에도 트래픽을 전달할 수 있다.
  • 로드밸런서 서비스는 클러스터로 트래픽을 전달해 주는 외부 로드밸런서와 함께 동작한다. 레이블 셀렉터와 일치하는 파드로 트래픽을 전달한다.
  • 아래는 웹 애플리케이션에 트래픽을 전달하는 로드밸런서 서비스의 정의이다.
apiVersion: v1
kind: Service

metadata:
  name: numbers-web

spec:
  ports:
    - port: 8080 #서비스가 주시하는 포트
      targetPort: 80 # 트래픽이 전달될 파드의 포트
  selector:
    app: numbers-web
  type: LoadBalancer # 로드밸런서 서비스
  • 이 서비스는 8080번 포트를 주시하다가 해당 포트로 웹 트래픽이 들어오면 웹 애플리케이션 파드의 80번 포트로 전달한다.
  • 따라서 이 서비스를 배포하면 위에서 했던 kubectl로 포트포워딩을 설정하지 않아도 된다.
# 로드밸런서 서비스를 배포한다.
kubectl apply -f numbers/web-service.yaml

# 아래와 같이 출력된다.
service/numbers-web created

# 서비스의 상세 정보를 확인한다.
kubectl get svc numbers-web

# 아래와 같이 출력된다. CLUSTER IP는 클러스터안에서의 IP 주소이다. EXTERNAL IP 주소로 들어오는 트래픽을 클러스터에 전달한다. EXTERNAL IP는 클러스터에서 제공되는 주소이다.
NAME          TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
numbers-web   LoadBalancer   10.105.62.64   localhost     8080:30752/TCP   8s

# 애플리케이션의 URL을 EXTERNAL IP로 출력한다.
kubectl get svc numbers-web -o jsonpath='http://{.status.loadBalancer.ingress[0].*}:8080'
  • 로드밸런서 서비스는 실제 IP 주소를 부여받는다. 클라우드에서 위의 명령을 실행하면 공인 IP 주소를 부여받는다. 현재는 로컬 환경이므로 localhost로 출력된다.
  • 로드밸런서 서비스는 환경에 따라 조금 다르게 동작한다.
  • 로컬 개발 환경에서 도커 데스크톱을 사용하면 단일 컴퓨터에서 클러스터가 동작한다. 따라서 로컬 컴퓨터의 네트워크 스택과 통합되어 로드밸런서 서비스가 로컬 호스트의 주소를 사용한다. 모든 로드밸런서 서비스가 localhost로 외부에 공개되므로 여러 개의 로드밸런서 서비스를 사용하려면 포트를 다르게 설정해야한다.
  • AKS나 EKS 같은 클라우드 환경의 쿠버네티스는 로드밸런서 서비스를 배포하면 실제 로드밸런서가 만들어진다. 로드밸런서 서비스의 IP 주소도 다르고, 공인 IP 주소로 인터넷에서 접근가능하다.

노드포트

  • 외부에서 클러스터로 들어오는 트래픽을 파드로 전달하는 역할을 하는 서비스 리소스 유형이 한 가지 더 있는데 바로 노드포트이다.
  • 노드포트 서비스는 클러스터를 구성하는 모든 노드가 이 서비스에 지정된 포트를 주시하며 들어온 트래픽을 대상 파드의 대상 포트로 전달한다.
  • 노드포트 서비스는 설정된 포트가 모든 노드에서 개방되어 있어야 하므로 로드밸런서 서비스만큼 유연하지는 않다.
apiVersion: v1
kind: Service

metadata:
  name: numbers-web-node

spec:
  ports:
    - port: 8080 # 다른 파드가 서비스에 접근하기 위해 사용하는 포트
      targetPort: 80 # 대상 파드에 트래픽을 전달하는 포트
      nodePort: 30080 # 서비스가 외부에 공개되는 포트
  selector:
    app: numbers-web
  type: NodePort # 노드의 IP 주소를 통해 접근 가능한 서비스

쿠버네티스 클러스터 외부로 트래픽 전달하기

  • 쿠버네티스는 거의 모든 서버용 소프트웨어를 실행할 수 있지만 데이터베이스 같은 스토리지 컴포넌트 등은 쿠버네티스 외부에서 주로 동작한다.
  • 클라우드 환경이라면 주로 매니지드 데이터베이스를 활용한다.
  • 클러스터 외부를 기리키는 도메인 네임 해소에도 쿠버네티스 서비스 리소스를 활용할 수 있다.

익스터널네임

  • 익스터널네임 서비스는 애플리케이션 파드에서 로컬 네임을 사용하고, 쿠버네티스 DNS 서버에 이 로컬 네임을 조회하면 외부 도메인으로 해소해 주는 방식이다.
  • 예를 들어 파드가 로컬 클러스터 네임 db-service를 사용하면 쿠버네티스 DNS 서버에서 외부 도메인 app.mydatabase.io로 해소한다.
# 현재 배포된 클러스터 IP 서비스를 삭제한다.
kubectl delete svc numbers-api

# 익스터널네임 서비스를 배포한다.
kubectl apply -f numbers-services/api-service-externalName.yaml

# 서비스 상세 정보를 확인한다.
kubectl get svc numbers-api

# 새로운 서비스는 깃허브의 도메인을 가리킨다.
NAME          TYPE           CLUSTER-IP   EXTERNAL-IP                 PORT(S)   AGE
numbers-api   ExternalName   <none>       raw.githubusercontent.com   <none>    10s
  • numbers-web 웹 애플리케이션은 기존의 API 주소인 http://numbers-api/sixeyed/kiamol/master/ch03/numbers/rng 를 그대로 사용한다.
  • numbers-api는 외부 주소인 raw.githubusercontent.com 로 대체되어 http://raw.githubusercontent.com/sixeyed/kiamol/master/ch03/numbers/rng 가 호출된다.
  • 익스터널네임 서비스는 애플리케이션 설정에 포함하기 어려운 환경 간 차이를 반영할 때 유용하다.
  • 예를 들어 개발 환경에서는 로컬 도메인 네임을 파드에서 동작하는 테스트용 데이터베이스 서버에 연결하고 운영 환경에서는 실제 도메인에 연결된 운영 데이터베이스 서버에 연결하도록 할 수 있다.
  • 아래는 익스터널네임 서비스에 대한 정의이다.
apiVersion: v1
kind: Service

metadata:
  name: numbers-api

spec:
  type: ExternalName
  externalName: raw.githubusercontent.com # 로컬 도메인 네임을 해소할 외부 도메인
  • 익스터널네임 서비스는 애플리케이션이 사용하는 주소가 가리키는 대상을 치환해 줄 뿐 요청의 내용 자체를 바꾸어주지는 못한다.
  • TCP 프로토콜을 쓰는 컴포넌트라면 문제가 없지만 HTTP 프로토콜은 요청의 헤더에 대상 호스트명이 들어간다. 따라서 헤더의 호스트명이 익스터널네임 서비스의 응답과 다르면 HTTP 요청이 실패한다.
  • 따라서 HTTP를 사용하려면 헤더의 호스트명을 수정하도록 코드를 구현해야한다.

헤드리스 서비스

  • 클러스터 안에서만 유효한 로컬 도메인 네임을 외부 시스템으로 연결할 수 있는 방법이 한 가지 더있다.
  • 익스터널네임 서비스와 비슷하게 도메인 네임 대신 IP 주소를 대체해 주는 방법이다.
  • HTTP 헤더 문제를 해결하지는 못한다.
apiVersion: v1
kind: Service

metadata:
  name: numbers-api

spec:
  type: ClusterIP # selector 필드가 없으므로 헤드리스 서비스가 된다.
  ports:
    - port: 80

---
kind: Endpoints #한 파일에 두 번째 리소스를 정의한다.
apiVersion: v1

metadata:
  name: numbers-api
subsets:
  - addresses: # 정적 IP 주소 목록
    - ip: 192.168.123.234
    ports:
      - port: 80 # 각 IP 주소에서 주시할 포트
  • 헤드리스 서비스는 클러스터IP의 형태로 정의되지만 레이블 셀렉터가 없다. 따라서 대상 파드가 없다.
  • 헤드리스 서비스는 자신이 제공해야 할 IP 주소의 목록이 담긴 엔드포인트 리소스와 함께 배포된다.
  • 따라서 numbers-api 도메인 네임으로 보낸 요청이 192.168.123.234:80 으로 연결된다.
# 기존 서비스를 제거한다.
kubectl delete svc numbers-api

# 헤드리스 서비스를 배포한다.
kubectl apply -f numbers-services/api-service-headless.yaml

# 서비스의 상세 정보를 확인한다.
kubectl get svc numbers-api

# 서비스 자체의 유형은 클러스터 IP로 클러스터 내부의 가상 IP 주소를 가진다.
numbers-api   ClusterIP   10.106.122.106   <none>        80/TCP    10s
# 엔드포인트 상세 정보를 확인한다.
kubectl get endpoints numbers-api

# 클러스터IP가 실제 연결하는 주소를 볼 수 있다.
NAME          ENDPOINTS            AGE
numbers-api   192.168.123.234:80   74s

쿠버네티스 서비스의 해소 과정

0개의 댓글