無知

갈 길이 먼 공부 일기

기술 공부/쿠버네티스

쿠버네티스 (5) | 서비스

moozii 2022. 2. 16. 23:05
앞으로의 스터디 내용은 <Kubernetes in Action>을 기반으로 진행합니다.
자세한 내용은, 해당 책을 확인해주세요! 
http://www.yes24.com/Product/Goods/89607047 

 

서비스, 파드의 검색과 통신을 위한 요소

파드가 다른 파드에게 제공하는 서비스를 사용하기 위해서는, 다른 파드를 검색하고 인식할 수 있어야 한다.

쿠버네티스가 나오기 전에는 사용자가 서비스 제공 서버의 주소와 호스트 이름을 지정해 구성해줬지만, 쿠버네티스에는 해당하지 않는다. 서비스를 제공하는 파드 자체가 일시적이라는 특성이 있고, 파드는 시작 직전에 IP 주소를 받기 때문에 미리 주소를 알아 구성해줄 수 없기 때문이다. 나아가, 수평적인 스케일링을 제공하려면, 여러 파드가 동일 서비스를 제공해야 하므로, 여러 병렬적인 파드의 수와 무관하게, 단일한 주소로 접속하도록 도와주는 별도의 장치가 필요하기 때문이다.

 

 

서비스가 뭘까

동일한 서비스를 제공하는 파드의 그룹에 단 하나의 접점을 만들어주는 리소스이다.

서비스는 IP주소와 포트가 삭제 전까지 동일하게 유지된다는 특성이 있다. 

클라이언트는 서비스 주소와 포트로 접속하면 그에 해당하는 파드 중 하나로 연결된다. 

파드가 여러 개이든, 생성되고 삭제되면서 이동하든, 주소가 변경되든 서비스로 파드 간 연결을 해결한다.

파드가 여러 개면 로드밸런싱을 진행해주기도 하는 파드, 서비스에 해당하는 파드의 범위는 어떻게 정해질까?

앞선 레플리카셋, 레플리케이션 컨트롤러 공부에도 나왔던 레이블 셀렉터 개념이 활용된다.

 

실습해보기 위해서 우선 파드를 생성해야 하는데, 

책의 실습은 레플리케이션 컨트롤러를 활용하라 하지만, 

레플리카셋을 활용해 유사하게 실습을 진행해보겠다.

$ kubectl create -f Chapter04/kubia-replicaset.yaml 
replicaset.apps/kubia created

$ cd Chapter05 

$ kubectl get pods -L app
NAME          READY   STATUS    RESTARTS   AGE   APP
kubia-7x64l   1/1     Running   0          41s   kubia
kubia-d9w79   1/1     Running   0          41s   kubia
kubia-kjrfk   1/1     Running   0          41s   kubia

이후 서비스 생성은 책 이전 부분에서는 kubectl expose 명령어를 주로 활용해왔는데, 이번 챕터부터는 kubernetes API 서버에 yaml을 게시해 수동으로 서비스를 생성하고자 한다.

 

서비스와 애플리케이션 연결하기

컨테이너 연결을 위한 쿠버네티스 모델 지속적으로 실행중이고, 복제된 애플리케이션을 가지고 있다면 네트워크에 노출할 수 있다. 쿠버네티스의 네트워킹 접근 방식을 논의하기 전에, 도커와

kubernetes.io

 

서비스 생성에 필요한 구성 파일은 아래와 같이 구성된다. 

$ cat kubia-svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

서비스가 사용할 포트, 서비스가 포워딩할 포트, 그리고 레이블 셀렉터 정보를 포함한다.

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

$ kubectl get svc
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1        <none>        443/TCP          20d
kubia        ClusterIP      10.104.112.103   <none>        80/TCP           27s
kubia-http   LoadBalancer   10.96.105.141    <pending>     8080:31387/TCP   8d

 

서비스 생성 후 클러스터에서 서비스로 요청을 보내보면서 테스트를 진행할 수 있다.

  • 서비스 클러스터 IP 요청을 보내 응답을 로그로 남기는 파드를 만든다. 파드의 로그를 검사해 응답을 확인한다.
  • 쿠버네티스 노드에 직접 ssh 접속을 진행해 curl 명령을 실행한다.
  • kubectl exec 명령어로 기존 파드에서 curl 명령을 실행한다.

 

curl 명령이란?

CURL 이란? cURL = Client URL
클라이언트에서 커맨드 라인이나 소스코드로 손 쉽게 웹 브라우저 처럼 활동할 수 있도록 해주는 기술(커맨드라인 Tool 혹은 라이브러리) 서버와 통신할 수 있는 커맨드 명령어 툴이다. 웹개발에 매우 많이 사용되고 있는 무료 오픈소스이다
사용법
curl [-option] https://shutcoding.tistory.com
하게되면 소스가 화면으로 출력된다. 이외에 옵션을 이용하여 사용할 수 있다.

출처: https://shutcoding.tistory.com/23 

kubectl exec 명령이란?

기존 파드 컨테이너 내에서 원격으로 임의의 명령어를 실행시키는 명령어. 
$ kubectl get pods 
NAME          READY   STATUS    RESTARTS   AGE
kubia-7x64l   1/1     Running   0          18m
kubia-d9w79   1/1     Running   0          18m
kubia-kjrfk   1/1     Running   0          18m

$ kubectl get svc
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1        <none>        443/TCP          20d
kubia        ClusterIP      10.104.112.103   <none>        80/TCP           7m33s
kubia-http   LoadBalancer   10.96.105.141    <pending>     8080:31387/TCP   8d

$ kubectl exec kubia-7x64l --curl -s http://10.104.112.103
Error: unknown flag: --curl
See 'kubectl exec --help' for usage.

$ kubectl exec kubia-7x64l -- curl -s http://10.104.112.103
You've hit kubia-d9w79​

파드의 이름과 서비스 주소를 확인하여 실행한 명령어. (출처: 쿠버네티스 인 액션)

명령어에서 --, 더블대시의 의미

kubectl 명령줄 옵션의 끝을 의미한다. --, 더블대시 뒤에 위치한 모든 내용은 파드 내에서 실행됨을 의미한다.
대시로 시작하는 인수가 없으면, 굳이 더블대시를 사용하지 않아도 괜찮지만, 예전에는 오류 때문에 더블대시를 사용해야만 했다고 한다. (출처: 쿠버네티스 인 액션)

 

 

kubectl exec {pod_name} -- curl {service_ip}  명령어 해설

1. curl 명령어가 해당 파드의 컨테이너 내에서 실행된다

