쿠버네티스 인 액션 - 서비스: 클라이언트가 파드를 검색하고 통신을 가능하게 함 (4)

hyeokjin·2022년 9월 6일
0

kubernetes

목록 보기
4/9
post-thumbnail

서비스 소개

쿠버네티스의 서비스는 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들려고 할 때 생성하는 리소스다. 각 서비스는 서비스가 존재하는 동안 절대 바뀌지 않는 ip주소와 포트가 있다. 클라이언트는 해당 ip와 포트로 접속한 다음 해당 서비스를 지원하는 파드 중 하나로 연결된다. 이런 방식으로 서비스의 클라이언트는 서비스를 제공하는 개별 파드의 위치를 알 필요 없으므로, 이 파드는 언제든지 클러스터 안에서 이동할 수 있다.

서비스 생성

서비스 연결은 서비스 뒷단의 모든 파드로 로드밸런싱된다. 어떤 파드가 서비스의 일부분인지 아닌지를 정의하는 방법은 레이블 셀렉터를 이용하는 것이다.

이 전 장에서 node.js 애플리케이션이 포함된 파드의 세 개의 인스턴스를 실행하는 레플리케이션컨트롤러를 만들었다. 레플리케이션컨트롤러를 다시 생성하고 이 세 개에 관한 서비스를 만들어 보자.

YAML 디스크럽터를 통한 서비스 생성

kubia-svc.yaml

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

kubectl create를 사용해 yaml 파일을 게시해 서비스를 생성하자.

$ kubectl create -f kubia-svc.yaml
service/kubia created

새 서비스 검사하기

YAML을 게시한 후 네임스페이스의 모든 서비스 리소스를 조회하고 서비스에 내부 클러스터 IP가 할당됐는지 확인할 수 있다.

$ kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.36.0.1     <none>        443/TCP   20h
kubia        ClusterIP   10.36.8.134   <none>        80/TCP    13s

서비스에 할당된 IP 주소를 보여준다. 클러스터 IP 이므로 클러스터 내부에서만 액세스할 수 있다. 서비스의 기본 목적은 파드 그룹을 클러스터의 다른 파드에 노출시키는 것이지만 대개 서비스를 외부로 노출하기를 원할 것이다.

클러스터 내에서 서비스 테스트

몇 가지 방법으로 클러스터 내에서 서비스로 요청을 보낼 수 있다.

  • 서비스의 클러스터 IP로 요청을 보내고 응답을 로그로 남기는 파드를 만든다. 그런 당므 파드의 로그를 검사해 서비스의 응답이 무엇인지 확인할 수 있다.
  • 쿠버네티스 노드로 SSH 접속하고 curl 명령을 수행할 수 있다.
  • kubectl exec 명령어로 기존 파드에서 curl 명령을 실행할 수 있다.

실행 중인 컨테이너에 원격으로 명령어 실행

kubectl exec 명령어를 사용하면 기존 파드의 컨테이너 내에서 원격으로 임의의 명령어를 실행할 수 있다.
컨테이너의 내용, 상태, 환경을 검사할 때 유용하다.

$ kubectl exec kubia-5rpz4 -- curl -s http://10.36.8.134
You've hit kubia-95xvv

명령을 실행했을 때 파드의 컨테이너 내에세 curl 명령을 실행하도록 쿠버네티스에 지시했다. curl은 HTTP 요청을 서비스 IP로 보냈다. 이 IP에는 세 개의 파드가 연결돼 있다.
쿠버네티스 서비스 프록시가 연결을 가로채서 세 개의 파드 중 임의의 파드로 요청을 전달한다. 해당 파드 내에서 실행 중인 node.js는 요청을 처리하고 해당 파드의 이름을 포함하는 HTTP 응답을 반환한다.
curl은 표준 출력으로 응답을 출력하고 이를 kubectl이 있는 로컬 시스템의 표준 출력에 다시 표시한다.

서비스의 세션 어피니티 구성

특정 클라이언트의 모든 요청을 매번 같은 파드로 리다이렉션하려면 서비스의 세션 어피니티 속성을 기본값 None 대신 ClientIP로 설정한다.


apiVersion: v1
kind: Service
spec:
  sessionAffinity: ClientIP
...

동일한 서비스에서 여러 개의 포트 노출

서비스는 단일 포트만 노출하지만 여러 포트를 지원할 수도 있다. 예를 들어 파드가 두 개의 포트를 수신한다면 하나의 서비스를 사용해 포트 80과 443을 파드의 포트 8080과 8443으로 전달할 수 있다.


apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  port:	
  - name: http	# 포트 80은 파드의 포트 8080에 매핑된다
    port: 80
	targetPort: 8080
  - name: https	# 포트 443은 파드의 포트 8443에 매핑된다
    port: 443
	targetPort: 8443
  selector:	# 레이블 셀렉터는 항상 모든 서비스에 적용된다.
    app: kubia

이름이 지정된 포트 사용

지금까지는 대상 포트를 번호로 참조했지만 각 파드의 포트에 이름을 지정하고 서비스 스펙에서 이름으로 참조할 수도 있다.


kind: Pod
spec:
  containers:
  - name: kubia
    ports:	
    - name: http	# 컨테이너 포트 8080은 http라고 한다.
      containerPort: 8080
    - name: https	# 컨테이너 포트 8443은 https라고 한다.
      containerPort: 8443

파드를 정의하고 나면, 서비스 스펙에서 이름으로 해당 포트를 참조할 수 있다.


apiVersion: v1
kind: Service
spec:
  ports:	
  - name: http	# 포트 80은 컨테이너 포트와 매핑된다
    port: 80
	targetPort: http
  - name: https	# 포트 443은 컨테이너 포트와 매핑된다
    port: 443
	targetPort: https

서비스 검색

서비스를 만들면 파드에 액세스 할 수 있는 안정적인 IP 주소와 포트가 생긴다. 이 주소는 서비스가 유지되는 동안 변경되지 않는다.
클라이언트 파드는 서비스의 IP와 포트를 어떻게 알 수 있을까?

환경변수를 통한 서비스 검색

파드가 시작되면 쿠버네티스는 해당 시점에 존재하는 각 서비스를 가리키는 환경변수 세트를 초기화한다. 클라이언트 파드를 생성하기 전에 서비스를 생성하면 해당 파드의 프로세스는 환경변수를 검사해 서비스의 IP 주소와 포트를 얻을 수 있다.
서비스에 대한 환경변수를 보려면 먼저 모든 파드를 삭제하고 레플리케이션컨트롤러에서 새로 파드를 만들어야 한다.

$ kubectl delete po --all
pod "kubia-5rpz4" deleted
pod "kubia-95xvv" deleted
pod "kubia-kf846" deleted


$ kubectl exec kubia-fgrhp env

kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-fgrhp
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.36.0.1
KUBIA_SERVICE_HOST=10.36.8.134 		
KUBIA_PORT=tcp://10.36.8.134:80		
KUBIA_PORT_80_TCP_PORT=80
KUBERNETES_PORT=tcp://10.36.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBIA_SERVICE_PORT=80
KUBIA_PORT_80_TCP=tcp://10.36.8.134:80
KUBIA_PORT_80_TCP_ADDR=10.36.8.134
KUBERNETES_SERVICE_HOST=10.36.0.1	# 서비스의 클러스터 IP다
KUBERNETES_SERVICE_PORT_HTTPS=443	# 서비스가 제공하는 포트다
KUBERNETES_PORT_443_TCP=tcp://10.36.0.1:443
KUBIA_PORT_80_TCP_PROTO=tcp
HOME=/root

DNS를 통한 서비스 검색

파드에서 실행 중인 프로세스에서 수행된 모든 DNS 쿼리는 시스템에서 실행 중인 모든 서비스를 알고 있는 쿠버네티스의 자체 DNS 서버로 처리한다.
각 서비스는 내부 DNS 서버에서 DNS 항목을 가져오고 서비스 이름을 알고 있는 클라이언트 파드는 환경변수 대신 FQDN으로 액세스 할 수 있다.

FQDN으로 kubia 서비스에 액세스하자. 기존 내의 파드 내에서 수행해야한다. kubectl exec curl 를 사용해 파드의 컨테이너에서 명령어를 실행했었지만 이번에는 bash 셸을 실행해 컨테이너에서 명령을 실행해보자.

$ kubectl exec -it kubia-fgrhp bash

이제 컨테이너 안으로 들어왔다. curl 명령어를 사용해 다음 방법으로 kubia 서비스에 액세스할 수 있다.

root@kubia-fgrhp:/# curl http://kubia.default.svc.cluster.local
You've hit kubia-x7pq8

여기서 kubia = 서비스이름, default = 서비스가 정의된 네임스페이스, svc.cluster.local = 모든 클러스터의 로컬 서비스 이름에 사용되는 클러스터의 도메인 접미사다.
동일한 네임스페이스에 있는 경우 접미사와, 네임스페이스는 생략할 수 있다.

