Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Versioning documentation #61

Merged
merged 6 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions docs/deployment-operations/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ sidebar_position: 2

# Deployment

This page contains some walkthroughs to deploy Restate and services.

## Deploying Restate

Restate is currently a single binary that contains everything you need.
It exposes three services by default, each on different ports:

Expand All @@ -19,7 +22,9 @@ It will store metadata and RocksDB data in the relative directory of /target und
It requires outbound connectivity to services in order to discover them and to send requests.

### In Kubernetes

The recommended Kubernetes deployment strategy is a one-replica StatefulSet. We recommend installing Restate in its own namespace.

```yaml
apiVersion: v1
kind: Namespace
Expand Down Expand Up @@ -98,16 +103,25 @@ spec:
```

You will also need to create an image pull secret using a classic github personal access token with the `read:packages` permission.

```bash
$ kubectl create secret docker-registry github --docker-server=ghcr.io --docker-username=<your-github-username> --docker-password=<your-personal-access-token>
```

## Deploying Services
Services in Restate are fairly similar to gRPC services. They speak HTTP2, and are always called *by* Restate;
they don't make any outbound requests as part of the Restate protocol.
## Deploying services

Restate services are deployed within *Service endpoints*. The Restate runtime interacts with service endpoints by sending requests to them using a custom protocol on top of HTTP.

Depending on the SDK support, a service endpoint can be deployed as a Lambda function, a Kubernetes pod, a Knative Service, or any other deployment environment where the service endpoint can be reached at a specific URL.
slinkydeveloper marked this conversation as resolved.
Show resolved Hide resolved

The URL (including path prefix) MUST be **unique**, meaning that no two service endpoints with the same URL can exist at the same time in a Restate instance.

Moreover, service endpoints are **immutable**, and are assumed to be reacheable throughout the entire lifecycle of an invocation. To deploy any change to a service, either in the Protobuf definition or in the business logic, you should deploy a new service endpoint with a new URL. See the [versioning documentation](./versioning.md) for more details on how to update services.

### Deploying service endpoints

They can be deployed like any gRPC service; a Deployment of more than one replica is generally appropriate. However,
like gRPC services, they must be appropriately load balanced at L7 if you want multiple service pods. Native Kubernetes
Service endpoints can be deployed like any gRPC service; a Deployment of more than one replica is generally appropriate. However,
like gRPC services, they must be appropriately load balanced at L7 if you want multiple service endpoint pods. Native Kubernetes
ClusterIP load balancing will lead to the Restate binary sending all requests to a single pod, as HTTP2 connections
are aggressively reused. This is fine for local testing, but in production an approach must be found. If your
infrastructure already has an approach for L7 load balancing gRPC services, you can use the same approach here.
Expand All @@ -121,6 +135,7 @@ Otherwise, some recommended approaches are detailed below:
| Any | Use an envoy sidecar on the restate pod; see below |

### Local Kubernetes development

A simple deployment setup (eg, for local use with Minikube) with a single pod in Kubernetes is as follows:

```yaml
Expand Down Expand Up @@ -161,6 +176,7 @@ spec:
L7 load balancing is not needed when there is only one pod, so it's acceptable to use a normal ClusterIP Service.

### Simple L7 load balancing with an envoy sidecar

A simple approach to L7 load balancing is to set up an Envoy sidecar in the Restate pod which acts as a transparent HTTP proxy
which will resolve and L7 load balance to Kubernetes Services based on their DNS name. For this to work, Services must be
deployed as Headless, ie without a ClusterIP. This is achieved by specifying `clusterIP: None` in a `type: ClusterIP` Service.
Expand Down Expand Up @@ -248,16 +264,26 @@ data:
dns_refresh_rate: 5s
```

## Discovering Services
Services are always called *by* Restate, and communicate with Restate only through their responses. As such, Restate acts as
an API gateway, and must know how to convert a service name like `greeter.Greeter` into the right endpoint to reach out to.
We inform Restate of this via 'discovery'; we give it the endpoint, and it will use reflection to determine the relevant
services, methods and request/response objects hosted there. This needs to be done whenever you add a new service, and
when you add new methods to existing services. Discovering a service again is always safe.
### Registering service endpoints

After deploying a service endpoint, in order to use it, it must be registered with Restate as follows:
slinkydeveloper marked this conversation as resolved.
Show resolved Hide resolved

```bash
$ curl <RESTATE_META_ENDPOINT>/endpoints --json '{"uri": "<SERVICE_ENDPOINT_URI>"}'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is --json supported by all curl version that we want to support?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know, see #31. The deployments page was using the --json parameter, so I did the same here. I can replace with the old command template, but TBH I think this is better to read.

Copy link
Contributor

@gvdongen gvdongen Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a solution is specifying that you need a curl version more recent than 7.82.0?
So people for who this fails, understand that they need to upgrade curl?
But I also think that there should be a single unified command across the docs for discovery.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a suggestion to specify the curl (>v7.82.0) requirement

```