2. curl 명령어가 http GET 요청을 해당 주소의 서비스로 보낸다

3. 서비스는 받은 http 연결을 서비스 내 임의의 파드로 전달한다

4. 요청을 받은 파드의 해당 컨테이너 내 node.js가 요청을 처리한다.

5. http 응답은 다시 curl로 전송된다

6. curl 명령의 출력 내용은 다시 curl이 실행된 컨테이너에서 kubectl로 전달되어 출력된다

 

 

세션 어피티니, 클라이언트의 요청을 모두 같은 파드로 리디렉션

원래의 서비스는, 서비스 프록시를 통해 여러번 요청마다 임의의 파드를 선택해 전달해서, 다른 파드들이 선택된다. 

만약 모든 요청을 동일한 파드로 리디렉션하려면, 서비스의 세션 어피니티 속성을 None에서 ClientIP로 설정해야 한다.

$ cat kubia-svc-client-ip-session-affinity.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  sessionAffinity: ClientIP
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

참고로, 쿠버네티스는 http 수준에서 작동하지 않기 때문에, 쿠키 기반 세션 어피니티 옵션은 존재하지 않는다. 

 

 

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

파드가 여러개의 포트를 수신하면 가능하다. 하나의 서비스로 멀티 포트 서비스를 사용하면 하나의 클러스터 IP로 모든 서비스 포트가 노툴된다. 이러한 서비스의 스펙은 아래처럼 정의된다. 여러 포트가 있는 서비스를 만들기 위해서는 포트의 이름을 반드시 지정해야 함이 포인트다. 레이블 셀렉터는 서비스 전체에 적용되어 포트별로 적용이 어렵다.

$ cat kubia-svc-named-ports.yaml 
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)에서, 파드 정의 내에 포트 이름을 정의해주면, 그 뒤에 서비스의 이름에서 targetPort 칸에 파드 정의 상의 포트 이름을 작성해도 참조하여 작동한다. 이럴 경우 파드 정의 상의 이름과 서비스 포트가 연결되기 때문에, 파드의 포트가 변경되어도 이름이 동일하면 구태여 양쪽 모두를 수정할 필요가 없어진다는 장점이 있다.

 

 

 

서비스 검색

클라이언트 파드가 서비스의 IP와 포트를 알기 위해서 검색하는 방법이 존재한다. 

 

 

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

파드는 시작할 때 각 서비스를 가리키는 환경변수를 초기화한다. 파드 생성 전에 서비스를 생성하면, 해당 파드의 프로세스가 환경변수를 검사해 서비스의 주소와 포트를 얻는다. 

$ kubectl delete po --all
pod "kubia-7x64l" deleted
pod "kubia-d9w79" deleted
pod "kubia-kjrfk" deleted

$ kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-gfqqj   1/1     Running   0          31s
kubia-pxp9k   1/1     Running   0          31s
kubia-vfjg4   1/1     Running   0          31s

$ kubectl exec kubia-gfqqj 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-gfqqj
KUBIA_PORT_80_TCP=tcp://10.104.112.103:80
KUBIA_PORT_80_TCP_PORT=80
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBIA_HTTP_SERVICE_HOST=10.96.105.141
KUBIA_HTTP_PORT_8080_TCP_PORT=8080
KUBIA_PORT=tcp://10.104.112.103:80
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_PORT_80_TCP_ADDR=10.104.112.103
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBIA_HTTP_PORT=tcp://10.96.105.141:8080
KUBIA_HTTP_PORT_8080_TCP=tcp://10.96.105.141:8080
KUBIA_SERVICE_PORT=80
KUBIA_SERVICE_HOST=10.104.112.103
KUBIA_PORT_80_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBIA_HTTP_SERVICE_PORT=8080
KUBIA_HTTP_PORT_8080_TCP_PROTO=tcp
KUBIA_HTTP_PORT_8080_TCP_ADDR=10.96.105.141
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
HOME=/root

$ kubectl exec kubia-gfqqj -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-gfqqj
KUBIA_PORT_80_TCP=tcp://10.104.112.103:80
KUBIA_PORT_80_TCP_PORT=80
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBIA_HTTP_SERVICE_HOST=10.96.105.141
KUBIA_HTTP_PORT_8080_TCP_PORT=8080
KUBIA_PORT=tcp://10.104.112.103:80
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_PORT_80_TCP_ADDR=10.104.112.103
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBIA_HTTP_PORT=tcp://10.96.105.141:8080
KUBIA_HTTP_PORT_8080_TCP=tcp://10.96.105.141:8080
KUBIA_SERVICE_PORT=80
KUBIA_SERVICE_HOST=10.104.112.103
KUBIA_PORT_80_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBIA_HTTP_SERVICE_PORT=8080
KUBIA_HTTP_PORT_8080_TCP_PROTO=tcp
KUBIA_HTTP_PORT_8080_TCP_ADDR=10.96.105.141
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
HOME=/root
 

kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.

자격증 공부를 시작하게 되면서 kubernetes 1.20을 설치한뒤 kubectl exec 명령을 아래와 같이 실행하니 제목과 같은 문구가 나왔다 kubectl exec -it mynginx-pod bash 기존에는 이런 식으로 pod 내부의 컨테이너.

zgundam.tistory.com

 

DNS를 통한 서비스 검색

kube-system 네임스페이스에 있는 kube-dns라는 파드는, DNS 서버를 실행한다. 클러스터 내 다른 모든 파드는 해당 파드를 사용해, 모든 DNS 쿼리를 보내, 쿠버네티스의 자체 DNS 서버가 처리한다.

 

 

FQDN을 통한 서비스 연결

내부 DNS 서버에서 해당 항목을 가져오면 클라이언트 파드는 환경변수 대신 알고 있는 서비스 이름을 기반으로 FQDN으로 액세스가 가능하다. 예를 들어 backend-database.default.svc.cluster.local과 같이, {service_name}.{namespace_name}.{cluster_domain_suffix} 형태로 풀도메인을 구성한다. 이런 도메인을 기존 파드 내에서 bash 셸로 접근해, 컨테이너 내에서 명령어를 실행해서 확인할 수 있다.