root@kubia-fgrhp:/# curl http://kubia
You've hit kubia-xgws7

서비스 IP에 핑을 할 수 없는 이유

root@kubia-fgrhp:/# ping kubia
PING kubia.default.svc.cluster.local (10.36.8.134): 56 data bytes
^C--- kubia.default.svc.cluster.local ping statistics ---
67 packets transmitted, 0 packets received, 100% packet loss

서비스로 curl은 동작하지만 핑은 응답이 없다. 이는 서비스의 클러스터 IP가 가상 IP 이므로 서비스 포트와 결합된 경우에만 의미가 있기 때문이다.

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

쿠버네티스 서비스 기능으로 외부 서비스를 노출하려는 경우, 서비스가 클러스터 내에 있는 파드로 연결을 전달하는 게 아니라, 외부 IP와 포트로 연결을 전달하는 것이다.
이 경우 서비스 로드밸런싱과 서비스 검색 모두 활용할 수 있다. 클러스터에서 실행 중인 클라이언트 파드는 내부 서비스에 연결하는 것처럼 외부 서비스에 연결할 수 있다.

서비스 엔드포인트 소개

서비스는 파드에 직접 연결되지 않는다. 대신 엔드포인트 리소스가 그 사이에있다.
kubectl describe 명령을 사용하면 엔드포인트를 확인할 수 있다.

$ kubectl describe svc kubia
Name:              kubia
Namespace:         default
Labels:            <none>
Annotations:       cloud.google.com/neg: {"ingress":true}
Selector:          app=kubia		# 서비스의 파드 셀렉터는 엔드포인트 목록을 만드는 데 사용된다.
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.36.8.134
IPs:               10.36.8.134
Port:              <unset>  80/TCP
TargetPort:        8080/TCP
Endpoints:         10.32.1.11:8080,10.32.1.12:8080,10.32.2.16:8080	# 이 서비스의 엔드포인트를 나타내는 파드 IP와 포트 목록
Session Affinity:  None
Events:            <none>

엔드포인트 리소스는 서비스로 노출되는 파드의 IP 주소와 포트목록이다.
kubectl get을 사용해 기본 정보를 표시 할 수 있다.

$ kubectl get endpoints kubia
NAME    ENDPOINTS                                         AGE
kubia   10.32.1.11:8080,10.32.1.12:8080,10.32.2.16:8080   16m

클라이언트가 서비스에 연결하면 서비스 프록시는 이들 중 하나의 IP와 포트 쌍을 선택하고 들어온 연결을 대상 파드의 수신 대기 서버로 전달한다.

서비스 엔드포인트 수동 구성

수동으로 관리되는 엔드포인트를 사용해 서비스를 만들려면 서비스와 엔드포인트 리소스를 모두 만들어야한다.

셀렉터 없이 서비스 생성

external-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: external-service	# 서비스의 이름은 엔드포인트 오브젝트 이름과 일치해야 한다.
spec:	# 이 서비스에는 셀렉터가 정의되어있지 않다.
  ports:
  - port: 80

포트 80으로 들어오는 연결을 허용하는 external-service라는 서비스를 정의했다.
이제 엔트포인트 리소스를 생성한다.

external-service-endpoints.yaml

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

서비스와 엔드포인트 리소스가 모두 서버에 게시되면 파드 셀렉터가 있는 일반 서비스처럼 서비스를 사용할 수 있다.
서비스가 만들어진 후 만들어진 컨테이너에는 서비스의 환경변수가 포함되며 IP:포트 쌍에 대한 모든 연결은 서비스 엔드포인트 간에 로드밸런싱한다.

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

서비스의 엔드포인트를 수동으로 구성해 외부 서비스를 노출하는 대신 좀더 간단한 방법으로 FQDN으로 외부 서비스를 참조 할 수 있다.
외부 서비스 별칭으로 사용되는 서비스를 만들려면 유형필드를 ExternalName으로 설정해 서비스 리소스를 만든다.

api.somecompany.com에 공개 API가 있다고 가정하고, 다음과 같이 표시된 대로 이를 가리키는 서비스를 정의할 수 있다.

external-service-externalname.yaml

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: ExternalName	# 서비스 유형이 ExternalName으로 설정된다.
  externalName: api.somecompany.com		# 실제 서비스의 정규화된 도메인 이름
  ports:
  - port: 80
  

