Migration to the new policies

Kuma provides two set of policies to configure proxies. The original source/destination policies, while provided a lot of features, haven’t met users expectations in terms of flexibility and transparency. The new targetRef policies were designed to preserve what already worked well, and enhance the matching functionality and overall UX.

In this guide, we’re going to setup a demo with old policies and then perform a migration to the new policies.

Prerequisites

  • helm - a package manager for Kubernetes
  • kind - a tool for running local Kubernetes clusters
  • jq - a command-line JSON processor
  • jd - a command-line utility to visualise JSONPatch

Start Kubernetes cluster

Start a new Kubernetes cluster on your local machine by executing:

  1. kind create cluster --name=mesh-zone

You can skip this step if you already have a Kubernetes cluster running. It can be a cluster running locally or in a public cloud like AWS EKS, GCP GKE, etc.

Install Kuma

Install Kuma control plane with skipMeshCreation set to true by executing:

  1. helm repo add kuma https://kumahq.github.io/charts
  2. helm repo update
  3. helm install --create-namespace --namespace kuma-system kuma kuma/kuma --set "controlPlane.defaults.skipMeshCreation=true"

Make sure the list of meshes is empty:

  1. kubectl get meshes

Expected output:

  1. No resources found

Setup demo with old policies

In the first half of this guide we’re going to deploy a demo app in the default mesh and configure it using old policies.

Create default mesh

  1. echo 'apiVersion: kuma.io/v1alpha1
  2. kind: Mesh
  3. metadata:
  4. name: default
  5. spec:
  6. # for the purpose of this guide we want to setup mesh with old policies first,
  7. # that is why we are skipping the default policies creation
  8. skipCreatingInitialPolicies: ["*"] ' | kubectl apply -f-

Deploy demo application

  1. Deploy the application

    1. kubectl apply -f https://raw.githubusercontent.com/kumahq/kuma-counter-demo/master/demo.yaml
    2. kubectl wait -n kuma-demo --for=condition=ready pod --selector=app=demo-app --timeout=90s
  2. Port-forward the service to the namespace on port 5000:

    1. kubectl port-forward svc/demo-app -n kuma-demo 5000:5000
  3. In a browser, go to 127.0.0.1:5000 and increment the counter.

Enable mTLS and deploy TrafficPermissions

  1. echo 'apiVersion: kuma.io/v1alpha1
  2. kind: Mesh
  3. metadata:
  4. name: default
  5. spec:
  6. skipCreatingInitialPolicies: ["*"]
  7. mtls:
  8. enabledBackend: ca-1
  9. backends:
  10. - name: ca-1
  11. type: builtin' | kubectl apply -f-
  1. echo 'apiVersion: kuma.io/v1alpha1
  2. kind: TrafficPermission
  3. mesh: default
  4. metadata:
  5. name: app-to-redis
  6. spec:
  7. sources:
  8. - match:
  9. kuma.io/service: demo-app_kuma-demo_svc_5000
  10. destinations:
  11. - match:
  12. kuma.io/service: redis_kuma-demo_svc_6379' | kubectl apply -f -

Deploy TrafficRoute

  1. echo 'apiVersion: kuma.io/v1alpha1
  2. kind: TrafficRoute
  3. mesh: default
  4. metadata:
  5. name: route-all-default
  6. spec:
  7. sources:
  8. - match:
  9. kuma.io/service: "*"
  10. destinations:
  11. - match:
  12. kuma.io/service: "*"
  13. conf:
  14. destination:
  15. kuma.io/service: "*"' | kubectl apply -f-

Deploy Timeouts

  1. echo 'apiVersion: kuma.io/v1alpha1
  2. kind: Timeout
  3. mesh: default
  4. metadata:
  5. name: timeout-global
  6. spec:
  7. sources:
  8. - match:
  9. kuma.io/service: "*"
  10. destinations:
  11. - match:
  12. kuma.io/service: "*"
  13. conf:
  14. connectTimeout: 21s
  15. tcp:
  16. idleTimeout: 22s
  17. http:
  18. idleTimeout: 22s
  19. requestTimeout: 23s
  20. streamIdleTimeout: 25s
  21. maxStreamDuration: 26s' | kubectl apply -f-