FQDN(Fully Qualified Domain Name) 이란?
FQDN은 '절대 도메인 네임' 또는 '전체 도메인 네임' 이라고도 불리는도메인 전체 이름을 표기하는 방식을 의미한다. 웹 사이트 주소를 예로 들어보자. 1. www.tistory.com  2. onlywis.tistory.com 위의 두 주소 중 www 와 onlywis 부분이 '호스트'이고, tistory.com 부분이 '도메인'이다.위에 쓴 것처럼 호스트와 도메인을 함께 명시하여 전체 경로를 모두 표기하는 것을 FQDN 이라 한다. FQDN와 달리 전체 경로명이 아닌 하위 일부 경로만으로 식별 가능하게 하는 이름을 PQDN(Partially~)라 한다. 쿠버네티스의 경우 다른 Pod를 찾을 시 동일 네임스페이스 안에서 찾을 때에는 PQDN을 사용할 수 있지만,네임스페이스 외부에서 찾을 때에는 FQDN을 사용해야 한다.
출처: https://onlywis.tistory.com/14

만약 동일한 네임스페이스에 있으면 굳이 네임스페이스 이름이나 클러스터 도메인 접미사 없이 서비스 이름만으로 서비스 연결도 간단히 진행 가능함에 유의하자. 

$ kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-gfqqj   1/1     Running   0          15m
kubia-pxp9k   1/1     Running   0          15m
kubia-vfjg4   1/1     Running   0          15m

$ kubectl exec -it kubia-gfqqj bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.

root@kubia-gfqqj:/# exit
exit

$ kubectl exec -it kubia-gfqqj -- bash

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

root@kubia-gfqqj:/# curl http://kubia.default                  
You've hit kubia-vfjg4

root@kubia-gfqqj:/# curl http://kubia        
You've hit kubia-pxp9k

root@kubia-gfqqj:/# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local 
options ndots:5

파드 컨테이너 내부 각각에는 내부 DNS resolver가 있어서 네임스페이스, 클러스터 도메인 접미사 등 미리 정의된 것들은 생략도 가능하다고 한다. 

3) 리졸버 (Resolver)
웹 브라우저와 같은 DNS 클라이언트의 요청을 네임 서버로 전달하고 네임 서버로부터 정보(도메인 이름과 IP 주소)를 받아 클라이언트에게 제공하는 기능을 수행한다.
하나의 네임 서버 에게 DNS 요청을 전달하고 해당 서버에 정보가 없으면 다른 네임 서버에게 요청을 보내 정보를 받아 온다.
수많은 네임 서버에 접근하여 사용자로부터 요청 받은 도메인의 IP 정보를 조회하는 기능을 수행할 수 있어야 한다.
출처: https://peemangit.tistory.com/52

 

참고로, 서비스 IP에는 핑을 할 수 없는데, 그 이유는 서비스의 클러스터 IP가 가상이기 때문에 서비스 포트와 연결되지 않은 이상 무의미한 구조적인 문제가 있기 때문이다. 그에 대한 구체적인 해설은 추후 다뤄질 예정이고, 갈 길이 머니 우선은 넘어가자..

 

 

 

서비스를 클러스터 외부와 연결하기

서비스 기능으로 외부 서비스를 노출해보자.

 

 

서비스 엔드포인트란?

서비스 엔드포인트란, 서비스로 노출되는 파드의 IP 주소와 포트 목록이다.

쿠버네티스의 Services에 대해서 처음 다룬 포스트에서 이야기 했듯, 뒷단의 pod의 label을 사용해서 selector가 자동적으로 앞단의 service에 매칭한다는 사실을 알고 있을 것입니다. 만약 새로운 팟들이 해당하는 label을 달게 되었다면, service는 트래픽을 그 팟으로 보내는 방법을 알게 됩니다. 이러한 매핑을 endpoint에 추가하는 것으로 service가 이러한 일들을 할 수 있는 것 입니다. Endpoints는 service가 트래픽을 보낼 오브직트의 IP주소를 추적합니다. service의 selector가 pod의 라벨을 매칭시켰다면, 그 IP 주소가 당신의 endpoints들에 추가됩니다. 필요한 service 가 이것뿐이라면, 우리가 endpoints에 대해서 해야할 일은 별로 없습니다. 하지만 클러스터의 바깥이나 다른 namespace(아직 다루진 않았지만)에 service를 연결해야 하는 경우도 있습니다. service는 endpoints를 통해 트래픽을 보내는 주소의 목록을 관리합니다. endpoints는 labels와 selectors를 통해 자동으로 업데이트 될 수 있습니다. 그리고 경우에 따라 수동으로 endpoints를 설정할 수 있습니다.

출처: https://ozt88.tistory.com/65
$ kubectl describe svc kubia
Name:              kubia
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=kubia
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.104.112.103
IPs:               10.104.112.103
Port:              <unset>  80/TCP
TargetPort:        8080/TCP
Endpoints:         172.17.0.10:8080,172.17.0.8:8080,172.17.0.9:8080
Session Affinity:  None
Events:            <none>

$ kubectl get endpoints kubia
NAME    ENDPOINTS                                          AGE
kubia   172.17.0.10:8080,172.17.0.8:8080,172.17.0.9:8080   104m

레이블 기반의 파드 셀렉터는 서비스 스펙에 정의되어, 엔드포인트에 IP와 포트 목록을 작성하여 저장한다. 이를 통해 클라이언트가 서비스에 연결되면 서비스 프록시가 해당 목록 중 하나를 타고 들어온 연결을, 대상 파드의 수신 대기 서버로 전달한다.

 

파드 셀렉터 없이 서비스를 만들면 엔드포인트 리소스를 생성하지 못한다. 즉, 엔드포인트를 수동으로 관리하기 위해서는, 서비스와 엔드포인트 리소스 각각을 새로 모두 만들어야 한다. 그리고 그 이름을 아래와 같이 일치시키면 된다.

$ cat external-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: external-service	// 서비스 이름과 엔드포인트 오브젝트 이름은 일치해야
spec:						// 파드 셀렉터 없음.
  ports:
  - port: 80

$ kubectl create -f external-service.yaml 
service/external-service created

$ cat external-service-endpoints.yaml 
apiVersion: v1
kind: Endpoints
metadata:
  name: external-service	// 서비스 이름과 엔드포인트 오브젝트 이름은 일치해야
subsets:
  - addresses:
    - ip: 11.11.11.11
    - ip: 22.22.22.22
    ports:
    - port: 80 

$ kubectl create -f external-service-endpoints.yaml
endpoints/external-service created

 

 

외부 서비스 별칭 만들기

$ cat external-service-externalname.yaml 
apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: ExternalName		// 외부 서비스 별칭용 서비스 생성 시 해당 유형으로 설정
  externalName: api.somecompany.com // 서비스 FQDN
  ports:
  - port: 80
여기서 생성한 별칭용 ExternalName 서비스는 DNS 레벨에서만 구현되므로, 클라이언트는 서비스를 연결할 때 서비스 프록시를 무시하고 직접 연결하여, 별칭용 서비스는 별도의 ClusterIP를 얻지 못한다.

 

 

 

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

 

