
예전에 동료와 함께 구축했던 온프레미즈 쿠버네티스 클러스터 기반에서 운영되고 있는 서비스가 있었는데, 클러스터의 특정 피쳐들을 안쓰게 되면서 자연스럽게 방치되다가, 최근에 온프레미즈에서 다른 온프레미즈로 마이그레이션을 하게 되는 일을 돕게 되면서 쿠버네티스 세팅을 다시 하게 됐다.
그 과정에서 원래 사용하고 있던 nginx-ingress 대신에 istio gateway를 사용해서 구축하기로 했다. 이유는 ingress-NGINX가 2026년 3월까지 best-effort 유지보수 후 인퇴(리타이어) 예정이라는 점이 컸고, 이후에는 릴리즈/버그픽스/보안패치가 더 이상 제공되지 않기 때문이다.
또한 Istio가 Kubernetes Gateway API를 지원하고, 장기적으로 트래픽 매니지먼트의 기본 API로 가져가려는 방향성이 명확해지면서, 지금 시점에 Istio 기반으로 옮기는 편이 레퍼런스도 풍부하고 운영/확장 측면에서도 선택지가 넓다고 판단했다.
이번 포스팅에서는 istio gateway에 대한 내용을 다룬다. 실제로 온프레미즈에서 metallb와 함께 사용하는 과정을 다루었으며, 따라서 로컬에서 minikube 같은 셀프 호스팅 쿠버네티스 클러스터를 통해서 손쉽게 테스트할 수 있도록 포스팅을 작성하였다. istio gateway에 대한 공식 문서 레퍼런스는 다음과 같다.
- [istio] gateway : https://istio.io/latest/docs/reference/config/networking/gateway/
Gateway
Configuration affecting edge load balancer.
istio.io
또한, 이번 포스팅에서는 MetalLB를 통한 내용이 들어있다. 공식 문서에 따르면 Istio Gateway에 대해서 다음과 같이 쓰여있다.
For example, the following Gateway configuration sets up a proxy to act as a load balancer exposing port 80 and 9080 (http), 443 (https), 9443(https) and port 2379 (TCP) for ingress. The gateway will be applied to the proxy running on a pod with labels app: my-gateway-controller. While Istio will configure the proxy to listen on these ports, it is the responsibility of the user to ensure that external traffic to these ports are allowed into the mesh.
Gateway 리소스는 (Envoy) 프록시가 어떤 포트로 들어오는 트래픽을 받을지를 설정해주긴 하지만, 그 포트로 들어오는 외부 트래픽이 실제로 그 프록시 Pod까지 도달하게 만드는 일은 사용자가 책임져야 한다는 뜻이야. 즉, Gateway = L7 프록시(Envoy) 설정이고, 외부 노출을 별개라는 이야기다. 이번 포스팅에서는 이러한 빈 부분을 MetalLB를 통해서 해결한다.
본론
온프레미즈에서의 기본 세팅
클라우드(KS/GKE/EKS 등)에서는 Service type=LoadBalancer를 만들면 클라우드 로드밸런서가 알아서 붙고 External IP가 나오는데, 온프레미즈는 기본적으로 그 역할이 비어있다. 그래서 MetalLB같은 베어메탈/온프레미즈에서 External IP를 할당해주는 툴이나 외부 L4/L7 장비를 사용한다.
이번 포스팅에서는 MetalLB의 L2 모드를 기준으로 진행한다. MetalLB의 L2 모드는 ARP/NDP로 "이 External IP는 내가 받아줄게"를 네트워크에 알리는 방식이라, 로컬/온프레미즈 랩에서 빠르게 검증하기 좋다. 이에 대한 자세한 내용은 이전에 포스팅한 내용의 링크를 첨부한다.
- [marsboy] 온프레미즈 쿠버네티스에 쓰이는 MetalLB : https://marsboy.tistory.com/99
온프레미스 쿠버네티스에서 쓰이는 MetalLB
쿠버네티스에 대해서 공부하다보면 매니페스트 중에 네트워크 연결을 도와주는 Service라는 종류가 있다. Service 매니페스트는 크게 3가지로, 내부에서 IP를 통해서 연결할 수 있게 해주는 ClusterIP
marsboy.tistory.com
Istio Gateway에서 Ingress를 구성하는 방법
Istio의 Gateway 리소스는 "메시 가장자리(edge)에서" 들어오는 HTTP/TCP 트래픽을 받는 로드밸런서/프록시(Envoy) 설정이다. 어떤 포트를 열지, 어떤 프로토콜을 쓸지, 어떻게 TLS/SNI를 설정할 지 등을 선언할 수 있다. 보통은 아래와 같은 조합으로 ingress를 구성한다.
- Gateway : 어떤 포트/프로토콜로 트래픽을 받을지
- VirtualService : 받은 트래픽을 어떤 서비스로 라우팅할지(호스트/경로/해더 등)
여기서 전통적인 쿠버네티스 아키텍처에 대한 이해가 있는 사람이라면, 호스트나 경로 기반으로 라우팅하는 것은 기존의 쿠버네티스의 매니페스트를 통해서 구현할 수 있다는 것을 알 것이다. Service + Ingress + (Ingress Controller: ingress-nginx) 조합을 통해서 구현할 수 있다. 이러한 구조 대신에 VirtualService + Istio Gateway + Istio를 통해서 인그레스라고 부르는 것을 구축할 수 있다. 따라서 순서에 따라서 각 매니페스트들은 비슷한 역할을 나타낸다. (Service ~= VirtualService)
Ingress를 어떤 상황에서 쓸 수 있을까?
실제로 istio gateway가 필요한 상황에 대해서 그림을 예시로 들어 설명한다. 실제 온프레미즈에서 쿠버네티스 클러스터 세팅을 진행하면서 있었던 상황이고, 다음과 같다.

