Custom Header Routing with Seldon and Ambassador

This notebook shows how you can deploy Seldon Deployments which can have custom routing via Ambassador’s custom header routing.

Setup Seldon Core

Use the setup notebook to Setup Cluster with Ambassador Ingress and Install Seldon Core. Instructions also online.

Launch main model

We will create a very simple Seldon Deployment with a dummy model image seldonio/mock_classifier:1.0. This deployment is named example.

[77]:
!pygmentize model.json
{
    "apiVersion": "machinelearning.seldon.io/v1alpha2",
    "kind": "SeldonDeployment",
    "metadata": {
        "labels": {
            "app": "seldon"
        },
        "name": "example"
    },
    "spec": {
        "name": "production-model",
        "predictors": [
            {
                "componentSpecs": [{
                    "spec": {
                        "containers": [
                            {
                                "image": "seldonio/mock_classifier:1.0",
                                "imagePullPolicy": "IfNotPresent",
                                "name": "classifier"
                            }
                        ],
                        "terminationGracePeriodSeconds": 1
                    }}
                                  ],
                "graph":
                {
                    "children": [],
                    "name": "classifier",
                    "type": "MODEL",
                    "endpoint": {
                        "type": "REST"
                    }},
                "name": "single",
                "replicas": 1
            }
        ]
    }
}
[78]:
!kubectl create -f model.json
seldondeployment.machinelearning.seldon.io/example created
[79]:
!kubectl rollout status deploy/production-model-single-7cd068f
Waiting for deployment "production-model-single-7cd068f" rollout to finish: 0 of 1 updated replicas are available...
deployment "production-model-single-7cd068f" successfully rolled out

Get predictions

[80]:
from seldon_core.seldon_client import SeldonClient
sc = SeldonClient(deployment_name="example",namespace="seldon")

REST Request

[82]:
r = sc.predict(gateway="ambassador",transport="rest")
print(r)
Success:True message:
Request:
data {
  tensor {
    shape: 1
    shape: 1
    values: 0.42309184687609724
  }
}

Response:
meta {
  puid: "kqkcl64r9958ifefobob8okm04"
  requestPath {
    key: "classifier"
    value: "seldonio/mock_classifier:1.0"
  }
}
data {
  names: "proba"
  tensor {
    shape: 1
    shape: 1
    values: 0.07630983833322628
  }
}

gRPC Request

[84]:
r = sc.predict(gateway="ambassador",transport="grpc")
print(r)
Success:True message:
Request:
data {
  tensor {
    shape: 1
    shape: 1
    values: 0.8309579138697003
  }
}

Response:
meta {
  puid: "b5iv5qucju945d956u75mprt8g"
  requestPath {
    key: "classifier"
    value: "seldonio/mock_classifier:1.0"
  }
}
data {
  names: "proba"
  tensor {
    shape: 1
    shape: 1
    values: 0.11049363426286103
  }
}

Launch Model with Custom Routing

We will now create a new graph for our Canary with a new model seldonio/mock_classifier_rest:1.1. To make it a canary of the original example deployment we add two annotations

"annotations": {
        "seldon.io/ambassador-header":"location:london"
        "seldon.io/ambassador-service-name":"example"
    },

The first annotation says we want to route traffic that has the header location:london. The second says we want to use example as our service endpoint rather than the default which would be our deployment name - in this case example-canary. This will ensure that this Ambassador setting will apply to the same prefix as the previous one.

[85]:
!pygmentize model_with_header.json
{
    "apiVersion": "machinelearning.seldon.io/v1alpha2",
    "kind": "SeldonDeployment",
    "metadata": {
        "labels": {
            "app": "seldon"
        },
        "name": "example-header"
    },
    "spec": {
        "name": "header-model",
        "annotations": {
            "seldon.io/ambassador-service-name":"example",
            "seldon.io/ambassador-header":"location: london"
        },
        "predictors": [
            {
                "componentSpecs": [{
                    "spec": {
                        "containers": [
                            {
                                "image": "seldonio/mock_classifier_rest:1.1",
                                "imagePullPolicy": "IfNotPresent",
                                "name": "classifier"
                            }
                        ],
                        "terminationGracePeriodSeconds": 1
                    }}
                                  ],
                "graph":
                {
                    "children": [],
                    "name": "classifier",
                    "type": "MODEL",
                    "endpoint": {
                        "type": "REST"
                    }},
                "name": "single",
                "replicas": 1
            }
        ]
    }
}
[86]:
!kubectl create -f model_with_header.json
seldondeployment.machinelearning.seldon.io/example-header created
[87]:
!kubectl rollout status deploy/header-model-single-4c8805f
Waiting for deployment "header-model-single-4c8805f" rollout to finish: 0 of 1 updated replicas are available...
deployment "header-model-single-4c8805f" successfully rolled out

Check a request without a header goes to the existing model.

[88]:
r = sc.predict(gateway="ambassador",transport="rest")
print(r)
Success:True message:
Request:
data {
  tensor {
    shape: 1
    shape: 1
    values: 0.032151336779780104
  }
}

Response:
meta {
  puid: "3q46rdp5b0m3voov8mvgqnr7la"
  requestPath {
    key: "classifier"
    value: "seldonio/mock_classifier:1.0"
  }
}
data {
  names: "proba"
  tensor {
    shape: 1
    shape: 1
    values: 0.05292435544025589
  }
}

Check a REST request with the required header gets routed to the new model.

[89]:
r = sc.predict(gateway="ambassador",transport="rest",headers={"location":"london"})
print(r)
Success:True message:
Request:
data {
  tensor {
    shape: 1
    shape: 1
    values: 0.8108996070586438
  }
}

Response:
meta {
  puid: "5d3ip3bt3p9eot6p4gsc0p9d03"
  requestPath {
    key: "classifier"
    value: "seldonio/mock_classifier_rest:1.1"
  }
}
data {
  names: "proba"
  tensor {
    shape: 1
    shape: 1
    values: 0.10853755587593925
  }
}

Now do the same checks with gRPC

[91]:
r = sc.predict(gateway="ambassador",transport="grpc")
print(r)
Success:True message:
Request:
data {
  tensor {
    shape: 1
    shape: 1
    values: 0.8700690803552685
  }
}

Response:
meta {
  puid: "nrlscoi13e7e51n2rjupcevfgi"
  requestPath {
    key: "classifier"
    value: "seldonio/mock_classifier:1.0"
  }
}
data {
  names: "proba"
  tensor {
    shape: 1
    shape: 1
    values: 0.11439662796728152
  }
}

[92]:
r = sc.predict(gateway="ambassador",transport="grpc",headers={"location":"london"})
print(r)
Success:True message:
Request:
data {
  tensor {
    shape: 1
    shape: 1
    values: 0.40093443781255356
  }
}

Response:
meta {
  puid: "q939kepl18d4qolshduvq5rlk8"
  requestPath {
    key: "classifier"
    value: "seldonio/mock_classifier_rest:1.1"
  }
}
data {
  names: "proba"
  tensor {
    shape: 1
    shape: 1
    values: 0.07476262520943905
  }
}

[ ]: