Profile picture

[k8s] Deep Dive - Service(NodePort)

JaehyoJJAng2023년 04월 04일

▶︎ 개요

쿠버네티스에서는 외부에서 쿠버네티스 클러스터에 접속하는 방법을 서비스(Service)라고 한다.
외부 사용자가 쿠버네티스에 접속하여 파드를 이용하려면 서비스를 사용해야 한다.

앞서 실습한 ClusterIP는 외부에 접근할 수 없으며, 쿠버네티스 내부에서 접근할 때 사용되는 리소스이다. 그렇기에 클라이언트가 생성한 Service에 접근하기 위해서는 중간에 어떤 매개체를 요구할 수 밖에 없다.

이를 충족시켜 줄 첫 번째 방법으로 NodePort 타입의 서비스를 이용하는 것이다.
image


노드포트 타입으로 서비스를 생성하면 쿠버네티스는 모든 노드에 특정 포트를 할당하며 서비스를 구성하는 파드로 들어오는 트래픽을 전달한다.

아래 그림과 같이 모든 노드에 대하여 동일한 포트 번호(30080)가 열려 있다. 그렇기에 어느 노드에 서비스 파드가 존재하던지 간에 노드포트를 통해 파드와 통신이 가능하다.
image


▶︎ NodePort

외부에서 쿠버네티스 클러스터 내부에 접속하는 가장 쉬운 방법은 노드포트 서비스를 이용하는 것으로 노드포트 서비스를 설정하면 모든 워커 노드의 특정 포트를 열고 여기로 오는 모든 요청을 노드포트 서비스로 전달한다. 그리고 노드포트 서비스는 해당 업무를 처리할 수 있는 파드로 요청을 전달함.
image


‣ 외부에서 접속

아래 작업을 따라해보자.


1. 디플로이먼트로 파드를 생성하자. 이 때 이미지는 sysnet4admin 게정에 있는 echo-hname을 사용한다.

$ kubectl create deployment np-pods --image=sysnet4admin/echo-hname
deployment.apps/np-pods created

2. 배포된 파드 확인

$ kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
np-pods-5767d54d4b-9ss7p   1/1     Running   0          17s   172.16.221.182   w1-k8s   <none>           <none>

3. kubectl create로 노드포트 서비스를 생성하자. 여기서는 편의를 위해 이미 정의한 오브젝트 스펙을 사용한다.

$ kubectl create -f ~/_Book_k8sInfra/ch3/3.3.1/nodeport.yaml

사용하는 오브젝트 스펙은 다음과 같다.

apiVersion: v1
kind: Service
metadata:
  name: np-svc
spec:
  selector:
    app: np-pods
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30000
  type: NodePort

4. kubectl get services를 실행해 노드포트 서비스로 생성한 np-svc 서비스를 확인해보자

$ kubectl get svc -o wide
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE     SELECTOR
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        3d23h   <none>
np-svc       NodePort    10.109.11.30   <none>        80:30000/TCP   7s      app=np-pods

노드포트의 포트 번호가 30000번으로 지정되었다. CLUSTER-IP(10.109.11.30)는 쿠버네티스 클러스터의 내부에서 사용하는 IP로, 자동으로 지정된다.


5. 쿠버네티스 클러스터의 워커 노드 IP를 확인하자.

$ kubectl get nodes -o wide
NAME     STATUS   ROLES    AGE     VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION                CONTAINER-RUNTIME
m-k8s    Ready    master   3d23h   v1.18.4   192.168.1.10    <none>        CentOS Linux 7 (Core)   3.10.0-1160.90.1.el7.x86_64   docker://1.13.1
w1-k8s   Ready    <none>   3d23h   v1.18.4   192.168.1.101   <none>        CentOS Linux 7 (Core)   3.10.0-1160.90.1.el7.x86_64   docker://1.13.1
w2-k8s   Ready    <none>   5h41m   v1.18.4   192.168.1.102   <none>        CentOS Linux 7 (Core)   3.10.0-1160.90.1.el7.x86_64   docker://1.13.1

6. 호스트 PC에서 웹 브라우저를 띄우고 192.168.1.101 ~ 102와 30000번(노드포트의 포트 번호)으로 접속해 외부에서 접속되는지 확인해보자. 나는 편의상 마스터 노드 서버의 터미널에서 curl로 테스트해보겠다.

# w1-k8s
$ curl -X GET 192.168.1.101:30000
np-pods-5767d54d4b-9ss7p

# w2-k8s
$ curl -X GET 192.168.1.102:30000
np-pods-5767d54d4b-9ss7p

‣ 부하분산 테스트

디플로이먼트로 생성된 파드 1개에 접속 중인 상태에서 파드가 3개로 증가하면 현재 접속 상태가 어떻게 바뀔까? 부하가 분산되는지(로드밸런서 기능) 확인해보자.


1. 호스트 PC에 터미널 창을 띄우고 다음 명령을 실행하자. 이 명령은 반복적으로 192.168.1.101:30000에 접속해 접속한 파드 이름을 화면에 표시한다. 이렇게 하면 파드가 1개에서 3개로 늘어나는 시점을 관찰할 수 있다.

