이전 시간에는 ingress gateway를 위주로 알아봤었는데 이번 시간에는 egress gateway를 위주로 알아보자.
ingress gateway가 외부에서 mesh에 대한 인바운드 트래픽을 관리하는 것이라면 mesh에서 바깥으로 나가는 트래픽을 관리하기 위해서 사용하는 것이 egress gateway이다.
사용하는 이유
mesh에서 바깥으로 나가는 트래픽을 관리하기 위해 egressgateway를 사용하는 이유는 매우 다양하다.
1. ACL 관리
특정 기능을 제공하기 위해 외부 서비스의 기능을 사용하는 경우, 외부 서비스에서는 특정 IP만 해당 서버와 요청할 수 있도록 ACL 제한을 두는 경우가 있다. 하지만 모든 쿠버네티스 워커노드에 공인 IP가 있는 것은 아닐 것이다. 워커노드에서 인터넷이 되는 경우라면 워커노드에서 인터넷으로 통신할 때는 NAT(Network Address Translation)를 통해 특정 공인 IP로 IP 변환한 후 인터넷으로 패킷이 전달된다. 그런데 여기서 전달되는 공인 IP가 인프라 설정에 따라 주기적으로 변경이 될 수도 있다.
NAT IP를 확인하는 방법은 다음과 같다.
curl ifconfig.me
만약 외부 서비스에서 IP ACL 검사를 하는 경우라면, NAT IP가 변경되면 더 이상 통신을 할 수 없게 되어 장애가 발생할 것이다. 그렇다고 워커노드 각각에 대해 고정된 공인 IP를 부여할 수도 없다. 고정된 공인 IP를 구입하는 비용도 있으며 워커노드는 언제든지 늘어날 수 있으며 공인 IP가 추가되면 ACL에 추가된 IP도 넣어야 해서 관리의 어려움이 있다.
egressgateway를 사용한다면 특정 외부 서비스로 나가는 트래픽을 egressgateway를 통해 나가도록 구성할 수 있다. 노드 어피니티를 통해 egressgateway를 특정 노드에만 배포되게 함과 동시에 특정 노드에 공인 IP를 부여하면 위의 문제를 해결할 수 있다.
2. 인터넷이 안되는 환경인 경우
워커노드에서 인터넷으로 접근이 안되는 경우에도 마찬가지로 egressgateway를 활용할 수 있다. 이 때도 마찬가지로 특정 노드에 공인 IP를 할당하여 인터넷이 되게끔 한 다음 허용된 외부 서비스에 한해서 egressgateway를 통해 트래픽이 나가도록 구성할 수 있다.
3. 로깅 및 권한 제어
외부로 나가는 모든 트래픽을 egressgateway를 통하게끔 구성한다면 외부로 나가는 모든 트래픽에 대한 접근 로그는 egressgateway에 쌓일 것이다. 그러면 어떤 요청이 밖으로 나갔는지를 로그를 통해 쉽게 확인이 가능하며, 특정 서비스로 나가는 트래픽을 egressgateway 수준에서 차단 시키는 것도 가능하다. 즉, mesh에서 나가는 트래픽을 egressgateway를 통해 모두 제어할 수도 있다.
Egressgateway 설치
위의 그림처럼 트래픽이 전달되도록 egressgateway를 구성해보자. 먼저 egressgateway가 설치되어 있지 않다면 설치를 해야한다. Helm chart를 사용하여 egressgateway를 설치하자.
설치하기에 앞서 egressgateway를 배치할 노드에다가 특정 레이블을 넣자.
$ kubectl label no k8s-wn1 schedule/istio=egress
그 다음에는 values.yaml파일을 다음과 같이 구성한 후에 helm을 통해 egressgateway를 설치한다.
egressgateway.yaml
defaults:
revision: "1-23-4"
replicaCount: 1
autoscaling:
enabled: false
service:
type: ClusterIP
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1000m
memory: 128Mi
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: schedule/istio
operator: In
values:
- egress
schedule/istio=egress
레이블을 가지는 노드에만 스케줄링 하도록 제한시켰다.service.type
이 기본적으로 LoadBalancer로 지정이 되는데 egressgateway는 외부로 노출시킬 필요가 없기 때문에 ClusterIP로 지정해야 함.
$ helm install egressgateway istio/gateway -n istio-system --version 1.23.4 -f egressgateway.yaml
실제로 k8s-wn1 노드에 스케줄링 되었는지 확인해보자.
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
egressgateway-6f88757c54-dg5nz 1/1 Running 0 40s 10.244.2.140 k8s-wn1 <none> <none>
ingressgateway-64d96fccbb-mvvh2 1/1 Running 2 (170m ago) 6d22h 10.244.2.130 k8s-wn1 <none> <none>
ingressgateway-64d96fccbb-s5pxm 1/1 Running 2 (169m ago) 6d22h 10.244.1.20 k8s-wn2 <none> <none>
istiod-1-23-4-567f9c879-vkrpk 1/1 Running 2 (170m ago) 6d22h 10.244.2.136 k8s-wn1 <none> <none>
egressgateway를 설치만 하면 다음 그림과 같은 형식으로 통신이 될 것이다. 설치만 하고 아무 설정이 없다면 당연히 외부 트래픽을 제어하지 못한다. Ingressgateway에서 내부로 들어오는 트래픽을 제어하기 위해 특정 도메인에 대한 VirtualService와 Gateway를 추가한 것 처럼 Egressgateway에서도 특정 도메인에 대해 나가는 트래픽을 제어하기 위한 설정이 필요한 것이다.
실제로 istio proxy가 들어있는 파드에서 요청을 하고 egressgateway 로그를 확인해보면 아무 로그도 찍혀있지 않을 것이다.
HTTP 트래픽 제어하기
1. mesh VirtualService 추가
일단 mesh 내에서 edition.cnn.com
으로 요청할 경우 egressgateway로 트래픽을 보내야 한다. mesh 내의 트래픽을 제어하려면 VirtualService를 추가하면 된다.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: external-services
namespace: istio-system
spec:
hosts:
## 해당 호스트로 요청할 경우 egressgateway로 트래픽이 이동된다.
## egressgateway의 제어를 받을 호스트를 여기에 여러개 추가가 가능함.
- edition.cnn.com
gateways:
- mesh
http:
- route:
- destination:
## egressgateway 서비스의 FQDN
host: egressgateway.istio-system.svc.cluster.local
VirtualService를 추가한 후 istio-proxy가 있는 아무 파드에서 통신을 해보자. HTTP 통신을 했을 경우 에러가 발생한다.
$ kubectl exec -it reviews-v3-5d8f7bf4c8-vttlf -n bookinfo -- curl -sSL -o /dev/null -D - http://edition.cnn.com
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0HTTP/1.1 503 Service Unavailable
content-length: 190
content-type: text/plain
date: Sat, 18 Jan 2025 11:53:43 GMT
server: envoy
100 190 100 190 0 0 223 0 --:--:-- --:--:-- --:--:-- 223
istio-proxy 로그를 보면 다음과 같이 나오는데 일단 http의 경우에는 edition.cnn.com으로 통신하면 egressgateway까지는 잘 갔지만 egressgateway용 Gateway가 없어서 503 에러가 발생한다.
$ kubectl logs reviews-v3-5d8f7bf4c8-vttlf -c istio-proxy
그림으로 표현하면 다음과 같다.
2. Egress용 Gateway 추가
그 다음에 해야할 일은 Egress용 Gateway를 추가해야 한다. 다음과 같이 Gateway를 만들어보자.
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: egressgateway
namespace: istio-system
spec:
selector:
istio: egressgateway
servers:
- hosts:
- edition.cnn.com
port:
number: 80
name: http
protocol: HTTP
spec.selector
에는 egressgateway만 가지고 있는 레이블을 넣으면 된다.- egressgateway에서 다룰 트래픽을
spec.servers.hosts
에 넣으면 된다.
Egressgateway를 추가하고 통신하면 404에러가 발생할 것이다.
$ kubectl exec -it reviews-v3-5d8f7bf4c8-vttlf -n bookinfo -- curl -sSL -o /dev/null -D - http://edition.cnn.com
HTTP/1.1 404 Not Found
date: Sat, 18 Jan 2025 12:01:26 GMT
server: envoy
x-envoy-upstream-service-time: 5
content-length: 0
Gateway를 추가함으로써 http://edition.cnn.com:80
으로 들어오는 요청을 egressgateway가 받지만 해당 트래픽을 어디로 보낼지 정하지 않아서 404 에러가 발생한다. egressgateway 로그를 확인해보면 route_not_found가 뜬 것을 확인할 수 있다.
$ kubectl logs egressgateway-65566c4579-zf94w
현재 설정을 그림으로 표현하면 다음과 같다.
Egress Gateway에서는 외부 트래픽을 외부에 있는 원래 서비스로 전달만 해주면 된다. 해당 트래픽을 어디로 보낼지 정의하는 것은 VirtualService에서 하는 것이기 때문에 Egressgateway용 VirtualService를 만들어야 한다.
3. EgressGateway용 VirtualService 추가
실제로 egressgateway에서 http://edition.cnn.com
로 요청이 들어온 경우에는 실제 서버로 전달해주기만 하면 된다. 그렇게 하기 위해서는 다음과 같이 설정하면 된다.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: egressgateway
namespace: istio-system
spec:
hosts:
- edition.cnn.com
gateways:
- egressgateway
http:
- route:
- destination:
host: edition.cnn.com
하지만 VirtualService를 추가하고 난 후 통신을 하면 503 에러가 발생한다.
$ kubectl exec -it reviews-v3-5d8f7bf4c8-vttlf -n bookinfo -- curl -sSL -o /dev/null -D - http://edition.cnn.com
HTTP/1.1 503 Service Unavailable
date: Sat, 18 Jan 2025 12:15:54 GMT
server: envoy
x-envoy-upstream-service-time: 74
content-length: 0
503에러가 발생하는 이유는 istio는 destination.host
에 있는 도메인의 위치 (IP)를 모른다. istio는 내장 서비스 레지스트리가 존재하는데, 해당 서비스 레지스트리에 있는 서비스로만 트래픽 제어가 가능하다. istio는 내장 서비스 레지스트리에 edition.cnn.com
를 추가해줘야 실제로 통신이 가능하다.
외부 서비스를 서비스 레지스트리에 추가하려면 ServiceEntry를 등록하면 된다.
4. ServiceEntry
istio 내장 서비스 레지스트리는 자동으로 쿠버네티스 서비스 엔드포인트를 저장해둔다. 그래서 ingressgateway를 등록할 때나 mesh에서 edition.cnn.com
에 대한 요청을 egressgateway
로 보내는 것을 설정할 때는 위와 같은 이슈가 없었다. 하지만 쿠버네티스 바깥에 있는 서비스나 도메인에 대해 트래픽 제어를 하고싶다면 ServiceEntry를 통해 직접 추가해줘야 한다.
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-svc
namespace: istio-system
spec:
hosts:
- edition.cnn.com
ports:
- number: 80
name: http-port
protocol: HTTP
resolution: DNS
- 80 포트로 들어오는
edition.cnn.com
을 서비스 레지스트리에 넣는다. edition.cnn.com
IP를 찾는 방식을 DNS로 하려면spec.resolutions
를DNS
로 넣으면 된다.
이를 적용한 후에 통신하면 통신이 잘 되는 것을 확인할 수 있다.
$ kubectl exec -it reviews-v3-5d8f7bf4c8-vttlf -n bookinfo -- curl -sSL -o /dev/null -D - http://edition.cnn.com
HTTP/1.1 301 Moved Permanently
content-length: 0
...
HTTP/2 200
x-content-type-options: nosniff
content-type: text/html; charset=utf-8
...
하지만 실제로 egressgateway 로그를 보면 약간 이상함을 느끼게 된다. 301 코드로는 로그가 쌓였는데 HTTPS 통신에 대한 트래픽은 egressgateway를 거치지 않았다.
HTTPS 트래픽을 제어하려면 추가 설정이 필요하다.
HTTPS 트래픽 제어하기
1. mesh VirtualService HTTPS 추가
이전에 설정했던 mesh VirtualService에서 HTTPS에 대한 설정을 추가해야 한다.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: external-services
namespace: istio-system
spec:
hosts:
- edition.cnn.com
gateways:
- mesh
http:
- name: default
route:
- destination:
host: egressgateway.istio-system.svc.cluster.local
subset: external-services
tls:
- match:
- port: 443
sniHosts:
- edition.cnn.com
route:
- destination:
host: egressgateway.istio-system.svc.cluster.local
subset: external-services
port:
number: 443
해당 설정을 추가한 후에 https 통신을 해보자.
$ kubectl exec -it reviews-v3-5d8f7bf4c8-vttlf -n bookinfo -- curl -sSL -o /dev/null -D - https://edition.cnn.com
HTTP/2 200
x-content-type-options: nosniff
content-type: text/html; charset=utf-8
...
통신은 잘 되지만 istio-proxy 로그를 확인해보면 egressgateway로 가지 않는 것을 확인할 수 있다.
443포트에 대한 라우팅룰 설정을 해도 egressgateway로 가지 않은 이유는 tls.match.sniHosts 에 있는 도메인을 istio에서 알지 못해서일 수도 있다. sniHost 일치를 확인할 때 edition.cnn.com:443 이 서비스 레지스트리에 있는지 확인하는 것 같다. 그래서 ServiceEntry를 추가하면 해결된다.
2. ServiceEntry 443 추가하기
이전에 만들어 둔 ServiceEntry에서 443 포트에 대한 설정을 추가하자.
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-svc
namespace: istio-system
spec:
hosts:
- edition.cnn.com
ports:
- number: 80
name: http-port
protocol: HTTP
- number: 443
name: tls
protocol: TLS
resolution: DNS
ServiceEntry에 443 포트를 추가한 후에 통신을 해보면 TLS 에러가 발생한다.
$ kubectl exec -it reviews-v3-5d8f7bf4c8-vttlf -n bookinfo -- curl -sSL -o /dev/null -D - https://edition.cnn.com
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to edition.cnn.com:443
command terminated with exit code 35
이유는 마찬가지로 egressgateway까지는 갔지만 egressgateway에서 edition.cnn.com:443
에 대한 처리 방식을 설정하지 않았기 때문이다.
3. egress용 Gateway & VirtualService 443 설정 추가
egressgateway용 Gateway와 VirtualService에 대해 각각 해당 설정을 추가하자.
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: egressgateway
namespace: istio-system
spec:
selector:
istio: egressgateway
servers:
- hosts:
- edition.cnn.com
port:
number: 80
name: http
protocol: HTTP
- hosts:
- edition.cnn.com
port:
number: 443
name: https
protocol: HTTPS
tls:
mode: PASSTHROUGH
- TLS는 외부 서비스에 대한 인증서가 없기 때문에 PASSTHROUGH로 외부 서비스에다가 위임한다.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: egressgateway
namespace: istio-system
spec:
hosts:
- edition.cnn.com
gateways:
- egressgateway
http:
- route:
- destination:
host: edition.cnn.com
tls:
- match:
- port: 443
sniHosts:
- edition.cnn.com
route:
- destination:
host: edition.cnn.com
이렇게 추가하고 나면 https에 대한 통신도 egressgateway쪽으로 간 후 처리가 잘 될 것이다.
$ kubectl exec -it reviews-v3-5d8f7bf4c8-vttlf -n bookinfo -- curl -sSL -o /dev/null -D - https://edition.cnn.com
HTTP/2 200
x-content-type-options: nosniff
content-type: text/html; charset=utf-8
추가 외부 서비스 등록하기
위와 같이 하나의 외부 서비스를 등록해두면 다른 추가 서비스를 등록하는 것은 단순하다. 파일 4개만 등록해놓고 관리할 외부 서비스를 기계적으로 등록만 하면 된다.
serviceentry.yaml
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-svc
namespace: istio-system
spec:
hosts:
# 서비스 추가
- edition.cnn.com
- www.naver.com
ports:
- number: 80
name: http-port
protocol: HTTP
- number: 443
name: tls
protocol: TLS
resolution: DNS
mesh-virtualservice.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: external-services
namespace: istio-system
spec:
hosts:
# 서비스 추가
- edition.cnn.com
- www.naver.com
gateways:
- mesh
http:
- name: default
route:
- destination:
host: egressgateway.istio-system.svc.cluster.local
subset: external-services
tls:
- match:
- port: 443
sniHosts:
# 서비스 추가
- edition.cnn.com
- www.naver.com
route:
- destination:
host: egressgateway.istio-system.svc.cluster.local
subset: external-services
port:
number: 443
egressgateway.yaml
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: egressgateway
namespace: istio-system
spec:
selector:
istio: egressgateway
servers:
- hosts:
# 서비스 추가
- edition.cnn.com
- www.naver.com
port:
number: 80
name: http
protocol: HTTP
- hosts:
# 서비스 추가
- edition.cnn.com
- www.naver.com
port:
number: 443
name: https
protocol: HTTPS
tls:
mode: PASSTHROUGH
egressgateway-vs.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: egressgateway
namespace: istio-system
spec:
hosts:
# 서비스 추가
- edition.cnn.com
- www.naver.com
gateways:
- egressgateway
http:
# 서비스별로 추가
- match:
- authority:
exact: edition.cnn.com
route:
- destination:
host: edition.cnn.com
- match:
- authority:
exact: www.naver.com
route:
- destination:
host: www.naver.com
tls:
# 서비스별로 추가
- match:
- port: 443
sniHosts:
- edition.cnn.com
route:
- destination:
host: edition.cnn.com
- match:
- port: 443
sniHosts:
- www.naver.com
route:
- destination:
host: www.naver.com
댓글