서비스 (Service)

niyu·2022년 7월 29일
0

쿠버네티스 기초

목록 보기
7/15
post-thumbnail

파드는 일회성이다. 노드에서 파드를 제거해 다른 파드를 위한 공간을 확보하거나, 누군가 파드 수를 줄이거나, 클러스터 노드 실패로 인해 노드 간에 언제든지 이동할 수 있다.
또한 클라이언트는 서버 파드의 IP 주소를 미리 알 수 없다. k8s는 파드가 노드로 스케줄된 후 시작하기 전에 IP 주소를 할당하기 때문이다.

이렇게 파드는 쉽게 사라지고 생성되며 미리 IP 주소를 알 수 없기 때문에 파드와 직접 통신하는 대신 별도의 고정된 IP를 가진 서비스를 만들고 이 서비스를 통해 파드에 접근한다.

서비스

각 서비스는 서비스가 존재하는 동안 절대로 변경되지 않는 IP 주소와 포트가 있다. 클라이언트는 해당 IP 및 포트에 연결할 수 있고, 이런 연결은 해당 서비스를 지원하는 파드 중 하나로 라우팅된다.

서비스 기본 개념
서비스 기본 개념 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/5)

서비스는 여러 개의 파드에 접근할 수 있는 IP 하나를 제공한다. 본질적으로는 로드밸런서 역할이다.

서비스 생성

서비스에서도 레플리카셋과 동일하게 레이블 셀렉터 메커니즘이 그대로 적용된다.

서비스와 라벨 셀렉터
서비스와 라벨 셀렉터 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/5)

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80  # 서비스가 사용할 포트
    targetPort: 8080  # 서비스가 포워드할 컨테이너 포트
  selector:
    app: kubia  # app=kubia 레이블이 있는 모든 파드가 이 서비스에 포함된다

kubia 서비스는 80 포트로 들어오는 연결을 허용하고 각 연결을 대상으로 app=kubia 라벨 셀렉터에 매칭되는 파드 중 하나를 8080 포트로 라우팅한다.

🔎 여러 개의 포트 노출

kind: Pod
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080  # 포트 80은 파드의 8080 포트로 매핑
  - name: https
    port: 443
    targetPort: 8443  # 포트 443은 파드의 8443 포트로 매핑
  selector:
    app: kubia

80 포트와 443 포트를 파드의 8080 포트와 8443 포트로 포워딩한다.

🔎 이름이 지정된 포트 사용

각 파드의 포트에 이름을 부여할 수 있고 서비스 스펙에서 그 이름으로 포트를 설정할 수 있다.

kind: Pod
spec:
  ports:
  - name: http
    containerPort: 8080  # 포트 8080은 http라는 이름으로 설정
  - name: https
    containerPort: 8443  # 포트 8443은 http라는 이름으로 설정
apiVersion: v1
kind: Service
spec:
  ports:
  - name: http
    port: 80
    targetPort: http  # 포트 80은 컨테이너 포트의 이름이 http인 것에 매핑
  - name: https
    port: 443
    targetPort: https  # 포트 443은 컨테이너 포트의 이름이 https인 것에 매핑
  selector:
    app: kubia

서비스 연결 동작 과정

# 특정 Pod에 접속하여 서비스의 클러스터 IP로 HTTP 요청
$ kubectl exec kubia-7nog1 -- curl -s http://10.111.249.153

kubectl exec를 사용해 파드 중 하나에서 curl 명령을 실행해 서비스 연결 테스트하기
kubectl exec를 사용해 파드 중 하나에서 curl 명령을 실행해 서비스 연결 테스트하기 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/5)

컨테이너에 있는 파드들 중 하나에게 curl 명령을 실행하라고 k8s에게 지시했다. curl은 세 개의 파드와 연결돼 있는 서비스의 IP 주소로 HTTP 요청을 보냈다. k8s 서비스 프록시는 HTTP 연결을 가로채 세 개의 파드 중 랜덤으로 선택된 파드로 HTTP 요청을 리다이렉트한다. 파드 내의 동작 중인 Node.js는 요청을 처리하고 파드의 이름을 포함하는 HTTP 응답을 만들어 전송한다.

외부 서비스에서 외부 클라이언트로

특정 서비스를 외부에 노출하고 외부 클라이언트가 액세스할 수 있도록 하고 싶을 수도 있다. 외부에서 액세스할 수 있으려면 서비스 객체를 통해 IP를 노출해야 한다.

NodePort 서비스

NodePort 서비스의 각 클러스터 노드는 노드 자체의 이름을 통해 포트를 열고 포트에서 발생한 트래픽을 서비스로 리다이렉트한다.

