Skip to content

Commit

Permalink
Add custom image sample documentation (kubeflow#678)
Browse files Browse the repository at this point in the history
* Add documentation for custom sample

* Add TARGET env var

* Add requirements.txt and update README

* kfservingsdk example

* Add expected output

* Update model name
  • Loading branch information
rzgry authored Feb 24, 2020
1 parent 0ba7c64 commit e4ed2c6
Show file tree
Hide file tree
Showing 12 changed files with 1,316 additions and 4 deletions.
17 changes: 17 additions & 0 deletions docs/samples/custom/hello-world/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.7-slim

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY app.py requirements.txt ./

# Install production dependencies.
RUN pip install --no-cache-dir -r ./requirements.txt

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 app:app
69 changes: 69 additions & 0 deletions docs/samples/custom/hello-world/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Predict on a InferenceService using a Custom Image

## Setup

1. Your ~/.kube/config should point to a cluster with [KFServing installed](https://github.com/kubeflow/kfserving/blob/master/docs/DEVELOPER_GUIDE.md#deploy-kfserving).
2. Your cluster's Istio Ingress gateway must be network accessible.

## Build and push the sample Docker Image

The goal of custom image support is to allow users to bring their own wrapped model inside a container and serve it with KFServing. Please note that you will need to ensure that your container is also running a web server e.g. Flask to expose your model endpoints.

In this example we use Docker to build the sample python server into a container. To build and push with Docker Hub, run these commands replacing {username} with your Docker Hub username:

```
# Build the container on your local machine
docker build -t {username}/custom-sample .
# Push the container to docker registry
docker push {username}/custom-sample
```

## Create the InferenceService

In the `custom.yaml` file edit the container image and replace {username} with your Docker Hub username.

Apply the CRD

```
kubectl apply -f custom.yaml
```

Expected Output

```
$ inferenceservice.serving.kubeflow.org/custom-sample created
```

## Run a prediction

```
MODEL_NAME=custom-sample
CLUSTER_IP=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
SERVICE_HOSTNAME=$(kubectl get inferenceservice ${MODEL_NAME} -o jsonpath='{.status.url}' | cut -d "/" -f 3)
curl -v -H "Host: ${SERVICE_HOSTNAME}" http://$CLUSTER_IP/v1/models/${MODEL_NAME}:predict
```

Expected Output

```
* Trying 184.172.247.174...
* TCP_NODELAY set
* Connected to 184.172.247.174 (184.172.247.174) port 31380 (#0)
> GET /v1/models/custom-sample:predict HTTP/1.1
> Host: custom-sample.default.example.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 31
< content-type: text/html; charset=utf-8
< date: Thu, 13 Feb 2020 21:34:54 GMT
< server: istio-envoy
< x-envoy-upstream-service-time: 15
<
Hello Python KFServing Sample!
* Connection #0 to host 184.172.247.174 left intact
* Closing connection 0
```
15 changes: 15 additions & 0 deletions docs/samples/custom/hello-world/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os

from flask import Flask

app = Flask(__name__)


@app.route('/v1/models/custom-sample:predict')
def hello_world():
greeting_target = os.environ.get('GREETING_TARGET', 'World')
return 'Hello {}!\n'.format(greeting_target)


if __name__ == "__main__":
app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ kind: InferenceService
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: service-custom-sample
name: custom-sample
spec:
default:
predictor:
custom:
container:
image: seldonio/mock_classifier:1.0
image: {username}/custom-sample
env:
- name: PREDICTIVE_UNIT_SERVICE_PORT
value: "8080"
- name: GREETING_TARGET
value: "Python KFServing Sample"
2 changes: 2 additions & 0 deletions docs/samples/custom/hello-world/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask==1.1.1
gunicorn==20.0.4
15 changes: 15 additions & 0 deletions docs/samples/custom/kfserving-sdk/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.7-slim

ENV APP_HOME /app
WORKDIR $APP_HOME

# Install production dependencies.
COPY requirements.txt ./
RUN pip install --no-cache-dir -r ./requirements.txt

# Copy local code to container image
COPY model.py imagenet_classes.txt ./

CMD ["python", "model.py"]
73 changes: 73 additions & 0 deletions docs/samples/custom/kfserving-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Predict on a InferenceService using a KFServing Model Server

## Setup

1. Your ~/.kube/config should point to a cluster with [KFServing installed](https://github.com/kubeflow/kfserving/blob/master/docs/DEVELOPER_GUIDE.md#deploy-kfserving).
2. Your cluster's Istio Ingress gateway must be network accessible.

## Build and push the sample Docker Image

The goal of custom image support is to allow users to bring their own wrapped model inside a container and serve it with KFServing. Please note that you will need to ensure that your container is also running a web server e.g. Flask to expose your model endpoints.

In this example we use Docker to build the sample python server into a container. To build and push with Docker Hub, run these commands replacing {username} with your Docker Hub username:

```
# Build the container on your local machine
docker build -t {username}/kfserving-custom-model .
# Push the container to docker registry
docker push {username}/kfserving-custom-model
```

## Create the InferenceService

In the `custom.yaml` file edit the container image and replace {username} with your Docker Hub username.

Apply the CRD

```
kubectl apply -f custom.yaml
```

Expected Output

```
$ inferenceservice.serving.kubeflow.org/kfserving-custom-model created
```

## Run a prediction

```
MODEL_NAME=kfserving-custom-model
INPUT_PATH=@./input.json
CLUSTER_IP=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
SERVICE_HOSTNAME=$(kubectl get inferenceservice ${MODEL_NAME} -o jsonpath='{.status.url}' | cut -d "/" -f 3)
curl -v -H "Host: ${SERVICE_HOSTNAME}" http://${CLUSTER_IP}/v1/models/${MODEL_NAME}:predict -d $INPUT_PATH
```

Expected Output:
```
* Trying 169.47.250.204...
* TCP_NODELAY set
* Connected to 169.47.250.204 (169.47.250.204) port 80 (#0)
> POST /v1/models/kfserving-custom-model:predict HTTP/1.1
> Host: kfserving-custom-model.default.example.com
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 105318
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< content-length: 232
< content-type: text/html; charset=UTF-8
< date: Fri, 21 Feb 2020 20:19:37 GMT
< server: istio-envoy
< x-envoy-upstream-service-time: 258
<
* Connection #0 to host 169.47.250.204 left intact
{"predictions": {"Labrador retriever": 0.4158518612384796, "golden retriever": 0.1659165322780609, "Saluki, gazelle hound": 0.16286855936050415, "whippet": 0.028539149090647697, "Ibizan hound, Ibizan Podenco": 0.023924754932522774}}* Closing connection 0
```
12 changes: 12 additions & 0 deletions docs/samples/custom/kfserving-sdk/custom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: serving.kubeflow.org/v1alpha2
kind: InferenceService
metadata:
labels:
controller-tools.k8s.io: "1.0"
name: kfserving-custom-model
spec:
default:
predictor:
custom:
container:
image: {username}/kfserving-custom-model
Loading

0 comments on commit e4ed2c6

Please sign in to comment.