Deploy CircuitBreaker

  1. echo 'apiVersion: kuma.io/v1alpha1
  2. kind: CircuitBreaker
  3. mesh: default
  4. metadata:
  5. name: cb-global
  6. spec:
  7. sources:
  8. - match:
  9. kuma.io/service: "*"
  10. destinations:
  11. - match:
  12. kuma.io/service: "*"
  13. conf:
  14. interval: 21s
  15. baseEjectionTime: 22s
  16. maxEjectionPercent: 23
  17. splitExternalAndLocalErrors: false
  18. thresholds:
  19. maxConnections: 24
  20. maxPendingRequests: 25
  21. maxRequests: 26
  22. maxRetries: 27
  23. detectors:
  24. totalErrors:
  25. consecutive: 28
  26. gatewayErrors:
  27. consecutive: 29
  28. localErrors:
  29. consecutive: 30
  30. standardDeviation:
  31. requestVolume: 31
  32. minimumHosts: 32
  33. factor: 1.33
  34. failure:
  35. requestVolume: 34
  36. minimumHosts: 35
  37. threshold: 36' | kubectl apply -f-

Migration steps

It’s time to migrate the demo app to the new policies.

Each type of policy can be migrated separately; for example, once we have completely finished with the Timeouts, we will proceed to the next policy type, CircuitBreakers. It’s possible to migrate all policies at once, but small portions are preferable as they’re easily reversible.

The generalized migration process roughly consists of 4 steps:

  1. Create a new targetRef policy as a replacement for existing source/destination policy (do not forget about default policies that might not be stored in your source control). The corresponding new policy type can be found in the table. Deploy the policy in shadow mode to avoid any traffic disruptions.
  2. Using Inspect API review the list of changes that are going to be created by the new policy.
  3. Remove kuma.io/effect: shadow label so that policy is applied in a normal mode.
  4. Observe metrics, traces and logs. If something goes wrong change policy’s mode back to shadow and return to the step 2. If everything is fine then remove the old policies.

The order of migrating policies generally doesn’t matter, except for the TrafficRoute policy, which should be the last one deleted when removing old policies. This is because many old policies, like Timeout and CircuitBreaker, depend on TrafficRoutes to function correctly.

TrafficPermission -> MeshTrafficPermission

  1. Create a replacement policy for app-to-redis TrafficPermission and apply it with kuma.io/effect: shadow label:

    1. echo 'apiVersion: kuma.io/v1alpha1
    2. kind: MeshTrafficPermission
    3. metadata:
    4. namespace: kuma-system
    5. name: app-to-redis
    6. labels:
    7. kuma.io/mesh: default
    8. kuma.io/effect: shadow
    9. spec:
    10. targetRef:
    11. kind: MeshService
    12. name: redis_kuma-demo_svc_6379
    13. from:
    14. - targetRef:
    15. kind: MeshSubset
    16. tags:
    17. kuma.io/service: demo-app_kuma-demo_svc_5000
    18. default:
    19. action: Allow' | kubectl apply -f -
  2. Check the list of changes for the redis_kuma-demo_svc_6379 kuma.io/service in Envoy configuration using kumactl, jq and jd:

    1. DATAPLANE_NAME=$(kumactl get dataplanes -ojson | jq '.items[] | select(.networking.inbound[0].tags["kuma.io/service"] == "redis_kuma-demo_svc_6379") | .name')
    2. kumactl inspect dataplane ${DATAPLANE_NAME} --type=config --shadow --include=diff | jq '.diff' | jd -t patch2jd

    Expected output:

    1. @ ["type.googleapis.com/envoy.config.listener.v3.Listener","inbound:10.42.0.13:6379","filterChains","0","filters","0","typedConfig","rules","policies","allow-all-default"]
    2. - {"permissions":[{"any":true}],"principals":[{"authenticated":{"principalName":{"exact":"spiffe://default/demo-app_kuma-demo_svc_5000"}}}]}
    3. @ ["type.googleapis.com/envoy.config.listener.v3.Listener","inbound:10.42.0.13:6379","filterChains","0","filters","0","typedConfig","rules","policies","MeshTrafficPermission"]
    4. + {"permissions":[{"any":true}],"principals":[{"authenticated":{"principalName":{"exact":"spiffe://default/demo-app_kuma-demo_svc_5000"}}}]}

    As we can see, the only difference is the policy name MeshTrafficPermission instead of allow-all-default. The value of the policy is the same.

  3. Remove the kuma.io/effect: shadow label:

    1. kubectl label -n kuma-system meshtrafficpermission app-to-redis kuma.io/effect-

    Even though the old TrafficPermission and the new MeshTrafficPermission are both in use, the new policy takes precedence, making the old one ineffective.

  4. Check that the demo app behaves as expected. If everything goes well, we can safely remove TrafficPermissions:

    1. kubectl delete trafficpermissions --all

