This page was generated from examples/wrappers/go/SeldonGoModel.ipynb.

Example Go Wrapper for Seldon Core

This notebook goes through the steps to test the Go wrapper. This is presently an example of how to wrap code to run in Go in Seldon.

Below shows example code for a REST and gRPC server in Go that handles the Seldon Core microservice API for MODELs.

[1]:
!pygmentize server.go
package main

import (
        "context"
        "flag"
        "fmt"
        "github.com/golang/protobuf/jsonpb"
        "github.com/gorilla/mux"
        "github.com/seldonio/seldon-core/examples/wrappers/go/pkg/api"
        "google.golang.org/grpc"
        "log"
        "net"
        "net/http"
        "os"
        "strconv"
)

var (
        port       = flag.Int("port", 10000, "The server port")
        serverType = flag.String("server_type", "grpc", "The type of server grpc or rest")
)

// One struct for each type of Seldon Server. Here we just create one for MODELs
type ModelServer struct {
}

// Example Predict call with SeldonMessage proto
func  (s *ModelServer) Predict(ctx context.Context, m *api.SeldonMessage) (*api.SeldonMessage, error){

        test := &api.SeldonMessage{
                Status: &api.Status{
                        Code:                 0,
                        Info:                 "",
                        Reason:               "",
                        Status:               0,

                },
                Meta: &api.Meta{
                        Puid:                 "",
                        Tags:                 nil,
                        Routing:              nil,
                        RequestPath:          nil,
                        Metrics:              nil,

                },
                DataOneof:            &api.SeldonMessage_Data{
                        Data: &api.DefaultData{
                                Names:                nil,
                                DataOneof:            &api.DefaultData_Tensor{
                                        Tensor: &api.Tensor{
                                                Shape:                []int32{1,1},
                                                Values:               []float64{1, 3},
                                                XXX_NoUnkeyedLiteral: struct{}{},
                                                XXX_unrecognized:     nil,
                                                XXX_sizecache:        0,
                                        },
                                },

                        },
                },

        }
        return test, nil
}

// Feedback template
func (s *ModelServer) SendFeedback(ctx context.Context, f *api.Feedback) (*api.SeldonMessage, error) {
        return &api.SeldonMessage{}, nil
}

func handleError(w http.ResponseWriter,msg string) {
        ma := jsonpb.Marshaler{}
        errJson := &api.SeldonMessage{
                Status: &api.Status{
                        Code:   400,
                        Info:   "Failed",
                        Reason: msg,
                        Status: 1,
                },
        }
        _ = ma.Marshal(w, errJson)
}

// REST predict call. Extract parameter and send to Proto version
func RestPredict(w http.ResponseWriter, r *http.Request) {
        log.Println("REST Predict called")
        ma := jsonpb.Marshaler{}
        sm := &api.SeldonMessage{}
        err := r.ParseForm()
        if err != nil {
                handleError(w,"Failed to parse request")
        }
        value := r.FormValue("json")
        if err := jsonpb.UnmarshalString(value, sm); err != nil {
                log.Println("Error converting JSON to proto:", err)
                handleError(w,"Failed to extract json from request")
                return
        }

        log.Printf("message is %v\n",sm)
        modelServer := &ModelServer{}
        sm_resp, _ := modelServer.Predict(r.Context(),sm)
        log.Printf("message is %v\n",sm_resp)
        _ = ma.Marshal(w, sm_resp)
}

// REST SendFeedback call. Extract parameters and send to Proto version.
func RestSendFeedback(w http.ResponseWriter, r *http.Request) {
        log.Println("REST SendFeedback called")
        fe := &api.Feedback{}
        err := r.ParseForm()
        if err != nil {
                handleError(w,"Failed to parse request")
        }
        value := r.FormValue("json")
        if err := jsonpb.UnmarshalString(value, fe); err != nil {
                log.Println("Error converting JSON to proto:", err)
                handleError(w,"Failed to extract json from request")
                return
        }
        log.Printf("message is %v\n",fe)
        modelServer := &ModelServer{}
        sm_resp, _ := modelServer.SendFeedback(r.Context(),fe)
        log.Printf("message is %v\n",sm_resp)
        ma := jsonpb.Marshaler{}
        _ = ma.Marshal(w, sm_resp)
}