https://livebook.manning.com/book/kubernetes-in-action/chapter-5/158

1. 노드 포트로 서비스 유형 설정하기

각 클러스터 노드가 자체적으로 포트를 열어 수신된 트래픽을 전달한다.

노드포트 서비스를 만들어 쿠버네티스가 모든 노드에 특정 포트를 할당하고, 파드로 들어오는 트래픽을 전달한다.

내부 클러스터 IP 외에도 모든 노드에 노드포트로 액세스할 수 있다는 특징이 있다.

모든 노드에 동일한 포트 번호가 적용된다고 한다.

$ cd KIA/kubernetes-in-action/Chapter05

$ cat kubia-svc-nodeport.yaml 
apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  type: NodePort		// 서비스 유형이 노드포트
  ports:
  - port: 80			// 서비스 내부 클러스터 IP 포트
    targetPort: 8080	// 서비스 대상 파드의 포트
    nodePort: 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.109.13.149   <none>        80:30123/TCP   13s

$ kubectl get svc kubia-nodeport
NAME             TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubia-nodeport   NodePort   10.109.13.149   <none>        80:30123/TCP   10m

$ minikube service kubia-nodeport
|-----------|----------------|-------------|-----------------------------|
| NAMESPACE |      NAME      | TARGET PORT |             URL             |
|-----------|----------------|-------------|-----------------------------|
| default   | kubia-nodeport |          80 | http://192.168.59.100:30123 |
|-----------|----------------|-------------|-----------------------------|
🎉  Opening service default/kubia-nodeport in default browser...
책에서는 EXTERNAL-IP가 nodes라고 표시된다고 하지만, 그와 달리 실제 실습에서는 none이라고 나왔다.
어찌 되었든 두 클러스터 노드의 포트 30123에 서비스가 노출되고, 수신 연결이 이뤄지면 임의로 선택된 파드에 전달된다. 

미니쿠베를 사용할 때는 minikube service <service-name> [-n <namespace>]를 실행하면 브라우저로 쉽게 노드포트 서비스에 액세스 가능함을 확인해 해당 명령어로 진행했다.

1. EXTERNAL-IP is always none and unable to reach to Service from Host machine #723
I think this is normal, you need a LoadBalancer to get an external IP ? However, you should be able to reach it on minikube ip and the port. (중략) External IP is always going to be None, unless you deploy the Service as type: LoadBalancer. If you do that, you have to run minikube tunnel to get access to the service.
출처: https://github.com/kubernetes/minikube/issues/3966

2. minikube를 활용한 간단한 애플리케이션 서비스하기 PART 2
명령어를 확인해보면 CLUSTER-IP에 해당 서비스 IP 주소가 할당되어 있는 것을 확인할 수 있고 EXTERNAL-IP 항목에 외부 IP가 할당된다. pending 항목으로 뜨는 이유는 현재 외부 IP 주소로 할당받지 못했기 때문에 발생하는 문제인데, 실제 K8s 환경에서는 잠시 후 외부 IP 할당이 완료되지만 minikube환경에서는 추가적인 명령을 통해서 외부 IP로 바인딩한다. $ minikube service hello-node
출처: https://judo0179.tistory.com/71

 

 

2. 서비스 유형을 로드밸런서로 설정하기

로드밸런서가 트래픽을 모든 노드의 노드포트로 전달한다. 클라이언트는 로드밸런서 주소로 액세스한다.

노드포트 대신 로드밸런서 유형의 서비스를 사용하면 된다. 

로드밸런서는, 노드포트 서비스의 확장판이기 때문에 로드밸런서가 지원되지 않아도 노드포트와 동일하게 작동한다.

$ minikube tunnel
Password:
Status:	
	machine: minikube
	pid: 30800
	route: 10.96.0.0/12 -> 192.168.59.100
	minikube: Running
	services: [kubia-http]
    errors: 
		minikube: no errors
		router: no errors
		loadbalancer emulator: no errors

...

Status:	
	machine: minikube
	pid: 30800
	route: 10.96.0.0/12 -> 192.168.59.100
	minikube: Running
	services: [kubia-http, kubia-loadbalancer]
    errors: 
		minikube: no errors
		router: no errors
		loadbalancer emulator: no errors
$ kubectl create -f kubia-svc-loadbalancer.yaml
service/kubia-loadbalancer created

$ kubectl get svc
NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
external-service     ClusterIP      10.101.82.2      <none>          80/TCP           43h
kubernetes           ClusterIP      10.96.0.1        <none>          443/TCP          22d
kubia                ClusterIP      10.104.112.103   <none>          80/TCP           45h
kubia-http           LoadBalancer   10.96.105.141    10.96.105.141   8080:31387/TCP   10d
kubia-loadbalancer   LoadBalancer   10.109.92.185    10.109.92.185   80:30642/TCP     13s
kubia-nodeport       NodePort       10.109.13.149    <none>          80:30123/TCP     9h

$ kubectl delete svc kubia-loadbalancer 
service "kubia-loadbalancer" deleted

미니쿠베는 로드밸런서를 지원하지 않는다는 설명으로 책을 마무리하는데, 인터넷을 통해 가능한 방법을 찾아봐서 실습을 진행했다.

미니쿠베 공식 문서 가이드에 따라, 터미널에서 minikube tunnel 명령어를 실행해, 호스트에서 클러스터 IP주소를 통해 서비스 CIDR로 라우팅하는 네트워크를 생성하는 프로세스를 실행시켜, 외부 IP를 내부 호스트 운영체제 상의 프로그램에 노출시켜줄 수 있다고 한다. (이해는 잘 안된다..) 아무튼 한 터미널에서는 minikube tunnel을 실행시키고 별도의 터미널에서 로드밸런서 타입을 생성해 서비스를 확인했더니 외부 IP를 할당받은 바를 확인할 수 있다. 

 

Accessing apps

How to access applications running within minikube

minikube.sigs.k8s.io

 

파드에 http 요청이 전달되는 방법을 상세하게 바라보자. 외부 클라이언트(curl 등)가 로드밸런서 포트에 연결되고, 노드에 할당된 노드포트로 라우팅되며, 그 연결은 결국 파드 인스턴스로 전달된다.

 

로드밸런서 서비스 = 외부 요청 처리용 로드밸런서 + 노드포트 서비스

 

1. 외부 클라이언트의 요청이 로드밸런서로 보내진다.

2. 로드밸런서에는 쿠버네티스 클러스터의 각 노드에 할당된 노드포트들이 연결되어 있다. 