Timeout -> MeshTimeout

  1. Create a replacement policy for timeout-global Timeout and apply it with kuma.io/effect: shadow label:

    1. echo 'apiVersion: kuma.io/v1alpha1
    2. kind: MeshTimeout
    3. metadata:
    4. namespace: kuma-system
    5. name: timeout-global
    6. labels:
    7. kuma.io/mesh: default
    8. kuma.io/effect: shadow
    9. spec:
    10. targetRef:
    11. kind: Mesh
    12. to:
    13. - targetRef:
    14. kind: Mesh
    15. default:
    16. connectionTimeout: 21s
    17. idleTimeout: 22s
    18. http:
    19. requestTimeout: 23s
    20. streamIdleTimeout: 25s
    21. maxStreamDuration: 26s
    22. from:
    23. - targetRef:
    24. kind: Mesh
    25. default:
    26. connectionTimeout: 10s
    27. idleTimeout: 2h
    28. http:
    29. requestTimeout: 0s
    30. streamIdleTimeout: 2h' | kubectl apply -f-
  2. Check the list of changes for the redis_kuma-demo_svc_6379 kuma.io/service in Envoy configuration using kumactl, jq and jd:

    1. kumactl inspect dataplane ${DATAPLANE_NAME} --type=config --shadow --include=diff | jq '.diff' | jd -t patch2jd

    Expected output:

    1. @ ["type.googleapis.com/envoy.config.cluster.v3.Cluster","demo-app_kuma-demo_svc_5000","typedExtensionProtocolOptions","envoy.extensions.upstreams.http.v3.HttpProtocolOptions","commonHttpProtocolOptions","maxConnectionDuration"]
    2. + "0s"
    3. @ ["type.googleapis.com/envoy.config.listener.v3.Listener","outbound:10.43.146.6:5000","filterChains","0","filters","0","typedConfig","commonHttpProtocolOptions","idleTimeout"]
    4. - "22s"
    5. @ ["type.googleapis.com/envoy.config.listener.v3.Listener","outbound:10.43.146.6:5000","filterChains","0","filters","0","typedConfig","commonHttpProtocolOptions","idleTimeout"]
    6. + "0s"
    7. @ ["type.googleapis.com/envoy.config.listener.v3.Listener","outbound:10.43.146.6:5000","filterChains","0","filters","0","typedConfig","routeConfig","virtualHosts","0","routes","0","route","idleTimeout"]
    8. + "25s"
    9. @ ["type.googleapis.com/envoy.config.listener.v3.Listener","outbound:10.43.146.6:5000","filterChains","0","filters","0","typedConfig","requestHeadersTimeout"]
    10. + "0s"

    Review the list and ensure the new MeshTimeout policy won’t change the important settings. The key differences between old and new timeout policies:

    • Previously, there was no way to specify requestHeadersTimeout, maxConnectionDuration and maxStreamDuration (on inbound). These timeouts were unset. With the new MeshTimeout policy we explicitly set them to 0s by default.
    • idleTimeout was configured both on the cluster and listener. MeshTimeout configures it only on the cluster.
    • route/idleTimeout is duplicated value of streamIdleTimeout but per-route. Previously we’ve set it only per-listener.

    These 3 facts perfectly explain the list of changes we’re observing.

  3. Remove the kuma.io/effect: shadow label.

    1. kubectl label -n kuma-system meshtimeout timeout-global kuma.io/effect-

    Even though the old Timeout and the new MeshTimeout are both in use, the new policy takes precedence, making the old one ineffective.

  4. Check that the demo app behaves as expected. If everything goes well, we can safely remove Timeouts:

    1. kubectl delete timeouts --all