When registering a service endpoint, Restate uses a mechanism similar to "reflections" to discover the available services and their schemas and properties. A service can be registered only once, and subsequent registration requests to the same service endpoint will fail. For more details on how to update services, check the [versioning documentation](./versioning.md).

The service endpoint creation can be forced to overwrite an existing endpoint using:
slinkydeveloper marked this conversation as resolved.
Show resolved Hide resolved

Discovery can be done with a simple HTTP request
```bash
$ curl restate:8081/endpoints --json '{"uri": "http://service:8080"}'
$ curl <RESTATE_META_ENDPOINT>/endpoints --json '{"uri": "<SERVICE_ENDPOINT_URI>", "force": true}'
```

This will forcefully overwrite the existing endpoint with the same uri, forcing the discovery process again. It will also remove services that were served by that endpoint and are not available anymore.

:::warning
Forcing an endpoint registration is a feature designed to simplify local Restate service development, and should never be used in a production Restate deployment, as it potentially breaks all the in-flight invocations to that endpoint.
:::

For more details on the API, refer to the [Meta operational API docs](./meta-rest-api.mdx#tag/service_endpoint/operation/create_service_endpoint).
2 changes: 1 addition & 1 deletion docs/deployment-operations/throubleshooting.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 6
---

# Throubleshooting
Expand Down
92 changes: 92 additions & 0 deletions docs/deployment-operations/versioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
sidebar_position: 2
---

# Versioning

Restate comes with different solutions to update the services, to simplify development and evolution of your codebase without sacrificing Restate's strong guarantees.

## Deploy a new service revision

As described in the [deployment documentation](./deployment.md#deploying-services), *service endpoints* are immutable, and are assumed to be reacheable throughout the entire lifecycle of an invocation. In order to deploy any change to a service, either in the protobuf definition and/or in the business logic, a new service endpoint should be deployed and registered.

When registering a new service endpoint, Restate will detect if it contains already registered services, and will treat them as new revisions. Any new invocations to that service will be executed by the newly registered service endpoint, thus guaranteeing that new invocations are always routed to the latest service revision, while *old* invocations will continue to use the previous service endpoint. It must be guaranteed that the old service endpoint lives until all the existing invocations complete.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restate will detect if it contains already registered services -> Based on what does it detect that it is a new revision?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Service name.


For example, let's assume there is a `greeter.Greeter` service deployed on the service endpoint available at `http://greeter-v1/`. To update it, deploy a new service endpoint available at `http://greeter-v2/`, containing the new revision of `greeter.Greeter`, and then register it:

```bash
$ curl <RESTATE_META_ENDPOINT>/endpoints --json '{"uri": "http://greeter-v2/"}'
```

This returns:

```json
{
"id": "Z3JlZXRlci12Mi8",
"services": [
{
"name": "greeter.Greeter",
"revision": 2
}
]
}
```

This notifies that Restate detected a new revision of an already existing service, and from now on it will route any invocation of `greeter.Greeter` to `http://greeter-v2/`, while any existing invocation to `greeter.Greeter`, e.g. an invocation sleeping for some time, will continue executing on `http://greeter-v1/` until the end.

To check which endpoint is currently serving new invocations of `greeter.Greeter`:

```bash
$ curl <RESTATE_META_ENDPOINT>/services/greeter.Greeter
```

This returns:

```json
{
"name": "greeter.Greeter",
"revision": 2,
"endpoint_id": "Z3JlZXRlci12Mi8",
[...]
}
```

To get more info about the endpoint serving it:

```bash
$ curl <RESTATE_META_ENDPOINT>/endpoints/Z3JlZXRlci12Mi8
```

```json
{
"id": "Z3JlZXRlci12Mi8",
"uri": "http://greeter-v2/",
"additional_headers": {},
"protocol_type": "BidiStream",
"services": [
{
"name": "greeter.Greeter",
"revision": 2
}
]
}
```

For more details on the API, refer to the [Meta operational API docs](./meta-rest-api.mdx#tag/service_endpoint/operation/create_service_endpoint).

## Updating the Protobuf service schema

Restate adopts the same rules as [Protobuf for schema evolution](https://protobuf.dev/programming-guides/dos-donts/), but adds on top the following constraints:

* You can't change the instance type of a service.
* You can't modify the key fields in Keyed services.
Comment on lines +81 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe link to the definition of instance types and keyed services.

Copy link
Contributor Author

@slinkydeveloper slinkydeveloper Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that there isn't a page dedicated to that, nor in general to our service contracts, see #60


In all the aforementioned cases, you should define a new service instead.

:::info
Currently Restate doesn't implement all the "schema incompatibility" checks, so you must make sure the applied changes are backward compatible.
:::

## State compatibility

When updating Keyed and Singleton services, the new revisions will continue to use the same state created by previous revisions. You must ensure state entries are evolved in a backward compatible way.