API Connect allows an Application to subscribe to one plan for a product. This is traditionally used to determine which rate limit they are allowed to subscribe to. A couple of years ago Chris wrote an article on how to use this for plan variable to route to specific endpoints. Istio provides the facility to route to different endpoints depending on header variables. This article shows how you can take the plan from a context variable and set it to a header to be picked up by Istio.

What is a Service Mesh and Istio?

Taken from a future publication In traditional applications, communication patterns are usually built into application code and service endpoint configuration is usually statically defined per environment.

As application componentisation grows and applications become more cloud-native, so does the number of components on the network. These components, often called services, typically expose APIs to be consumable by other services. Service-to-service communication in cloud native world is dramatically more complex than in traditional IT.

Therefore, there is a requirement for an additional infrastructure layer that helps manage communication between complex applications consisting of a large number of distinct services. Such a layer is usually called a service mesh.

It is important to note that a service mesh is not an overlay network. A service mesh simplifies and enhances how service communicate over the network provided by the underlying platform.

The aim of a service mesh is to abstract that complexity away from applications and their components, and to manage it at cloud-native infrastructure level. As such, a service mesh is a cloud-native infrastructure layer which handles communication between services, and allows reliable delivery of requests across services.

At a high level, a service mesh is responsible for:

  • providing efficient communication between services,
  • abstracting the mechanism for reliable request/response delivery from the application code,
  • allowing management of services, independently from their number and growth rate,
  • handling network failures,
  • providing visibility and control of the service-to-service communication.

More specifically, typical functional requirements for a service mesh are:

  • Service discovery
  • Service registry
  • Traffic management
  • Traffic encryption
  • Observability and traceability
  • Authentication and authorisation
  • Failure recovery

Often a service mesh also has more complex operational requirements, like A/B testing, canary rollouts, rate limiting, access control, and end-to-end encryption. From a non-functional point of view, all these capabilities are available to applications and their components without impacting the application code, so that developers can leverage them without having to instrument their code.

Finally, a typical building block of cloud-native infrastructure is a container orchestration platform, as Kuberentes. For this reason, it is expected from a service mesh to be able to interact natively with Kubernetes controllers and resources, and to enhance their functionality, when it comes to service-to-service communication.