$ i=1; while true; do sleep 1; echo "$(( i++ )) - $(curl -s -X GET 192.168.1.101:30000)"; done

image


2. 위 코드를 실행했으면 쿠버네티스 마스터 노드에서 scale을 실행해 파드를 3개로 증가시키자

$ kubectl scale deployment/np-pods --replicas=3

3. 배포된 파드를 확인해보자.

$ kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
np-pods-5767d54d4b-9ss7p   1/1     Running   0          30m   172.16.221.182   w1-k8s   <none>           <none>
np-pods-5767d54d4b-hkg2r   1/1     Running   0          56s   172.16.103.143   w2-k8s   <none>           <none>
np-pods-5767d54d4b-lqscw   1/1     Running   0          56s   172.16.221.183   w1-k8s   <none>           <none>

4. 터미널 창을 확인해 부하분산이 제대로 되고 있는지 확인해보자.
image
어떻게 추가된 파드를 외부에서 추적해 접속하는 것일까? 이는 노드포트의 오브젝트 스펙에 적힌 np-pods와 디플로이먼트의 이름을 확인해 동일하면 같은 파드라고 간주하기 때문이다.

spec:
  selector:
  app: np-pods

‣ expose로 노드포트 생성

노드포트 서비스는 오브젝트 스펙 파일로만 생성할 수 있을까? 그건 아니다. 노드포트 서비스는 expose 명령어로도 생성이 가능하다.
아래 작업을 따라해보자.


1. expose 명령어를 사용해 서비스로 내보낼 디플로이먼트를 np-pods로 지정하자. 해당 서비스의 이름은 np-svc-v2로, 타입은 NodePort로 지정한다.(이 때 서비스 타입은 반드시 대소문자 구분) 마지막으로 서비스가 파드로 보내줄 연결 포트를 80번으로 지정하자

$ kubectl expose deployment/np-pods --type=NodePort --name=np-svc-v2 --port=80
service/np-svc-v2 exposed

2. kubectl get svc를 실행해 생성된 서비스를 확인해보자. 오브젝트 스펙으로 생성할 때는 노드포트 포트 번호를 30000번으로 지정했으나 expose를 사용하면 노드포트의 포트 번호를 지정할 수 없다. 포트 번호는 30000 ~ 32767 사이에서 임의로 지정된다.

$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        4d
np-svc       NodePort    10.109.11.30   <none>        80:30000/TCP   31m
np-svc-v2    NodePort    10.106.146.0   <none>        80:30100/TCP   71s

3. 호스트 PC에서 웹 브라우저 접속 OR 터미널 창에서 curl로 테스트해보자.

$ curl -X GET 192.168.1.101:30100
np-pods-5767d54d4b-lqscw

▶︎ Ingress

노드포트 서비스는 포트를 중복으로 사용할 수 없기 때문에 1개의 NodePort에 하나의 Deployment만 적용된다.

여러 개의 Deployment가 있을 때는 그 수만큼의 NodePort 서비스를 구동해야할까? 쿠버네티스에서는 이런 경우에 Ingress를 사용한다.

인그레스(Ingress)는 고유한 주소를 제공해 사용 목적에 따라 다른 응답을 제공할 수 있고, 트래픽에 대한 L4/L7 로드밸런서와 보안 인증서를 처리하는 기능을 제공한다.

인그레스를 사용하려면 인그레스 컨트롤러가 필요하다. 다양한 인그레스 컨트롤러가 있지만, 여기서는 쿠버네티스에서 프로젝트로 지원하는 **NGINX 인그레스 컨트롤러(NGINX Ingress Controller)**로 구성해보도록 하자.

여기서 NGINX Ingress Controller가 다음 단계로 작동한다.

  • 사용자는 노드마다 설정된 노드포트를 통해 노드포트 서비스로 이동한다. 이 때 노드포트 서비스를 NGINX 인그레스 컨트롤러로 구성한다.
  • NGINX 인그레스 컨트롤러는 사용자의 접속 경로에 따라 적합한 클러스터 IP 서비스로 경로를 제공한다.
  • 클러스터 IP 서비스는 사용자를 해당 파드로 연결해 준다.

💡

인그레스 컨트롤러는 파드와 직접 통신할 수 없어서 노드포트 또는 로드밸런서 서비스와 연동되어야 한다. 따라서 노드포트로 이를 연동하도록 하자.

image


‣ 실습

아래 작업을 따라해보자.

1. 테스트용으로 디플로이먼트 2개를 배포해보자

$ kubectl create deployment in-hname-pod --image=sysnet4admin/echo-hname
$ kubectl create deployment in-ip-pod --image=sysnet4admin/echo-ip

2. 배포된 파트 확인

$ kubectl get pods -o wide
NAME                            READY   STATUS              RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
in-hname-pod-8565c86448-ccw54   1/1     Running             0          10s   172.16.221.184   w1-k8s   <none>           <none>
in-ip-pod-76bf6989d-46jsh       0/1     ContainerCreating   0          4s    <none>           w2-k8s   <none>           <none>

