Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Unix Sockets for HTTP Replication #15708

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
214b7a4
Fixup how the HTTPConnectionPool key is declared and comments around/…
realtyem May 25, 2023
b41435a
Drive by fix: remove the disambiguity of seeing 'master' instead of '…
realtyem May 26, 2023
ca21dce
Create new InstanceUNIXLocationConfig, and finish wiring up UNIX Sock…
realtyem May 26, 2023
e75b177
Changelog
realtyem Jun 2, 2023
0aeae16
Merge branch 'develop' into finish-unix-replication-series
realtyem Jun 2, 2023
7d03b2d
Adapt test_workers to handle Class rename
realtyem Jun 4, 2023
0d79903
[OPTIONAL REVERT] Change Type[Model] to TypeAlias as a Hack
realtyem Jun 4, 2023
7bc139a
Try writing some docs
realtyem Jun 4, 2023
acd56db
Experimental testing setup for Unix sockets with Complement.
realtyem Jun 4, 2023
e14422f
[REVERT THIS] Enable testing for the Complement CI, each test will us…
realtyem Jun 4, 2023
eb65470
Revert "Drive by fix: remove the disambiguity of seeing 'master' inst…
realtyem Jun 14, 2023
e234ce9
Apply suggestions from code review
realtyem Jun 14, 2023
6a5e705
Nit over formatting
realtyem Jun 14, 2023
ee55d2a
Add in error messages to unit test setup to discourage using Unix soc…
realtyem Jun 15, 2023
e3d4fb4
Unify various incarnations of main_public.sock(and main_private.sock)…
realtyem Jun 15, 2023
fe18b66
Add detail to docs about Synapse not creating a directory auto-magica…
realtyem Jun 15, 2023
9394941
Adjust TCP and UNIX to be initial caps instead of all caps(in the sco…
realtyem Jun 15, 2023
79f63fc
Try seeing what the doc renderer will do with 4 #'s for a subheading(…
realtyem Jun 15, 2023
c4646ee
Merge branch 'develop' into finish-unix-replication-series
realtyem Jun 15, 2023
9882e0a
Apply suggestions from code review
realtyem Jun 15, 2023
52f84a2
Fix merge derp that encompasses the TLS replication fix from 15746
realtyem Jun 16, 2023
86c5fd5
Fix lack of detail in contributing guide for how to use Unix socket f…
realtyem Jun 16, 2023
e091ea2
Try and fix comment in ReplicationAgent to be clearer about why not u…
realtyem Jun 16, 2023
4946cb0
Adjust changelog.d/15708.feature
realtyem Jun 16, 2023
0b1a943
Update docs/usage/configuration/config_documentation.md
realtyem Jun 16, 2023
39597fc
Update docs a bit more
realtyem Jun 20, 2023
6dbc402
Apply suggestions from code review
realtyem Jun 21, 2023
2f20dda
From review: wrap lines(and fix a comment consistency)
realtyem Jun 21, 2023
5de0ef1
Merge branch 'develop' into finish-unix-replication-series
realtyem Jun 21, 2023
85eba85
unindent entire block
realtyem Jun 21, 2023
4c9de6f
Revert to `Type[Model]` to keep type variable constrained; add type i…
reivilibre Jul 5, 2023
67c278a
Fix some nits and some lint needed to be burned off
realtyem Jul 5, 2023
a5d500f
Use more specific mypy ignores
reivilibre Jul 6, 2023
41d2aae
Merge branch 'develop' into finish-unix-replication-series
realtyem Jul 6, 2023
fdb07e8
Fixup for new proxyagent
realtyem Jul 6, 2023
cd1579b
Add Unix socket support to the proxyagent
realtyem Jul 6, 2023
4f079d8
[REVERT THIS] Hardwire testing the proxy agent into Complement tempor…
realtyem Jul 6, 2023
9f41f3e
Revert "[REVERT THIS] Hardwire testing the proxy agent into Complemen…
realtyem Jul 7, 2023
0ae6036
Revert "[REVERT THIS] Enable testing for the Complement CI, each test…
realtyem Jul 7, 2023
92c6c7b
Update version number of Synapse this was added in
realtyem Jul 7, 2023
cd047fe
Merge branch 'develop' into finish-unix-replication-series
realtyem Jul 7, 2023
517654f
Merge branch 'develop' into finish-unix-replication-series
MadLittleMods Jul 10, 2023
3789a4f
Swap getClientAddress().host call for the UNIXAddress compatible repl…
realtyem Jul 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Expand Up @@ -571,6 +571,7 @@ jobs:
env:
POSTGRES: ${{ (matrix.database == 'Postgres') && 1 || '' }}
WORKERS: ${{ (matrix.arrangement == 'workers') && 1 || '' }}
UNIX_SOCKETS: 1
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
name: Run Complement Tests

MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
cargo-test:
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
1 change: 1 addition & 0 deletions changelog.d/15708.feature
@@ -0,0 +1 @@
Add Unix Socket support for HTTP Replication Listeners. Contributed by Jason Little.
realtyem marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions docker/conf-workers/nginx.conf.j2
Expand Up @@ -35,7 +35,11 @@ server {

# Send all other traffic to the main process
location ~* ^(\\/_matrix|\\/_synapse) {
{% if using_unix_sockets %}
proxy_pass http://unix:/run/mainpublic.sock;
{% else %}
proxy_pass http://localhost:8080;
{% endif %}
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
Expand Down
3 changes: 3 additions & 0 deletions docker/conf-workers/shared.yaml.j2
Expand Up @@ -6,6 +6,9 @@
{% if enable_redis %}
redis:
enabled: true
{% if using_unix_sockets %}
path: /tmp/redis.sock
{% endif %}
{% endif %}

{% if appservice_registrations is not none %}
Expand Down
4 changes: 4 additions & 0 deletions docker/conf-workers/supervisord.conf.j2
Expand Up @@ -19,7 +19,11 @@ username=www-data
autorestart=true

[program:redis]
{% if using_unix_sockets %}
command=/usr/local/bin/prefix-log /usr/local/bin/redis-server --unixsocket /tmp/redis.sock
{% else %}
command=/usr/local/bin/prefix-log /usr/local/bin/redis-server
{% endif %}
priority=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
Expand Down
4 changes: 4 additions & 0 deletions docker/conf-workers/worker.yaml.j2
Expand Up @@ -8,7 +8,11 @@ worker_name: "{{ name }}"

worker_listeners:
- type: http
{% if using_unix_sockets %}
path: "/run/worker.{{ port }}"
{% else %}
port: {{ port }}
{% endif %}
{% if listener_resources %}
resources:
- names:
Expand Down
10 changes: 9 additions & 1 deletion docker/conf/homeserver.yaml
Expand Up @@ -36,12 +36,17 @@ listeners:

# Allow configuring in case we want to reverse proxy 8008
# using another process in the same container
{% if SYNAPSE_USE_UNIX_SOCKET %}
# Unix sockets don't care about TLS or IP addresses or ports
- path: '/run/mainpublic.sock'
type: http
{% else %}
- port: {{ SYNAPSE_HTTP_PORT or 8008 }}
tls: false
bind_addresses: ['::']
type: http
x_forwarded: false

{% endif %}
resources:
- names: [client]
compress: true
Expand All @@ -57,8 +62,11 @@ database:
user: "{{ POSTGRES_USER or "synapse" }}"
password: "{{ POSTGRES_PASSWORD }}"
database: "{{ POSTGRES_DB or "synapse" }}"
{% if not SYNAPSE_USE_UNIX_SOCKET %}
{# Fun fact: if left with no host or port, Synapse looks for the default Unix socket instead. #}
realtyem marked this conversation as resolved.
Show resolved Hide resolved
host: "{{ POSTGRES_HOST or "db" }}"
port: "{{ POSTGRES_PORT or "5432" }}"
{% endif %}
cp_min: 5
cp_max: 10
{% else %}
Expand Down
102 changes: 76 additions & 26 deletions docker/configure_workers_and_start.py
Expand Up @@ -74,6 +74,9 @@
MAIN_PROCESS_INSTANCE_NAME = "main"
MAIN_PROCESS_LOCALHOST_ADDRESS = "127.0.0.1"
MAIN_PROCESS_REPLICATION_PORT = 9093
# Obviously, these would only be used with the UNIX socket option
MAIN_PROCESS_UNIX_SOCKET_PUBLIC_PATH = "/run/mainpublic.sock"
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH = "/run/mainprivate.sock"

# A simple name used as a placeholder in the WORKERS_CONFIG below. This will be replaced
# during processing with the name of the worker.
Expand Down Expand Up @@ -408,11 +411,15 @@ def add_worker_roles_to_shared_config(
)

# Map of stream writer instance names to host/ports combos
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}

if os.environ.get("SYNAPSE_USE_UNIX_SOCKET", False):
instance_map[worker_name] = {
"path": f"/run/worker.{worker_port}",
}
else:
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}
# Update the list of stream writers. It's convenient that the name of the worker
# type is the same as the stream to write. Iterate over the whole list in case there
# is more than one.
Expand All @@ -424,10 +431,15 @@ def add_worker_roles_to_shared_config(

# Map of stream writer instance names to host/ports combos
# For now, all stream writers need http replication ports
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}
if os.environ.get("SYNAPSE_USE_UNIX_SOCKET", False):
instance_map[worker_name] = {
"path": f"/run/worker.{worker_port}",
}
else:
instance_map[worker_name] = {
"host": "localhost",
"port": worker_port,
}


def merge_worker_template_configs(
Expand Down Expand Up @@ -719,17 +731,29 @@ def generate_worker_files(
# Note that yaml cares about indentation, so care should be taken to insert lines
# into files at the correct indentation below.

# Convenience helper for if using unix sockets instead of host:port
using_unix_sockets = environ.get("SYNAPSE_USE_UNIX_SOCKET", False)
# First read the original config file and extract the listeners block. Then we'll
# add another listener for replication. Later we'll write out the result to the
# shared config file.
listeners = [
{
"port": MAIN_PROCESS_REPLICATION_PORT,
"bind_address": MAIN_PROCESS_LOCALHOST_ADDRESS,
"type": "http",
"resources": [{"names": ["replication"]}],
}
]
listeners: List[Any]
if using_unix_sockets:
listeners = [
{
"path": MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH,
"type": "http",
"resources": [{"names": ["replication"]}],
}
]
else:
listeners = [
{
"port": MAIN_PROCESS_REPLICATION_PORT,
"bind_address": MAIN_PROCESS_LOCALHOST_ADDRESS,
"type": "http",
"resources": [{"names": ["replication"]}],
}
]
with open(config_path) as file_stream:
original_config = yaml.safe_load(file_stream)
original_listeners = original_config.get("listeners")
Expand Down Expand Up @@ -770,7 +794,15 @@ def generate_worker_files(

# A list of internal endpoints to healthcheck, starting with the main process
# which exists even if no workers do.
healthcheck_urls = ["http://localhost:8080/health"]
# This list ends up being part of the command line to curl, which added unix socket
# support in version 7.40. The scheme and hostname are ignored, the path is not.
realtyem marked this conversation as resolved.
Show resolved Hide resolved
if using_unix_sockets:
healthcheck_urls = [
f"--unix-socket {MAIN_PROCESS_UNIX_SOCKET_PUBLIC_PATH} "
"http://localhost/health"
realtyem marked this conversation as resolved.
Show resolved Hide resolved
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
]
else:
healthcheck_urls = ["http://localhost:8080/health"]

# Get the set of all worker types that we have configured
all_worker_types_in_use = set(chain(*requested_worker_types.values()))
Expand Down Expand Up @@ -807,8 +839,12 @@ def generate_worker_files(
# given worker_type needs to stay assigned and not be replaced.
worker_config["shared_extra_conf"].update(shared_config)
shared_config = worker_config["shared_extra_conf"]

healthcheck_urls.append("http://localhost:%d/health" % (worker_port,))
if using_unix_sockets:
healthcheck_urls.append(
f"--unix-socket /run/worker.{worker_port} http://localhost/health"
)
else:
healthcheck_urls.append("http://localhost:%d/health" % (worker_port,))

# Update the shared config with sharding-related options if necessary
add_worker_roles_to_shared_config(
Expand All @@ -827,6 +863,7 @@ def generate_worker_files(
"/conf/workers/{name}.yaml".format(name=worker_name),
**worker_config,
worker_log_config_filepath=log_config_filepath,
using_unix_sockets=using_unix_sockets,
)

# Save this worker's port number to the correct nginx upstreams
Expand All @@ -847,8 +884,13 @@ def generate_worker_files(
nginx_upstream_config = ""
for upstream_worker_base_name, upstream_worker_ports in nginx_upstreams.items():
body = ""
for port in upstream_worker_ports:
body += f" server localhost:{port};\n"
if using_unix_sockets:
for port in upstream_worker_ports:
body += f" server unix:/run/worker.{port};\n"

else:
for port in upstream_worker_ports:
body += f" server localhost:{port};\n"

# Add to the list of configured upstreams
nginx_upstream_config += NGINX_UPSTREAM_CONFIG_BLOCK.format(
Expand Down Expand Up @@ -878,10 +920,15 @@ def generate_worker_files(
# If there are workers, add the main process to the instance_map too.
if workers_in_use:
instance_map = shared_config.setdefault("instance_map", {})
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
"host": MAIN_PROCESS_LOCALHOST_ADDRESS,
"port": MAIN_PROCESS_REPLICATION_PORT,
}
if using_unix_sockets:
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
"path": MAIN_PROCESS_UNIX_SOCKET_PRIVATE_PATH,
}
else:
instance_map[MAIN_PROCESS_INSTANCE_NAME] = {
"host": MAIN_PROCESS_LOCALHOST_ADDRESS,
"port": MAIN_PROCESS_REPLICATION_PORT,
}

# Shared homeserver config
convert(
Expand All @@ -891,6 +938,7 @@ def generate_worker_files(
appservice_registrations=appservice_registrations,
enable_redis=workers_in_use,
workers_in_use=workers_in_use,
using_unix_sockets=using_unix_sockets,
)

# Nginx config
Expand All @@ -901,6 +949,7 @@ def generate_worker_files(
upstream_directives=nginx_upstream_config,
tls_cert_path=os.environ.get("SYNAPSE_TLS_CERT"),
tls_key_path=os.environ.get("SYNAPSE_TLS_KEY"),
using_unix_sockets=using_unix_sockets,
)

# Supervisord config
Expand All @@ -910,6 +959,7 @@ def generate_worker_files(
"/etc/supervisor/supervisord.conf",
main_config_path=config_path,
enable_redis=workers_in_use,
using_unix_sockets=using_unix_sockets,
)

convert(
Expand Down
56 changes: 53 additions & 3 deletions docs/usage/configuration/config_documentation.md
Expand Up @@ -462,6 +462,21 @@ See the docs [request log format](../administration/request_log.md).
* `additional_resources`: Only valid for an 'http' listener. A map of
additional endpoints which should be loaded via dynamic modules.

_Added in Synapse 1.86.0_
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved

* UNIX socket support:
* `path`: A path and filename for a UNIX socket. Ensure it is in a readable/writeable
directory. No default
* **Note**: Declaring a `path` is not compatible while also having a `port`
configuration for the same `listener`
* **Note**: `metrics` and `manhole` are not supported as `types` on UNIX socket `listeners`
* The `x_forwarded` option will default to true and may be left out.
* Other options that would not make sense to use with a UNIX socket, such as
`bind_address[es]` and `tls` will be ignored and can be removed.
realtyem marked this conversation as resolved.
Show resolved Hide resolved
* `mode`: The file permissions to set on the UNIX socket. Defaults to `666`
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
* `type: http` is valid (provided `metrics` is not included in the `resources`:`names`)


Valid resource names are:

* `client`: the client-server API (/_matrix/client), and the synapse admin API (/_synapse/admin). Also implies `media` and `static`.
Expand All @@ -474,7 +489,7 @@ Valid resource names are:

* `media`: the media API (/_matrix/media).

* `metrics`: the metrics interface. See [here](../../metrics-howto.md).
* `metrics`: the metrics interface. See [here](../../metrics-howto.md). (Not usable on a UNIX Socket `listener`)
realtyem marked this conversation as resolved.
Show resolved Hide resolved

* `openid`: OpenID authentication. See [here](../../openid.md).

Expand Down Expand Up @@ -533,6 +548,21 @@ listeners:
bind_addresses: ['::1', '127.0.0.1']
type: manhole
```
Example configuration #3:
```yaml
listeners:
# UNIX socket listener: for when Synapse is behind a reverse proxy that can utilise such.
realtyem marked this conversation as resolved.
Show resolved Hide resolved
#
# Note that x_forwarded will default to true, when using a UNIX socket. Please see
# https://matrix-org.github.io/synapse/latest/reverse_proxy.html.
#
- path: /var/run/synapse/main_public.sock
mode: 660
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
type: http
realtyem marked this conversation as resolved.
Show resolved Hide resolved
resources:
- names: [client, federation]
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
```

---
### `manhole_settings`

Expand Down Expand Up @@ -3909,8 +3939,8 @@ HTTP replication listener of the worker, if configured, and to the main process.
Each worker declared under [`stream_writers`](../../workers.md#stream-writers) needs
a HTTP replication listener, and that listener should be included in the `instance_map`.
The main process also needs an entry on the `instance_map`, and it should be listed under
`main` **if even one other worker exists**. Ensure the port matches with what is declared
inside the `listener` block for a `replication` listener.
`main` **if even one other worker exists**. Ensure the `port` or `path` matches with
what is declared inside the `listener` or `worker_listener` block for a `replication` listener.


Example configuration:
Expand All @@ -3923,6 +3953,14 @@ instance_map:
host: localhost
port: 8034
```
Example configuration(#2, for UNIX sockets):
```yaml
instance_map:
main:
path: /var/run/synapse/main_replication.sock
worker1:
path: /var/run/synapse/worker1_replication.sock
```
---
### `stream_writers`

Expand Down Expand Up @@ -4127,6 +4165,18 @@ worker_listeners:
resources:
- names: [client, federation]
```
Example configuration(#2, using UNIX sockets with a `replication` listener):
```yaml
worker_listeners:
- type: http
path: /var/run/synapse/worker_public.sock
resources:
- names: [client, federation]
- type: http
path: /var/run/synapse/worker_replication.sock
resources:
- names: [replication]
```
---
### `worker_manhole`

Expand Down
10 changes: 7 additions & 3 deletions docs/workers.md
Expand Up @@ -95,9 +95,13 @@ for the main process
* Secondly, you need to enable
[redis-based replication](usage/configuration/config_documentation.md#redis)
* You will need to add an [`instance_map`](usage/configuration/config_documentation.md#instance_map)
with the `main` process defined, as well as the relevant connection information from
it's HTTP `replication` listener (defined in step 1 above). Note that the `host` defined
is the address the worker needs to look for the `main` process at, not necessarily the same address that is bound to.
with the `main` process defined, as well as the relevant connection information from
it's HTTP `replication` listener (defined in step 1 above).
* Note that the `host` defined is the address the worker needs to look for the `main`
process at, not necessarily the same address that is bound to.
* If you are using a UNIX socket for replication to the `main` process, make sure to
use a `path` to the socket file instead of a `port`(It should match the `replication`
entry in the `listeners` for the `main` process).
realtyem marked this conversation as resolved.
Show resolved Hide resolved
* Optionally, a [shared secret](usage/configuration/config_documentation.md#worker_replication_secret)
can be used to authenticate HTTP traffic between workers. For example:

Expand Down