3. 로드밸런서는 노드포트 중 하나로 라우팅한다.

4. 요청을 받은 노드포트들은 클러스터 내 생성된 노드포트 서비스 리소스에 연결된다.

5. 연결된 서비스 리소스가 각 노드들 내의 파드 인스턴스 중 하나에 요청을 전달한다. 

 

서비스 명세에 externalTrafficPolicy = local을 사용하기

1. 홉을 방지하는 externalTrafficPolicy와 그로 인한 부가적 문제

외부 클라이언트가 노드포트로 접속하면, (로드밸런서도 노드포트의 확장판이니 해당) (그에 대한 설명은 아래에 나온다) 요청을 처음 받은 노드와 동일한 노드에 속한 파드가 그 요청을 처리할 수도 있고, 그렇지 않을 수도 있다. 아래의 로드밸런서 서비스의 연결 과정을 보면 알겠지만, 구조상 중간에 노드포트 서비스로 다시 요청이 모여진 뒤 각 파드 인스턴스 중 하나를 선택해 전달하기 때문이다. 파드에 도달하려면 추가적인 네트워크 홉이 필요할 수 있다. 

 

책의 설명에 따르면, 네트워크 홉은, 컴퓨터 네트워크에서 출발지와 목적지 사이에 위치한 경로의 한 부분을 의미한다.

홉(hop)은 컴퓨터 네트워크에서 출발지와 목적지 사이에 위치한 경로의 한 부분이다. 데이터 패킷은 브리지, 라우터, 게이트웨이를 거치면서 출발지에서 목적지로 경유한다. 패킷이 다음 네트워크 장비로 이동할 때마다 홉이 하나 발생한다. 홉 카운트(hop count)는 데이터가 출발지와 목적지 사이에서 통과해야 하는 중간 장치들의 개수를 가리킨다.
출처 : https://ko.wikipedia.org/wiki/%ED%99%89_(%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC)

서비스의 명세에 spec: externalTrafficPolicy: Local과 같이 외부 트래픽 정책을 설정하면, 외부 연결을 받은 노드에서 실행 중인 파드로만 전달하도록 서비스를 구성할 수 있다. 이를 통해 추가적인 홉의 발생을 막는다. 로컬 설정을 통해 서비스 프록시는 로컬 파드를 우선 선택하고, 로컬 파드가 없으면 연결이 중단되므로, 로컬에서 실행 중인 파드가 1개 이상 있는 노드에만 연결을 전달해야 한다는 맹점이 있다. 또한, 연결을 균등하게 분산하지 못하게 되는 단점이 발생한다. 

 

 

2. 클라이언트 IP 주소가 보존되지 않는 문제도 해결해는 로컬 외부 트래픽 정책

클러스터 내 클라이언트가 서비스로 연결할 때 파드가 클라이언트의 주소를 얻는다. 하지만, 노드포트 서비스로 연결을 받으면, 패킷에서 "소스 네트워크 주소 변환"이 수행되어 패킷 소스 IP 주소가 변경된다. 즉, 파드가 실제 주소는 알지 못하기 때문에 클라이언트 주소를 정확히 알아야 하는 애플리케이션에는 사용하지 못한다. 앞에서 말한 로컬 외부 트래픽 정책을 설정하면, 추가 홉이 없어 "소스 네트워크 주소 변환"이 수행되지 않아 주소를 보존할 수 있다. 

 

 

3. 단일 IP 주소로 서비스를 노출하는 인그레스 리소스 만들기

HTTP 레벨에서 작동(7계층 작동)하기 때문에 4계층 서비스보다 더 많은 기능을 제공한다.

인그레스란?
인그레스(Ingress) = 클러스터 내의 서비스에 대한 외부 접근을 관리하는 API 오브젝트이며, 일반적으로 HTTP를 관리함. 인그레스는 부하 분산, SSL 종료, 명칭 기반의 가상 호스팅을 제공할 수 있다. 인그레스는 클러스터 외부에서 클러스터 내부 서비스로 HTTP와 HTTPS 경로를 노출한다. 트래픽 라우팅은 인그레스 리소스에 정의된 규칙에 의해 컨트롤된다. 인그레스는 외부에서 서비스로 접속이 가능한 URL, 로드 밸런스 트래픽, SSL / TLS 종료 그리고 이름-기반의 가상 호스팅을 제공하도록 구성할 수 있다. 인그레스 컨트롤러는 일반적으로 로드 밸런서를 사용해서 인그레스를 수행할 책임이 있으며, 트래픽을 처리하는데 도움이 되도록 에지 라우터 또는 추가 프런트 엔드를 구성할 수도 있다. 인그레스는 임의의 포트 또는 프로토콜을 노출시키지 않는다. HTTP와 HTTPS 이외의 서비스를 인터넷에 노출하려면 보통 Service.Type=NodePort 또는 Service.Type=LoadBalancer 유형의 서비스를 사용한다.
https://kubernetes.io/ko/docs/concepts/services-networking/ingress/

 

인그레스의 필요성

1. 하나의 IP 주소로 여러 서비스에 접근이 가능하도록 지원한다. (요청 호스트와 경로에 따라 서비스를 지정)

2. HTTP, 네트워크 스택 애플리케이션 계층에서 작동하여, 쿠키 기반 세션 어피니티 기능을 지원한다.

 

인그레스 컨트롤러

* 미니쿠베는 기본 인그레스 컨트롤러를 제공하지 않지만, 시험 가능한 애드온을 제공하므로 이를 활성화해 사용해야 한다.

* 환경별로 제공 여부에 차이가 있으니 항상 확인하자. 

 

인그레스 컨트롤러 설치 확인하기

더보기
$ minikube addons list
|-----------------------------|----------|--------------|--------------------------------|
|         ADDON NAME          | PROFILE  |    STATUS    |           MAINTAINER           |
|-----------------------------|----------|--------------|--------------------------------|
...
| helm-tiller                 | minikube | disabled     | third-party (helm)             |
| ingress                     | minikube | disabled     | unknown (third-party)          |
| ingress-dns                 | minikube | disabled     | google                         |
| istio                       | minikube | disabled     | third-party (istio)            |
...
| storage-provisioner         | minikube | enabled ✅   | google                         |
| storage-provisioner-gluster | minikube | disabled     | unknown (third-party)          |
| volumesnapshots             | minikube | disabled     | kubernetes                     |
|-----------------------------|----------|--------------|--------------------------------|

$ minikube addons enable ingress
    ▪ Using image k8s.gcr.io/ingress-nginx/controller:v1.1.0
    ▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
    ▪ Using image k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
