Skip to content

Commit

Permalink
Merge pull request #1038 from openziti/ha-docs-operations
Browse files Browse the repository at this point in the history
Add controller clustering operations page.
  • Loading branch information
plorenz authored Feb 6, 2025
2 parents ad90580 + 227c64a commit b2a25fa
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 43 deletions.
20 changes: 7 additions & 13 deletions docusaurus/docs/reference/30-configuration/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The controller configuration file has several top level configuration sections t
related configuration settings.

- [`ctrl`](#ctrl) - define control channel listener
- [`cluster`](#cluster) - allows configuring the controller in an controller cluster
- [`db`](#db) - specifies database file location
- [`edge`](#edge) - configures edge specific functionality
- [`events`](#events) - allows configuration of event output
Expand All @@ -23,7 +24,6 @@ related configuration settings.
listening, and CA bundles
- [`network`](#network) - set network level cost values
- [`profile`](#profile) - enables profiling of controller memory and CPU statistics
- [`raft`](#raft) - allows configuring the controller in an HA cluster
- [`trace`](#trace) - adds a peek handler to all controller messaging for debug purposes
- [`web`](#web) - configures API presentation exposure
- [`v`](#v) - A special section to note the version of the configuration file, only `v: 3` is
Expand All @@ -32,13 +32,13 @@ related configuration settings.
The standard OpenZiti experience minimally requires the following sections:

- `ctrl`
- `db` or `raft`
- `db` or `cluster`
- `identity`
- `edge`
- `web`
- `v`

Of those values, to start the controller only the `ctrl`, `db` or `raft`, `v`, and `identity`
Of those values, to start the controller only the `ctrl`, `db` or `cluster`, `v`, and `identity`
sections are required. However, not including the `edge` section will start the controller in "
fabric only" mode and will not support any edge functionality or concepts (identities, JWT
enrollment, 3rd Party CAs, policies, etc.). Not including the `web` section will result in none of
Expand Down Expand Up @@ -91,7 +91,7 @@ This includes the protocol(s) used for router connections and how those connecti
See [addressing](./conventions.md#addressing).
- `options` - a set of option which includes the below options and those defined
in [channel options](./conventions.md#channel)
- `advertiseAddress` - (required when raft is enabled) - configures the address at which this
- `advertiseAddress` - (required when controller clustering is enabled) - configures the address at which this
controller should be reachable by other controllers in the cluster
- `newListener` - (optional) an `<protocol>:<interface>:<port>` address that is sent to routers
to indicate a controller address migration. Should only be specified when the new listener
Expand Down Expand Up @@ -473,12 +473,10 @@ profile:
intervalMs: 150000
```

### `raft`
### `cluster`

The raft section enables running multiple controllers in a cluster.
The cluster section enables running multiple controllers in a cluster.

- `initialMembers` - (optional) Only used when bootstrapping the cluster. List of initial clusters
members. Should only be set on one of the controllers in the cluster.
- `commandHandler` - (optional)
- `maxQueueSize` - (optional, 1000) max size of the queue for processing incoming raft log
entries
Expand Down Expand Up @@ -514,11 +512,7 @@ The raft section enables running multiple controllers in a cluster.
a cluster with no leader for a duration which exceeds this threshold.

```text
raft:
initialMembers:
- tls:127.0.0.1:6262
- tls:127.0.0.1:6363
- tls:127.0.0.1:6464
cluster:
commandHandler:
maxQueueSize: 1000
commitTimeout: 50ms
Expand Down
61 changes: 36 additions & 25 deletions docusaurus/docs/reference/ha/bootstrapping/certificates.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,60 @@ sidebar_position: 20
# Controller Certificates

For controllers to communicate and trust one another, they need certificates that have
been generated with the correct attribute and relationships.
been generated with the correct attributes and relationships.

## Requirements
## Glossary

1. The certificates must have a shared root of trust
2. The controller client and server certificates must contain a
[SPIFFE ID](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-id)
### SPIFFE ID

## Steps to Certificate Creation
There are many ways to set up certificates, so this will just cover a recommended configuration.
A SPIFFE ID is a specially formatted URI which is intended to be embedded in a certificates. Applications
use these identifiers to figure out the following about the applications connecting to them.

The primary thing to ensure is that controllers have a shared root of trust.
A standard way of generating certs would be as follows:
1. What organization the peer belongs to
1. What type of application the peer is
1. The application's unique identifier

1. Create a self-signed root CA
1. Create an intermediate CA for each controller
1. Issue a server cert using the intermediate CA for each controller
1. Issue a client cert using the intermediate CA for each controller
Controller certificates use SPIFFE IDs to allow the controllers to identify each during mTLS negotiation.

Note that controller server and client certs must contain a SPIFFE id of the form
See [SPIFFE IDs](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#spiffe-id) for more information.

```
spiffe://<trust domain>/controller/<controller id>
```
### Trust domain

A [trust domain](https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/#trust-domain)
is the part of a SPIFFE ID that indicates the organization that an identity belongs to.

So if your trust domain is `example.com` and your controller id is `ctrl1`, then your SPIFFE id
## Requirements

1. The certificates must have a shared root of trust
1. The controller client and server certificates must contain a SPIFFE ID.
1. The SPIFFE ID must be set as the only URI in the `X509v3 Subject Alternative Name` field in the
certificate.
1. The SPIFFE ID must have the following format: `spiffe://<trust domain>/controller/<controller id>`

So if the trust domain is `example.com` and the controller id is `ctrl1`, then the SPIFFE id
would be:

```
spiffe://example.com/controller/ctrl1
```

**SPIFFE ID Notes:**
## Steps to Certificate Creation
There are many ways to set up certificates, so this will just cover an example configuration.

* This ID must be set as the only URI in the `X509v3 Subject Alternative Name` field in the
certificate.
* These IDs are used to allow the controllers to identify each during the mTLS negotiation.
* The OpenZiti CLI supports creating SPIFFE IDs in your certs
* Use the `--trust-domain` flag when creating CAs
* Use the `--spiffe-id` flag when creating server or client certificates
The primary thing to ensure is that controllers have a shared root of trust.
One way of generating certs would be as follows:

1. Create a root CA
1. Create an intermediate CA for each controller
1. Issue a server cert using the intermediate CA for each controller
1. Issue a client cert using the intermediate CA for each controller

## Example

* The OpenZiti CLI supports creating SPIFFE IDs in certificates
* Use the `--trust-domain` flag when creating CAs
* Use the `--spiffe-id` flag when creating server or client certificates

Using the OpenZiti PKI tool, certificates for a three node cluster could be created as follows:

```bash
Expand Down
4 changes: 2 additions & 2 deletions docusaurus/docs/reference/ha/bootstrapping/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ be configured.

```yaml
ctrl:
listener: tls:0.0.0.0:6262
listener: tls:0.0.0.0:1280
options:
advertiseAddress: tls:ctrl1.ziti.example.com:6262
advertiseAddress: tls:ctrl1.ziti.example.com:1280
```

Finally, cluster-capable SDK clients use OIDC for authentication, so an OIDC endpoint must be configured.
Expand Down
6 changes: 3 additions & 3 deletions docusaurus/docs/reference/ha/bootstrapping/initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ and wait for initialization. While waiting it will periodically emit a message:

```buttonless title="Output"
[ 3.323] WARNING ziti/controller/server.(*Controller).checkEdgeInitialized: the
Ziti Edge has not been initialized, no default admin exists. Add this node to a
cluster using 'ziti agent cluster add tls:localhost:6262' against an existing
controller has not been initialized, no default admin exists. Add this node to a
cluster using 'ziti agent cluster add tls:ctrl1.ziti.example.com:1280' against an existing
cluster member, or if this is the bootstrap node, run 'ziti agent controller init'
to configure the default admin and bootstrap the cluster
```
Expand All @@ -28,7 +28,7 @@ As this is the first node in the cluster, there's no existing cluster to add it
To add the default administrator, run:

```
ziti agent controller init <admin username> <admin password> <admin identity name>
ziti agent cluster init <admin username> <admin password> <admin identity name>
```

This initializes an admin user that can be used to manage the network.
Expand Down
189 changes: 189 additions & 0 deletions docusaurus/docs/reference/ha/operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
sidebar_label: Operations
sidebar_position: 50
---

# Operating a Controller Cluster

## Cluster Management APIs

A cluster can be managed via the REST endpoint and via the IPC agent.

### REST Operations

The REST operations can be invoked remotely using the `ziti` CLI as long as the
the CLI is logged in.


```
$ ziti ops cluster
Controller cluster operations
Usage:
ziti ops cluster [flags]
ziti ops cluster [command]
Available Commands:
add add cluster member
list list cluster members and their status
remove remove cluster member
transfer-leadership transfer cluster leadership to another member
Flags:
-h, --help help for cluster
Use "ziti ops cluster [command] --help" for more information about a command.
```

### IPC Operations

The IPC versions can be invoked via the CLI and don't require any authentication,
but need to be run on the same machine as the controller, by a user with access
to the IPC pipe.

```
$ ziti agent cluster
Manage an HA controller cluster using the IPC agent
Usage:
ziti agent cluster [flags]
ziti agent cluster [command]
Available Commands:
add adds a node to the controller cluster
init Initializes a cluster with a default administrator
init-from-db Initializes a cluster using an existing database snapshot
list lists the nodes in the controller cluster
remove removes a node from the controller cluster
restore-from-db Restores a cluster to the state in the given database snapshot
transfer-leadership triggers a new leader election in the controller cluster, optionally selecting a new preferred leader
Flags:
-h, --help help for cluster
Use "ziti agent cluster [command] --help" for more information about a command.
```

## Growing the Cluster

After at least one controller is running as part of a cluster and initialized,
additional nodes may be added to the cluster. Additional nodes should be configured
similarly to the initial node, though advertise addresses will vary.

Assume a network where the first node has been initialized, and is available at `ctrl1.ziti.example.com:1280`.

If the second node is running at `ctrl2.ziti.example.com:1280`, then it can be added to the
cluster in one of two ways.

### From An Existing Node

From a node already in the cluster, in this case the initial node, the new node can be added as follows:

```
user@node1$ ziti agent cluster add tls:ctrl2.ziti.example.com:1280
```

### From A New Node

The new node, which is not yet part of the cluster, can also be directed to reach
out to an existing cluster node and request to be joined.

```
user@node2$ ziti agent cluser add tls:ctrl1.ziti.example.com:1280
```

## Shrinking the Cluster

From any node in the cluster, nodes can be removed as follows:

```
user@node1$ ziti agent cluster remove ctrl2
```

## Restoring from Backup

To restore from a database snapshot, use the following CLI command:

```
ziti agent controller restore-from-db /path/to/backup.db
```

As this is an agent command, it must be run on the same machine as the controller. The path
provided will be read by the controller process, not the CLI.

The controller will apply the snapshot and then terminate. All controllers in the cluster will
terminate and expect to be restarted. This is so in memory caches won't be out of sync with
the database which has changed.

## Snapshot Application and Restarts

If a controller is out of communcation for a while, it may receive a snapshot to apply, rather
than a stream of events.

If a controller receives a snapshot to apply after startup is complete, it will apply the snapshot and then
terminate. This assumes that there is a process manager to restart controller after it terminates.

This should only happen if a controller is connected to the cluster and then gets disconnected for
long enough that a snapshot is created while it's disconnected. Because applying a snapshot requires
replacing the underlying controller bolt DB, the easiest way to do that is restart. That way the
controller don't have to worry about replacing the bolt DB underneath a running system.

## Events

All events now contain a `event_src_id` to indicate which controller emitted them.

There are some new events which are specific to clusters. See [Cluster Events](../events#cluster)
for more detail.

## Metrics

In an HA system, routers will send metrics to all controllers to which they are connected. There is
a new `doNotPropagate` flag in the metrics message, which will be set to false until the router has
successfully delivered the metrics message to a controller. The flag will then be set to true. So
the first controller to get the metrics message is expected to deliver the metrics message to the
events system for external integrators. The other controllers will have `doNotPropage` set to true,
and will only use the metrics message internally, to update routing data.

## Open Ports

Controllers now establish connections with each other, for two purposes.

1. Forwarding model updates to the leader, so they can be applied to the distributed journal
2. distributed journal communication

Both kinds of traffic flow over the same connection.

These connections do not require any extra open ports as the controller uses the control channel listener
to listen to both router and controller connections. As part of the connection process the
connection type is provided and the appropriate authentication and connection setup happens based on
the connection type. If no connection type is provided, it's assumed to be a router.

## System of Record

In a controller that's not configured for HA, the bolt database is the system of record. In an HA
setup, the distributed journal (managed via RAFT) is the system of record. The raft journal is
stored in two places, a snapshot directory and a bolt database of raft journal entries.

So a non-HA setup will have:

* ctrl.db

An HA setup will have:

* raft.db - the bolt database containing Raft journal entries
* snapshots/ - a directory containing Raft snapshots. Each snapshot is snapshot of the controller
bolt db
* ctrl.db - the controller bolt db, with the current state of the model

The location of all three is controlled by the [cluster/dataDir](../configuration/controller#cluster) config property.

```yaml
raft:
dataDir: /var/lib/ziti/controller/
```
When an HA controller starts up, it will first apply the newest snapshot, then any newer journal
entries that aren't yet contained in a snapshot. This means that an HA controller should start with
a blank DB that can be overwritten by snapshot and/or have journal entries applied to it. So an HA
controller will delete or rename the existing controller database and start with a fresh bolt db.

0 comments on commit b2a25fa

Please sign in to comment.