Istio (https://istio.io) is one of the most popular technology implementations for a service mesh. Istio is a Kubernetes-compatible open platform for providing a uniform way to integrate microservices, manage traffic flow across microservices, enforce policies and aggregate telemetry data. In short, Istio allows to connect, secure, control, and observe microservices running on Kubernetes.

Writing the API and Product Logic

In order for this to work the keyword that Istio will look for will be the name as the name of the plan. Please note this is not the title.

Product Sample

In the example below plan1 will be picked up and sent to istio.

plan1:
  rate-limits:
    default:
      value: 100/1minute
  title: PLAN1
  description: First Plan
  approval: false

Complete sample available at the end of the document

API Sample

The API Logic is extract the plan value from the context variable, set it as a header and invoke the proxy. The core logic from the yaml is below.

- set-variable:
    version: 1.0.0
    title: set-variable
    actions:
      - value: $(plan.name)
        set: message.headers.X-PLAN
- proxy:
    title: proxy
    version: 1.0.0
    verb: GET
    target-url: $(target-url)/theInfo

And a screenshoot from the UI.

The complete sample is available at the end of this article.

Istio configuration

In your istio-enabled namespace, create a Kubernetes service, an Istio virtual service and two Istio destination rules, each pointing at the different Kubernetes deployments that you want to connect to.

The Kubernetes Service will have a label selector which we’ll use to point at the two deployments. The K8s Service will look like this, where the label selector is istio-plan.

# Kubernetes Service
apiVersion: v1
kind: Service
metadata:
  name: istio-plan-routing-svc
  labels:
    istio: istio-plan-routing
spec:
  type: ClusterIP
  ports:
  - port: < Port number for the service >
    targetPort: < Target port number for the service >
    protocol: TCP
    name: http-istio
  selector:
    istio: istio-plan

The Istio Virtual Service will point at the K8s service, and in addition will:

  • match the header in the API invocation, mapping the plan name, to the subset in the destination rules.
  • weight the traffic distribution across the two deployments. In this case 100% on one or the other.

# Virtual Service
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: istio-plan-routing-vs
spec:
  hosts:
  - "*"
  gateways:
  - < Istio Ingress Gateway >
  http:
  # Match sends ALL traffic with the PLAN1 header
  - match:
    - headers:
        X-PLAN:
          exact: PLAN1
    route:
    - destination:
        port:
          number: < Port number for the service >
        host: istio-plan-routing-svc
        subset: PLAN1
      weight: 100
    - destination:
        port:
          number: < Port number for the service >
        host: istio-plan-routing-svc
        subset: PLAN2
      weight: 0
    # Match sends ALL traffic with the PLAN2 header
  - match:
    - headers:
        X-PLAN:
          exact: PLAN2
    route:
    - destination:
        port:
          number: < Port number for the service >
        host: istio-plan-routing-svc
        subset: PLAN1
      weight: 0
    - destination:
        port:
          number: < Port number for the service >
        host: istio-plan-routing-svc
        subset: PLAN2
      weight: 100

The Destination Rules will:

  • reference to the K8s service.
  • create a subset pointing at one specific deployment.
  • Enforce encryption policies. E.g. Mutual TLS.

# Destination Rule 1
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: istio-plan-routing-dr1
spec:
  host: istio-plan-routing-svc
  subsets:
  - name: PLAN1
    labels:
      version: PLAN1
  trafficPolicy:
    tls:
      #mode: ISTIO_MUTUAL
      mode: < value to enable mutual TLS >

---
# Destination Rule 2
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: istio-plan-routing-dr2
spec:
  host: istio-plan-routing-svc
  subsets:
  - name: PLAN2
    labels:
      version: PLAN2
  trafficPolicy:
    tls:
      #mode: ISTIO_MUTUAL
      mode: < value to enable mutual TLS >

For all of it to work, make sure to label your deployments with:

  • the label for the K8s service to select them. E.g. istio-plan for both deployments.
  • the destination rule’s subset value. E.g. PLAN1 for one deployment, and PLAN2 for the other.

Samples

Product Sample


info:
  name: istoRoutingSample-product
  title: istoRoutingSample product
  version: 1.0.0
gateways:
  - datapower-gateway
plans:
  default-plan:
    rate-limits:
      default:
        value: 100/1hour
    title: Default Plan
    description: Default Plan
    approval: false
  plan1:
    rate-limits:
      default:
        value: 100/1minute
    title: PLAN1
    description: First Plan
    approval: false
  plan2:
    title: PLAN2
    description: Everything Else Plan
    approval: false
    rate-limits:
      default:
        value: 3/1minute
        hard-limit: true
    burst-limits: {}
apis:
  istoRoutingSample1.0.0:
    $ref: istoRoutingSample_1.0.0.yaml
visibility:
  view:
    type: public
    orgs: []
    enabled: true
  subscribe:
    type: authenticated
    orgs: []
    enabled: true
product: 1.0.0

API Sample

swagger: '2.0'
info:
  title: istoRoutingSample
  x-ibm-name: istoRoutingSample
  version: 1.0.0
schemes:
  - http
  - https
basePath: /IstoRoutingSample/v2
security:
  - clientID: []
securityDefinitions:
  clientID:
    type: apiKey
    in: header
    name: X-IBM-Client-Id
x-ibm-configuration:
  properties:
    target-url:
      value: 'https://ISTIOTargetHost0/simpleinfoapp/v1'
      description: URL of the proxy policy
      encoded: false
  cors:
    enabled: true
  gateway: datapower-gateway
  type: rest
  phase: realized
  enforced: true
  testable: true
  assembly:
    execute:
      - set-variable:
          version: 1.0.0
          title: set-variable
          actions:
            - value: $(plan.name)
              set: message.headers.X-PLAN
      - proxy:
          title: proxy
          version: 1.0.0
          verb: GET
          target-url: $(target-url)/theInfo
    catch:
      - errors: []
        execute: []
  application-authentication:
    certificate: false
definitions:
  env data model:
    type: object
    properties:
      inputVar:
        type: string
      targetEndPoint:
        type: string
      soapReqDest:
        type: string
paths:
  /theInfo:
    get:
      operationId: gettheInfo
      responses:
        '200':
          description: The operation was successful.
          schema:
            $ref: '#/definitions/env data model'
      produces:
        - application/json
      description: Retrieve theInfo
      parameters:
        - name: varName
          in: query
          type: string
          description: Name of the backend service

Demo Video