모든 노드를 대상으로 포트를 예약한다. 모든 노드에 걸쳐 동일한 포트 번호를 사용하게 된다. 예를 들어 node1:8080, node2:8080 등으로 노드에 상관없이 포트 번호만 서비스에 지정된 것을 사용하면 접근이 가능하다. 그리고 들어오는 접속을 서비스 각 부분의 포트로 전송한다.

apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  type: NodePort  # 서비스 타입을 노드포트로 설정
  ports:
  - port: 80  # 서비스의 내부 클러스터 IP의 포트
    targetPort: 8080  # 서비스 대상 파드의 포트
    nodePort: 30123  # 각 클러스터 노드의 포트 30123을 통해 서비스에 엑세스 할 수 있다
  selector:
    app: kubia

NodePort로 타입을 설정하고 서비스가 모든 클러스터 노드와 바운드될 노드 포트를 지정한다. 포트 지정을 하지 않을 경우 임의의 포트로 지정된다.

외부 클라이언트는 노드 1 혹은 노드 2를 통해 노드 포트 서비스로 연결
외부 클라이언트는 노드 1 혹은 노드 2를 통해 노드 포트 서비스로 연결 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/5)

클러스터 노드 모두에 30123 포트를 서비스가 노출하고 있다. 포트로 연결이 들어오면 노드에 실행중인 한 개 혹은 여러 개의 임의로 선택된 파드로 리다이렉트된다. 첫 번째 노드의 30123 포트로 들어오는 연결은 첫 번째 노드에서 실행 중인 파드로 포워드되거나 두 번째 노드에서 실행 중인 파드로 포워드될 수 있다.

LoadBalancer 서비스

만일 클라이언트가 첫 번째 노드만을 가리킨다면 그 노드가 실패했을 때 클라이언트는 더 이상 서비스에 액세스할 수 없다. 그렇기 때문에 로드 밸런서를 노드 앞에 배치해 정상적인 모든 노드에 요청을 분산시키고 그 순간 오프라인 상태의 노드로 요청을 보내지 않도록 해야 한다.

AWS, GCP 같은 클라우드 서비스를 사용할때 사용가능한 옵션이다. k8s가 실행중인 클라우드 인프라스트럭처에 프로비전된 로드밸런서를 통해 서비스 액세스를 할 수 있게 된다.

로드 밸런서는 발생한 트래픽을 모든 노드에서 노드 포트로 리다이렉트한다. 클라이언트는 로드 밸런서 IP를 통해 서비스에 접속한다.

apiVersion: v1
kind: Service
metadata:
  name: kubia-loadbalancer
spec:
  type: LoadBalancer  # 쿠버네티스 클러스터를 호스팅하는 인프라에서 로드밸런서를 얻을 수 있다
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

로드밸런서 서비스로 연결하는 외부 클라이언트
로드밸런서 서비스로 연결하는 외부 클라이언트 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/5)

외부 클라이언트는 로드밸런서의 80 포트에 연결하고 노드들 중 하나의 노드에서 내부적으로 할당된 노드 포트로 라우팅된다.

브라우저는 NodePort에 직접 요청을 보내는 것이 아니라 Load Balancer에 요청하고 Load Balancer가 알아서 살아 있는 노드에 접근한다. 또한 로드 밸런서 서비스는 NodePort 타입의 확장형이기 때문에 서비스는 NodePort처럼 동작한다.

클러스터 외부 서비스에 연결

서비스를 외부로 노출할 필요가 있는 경우가 있다. 클러스터 내의 파드로 연결을 리다이렉트하는 대신, 외부의 IP와 포트로 리다이렉트하는 경우가 있다.

서비스는 파드를 직접 링크하지 않는다. 그 대신 엔드포인트(EndPoint) 라 불리는 리소스가 파드와 서비스 사이에 위치한다. 엔드포인트 리소스는 서비스에 의해 노출되는 IP 주소와 포트의 목록이다.

파드 셀렉터는 IP와 포트의 목록을 만드는 데 사용되고 엔드포인트 리소스에 저장된다. 파드 셀렉터 없이 서비스를 만들었다면 k8s는 엔드포인트 리소스조차 만들지 못한다.

🔎 수동으로 서비스 엔드포인트 설정

서비스의 엔드포인트를 서비스와 분리하면 엔드포인트를 수동으로 구성하고 업데이트할 수 있다.

apiVersion: v1
kind: Service
metadata:
  name: external-service  # 엔드포인트 오브젝트 이름과 일치해야 한다
spec:  # 셀렉터를 따로 정의하지 않는다
  ports:
  - port: 80

80 포트로 들어오는 연결을 처리한다. 셀렉터 없이 서비스를 생성했기 때문에 이에 상응하는 엔드포인트 리소스는 자동으로 생성되지 않는다.

apiVersion: v1
kind: Endpoints
metadata:
  name: external-service  # 서비스 이름과 일치해야 한다
