본문 바로가기

프로그래밍/컴퓨터 서적 리뷰 & After Service

가상머신 Kubernetes 환경에서 NodePort로 Ingress 구축하기(feat. 시작하세요 도커/쿠버네티스)

요즘 쿠버네티스(이하 k8s 라고 하겠다. 실제 이 용어로도 통용되고 있으니까 혼선은 없겠지만..) 관련해서 시작하세요 도커/쿠버네티스 란 책을 보고 있는데, 개인적으로는 정말 잘 맞는 책이라고 생각한다. 이전에 docker를 이용한 jenkins 분산 빌드 환경에 대한 글을 쓸때는 도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문 이란 책을 봤는데, 그때는 책이 나랑 잘 맞지 않았었다. 번역서라서 그런지 의미전달이 잘 안되는 부분도 있었는데 이번에 이 책을 보면서 k8s에 대한 개인 실습이 아주 막힘없이 잘 진행되고 있어서 정말 잘 맞는다는 생각이 든다.

암튼 이 책을 보며 공부하던중 Ingress 부분이 나오는데 Ingress 실습에 대해서는 이 책에서는 AWS, GKE 환경에서 Load Balancer 타입으로 Ingress 에 대한 실습을 진행했다. 그러나 나는 AWS, GKE 환경을 이용할수는 없어서 Virtual Box와 Vagrant를 활용한 가상머신 3대를 만들어 여기에 k8s 환경을 구축했다. 그러다보니 이 환경에서는 Load Balancer를 구현할수 없어서(물론 아주 구현하지 못하는것은 아니다. MetalLB 를 이용하면 구현은 가능하다) 책에서 안내해주는데로 NodePort를 이용해서 이를 구현하게 되었다. 그러나 이 책의 저자는 누구나가 클라우드 환경을 이용할 수 있다고 생각했는지 Node Port를 이용한 Ingress 에 대한 설명이 없어서 이 부분에 대해 글을 남기고자 한다. 이 글에서 실습으로 언급한 yaml 파일들은 github.com/alicek106/start-docker-kubernetes/tree/master/chapter8 에 공개되어 있으니 yaml 파일 내용을 보고 싶으면 해당 주소에 가서 보면 된다.

 

먼저 나의 k8s cluster 구성은 다음과 같다. 아래의 그림은 k8s master node에 접속한 상태에서 실행한 명령의 결과이다.

(k 라는 명령어는 alias k="kubectl" 을 실행시켜서 만든 kubectl 명령어의 별칭이다)

 

k8s node 구성

 

이름으로 나오는 kubernetes-master, kubernetes-worker1, kubernetes-worker2는 각각 CentOS 7 기반의 Vagrant Box를 이용해서 만든 Vagrant 가상 머신이다. 그리고 해당 가상머신이 가지고 있는 IP가 위의 그림에서 INTERNAL-IP 항목으로 나타내고 있는 IP 주소이다. 책의 경우는 master node 1개에 worker node 3개를 구성했지만 나는 노트북에서 하다보니 worker node를 3개까지 구성하지는 않았다. pod이 배포되는 상황만 보면 되는지라 worker node를 2개로 해서 cluster를 구성했다.

 

Ingress 자체를 정의하는 것에 대해서는 별도 언급은 하지 않겠다. 이 부분은 책에 있는 것을 그대로 썼기 때문에 별 문제는 없다(책에 나온대로 ingress-example.yaml 파일을 설치하면 된다). 그러나 책에서 Ingress 정의 이후에 설명하고 있는 Ingress Controller 설치 부분과 관련해서는 책과는 다른 점이 있어서 이 부분부터 설명하도록 하겠다. Nginx Ingress Controller를 설치하기 위해 책에서는 다음의 명령어를 실행하라고 나오고 있다.

 

kubectl apply -f \
https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml

 