이러한 상황에서 직접 쿠버네티스 클러스터를 구축하고, 같은 레이어에 같은 노드를 하나 띄운다고 생각하면 다음과 같이 나타낼 수 있다. 나는 예를 들어 tailscale이라는 VPN 서버를 띄워두었다.

위와 같은 상황에서, 공유기 내에 있는 노드들은 192.168.0.0/16 대역을 받게 된다. 이 상황에서 Ingress + service 조합을 써야하는 상황이 온다. 쿠버네티스의 노드 안에서 실행되는 파드를 외부에서 접근 가능하게 해야하는 상황이다. 이 때, 쿠버네티스의 Service 매니페스트를 통해서 노출시키는 방법은 loadBalancer와 NodePort 두 가지가 있다.
이 때, 만약에 nodePort를 통해서 특정 파드를 외부로 노출시키게 되면, 포트를 통해서 접근해야하기 때문에 접근 관리가 번거로워지게 된다. 또한, loadBalancer 타입을 통해서 외부로 노출시키면, 포트보다는 접근이 편해지지만, 여전히 외부에서 IP를 통해서 트래픽을 프록시해줘야 한다.

nginx를 사용하는 예시를 들어 보면 위와 같다. worker nodes에 떠있는 kiali나 grafana 대시보드가 192.168.0.200-201를 할당받고 외부에서 접근이 가능해졌다고 하더라도, L7 레이어에 해당하는 서브 도메인을 통한 트래픽을 나누어주기 위해서는 결국 앞단에 nginx 같은 프록시를 붙여야 한다.