🔎  Verifying ingress addon...
🌟  'ingress' 애드온이 활성화되었습니다

$ minikube addons list          
|-----------------------------|----------|--------------|--------------------------------|
|         ADDON NAME          | PROFILE  |    STATUS    |           MAINTAINER           |
|-----------------------------|----------|--------------|--------------------------------|
...
| gvisor                      | minikube | disabled     | google                         |
| helm-tiller                 | minikube | disabled     | third-party (helm)             |
| ingress                     | minikube | enabled ✅   | unknown (third-party)          |
| ingress-dns                 | minikube | disabled     | google                         |
| istio                       | minikube | disabled     | third-party (istio)            |
...
| storage-provisioner         | minikube | enabled ✅   | google                         |
| storage-provisioner-gluster | minikube | disabled     | unknown (third-party)          |
| volumesnapshots             | minikube | disabled     | kubernetes                     |
|-----------------------------|----------|--------------|--------------------------------|

$ kubectl get po --all-namespaces
NAMESPACE              NAME                                        READY   STATUS      RESTARTS   AGE
default                kubia-gfqqj                                 1/1     Running     1          44h
default                kubia-pxp9k                                 1/1     Running     1          44h
default                kubia-vfjg4                                 1/1     Running     1          44h
ingress-nginx          ingress-nginx-admission-create-btnhd        0/1     Completed   0          47s
ingress-nginx          ingress-nginx-admission-patch-68xkt         0/1     Completed   1          47s
ingress-nginx          ingress-nginx-controller-6d5f55986b-4rk69   1/1     Running     0          47s
kube-system            coredns-64897985d-w9lm5                     1/1     Running     4          22d
kube-system            etcd-minikube                               1/1     Running     5          22d
kube-system            kube-apiserver-minikube                     1/1     Running     5          22d
kube-system            kube-controller-manager-minikube            1/1     Running     5          22d
kube-system            kube-proxy-gqm9x                            1/1     Running     5          22d
kube-system            kube-scheduler-minikube                     1/1     Running     5          22d
kube-system            storage-provisioner                         1/1     Running     6          22d
kubernetes-dashboard   dashboard-metrics-scraper-58549894f-5s4br   1/1     Running     3          10d
kubernetes-dashboard   kubernetes-dashboard-ccd587f44-s52d5        1/1     Running     4          10d

한가지 이상한 점은, 책의 결과 출력에서는 nginx-ingress-controller가 kube-system의 파드에 생성되었다는 것인데, 그와 달리 나의 실습 환경에서는 ingress-nginx-controller가 별도의 ingress-nginx 네임스페이스에 생성된다는 것이다. 

 

인그레스 리소스를 생성해보자

책의 kubia-ingress.yaml은 apiVersion부터 yaml 양식까지 최신화가 필요하다.

아래 코드의 흐름을 따라가면서 수정해보았고, 결국 생성에 성공했다. 

$ kubectl create -f kubia-ingress.yaml 
error: unable to recognize "kubia-ingress.yaml": no matches for kind "Ingress" in version "extensions/v1beta1"

$ vim kubia-ingress.yaml              
$ cat kubia-ingress.yaml              
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: kubia-nodeport
          servicePort: 80
          
$ kubectl create -f kubia-ingress.yaml
error: error validating "kubia-ingress.yaml": error validating data: [ValidationError(Ingress.spec.rules[0].http.paths[0].backend): unknown field "serviceName" in io.k8s.api.networking.v1.IngressBackend, ValidationError(Ingress.spec.rules[0].http.paths[0].backend): unknown field "servicePort" in io.k8s.api.networking.v1.IngressBackend, ValidationError(Ingress.spec.rules[0].http.paths[0]): missing required field "pathType" in io.k8s.api.networking.v1.HTTPIngressPath]; if you choose to ignore these errors, turn validation off with --validate=false

$ vim kubia-ingress.yaml              
$ cat kubia-ingress.yaml              
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: kubia-nodeport
            port:
              number: 80

$ kubectl create -f kubia-ingress.yaml
ingress.networking.k8s.io/kubia created

$ kubectl describe ingress kubia 
Name:             kubia
Namespace:        default
Address:          localhost
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host               Path  Backends
  ----               ----  --------
  kubia.example.com  
                     /   kubia-nodeport:80 (172.17.0.2:8080,172.17.0.3:8080,172.17.0.4:8080)
Annotations:         <none>
Events:
  Type    Reason  Age                    From                      Message
  ----    ------  ----                   ----                      -------
  Normal  Sync    3m43s (x2 over 3m58s)  nginx-ingress-controller  Scheduled for sync
 
$ kubectl get ingresses         
NAME    CLASS   HOSTS               ADDRESS     PORTS   AGE
kubia   nginx   kubia.example.com   localhost   80      30s

$ curl http://kubia.example.com
curl: (6) Could not resolve host: kubia.example.com

$ minikube ip
192.168.59.100

$ sudo vim /etc/hosts
Password:

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

https://kubernetes.io/docs/concepts/services-networking/ingress/

결국 인그레스 관련 공식 문서를 찾아 양식을 맞추면서 문제를 해결해 생성했다.

 

Ingress

FEATURE STATE: Kubernetes v1.19 [stable] An API object that manages external access to the services in a cluster, typically HTTP. Ingress may provide load balancing, SSL termination and name-based virtual hosting. Terminology For clarity, this guide define

kubernetes.io

https://kubernetes.io/ko/docs/tasks/access-application-cluster/ingress-minikube/

 

NGINX 인그레스(Ingress) 컨트롤러로 Minikube에서 인그레스 설정하기

인그레스는 클러스터의 서비스에 대한 외부 액세스를 허용하는 규칙을 정의하는 API 객체이다. 인그레스 컨트롤러는 인그레스에 설정된 규칙을 이행한다. 이 페이지에서는 HTTP URI에 따라 요청을

kubernetes.io

 

동작 방식은 다음과 같다. 

1. 클라이언트가 DNS에 kubia.example.com을 찾는다. curl htttp://kubia.example.com

2. 클라이언트가 헤더 Host:kubia.example.com과 함께 HTTP GET 요청을 인그레스 컨트롤러에 보낸다. 

3. 인그레스 컨트롤러가 파드에 요청을 보낸다. 

(인그레스 컨트롤러 - 인그레스 - 서비스 - 엔드포인트 형태로 연결됨)

 

여러 서비스 노출하기

동일한 호스트의 다른 경로로 여러 서비스를 노출할 수 있다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /kubia
        pathType: Prefix
        backend:
          service:
            name: kubia
            port:
              number: 80
      - path: /bar
        pathType: Prefix
        backend:
          service:
            name: bar
            port: 80

