Cloud Tech

Istio On EKS

Hanhorang31 2024. 3. 16. 14:27
 

Overview

EKS에서 서비스 매시를 활용하기 위해 ISTIO를 학습한 내용을 정리하겠습니다. 학습 내용은 CloudNet@ 가시다님께서 진행하시는 스터디(AEWS2) 내용을 참고하였습니다. 좋은 자료 공유해주시는 가시다님 무한 감사드립니다!

 

ISTIO

서비스 매시(service Mesh)

서비스 매시는 복잡해진 분산 애플리케이션의 서비스(ex. 마이크로서비스 아키텍처)간 통신을 쉽게 관리하고 안정적으로 운영하기 위해 등장했습니다.

서비스 매시는 서비스 간에 매시 형태의 통신이나 그 경로를 제어하는 것을 뜻합니다. 쿠버네티스로 예를 들면 파드 간 경로에 프록시를 놓고 트래픽 모니터링과 트래픽 컨트롤하여 복잡한 운영 요구 사항을 해결할 수 있게 됩니다.요구 사항에는 A/B 테스트, 카나리아 배포, 속도 제한, 액세스 제어, 암호화, 엔드투엔드 인증 등이 있습니다. 또한, 서비스 매시는 전용 인프라 계층으로 관리되기에 기존 애플리케이션의 수정 없이 요구 사항을 해결할 수 있습니다.

 

ISTIO

Istio는 Service Mesh 오픈소스입니다. Envoy라는 프록시를 파드의 사이드카로 배포하여 파드 간의 트래픽을 관리합니다. 또한, ISTIO는 확장성을 고려하여 설계되어 쿠버네티스와의 호환성을 제공하며 외부 VM 또는 기타 엔드포인트를 연결하여 트래픽을 제어할 수 있습니다.

구성 요소는 컨트롤 플레인(Istiod), 데이터플레인(Envoy)로 나뉩니다.

  • Istiod : 서비스 매시 내의 중앙 관리 컴포넌트입니다. 구성 요소 Pilot(envoy과 통신하면서 라우팅 규칙 동기화), Citadel(통신 간의 암호화, 액세스 제어 및 인증 관리), Gally(Istio 구성의 관리와 검증)를 통해 트래픽을 관리합니다.
  • Envoy : C++로 개발된 고성능 프록시입니다. 서비스 매시의 기능들을 수행합니다. Istio에서는 쿠버네티스 파드 안 사이드카 형태로 주입되어 실행됩니다.

 

결국, Istio는 Envoy를 쓰기 편하게 만든 플랫폼으로 Istiod 를 통해 envoy Proxy 트래픽이나 정책을 관리하는 것으로 보시면 됩니다.

 

데모 환경 구성

실습을 위해 데모 환경을 구성하겠습니다. 먼저 Istio와 EKS 설치를 위해 오픈소스 terraform-aws-eks-blueprint 에서 istio 를 사용했습니다.

 

EKS 버전 1.29, istio chart version 1.20.2 기준입니다. 설치에 대략 20분 정도 소요됩니다.

 

terraform init
terraform apply -auto-approve
  • 반복하여 설치하는 경우, KMS와 Cloudwatch 리소스가 이미 있어 에러가 발생합니다. 다음의 명령어를 통해 삭제해주세요. 참고
  • 노드 그룹 생성시 생성 실패 가 나온다면 노드 그룹 IAM 정책을 확인하여 AmazonEKS_CNI_Policy 정책을 추가해주세요. 추가 후 addon 설치는 명령어 terraform apply -target="module.eks_blueprints_addons" -auto-approve 를 통해 진행해주세요.
  • 클러스터 접근이 안되어 istio 네임스페이스 생성이 안되는 경우, aws eks update-kubeconfig --region ap-northeast-2 --name istio 를 통해 인증을 진행해주세요.

 

클러스터 생성 후, istiod 종속성 문제로 istio-ingress 를 다시 시작해야 합니다.

 

kubectl rollout restart deployment istio-ingress -n istio-ingress

 

또한, Istio 전용 대시보드인 Kiali를 설치하겠습니다. 대시보드는 바로 밑 예제 애플리케이션 배포 후 확인하겠습니다.

 

for ADDON in kiali jaeger prometheus grafana
do
    ADDON_URL="https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/$ADDON.yaml"
    kubectl apply -f $ADDON_URL
done 

설치 이후 파드를 확인하면 다음과 같이 구성됩니다.

kubectl get pods -A 

 

