인그레스(Ingress)
인그레스(Ingress)는 일반적으로 외부에서 내부로 향하는 것을 지칭하는 단어이다. 예를 들어 인그레스 트래픽은 외부에서 서버로 유입되는 트래픽을 의미하며, 인그레스 네트워크는 인그레스 트래픽을 처리하기 위한 네트워크를 의미한다.
서비스 오브젝트가 외부 요청을 받아들이기 위한 것이었다면 ‘인그레스'는 외부 요청을 어떻게 처리할 것인지 네트워크 7계층 레벨에서 정의하는 쿠버네티스 오브젝트이다.
여기서 ‘처리한다'라는 문장에는 많은 기능이 내포돼 있는데 인그레스 오브젝트가 담당할 수 있는 기본적인 기능만 간단히 나열해보면 다음과 같다.
- 외부 요청의 라우팅: /apple, /apple/red 등과 같이 특정 경로로 들어온 요청을 어떠한 서비스로 전달할지 정의하는 라우팅 규칙을 설정할 수 있다.
- 가상 호스트 기반의 요청 처리: 같은 IP에 대해 다른 도메인 이름으로 요청이 도착했을 때, 어떻게 처리할 것인지 정의할 수 있다.
- SSL/TLS 보안 연결 처리: 여러 개의 서비스로 요청을 라우팅할 때, 보안 연결을 위한 인증서를 쉽게 적용할 수 있다.
인그레스의 기능은 위 기능에만 제한되는 것이 아니며, 인그레스를 어떻게 사용하냐(ALB 를 인그레스로 사용하는 등..)에 따라 다양한 기능을 사용할 수 있다.
인그레스 자체의 기능은 비교적 정해져 있어도 인그레스의 요청을 처리할 서버로 무엇을 선택하느냐에 따라 기능이 조금씩 달라지기 때문이다.
인그레스를 사용하는 이유
서비스에서 사용할 수 있는 NodePort나 LoadBalancer 타입의 서비스를 사용해도 위 기능들을 구현하는 것이 불가능하지는 않기 때문에 인그레스를 왜 사용해야 하는지 알아야 할 필요가 있다.
내 애플리케이션이 3개의 디플로이먼트로 생성돼 있다고 가정해 볼 때 각각의 디플로이먼트를 외부에 노출해야 한다면 NodePort 또는 LoadBalancer 타입의 서비스 3개를 생성하는 방법을 먼저 떠올릴 것이다. 각 디플로이먼트에 대응하는 서비스를 하나씩 연결해 준 셈이다.
이런 방식은 잘 작동하는 것 같지만, 서비스마다 세부적인 설정을 할 때 추가적인 복잡성이 발생하게 된다. SSL/TLS 보안 연결, 접근 도메인 및 클라이언트 상태에 기반한 라우팅 등을 구현하려면 각 서비스와 디플로이먼트에 대해 일일이 설정을 해야 하기 때문이다.
이런 경우 쿠버네티스가 제공하는 인그레스 오브젝트를 사용하면 URL 엔드포인트를 단 하나만 생성함으로써 이러한 번거로움을 쉽게 해결할 수 있다.
3개의 디플로이먼트를 외부로 노출하는 인그레스를 생성하면 3개의 서비스에 대해 3개의 URL이 각각 존재하는 것이 아닌 인그레스에 접근하기 위한 하나의 URL만 존재한다. 따라서 클라이언트는 인그레스의 URL로만 접근하게 되며, 해당 요청은 인그레스에서 정의한 규칙에 따라 처리된 뒤 적절한 디플로이먼트의 파드로 전달된다.
쉽게 AWS ELB로 풀어 설명하자면 클라이언트는 하나의 ALB에 접근을 하게 되고 ALB가 3개의 NLB로 부하 분산을 시켜 주는 것이다. 여기서 ALB는 인그레스가 NLB는 서비스 오브젝트가 된다.
인그레스 구조
[1] host: 해당 도메인 이름으로 접근하는 요청에 대해서 처리 규칙을 적용한다. 위 예시에서는 theotters.net 이라는 도메인으로 접근하는 요청만 처리하지만 여러 개의 host를 정의해 사용할 수도 있다.
[2] path: 해당 경로에 들어온 요청을 어느 서비스로 전달할 것인지 정의한다. 위 예시에서는 /login 이라는 경로의 요청을 backend에 정의된 서비스로 전달한다. 여러 개의 path를 정의해 경로를 처리할 수도 있다.
[3] serviceName, servicePort: path로 들어온 요청이 전달될 서비스와 포트이다.
실제 이 파일을 만들고 kubectl apply -f ingress-example.yaml 이라는 이름의 인그레스를 생성해도 이것만으로는 아무 일도 일어나지 않는다.
인그레스는 단지 요청을 처리하는 규칙을 정의하는 선언적인 오브젝트일 뿐, 외부 요청을 받아들일 수 있는 실제 서버가 아니기 때문이다. 인그레스는 인그레스 컨트롤러(Ingress Controller)라고 하는 특수한 서버에 적용해야만 그 규칙을 사용할 수 있다.
즉, 실제로 외부의 요청을 받아들이는 것은 인그레스 컨트롤러 서버이며, 이 서버가 인그레스 규칙을 로드해 사용한다.
인그레스 컨트롤러 ——————> 인그레스 규칙 적용 ——————> 서비스
따라서 쿠버네티스의 인그레스는 반드시 인그레스 컨트롤러라는 서버와 함께 사용해야 한다. 인그레스 컨트롤러 서버는 여러 종류가 있으며 필요에 따라 하나를 골라서 사용하면 되고 대표적으로 쿠버네티스에서 가장 많이 사용하는 Nginx 웹 서버 인그레스 컨트롤러가 있다.
Nginx 인그레스 컨트롤러는 쿠버네티스에서 공식적으로 개발되고 있기 때문에 설치를 위한 YAML 파일을 공식 깃허브 저장소에서 내려받을 수 있다. 그래서 명령어를 통해 Nginx 인그레스 컨트롤러와 관련된 모든 리소스를 한 번에 설치할 수 있다.(https://kubernetes.github.io/ingress-nginx/deploy/)
kubectl apply -f <https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.3.0/deploy/static/provider/cloud/deploy.yaml>
Nginx 인그레스 컨트롤러를 설치하면 자동으로 생성되는 서비스는 LoadBalancer 타입이다. 실제 운영 환경일 경우 LoadBalancer 타입에 DNS 이름을 할당함으로써 Nginx 인그레스 컨트롤러에 접근하는 것이 일반적이다.
가상 머신처럼 클라우드가 아닌 환경에서 인그레스를 테스트
가상 머신처럼 클라우드가 아닌 환경에서 인그레스를 테스트하고 싶다면 LoadBalancer 대신 NodePort 타입의 서비스를 생성해 사용해도 된다. 이 경우에는 각 노드의 랜덤한 포트로 Nginx 인그레스 컨트롤러에 접근할 수 있다.
혹은 온프레미스에서의 운영 단계를 계획하고 있다면 MetalLB나 오픈스택의 로드 밸런서를 사용할 수도 있다.
💡 인그레스 컨트롤러에 의해 요청이 최종적으로 도착할 디플로이먼트의 서비스는 어떤 타입이든지 상관은 없다. 다만 외부에 서비스를 노출할 필요가 없다면 ClusterIP 타입을 사용하는 것이 좋다.
💡 인그레스의 기능을 하나씩 사용해보기 위해 YAML 파일에 host, path 항목 등을 모두 명시했지만, 이는 반드시 정의할 필요는 없으며 인그레스 항목을 최소한으로 하여 설정으로 생성이 가능하다.
nginx 인그레스를 사용하는 방법을 순서대로 정리해보면
- 공식 깃허브에서 제공되는 YAML 파일로 Nginx 인그레스 컨트롤러를 생성한다.
- Nginx 인그레스 컨트롤러를 외부로 노출하기 위한 서비스를 생성한다.
- 요청 처리 규칙을 정의하는 인그레스 오브젝트를 생성한다.
- Nginx 인그레스 컨트롤러로 들어온 요청은 인그레스 규칙에 따라 적절한 서비스로 전달된다.
위 과정 중 3번에서 인그레스를 생성하면 인그레스 컨트롤러는 자동으로 인그레스를 로드해 Nginx 웹 서버에 적용한다. 이를 위해 Nginx 인그레스 컨트롤러는 항상 인그레스 리소스의 상태를 지켜보고 있으며, 기본적으로 모든 네임스페이스의 인그레스 리소스를 읽어와 규칙을 적용한다.
특정 경로와 호스트 이름으로 들어온 요청은 인그레스에 정의된 규칙에 따라 서비스로 전달된다.
요청이 실제로 서비스로 전달되는 것은 아니며, Nginx 인그레스 컨트롤러는 서비스에 의해 생성된 엔드포인트로 요청을 직접 전달한다. 즉 서비스의 ClusterIP 가 아닌 엔드포인트의 실제 종착 지점들로 요청이 전달되는 셈이다.
어노테이션을 이용한 설정
kubernetes.io/ingress.class 는 해당 인그레스 규칙을 어떤 인그레스 컨트롤러에 적용할 것인지를 의미한다. 인그레스 컨트롤러로는 Nginx 말고도 Kong이나 GKE 등 여러 가지 중 하나를 선택해 사용할 수 있다.
쿠버네티스 클러스터 자체에서 기본적으로 사용하도록 설정된 인그레스 컨트롤러가 존재하는 경우가 있는데, 이 경우에는 어떤 인그레스 컨트롤러를 사용할 것인지 반드시 인그레스에 명시해주는 것이 좋다.
예를 들어 GKE에서 쿠버네티스를 사용하고 있다면 GKE에서 제공하는 인그레스 컨트롤러를 기본적으로 사용하도록 설정돼 있다. 따라서 GKE에서 인그레스를 생성할 때 만약 annotation 항목에서 kubernetes.io/ingress.classs 를 사용하지 않으면 GKE의 인그레스 컨트롤러를 자동으로 생성해 사용하게 된다.
따라서 GKE 나 다른 클라우드 서비스에서 Nginx 인그레스 컨트롤러를 사용하고 싶다면 반드시 kubernetes.io/ingress.class 를 nginx로 설정해야 한다.
nginx.ingress.kubernetes.io/rewrite-target 이라는 주석은 Nginx 인그레스 컨트롤러에서만 사용할 수 있는 기능이다. 이 주석은 인그레스에 정의된 경로로 들어오는 모든 요청을 rewrite-target에 설정된 경로로 전달한다.
예를 들어, Nginx 인그레스 컨트롤러로 /login 으로 접근하면 서비스에는 / 경로로 전달된다. /login 으로 시작하는 모든 요청을 서비스의 / 로 전달한다. 옳은 URL 은 아니지만 /login/success/good 라는 경로로 요청을 보내도 똑같이 / 로 전달된다.
이 외에도 루트 경로로 접근했을 때 특정 path 로 리다이렉트하는 app-root 주석이나 SSL 리다이렉트를 위한 ssl-redirect 주석 등을 사용할 수 있다. Nginx 인그레스 컨트롤러에서 사용할 수 있는 주석은 공식 사이트에서 확인 가능하다.
단, 이런 주석들은 Nginx 인그레스 컨트롤러에서만 사용할 수 있으며, 다른 인그레스 컨트롤러 서버를 사용한다면 해당 인그레스 컨트롤러의 공식 문서를 참고해 적절한 방법으로 기능을 사용해야 한다.
Nginx 인그레스 컨트롤러에 SSL/TLS 보안 연결 적용
인그레스의 장점 중 하나는 쿠버네티스의 뒤쪽에 있는 디플로이먼트와 서비스가 아닌 앞쪽에 있는 인그레스 컨트롤러에서 편리하게 SSL/TLS 보안 연결을 설정할 수 있다는 것이다.
즉, 인그레스 컨트롤러 지점에서 인증서를 적용해 두면 요청이 전달되는 애플리케이션에 대해 모두 인증서 처리를 할 수 있다. 따라서 인그레스 컨트롤러가 보안 연결을 수립하기 위한 일종의 관문(Gateway) 역할을 한다고도 볼 수 있다.
AWS와 같은 클라우드 환경에서 LoadBalancer 타입의 서비스를 사용할 계획이라면 클라우드 플랫폼 자체에서 관리해주는 인증서를 인그레스 컨트롤러에 적용할 수도 있다. 예를 들어 AWS의 ACM을 LoadBalancer 타입의 서비스에 제공하는 방식.
Nginx 인그레스 컨트롤러 또한 인증서를 통한 보안 연결 기능을 제공하기 때문에 어렵지 않게 보안 연결을 설정할 수 있다. 가장 먼저 보안 연결에 사용할 인증서와 비밀키를 생성하자.
openssl req -x509 -nodes -days 365 -newkey rsa:2048
-keyout tls.key -out tls.crt -subj "/CN=dev.theotters.net/0=dev"
/CN 에는 Nginx 인그레스 컨트롤러에 접근하기 위한 Public DNS 이름을 입력해야 한다. 위 예시에서 Nginx 인그레스 컨트롤러와 연결된 서비스에 dev.theotters.net 이라는 도메인으로 접근한다고 가정한 것이며, 인증서 생성 옵션은 환경에 맞게 적절히 변경해 사용해야 한다.
예를 들어, AWS에서 생성되어 동적인 도메인 이름을 할당받은 클래식 로드 밸런서라면 /CN=*.ap-northeast-2.elb.amazonaws.com 처럼 사용하는 것도 가능하다.
$ ls
tls.crt tls.key ...
$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
tls 항목이 추가되었고 spec.tls.hosts 항목에서는 보안 연결을 적용할 도메인 이름을, spec.tls.secretName 은 앞서 생성했던 tls 타입의 시크릿 이름을 입력했다. 이는 dev.theotters.net 이라는 도메인 이름으로 접근하는 요청에 대해 tls-secret 시크릿의 인증서로 보안 연결을 수립하겠다는 뜻이다.