서로 다른 호스트로 여러 서비스 노출도 가능하다.

 

TLS 트래픽 처리하기

HTTPS 트래픽을 전달하기 위해서는 TLS를 지원하도록 해야 한다. 

HTTPS
HTTPS (HTTP Secure) 는 HTTP protocol의 암호화된 버전이다. 이것은 대개 클라이언트와 서버 간의 모든 커뮤니케이션을 암호화 하기 위하여 SSL 이나 TLS을 사용한다. 이 커넥션은 클라이언트가 민감한 정보를 서버와 안전하게 주고받도록 해준다. 예를들면 금융 활동 이나 온라인 쇼핑이 있을 수 있
https://developer.mozilla.org/ko/docs/Glossary/https

클라이언트가 인그레스 컨트롤러에 대한 TLS 연결을 하면 컨트롤러는 그 연결을 종료한다. 클라이언트 - 컨트롤러 통신은 암호화되어도, 컨트롤러 - 백엔드 통신은 암호화되지 않는다. 파드의 애플리케이션은 암호화를 지원할 필요가 없다. 컨트롤러가 모두 처리하도록 할 수 있는데, 이를 위해선 인증서 + 개인 키를 인그레스에 첨부해야 한다. 이때 쿠버네티스 리소스, "시크릿"을 사용해 저장하고 인그레스 매니페스트가 참조한다. 시크릿은 추후 설명된다.

암호화 보안 프로토콜: TLS
암호화 프로토콜은 두 당사자가 개인정보 보호 및 데이터 무결성으로 통신하도록 하여 안전한 연결을 제공합니다. TLS(Transport Layer Security) 프로토콜은 SSL(Secure Socket Layer) 프로토콜에서 발전한 것입니다. IBM® MQ는 TLS를 지원합니다. 두 프로토콜의 기본 목표는 기밀성(개인정보 보호라고도 함), 데이터 무결성, 인증, 디지털 인증서를 사용하는 인증을 제공하는 것입니다. 두 프로토콜이 유사하긴 하지만 SSL 3.0과 TLS의 다양한 버전이 상호 운용되지 않는다는 점은 상당한 차이점입니다.
https://www.ibm.com/docs/ko/ibm-mq/9.1?topic=mechanisms-cryptographic-security-protocols-tls
CertificateSigningRequest 리소스로 인증서 서명하기
해당 리소스를 통해 다음과 같이 인증서 요청을 승인할 수 있다. $ kubectl certificate approve <name of CSR>
그뒤 해당 리소스의 status.certificate 필드에서 인증서 검색이 가능하다. 서명자 구성 요소가 실행중이어야 승인/거부가 작동함에 유의하자. (출처: 책)
$ openssl genrsa -out tls.key 2048
Generating RSA private key, 2048 bit long modulus
................................+++
............................+++
e is 65537 (0x10001)

$ 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

$ vim kubia-ingress-tls.yaml 
$ cat kubia-ingress-tls.yaml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubia
spec:
  tls:
  - hosts: 
    - kubia.example.com
    secretName: tls-secret
  rules:
  - host: kubia.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: 
            name: kubia-nodeport
            port: 
              number: 80

$ kubectl create -f kubia-ingress-tls.yaml
ingress.networking.k8s.io/kubia created

$ minikube ip
192.168.59.100

$ sudo vim /etc/hosts
Password:

$ curl -k -v https://kubia.example.com/kubia
*   Trying 192.168.59.100:443...
* Connected to kubia.example.com (192.168.59.100) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=kubia.example.com
*  start date: Feb 16 12:13:49 2022 GMT
*  expire date: Feb 11 12:13:49 2023 GMT
*  issuer: CN=kubia.example.com
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fd776012e00)
> GET /kubia HTTP/2
> Host: kubia.example.com
> user-agent: curl/7.77.0
> accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200 
< date: Wed, 16 Feb 2022 12:23:01 GMT
< 
You've hit kubia-gfqqj
* Connection #0 to host kubia.example.com left intact
삭제하고 다시 만드는 대신 포수 $ kubectl apply -f kubia-ingress-tls.yaml을 호출하면 자동으로 업데이트된다.

 

파드가 요청을 처리할 준비가 되었을 때 신호 발송하기

레디니스 프로브

레디니스 프로브는 주기적으로 호출되어 파드가 요청 수신이 가능한지를 확인한다. 레디니스 프로브가 성공을 반환하면 준비가 완료되었다는 의미다. 유형은 아래와 같이 3가지이다. 

 

1. Exec 프로브 : 프로세스를 실행. 컨테이너 상태를 프로세스 종료 상태 코드로 결정한다.

2. HTTP GET 프로브 : HTTP GET 요청을 컨테이너로 보내고 응답 상태를 확인한다.

3. TCP 소켓 프로브 : 컨테이너 지정 포트로 TCP 연결을 열어, 연결이 성공하면 준비된 것이다.

 

동작 방식은 다음과 같다. 

1. 최초 레디니스 점검 이전, 컨테이너 시작 시 구성 가능 시간을 일정량 기다리도록 구성이 가능하다.

2. 주기적으로 레디니스 프로브를 호출한다. 

3. 그 결과에 따라 작동한다. (파드가 준비됨 = 그대로 진행. 실패 시 서비스 제거.)

점검에 실패한다고 컨테이너가 종료되거나 재시작되지 않다는 특징이 있다.

준비가 된 파드의 컨테이너만 요청을 수신하도록 하지, 굳이 교체해 정상 유지까지 진행하지는 않는다.

레디니스 프로브 실패 -> 엔드포인트 오브젝트에서 파드를 제거 -> 클라이언트 요청 미전달

레디니스 프로브가 실패한 것은 서비스 엔드포인트에서 제거된다.

미리 준비 상태를 확인하여 정상 상태인 파드하고만 통신시키므로 클라이언트는 문제를 알아차리지 못하는 장점이 있다.

 

$ cat kubia-rc-readinessprobe.yaml 
apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - name: http
          containerPort: 8080
        readinessProbe:
          exec:
            command:
            - ls
            - /var/ready

위의 명세에서는 ls는 파일이 존재하면 종료코드 0을, 존재하지 않으면 0이 아닌 값을 반환한다. 

 

$ cat kubia-rc-readinessprobe.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - name: http
          containerPort: 8080
        readinessProbe:
          exec:
            command:
            - ls
            - /var/ready

$ kubectl create -f kubia-rc-readinessprobe.yaml
replicationcontroller/kubia created