CircuitBreaker -> MeshCircuitBreaker

  1. Create a replacement policy for cb-global CircutBreaker and apply it with kuma.io/effect: shadow label:

    1. echo 'apiVersion: kuma.io/v1alpha1
    2. kind: MeshCircuitBreaker
    3. metadata:
    4. namespace: kuma-system
    5. name: cb-global
    6. labels:
    7. kuma.io/mesh: default
    8. kuma.io/effect: shadow
    9. spec:
    10. targetRef:
    11. kind: Mesh
    12. to:
    13. - targetRef:
    14. kind: Mesh
    15. default:
    16. connectionLimits:
    17. maxConnections: 24
    18. maxPendingRequests: 25
    19. maxRequests: 26
    20. maxRetries: 27
    21. outlierDetection:
    22. interval: 21s
    23. baseEjectionTime: 22s
    24. maxEjectionPercent: 23
    25. splitExternalAndLocalErrors: false
    26. detectors:
    27. totalFailures:
    28. consecutive: 28
    29. gatewayFailures:
    30. consecutive: 29
    31. localOriginFailures:
    32. consecutive: 30
    33. successRate:
    34. requestVolume: 31
    35. minimumHosts: 32
    36. standardDeviationFactor: "1.33"
    37. failurePercentage:
    38. requestVolume: 34
    39. minimumHosts: 35
    40. threshold: 36' | kubectl apply -f-
  2. Check the list of changes for the redis_kuma-demo_svc_6379 kuma.io/service in Envoy configuration using kumactl, jq and jd:

    1. kumactl inspect dataplane ${DATAPLANE_NAME} --type=config --shadow --include=diff | jq '.diff' | jd -t patch2jd

    The expected output is empty. CircuitBreaker and MeshCircuitBreaker configures Envoy in the exact similar way.

  3. Remove the kuma.io/effect: shadow label.

    1. kubectl label -n kuma-system meshcircuitbreaker cb-global kuma.io/effect-

    Even though the old CircuitBreaker and the new MeshCircuitBreaker are both in use, the new policy takes precedence, making the old one ineffective.

  4. Check that the demo app behaves as expected. If everything goes well, we can safely remove CircuitBreakers:

    1. kubectl delete circuitbreakers --all

TrafficRoute -> MeshTCPRoute

It’s safe to simply remove route-all-default TrafficRoute. Traffic will flow through the system even if there are neither TrafficRoutes nor MeshTCPRoutes/MeshHTTPRoutes.

MeshGatewayRoute -> MeshHTTPRoute/MeshTCPRoute

The biggest change is that there are now 2 protocol specific routes, one for TCP and one for HTTP. MeshHTTPRoute always takes precedence over MeshTCPRoute if both exist.

Otherwise the high-level structure of the routes hasn’t changed, though there are a number of details to consider. Some enum values and some field structures were updated, largely to reflect Gateway API.

Please first read the MeshGatewayRoute docs, the MeshHTTPRoute docs and the MeshTCPRoute docs. Always refer to the spec to ensure your new resource is valid.

Note that MeshHTTPRoute has precedence over MeshGatewayRoute.

We’re going to start with a gateway and simple legacy MeshGatewayRoute, look at how to migrate MeshGatewayRoutes in general and then finish with migrating our example MeshGatewayRoute.

Let’s start with the following MeshGateway and MeshGatewayInstance:

  1. echo "---
  2. apiVersion: kuma.io/v1alpha1
  3. kind: MeshGateway
  4. mesh: default
  5. metadata:
  6. name: demo-app
  7. labels:
  8. kuma.io/origin: zone
  9. spec:
  10. conf:
  11. listeners:
  12. - port: 80
  13. protocol: HTTP
  14. tags:
  15. port: http-80
  16. selectors:
  17. - match:
  18. kuma.io/service: demo-app-gateway_kuma-demo_svc
  19. ---
  20. apiVersion: kuma.io/v1alpha1
  21. kind: MeshGatewayInstance
  22. metadata:
  23. name: demo-app-gateway
  24. namespace: kuma-demo
  25. spec:
  26. replicas: 1
  27. serviceType: LoadBalancer" | kubectl apply -f-

