본문 바로가기
Framework and Tool/Kubernetes

Kubernetes - Service intro

by ocwokocw 2022. 10. 21.

- 출처: Kubernetes in action

- 서비스

시스템을 마이크로 서비스로 구성하기로 했다면 파드가 통신하는 유형은 대게 아래 2가지 경우에 해당할것이다.
 
  • 클러스터 내부의 다른 파드에서 오는 HTTP 요청에 응답
  • 클러스터 외부 클라이언트에서 오는 HTTP 요청에 응답
 
만약 쿠버네티스를 사용하지 않는 상황에서 다른 서비스를 이용할때 보통은 환경설정 파일에 IP나 Host 이름을 설정하는 방식으로 사용한다. 하지만 쿠버네티스는 이런 방식으로 사용할 수 없는데 그 이유는 아래와 같다.
 
  • 파드는 일시적이다.
  • IP 주소 할당 시점이 노드에 파드를 스케줄링한 후 파드가 시작되기 바로 전이라 미리 알수 없다.
  • 쿠버네티스는 수평 스케일링을 제공한다. 이 의미는 파드수와 IP에 상관없이 여러 파드가 동일한 서비스를 제공한다는 의미이다.
 
서비스는 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들려고할 때 생성하는 리소스이다. 서비스는 존재하는 동안 IP와 Port가 변경되지 않는다. 만약 클라이언트에서 서비스로 요청을 보내면 서비스는 여러 파드 중 임의의 파드로 연결한다.
 
프론트엔드와 디비서버가 있다고 가정해보자. 외부 Client는 수십 혹은 수백대의 웹서버(프론트엔드) 파드에 연결 가능해야 한다. 연쇄적으로 프론트엔드 파드가 디비서버(파드)로 연결할 때 디비 파드 IP가 변경되어도 프론트엔드 파드에서 연결할 수 있어야 한다. 이렇게 하려면 아래와 같이 프론트엔드와 백엔드 디비서버에 대한 서비스를 각각 하나씩 구성해야 한다.
 
[외부 Client] --> [프론트엔드 서비스] --> [프론트엔드 파드 1 ... N] --> [백엔드 서비스] --> [백엔드 파드 1 .. N]
 

- 서비스 생성

서비스를 지원하는 파드가 많을 때 서비스에 연결하면 뒷단 파드로 로드밸런싱 된다고 했다. 그렇다면 특정 서비스와 연관이 있는 파드를 어떤 기준으로 알 수 있는가? 레플리케이션 컨트롤러에서 레이블 셀렉터와 같은 매커니즘으로 동작한다. 
 
kubectl expose를 사용하면 간단하게 서비스를 노출할 수 있지만 이 명령어 대신 yaml 파일을 사용해서 서비스를 생성한다.
 
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia
 
"kubectl create -f" 명령어로 서비스를 생성한 뒤 "kubectl get service"로 CLUSTER_IP가 할당되었는지 확인한다. 이 아이피는 내부에서만 접근 가능하다.
 
 
 
서비스를 생성했으니 클러스터내에서 서비스를 테스트해보자. 테스트를 할 수 있는 방법은 아래 3가지가 있다.
 
  • 클러스터 IP로 요청 보내고 응답을 로그로 남기는 파드 생성
  • 쿠버네티스 노드로 ssh 접속한 후 curl 실행
  • "kubectl exec" 명령어로 파드에서 curl 명령 실행
 
가장 간단하게 할 수 있는 마지막 방법을 사용한다. "kubectl exec" 명령어는 파드 컨테이너에서 원격으로 임의의 명령어를 사용할 수 있도록 해준다. "kubectl exec [파드명] -- [curl 명령어]"를 이용하여 실행한다. -- 는 쿠버네티스 명령의 끝을 의미한다. -- 뒤는 파드내에서 실행되는 명령을 의미한다. -- 가 없으면 -s 옵션이 kubectl exec 옵션으로 해석되어 잘못된 결과를 가져올 수 있다.
 
 
 
명령어를 실행했을 때 어떤일이 일어났는지 알아보자. 우선 쿠버네티스에 지정한 파드 내 컨테이너에서 curl 명령을 실행했다. 지정된 파드 컨테이너에서 curl을 실행해서 HTTP 요청 서비스 IP로 전송한다. 쿠버네티스 프록시가 서비스 IP와 3대의 연결을 가로채서 임의의 파드로 전달한다. 해당 파드에서 요청을 처리하고 응답한다.

- 서비스의 세션 어피니티(Session affinity)

curl 요청을 통해 확인했듯이 동일한 client 에서 여러 번 요청해도 서비스 프록시가 임의의 파드로 요청을 전송한다. 요구 사항에 따라 특정 client가 모든 요청을 매번 같은 파드로 리디렉션 하기를 원할 수 있다.
 
이때는 세션 어피니티 속성을 이용해야 한다. 서비스의 sessionAffinity 기본값은 None 인데 이를 ClientIP로 설정한다.
 
쿠키 기반의 세션 어피니티는 지원하지 않는다. 쿠버네티스 서비스는 HTTP 수준에서 작동하지 않기 때문에 쿠키를 알 수 없다.

