Serve#
Executor
s can be served - and remotely accessed - directly, without instantiating a Flow manually.
This is especially useful when debugging an Executor in a remote setting. It can also be used to run external/shared Executors to be used in multiple Flows.
There are different options for deploying and running a standalone Executor:
Run the Executor directly from Python with the
.serve()
class methodRun the static
to_kubernetes_yaml()
method to generate K8s deployment configuration filesRun the static
to_docker_compose_yaml()
method to generate a Docker Compose service file
Served vs. shared Executor
In Jina there are two ways of running standalone Executors: Served Executors and shared Executors.
A served Executor is launched by one of the following methods:
.serve()
,to_kubernetes_yaml()
, orto_docker_compose_yaml()
. It resides behind a Gateway and can thus be directly accessed by a Client. It can also be used as part of a Flow.A shared Executor is launched using the Jina CLI and does not sit behind a Gateway. It is intended to be used in one or more Flows. Because a shared Executor does not reside behind a Gataway, it cannot be directly accessed by a Client, but it requires fewer networking hops when used inside of a Flow.
Both served and shared Executors can be used as part of a Flow, by adding them as an external Executor.
Serve directly#
An Executor
can be served using the serve()
method:
from docarray import DocumentArray, Document
from jina import Executor, requests
class MyExec(Executor):
@requests
def foo(self, docs: DocumentArray, **kwargs):
docs[0] = 'executed MyExec' # custom logic goes here
MyExec.serve(port=12345)
from jina import Client, DocumentArray, Document
print(Client(port=12345).post(inputs=DocumentArray.empty(1), on='/foo').texts)
['executed MyExec']
Internally, the serve()
method creates and starts a Flow
. Therefore, it can take all associated parameters:
uses_with
, uses_metas
, uses_requests
are passed to the internal add()
call, stop_event
stops
the Executor, and **kwargs
is passed to the internal Flow()
initialisation call.
See Also
For more details on these arguments and the workings of a Flow, see the Flow section.
Serve via Kubernetes#
You can generate Kubernetes configuration files for your containerized Executor by using the static Executor.to_kubernetes_yaml()
method. This works like deploying a Flow in Kubernetes, because your Executor is wrapped automatically in a Flow and uses the very same deployment techniques.
from jina import Executor
Executor.to_kubernetes_yaml(
output_base_path='/tmp/config_out_folder',
port_expose=8080,
uses='jinaai+docker://jina-ai/DummyHubExecutor',
executor_type=Executor.StandaloneExecutorType.EXTERNAL,
)
kubectl apply -R -f /tmp/config_out_folder
The above example deploys the DummyHubExecutor
from Executor Hub into your Kubernetes cluster.
Hint
The Executor you use needs to be already containerized and stored in a registry accessible from your Kubernetes cluster. We recommend Executor Hub for this.
Serve via Docker Compose#
You can generate a Docker Compose service file for your containerized Executor by using the static to_docker_compose_yaml()
method. This works like running a Flow with Docker Compose, because your Executor is wrapped automatically in a Flow and uses the very same deployment techniques.
from jina import Executor
Executor.to_docker_compose_yaml(
output_path='/tmp/docker-compose.yml',
port_expose=8080,
uses='jinaai+docker://jina-ai/DummyHubExecutor',
)
docker-compose -f /tmp/docker-compose.yml up
The above example runs the DummyHubExecutor
from Executor Hub locally on your computer using Docker Compose.
Hint
The Executor you use needs to be already containerized and stored in an accessible registry. We recommend Executor Hub for this.
Reload Executor#
While developing your Executor, it can be useful to have the Executor be refreshed from the source code while you are working on it, without needing to restart the complete server.
For this you can use the Executor’s reload
argument so that it watches changes in the source code and ensures changes are applied live to the served Executor.
The Executor will keep track in changes inside the Executor source file, every file passed in py_modules
argument from add()
and all Python files in the folder (and its subfolders) where the Executor class is defined.
Caution
This feature aims to let developers iterate faster while developing or improving the Executor, but is not intended to be used in production.
Note
This feature requires watchfiles>=0.18 package to be installed.
To see how this works, let’s define an Executor in a file my_executor.py
:
from jina import Executor, requests
class MyExecutor(Executor):
@requests
def foo(self, docs, **kwargs):
for doc in docs:
doc.text = 'I am coming from the first version of MyExecutor'
Build a Flow and expose it:
import os
from jina import Flow
from my_executor import MyExecutor
os.environ['JINA_LOG_LEVEL'] = 'DEBUG'
f = Flow(port=12345).add(uses=MyExecutor, reload=True)
with f:
f.block()
You can see that the Executor is successfully serving:
from jina import Client, DocumentArray
c = Client(port=12345)
print(c.post(on='/', inputs=DocumentArray.empty(1))[0].text)
I am coming from the first version of MyExecutor
You can edit the Executor file and save the changes:
from jina import Executor, requests
class MyExecutor(Executor):
@requests
def foo(self, docs, **kwargs):
for doc in docs:
doc.text = 'I am coming from a new version of MyExecutor'
You should see in the logs of the serving Executor
INFO executor0/rep-0@11606 detected changes in: ['XXX/XXX/XXX/my_executor.py']. Refreshing the Executor
And after this, the Executor will start serving with the renewed code.
from jina import Client, DocumentArray
c = Client(port=12345)
print(c.post(on='/', inputs=DocumentArray.empty(1))[0].text)
'I am coming from a new version of MyExecutor'