서비스 간 Passport 통신 최적화 (1)

hbjs97·2024년 7월 10일
1
post-thumbnail

문제 정의

진행중인 프로젝트 특성상 다양한 서비스가 함께 운영되고있다. 사용자의 요청이 어려 서비스를 거쳐 기능을 수행하기도 하는데, 인증/인가 된 사용자의 Passport 를 여러 서비스에 전파하기 어려운 문제가 있었다.

Passport는 이메일, 이름 등 유저의 다양한 정보를 담고있는 객체를 의미한다.
여러 서비스에서 공통적으로 사용하는 요청자(사용자) 정보를 담기위해 사용되고있다.

Gateway 에서 Authorization 에 담긴 토큰을 파싱하고 인증/인가를 수행한다. 그리고 Passport 를 만들어 이후 서비스에 전파해야한다.

그러나 다른 서비스로 요청 시 HTTP 클라이언트를 사용해 Passport 정보를 헤더에 담아야 하며, 이로 인해 의존성이 전파되는 문제가 발생했다.

presentation (inbound adapter) → (inbound port) application (outbound port) → (outbound adapter) infrastructure → other service

위 구조에서 요청이 들어온 후 다른 서비스로 나가기까지 모든 영역에서 Passport context를 유지해야 한다. 즉, 각 서비스가 서로 통신할 때 Passport 정보를 직접 전달해야 했고 이 구조는 다양한 문제점을 가지고 있었다.

문제점

  • 복잡성 증가
    Passport 정보를 전달하기 위해 모든 서비스와 함수에 이 정보를 포함시켜야 한다. 이는 코드를 복잡하게 만들고, 이해하기 어렵게 만든다.

  • 유지보수성 저하
    Passport 정보와 관련된 코드가 여러 곳에 분산되어 있으면, 이 정보를 변경해야 할 때 많은 부분을 수정해야 한다. 이는 유지보수를 어렵게 만든다.

  • 비즈니스 로직과 인증 정보의 결합
    Passport 정보를 각 서비스에 직접 전달하는 방식은 비즈니스 로직이 인증 정보에 의존하게 만든다. 예를 들어, 사용자가 주문을 할 때 주문 서비스는 사용자의 Passport 정보를 알아야만 동작할 수 있습니다. 이는 비즈니스 로직과 인증 정보가 결합된 상태입니다.

  • 관심사의 분리 부족
    비즈니스 로직을 처리하는 서비스는 Passport 정보를 다룰 필요가 없다. 비즈니스 로직의 관심사는 주로 주문, 결제, 사용자 관리와 같은 비즈니스 관련 작업이어야 한다. 그러나 기존 방식에서는 비즈니스 로직이 Passport 정보를 처리해야 하는 추가적인 책임을 가지게 된다.

대안

  • BasicAuth 사용
    Gateway 이후의 서비스들 간 통신에는 BasicAuth를 사용하려 했으나, 정적인 id:password 방식으로는 사용자 정보를 담을 수 없고 의존성 전파 문제가 여전히 해결되지 않았다.

  • 공유 저장소(Redis) 캐시
    passport 에 해당하는 정보를 redis 에 캐시하고 각 서비스에서 조회해 사용하려 했으나, 사용자를 특정할 수 있는 key 를 요청에 담아야한다.결국 passport 주입 → key 주입 으로 주입되는 값만 바뀌었고 기존 문제와 같은 상황(의존성 전파)이 발생한다.

  • Istio EnvoyFilter 사용
    sidecar 에서 Inbound 트래픽을 후킹해 헤더의 값을 Outbound 로 전파한다.

해결책? - Istio EnvoyFilter

서비스 메시

서미스 메시란?
서비스 메시는 애플리케이션의 서비스 간 모든 통신을 처리하는 소프트웨어 계층입니다. 이 계층은 컨테이너화된 마이크로서비스로 구성됩니다. 애플리케이션이 확장되고 마이크로서비스의 수가 증가함에 따라 서비스의 성능을 모니터링하기가 점점 어려워지고 있습니다. 서비스 메시는 서비스 간 연결을 관리하기 위해 모니터링, 로깅, 추적, 트래픽 제어와 같은 새로운 기능을 제공합니다. 이러한 기능은 각 서비스의 코드와 독립적이므로 네트워크 경계를 넘어 여러 서비스 관리 시스템에서 작동할 수 있습니다.

요약하면 애플리케이션 레이어가 아닌 플랫폼 레이어에 구성되는 네트워크 제어 방법으로 볼 수 있다.

Istio

Istio 는 오픈 소스 서비스 메시 프로젝트다. k8s 에서 사용할 수 있으며, Envoy(Proxy) 는 Pod 에 사이드카 형태로 배포된다.

  • 서비스 디스커버리
    Istio는 Kubernetes와 통합되어 자동으로 서비스 디스커버리를 수행합니다.

  • 로드 밸런싱
    Envoy 프록시는 자동으로 각 서비스 인스턴스 간의 트래픽을 분산시켜 로드 밸런싱을 제공합니다.

  • 보안
    Citadel은 서비스 간의 통신을 암호화하고, 강력한 인증 및 권한 부여 메커니즘을 제공합니다.

  • 관찰 가능성
    Istio는 모니터링, 로깅, 분산 추적 등의 기능을 통해 서비스 간의 트래픽을 관찰할 수 있게 합니다.

  • 트래픽 관리
    Istio는 트래픽 셰이핑, 서킷 브레이커, 리트라이 정책 등을 통해 트래픽 관리를 지원합니다.