- 여러 포트 노출

서비스는 여러 포트를 지원할 수 있다. 만약 파드가 HTTP로 8080, HTTPS로 8443을 수신받는다면 이런 기능이 필요할것이다. 하나의 서비스를 사용해서 멀티포트 서비스를 사용하면 단일 클러스터 IP로 모든 서비스가 노출된다.
 
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  - name: https
    port: 443
    targetPort: 8443
  selector:
    app: kubia
 
여러 포트가 있는 서비스를 만들때에는 각 포트에 이름을 지정해야 한다.
 
지금까지 살펴본 파드의 예제는 모두 대상 포트(targetPort)를 번호로 참조했지만 파드에서 포트이름을 지정하고 서비스에서 해당 이름을 참조하도록 구성할 수도 있다.
 
이름을 지정하면 단순히 알기 쉽도록 해주는것외에 장점이 있는가?라고 생각할 수도 있다. 이름으로 참조하면 서비스 스펙 변경없이 파드의 포트를 변경할 수 있다. 아래와 같은 레필르케이션 컨트롤러와 service yaml 파일로 생성하여 확인해보면 동작을 확인할 수 있다.
 
apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: ocwokocw/kubia
        ports: 
        - name: http
          containerPort: 8080
        - name: https
          containerPort: 8443

---

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http
    port: 80
    targetPort: http
  - name: https
    port: 443
    targetPort: https
  selector:
    app: kubia  
 
 

- 서비스 검색

서비스를 생성하면 파드에 접근할 수 있는 IP 주소와 포트가 생성되어 변경되지 않으므로 파드가 새로 생성되거나 삭제되어도 서비스 IP주소로 접근이 가능하다.
 
그런데 Client 파드는 서비스의 IP와 포트를 어떻게 알 수 있을까? 그래서 쿠버네티스는 서비스의 IP와 포트를 검색하는 방법을 제공한다.

- 환경변수를 통한 검색

파드가 시작되면 쿠버네티스는 해당 시점에 서비스를 가리키는 환경변수 세트를 초기화 한다. 그래서 파드를 생성하기 전에 서비스를 생성하면 환경변수에서 서비스 IP와 포트를 알 수 있다.
 
한번 환경변수는 확인해보자. "kubectl exec [파드명] env" 명령어를 실행하면 된다.
 
 
 
현재 클러스터에는 kubernetes와 kubia 2개의 서비스가 정의되어 있다. kubia 서비스 연관 변수로 KUBIA_SERVICE 접두사의 환경변수가 있는것을 확인할 수 있다.

- DNS를 통한 검색

kube-system namespace 파드 중에는 kube-dns 서비스와 coredns 라는 파드가 존재하다. 이 파드는 DNS 서버를 실행하고 클러스터 내 다른 모든 파드는 자동으로 해당 파드를 사용하도록 구성된다.
 
파드내 프로세스에서 수행된 모든 DNS 쿼리는 쿠버네티스 자체 DNS 서버로 처리된다. 파드 내부 DNS 서버 사용여부를 설정하고 싶다면 dnsPolicy 속성을 이용하면 된다.

- FQDN을 통한 서비스 연결

서비스 이름을 알고 있는 클라이언트는 환경 변수대신 FQDN으로 액세스를 할 수 있다. 프론트엔드에서 백엔드 디비 서비스로 연결하고 싶다면 아래와 같은 FQDN을 사용하면 된다.
 
backend-database.default.svc.cluster.local
 
  • backend-database: 서비스 이름
  • default: 네임스페이스
  • svc.cluster.local: 모든 클러스터 로컬 서비스이름에 사용되는 접미사
 
만약 프론트엔드와 디비 파드가 동일 네임스페이스에 존재한다면 default.svc.cluster.local 접미사를 생략할 수 있다. IP대신 FQDN을 통해서 서비스에 액세스해보자. 이전에는 curl을 수행할 때마다 kubectl exec 명령어를 사용했지만 이번에는 bash를 실행해서 컨테이너에 접속해본다.
 
 
 
우선 나도 위에서 실수했지만 bash 셸을 이용할 때 대화형 터미널 모드로 접속해야 하기 때문에 -it 옵션을 줘야 한다. 그리고 curl이 아닌 /bin/bash 셸을 실행한다.
 
위에서 부터 순차적으로 FQDN과 접미사, 네임스페이스를 차례대로 생략해도 동작한것을 확인할 수 있다.
 
이것이 가능한 이유는 파드 컨테이너 내부의 DNS resolver 가 구성되어 있기 때문이다. 컨테이너에서 /etc/resolv.conf 파일을 살펴보자.
 
 
 
 

'Framework and Tool > Kubernetes' 카테고리의 다른 글

Kubernetes - ingress  (0) 2022.10.30
Kubernetes - External service, Service export  (0) 2022.10.24
Kubernetes - job and cron job  (0) 2022.10.21
Kubernetes - ReplicaSet, DaemonSet  (0) 2022.10.14
Kubernetes - Replication controller  (0) 2022.10.11

댓글