|
| 1 | +# Unison Cloud architecture |
| 2 | + |
| 3 | +At a high level, the system architecture for Unison Cloud looks like this: |
| 4 | + |
| 5 | +```mermaid |
| 6 | +flowchart LR |
| 7 | + dev("🧑💻 Developer<br/>ucm, @unison/cloud library") |
| 8 | + browser("🌐 Client <br/>browser, curl, external service, etc") |
| 9 | + internet("🌐 Public internet") |
| 10 | + subgraph data-plane["☁️ Data plane (your infra)"] |
| 11 | + reverse-proxy("Reverse proxy<br/>(optional)<br>Nginx, Caddy, etc") |
| 12 | + subgraph nimbus["Nimbus instance"] |
| 13 | + nimbus-public@{ shape: comment, label: "Public HTTP port" } |
| 14 | + nimbus-gossip@{ shape: brace-r, label: "Gossip port" } |
| 15 | + end |
| 16 | + other-nimbus-instances@{ shape: processes, label: "Other nimbus instances" } |
| 17 | + storage[("Storage (DynamoDB)")] |
| 18 | + blobstore[("Blobs (S3)")] |
| 19 | + secrets[("Secret store (Vault)")] |
| 20 | + outbound-proxy["Outbound proxy<br/>(optional)"] |
| 21 | + end |
| 22 | + control-plane("☁️ Control plane<br/>(Unison Computing infra)") |
| 23 | + dev -->|Cloud.run auth| control-plane |
| 24 | + dev -->|Cloud.run| reverse-proxy |
| 25 | + reverse-proxy --> nimbus-public |
| 26 | + browser -->|HTTP request to user-deployed service| reverse-proxy |
| 27 | + nimbus --> storage |
| 28 | + nimbus --> blobstore |
| 29 | + nimbus --> secrets |
| 30 | + nimbus --->|Persistent WebSocket| control-plane |
| 31 | + nimbus --> outbound-proxy --> internet |
| 32 | + nimbus-gossip <--> other-nimbus-instances |
| 33 | +``` |
| 34 | + |
| 35 | +The server side of Unison Cloud is divided between the [control plane](#control-plane) and the [data plane](#data-plane). The **control plane** is in charge of authentication/authorization and coordination, while the **data plane** runs user jobs/services and houses user data. |
| 36 | + |
| 37 | +To understand the connections between these components it may be helpful to see a diagram of the sequence of events when a developer deploys a Unison Cloud web service and then another user accesses it: |
| 38 | + |
| 39 | +```mermaid |
| 40 | +sequenceDiagram |
| 41 | + box rgba(128,128,128, 0.1) Control Plane<br/>#40;Unison Computing infra#41; |
| 42 | + participant cloud-api as cloud-api |
| 43 | + end |
| 44 | + actor dev as developer (via cloud client lib) |
| 45 | + box rgba(128,128,128, 0.1) Data Plane #40;your infra#41; |
| 46 | + participant nimbus |
| 47 | + end |
| 48 | + actor consumer as service consumer (browser, external service, etc) |
| 49 | + dev->>+cloud-api: deploy service to env `task-app-prod` (auth token) |
| 50 | + cloud-api->>dev: `task-app-prod` access confirmed (scoped token) |
| 51 | + dev->>+nimbus: Deploy service (scoped token, code) |
| 52 | + nimbus->>dev: Service deployed! (service URL) |
| 53 | + consumer->>nimbus: GET /s/task-app/tasks |
| 54 | + nimbus->>consumer: ["dishes", "laundry"] |
| 55 | +``` |
| 56 | + |
| 57 | +## Control plane |
| 58 | + |
| 59 | +The control plane is in charge of authentication/authorization and coordination of Unison Cloud clusters. It typically runs on centralized Unison Computing infrastructure (but contact us if you have other needs) and does not have access to the user/application secrets, code, or data that are housed in the data plane. |
| 60 | + |
| 61 | +Interactions with the control plane go through an HTTP API that you may see referred to as `cloud-api`. |
| 62 | + |
| 63 | +### Authentication and authorization |
| 64 | + |
| 65 | +The control plane manages two separate types of **authentication**: |
| 66 | + |
| 67 | +- Nimbus nodes requesting to join a cluster |
| 68 | +- Developers or CI/CD servers submitting jobs, deploying services, creating databases, etc. These requests typically come from the [cloud client][cloud-client]. |
| 69 | + |
| 70 | +While **authentication** uses long-lived credentials, **authorization** for job submissions, service deployments, etc is enabled via narrowly-scoped per-request tokens. |
| 71 | + |
| 72 | +See [the security guide][auth] for details about authentication and authorization. |
| 73 | + |
| 74 | +### Cluster membership and orchestration |
| 75 | + |
| 76 | +One of the primary functions of the control plane is to keep track of cluster membership. As each Nimbus node starts up, it establishes a persistent connection to the control plane, registering with a location ID and address (URI). The control plane then broadcasts a message to other connected Nimbus nodes to inform them about the new cluster member. It sends periodic health checks to each Nimbus instance and will alert other nodes if one disconnects or fails health checks. |
| 77 | + |
| 78 | +As the source of truth on cluster membership, the control plane also orchestrates cluster operations. For example it informs each node which [daemons][daemons] it should run. |
| 79 | + |
| 80 | +### Cluster events |
| 81 | + |
| 82 | +The control plane broadcasts messages to each connected Nimbus node to keep it updated on cluster events. In many cases these events are used to optimize/invalidate local caches. Events include: |
| 83 | + |
| 84 | +- members joining, leaving, or failing health checks (mentioned above) |
| 85 | +- [Environment][Environment] changes, such as a [Config][env-config] value changing |
| 86 | +- user service changes, such as a new implementation being assigned to a [service name][ServiceName] |
| 87 | + |
| 88 | +## Data plane |
| 89 | + |
| 90 | +### Nimbus |
| 91 | + |
| 92 | +Nimbus instances are the primary workers of a Unison Cloud cluster. They run user-submitted jobs (via [Cloud.submit][Cloud.submit]) and services (via [Cloud.deploy][Cloud.deploy]), supporting the [Remote ability][Remote] for distributed programs. |
| 93 | + |
| 94 | +Nimbus is itself implemented in Unison and greatly benefits from the Unison programming language's support for distributed computation: |
| 95 | + |
| 96 | +- Values and entire programs/services can be serialized and sent to another node to distribute computation. |
| 97 | +- Thanks to content-addressed code there are never runtime dependency conflicts or name collisions, even in a shared cluster. |
| 98 | +- User code is sandboxed with fine-grained control of which operations are permitted. |
| 99 | + |
| 100 | +Nimbus serves a few different types of requests: |
| 101 | + |
| 102 | +- Authenticated developer requests to run jobs, deploy services, and other functionality provided by the [cloud client][cloud-client]. These arrive via the public HTTP port. |
| 103 | +- Unauthorized requests to user-deployed services that arrive via the public HTTP port under dedicated `/s/` (service name) and `/h/` (service hash) endpoints. The underlying user-defined web service may implement its own authentication, but Nimbus itself considers these endpoints to be public. |
| 104 | +- Nimbus cluster peer requests that arrive via the gossip port. These are requests supporting distributed computation such as "run this program in a new thread", "cancel this thread", "atomically modify this mutable reference", and "read this promise and send me the result when it is available". |
| 105 | + |
| 106 | +For more details on Nimbus authentication and ports see the [security documentation][security]. |
| 107 | + |
| 108 | +### Transactional Storage |
| 109 | + |
| 110 | +The transactional storage provider of the data plane supports the [Storage][Storage] ability. Currently the only supported backend is [DynamoDB][DynamoDB]. |
| 111 | + |
| 112 | +### Blob store |
| 113 | + |
| 114 | +The blob store provider of the data plane supports the [Blobs][Blobs] ability. Currently blob stores with an [s3][s3]-compatible API are supported. |
| 115 | + |
| 116 | +### Secrets |
| 117 | + |
| 118 | +The secrets provider of the data plane supports the [Environment][Environment] and [Environment.Config][Environment.Config] abilities. Currently [Vault][vault] is supported, but we plan to support other secrets providers as requested. |
| 119 | + |
| 120 | +### Outbound proxy |
| 121 | + |
| 122 | +The data plane can optionally configure an outbound network proxy that will be used for all user [{Http}][Http] and [{Tcp}][Tcp] requests. This may be your existing corporate proxy or could be a custom [squid][squid] proxy that only lets user code connect to vetted domains/ports/IPs/etc. |
| 123 | + |
| 124 | +[auth]: security.md |
| 125 | +[Blobs]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/Blobs |
| 126 | +[cloud-client]: https://share.unison-lang.org/@unison/cloud |
| 127 | +[Cloud.deploy]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/terms/Cloud/deploy |
| 128 | +[Cloud.submit]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/terms/Cloud/submit |
| 129 | +[daemons]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/Daemon |
| 130 | +[DynamoDb]: https://docs.aws.amazon.com/dynamodb/ |
| 131 | +[Environment]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/Environment |
| 132 | +[Environment.Config]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/Environment/Config |
| 133 | +[env-config]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/Environment/Config |
| 134 | +[Http]: https://share.unison-lang.org/@unison/http/code/releases/5.0.2/latest/types/client/Http |
| 135 | +[Remote]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/Remote |
| 136 | +[security]: security.md |
| 137 | +[ServiceName]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/ServiceName |
| 138 | +[s3]: https://aws.amazon.com/s3/ |
| 139 | +[squid]: https://www.squid-cache.org/ |
| 140 | +[Storage]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/Storage |
| 141 | +[Tcp]: https://share.unison-lang.org/@unison/cloud/code/releases/21.2.0/latest/types/provisional/Remote/Tcp |
| 142 | +[vault]: https://www.hashicorp.com/en/products/vault |
0 commit comments