서비스가 생성되면 파드는 서비스의 FQDN을 사용하는 대신 external-service.default.svc.cluster.local 도메인 이름으로 외부 서비스에 연결 할 수 있다.
이렇게 하면 서비스를 사용하는 파드에서 실제 서비스 이름과 위치가 숨겨져 나중에 externalName 속성을 변경하거나 유형을 다시 ClusterIP로 변경하고 서비스 스펙을 만들어 서비스 스펙을 수정하면 나중에 다른 서비스를 가리킬 수 있다.

외부 클라이언트에 서비스 노출

외부에서 서비스를 액세스할 수 있는 몇 가지 방법이 있다.

  • 노드포트로 서비스 유형 설정 : 노드포트 서비스의 경우 각 클러스터 노드는 노드 자체에서 포트를 열고 해당 포트로 수신된 트래픽을 서비스로 전달한다. 서비스는 내부 클러스터 IP와 포트로 액세스할 수 있을 뿐만 아니라 모든 노드의 전용 포트로도 액세스할 수 있다.

  • 서비스 유형을 노드포트 유형의 확장인 로드밸런서로 설정 : 쿠버네티스가 실행 중인 클라우드 인프라에서 프로비저닝된 전용 로드밸런서로 서비스에 액세스할 수 있다. 로드밸런서는 트래픽을 모든 노드의 노드포트로 전달한다. 클라이언트는 로드밸런서의 IP로 서비스에 액세스한다.

  • 단일 IP 주소로 여러 서비스를 노출하는 인그레스 리소스 만들기 : HTTP 레벨에서 작동하므로 4게층 서비스 보다 더 많은기능 제공할 수 있다.

노드포트 서비스 사용

노드포트 서비스를 만들면 쿠버네티스는 모든 노드에 특정 포트를 할당하고 서비스를 구성하는 파드로 들어오는 연결을 전달한다. 내부 클러스터 IP뿐만 아니라 모든 노드의 IP와 할당된 노드포트로 서비스에 액세스할 수 있다.

kubia-svc-nodeport.yaml

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

서비스를 생성하고 정보를 살펴보자

$ kubectl create -f kubia-svc-nodeport.yaml
service/kubia-nodeport created
$ kubectl get svc kubia-nodeport
NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubia-nodeport   NodePort   10.36.11.131   <none>        80:30123/TCP   15s

EXTENAL-IP 열은 [nodes]라고 표시돼 있고 클러스터 노드의 IP 주소로 서비스에 액세스할 수 있음을 나타낸다. PORT 열에는 클러스터 IP 내부포트 80과 노드포트 30123이 모두 표시된다.

외부 클라이언트가 노드포트 서비스에 액세스할 수 있도록 방화벽 규칙 변경

노드포트로 서비스에 액세스하려면 해당 노드포트에 대한 외부 연결을 허용하도록 방화벽을 구성해야 한다.
구글 클라우드 플랫폼 방화벽 설정 작업을 수행해보자.

$ gcloud compute firewall-rules create kubia-svc-rule --allow=tcp:30123
Creating firewall...working..Created [https://www.googleapis.com/compute/v1/projects/bright-vision-360512/global/firewalls/kubia-svc-rule].
Creating firewall...done.
NAME: kubia-svc-rule
NETWORK: default
DIRECTION: INGRESS
PRIORITY: 1000
ALLOW: tcp:30123
DENY:
DISABLED: False

이제 노드 IP와 포트 30123으로 서비스에 액세스할 수 있다. 먼저 노드 IP를 알아야한다.

노드의 JSON 또는 YAML 요약에서 IP를 찾을수 있다. kubectl에게 노드 IP만 출력하도록 해보자

$ kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="ExternalIP")].address}'
35.247.77.246 34.145.104.124 35.230.51.22
  • item 속성의 모든 항목을 조회한다.
  • 각 항목의 status 속성을 조회한다.
  • addresses 속성의 항목에서 type 속성이 ExternalIP로 설정된 항목으로 필터링한다.
  • 마지막으로 필터링된 항목의 address 속성을 출력한다.

노드의 IP를 알고나면 서비스에 액세스 할 수 있다.

$ curl http://35.247.77.246:30123
You've hit kubia-x7pq8

$ curl http://34.145.104.124:30123
You've hit kubia-xgws7

$ curl http://35.230.51.22:30123
You've hit kubia-xgws7

외부 로드밸런서로 서비스 노출

