Nginx & WSGI pod를 service로 노출하기

최초작성일 [2020.03.18]

아래와 같은 구조로 쿠버네티스의 service를 생성하려고 한다. (replicaset 또는 deployment를 미리 생성해야 한다.)

1. WSGI pod의 service 생성하기

WSGI 프로세스는 아래의 configuration으로 작동한다. 포트번호 9090을 통해 트래픽을 받아 flask 애플리케이션으로 전달한다.

[uwsgi]
wsgi-file=app/app.py
# flask app's name
callable=flask_app

# bind to the specified UNIX/TCP socket using default protocol
socket=:9090
# set internal http socket timeout
http-timeout=5

# enable master process
master = 1
# spawn the specified number of workers/processes
processes = 4

enable-threads=true

# serialize accept(), AKA Thundering Herd
# https://uwsgi-docs.readthedocs.io/en/latest/articles/SerializingAccept.html
thunder-lock=true

WSGI는 nginx를 통해 들어오는 트래픽만을 허용하므로 service 타입은 ClusterIP로 선택한다. ClusterIP 타입은 pod를 클러스터 내부에 한정하여 노출하려고 할 때 사용한다.

apiVersion: v1

kind: Service

metadata:
  name: service-flask-wsgi

spec:
  type: ClusterIP
  selector:
    app: flask-wsgi
  ports:
    - name: port-flask-wsgi
      port: 9090
      targetPort: 9090

위에서 작성한 템플릿 service-app.yml 을 클러스터에 적용한다.

$ kubectl apply -f service-app.yml
service/service-flask-wsgi created

$ kubectl get service
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes           ClusterIP   100.64.0.1       <none>        443/TCP          4d23h
service-flask-wsgi   ClusterIP   100.66.110.185   <none>        9090/TCP         121m

metadata.name의 값에 맞게 service-flask-wsgi라는 서비스가 생성되었다. CLUSTER-IP 필드의 IP주소는 클러스터 내부에서 해당 서비스에 접근하는 용도로 쓰인다. 즉, 클러스터 외부에서는 CLUSTER-IP필드의 주소로 접근할 수 없다.

kubernetes라는 서비스가 이미 있는 것을 볼 수 있다. kubernetes서비스는 pod 내부에서 쿠버네티스의 API에 접근하기 위한 용도로 쓰인다.

2. Nginx pod의 service 생성하기

nginx.conf는 아래와 같다. Nginx가 업스트리밍하는 서버의 주소가 위에서 생성한 서비스의 이름에 해당하 service-flask-wsgi인 것을 알 수 있다. 쿠버네티스 애플리케이션에는 service나 pod와 같은 리소스를 쉽게 찾을 수 있도록 리소스의 이름을 레코드로 관리하는 내부 DNS가 있다. 그리고 pod들은 이 DNS에 주소를 질의하기 때문에 service의 이름을 주소로 사용할 수 있다.

user nginx;
worker_processes auto;
...
http {
    ...
    upstream flask_server {
        server service-flask-wsgi:9090;
        keepalive 512;
        keepalive_timeout 5;
    }
    server {
        listen 80;
        server_name service-flask-wsgi;

        location / {
            include /etc/nginx/uwsgi_params;
            uwsgi_pass flask_server;
        }
    }
}

Nginx pod를 클러스터 외부에 노출하기 위해 아래와 같은 템플릿을 작성한다. NodePort 타입은 모든 노드의 특정 포트를 개방하여 외부에서 서비스에 접근하도록 허용한다. 아래의 service는 모든 노드에 포트번호 80번을 개방한다.

apiVersion: v1

kind: Service

metadata:
  name: service-my-nginx

spec:
  type: NodePort
  selector:
    app: my-nginx
  ports:
    - name: port-my-nginx
      port: 8080
      targetPort: 80

위에서 작성한 템플릿 service-nginx.yml을 클러스터에 생성한다.

$ kubectl apply -f service-nginx.yml
service/service-my-nginx created

$ kubectl get services
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes           ClusterIP   100.64.0.1       <none>        443/TCP          4d22h
service-flask-wsgi   ClusterIP   100.66.110.185   <none>        9090/TCP         60m
service-my-nginx     NodePort    100.68.131.17    <none>        8080:31085/TCP   4s

service-my-nginx라는 이름으로 service가 생성된 것을 확인할 수 있다. service 타입이 NodePort였음에도 CLUSTER-IP가 만들어진 것을 볼 수 있는데, NodePort 타입의 service가 ClusterIP 타입의 기능을 포함하기 때문이다. 따라서 NodePort 타입으로 생성한 서비스는

  • 클러스터 내부에서 CLUSTER-IP 또는 service 이름을 통해 접근할 수 있고

  • 클러스터 외부에서 노드의 IP를 통해 접근할 수 있다.

PORT(S)의 값은 <클러스터 내부에서 접근할 때 쓰는 포트번호>:<클러스터 외부에서 접근할 때 쓰는 포트번호>로 되어있다.

클러스터 내부에서 service-my-nginx 에 접근해보자. 클러스터 내부의 워커 노드 중 하나에 접속하여 curl을 실행했다. IP주소는 CLUSTER-IP에 해당하는 100.68.131.17이고 포트번호 8080으로 접근한다.

admin@ip-172-20-40-1:~$ curl 100.68.131.17:8080
Hello, This is DaEun Kim.

이번에는 클러스터 외부에서 service-my-nginx에 접근한다. 워커 노드의 퍼블릭IP주소와 함께 포트번호는 31085으로 접근한다. 내/외부 IP주소를 포함한 노드의 정보를 조회하고 싶다면 아래의 커맨드를 실행한다.

$ kubectl get nodes -o wide  

위 커맨드에서 조회한 노드의 퍼블릭 IP주소로 curl을 실행한다.

$ curl 13.125.164.232:31085
Hello, This is DaEun Kim. 

참고

Last updated