[cilium] L7 Traffic Shifting 테스트

cilium의 service mesh에서는 traffic shifting 설정을 CiliumEnvoyConfig로 할 수 있다. 

 

샘플 애플리케이션 배포하기

# traffic-shifting 이라는 넴스페이스를 생성해서 여기서 진행하겠습니다.
kubectl create ns traffic-shifting

kubectl config set-context --current --namespace traffic-shifting

먼저 helloworld의 백엔드로 2개의 deployment를 준비합니다. 이 deployment의 라벨은 version: v1과 v2가 서로 다르게 붙어있습니다. 

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
    service: helloworld
spec:
  ports:
    - port: 5000
      name: http
  selector:
    app: helloworld
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-v1
  labels:
    app: helloworld
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld
      version: v1
  template:
    metadata:
      labels:
        app: helloworld
        version: v1
    spec:
      containers:
        - name: helloworld
          image: docker.io/istio/examples-helloworld-v1
          resources:
            requests:
              cpu: "100m"
          imagePullPolicy: Always
          ports:
            - containerPort: 5000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-v2
  labels:
    app: helloworld
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld
      version: v2
  template:
    metadata:
      labels:
        app: helloworld
        version: v2
    spec:
      containers:
        - name: helloworld
          image: docker.io/istio/examples-helloworld-v2
          resources:
            requests:
              cpu: "100m"
          imagePullPolicy: Always
          ports:
            - containerPort: 5000
EOF

 

그리고 클라이언트 역할을 할 client-pod를 하나 구성합니다. 

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
  labels:
    app: curl
spec:
  containers:
  - name: curl
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
EOF

 

 

기본 설정의 경우 

아무런 설정이 안되어있는 경우 랜덤하게 호출이 되며 5:5 비율로 호출이 되게 됩니다. 

for i in {1..100}; do kubectl exec -i curl-pod -- curl -s helloworld:5000/hello; done \
| tee /dev/stderr \
| awk '/version: v1/{v1++} /version: v2/{v2++} END{printf("v1=%d  v2=%d  total=%d\n", v1, v2, v1+v2)}'

 

100번을 호출했을때 50대 50으로 호출이 된것을 볼 수 있습니다. 

 

 

트래픽 시프팅 해보기

CiliumEnvoyConfig를 사용하면 클라이언트에서는 동일하게 helloworld를 호출하지만 Envoy가 중간에 트래픽을 가로채서 helloworld-v1/-v2라는 ‘서비스에 매핑된 엔드포인트 집합’으로 가중치 분배를 수행해 직접 파드로 연결할 수 있습니다.

 

직접 실행해보면서 확인해보겠습니다. 먼저 helloworld의 각각의 버전을 호출할 수 있도록 서비스를 2개를 추가 생성합니다. 

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: helloworld-v1
  labels:
    app: helloworld
    service: helloworld
    version: v1
spec:
  ports:
    - port: 5000
      name: http
  selector:
    app: helloworld
    version: v1
---
apiVersion: v1
kind: Service
metadata:
  name: helloworld-v2
  labels:
    app: helloworld
    service: helloworld
    version: v2
spec:
  ports:
    - port: 5000
      name: http
  selector:
    app: helloworld
    version: v2
EOF

 

이제 총 3개의 서비스가 생성되었습니다.

 

이제 helloworld를 호출하면 helloworld-v1혹은 v2가 호출 될 수 있도록 cilium envoy config를 설정합니다. 테스트하는 네임스페이스를 잘 살펴서 설정을 변경해 배포하시길 바랍니다. 

cat << EOF | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumEnvoyConfig
metadata:
  name: envoy-lb-listener
spec:
  services:
    - name: helloworld
      namespace: traffic-shifting # 넴스페이스 수정
  backendServices:
    - name: helloworld-v1
      namespace: traffic-shifting # 넴스페이스 수정
    - name: helloworld-v2
      namespace: traffic-shifting # 넴스페이스 수정
  resources:
    - "@type": type.googleapis.com/envoy.config.listener.v3.Listener
      name: envoy-lb-listener
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: envoy-lb-listener
                rds:
                  route_config_name: lb_route
                http_filters:
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
    - "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration
      name: lb_route
      virtual_hosts:
        - name: "lb_route"
          domains: [ "*" ]
          routes:
            - match:
                prefix: "/"
              route:
                weighted_clusters:
                  clusters:
                    - name: "traffic-shifting/helloworld-v1"  # 넴스페이스 수정
                      weight: 90 
                    - name: "traffic-shifting/helloworld-v2"  # 넴스페이스 수정
                      weight: 10
                retry_policy:
                  retry_on: 5xx
                  num_retries: 3
                  per_try_timeout: 1s
    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: "traffic-shifting/helloworld-v1"  # 넴스페이스 수정
      connect_timeout: 5s
      lb_policy: ROUND_ROBIN
      type: EDS
      outlier_detection:
        split_external_local_origin_errors: true
        consecutive_local_origin_failure: 2
    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: "traffic-shifting/helloworld-v2"  # 넴스페이스 수정
      connect_timeout: 3s
      lb_policy: ROUND_ROBIN
      type: EDS
      outlier_detection:
        split_external_local_origin_errors: true
        consecutive_local_origin_failure: 2
EOF

 

이상태에서 아까와 동일하게 helloworld를 호출해보겠습니다.

for i in {1..100}; do kubectl exec -i curl-pod -- curl -s helloworld:5000/hello; done \
| tee /dev/stderr \
| awk '/version: v1/{v1++} /version: v2/{v2++} END{printf("v1=%d  v2=%d  total=%d\n", v1, v2, v1+v2)}'

 

이제는 아까와는 다르게 9대 1로 설정이 변경된 것을 알 수 있습니다. 

 

 

hubble에서는 기본으로 보았을 때에는 helloworld로 가는 것 처럼 보이고 

 

helloworld를 클릭했을 때 버전 정보는 나오지만 CiliumEnvoyConfig로 인해 트래픽의 가중치가 변경되었는지는 알기 어렵습니다.