근데 위에서 언급되어 있는 URL을 웹브라우저로 접근해보면 404 Not Found가 나온다. 이 책도 그 점을 인식했는지 각주 형태로 해당 페이지의 밑에 언급을 한 내용이 있다. 공식 문서에 대한 주소를 언급했는데 이 공식 문서를 보면 설치하는 방법이 시스템 상황별로 설명되어 있다. 각자 자신이 사용하고 있는 k8s 상황에 맞춰서 이를 설치하면 되는데 나의 경우는 Vagrant 가상 머신에서 이를 설치하는 것이기 때문에 Bare-metal 을 클릭해서 거기에서 안내해주는 내용대로 설치하면 된다. Bare-metal을 보면 다음과 같이 설치하라고 안내를 해주고 있다.(문서에서는 linux 명령어 실행시 개행해서 실행시키는 문자인 \가 들어가 있지 않지만 여기서는 보기 편하도록 하기 위해 \을 넣어서 실행하게끔 했다)

 

kubectl apply -f \
https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-0.32.0/deploy/static/provider/baremetal/deploy.yaml

 

이렇게 실행하면 k8s에 ingress-nginx 라는 namespace 가 별도로 생기면서 여기에 각종 자원들이 생성되는데 그 중 이 글과 관련된 부분에 대한 내용을 보여준다면 다음과 같이 나타난다(pod 이름의 경우는 이 글과는 다르게 나올수도 있다. 대강 이런 종류의 pod이 생긴다고 보고 넘어가면 된다)

 

ingress-nginx namespace에 있는 pod과 service

 

위의 그림에서 눈여겨 봐야 할 것은 service 항목에 있는 NodePort 이다. 이 NodePort를 보면 32064번 포트가 80번 포트와 연결이 되고, 31368번 포트가 443번 포트와 연결되는 것을 볼 수 있다. 이거를 알고 이제 실습을 하면 된다. 책에서는 LoadBalancer 타입으로 설명하고 있기 때문에 이 부분을 제껴두고 hostname-deployment.yaml 파일과 hostname-service.yaml 파일을 이용해서 default namespace에 deployment와 service를 설치한다. 이 두 파일을 설치한뒤 default namespace의 ingress, service, deployment, pod를 조회하면 다음과 같이 나타난다

 

default namespace의 ingress, service, deployment, pod

 

이제 정리되어야 할 것은 이렇게 설치된 자원들이 어떻게 연결되는지를 보면 된다. 먼저 hostname-deployment.yaml 파일을 통해 설치된 deployment와 pod을 살펴보자. hostname-deployment.yaml 파일을 통해 my-webserver란 이름의 container를 alicek106/ingress-annotation-test:0.0 docker 이미지를 이용해서 만들게 된다. 이 과정에서 flask-port 라는 이름으로 container의 5000번 포트가 열리게 된다. 이러한 container가 1개 들어있는 pod을 3개를 만들게 된다. 여기까지가 hostname-deployment.yaml 파일이 하는 일이다.

 

그러면 hostname-service.yaml 파일을 통해 service가 설치되는 내용을 보자. hostname-service 란 이름으로 service가 만들어지게 되는데 이 service는 ClusterIP 타입이기 때문에 k8s cluster 내부에서는 통신이 이루어질수 있겠지만 cluster 바깥에서는 cluster 내부로 통신할 수가 없다. 이 service는 web-port 라는 이름으로 80 포트를 사용하는데 이 포트가 연결하는 대상은 flask-port, 즉 우리가 hostname-deployment.yaml 파일에서 container를 만들때 5000번 포트 이름으로 사용된 이 flask-port 와 연결이 되는 것이다. 정리하자면 service의 80 포트와 container의 5000번 포트가 연결되는 것이다. 이렇게가 가능한 이유는 hostname-service.yaml 파일에서 selector로 app: webserver를 사용하고 있는데 위에서 deployment를 만들때 사용한 hostname-deplotment.yaml 파일에서 template의 label을 app: webserver를 사용했기 때문에 이것과 match 하여 flask-port 정보를 찾아 연결하게 되는 것이다. 이렇게 해서 service와 pod이 연결되는 것이다. 그러나 이것만으로는 외부에서 pod으로 접속할 수가 없다. 왜냐면 방금 언급했다시피 현재 만들어진 service는 ClusterIP이기 때문에 k8s cluster의 내부 통신만 가능하기 때문이다. service와 pod의 관계만 놓고 보면 현재로써는 외부에서 들어오는 통신은 할 수 없지만 이 부분은 나중에 Ingress Controller와 Ingress를 통해 해결이 되니 오해가 없길 바란다.

 