보통 클라우드 공급자에서 실행되는 쿠버네티스 클러스터는 일반적으로 클라우드 인프라에서 로드밸런서를 자동으로 프로비저닝 하는 기능을 제공한다.
그러나 쿠버네티스가 로드밸런서 서비스를 지원하지 않는 환경에서 실행 중인 경우 로드밸런서는 프로비저닝 되지 않지만 서비스는 여전히 노드포트 서비스처럼 작동한다.
로드밸런서런서 서비스는 노드포트 서비스의 확장이기 때문이다.

로드밸런서 서비스 생성

로드밸런서를 사용해 서비스를 생성해보자

kubia-svc-loadbalancer.yaml

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

서비스 유형은 노드포트 대신 로드밸런서로 설정돼 있다. 특정 노드포트를 지정할 수 있지만 지정하지 않는다(쿠버네티스가 포트를 선택하게 한다)

서비스를 생성한 후 클라우드 인프라가 로드밸런서를 생성하고 IP 주소를 서비스 오브젝트에 쓰는 데 시간이 걸린다.
그것이 완료되면 로드밸런서 IP 주소가 서비스의 EXTENAL-IP 주소로 표시된다.

$ kubectl create -f kubia-svc-loadbalancer.yaml
service/kubia-loadbalancer created

$ kubectl get svc kubia-loadbalancer
NAME                 TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)        AGE
kubia-loadbalancer   LoadBalancer   10.36.11.98   34.83.221.88   80:32752/TCP   38s
$ curl http://34.83.221.88
You've hit kubia-xgws7

성공적으로 호출이된다. 노드포트 서비스와 달리 방화벽을 설정할 필요가 없었다.

인그레스 리소스로 서비스 외부 노출

인그레스가 필요한 이유는 로드밸런서 서비스는 자신의 공용 IP 주소를 가진 로드밸런서가 필요하지만, 인그레스는 한 IP 주소로 수십 개의 서비스에 접근이 가능하도록 지원해준다.
클라이언트가 HTTP 요청을 인그레스에 보낼 때, 요청한 호스트와 경로에 따라 요청을 전달할 서비스가 결정된다.

인그레스 오브젝트가 제공하는 기능을 살펴보기 전에 인그레스 리소스를 작동시키려면 클러스터에 인그레스 컨트롤러를 실행해야 한다.
쿠버네티스 환경마다 다른 컨트롤러 구현을 사용할 수 있지만 일부는 기본 컨트롤러를 전혀 제공하지 않는다.
구글 쿠버네티스 엔진은 구글 클라우드 플랫폼의 고유한 HTTP 로드밸런싱 기능을 사용해 인그레스 기능을 제공한다.

인그레스 리소스 생성

kubia-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.example.com		# 인그레스는 kubia.example.com 도메인 이름을 서비스에 매핑한다.
    http:
      paths:	
      - path: /				# 모든 요청은 kubia-nodeport 서비스의 포트 80으로 전달된다.
        pathType: Prefix
        backend:
          service: 
            name: kubia-nodeport
            port: 
              number: 80

kubia.example.com으로 요청되는 인그레스 컨트롤러에 수신된 모든 HTTP 요청을 포트 80의 kubia-nodeport 서비스로 전송하도록 하는 인그레스 규칙을 정의했다.

인그레스로 서비스 액세스

http://kubia.example.com 서비스에 액세스하려면 도메인 이름이 인그레스 컨트롤러의 IP와 매핑되도록 해야한다.

$ kubectl get ingress
NAME    CLASS    HOSTS               ADDRESS         PORTS   AGE
kubia   <none>   kubia.example.com   34.110.229.83   80      4m12s

인그레스 목록에서 IP를 찾을수 있다.

인그레스 컨트롤러가 구성된 호스트의 IP를 인그레스 엔드포인트로 지정

IP를 알고나면 kubia.example.com을 해당 IP로 확인하도록 DNS 서버를 구성하거나,
다음 줄을 /etc/hosts에 추가할 수 있다

34.110.229.83	kubia.example.com

이제 모든 것이 설정됐으므로 브라우저 또는 curl을 사용해 서비스에 액세스할 수 있다.

$ curl http://kubia.example.com 
You've hit kubia-xgws7

하나의 인그레스로 여러 서비스 노출

인그레스 스펙을 자세히 보면 규칙과 경로가 모두 배열이므로 여러 항목을 가질 수 있다.

다음은 동일한 호스트의 다른 경로로 여러 서비스 매핑하는 방법이다

