docs: add documentation for discovery chains

Fixes #6273
This commit is contained in:
R.B. Boyer 2019-08-07 14:55:04 -05:00 committed by R.B. Boyer
parent f3a46e5a48
commit e04395ae1a
7 changed files with 819 additions and 2 deletions

View File

@ -0,0 +1,567 @@
---
layout: api
page_title: Discovery Chain - HTTP API
sidebar_current: api-discovery-chain
description: |-
The /discovery-chain endpoints are for interacting with the discovery chain.
---
# Discovery Chain HTTP Endpoint <sup>beta</sup>
~> This is a low-level API primarily targeted at developers building external
[Connect proxy integrations](/docs/connect/proxies/integrate.html). Future
high-level proxy integration APIs may obviate the need for this API over time.
The `/discovery-chain` endpoint returns the compiled [discovery
chain](/docs/internals/discovery-chain.html) for a service.
This will fetch all related [configuration
entries](/docs/agent/config_entries.html) and render them into a form suitable
for use by a [connect proxy] (/docs/connect/proxies.html) implementation. This
is a key component of [L7 Traffic
Management](/docs/connect/l7-traffic-management.html).
## Read Compiled Discovery Chain
If overrides are needed they are passed as the JSON-encoded request body and
the `POST` method must be used, otherwise `GET` is sufficient.
| Method | Path | Produces |
| ------------------ | ---------------------------- | -------------------------- |
| `GET` | `/discovery-chain/:service` | `application/json` |
| `POST`<sup>1</sup> | `/discovery-chain/:service` | `application/json` |
<sup>1</sup> Both GET and POST are for **read** operations. GET requests do not
permit request bodies so a POST is required if override parameters are needed.
The table below shows this endpoint's support for
[blocking queries](/api/features/blocking.html),
[consistency modes](/api/features/consistency.html),
[agent caching](/api/features/caching.html), and
[required ACLs](/api/index.html#authentication).
| Blocking Queries | Consistency Modes | Agent Caching | ACL Required |
| ---------------- | ----------------- | -------------------- | -------------- |
| `YES` | `all` | `background refresh` | `service:read` |
### URL Parameters
- `service` `(string: <required>)` - Specifies the service to query when
compiling the discovery chain. This is provided as part of the URL.
- `compile-dc` `(string: "")` - Specifies the datacenter to use as the basis of
compilation. This will default to the datacenter of the agent being queried.
This is specified as part of the URL as a query parameter.
This value comes from an [upstream
configuration](/docs/connect/registration/service-registration.html#upstream-configuration-reference)
[`datacenter`](/docs/connect/registration/service-registration.html#datacenter)
parameter.
### POST Body Parameters
- `OverrideConnectTimeout` `(duration: 0s)` - Overrides the final [connect
timeout](/docs/agent/config-entries/service-resolver.html#connecttimeout) for
any service resolved in the compiled chain.
This value comes from the `connect_timeout_ms` key in an [upstream
configuration](/docs/connect/registration/service-registration.html#upstream-configuration-reference)
opaque
[`config`](/docs/connect/registration/service-registration.html#config-1)
parameter.
- `OverrideProtocol` `(string: "")` - Overrides the final
[protocol](/docs/agent/config-entries/service-defaults.html#protocol) used in
the compiled discovery chain.
If the chain ordinarily would be TCP and an L7 protocol is passed here the
chain will still not include Routers or Splitters. If the chain ordinarily
would be L7 and TCP is passed here the chain will not include Routers or
Splitters.
This value comes from the `protocol` key in an [upstream
configuration](/docs/connect/registration/service-registration.html#upstream-configuration-reference)
opaque
[`config`](/docs/connect/registration/service-registration.html#config-1)
parameter.
- `OverrideMeshGateway` `(MeshGatewayConfig: <optional>)` - Overrides the final
[mesh gateway configuration](/docs/connect/mesh_gateway.html#connect-proxy-configuration)
for this any service resolved in the compiled chain.
This value comes from either the [proxy
configuration](/docs/connect/registration/service-registration.html#complete-configuration-example)
[`mesh_gateway`](/docs/connect/registration/service-registration.html#mesh_gateway)
parameter or an [upstream
configuration](/docs/connect/registration/service-registration.html#upstream-configuration-reference)
[`mesh_gateway`](/docs/connect/registration/service-registration.html#mesh_gateway-1)
parameter. If both are present the value defined on the upstream is used.
- `Mode` `(string: "")` - One of `none`, `local`, or `remote`.
### Sample Compilations
Full documentation for the output fields is found on the [discovery chain
internals](http://localhost:4567/docs/internals/discovery-chain.html#compileddiscoverychain)
page.
#### Multi-Datacenter Failover
Config entries defined:
```hcl
kind = "service-resolver"
name = "web"
connect_timeout = "15s"
failover = {
"*" = {
datacenters = ["dc3", "dc4"]
}
}
```
Request:
```text
$ curl http://127.0.0.1:8500/v1/discovery-chain/web
```
Response:
```json
{
"Chain": {
"ServiceName": "web",
"Namespace": "default",
"Datacenter": "dc1",
"Protocol": "tcp",
"StartNode": "resolver:web.default.dc1",
"Nodes": {
"resolver:web.default.dc1": {
"Type": "resolver",
"Name": "web.default.dc1",
"Resolver": {
"ConnectTimeout": "15s",
"Target": "web.default.dc1",
"Failover": {
"Targets": [
"web.default.dc3",
"web.default.dc4"
]
}
}
}
},
"Targets": {
"web.default.dc1": {
"ID": "web.default.dc1",
"Service": "web",
"Namespace": "default",
"Datacenter": "dc1",
"MeshGateway": {},
"Subset": {},
"SNI": "web.default.dc1.internal.47e25151-6212-ba25-8b7e-81adbbbab461.consul",
"Name": "web.default.dc1.internal.47e25151-6212-ba25-8b7e-81adbbbab461.consul"
},
"web.default.dc3": {
"ID": "web.default.dc3",
"Service": "web",
"Namespace": "default",
"Datacenter": "dc3",
"MeshGateway": {},
"Subset": {},
"SNI": "web.default.dc3.internal.47e25151-6212-ba25-8b7e-81adbbbab461.consul",
"Name": "web.default.dc3.internal.47e25151-6212-ba25-8b7e-81adbbbab461.consul"
},
"web.default.dc4": {
"ID": "web.default.dc4",
"Service": "web",
"Namespace": "default",
"Datacenter": "dc4",
"MeshGateway": {},
"Subset": {},
"SNI": "web.default.dc4.internal.47e25151-6212-ba25-8b7e-81adbbbab461.consul",
"Name": "web.default.dc4.internal.47e25151-6212-ba25-8b7e-81adbbbab461.consul"
}
}
}
}
```
#### Datacenter Redirect with Overrides
Config entries defined:
```hcl
kind = "service-resolver"
name = "web"
redirect {
datacenter = "dc2"
}
```
Request:
```text
$ curl -X POST \
-d'
{
"OverrideConnectTimeout": "7s",
"OverrideProtocol": "grpc",
"OverrideMeshGateway": {
"Mode": "remote"
}
}
' http://127.0.0.1:8500/v1/discovery-chain/web
```
Response:
```json
{
"Chain": {
"ServiceName": "web",
"Namespace": "default",
"Datacenter": "dc1",
"CustomizationHash": "b94f529a",
"Protocol": "grpc",
"StartNode": "resolver:web.default.dc2",
"Nodes": {
"resolver:web.default.dc2": {
"Type": "resolver",
"Name": "web.default.dc2",
"Resolver": {
"ConnectTimeout": "7s",
"Target": "web.default.dc2"
}
}
},
"Targets": {
"web.default.dc2": {
"ID": "web.default.dc2",
"Service": "web",
"Namespace": "default",
"Datacenter": "dc2",
"MeshGateway": {
"Mode": "remote"
},
"Subset": {},
"SNI": "web.default.dc2.internal.59c17fd4-8dfa-f54a-ae71-855b26faf637.consul",
"Name": "web.default.dc2.internal.59c17fd4-8dfa-f54a-ae71-855b26faf637.consul"
}
}
}
}
```
#### Version Split For Alternate Datacenter
Config entries defined:
```hcl
kind = "service-resolver"
name = "web"
default_subset = "v1"
subsets = {
"v1" = {
filter = "Service.Meta.version == v1"
}
"v2" = {
filter = "Service.Meta.version == v2"
}
}
# ---------------------------
kind = "service-defaults"
name = "web"
protocol = "http"
# ---------------------------
kind = "service-splitter"
name = "web"
splits = [
{
weight = 90
service_subset = "v1"
},
{
weight = 10
service_subset = "v2"
},
]
```
Request:
```text
$ curl http://127.0.0.1:8500/v1/discovery-chain/web?compile-dc=dc2
```
Response:
```json
{
"Chain": {
"ServiceName": "web",
"Namespace": "default",
"Datacenter": "dc2",
"Protocol": "http",
"StartNode": "splitter:web",
"Nodes": {
"resolver:v1.web.default.dc2": {
"Type": "resolver",
"Name": "v1.web.default.dc2",
"Resolver": {
"ConnectTimeout": "5s",
"Target": "v1.web.default.dc2"
}
},
"resolver:v2.web.default.dc2": {
"Type": "resolver",
"Name": "v2.web.default.dc2",
"Resolver": {
"ConnectTimeout": "5s",
"Target": "v2.web.default.dc2"
}
},
"splitter:web": {
"Type": "splitter",
"Name": "web",
"Splits": [
{
"Weight": 90,
"NextNode": "resolver:v1.web.default.dc2"
},
{
"Weight": 10,
"NextNode": "resolver:v2.web.default.dc2"
}
]
}
},
"Targets": {
"v1.web.default.dc2": {
"ID": "v1.web.default.dc2",
"Service": "web",
"ServiceSubset": "v1",
"Namespace": "default",
"Datacenter": "dc2",
"MeshGateway": {},
"Subset": {
"Filter": "Service.Meta.version == v1"
},
"SNI": "v1.web.default.dc2.internal.6c9594ec-d798-28b9-d084-aa03e81cf078.consul",
"Name": "v1.web.default.dc2.internal.6c9594ec-d798-28b9-d084-aa03e81cf078.consul"
},
"v2.web.default.dc2": {
"ID": "v2.web.default.dc2",
"Service": "web",
"ServiceSubset": "v2",
"Namespace": "default",
"Datacenter": "dc2",
"MeshGateway": {},
"Subset": {
"Filter": "Service.Meta.version == v2"
},
"SNI": "v2.web.default.dc2.internal.6c9594ec-d798-28b9-d084-aa03e81cf078.consul",
"Name": "v2.web.default.dc2.internal.6c9594ec-d798-28b9-d084-aa03e81cf078.consul"
}
}
}
}
```
#### HTTP Path Routing
Config entries defined:
```hcl
kind = "service-resolver"
name = "web"
subsets = {
"canary" = {
filter = "Service.Meta.flavor == canary"
}
}
# ---------------------------
kind = "proxy-defaults"
name = "web"
config {
protocol = "http"
}
# ---------------------------
kind = "service-router"
name = "web"
routes = [
{
match {
http {
path_prefix = "/admin"
}
}
destination {
service = "admin"
prefix_rewrite = "/"
request_timeout = "15s"
}
},
{
match {
http {
header = [
{
name = "x-debug"
exact = "1"
},
]
}
}
destination {
service = "web"
service_subset = "canary"
num_retries = 5
retry_on_connect_failure = true
retry_on_status_codes = [401, 409]
}
},
]
```
Request:
```text
$ curl http://127.0.0.1:8500/v1/discovery-chain/web
```
Response:
```json
{
"Chain": {
"ServiceName": "web",
"Namespace": "default",
"Datacenter": "dc1",
"Protocol": "http",
"StartNode": "router:web",
"Nodes": {
"resolver:admin.default.dc1": {
"Type": "resolver",
"Name": "admin.default.dc1",
"Resolver": {
"ConnectTimeout": "5s",
"Default": true,
"Target": "admin.default.dc1"
}
},
"resolver:canary.web.default.dc1": {
"Type": "resolver",
"Name": "canary.web.default.dc1",
"Resolver": {
"ConnectTimeout": "5s",
"Target": "canary.web.default.dc1"
}
},
"resolver:web.default.dc1": {
"Type": "resolver",
"Name": "web.default.dc1",
"Resolver": {
"ConnectTimeout": "5s",
"Target": "web.default.dc1"
}
},
"router:web": {
"Type": "router",
"Name": "web",
"Routes": [
{
"Definition": {
"Match": {
"HTTP": {
"PathPrefix": "/admin"
}
},
"Destination": {
"RequestTimeout": "15s",
"Service": "admin",
"PrefixRewrite": "/"
}
},
"NextNode": "resolver:admin.default.dc1"
},
{
"Definition": {
"Match": {
"HTTP": {
"Header": [
{
"Name": "x-debug",
"Exact": "1"
}
]
}
},
"Destination": {
"Service": "web",
"ServiceSubset": "canary",
"NumRetries": 5,
"RetryOnConnectFailure": true,
"RetryOnStatusCodes": [
401,
409
]
}
},
"NextNode": "resolver:canary.web.default.dc1"
},
{
"Definition": {
"Match": {
"HTTP": {
"PathPrefix": "/"
}
},
"Destination": {
"Service": "web"
}
},
"NextNode": "resolver:web.default.dc1"
}
]
}
},
"Targets": {
"admin.default.dc1": {
"ID": "admin.default.dc1",
"Service": "admin",
"Namespace": "default",
"Datacenter": "dc1",
"MeshGateway": {},
"Subset": {},
"SNI": "admin.default.dc1.internal.fce8a058-0981-2c04-d23c-b7375af64ce8.consul",
"Name": "admin.default.dc1.internal.fce8a058-0981-2c04-d23c-b7375af64ce8.consul"
},
"canary.web.default.dc1": {
"ID": "canary.web.default.dc1",
"Service": "web",
"ServiceSubset": "canary",
"Namespace": "default",
"Datacenter": "dc1",
"MeshGateway": {},
"Subset": {
"Filter": "Service.Meta.flavor == canary"
},
"SNI": "canary.web.default.dc1.internal.fce8a058-0981-2c04-d23c-b7375af64ce8.consul",
"Name": "canary.web.default.dc1.internal.fce8a058-0981-2c04-d23c-b7375af64ce8.consul"
},
"web.default.dc1": {
"ID": "web.default.dc1",
"Service": "web",
"Namespace": "default",
"Datacenter": "dc1",
"MeshGateway": {},
"Subset": {},
"SNI": "web.default.dc1.internal.fce8a058-0981-2c04-d23c-b7375af64ce8.consul",
"Name": "web.default.dc1.internal.fce8a058-0981-2c04-d23c-b7375af64ce8.consul"
}
}
}
}
```

View File

@ -41,6 +41,11 @@ Protocol = "http"
- `Mode` `(string: "")` - One of `none`, `local`, or `remote`.
- `ExternalSNI` <sup>(beta)</sup> `(string: "")` - This is an optional setting
that allows for the TLS
[SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) value to be
changed to a non-connect value when federating with an external system.
## ACLs
Configuration entries may be protected by

View File

@ -53,6 +53,15 @@ root certificates from the
## Configuration Discovery
Any proxy can discover proxy configuration registered with a local service
instance using the [agent/service/:service_id
endpoint](/api/agent/service.html#get-service-configuration).
instance using the
[`/v1/agent/service/:service_id`](/api/agent/service.html#get-service-configuration)
API endpoint.
The [discovery chain](/docs/internals/discovery-chain.html) for each upstream
service should be fetched from the
[`/v1/discovery-chain/:service_id`](/api/discovery-chain.html) API endpoint.
For each [target](/docs/internals/discovery-chain.html#targets) in the
resulting discovery chain, a list of healthy endpoints can be fetched from the
[`/v1/health/connect/:service_id`](/api/health.html#list-nodes-for-connect-capable-service)
API endpoint.

View File

@ -0,0 +1,229 @@
---
layout: "docs"
page_title: "Discovery Chain"
sidebar_current: "docs-internals-discovery-chain"
description: |-
The service discovery process can be modeled as a "discovery chain" which passes through three distinct stages: routing, splitting, and resolution. Each of these stages is controlled by a set of configuration entries.
---
# Discovery Chain <sup>beta</sup>
~> This topic is part of a [low-level API](/api/discovery-chain.html)
primarily targeted at developers building external [Connect proxy
integrations](/docs/connect/proxies/integrate.html).
The service discovery process can be modeled as a "discovery chain" which
passes through three distinct stages: routing, splitting, and resolution. Each
of these stages is controlled by a set of [configuration
entries](/docs/agent/config_entries.html). By configuring different phases of
the discovery chain a user can control how proxy upstreams are ultimately
resolved to specific instances for load balancing.
-> **Note:** The discovery chain is currently only used to discover
[Connect](/docs/connect/index.html) proxy upstreams.
## Configuration
The configuration entries used in the discovery chain are designed to be simple
to read and modify for narrowly tailored changes, but at discovery-time the
various configuration entries interact in more complex ways. For example:
* If a [`service-resolver`](/docs/agent/config-entries/service-resolver.html)
is created with a [service
redirect](/docs/agent/config-entries/service-resolver.html#service) defined,
then all references made to the original service in any other configuration
entry is replaced with the redirect destination.
* If a [`service-resolver`](/docs/agent/config-entries/service-resolver.html)
is created with a [default
subset](/docs/agent/config-entries/service-resolver.html#defaultsubset)
defined then all references made to the original service in any other
configuration entry that did not specify a subset will be replaced with the
default.
* If a [`service-splitter`](/docs/agent/config-entries/service-splitter.html)
is created with a [service
split](/docs/agent/config-entries/service-splitter.html#splits), and the target service has its
own `service-splitter` then the overall effect is flattened and only a single
aggregate traffic split is ultimately configured in the proxy.
* [`service-resolver`](/docs/agent/config-entries/service-resolver.html)
redirect loops must be rejected as invalid.
* [`service-router`](/docs/agent/config-entries/service-router.html) and
[`service-splitter`](/docs/agent/config-entries/service-splitter.html)
configuration entries require an L7 compatible protocol be set for the
service via either a
[`service-defaults`](/docs/agent/config-entries/service-defaults.html) or
[`proxy-defaults`](/docs/agent/config-entries/proxy-defaults.html) config
entry. Violations must be rejected as invalid.
* If an [upstream
configuration](/docs/connect/registration/service-registration.html#upstream-configuration-reference)
[`datacenter`](/docs/connect/registration/service-registration.html#datacenter)
parameter is defined then any configuration entry that does not explicitly
refer to a desired datacenter should use that value from the upstream.
## Compilation
To correctly interpret a collection of configuration entries as a valid
discovery chain, we first compile them into a form more directly usable by the
layers responsible for configuring Connect sidecar proxies.
You can interact with the compiler directly using the [discovery-chain
API](/api/discovery-chain.html).
### Compilation Parameters
* **Service Name** - The service being discovered by name.
* **Datacenter** - The datacenter to use as the basis of compilation.
* **Overrides** - Discovery-time tweaks to apply when compiling. These should
be derived from either the
[proxy](/docs/connect/registration/service-registration.html#complete-configuration-example)
or
[upstream](/docs/connect/registration/service-registration.html#upstream-configuration-reference)
configurations if either are set.
### Compilation Results
The response is a single wrapped `CompiledDiscoveryChain` field:
```json
{
"Chain": {...<CompiledDiscoveryChain>...}
}
```
#### `CompiledDiscoveryChain`
The chain encodes a digraph of [nodes](#discoverygraphnode) and
[targets](#discoverytarget). Nodes are the compiled representation of various
discovery chain stages and targets are instructions on how to use the [health
API](/api/health.html#list-nodes-for-connect-capable-service) to retrieve
relevant service instance lists.
You should traverse the nodes starting with [`StartNode`](#startnode). The
nodes can be resolved by name using the [`Nodes`](#nodes) field. Targets can be
resolved by name using the [`Targets`](#targets) field.
- `ServiceName` `(string)` - The requested service.
- `Namespace` `(string)` - The requested namespace.
- `Datacenter` `(string)` - The requested datacenter.
- `CustomizationHash` `(string: <optional>)` - A unique hash of any overrides
that affected the compilation of the discovery chain.
If set, this value should be used to prefix/suffix any generated load
balancer data plane objects to avoid sharing customized and non-customized
versions.
- `Protocol` `(string)` - The overall protocol shared by everything in the
chain.
- `StartNode` `(string)` - The first key into the `Nodes` map that should be
followed when traversing the discovery chain.
- `Nodes` `(map<string|DiscoveryGraphNode>)` - All nodes available for traversal in
the chain keyed by a unique name. You can walk this by starting with
`StartNode`.
-> The names should be treated as opaque values and are only guaranteed to be
consistent within a single compilation.
- `Targets` `(map<string|DiscoveryTarget>)` - A list of all targets used in this chain.
-> The names should be treated as opaque values and are only guaranteed to be
consistent within a single compilation.
#### `DiscoveryGraphNode`
A single node in the compiled discovery chain.
- `Type` `(string)` - The type of the node. Valid values are: `router`,
`splitter`, and `resolver`.
- `Name` `(string)` - The unique name of the node.
- `Routes` `(array<DiscoveryRoute>)` - Only set for `Type:router`. List of routes to
render.
- `Definition` `(ServiceRoute)` - Relevant portion of underlying
`service-router`
[route](/docs/agent/config-entries/service-router.html#routes).
- `NextNode` `(string)` - The name of the next node in the chain in [`Nodes`](#nodes).
- `Splits` `(array<DiscoverySplit>)` - Only set for `Type:splitter`. List of traffic
splits.
- `Weight` `(float32)` - Copy of underlying `service-splitter`
[`weight`](/docs/agent/config-entries/service-splitter.html#weight) field.
- `NextNode` `(string)` - The name of the next node in the chain in [`Nodes`](#nodes).
- `Resolver` `(DiscoveryResolver: <optional>)` - Only set for `Type:resolver`. How
to resolve the service instances.
- `Default` `(bool)` - Set to true if no `service-resolver` config entry is
defined for this node and the default was synthesized.
- `ConnectTimeout` `(duration)` - Copy of the underlying `service-resolver`
[`ConnectTimeout`](/docs/agent/config-entries/service-resolver.html#connecttimeout)
field. If one is not defined the default of `5s` is returned.
- `Target` `(string)` - The name of the target to use found in [`Targets`](#targets).
- `Failover` `(DiscoveryFailover: <optional>)` - Compiled form of the
underlying `service-resolver`
[`Failover`](/docs/agent/config-entries/service-resolver.html#failover)
definition to use for this request.
- `Targets` `(array<string>)` - List of targets found in
[`Targets`](#targets) to failover to in order of preference.
#### `DiscoveryTarget`
- `ID` `(string)` - The unique name of this target.
- `Service` `(string)` - The service to query when resolving a list of service instances.
- `ServiceSubset` `(string: <optional>)` - The
[subset](/docs/agent/config-entries/service-resolver.html#service-subsets) of
the service to resolve.
- `Namespace` `(string)` - The namespace to use when resolving a list of service instances.
- `Datacenter` `(string)` - The datacenter to use when resolving a list of service instances.
- `Subset` `(ServiceResolverSubset)` - Copy of the underlying
`service-resolver`
[`Subsets`](/docs/agent/config-entries/service-resolver.html#subsets)
definition for this target.
- `Filter` `(string: "")` - The
[filter expression](/api/features/filtering.html) to be used for selecting
instances of the requested service. If empty all healthy instances are
returned.
- `OnlyPassing` `(bool: false)` - Specifies the behavior of the resolver's
health check interpretation. If this is set to false, instances with checks
in the passing as well as the warning states will be considered healthy. If
this is set to true, only instances with checks in the passing state will
be considered healthy.
- `MeshGateway` `(MeshGatewayConfig)` - The [mesh gateway
configuration](/docs/connect/mesh_gateway.html#connect-proxy-configuration)
to use when connecting to this target's service instances.
- `Mode` `(string: "")` - One of `none`, `local`, or `remote`.
- `External` `(bool: false)` - True if this target is outside of this consul cluster.
- `SNI` `(string)` - This value should be used as the
[SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) value when
connecting to this set of endpoints over TLS.
- `Name` `(string)` - The unique name for this target for use when generating
load balancer objects. This has a structure similar to [SNI](#sni), but will
not be affected by SNI customizations such as
[`ExternalSNI`](/docs/agent/config-entries/service-defaults.html#externalsni).

View File

@ -20,6 +20,7 @@ Please review the following documentation to understand how Consul works.
* [Sessions](/docs/internals/sessions.html)
* [Anti-Entropy](/docs/internals/anti-entropy.html)
* [Security Model](/docs/internals/security.html)
* [Discovery Chain <sup>beta</sup>](/docs/internals/discovery-chain.html)
You should also be familiar with [Jepsen testing](/docs/internals/jepsen.html), before deploying
a production datacenter.

View File

@ -81,6 +81,9 @@
<li<%= sidebar_current("api-coordinate") %>>
<a href="/api/coordinate.html">Coordinates</a>
</li>
<li<%= sidebar_current("api-discovery-chain") %>>
<a href="/api/discovery-chain.html">Discovery Chain <sup>(beta)</sup></a>
</li>
<li<%= sidebar_current("api-event") %>>
<a href="/api/event.html">Events</a>
</li>

View File

@ -62,6 +62,9 @@
<li<%= sidebar_current("docs-internals-jepsen") %>>
<a href="/docs/internals/jepsen.html">Jepsen Testing</a>
</li>
<li<%= sidebar_current("docs-internals-discovery-chain") %>>
<a href="/docs/internals/discovery-chain.html">Discovery Chain <sup>beta</sup></a>
</li>
</ul>
</li>