서비스매시 동작을 확인하기 위해 예제 애플리케이션을 배포하겠습니다. 예제 애플리케이션은 MSA로 구성된 카탈로그 조회 시스템입니다. 소스 예제는 다음의 깃을 참고해주세요.

 

git clone https://github.com/aws-samples/istio-on-eks.git

# Change directory to the right folder
cd modules/01-getting-started

# Create workshop namespace 
kubectl create namespace workshop
kubectl label namespace workshop istio-injection=enabled

# Install all the microservices in one go
helm install mesh-basic . -n workshop
  • Istio는 파드 라벨을 통해 enovy 사이드카를 주입합니다. 예제에서는 네임스페이스 workshop 에 라벨을 미리 설정하여 사이드카를 주입할 수 있도록 설정했습니다.

 

예제를 간단히 확인하면, Catalog 파드 접근에 따라 Vendor가 다르게 나오는 것을 확인할 수 있습니다.

ISTIO_INGRESS_URL=$(kubectl get svc istio-ingress -n istio-ingress -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')
echo "http://$ISTIO_INGRESS_URL"

 

앞에서 설치한 Kiali 대시보드를 접근하면 파드들의 트래픽 경로를 알 수 있습니다.

kubectl port-forward svc/kiali 20001:20001 -n istio-system
  • 설정에 따라 원하는 대시보드를 구성할 수 있습니다. 필자와 같이 옵션을 설정해주세요.

 

 

Istio 동작 원리 이해

예제 배포 후 isitio 사이드카 파드를 확인하겠습니다.

# Container 확인 
kubectl describe pods catalogdetail-5896fff6b8-v5z6b  -n workshop;

# istio proxy 파드 접근 
kubectl exec -it catalogdetail-5896fff6b8-v5z6b -n workshop -c istio-proxy -- sh 
# 라우터 확인
netstat -anr
# 포트 확인 
ss -ltp

# isti-init 파드 로그 확인 
kubextl log -it catalogdetail-5896fff6b8-v5z6b -n workshop -c istio-init
  • 사이드카 포트를 확인하면 envoy Proxy가 포트 15001~15020포트를 사용하고 있는 것을 확인할 수 있고, 포트에 따라 트래픽이 라우팅된 것을 확인할 수 있습니다.

 

Enovy 프록시 설정은 istio-proxy 파드 내에서 확인 가능합니다. 프록시 설정을 확인하면 엔드포인트, 라우팅을 비롯한 보안 설정을 확인할 수 있습니다.

# istio proxy 파드 접근 
kubectl exec -it catalogdetail-5896fff6b8-v5z6b -n workshop -c istio-proxy -- sh 

cat /etc/istio/proxy/envoy-rev.json

 

Istio는 위 Envoy 설정을 비동기적으로 동기화하여 관리합니다. istio 커맨드 툴인 istioctl를 이용하여 동기화를 확인하겠습니다.

istioctl proxy-status
  1. CDS (Cluster Discovery Service): 먼저, Envoy 프록시는 CDS를 통해 사용 가능한 서비스(또는 클러스터)의 목록과 그 설정을 받아옵니다. 이는 전체 서비스 메시의 구조와 각 서비스의 위치를 파악하는 기본 단계입니다.
  2. LDS (Listener Discovery Service): CDS로부터 클러스터 정보를 받은 후, Envoy 프록시는 LDS를 통해 특정 포트에서 들어오는 연결을 처리할 리스너의 구성을 받아옵니다. 이 단계에서는 트래픽을 어떻게 받을지에 대한 구성이 이루어집니다.
  3. EDS (Endpoint Discovery Service): Envoy 프록시는 각 서비스(또는 클러스터)의 실제 네트워크 엔드포인트(예: IP 주소와 포트) 정보를 EDS를 통해 받아옵니다. 이 정보를 바탕으로 트래픽을 올바른 목적지로 라우팅할 수 있게 됩니다.
  4. RDS (Route Discovery Service): Envoy 프록시는 RDS를 통해 특정 HTTP 라우트의 구성을 받아, 들어오는 요청을 어떤 서비스로 전달할지 결정하는 라우팅 규칙을 동적으로 설정합니다. 이는 보다 세밀한 트래픽 관리를 가능하게 합니다.
  5. ECDS (Extension Configuration Discovery Service): 마지막으로, Envoy 프록시는 ECDS를 통해 사용자 정의 확장 및 필터 구성을 받을 수 있습니다. 이는 고급 사용 사례에서 특정 로직이나 필터를 프록시에 동적으로 적용할 때 사용됩니다.

[참고: ChatGPT]

 

Istio 를 통한 트래픽 관리는 다음의 CRD 객체를 통해 관리합니다.

kubectl get Gateway,VirtualService,DestinationRule -n workshop

배포된 객체 구성도를 확인하면 다음과 확인할 수 있습니다. 위에서 구성한 컨트롤러는 다음과 같습니다.

  1. Gateway: Gateway는 클러스터 외부의 트래픽이 쿠버네티스 클러스터 내부의 서비스로 들어오거나 나가는 방식을 제어합니다. 즉, 외부 트래픽을 어떻게 쿠버네티스 서비스로 라우팅할지 정의합니다. 이를 통해 특정 호스트, 포트, 프로토콜 등을 기준으로 트래픽을 관리할 수 있습니다.
  2. VirtualService: 트래픽 라우팅 규칙을 정의하여 서비스 간의 트래픽 흐름을 제어합니다. 이 객체는 URI, HTTP 메소드 등과 같은 요청 속성을 기반으로 트래픽을 어떤 방향으로 보낼지 결정하는 데 사용됩니다. VirtualService는 한 개 이상의 Gateway에 연결될 수 있으며, 특정 서비스나 여러 서비스의 버전(예: A/B 테스팅, 카나리 배포 등)으로 트래픽을 분할하는 기능을 제공합니다.
  3. DestinationRule: 이름처럼, 트래픽의 최종 목적지를 정의하는 정책입니다. 이 정책에는 로드 밸런싱 설정, 연결 풀 설정, 아웃바운드 연결 타임아웃 등이 포함될 수 있습니다. 이러한 정책은 변수로 지정되어 VirutalService에서 트래픽을 라우팅하는 데 사용됩니다.

 

참고한 개념을 통해서 구성을 확인하면, 외부에서 오는 트래픽을 게이트웨이를 통해 들어오며, 호스트 * 에 대해 / url로 오는 경로들은 전부 fronted:9000 으로 통과하도록 구성되어 있습니다. VirtualService 객체를 확인하면 라우팅 정보를 확인할 수 있습니다.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
...
spec:
  gateways:
  - productapp-gateway
  hosts:
  - '*'
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: frontend
        port:
          number: 9000

 

 

기능 확인

앞에서 확인한 istio 객체를 통해 트래픽을 관리하는 예제를 확인해보겠습니다. Istio를 통한 서비스 매시 기능을 구현은 트래픽 관리 요소, 보안 요소, 모니터링 요소로 나눠집니다. 공식 문서 Task에서 요소 별 기능을 확인할 수 있습니다.

 

Traffic Management

  • Request Routing: 클라이언트 요청을 서비스의 다양한 버전으로 라우팅합니다.
  • Fault Injection: 테스트 목적으로 네트워크 실패나 서비스 실패를 인위적으로 주입합니다.
  • Traffic Shifting: 트래픽의 비율을 조절하여 서비스 버전 간에 점진적으로 이동합니다.
  • TCP Traffic Shifting: TCP 트래픽을 다른 서비스 버전으로 점진적으로 전환합니다.
  • Request Timeouts: 특정 요청이 지정된 시간 내에 완료되지 않으면 타임아웃을 발생시킵니다.
  • Circuit Breaking: 서비스에 과도한 요청이 발생할 때 연결을 일시적으로 끊어 부하를 줄입니다.
  • Mirroring: 실제 트래픽의 복사본을 다른 서비스로 보내 테스트합니다.
  • Locality Load Balancing: 사용자에게 가장 가까운 서비스 인스턴스에 트래픽을 라우팅합니다.

Security

  • Certificate Management: 서비스 간 통신을 위한 인증서를 자동으로 관리합니다.
  • Authentication: 서비스 간 또는 사용자와 서비스 간의 신원을 검증합니다.
  • Authorization: 요청에 따른 서비스 접근 권한을 관리합니다.
  • TLS Configuration: 서비스 간 통신을 위한 TLS 설정을 관리합니다.
  • Policy Enforcement: 정의된 보안 정책을 강제합니다.

Observability

  • Telemetry API: 서비스 메시 내의 통신에 대한 메트릭, 로그 등을 수집합니다.
  • Metrics: 서비스 사용에 대한 정량적 데이터를 수집합니다.
  • Logs: 서비스 운영에 대한 로그 데이터를 수집합니다.
  • Distributed Tracing: 서비스 간 요청의 경로를 추적하여 성능 문제를 분석합니다.

 

각 기능은 공식 문서 예제를 통해 참고해주세요.

이번 글에서는 필자가 우선적으로 사용할 것 같은 기능들을 뽑아 테스트하겠습니다.

 

 

1. Traffic Shifting를 통한 카나리 배포

트래픽 라우팅을 수정할 수 있는 객체인 VirtualService를 이용하여 위 예제의 가중치를 변경하여 카나리 배포를 실습하겠습니다. 예제는 간단합니다. catalogdetail 파드 v1에 90, v2에 10의 트래픽을 전달하는 예제입니다. 트래픽 관련 예제는 AWS 블로그를 참고하였습니다.

 

 

kubectl apply -f ./weight-based-routing/catalogdetail-virtualservice.yaml

트래픽을 확인하기 위해 siege 도구를 통해 트래픽을 전달하여 대시보드를 확인하겠습니다.

Copy
ISTIO_INGRESS_URL=$(kubectl get svc istio-ingress -n istio-ingress -o jsonpath='{.status.loadBalancer.ingress[*].hostname}')
siege http://$ISTIO_INGRESS_URL -c 5 -d 10 -t 2M
  • 대시보드에서 istio 객체에 따라 파드 이름에 보라색 아이콘이 다르게 표시됩니다.

 

2. 헤더 기반 라우팅

요청 헤더에 따라 다르게 라우팅되는 예제를 살펴보겠습니다. 아래 헤더 값에 따라 internal 이 포함되는 경우 v2로 아니면 v1로 보내는 예제입니다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalogdetail
  namespace: workshop
spec:
  hosts:
  - catalogdetail
  http:
  - match:
    - headers:
        user-type:
          exact: internal
    route:
    - destination:
        host: catalogdetail
        port:
          number: 3000
        subset: v2
  - route:
    - destination:
        host: catalogdetail
        port:
          number: 3000
        subset: v1

요청 헤더를 수정하기 위해 istio EnvoyFilter 객체를 사용하겠습니다. 객체 정의 부분 lua 스크립트를 통해 랜덤 숫자 중 30 이하가 헤더에 internal 이 포함되도록, 아니면 external이 되도록 헤더를 설정합니다.

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: productcatalog
  namespace: workshop
spec:
  workloadSelector:
    labels:
      app: productcatalog
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        filterChain:
          filter:
            name: "envoy.filters.network.http_connection_manager"
            subFilter:
              name: "envoy.filters.http.router"
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.lua
        typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          defaultSourceCode:
            inlineString: |-
              function envoy_on_request(request_handle)
                  math.randomseed(os.clock()*100000000000);
                  local r = math.random(1, 100);
                  if r <= 30 then
                  request_handle:headers():add("USER-TYPE", "internal");
                  else
                  request_handle:headers():add("USER-TYPE", "external");
                  end
              end

 

3. 트래픽 미러링

트래픽 미러링은 실시간 트래픽 사본을 서비스로 보내는 기능입니다. 미러링된 트래픽은 기본 서비스의 응답에 영향을 미치지 않습니다. 이 전략을 사용하면 새 릴리스가 올바르게 응답하지 않는 경우 고객에게 영향을 주지 않고 새 버전이 실제 트래픽에서 어떻게 작동하는지 테스트할 수 있습니다. 다음 예제는 v1 로 보낸 트래픽 중 절반을 미러링하여 v2로 보내는 예제입니다.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: catalogdetail
  namespace: workshop
spec:
  hosts:
  - catalogdetail
  http:
  - route:
    - destination:
        host: catalogdetail
        port:
          number: 3000
        subset: v1
      weight: 100
    mirror:
      host: catalogdetail
      port:
        number: 3000
      subset: v2
    mirrorPercentage:
      value: 50

 

 

4. mTLS 를 Zero Trust 구현

제로 트러스트(Zero Trust)는 네트워크 보안의 한 모델로서, 네트워크 내부 또는 외부에서의 접근에 있어서 모두 신뢰하지 않는다는 원칙입니다. Istio를 이용하여 클러스터 노드 안에서 파드의 접근까지도 tls가 없으면 허용하지 않는 예제를 구현하겠습니다.

kubectl apply -n workshop -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: mtls
spec:
  mtls:
    mode: STRICT
EOF

핵심은 spec.mtls 옵션입니다. 옵션이 STRICT인 경우 상호 트래픽이 mtls인 경우(envoy 사이드카가 주입된 경우)에만 통신됩니다. 대시보드를 확인하면 통신 트래픽에 자물쇠가 표시됩니다. 자물쇠가 안보이는 경우 Display 옵션 Security을 체크해주세요.

 

이번에는 사이드카가 없는 파드에서 workshop 네임스페이스의 frontend 파드를 호출하는 경우를 확인하겠습니다. 예제에 사용한 파드는 istio 예제를 확인해주세요.

# 예제 sleep  배포
kubectl apply -f ./sleep.yml -n default
# 파드 접속
kubectl exec -it sleep-7656cf8794-bbfvt  -n test -- sh
# 워크샵 파드 접근 
curl -XGET frontend.workshop/health:9000 -v

커넥션 자체가 안되는 것을 확인할 수 있습니다.

 

5. JWT 토큰을 통한 인증 위임

다음은 파드에 JWT토큰을 통해 인증을 받을 경우에만 통신할 수 있도록 하는 예제를 진행하겠습니다. 공식 문서를 참고하여 진행하면 다음과 같이 구현할 수 있습니다.

kubectl create ns foo
kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo

아래 명령어는 sleep 파드에서 httpbin 파드로 호출하여 http_code를 확인합니다. 정상적으로 200이 나올텐데요.

kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl http://httpbin.foo:8000/ip -sS -o /dev/null -w "%{http_code}\n"

아래 정책을 통해 허용된 JWT JWT 토큰을 통한 인증 요청하는 정책을 생성합니다.

kubectl apply -f - <https://raw.githubusercontent.com/istio/istio/release-1.20/security/tools/jwt/samples/jwks.json"
EOF

추가로, JWT 토큰이 아예 없는 경우에도 200호출이 반환되어 허용된 JWT에만 트래픽이 허용되도록 하는 인증이 필요합니다.

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: foo
spec:
  selector:
    matchLabels:
      app: httpbin
  action: ALLOW
  rules:
  - from:
    - source:
       requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
EOF

 

가이드문서에 따라 JWT토큰을 발급받아 호출하면 200이 반환되고 그 이외의 호출은 403이 떨어지는 것을 확인할 수 있습니다.

# 토큰 반환 후 호출 
TOKEN=$(curl https://raw.githubusercontent.com/istio/istio/release-1.20/security/tools/jwt/samples/demo.jwt -s) && echo "$TOKEN" | cut -d '.' -f2 - | base64 --decode -
# 토큰 있는 호출, 반환 200 
kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -H "Authorization: Bearer $TOKEN" -w "%{http_code}\n"

# 토큰 없는 호출, 반환 403 
kubectl exec "$(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})" -c sleep -n foo -- curl "http://httpbin.foo:8000/headers" -sS -o /dev/null -w "%{http_code}\n"

 

 

6. 트레이싱

Istio 를 통해 트래픽 트레이싱도 가능합니다. 트레이싱(Tracing)은 분산 시스템에서 서비스 간의 요청과 응답을 추적하는 과정입니다. 트래픽의 지연이 발생하는 경우에는 시스템의 로그가 없어 원인을 분석하기 힘들지만, 트레이싱을 통해 지연 원인을 찾을 수 있습니다. 트레이싱은 대시보드 kiail 와 트레이싱툴인 jaeger에서 확인이 가능합니다. 먼저 kiali 대시보드에서 각 파드별 트레이싱 정보를 확인할 수 있습니다.

 

  • Trace Details 에서 2b62d1c9929d09808f7b59f3f269aa45 는 istio에서 각 트래픽을 구분하기 위한 키입니다. 해당 키를 이용하여 jaeger에서 트레이싱이 가능합니다.

 

jaeger 는 다음의 명령어를 통해 대시보드로 접근 가능합니다.

istioctl dashboard jaeger

대시보드에서 트레이싱을 검색해서 태그를 확인한 다음 태그 값에 맞게 검색하여 트레이싱할 수 있습니다.

 

트레이싱 데이터가 쌓이면 성능상의 이슈가 발생할 것 인데요, 기본 옵션으로 트레이싱은 1%만 수집되도록 설정되어 있습니다. 트레이싱 수집률을 설정하려면 다음의 정책을 배포해주세요.

kubectl apply -f - <<EOF
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
  name: mesh-default
  namespace: istio-system
spec:
  tracing:
    - providers:
        - name: "zipkin"
      randomSamplingPercentage: 100.00
EOF

 

 

 

 

 

자원 삭제

자원 삭제를 위해서는 ingress 자원 삭제를 먼저 진행해주셔야 합니다. 삭제 과정 중 로드밸런서와 자원간의 비동기 삭제로 종속성 문제로 삭제가 안된다고 합니다.

terraform destroy -target='module.eks_blueprints_addons.helm_release.this["istio-ingress"]' -auto-approve
terraform destroy 

필자의 경우, 위 과정을 진행하였음에도 EKS 클러스터가 살아있어 콘솔에서 삭제했습니다. 리소스 자원을 확인해서 비용이 발생하지 않도록 해주세요.