and the following initial MeshGatewayRoute:

  1. echo "apiVersion: kuma.io/v1alpha1
  2. kind: MeshGatewayRoute
  3. mesh: default
  4. metadata:
  5. name: demo-app-gateway
  6. spec:
  7. conf:
  8. http:
  9. hostnames:
  10. - example.com
  11. rules:
  12. - matches:
  13. - path:
  14. match: PREFIX
  15. value: /
  16. backends:
  17. - destination:
  18. kuma.io/service: demo-app_kuma-demo_svc_5000
  19. weight: 1
  20. selectors:
  21. - match:
  22. kuma.io/service: demo-app-gateway_kuma-demo_svc" | kubectl apply -f-

Targeting

The main consideration is specifying which gateways are affected by the route. The most important change is that instead of solely using tags to select MeshGateway listeners, new routes target MeshGateways by name and optionally with tags for specific listeners.

So in our example:

  1. spec:
  2. selectors:
  3. - match:
  4. kuma.io/service: demo-app-gateway_kuma-demo_svc
  5. port: http-80

becomes:

  1. spec:
  2. targetRef:
  3. kind: MeshGateway
  4. name: demo-app
  5. tags:
  6. port: http-80
  7. to:

because we’re now using the name of the MeshGateway instead of the kuma.io/service it matches.

Spec

As with all new policies, the spec is now merged under a default field. MeshTCPRoute is very simple, so the rest of this is focused on MeshHTTPRoute.

Note that for MeshHTTPRoute the hostnames are directly under the to entry:

  1. conf:
  2. http:
  3. hostnames:
  4. - example.com
  5. # ...

becomes:

  1. to:
  2. - targetRef:
  3. kind: Mesh
  4. hostnames:
  5. - example.com
  6. # ...
Matching

Matching works the same as before. Remember that for MeshHTTPRoute that merging is done on a match basis. So it’s possible for one route to define filters and another backendRefs for a given match, and the resulting rule would both apply the filters and route to the backends.

Given two routes, one with:

  1. to:
  2. rules:
  3. - matches:
  4. - path:
  5. match: PathPrefix
  6. value: /
  7. default:
  8. filters:
  9. - type: RequestHeaderModifier
  10. requestHeaderModifier:
  11. set:
  12. - name: x-custom-header
  13. value: xyz

and the other:

  1. to:
  2. rules:
  3. - matches:
  4. - path:
  5. match: PathPrefix
  6. value: /
  7. default:
  8. backendRefs:
  9. - kind: MeshService
  10. name: backend
  11. namespace: kuma-demo
  12. port: 3001

Traffic to / would have the x-custom-header added and be sent to the backend.

Filters

Every MeshGatewayRoute filter has an equivalent in MeshHTTPRoute. Consult the documentation for both resources to find out how each filter looks in MeshHTTPRoute.

Backends

Backends are similar except that instead of targeting with tags, the targetRef structure with kind: MeshService/kind: MeshServiceSubset is used.

Equivalent MeshHTTPRoute

So all in all we have:

  1. Create the equivalent MeshHTTPRoute

    1. echo "apiVersion: kuma.io/v1alpha1
    2. kind: MeshHTTPRoute
    3. metadata:
    4. name: demo-app
    5. namespace: kuma-system
    6. labels:
    7. kuma.io/origin: zone
    8. kuma.io/mesh: default
    9. spec:
    10. targetRef:
    11. kind: MeshGateway
    12. name: demo-app
    13. to:
    14. - targetRef:
    15. kind: Mesh
    16. hostnames:
    17. - example.com
    18. rules:
    19. - default:
    20. backendRefs:
    21. - kind: MeshService
    22. name: demo-app_kuma-demo_svc_5000
    23. matches:
    24. - path:
    25. type: PathPrefix
    26. value: /" | kubectl apply -f -
  2. Check that traffic is still working.

  3. Delete the previous MeshGatewayRoute:

    1. kubectl delete meshgatewayroute --all

Next steps