/*
  Start gRPC or REST server
 */
func main() {
        flag.Parse()
        var portEnv = os.Getenv("PREDICTIVE_UNIT_SERVICE_PORT")
        if portEnv != "" {
                portVal,err := strconv.Atoi(portEnv)
                if err != nil {
                        log.Fatal("Bad env variable PREDICTIVE_UNIT_SERVICE_PORT")
                } else {
                        port = &portVal
                }
        }
        log.Printf("Server_type: %s\n",*serverType)
        log.Printf("Port: %d\n",*port)
        if *serverType == "rest" {
                log.Println("Starting REST Server")
                router := mux.NewRouter()
                router.HandleFunc("/predict", RestPredict).Methods("GET","POST")
                router.HandleFunc("/send-feedback", RestSendFeedback).Methods("GET","POST")
                log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), router))
        } else {
                log.Println("Starting gRPC Server")
                flag.Parse()
                lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
                if err != nil {
                        log.Fatalf("failed to listen: %v", err)
                }
                var opts []grpc.ServerOption
                grpcServer := grpc.NewServer(opts...)
                api.RegisterModelServer(grpcServer, &ModelServer{})
                grpcServer.Serve(lis)
        }


}

Local Docker Test

For this test we will use the code as is which returns a hard-wired result. For real use you should copy this Go project to build your own components.

[2]:
!make build_docker
docker build -t seldonio/gomodel:0.1 .
Sending build context to Docker daemon  486.9kB
Step 1/7 : FROM golang
latest: Pulling from library/golang

55d5a1d1: Pulling fs layer
80d00ae9: Pulling fs layer
b3117dca: Pulling fs layer
a19181b2: Pulling fs layer
759e848f: Pulling fs layer
9eaeed19: Pulling fs layer
Digest: sha256:5825f1549d8929cb8bc9525b372668105cf6517df5cd8c7f5b5e3a51673e0e07Downloading  67.18MB/127.9MB
Status: Downloaded newer image for golang:latest
 ---> 7ced090ee82e
Step 2/7 : RUN go get -u google.golang.org/grpc
 ---> Running in 54349cbf622c
Removing intermediate container 54349cbf622c
 ---> 870c6a74226b
Step 3/7 : RUN go get -u github.com/gorilla/mux
 ---> Running in 38b37c710bdf
Removing intermediate container 38b37c710bdf
 ---> e1d97f4a05a0
Step 4/7 : WORKDIR /go/src/github.com/seldonio/seldon-core/examples/wrappers/go
 ---> Running in 177cbd8d0228
Removing intermediate container 177cbd8d0228
 ---> 3cac5de40b35
Step 5/7 : COPY . .
 ---> 042fc193a467
Step 6/7 : RUN go build -o /server
 ---> Running in e367347e4512
Removing intermediate container e367347e4512
 ---> 954b75558834
Step 7/7 : ENTRYPOINT [ "sh", "-c", "/server --server_type ${SERVER_TYPE:-grpc}" ]
 ---> Running in ca5fab9640eb
Removing intermediate container ca5fab9640eb
 ---> a0a6577db0ec
Successfully built a0a6577db0ec
Successfully tagged seldonio/gomodel:0.1

Run a REST test

[3]:
!make test_docker_rest
docker run -d --name "gomodel" -p 10000:10000 -e SERVER_TYPE='rest' --rm seldonio/gomodel:0.1
ffe632b3eeaf06c3da6a3bd1ba00a1f036742554705d5b606a5f6338d7b4b818
[4]:
!seldon-core-tester contract.json 0.0.0.0 10000 -p
----------------------------------------
SENDING NEW REQUEST:

[[4.954]]
RECEIVED RESPONSE:
status {
}
meta {
}
data {
  tensor {
    shape: 1
    shape: 1
    values: 1.0
    values: 3.0
  }
}