Istio Ingress Gateway

Istio Ingress Gateway 는 외부 트래픽을 클러스터 내부 서비스로 안전하고 효율적으로 라우팅하는 역할을 한다. 고급 트래픽 관리, 보안 강화, 통합 모니터링 등의 기능을 제공하여 마이크로서비스 환경에서 서비스 간의 통신을 최적화할 수 있다. 이를 통해 클러스터 내부의 서비스들을 보호하고, 외부와의 통신을 일관되게 관리할 수 있다.

Istio Envoy 로 어떻게 문제를 해결할 수 있나?

  • 헤더 주입
    Envoy 필터 기능을 통해 Pod의 인바운드 및 아웃바운드 트래픽을 별도로 처리하여, 각 요청에 필요한 Passport 정보를 자동으로 헤더에 추가할 수 있다.

  • 투명성
    애플리케이션의 HTTP 클라이언트 코드에서 Passport를 직접 주입할 필요가 없다. Envoy가 HTTP 요청을 라우팅할 때 자동으로 Passport 정보를 주입하여, 애플리케이션 코드 변경 없이도 Passport 정보를 안전하게 전달할 수 있다.

이러한 기능을 통해 Passport 정보를 안전하고 효율적으로 각 서비스에 전달할 수 있다. 비즈니스 로직과 인증 정보의 분리, 코드에 의존하지 않는 헤더 전파 등의 이점을 누릴 수 있다.

Istio 적용(예상)

istio 가 적용되면 위와같이 사용자의 요청이 전파될 것이다.
gateway 와 envoy 가 어떻게 passport 를 처리할지 알아본다.

로그인 후 사용자가 토큰을 담아 요청할 경우 반드시 gateway 를 거친다.

  1. Authorization 헤더에 토큰을 담아 요청한다.

  2. gateway 에서 토큰을 파싱하고 사용자 인증 후 모든 서비스에 전파할 사용자 정보(Passport)를 만들어 헤더에 추가한다.

  3. Gateway 정책에 따라 요청을 Service A 로 라우팅한다.

  4. Gateway 의 Proxy 는 설정된 EnvoyFilter 정의에 따라 OUTBOUND 트래픽에 대해 passport 헤더를 추가한다.

  5. Service A 는 클라이언트가 보낸 요청의 passport 헤더 값을 그대로 INBOUND 요청에 추가한다.

  6. 필요 시 Passport 정보를 헤더에서 추출해 비즈니스 처리에 사용한다.

Istio 적용 시 예상되는 문제점

Istio 는 마이크로서비스 아키텍처에서 강력한 서비스 메시 기능을 제공하지만 가파른 학습 곡선, 추가적인 자원 점유, 운영 복잡성, 성능 저하, 보안 설정의 복잡성, 디버깅의 어려움 등의 단점도 존재한다.


설치 및 배포

Istio

Istio 설치 가이드에 따르면 istioctl, helm, operator 등 istio 를 설치하는 다양한 방법이 있다. istioctl, operator 의 경우 뚜렷한 단점이 있어 helm 을 선택했다.

Istioctl은 Istio 설치와 관리를 위한 간단하고 직관적인 도구이지만, 유연성 부족, 재현성 문제, 업그레이드와 롤백의 어려움, 커뮤니티와 문서의 제한, 템플릿 기능의 부재 등 여러 단점이 존재한다.

Istio Operator는 Istio 설치 및 관리를 자동화하고 단순화하는데 유용하지만, 구성 변경, 업그레이드 문제, 새로운 기능 지원의 제한, 보안 문제 등 여러 단점을 가지고 있다.

helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update

kubectl create namespace istio-system
helm install istio-base istio/base --version 1.22.2 -n istio-system --set defaultRevision=default --wait
helm install istiod istio/istiod --version 1.22.2 -n istio-system --wait

istio 설치가 끝나면 사이드카를 주입할 네임스페이스를 지정해 라벨을 붙여야한다.

kubectl label namespace <YOUR_SERVICE_NAMESPACE> istio-injection=enabled

Ingress Gateway

kubectl create namespace istio-ingress
kubectl label namespace istio-ingress istio-injection-
helm install istio-ingress istio/gateway --version 1.22.2 -n istio-ingress --wait

설치가 끝나면 ingress 요청을 라우팅할 Gateway 를 배포해야한다.

# istio-ingress-gateway.yml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-ingressgateway
  namespace: istio-ingress
spec:
  selector:
    istio: ingress
  servers:
    - hosts:
        - '*'
      port:
        number: 80
        name: http
        protocol: HTTP

주의