이제는 특정 domain과 path를 통해 접속을 요청하는 것을 어떻게 service와 연결하는지를 알아보자. ingress-example.yaml 파일을 보면 alicek106.example.com 도메인을 사용하면서 path을 /echo-hostname을 사용하는 요청에 대해 service 이름이 hostname-service에 80포트를 사용해서 연결하고 있다. hostname-service는 위에서 설명했다시피 80포트를 통해서 k8s cluster에 만들어진 3개의 pod에 열려있는 5000번 포트와 연결되어 있는 상태이다. 즉 url을 alicek106.example.com/echo-hostname으로 요청을 주게 되면 container의 5000번 포트와 연결이 되어 그 결과를 알려주게 되는 것이다. 이렇게 생성된 ingress는 어떻게 이용이 되는지 살펴보자.

 

글의 시작 무렵에 Nginx Ingress Controller 설치를 하면서 생성된 Node Port에 대해 언급한 적이 있다. Bare-Metal 에서는 LoadBalancer가 없기 때문에 이 역할을 대신 해주는 것이 Node Port가 된다. 이 Node Port가 특정 도메인과 경로, 그리고 port에 대한 요청을 받으면 이 Node Port와 연결되어 있는 Ingress Controller가 이것을 처리할 수 있는 ingress가 있는지를 찾게 되고 이러한 ingress를 찾으면 이 ingress에 설정된 service의 정보를 통해 해당 pod으로 요청을 bypass 하게 되는 것이다(spring framework에 대해 잘 아는 자바 프로그래머라면 외부에서 오는 요청을 dispatcher servelet이 받아 이를 처리할 controller를 찾아서 처리하게끔 하는데 이 Dispatcher Servlet이 Ingress Controller, Controller가 Ingress라고 이해하면 되겠다). 큰 흐름은 대강 이정도로 얘기하고 조금 디테일하게 들어가보자. 위에서 ingress-example.yaml 이 생성한 ingress의 내용을 개념적으로 설명하면 alicek106.example.com/echo-hostname 으로 요청이 들어오면 이를 hostname-service 서비스가 80 port로 받아서 이를 처리하는 것이다. 이러한 ingress 를 Ingress Controller가 처리하게 되는 것인데 Ingress Controller 앞에는 LoadBalancer 역할을 대신 해주는 Node Port가 있는 상황이다. 그래서 전체적인 흐름을 그려보자면 다음과 같은 상황이 된다

 

NodePort -> Ingress Controller -> Ingress -> Service -> Pod

 

하지만 NodePort로 인해 요청의 방법이 방금 언급했던 것과는 약간 바뀌에 된다. 위에서 Nginx Ingress Controller를 생성하면서 만들어진 NodePort에 대한 정보를 다시 언급하자면 32064 port를 사용하는 요청을 80 port로 변환해서 처리하는 것을 알 수 있다. 외부에서 k8s에 접근할때 가장 먼저 만나는 것이 NodePort이기 때문에 Ingress Controller에서 80 port로 받아 처리하게끔 할려면 요청을 32064 port를 사용하는 형태로 요청을 해야 NodePort 에서 이를 80 port로 바꾸어 요청할수 있게 되는 것이다. 그래서 위에서 사용했던 요청인 alicek106.example.com/echo-hostname은 alicek106.example.com:32064/echo-hostname 이런 식으로 32064번 port를 사용하는 형태로 요청해야 하는 것이다. 