...
  - host: kubia.example.com
    http:
      paths:	
      - path: /kubia		# kubia.example.com/kubia 으로의 요청은 kubia 서비스로 라우팅된다.
        pathType: Prefix
        backend:
          service:
            name: kubia
            port:
              number: 80
	  - path: /bar	
        pathType: Prefix
        backend:
          service:
            name: bar # kubia.example.com/bar 으로의 요청은 bar 서비스로 라우팅된다.
            port: 
              number: 80
		  

다음은 서로 다른 호스트로 서로 다른 서비스 매핑하는 방법이다.

spec:
  rules:
  - host: foo.example.com	# foo.example.com 으로의 요청은 서비스 foo로 라우팅된다.
    http:
      paths:	
      - path: /
        pathType: Prefix
        backend:
          service
            name: foo
            port:
              number: 80
  - host: bar.example.com	# bar.example.com 으로의 요청은 서비스 bar로 라우팅된다.
    http:
      paths:	
      - path: /
        pathType: Prefix
        backend:
          service
            name: bar
            port: 
              number: 80

TLS 트래픽을 처리하도록 인그레스 구성

예를 들어 파드가 웹 서버를 실행하는 경우 HTTP 트래픽만 허용하고 인그레스 컨트롤러가 TLS 와 관련된 모든 것을 처리하도록 할 수 있다.
컨트롤러가 그렇게 하려면 인증서와 개인 키를 인그레스에 첨부해야 한다. 이 두 개는 시크릿이라는 쿠버네티스 리소스에 저장하며 인그레스 매니페스트에서 참조한다.

먼저 개인 키와 인증서를 만들어야 한다.

$ openssl genrsa -out tls.key 2048
$ openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj /CN=kubia.example.com

그런 다음 두 파일로 시크릿을 만든다.

$ kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
secret/tls-secret created

개인 키와 인증서는 이제 tls-secret이라는 시크릿에 저장된다. 인그레스 오브젝트를 업데이트하려면 kubia.example.com에 대한 HTTPS 요청도 수락할 수 있다. 인그레스 매니페스트는 다음 예제와 같이 보일 것이다.

kubia-ingress-tls.yaml


apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia
spec:	# 전체 TLS 구성이 이 속성 아래에 있다.
  tls:	# kubia.example.com 호스트 이름의 TLS 연결이 허용된다.
  - hosts: 
    - kubia.example.com		# 개인 키와 인증서는 이전에 작성한 tls-secret을 참조한다.
    secretName: tls-secret
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kubia-nodeport
            port:
              number: 80

이제 HTTPS로 인그레스를 통해 서비스에 액세스할 수 있다.

$ curl -k -v https://kubia.example.com/kubia

명령어의 출력에는 애플리케이션 응답과 인그레스에 구성한 서버 인증서가 표시된다.

파드가 연결을 수락할 준비가 됐을 때 신호 보내기

파드의 레이블이 서비스의 파드 셀렉터와 일치할 경우 파드가 서비스의 엔드포인트로 포함된다는 것을 배웠다.
적절한 레이블을 가진 새 파드가 만들어지자마자 서비스 일부가 돼 요청이 파드로 전달되기 시작한다.
하지만 만약 그 파드가 즉시 요청을 처리항 준비가 돼 있지 않다면 어떻게 해야 될까?

레디니스 프로브 소개

이 전 장에서 라이브니스 프로브와 불안전한 컨테이너를 자동으로 다시 시작해 애플리케이션의 상태를 원활히 유지하는 방법을 배웠다.
쿠버네티스에서는 라이브니스 프로브와 비슷하게 파드에 레디니스 프로브를 정의할 수 있다.

레디니스 프로브는 주기적으로 호출되며 특정 파드가 클라이언트 요청을 수신할 수 있는지를 결정한다. 컨테이너의 레디니스 프로브가 성공을 반환하면 컨테이너가 요청을 수락할 준비가 됐다는 신호다.

레디니스 프로브 유형

라이브니스 프로브와 마찬가지로 세 가지 유형의 레디니스 프로브가 있다.

  • 프로세스를 실행하는 Exec 프로브는 컨테이너의 상태를 프로세스의 종료 상태 코드로 결정한다.
  • HTTP GET 프로브는 HTTP GET 요청을 컨테이너로 보내고 응답의 HTTP 상태 코드를 보고 컨테이너가 준비됐는지 여부를 결정한다.
  • TCP 소켓 프로브는 컨테이너의 지정된 포트로 TCP 연결을 연다. 소켓이 연결되면 컨테이너가 준비된 것으로 간주한다.

파드에 레디니스 프로브 추가