이제 istio gateway를 사용하는 경우에는 위와 같이 설정할 수 있다. 모든 80/443 요청을 쿠버네티스 클러스터 안에 있는 istio gateway에 요청을 보내고, 클러스터 내부적으로 서브도메인을 각각의 파드로 알맞게 보낼 수 있다. 이렇게 하는 것만으로 복잡한 프록시 설정을 앞단에 둘 필요가 없으며, 서비스 진입점에 해당하는 서비스 메시 또한 수집하고 확인할 수 있게 된다.
이에 관련해서 istio gateway 관련된 문서가 있다. 앞서 첨부한 공식 문서는 이론에 해당하는 문서이고, 다음의 문서는 외부에서 클러스터/메시 안으로 들어오는 트래픽(Ingress)를 어떻게 노출할지에 대한 내용이다.
- [istio] istio Ingress control : https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/
Ingress Gateways
Describes how to configure an Istio gateway to expose a service outside of the service mesh.
istio.io
이제 간단하게 istio gateway가 무엇인지, 그리고 어떤 상황에서 쓰는 지에 대한 간략한 소개를 했으니 본격적으로 istio gateway를 세팅해보자.
Istio Gateway에 대해서
Istio Gateway는 "클러스터 엣지에 배치된 Envoy 프록시(Ingressgateway)가 어떤 포트/프로토콜/TLS로 요청을 받을지"를 선언하는 설정이고, 그 뒤에 VirtualService가 그 요청을 어디로 라우팅할지를 선언한다. 예시 스크립트를 놓고 보면 다음과 같다.
istio라는 서비스는 Envoy라는 서비스와 함께 사용하기 때문에, istio-ingressgateway가 이미 설치되어있고, 라벨 istio: ingressgateway를 가진 워크로드가 존재한다고 가정하면 다음과 같이 쓸 수 있다.
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: demo-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway # 어떤 gateway(Envoy) 워크로드에 적용할지
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "app.demo.local" # Host 헤더 기준으로 매칭
Envoy가 80 포트로 HTTP 요청을 받을 Listener를 만들게 되고, 여기까지는 "받기"만 열어준 상태고, "어디로 보낼지"는 VirtualService가 결정하게 된다. VirtualService는 아래와 같이 정의할 수 있다.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: demo-vs
namespace: demo
spec:
hosts:
- "app.demo.local"
gateways:
- istio-system/demo-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: demo-app.demo.svc.cluster.local
port:
number: 8080
이렇게 설정하게 되면, app.demo.local로 들어온 요청을 demo-app 서비스의 8080으로 라우팅하게 된다. 좀 더 실무적인 내용으로 80번(HTTP) 요청으로 들어온 경우 443번(HTTPS)로 말아서 리다이렉트하는 경우에는 다음과 같이 작성할 수 있다.
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: demo-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
# 1) 80으로 들어오면 HTTPS로 리다이렉트
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "app.demo.local"
tls:
httpsRedirect: true
# 2) 443에서 TLS 종료(termination) 후 HTTP 라우팅
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- "app.demo.local"
tls:
mode: SIMPLE
credentialName: app-demo-tls
# credentialName은 "istio-system" 네임스페이스에 있는 TLS Secret을 참조한다고 생각하면 편함
스크립트는 대략 위와 같이 이루어진다. 상술했던 대로, 기존의 Ingress + Service를 쓰는 구조와 크게 비슷하지는 않다. 대략적으로 istio를 쓰는 구조와 쓰지 않는 구조 모두 비슷한 내용을 나타내기 때문이다.
이에 관해서는 실습을 통해서 살펴보자. 직접 istio gateway를 띄운 다음에, curl 기반으로 제대로 라우팅을 해주는 지 확인을 한다.
Istio Gateway 실습
minikube 및 metallb 설정
minikube를 사용하는 환경에서 간단하게 서비스를 만들어본다. 포스팅의 서두에 남겨두었던 것처럼, minikube를 사용하는 경우에는 loadBalancer 타입의 Service 매니페스트를 사용하기 위해서는 metallb라는 써드파티가 필요하다.
다음의 스크립트를 통해서 metallb는 간단하게 설치가 가능하다.
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.3/config/manifests/metallb-native.yaml
이제 본격적으로 metallb 관련 설정 스크립트를 작성한다.
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: minikube-pool
namespace: metallb-system
spec:
addresses:
- 192.168.49.100-192.168.49.120
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: minikube-l2
namespace: metallb-system
spec:
ipAddressPools:
- minikube-pool
위 스크립트를 kubectl apply를 통해서 적용할 수 있다. metallb의 ipaddresspool과 l2advertisement가 정상적으로 created되면 완료된 것이다.
이 때 참고로, IP는 minikube와 같은 IP 대역을 사용해야한다. 따라서, minikube ip 명령어를 통해서 IP를 확인한 다음에, 대역에 맞게 설정해주면 된다. 일반적으로는 192.168.49.2를 사용하기 때문에 같은 대역으로 설정해준다.
istio 설치
다음으로는 istio를 설치한다. 가장 쉬운 방법인 istioctl을 통해서 설치한다.
istioctl install -y
istio를 정상적으로 설치하면, istio의 로고인 배 모양의 로그가 터미널에 뜬다. 이제 새롭게 네임스페이스에 서비스를 배포할 때, istio-injection이라는 설정을 enable로 설정해줘야. 정상적으로 istio를 사용할 수 있다.
이제 istio를 위한 샘플 앱을 배포할 예정이다. 새롭게 네임스페이스를 만든 후에 httpbin과 http라는 샘플 앱을 배포한다. 먼저 네임스페이스를 설정한다.
kubectl create ns demo
kubectl label ns demo istio-injection=enabled
다음으로 샘플 앱을 배포한다.
# httpbin
kubectl -n demo apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/httpbin/httpbin.yaml
# sleep
kubectl -n demo apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/sleep/sleep.yaml
이제 demo 네임스페이스에 있는 pod를 살펴보면 정상적으로 pod가 running이 되어있으면 된다. 참고로 metallb 설정에 따라서 정상적으로 External IP가 할당되어있는 것을 확인해야한다.
➜ sources kubectl -n istio-system get svc istio-ingressgateway -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
istio-ingressgateway LoadBalancer 10.102.207.200 192.168.49.100 15021:30656/TCP,80:31952/TCP,443:31816/TCP 82m app=istio-ingressgateway,istio=ingressgateway
EXTERNAL-IP를 확인하면 metallb에서 설정했던 IP Pool에 따라서 192.168.49.100-120에 해당하는 IP를 할당받은 것을 볼 수 있다. 만약에 minikube 같은 클라우드 벤더가 아닌 상황에서 loadBalancer 타입을 실행하게 되면, EXTERNAL-IP가 <pending> 상태가 된다.
Gateway 적용
이제 기본적으로 istio 세팅과, 샘플 앱에 istio가 적용되도록 label을 설정해두었다. 이제는 본격적으로 istio gateway 설정을 해야한다. 다음과 같이 설정할 수 있다.
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: demo-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "httpbin.demo.local"
이 스크립트 또한 kubectl apply를 통해서 적용할 수 있다. 다음으로는 VirtualService 타입의 매니페스트를 작성해서 적용한다.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: httpbin-vs
namespace: demo
spec:
hosts:
- "httpbin.demo.local"
gateways:
- istio-system/demo-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: httpbin
port:
number: 8000
확인 및 디버깅
minikube 환경에서는 드라이버/네트워크 구조 때문에 Service type=LoadBalancer에 할당된 External IP가 호스트에서 바로 도달되지 않는 경우가 있다. 이 글에서는 로컬에서 누구나 동일하게 재현할 수 있도록 minikube tunnel을 사용해 LoadBalancer 서비스(istio-gateway)에 대한 접근 경로를 열었다.
터널을 실행하면 호스트(로컬)에서 80/443 포트를 통해서 ingressgateway로 트래픽을 보낼 수 있고, 그 위에서 istio gateway/virtualService가 Host 기반 라우팅을 수행하는 지 검증할 수 있다.
minikube tunnel
위 명령어를 입력하면 minikube 환경에서 다음과 같은 터미널 로그를 볼 수 있다.
➜ ~ minikube tunnel
✅ Tunnel successfully started
📌 NOTE: Please do not close this terminal as this process must stay alive for the tunnel to be accessible ...
❗ The service/ingress istio-ingressgateway requires privileged ports to be exposed: [80 443]
🔑 sudo permission will be asked for it.
🏃 Starting tunnel for service istio-ingressgateway.
이제 다음과 같이 curl 명령어를 로컬호스트에 보내면서, Host 헤더를 지정해줄 수 있는데, Host에 따라서 다른 응답을 확인할 수 있다.
# httpbin
curl -H "Host: httpbin.demo.local" http://127.0.0.1/headers
일반적으로 공유기나 네트워크 브릿지 세팅이 없는 경우에는 192.168.49.100 IP에 대한 접근이 어렵기 때문에, minikube tunne을 통해서 접근할 수 있다. 만약에 192.168.49.100 IP를 통해서 curl 명령어를 보내고 싶다면, 쿠버네티스 클러스터 안에 있는 다른 pod에 exec로 붙어서 curl 명령어를 보내는 방법도 있다.
SLEEP_POD=$(kubectl -n demo get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}')
kubectl -n demo exec -it $SLEEP_POD -c sleep -- \
curl -H "Host: httpbin.demo.local" http://192.168.49.100/headers
위 명령어를 통해서 헤더를 확인할 수 있다. 헤더를 통해서 정상적으로 요청 소스가 제대로 나오면 정상적으로 istio에 대한 설정이 완료된 것이다.
'DevOps > kubernetes' 카테고리의 다른 글
| 온프레미스 쿠버네티스에서 쓰이는 MetalLB (1) | 2025.11.29 |
|---|---|
| 학교 공지사항 스크래핑 & 푸시알림 서버 만들기 (0) | 2025.09.18 |
| [CKA] 기출 문제 정리 (0) | 2025.01.12 |
| 쿠버네티스의 Kustomize에 대해서 (1) | 2024.12.20 |
| ETCD Leader Election이란? (1) | 2024.12.18 |