[5]:
!docker rm -f gomodel
gomodel

Run a gRPC test

[6]:
!make test_docker_grpc
docker run -d --name "gomodel" -p 10000:10000 --rm seldonio/gomodel:0.1
be15ba3b8a729321063bb941be7501c7aec0e7d4f309fc9a95bb56481d18a616
[7]:
!seldon-core-tester contract.json 0.0.0.0 10000 -p --grpc --tensor
----------------------------------------
SENDING NEW REQUEST:

[[3.577]]
RECEIVED RESPONSE:
status {
}
meta {
}
data {
  tensor {
    shape: 1
    shape: 1
    values: 1.0
    values: 3.0
  }
}


[8]:
!docker rm -f gomodel
gomodel

Test in Minikube

[ ]:
!minikube start --memory 4096
[9]:
!kubectl create clusterrolebinding kube-system-cluster-admin --clusterrole=cluster-admin --serviceaccount=kube-system:default
clusterrolebinding.rbac.authorization.k8s.io/kube-system-cluster-admin created
[10]:
!helm init
$HELM_HOME has been configured at /home/clive/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
[11]:
!kubectl rollout status deploy/tiller-deploy -n kube-system
Waiting for deployment "tiller-deploy" rollout to finish: 0 of 1 updated replicas are available...
deployment "tiller-deploy" successfully rolled out
[12]:
!helm install ../../../helm-charts/seldon-core-operator --name seldon-core --set usageMetrics.enabled=true --namespace seldon-system
NAME:   seldon-core
LAST DEPLOYED: Thu May  9 15:47:55 2019
NAMESPACE: seldon-system
STATUS: DEPLOYED

RESOURCES:
==> v1/Pod(related)
NAME                                  READY  STATUS             RESTARTS  AGE
seldon-operator-controller-manager-0  0/1    ContainerCreating  0         0s

==> v1/Secret
NAME                                   TYPE    DATA  AGE
seldon-operator-webhook-server-secret  Opaque  0     0s

==> v1beta1/CustomResourceDefinition
NAME                                         AGE
seldondeployments.machinelearning.seldon.io  0s

==> v1/ClusterRole
seldon-operator-manager-role  0s

==> v1/ClusterRoleBinding
NAME                                 AGE
seldon-operator-manager-rolebinding  0s

==> v1/Service
NAME                                        TYPE       CLUSTER-IP     EXTERNAL-IP  PORT(S)  AGE
seldon-operator-controller-manager-service  ClusterIP  10.105.155.80  <none>       443/TCP  0s

==> v1/StatefulSet
NAME                                DESIRED  CURRENT  AGE
seldon-operator-controller-manager  1        1        0s


NOTES:
NOTES: TODO


[13]:
!kubectl rollout status statefulset.apps/seldon-operator-controller-manager -n seldon-system
Waiting for 1 pods to be ready...
partitioned roll out complete: 1 new pods have been updated...

Setup Ingress