kubectl edit 명령어로 기존 레플리케이션컨트롤러의 파드 템플릿에 프로브를 추가한다.

$ kubectl edit rc kubia

... 
image: luksa/kubia # 해당 이미지 아래에 적용
readinessProbe: # 파드의 각 컨테이너에 레디니스 프로브가 정의될 수 있다.
  exec:
    command:
	- ls
	- /var/ready
...

레디니스 프로브는 컨테이너 내부에서 ls /var/ready 명령어를 주기적으로 수행한다.
ls 명령어는 파일이 존재하면 종료 코드 0을 반환하고 그렇지 않으면 0이 아닌 값을 반환한다.
파일이 있으면 레디니스 프로브가 성공하고 그렇지 않으면 실패한다

이렇게 정의한 이유는 문제의 파일을 생성하거나 제거해 그 결과를 바로 전환할 수 있기 때문이다.

기존 파드는 여전히 레디니스 프로브가 정의되어 있지않다. 파드를 삭제하면 레플리케이션컨트롤러가 다시 파드를 생성한다.

$ kubectl delete po --all
pod "kubia-fgrhp" deleted
pod "kubia-x7pq8" deleted
pod "kubia-xgws7" deleted

이제 새 파드는 레디니스 점검에 실패하고 각각에 /var/ready 파일을 만들 때까지 서비스의 엔드포인트에 포함되지 않는다.

$ kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-hgsdk   0/1     Running   0          52s
kubia-q5k4d   0/1     Running   0          52s
kubia-q8ktv   0/1     Running   0          51s

아래의 명령으로 하나의 파드에 /var/ready 파일을 만들어 레디니스 프로브가 성공을 반환하도록 해보자.

$ kubectl exec kubia-hgsdk -- touch /var/ready

touch 명령어는 파일이 아직 없으면 파일을 만든다. 파드의 레디니스 프로브 명령이 이제 상태 코드 0으로 종료돼야 한다.

$ kubectl get po kubia-hgsdk
NAME          READY   STATUS    RESTARTS   AGE
kubia-hgsdk   1/1     Running   0          2m38s

레디니스 프로브는 기본으로 10초마다 주기적으로 프로브가 실행된다. 늦어도 10초 안에 파드는 준비 상태가 된다.

다음 명령을 사용하여 레디니스 프로브 파일이 설정된 해당 IP의 서비스의 유일한 엔드포인트로 조회하고 서비스를 호출해 본다.

$ kubectl get endpoints
NAME                 ENDPOINTS          AGE
kubernetes           34.105.79.56:443   22h
kubia                10.32.1.13:8080    122m
kubia-loadbalancer   10.32.1.13:8080    97m
kubia-nodeport       10.32.1.13:8080    103m
$ curl http://10.32.1.13
You've hit kubia-hgsdk
$ curl http://10.32.1.13
You've hit kubia-hgsdk
$ curl http://10.32.1.13
You've hit kubia-hgsdk

여러번 호출해도 하나의 파드로 전달되는 것을 확인했다.

실제 환경에서 레디니스 프로브가 수행해야 하는 기능

실제 환경에서 레디니스 프로브는 애플리케이션이 클라이언트 요청을 수신할 수 있는지 여부에 따라 성공 또는 실패를 반환해야 한다.
서비스에서 파드를 수동으로 제거하려면 수동으로 프로브의 스위치를 전환하는 대신 파드를 삭제하거나 파드 레이블을 변경해야 한다.

두 가지 강조 사항이 있다.

  1. 레디니스 프로브를 항상 정의한다.
    파드에 레디니스 프로브를 추가하지 않으면 파드가 시작하는 즉시 서비스 엔드포인트가 된다.
    애플리케이션이 수신 연결을 시작하는데 너무 오래 걸리는 경우 클라이언트의 서비스 요청은 여전히 시작 단계로 수신 연결을 수락할 준비가 되지 않은 상태에서 파드로 전달된다.
    따라서 클라이언트는 Connection refused 유형의 에러를 보게된다.

  2. 레디니스 프로브에 파드의 종료 코드를 포함하지 않는다.
    연결 오류가 발생한 클라이언트에서 파드가 종료할 때, 실행되는 애플리케이션은 종료 신호를 받자마자 연결 수락을 중단한다.
    쿠버네티스는 파드를 삭제하자마자 모든 서비스에서 파드를 제거한다는 것을 기억하자.

헤드리스 서비스로 개별 파드 찾기