$ kubectl get po                  
NAME          READY   STATUS              RESTARTS   AGE
kubia-h9cp5   0/1     ContainerCreating   0          3s
kubia-j49xw   0/1     ContainerCreating   0          3s
kubia-mfrd6   0/1     ContainerCreating   0          3s

$ kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-h9cp5   0/1     Running   0          11s
kubia-j49xw   0/1     Running   0          11s
kubia-mfrd6   0/1     Running   0          11s

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

$ kubectl get po kubia-h9cp5 
NAME          READY   STATUS    RESTARTS   AGE
kubia-h9cp5   1/1     Running   0          42s

$ kubectl describe po kubia-h9cp5 
Name:         kubia-h9cp5
Namespace:    default
Priority:     0
Node:         minikube/192.168.59.100
Start Time:   Wed, 16 Feb 2022 22:34:42 +0900
Labels:       app=kubia
Annotations:  <none>
Status:       Running
IP:           172.17.0.2
IPs:
  IP:           172.17.0.2
Controlled By:  ReplicationController/kubia
Containers:
  kubia:
    Container ID:   docker://bfe76cabd1e1b668eb3c93a611a2de951ad4f8e586e20260df338198ebc85ac4
    Image:          luksa/kubia
    Image ID:       docker-pullable://luksa/kubia@sha256:3f28e304dc0f63dc30f273a4202096f0fa0d08510bd2ee7e1032ce600616de24
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Wed, 16 Feb 2022 22:34:45 +0900
    Ready:          True
    Restart Count:  0
    Readiness:      exec [ls /var/ready] delay=0s timeout=1s period=10s #success=1 #failure=3
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-54wpv (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  kube-api-access-54wpv:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  68s                default-scheduler  Successfully assigned default/kubia-h9cp5 to minikube
  Normal   Pulling    67s                kubelet            Pulling image "luksa/kubia"
  Normal   Pulled     65s                kubelet            Successfully pulled image "luksa/kubia" in 2.417118382s
  Normal   Created    65s                kubelet            Created container kubia
  Normal   Started    65s                kubelet            Started container kubia
  Warning  Unhealthy  38s (x6 over 64s)  kubelet            Readiness probe failed: ls: cannot access /var/ready: No such file or directory

$ kubectl get endpoints kubia-loadbalancer
Error from server (NotFound): endpoints "kubia-loadbalancer" not found

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

$ kubectl get endpoints kubia-loadbalancer
NAME                 ENDPOINTS         AGE
kubia-loadbalancer   172.17.0.2:8080   3s

$ kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-h9cp5   1/1     Running   0          3m17s
kubia-j49xw   0/1     Running   0          3m17s
kubia-mfrd6   0/1     Running   0          3m17s

서비스에서 파드를 수동으로 제거하려면 파드를 삭제하거나 파드의 레이블을 변경해야 한다. 

파드와 서비스의 레이블 셀렉터에 enabled=true 레이블을 추가하면 된다.

그외로 서비스에서 파드를 제거하려면 레이블 자체를 제거해야 한다.

 

유의사항

1. 레디니스 프로브를 항상 정의하자.

파드에 레디니스 프로브를 추가하지 않으면 시작하는 즉시 서비스 엔드포인트가 된다. 수신 연결의 문제로 Connection Refused의 문제를 겪지 않도록 항상 미리 정의해두자.

2. 레디니스 프로브에 파드 종료 코드를 포함하지 않도록 유의하자.

파드 종료 시 애플리케이션은 종료 신호를 받자마자 연결을 중단하지만, 종료 즉시 레디니스 프로브가 실행되도록 만들 필요가 없다. 파드 삭제 시 모든 서비스에서 해당 파드를 자동적으로 제거하기 때문이다.

 

 

헤드리스 서비스

클라이언트가 모든 파드에 연결하려면, 

1. 쿠버네티스 API 서버를 호출해 파드 전체의 주소 목록을 가져오기 (바람직하지 않다)

2. 쿠버네티스가 서비스에 대한 DNS 조회로 파드 주소 찾기 (서비스 명세에 clusterIP=None으로 설정)

2개의 방법이 존재한다.

 

헤드리스 상태는, 클라이언트가 서비스에 연결할 수 있는 클러스터 IP를 할당하지 않아 서비스가 되는 상태이다.

Headless Service
일반적인 쿠버네티스 서비스와 유사하지만 IP를 할당받지 않는다. request를 로드밸런싱하지 않으며, 포드 이름과 하위 도메인을 이용해 각 포드에 대한 DNS만 생성한다.
https://passwd.tistory.com/119
$ kubectl create -f kubia-svc-headless.yaml
service/kubia-headless created

$ cat kubia-svc-headless.yaml 
apiVersion: v1
kind: Service
metadata:
  name: kubia-headless
spec:
  clusterIP: None
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

$ kubectl get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-h9cp5   1/1     Running   0          20m
kubia-j49xw   0/1     Running   0          20m
kubia-mfrd6   0/1     Running   0          20m

$ kubectl exec kubia-j49xw -- touch /var/ready
$ kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity
Error: unknown flag: --generator

해당 플래그는 더이상 사용 불가능함이 이전 챕터에도 등장했는데, 이번 챕터에서도 별도 레플리케이션 컨트롤러 없이 파드만 단독 생성하고자 해당 플래그를 사용한 것으로 보인다. ( https://kubernetes.io/ko/docs/reference/kubectl/conventions/ )

그래서 우선 실습을 중단하고 내용을 이어가자면, 헤드리스 서비스는 파드의 IP를 반환하기 때문에 클라이언트가 서비스 프록시 대신 파드에 직접 연결한다는 점, 여전히 파드 간 로드밸런싱을 제공은 하지만 DNS 라운드 로빈 메커니즘 기반이지 서비스 프록시 기반이 아니라는 점 등이 특징으로 나온다.

 

준비되지 않은 파드도 서비스에 추가되게 하려면 annotation을 수정하면 된다. 

 

 

서비스 관련 문제 해결 가이드

1. 클러스터 IP에 클러스터 내부에서 연결 가능한지 확인하기

2. 서비스 IP 핑은 불가하니 시도하지 않기

3. 레디니스 프로브 정의했는지, 응답은 잘 성공했는지 확인하기

4. 파드가 서비스에 포함됐는지 엔드포인트 확인하기

5. 도메인(FQDN 등) 문제인지 IP 문제인지 둘 다 시도해보기

6. 올바른 서비스 노출 포트에 연결 시도 중인지 확인하기

7. 파드에 직접 연결해 확인하기

8. 파드에 직접 액세스가 안될 경우 애플리케이션이 로컬호스트에만 연결됐는지 확인하기