Please note: There are reported gRPC issues with ambassador (see https://github.com/SeldonIO/seldon-core/issues/473).

[14]:
!helm install stable/ambassador --name ambassador --set crds.keep=false
NAME:   ambassador
LAST DEPLOYED: Thu May  9 15:48:08 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Pod(related)
NAME                         READY  STATUS             RESTARTS  AGE
ambassador-5b89d44544-2s79n  0/1    ContainerCreating  0         0s
ambassador-5b89d44544-fmvgk  0/1    ContainerCreating  0         0s
ambassador-5b89d44544-gmd8s  0/1    ContainerCreating  0         0s

==> v1/ServiceAccount
NAME        SECRETS  AGE
ambassador  1        0s

==> v1beta1/ClusterRole
NAME        AGE
ambassador  0s

==> v1beta1/ClusterRoleBinding
NAME        AGE
ambassador  0s

==> v1/Service
NAME               TYPE          CLUSTER-IP     EXTERNAL-IP  PORT(S)                     AGE
ambassador-admins  ClusterIP     10.111.86.228  <none>       8877/TCP                    0s
ambassador         LoadBalancer  10.99.124.251  <pending>    80:32260/TCP,443:31122/TCP  0s

==> v1/Deployment
NAME        DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
ambassador  3        3        3           0          0s


NOTES:
Congratuations! You've successfully installed Ambassador.

For help, visit our Slack at https://d6e.co/slack or view the documentation online at https://www.getambassador.io.

To get the IP address of Ambassador, run the following commands:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
     You can watch the status of by running 'kubectl get svc -w  --namespace default ambassador'

  On GKE/Azure:
  export SERVICE_IP=$(kubectl get svc --namespace default ambassador -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

  On AWS:
  export SERVICE_IP=$(kubectl get svc --namespace default ambassador -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

  echo http://$SERVICE_IP:

[15]:
!kubectl rollout status deployment.apps/ambassador
Waiting for deployment "ambassador" rollout to finish: 0 of 3 updated replicas are available...
Waiting for deployment "ambassador" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "ambassador" rollout to finish: 2 of 3 updated replicas are available...
deployment "ambassador" successfully rolled out
[16]:
!eval $(minikube docker-env) && make build_docker
docker build -t seldonio/gomodel:0.1 .
Sending build context to Docker daemon  543.7kB
Step 1/7 : FROM golang
latest: Pulling from library/golang

55d5a1d1: Pulling fs layer
80d00ae9: Pulling fs layer
b3117dca: Pulling fs layer
a19181b2: Pulling fs layer
759e848f: Pulling fs layer
9eaeed19: Pulling fs layer
Digest: sha256:5825f1549d8929cb8bc9525b372668105cf6517df5cd8c7f5b5e3a51673e0e07Downloading  35.83MB/45.34MB
Status: Downloaded newer image for golang:latest
 ---> 7ced090ee82e
Step 2/7 : RUN go get -u google.golang.org/grpc
 ---> Running in c47778d3b2ed
Removing intermediate container c47778d3b2ed
 ---> ca1dff8faf05
Step 3/7 : RUN go get -u github.com/gorilla/mux
 ---> Running in e8ab459e22ab
Removing intermediate container e8ab459e22ab
 ---> ff67a9ffe7c8
Step 4/7 : WORKDIR /go/src/github.com/seldonio/seldon-core/examples/wrappers/go
 ---> Running in b5bd80b58c56
Removing intermediate container b5bd80b58c56
 ---> 220974fdf4f9
Step 5/7 : COPY . .
 ---> 6cdc57523d88
Step 6/7 : RUN go build -o /server
 ---> Running in e17c70fe376c
Removing intermediate container e17c70fe376c
 ---> 10fd1ac9d773
Step 7/7 : ENTRYPOINT [ "sh", "-c", "/server --server_type ${SERVER_TYPE:-grpc}" ]
 ---> Running in c68e836ec343
Removing intermediate container c68e836ec343
 ---> ac119b5ba1bc
Successfully built ac119b5ba1bc
Successfully tagged seldonio/gomodel:0.1
[17]:
!kubectl create -f resources/deployment_example.json
seldondeployment.machinelearning.seldon.io/example-go created
[19]:
!kubectl rollout status deploy/go-deployment-go-predictor-27dfae4
Waiting for deployment "go-deployment-go-predictor-27dfae4" rollout to finish: 0 of 1 updated replicas are available...
deployment "go-deployment-go-predictor-27dfae4" successfully rolled out
[22]:
!seldon-core-api-tester contract.json `minikube ip` `kubectl get svc ambassador -o jsonpath='{.spec.ports[0].nodePort}'` \
    example-go --namespace default -p
----------------------------------------
SENDING NEW REQUEST:

[[7.961]]
RECEIVED RESPONSE:
status {
}
meta {
  puid: "p4vmdh0epbh4o4vtpu469s1ecc"
  requestPath {
    key: "gomodel"
    value: "seldonio/gomodel:0.1"
  }
}
data {
  tensor {
    shape: 1
    shape: 1
    values: 1.0
    values: 3.0
  }
}


[ ]:
!minikube delete