지금까지 서비스의 파드에 클라이언트 연결을 허용하려고 서비스가 안정적인 IP 주소를 제공하는 방법을 살펴봤다.
서비스 스펙의 clusterIP 필드를 None으로 설정하면 쿠버네티스는 클라이언트가 서비스의 파드에 연결할 수 있는 클러스터 IP를 할당하지 않기 때문에 서비스가 헤드리스 상태가 된다.

헤드리스 서비스를 생성해 보자.

kubia-svc-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia-headless
spec:
  clusterIP: None	# 이 부분이 서비스를 헤드리스 서비스로 만든다.
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

kubectl create로 서비스 생성 후 kubectl get과 kubectl describe로 서비스를 살펴볼 수 있다.
클러스터 IP가 없고 엔드포인트에 파드 셀렉터와 일치하는 파드가 포함돼 있음을 알 수 있다.

파드에 레디니스 프로브가 포함돼 있기 때문에 준비된 파드만 서비스의 엔드포인트로 조회된다.
계속하기 전에 이전 예제와 같이 파일을 만들어 두 개 이상의 파드가 준비돼 있는지 확인하자.

$ kubectl exec kubia-q5k4d -- touch /var/ready

DNS로 파드 찾기

파드가 준비되면 DNS 조회로 실제 파드 IP를 얻을 수 있는지 확인할 수 있다. 파드 내부에서 조회해야 한다.
클러스터에서 실행 중인 파드 내부에서 DNS 조회를 수행하기만 하면 된다. 필요한 바이너리가 포함된 이미지를 기반으로 새 파드를 실행하자.
DNS 관련 작업을 수행하려면 도커 허브의 nslookup 및 dig 바이너리를 모두 포함하는 tutum/dnsutils 컨테이너 이미지를 사용할 수 있다.

yaml이 아닌 명령어를 통해 파드를 만들어본다.

$ kubectl run dnsutils --image=tutum/dnsutils  --command -- sleep infinity

새 서비스로 DNS 조회를 수행해보자.

$ kubectl exec dnsutils nslookup kubia-headless
Server:         10.36.0.10
Address:        10.36.0.10#53

Name:   kubia-headless.default.svc.cluster.local
Address: 10.32.1.13
Name:   kubia-headless.default.svc.cluster.local
Address: 10.32.1.14

FQDN에 대해 서로 다른 두 개의 IP를 반환한다. 바로 준비됐다고 보고된 파드 두 개의 IP다

아래는 kubia 서비스의 클러스터 IP 다.

$ kubectl exec dnsutils nslookup kubia
Server:         10.36.0.10
Address:        10.36.0.10#53

Name:   kubia.default.svc.cluster.local
Address: 10.36.8.134

헤드리스 서비스는 일반 서비스와 다르게 보일 수 있지만 클라이언트 관점에서는 다르지 않다.
헤드리스 서비스를 사용하더라도 클라이언트는 일반 서비스와 마찬가지로 서비스의 DNS 이름에 연결해 파드에 연결할 수 있다. 그러나 헤드리스 서비스에서는 DNS가 파드의 IP를 반환하기 때문에 클라이언트는 서비스 프록시 대신 파드에 직접 연결한다.

서비스 문제 해결

마지막으로 서비스 IP 또는 FQDN으로 파드에 연결할 수 없는 이유를 파악하는데 다음과 같은 내용을 확인해 보자.

  • 먼저 외부가 아닌 클러스터 내에서 서비스의 클러스터 IP에 연결되는지 확인하다.
  • 서비스에 액세스할 수 있는지 확인하려고 서비스 IP로 핑을 할 필요 없다.
  • 레디니스 프로브를 정의했다면 성공했는지 확인하자 그렇지 않으면 파드는 서비스에 포함되지 않는다.
  • 파드가 서비스의 일부인지 확인하려면 kubectl get endpoints를 사용해 해당 엔드포인드 오브젝트를 확인한다.
  • FQDN이나 그 일부로 서비스에 액세스하려고 하는데 작동하지 않는 경우, FQDN 대신 클러스터 IP를 사용해 액세스할 수 있는지 확인한다.
  • 대상포트가 아닌 서비스로 노출된 포트에 연결하고 있는지 확인한다.
  • 파드 IP에 직접 연결해 파드가 올바른 포트에 연결돼 있는지 확인한다.
  • 파드 IP로 애플리케이션에 액세스할 수 없는 경우 애플리케이션이 로컬호스트에만 바인딩하고 있는지 확인한다.
profile
노옵스를향해

0개의 댓글