이렇게 32064번 port로 요청하면 NodePort를 통해 80 Port로의 요청으로 바뀌게 되고 이것이 Ingress Controller에서 해당 도메인의 80 Port 요청을 처리하는 Ingress를 찾아서 이 Ingress에 맞는 Service가 해당 Pod으로 요청을 처리하게 되는 것이다. 이제 이러한 요청의 실제적인 실습을 통해 어떻게 출력되는지를 보도록 하자.

 

책에서는 curl 명령을 이용해서 alicek106.example.com을 접속하고 있는데 책의 예제는 LoadBalancer 타입일 경우이지 NodePort 타입일 경우는 아니다. 방금 언급했다시피 NodePort 에서는 외부에서 접속시 사용해야 할 포트가 별도로 명시되어 있기 때문에 다음과 같이 호출해야 한다.

 

curl을 이용해서 NodePort 타입의 Ingress Controller를 통한 접속

책에서는 LoadBalancer 타입이기 때문에 80 포트로 접속이 가능할수 있었겠지만 여기서는 NodePort 타입이기 때문에 NodePort에서 80 포트와 통신하기로 정해져 있는 32064 포트를 이용했다. 그리고 pod 이 설치되어 있는 worker node의 ip인 192.168.101.12를 alicek106.example.com 도메인과 연결되도록 설정해주어서 호출했다. 위의 그림은 master node에서 실행한 화면이지만, 가상머신 바깥인 Host에서 이를 테스트 할때는 C:\Windows\System32\drivers\etc 디렉토리에 있는 hosts 파일(리눅스의 경우는 /etc 디렉토리에 있는 hosts 파일)에 alicek106.example.com을 worker node ip와 연결해주면 테스트가 가능하다. 아래의 그림은 브라우저로 이를 접근했을때의 결과이다.

 

브라우저로 실행한 결과화면

 

지금까지의 설명이 장황할수는 있겠지만 큰 그림에서 보면 위에서 언급했듯이 NodePort -> Ingress Controller -> Ingress -> Service -> Pod 으로의 연결 관계가 형성된다는 전제하에서 글을 보면 이해하는데 도움이 되리라 생각된다. 그리고 LoadBalancer 대신 NodePort를 이용하여 Ingress를 구현해보긴 했지만 실제로 사용하기에는 부적합하다는 느낌을 받았다. 가장 결정적인 것은 우리에게 잘 알려져 있는 Port인 80 Port나 443 Port를 외부에서 직접적으로 이용할 수 없다는 것이다.인증서 관련 작업에 대한 설정시에도 문제의 소지가 있고 NodePort를 재생성하게 되면 다시 Port 번호가 바뀌기 때문에(물론 안바뀌도록 특정 Port를 강제로 할당받게 할 수도 있다) Port 번호가 유동성을 지니게 되어 이용에 불편함이 발생할 소지가 다분하기 때문이다. 그래서 이러한 NodePort 형태는 Ingress를 공부하기 위한 용도로는 문제는 없겠으나 실제 운영상에서 사용하기엔 분명 한계가 존재한다. Bare Metal 형태의 경우 이 책에서 소개한 MetalLB 라는 Load Balancer Solution을 사용하도록 권장하고 있지만 문제는 이 solution이 현재 beta 버전인데다가 kubernetes 측에서 직접적으로 관리하고 있지 않기 때문에 버전 업그레이드 과정에서 엇박자의 소지가 될 가능성이 있다. 결국 이거저거 다 따져보면 k8s는 AWS나 GKE 같은 Cloud 환경에서 운영하는 것이 가장 이상적인듯 하다. 그러나 공부하는 시점부터 Cloud 환경을 이용할수는 없기 때문에 일단은 Bare Metal 형태에서 k8s 를 구축해서 공부하는 것이 바람직하다. 언제가 될지는 모르겠지만 MetalLB를 이용한 Ingress Controller 구축도 한번 해봐야겠다. 이 글을 끝맺는 말로 이 책의 저자에게 이런 책을 만들어주셔서 진심으로 고맙다는 말씀을 드리고 싶다.