대부분의 레퍼런스는 selector 를 istio: ingressgateway 로 안내하고있지만 helm 으로 설치한경우 istio: ingress 로 배포해야한다.istio: ingressgateway 로 배포하면 istio-ingress service 에 요청이 가지 않는다.

root@master-1:~/workspace/k8s-manifest/istio# kubectl get svc -n istio-ingressNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEistio-ingress LoadBalancer 10.110.132.94 192.168.5.25 15021:30031/TCP,80:31346/TCP,443:32049/TCP 101m

위와같이 EXTERNAL-IP 와 80 포트가 할당되어있지만 요청에 실패한다.

➜ ~ curl http://192.168.5.25:80/actuatorcurl: (7) Failed to connect to 192.168.5.25 port 80 after 5 ms: Couldn't connect to server
➜ ~ telnet 192.168.5.25 80
Trying 192.168.5.25...telnet: connect to address 192.168.5.25: Connection refused
telnet: Unable to connect to remote host

istio-ingressgateway connection refused

kubectl apply -f istio-ingress-gateway.yml

VirtualService

Ingress Gateway 이 외부 트래픽을 수신한다면, Virtual Service 는 해당 트래픽을 클러스터 내부의 서비스로 라우팅 한다.

# virtual-service.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
    name: <YOUR_VIRTUAL_SERVICE_NAME>
    namespace: <YOUR_SERVICE_NAMESPACE>
spec:
    hosts:
        - <YOUT_DOMAIN>
    gateways:
        - istio-ingress/istio-ingressgateway
    http:
        - match:
              - uri:
                    prefix: /
          route:
              - destination:
                    host: <YOUR_GATEWAY_SERVICE>.<YOUR_SERVICE_NAMESPACE>.svc.cluster.local
                    port:
                        number: <YOUR_GATEWAY_SERVICE_PORT>

EnvoyFilter

EnvoyFilter 는 Istio의 트래픽 관리 기능을 확장하여, 세부적인 트래픽 조작, 추가 기능 주입, 보안 강화 및 성능 최적화 등을 가능하게 한다.
지금은 트래픽의 passport 헤더를 조작해야 한다.

# envoy-filter.yaml
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
    name: propagate-passport-header
    namespace: kitech
spec:
    configPatches:
        - applyTo: HTTP_ROUTE
          match:
              context: SIDECAR_OUTBOUND
          patch:
              operation: MERGE
              value:
                  route:
                      request_headers_to_add:
                          - header:
                                key: "passport"
                                value: "%DOWNSTREAM_HTTP:passport%"
        - applyTo: HTTP_ROUTE
          match:
              context: SIDECAR_INBOUND
          patch:
              operation: MERGE
              value:
                  route:
                      request_headers_to_add:
                          - header:
                                key: "passport"
                                value: "%DOWNSTREAM_HTTP:passport%"

적용 후 외부 서비스 호출 예시

이제 애플리케이션 레이어에서 다른 서비스를 호출할 때 passport 를 신경쓰지 않아도 될 것이다.
헤더 설정없이 비즈니스 처리에 집중 할 수 있다!!

라고 생각했지만 실패했다.

여러 EnvoyFilter 를 적용시켜봤는데 전부 실패했다.

SIDECAR_INBOUND, SIDECAR_OUTBOUND 는 각각의 독립적인 컨텍스트를 가지므로 간단히 헤더를 공유할 수 없었다.

해결책에 관해선 다음 포스팅에서 다룬다.


오버엔지니어링?

istio 를 적용했으나 다양한 기능중 일부만 사용하고있다. 몇몇 기능만 사용하기 위해 istio 라는 신기술을 도입하는것에 대해 고민이 많았다.
개발 인력도 부족하고 대규모 프로젝트도 아닌데 이게 꼭 필요한가 싶었으나 다른 방법들이 마음에 들지 않아 결국 도입했다.

쿠버네티스를 이번 프로젝트를 시작하면서 처음 사용해봤는데, 초기 학습 곡선이 가파르다는 점을 제외하면 매우 만족스러웠다. 마찬가지로, Istio의 다양한 기능들을 사용하면서 여러 가지 이점을 경험할 수 있을 것으로 기대하고 있다. 아직 Istio 를 제대로 사용해보진 않았지만, 앞으로 사용하면서 기대되는 부분들이 많다.

오버엔지니어링이 맞다고 생각한다…


Kaili 그래프 시각화

istio 와 함께 사용할 수 있는 여러 툴이있다.
대표적으로 istio 서비스 메시의 상태를 시각화하고 관리할 수 있는 Kiali 가 있고 grafana, prometheus, jaeger, zipkin 등 여러 툴을 함께 사용할 수 있다.

이 중에 Kiali 를 적용시켜 트래픽을 시각화해서 보려고한다.
다양한 설치방법이 있지만 가장 간단한 방법으로 안내한다.

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.22/samples/addons/prometheus.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.22/samples/addons/kiali.yaml

Kiali 의 트래픽 시각화를 위해 Prometheus 를 함께 배포해야한다.

배포 후 Kiali 서비스를 NodePort 로 열고 접속하면 다음과 같은 트래픽 그래프를 확인할 수 있다.


참조

0개의 댓글