subsets:                                
  - addresses:  # 서비스가 연결을 전달할 엔드포인트 IP 생성
    - ip: 11.11.11.11
    - ip: 22.22.22.22
    ports:
    - port: 80  # 엔드포인트의 대상 포트

두 개의 외부 엔드포인트를 갖는 서비스를 사용하는 파드
두 개의 외부 엔드포인트를 갖는 서비스를 사용하는 파드 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/5)

외부 엔드포인트를 갖는 서비스로의 연결을 맺는 3가지 파드를 보여준다.

🔎 외부 서비스를 위한 별칭 생성

외부 서비스의 별칭을 제공하는 서비스를 생성하기 위해 ExternalName 유형의 리소스를 갖는 서비스를 생성할 수 있다.

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: ExternalName  # 서비스 타입은 ExternalName으로 설정
  externalName: api.somecompany.com  # 실제 서비스의 전체 도메인 주소
  ports:
  - port: 80

이 서비스로 접근하면 설정해둔 외부 도메인 값으로 연결되서 클러스터 외부로 접근할 수 있다.

📚 서비스 종류

  • ClusterIP: 기본 서비스 타입으로, 클러스터 내부에 새로운 IP를 할당하고 여러 개의 Pod을 바라보는 로드밸런서 기능을 제공한다. 클러스터 안에서만 사용할 수 있다.
  • NodePort: 서비스 하나에 모든 노드의 지정된 포트를 할당한다. 클러스터 외부에서도 접근할 수 있다.
  • LoadBalancer : 클라우드에서 제공하는 로드밸런서와 파드를 연결한 후 해당 로드밸런서의 IP를 이용해 클러스터 외부에서 파드를 접근할 수 있도록 한다.
  • ExternalName: 클러스터 안에서 외부에 접근할 때 사용한다.

레디네스 프로브 (Readiness probe)

레디네스 프로브는 주기적으로 호출되어 특정 파드가 클라이언트 요청의 수락 여부를 결정한다. 컨테이너의 레디네스 프로브가 성공을 반환한다면 컨테이너가 요청을 받아들일 준비가 됐다는 신호다.

🔎 레디네스 프로브 타입

  • HTTP GET 프로브 : HTTP GET 요청을 컨테이너에게 보내고 응답의 HTTP 상태 코드를 통해 컨테이너가 준비된 상태인지 아닌지 판별한다.
  • TCP 소켓 프로브 : 컨테이너의 지정된 포트로 TCP를 연결한다. 연결되면 컨테이너는 준비로 간주된다.
  • Exec 프로브 : 프로세스를 실행시킨다. 컨테이너의 상태는 프로세스의 종료 상태 코드에 의해 결정된다.

🔎 레디네스 프로브 생성

apiVersion: v1
kind: ReplicaSet
spec:
...
    spec:
      containers:
        - name: kubia
          image: luksa/kubia
          readinessProbe:  # 파드의 각 컨테이너에 레디니스 프로브를 정의
              exec:  # ls /var/ready 명령어를 주기적으로 수행하여 존재하면 0(성공), 그렇지 않으면 다른 값(실패)
                command:
                  - ls
                  - /var/ready

레디네스 프로브는 주기적으로 컨테이너 안에서 ls /var/ready 명령을 실행한다. 파일이 있으면 레디네스 프로브는 성공하고 그렇지 않다면 실패한다.

🔎 레디네스 프로브 동작

컨테이너가 시작되면 k8s는 첫 번째 준비 확인을 수행하기 전에 구성 가능한 시간이 경과할 때까지 대기하도록 할 수 있다. 그런 다음 주기적으로 프로브를 호출하고 레디네스 프로브의 결과를 기반으로 수행한다. 파드가 준비돼 있지 않다고 알려주면 서비스 목록에서 제외된다. 파드가 다시 준비가 되면 서비스에 다시 추가된다.

라이브니스 프로브와 달리 컨테이너가 준비 확인에 실패한다면 종료되거나 다시 시작하지 않는다.

레디네스 프로브가 실패한 파드는 서비스의 엔드포인트에서 삭제된다
레디네스 프로브가 실패한 파드는 서비스의 엔드포인트에서 삭제된다 (출처: github.com/sungsu9022/study-kubernetes-in-action/issues/5)

파드의 레디네스 프로브가 실패한다면 파드는 엔드포인트 오브젝트에서 제거된다. 서비스로 연결하는 클라이언트는 파드로 리다이렉트되지 않는다.

🔎 레디네스 프로브가 중요한 이유

파드 중 하나에 문제가 발생할 경우 파드가 요청을 처리할 준비가 되지 않았다는 신호를 레디네스 프로브가 k8s에 알려준다. 클라이언트가 정상 상태인 파드하고만 통신하게 하고 시스템에 문제가 있다는 것을 알아차리지 못하게 한다.


References

0개의 댓글