3. NGINX 인그레스 컨트롤러를 설치하자. 여기에는 많은 종류의 오브젝트 스펙이 포함된다. 설치되는 요소들은 NGINX 인그레스 컨트롤러 서비스를 제공하기 위해 미리 지정되어 있다.

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress-nginx.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created

4. NGINX 인그레스 컨트롤러의 파드가 배포되었는지 확인해보자. NGINX 인그레스 컨트롤러는 default 네임스페이스가 아닌 ingress-nginx 네임스페이스에 속하므로 -n ingress-nginx 옵션을 추가해야 한다. 여기서 -n은 namespaces의 약어로, default 외의 네임스페이스를 확인할 때 사용하는옵션이다. 파드뿐만 아니라 서비스를 확인할 때도 동일한 옵션을 준다.

$ kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-5bb8fb4bb6-qst9p   1/1     Running   0          97s

5. 인그레스를 사용자 요구 사항에 맞게 설정하려면 경로와 작동을 정의해야 한다. 파일로도 설정할 수 있으므로 다음 경로로 실행해 미리 정의해둔 설정을 적용한다.

$ kubectl apply -f ~/_Book_k8sInfra/3.3.2/ingress-config.yaml
ingress.networking.k8s.io/ingress-nginx created
# ingress-config.yaml
kind: Ingress
metadata:
  name: ingress-nginx
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path:
        backend:
          serviceName: hname-svc-default
          servicePort: 80
      - path: /ip
        backend:
          serviceName: ip-svc
          servicePort: 80
      - path: /your-directory
        backend:
          serviceName: your-svc
          servicePort: 80

이 파일은 들어오는 주소 값과 포트에 따라 노출된 서비스를 연결하는 역할을 설정한다. 외부에서 주소 값과 노드포트를 가지고 들어오는 것은 hname-svc-default 서비스와 연결된 파드로 넘기고, 외부에서 들어오는 주소 값, 노드포트와 함께 뒤에 /ip를 추가한 주소 값은 ip-svc 서비스와 연결된 파드로 접속하게 설정했다.


6. ingress 설정 파일이 제대로 동작하는지 확인해보자

$ kubectl get ingress -o wide
NAME            CLASS    HOSTS   ADDRESS   PORTS   AGE
ingress-nginx   <none>   *                 80      2m28s

7. kubectl get ingress -o yaml 을 실행해 인그레스에 요청한 내용이 확실하게 적용됐는지 확인하자.

$ kubectl get ingress -o yaml
apiVersion: v1
items:
- apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
...[생략]...

8. NGINX 인그레스 컨트롤러 생성과 인그레스 설정을 완료했다. 이제 외부에서 NGINX 인그레스 컨트롤러에 접속할 수 있도록 노드포트 서비스로 NGINX 인그레스 컨트롤러를 외부에 노출하면 된다.

$ kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress.yaml
# ingress.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
spec:
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30100
  - name: https
    protocol: TCP
    port: 443
    targetPort: 443
    nodePort: 30101
  selector:
    app.kubernetes.io/name: ingress-nginx

기존 노드포트와 달리 http를 처리하기 위해 30100번 포트로 들어온 요청을 80번 포트로 넘기고, https를 처리하기 위해 30101번 포트로 들어온 요청을 443번 포트로 넘긴다. 그리고 NGINX 인그레스 컨트롤러가 위치하는 네임스페이스를 ingress-nginx로 지정하고 NGINX 인그레스 컨트롤러의 요구 사항에 따라 셀렉터를 ingress-nginx로 지정하였다.


9. 노드포트 서비스로 생성된 NGINX 인그레스 컨트롤러를 확인하자. 이 때도 -n ingress-nginx 로 네임스페이스를 지정해야만 내용 확인이 가능하다

$ kubectl get svc -n ingress-nginx
NAME                       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
nginx-ingress-controller   NodePort   10.98.198.103   <none>        80:30100/TCP,443:30101/TCP   2m38s

10. expose 명령으로 디플로이먼트(in-hname-pod,in-ip-pod)도 서비스로 노출하자. 외부에서 통신하기 위해 클러스터 내부에서만 사용하는 파드를 클러스터 외부에 노출할 수 있는 구역으로 옮기는 것이다.

$ kubectl expose deployment/in-hname-pod --name=hname-svc-default --port=80,443
service/hname-svc-default exposed
$ kubectl expose deployment/in-ip-pod --name=ip-svc --port=80,443
service/ip-svc exposed

11. 생성된 서비스를 점검하여 디플로이먼트들이 서비스에 정상적으로 노출되는지 확인하자.

$ kubectl get svc

12. 호스트 PC OR 터미널 창에서 192.168.1.101:30100에 접속해 외부에서 접속되는 경로에 따라 다르게 작동하는지 확인하자

$ curl -X GET 192.168.1.101:30100 
in-hname-pod-8565c86448-ccw54

$ curl -X GET 192.168.1.101:30100/ip
request_method : GET | ip_dest: 172.16.103.144

Loading script...