Commit Graph

1041 Commits

Author SHA1 Message Date
Alex Vandiver
50e9df448d puppet: Do not start the "puppet" service.
Zulip runs puppet manually, using the command-line tool; it does not
make use of the `puppet` service which, by default, attempts to
contact a host named `puppet` every two minutes to get a manifest to
apply.  These attempts can generate log spam and user confusion.

Disable and stop the `puppet` service via puppet.
2023-01-23 13:02:09 -08:00
Anders Kaseorg
7a7513f6e0 ruff: Fix SIM201 Use … != … instead of not … == ….
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-23 11:18:36 -08:00
Anders Kaseorg
b0e569f07c ruff: Fix SIM102 nested if statements.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-23 11:18:36 -08:00
Alex Vandiver
04cf68b45e uploads: Serve S3 uploads directly from nginx.
When file uploads are stored in S3, this means that Zulip serves as a
302 to S3.  Because browsers do not cache redirects, this means that
no image contents can be cached -- and upon every page load or reload,
every recently-posted image must be re-fetched.  This incurs extra
load on the Zulip server, as well as potentially excessive bandwidth
usage from S3, and on the client's connection.

Switch to fetching the content from S3 in nginx, and serving the
content from nginx.  These have `Cache-control: private, immutable`
headers set on the response, allowing browsers to cache them locally.

Because nginx fetching from S3 can be slow, and requests for uploads
will generally be bunched around when a message containing them are
first posted, we instruct nginx to cache the contents locally.  This
is safe because uploaded file contents are immutable; access control
is still mediated by Django.  The nginx cache key is the URL without
query parameters, as those parameters include a time-limited signed
authentication parameter which lets nginx fetch the non-public file.

This adds a number of nginx-level configuration parameters to control
the caching which nginx performs, including the amount of in-memory
index for he cache, the maximum storage of the cache on disk, and how
long data is retained in the cache.  The currently-chosen figures are
reasonable for small to medium deployments.

The most notable effect of this change is in allowing browsers to
cache uploaded image content; however, while there will be many fewer
requests, it also has an improvement on request latency.  The
following tests were done with a non-AWS client in SFO, a server and
S3 storage in us-east-1, and with 100 requests after 10 requests of
warm-up (to fill the nginx cache).  The mean and standard deviation
are shown.

|                   | Redirect to S3      | Caching proxy, hot  | Caching proxy, cold |
| ----------------- | ------------------- | ------------------- | ------------------- |
| Time in Django    | 263.0 ms ±  28.3 ms | 258.0 ms ±  12.3 ms | 258.0 ms ±  12.3 ms |
| Small file (842b) | 586.1 ms ±  21.1 ms | 266.1 ms ±  67.4 ms | 288.6 ms ±  17.7 ms |
| Large file (660k) | 959.6 ms ± 137.9 ms | 609.5 ms ±  13.0 ms | 648.1 ms ±  43.2 ms |

The hot-cache performance is faster for both large and small files,
since it saves the client the time having to make a second request to
a separate host.  This performance improvement remains at least 100ms
even if the client is on the same coast as the server.

Cold nginx caches are only slightly slower than hot caches, because
VPC access to S3 endpoints is extremely fast (assuming it is in the
same region as the host), and nginx can pool connections to S3 and
reuse them.

However, all of the 648ms taken to serve a cold-cache large file is
occupied in nginx, as opposed to the only 263ms which was spent in
nginx when using redirects to S3.  This means that to overall spend
less time responding to uploaded-file requests in nginx, clients will
need to find files in their local cache, and skip making an
uploaded-file request, at least 60% of the time.  Modeling shows a
reduction in the number of client requests by about 70% - 80%.

The `Content-Disposition` header logic can now also be entirely shared
with the local-file codepath, as can the `url_only` path used by
mobile clients.  While we could provide the direct-to-S3 temporary
signed URL to mobile clients, we choose to provide the
served-from-Zulip signed URL, to better control caching headers on it,
and greater consistency.  In doing so, we adjust the salt used for the
URL; since these URLs are only valid for 60s, the effect of this salt
change is minimal.
2023-01-09 18:23:58 -05:00
Alex Vandiver
ed6d62a9e7 avatars: Serve /user_avatars/ through Django, which offloads to nginx.
Moving `/user_avatars/` to being served partially through Django
removes the need for the `no_serve_uploads` nginx reconfiguring when
switching between S3 and local backends.  This is important because a
subsequent commit will move S3 attachments to being served through
nginx, which would make `no_serve_uploads` entirely nonsensical of a
name.

Serve the files through Django, with an offload for the actual image
response to an internal nginx route.  In development, serve the files
directly in Django.

We do _not_ mark the contents as immutable for caching purposes, since
the path for avatar images is hashed only by their user-id and a salt,
and as such are reused when a user's avatar is updated.
2023-01-09 18:23:58 -05:00
Alex Vandiver
24f95a3788 uploads: Move internal upload serving path to under /internal/. 2023-01-09 18:23:58 -05:00
Alex Vandiver
b20ecabf8f tornado: Move internal tornado redirect to under /internal/. 2023-01-09 18:23:58 -05:00
Alex Vandiver
cc9b028312 uploads: Set X-Accel-Redirect manually, without using django-sendfile2.
The `django-sendfile2` module unfortunately only supports a single
`SENDFILE` root path -- an invariant which subsequent commits need to
break.  Especially as Zulip only runs with a single webserver, and
thus sendfile backend, the functionality is simple to inline.

It is worth noting that the following headers from the initial Django
response are _preserved_, if present, and sent unmodified to the
client; all other headers are overridden by those supplied by the
internal redirect[^1]:
 - Content-Type
 - Content-Disposition
 - Accept-Ranges
 - Set-Cookie
 - Cache-Control
 - Expires

As such, we explicitly unset the Content-type header to allow nginx to
set it from the static file, but set Content-Disposition and
Cache-Control as we want them to be.

[^1]: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/
2023-01-09 18:23:58 -05:00
Alex Vandiver
497abc2e48 nginx: Move uploads handling into app_frontend_base.
As uploads are a feature of the application, not of a generic nginx
deployment, move them into the `zulip::app_frontend_base` class.  This
is purely for organizational clarity -- we do not support deployments
with has `zulip::nginx` but not `zulip::app_frontend_base`.
2023-01-09 18:23:58 -05:00
Anders Kaseorg
f7e97b1180 ruff: Fix PLW0602 Using global but no assignment is done.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-04 16:25:07 -08:00
Alex Vandiver
8ba51f90e6 puppet: Go's website is officially go.dev, not golang.org. 2023-01-04 14:33:37 -08:00
Anders Kaseorg
f3f5dfb5aa ruff: Fix RUF004 exit() is only available in the interpreter.
‘exit’ is pulled in for the interactive interpreter as a side effect
of the site module; this can be disabled with python -S and shouldn’t
be relied on.

Also, use the NoReturn type where appropriate.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-12-04 22:11:24 -08:00
Alex Vandiver
ea9988cc9e grafana: Upgrade to 9.3.0. 2022-11-30 12:41:18 -05:00
Alex Vandiver
7069e2c8c2 puppet: Align more sections of $versions. 2022-11-30 12:13:47 -05:00
Alex Vandiver
89f20140c0 wal-g: Use pre-built aarch64 binary, rather than building from source.
Starting with wal-g 2.0.1, they provide `aarch64` assets[^1].
Effectively revert d7b59c86ce, and use
the pre-built binary for `aarch64` rather than spend a bunch of space
and time having to build it from source.

[^1]: https://github.com/wal-g/wal-g/releases/tag/v2.0.1
2022-11-30 12:13:47 -05:00
Anders Kaseorg
e5c26eeb86 tornado: Support sharding by user ID.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-11-15 17:27:01 -08:00
Alex Vandiver
6517e4b239 puppet: Update third-party package versions. 2022-11-08 13:26:32 -08:00
Alex Vandiver
df201bd132 puppet: Monitor "hosts_fullstack" hosts (e.g. CZO).
These hosts were excluded from `zulipconf_nagios_hosts` in
8cff27f67d, because it was replicating the previously hard-coded
behaviour exactly.  That behaviour was an accident of history, in that
4fbe201187 and before had simply not monitored hosts of this class.

There is no reason to not add SSH tunnels and munin monitoring for
these hosts; stop skipping them.
2022-11-01 22:24:40 -07:00
Alex Vandiver
01f38c4516 puppet: Bump Grafana version. 2022-10-12 22:00:27 -07:00
Alex Vandiver
798ab420db puppet: Update third-party package versions. 2022-10-10 08:46:29 -07:00
Anders Kaseorg
11a86ec328 install: Remove PostgreSQL 10 support.
PostgreSQL 10 reaches its upstream end of life in November, and is not
supported by Django 4.1.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-10-06 15:59:07 -07:00
Anders Kaseorg
ce9ceb7f9f tornado: Fix Tornado CSRF check with X-Forwarded-Proto.
Since Django factors request.is_secure() into its CSRF check, we need
this to tell it to consider requests forwarded from nginx to Tornado
as secure.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-09-23 16:01:12 -07:00
Anders Kaseorg
987ab741f9 sharding: Support Tornado sharding by regexes.
One should now be able to configure a regex by appending _regex to the
port number:

[tornado_sharding]
9802_regex = ^[l-p].*\.zulipchat\.com$

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-09-15 16:07:50 -07:00
Anders Kaseorg
7666ff603d sharding: Configure Tornado sharding with nginx map.
https://nginx.org/en/docs/http/ngx_http_map_module.html

Since Puppet doesn’t manage the contents of nginx_sharding.conf after
its initial creation, it needs to be renamed so we can give it
different default contents.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-09-15 16:07:50 -07:00
Anders Kaseorg
5d77d50423 scripts: Help mypy resolve the psycopg2.connect overload.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-08-30 17:36:21 -07:00
Matt Keller
91e5ae84ac uwsgi: Increase timeout before harakiri.
Some legitimate requests in Zulip can take more than 20s to be
processed, and we don't have a current problem where having a 20s
limit here is preventing a problem.
2022-08-23 15:28:10 -07:00
Alex Vandiver
9d29c46078 puppet: Upgrade Grafana, Prometheus and redis_exporter. 2022-07-15 09:18:58 -07:00
Alex Vandiver
42dc5d003e puppet: Upgrade Smokescreen and golang. 2022-07-15 09:18:58 -07:00
Alex Vandiver
4fd51cb5ad uwsgi: Increase request buffer size to 64k, from 8k default.
The default value in uwsgi is 4k; receiving more than this amount from
nginx leads to a 502 response (though, happily, the backend uwsgi does not
terminate).

ab18dbfde5 originally increased it from the unstated uwsgi default
of 4096, to 8192; b1da797955 made it configurable, in order to allow
requests from clients with many cookies, without causing 502's[1].

nginx defaults to a limitation of 1k, with 4 additional 8k header
lines allowed[2]; any request larger than that returns a response of
`400 Request Header Or Cookie Too Large`.  The largest header size
theoretically possible from nginx, by default, is thus 33k, though
that would require packing four separate headers to exactly 8k each.

Remove the gap between nginx's limit and uwsgi's, which could trigger
502s, by removing the uwsgi configurability, and setting a 64k size in
uwsgi (the max allowable), which is larger than nginx's default limit.

uWSGI's documentation of `buffer-size` ([3], [4]) also notes that "It
is a security measure too, so adapt to your app needs instead of
maxing it out."  Python has no security issues with buffers of 64k,
and there is no appreciable memory footprint difference to having a
larger buffer available in uwsgi.

[1]: https://chat.zulip.org/#narrow/stream/31-production-help/topic/works.20in.20Edge.20not.20Chrome/near/719523
[2]: https://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_buffer_size
[3]: https://uwsgi-docs.readthedocs.io/en/latest/ThingsToKnow.html
[4]: https://uwsgi-docs.readthedocs.io/en/latest/Options.html#buffer-size
2022-06-28 16:14:24 -07:00
Anders Kaseorg
ef3510fa6d nginx: Remove legacy X-XSS-Protection header.
Support for this header was removed in Chrome 78, Safari 15.4, and
Edge 17.  It was never supported in Firefox.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-06-27 17:38:18 -07:00
Alex Vandiver
41deef40cf nagios: Switch to generic check_cron_file for queues and consumers.
These share a common root; 91da4bd59b duplicated the code, but
didn't move the existing uses to the new utility.
2022-06-22 12:07:38 -07:00
Alex Vandiver
b2d0bad9af check_cron_file: Remove unnecessary quotes. 2022-06-22 12:07:38 -07:00
Alex Vandiver
41b7ae4e44 check_cron_file: Don't crash on missing cron file.
This is 5050fb19f6, but for `check_cron_file`, which was introduced
in 91da4bd59b.
2022-06-22 12:07:38 -07:00
Anders Kaseorg
dc6af98e52 nginx: Add Cache-Control headers for Django-hashed static files.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-06-21 17:26:23 -07:00
Alex Vandiver
0645656fd8 process_fts_updates: Nagios may lack permissions to load Django config.
Even if Django and PostgreSQL are on the same host, the `nagios` user
may lack permissions to read accessory configuration files needed to
load the Django configuration (e.g. authentication keys).

Catch those failures, and switch to loading the required settings from
`/etc/zulip/zulip.conf`.
2022-06-21 12:50:13 -07:00
Anders Kaseorg
a7f9c4f958 logging: Pass more format arguments to logging.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-06-03 12:27:23 -07:00
Alex Vandiver
aa46d8d2a8 puppet: Enable strict typo checking in uwsgi. 2022-06-02 13:20:48 -07:00
Alex Vandiver
18ec3b6215 puppet: Enable background worker threads in uwsgi.
Without this, uwsgi does not release the GIL before going back into
`epoll_wait` to wait for the next request.  This results in any
background threads languishing, unserviced.[1]

Practically, this results in Sentry background reporter threads timing
out when attempting to post results -- but only in situations with low
traffic, as in those significant time is spent in `epoll_wait`.  This
is seen in logs as:

    WARN [urllib3.connectionpool] Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLEOFError(8, 'EOF occurred in violation of protocol (_ssl.c:1131)'))': /api/123456789/envelope/

Or:

    WARN [urllib3.connectionpool] Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProxyError('Cannot connect to proxy.', RemoteDisconnected('Remote end closed connection without response'))': /api/123456789/envelope/

Sentry attempts to detect this and warn, but due to startup ordering,
the warning is not printed without lazy-loading.

Enable threads, at a miniscule performance cost, in order to support
background workers like Sentry[2].

[1] https://github.com/unbit/uwsgi/issues/1141#issuecomment-169042767
[2] https://docs.sentry.io/clients/python/advanced/#a-note-on-uwsgi
2022-06-02 13:20:48 -07:00
Alex Vandiver
919c904091 puppet: Give the uwsgi processes a shorter process name.
Previously, the complete command line, which is quite long, is shown:

    3963143 ?        SN     0:00 /home/zulip/deployments/current/zulip-current-venv/bin/uwsgi --ini /etc/zulip/uwsgi.ini
    3963144 ?        SN     0:03  \_ /home/zulip/deployments/current/zulip-current-venv/bin/uwsgi --ini /etc/zulip/uwsgi.ini
    3963145 ?        SN     0:03  \_ /home/zulip/deployments/current/zulip-current-venv/bin/uwsgi --ini /etc/zulip/uwsgi.ini
    3963146 ?        SN     0:03  \_ /home/zulip/deployments/current/zulip-current-venv/bin/uwsgi --ini /etc/zulip/uwsgi.ini
    3963147 ?        SN     0:03  \_ /home/zulip/deployments/current/zulip-current-venv/bin/uwsgi --ini /etc/zulip/uwsgi.ini
    3963148 ?        SN     0:03  \_ /home/zulip/deployments/current/zulip-current-venv/bin/uwsgi --ini /etc/zulip/uwsgi.ini
    3963149 ?        SN     0:03  \_ /home/zulip/deployments/current/zulip-current-venv/bin/uwsgi --ini /etc/zulip/uwsgi.ini

Configure uwsgi to rename and number the processes.  This results in:

    3907613 ?        SN     0:00 zulip-django uWSGI master
    3907614 ?        SN     0:05  \_ zulip-django uWSGI worker 1
    3907615 ?        SN     0:03  \_ zulip-django uWSGI worker 2
    3907616 ?        SN     0:05  \_ zulip-django uWSGI worker 3
    3907617 ?        SN     0:05  \_ zulip-django uWSGI worker 4
    3907618 ?        SN     0:05  \_ zulip-django uWSGI worker 5
    3907619 ?        SN     0:05  \_ zulip-django uWSGI worker 6
2022-06-02 13:20:48 -07:00
Alex Vandiver
a522ad1d9a puppet: Always create a uwsgi master control socket.
This is potentially useful even with rolling restarts disabled.
2022-06-02 13:20:48 -07:00
Alex Vandiver
721a101f12 puppet: Reorganize and comment uwsgi.ini file.
As the uwsgi documentation is somewhat obtuse, more comments are added
here than might usually be.
2022-06-02 13:20:48 -07:00
Alex Vandiver
a201e3b25b puppet: Upgrade wal-g to 2.0.0. 2022-05-22 14:51:18 -07:00
Alex Vandiver
c8ee53619d puppet: Upgrade go and smokescreen. 2022-05-22 14:51:18 -07:00
Alex Vandiver
4a5e530743 puppet: Upgrade Grafana to 8.5.3, for CVE-2022-29170. 2022-05-22 14:51:18 -07:00
Alex Vandiver
baed1214f2 puppet: Only fix certbot certificates if https is enabled.
This is a reprise of c97162e485, but for the case where certbot
certs are no longer in use by way of enabling `http_only` and letting
another server handle TLS termination.

Fixes: #22034.
2022-05-17 15:03:44 -07:00
Alex Vandiver
62f234328d puppet: Include the OS-enabled nginx module configurations.
This allows system-level configuration to be done by `apt-get install`
of nginx modules, which place their load statements in this directory.

The initial import in ed0cb0a5f8 of the stock nginx config omitted
this include -- one potential explanation was in an effort to reduce
the memory footprint of the server.

The default nginx install enables:

    50-mod-http-auth-pam.conf
    50-mod-http-dav-ext.conf
    50-mod-http-echo.conf
    50-mod-http-geoip2.conf
    50-mod-http-geoip.conf
    50-mod-http-image-filter.conf
    50-mod-http-subs-filter.conf
    50-mod-http-upstream-fair.conf
    50-mod-http-xslt-filter.conf
    50-mod-mail.conf
    50-mod-stream.conf

While Zulip doesn't actively use any of these, they likely don't do
any harm to simply be loaded -- they are loaded into every nginx by
default.

Having the `modules-enabled` include allows easier extension of the
server, as neither of the existing wildcard
includes (`/etc/nginx/conf.d/*.conf` and
`/etc/nginx/zulip-include/app.d/*.conf`) are in the top context, and
thus able to load modules.
2022-05-17 15:03:07 -07:00
Alex Vandiver
814841c9ec puppet: Remove typo'd cron job.
54b6a83412 fixed the typo introduced in 49ad188449, but that does
not clean up existing installs which had the file with the wrong name
already.

Remove the file with the typo'd name, so two jobs do not race, and fix
the typo in the comment.
2022-05-16 14:57:21 -07:00
Alex Vandiver
20b7a2d450 puppet: Each worker should chdir after forking.
The top-level `chdir` setting only does the chdir once, at initial
`uwsgi` startup time.  Rolling restarts, however, however, require
that `uwsgi` pick up the _new_ value of the `current` directory, and
start new workers in that directory -- as currently implemented,
rolling restarts cannot restart into newer versions of the code, only
the same one in which they were started.

Use [configurable hooks][1] to execute the `chdir` after every fork.
This causes the following behaviour:

```
Thu May 12 18:56:55 2022 - chain reload starting...
Thu May 12 18:56:55 2022 - chain next victim is worker 1
Gracefully killing worker 1 (pid: 1757689)...
worker 1 killed successfully (pid: 1757689)
Respawned uWSGI worker 1 (new pid: 1757969)
Thu May 12 18:56:56 2022 - chain is still waiting for worker 1...
running "chdir:/home/zulip/deployments/current" (post-fork)...
Thu May 12 18:56:57 2022 - chain is still waiting for worker 1...
Thu May 12 18:56:58 2022 - chain is still waiting for worker 1...
Thu May 12 18:56:59 2022 - chain is still waiting for worker 1...
WSGI app 0 (mountpoint='') ready in 3 seconds on interpreter 0x55dfca409170 pid: 1757969 (default app)
Thu May 12 18:57:00 2022 - chain next victim is worker 2
[...]
```

..and so forth down the line of processes.  Each process is correctly
started in the _current_ value of `current`, and thus picks up the
correct code.

[1]: https://uwsgi-docs.readthedocs.io/en/latest/Hooks.html
2022-05-12 21:54:02 -07:00
Alex Vandiver
7f6a77da31 puppet: Add a redis exporter. 2022-05-03 17:13:44 -07:00
Alex Vandiver
d891b9590a puppet: Fix non-replicated PostgreSQL 10 and 11 configuration.
6f5ae8d13d removed the `$replication` variable from the
configurations of PostgreSQL 12 and higher, but left it in the
templates for PostgreSQL 10 and 11.  Because `undef != ''`,
deployments on PostgreSQL 10 and 11 started trying to push to S3
backups, regardless of if they were configured, leaving frequent log
messages like:

```
2022-04-30 12:45:47.805 UTC [626d24ec.1f8db0]: [107-1] LOG: archiver process (PID 2086106) exited with exit code 1
2022-04-30 12:45:49.680 UTC [626d24ee.1f8dc3]: [18-1] LOG: checkpoint complete: wrote 19 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=1.910 s, sync=0.022 s, total=1.950 s; sync files=16, longest=0.018 s, average=0.002 s; distance=49 kB, estimate=373 kB
/usr/bin/timeout: failed to run command "/usr/local/bin/env-wal-g": No such file or directory
2022-04-30 12:46:17.852 UTC [626d2f99.1fd4e9]: [1-1] FATAL: archive command failed with exit code 127
2022-04-30 12:46:17.852 UTC [626d2f99.1fd4e9]: [2-1] DETAIL: The failed archive command was: /usr/bin/timeout 10m /usr/local/bin/env-wal-g wal-push pg_wal/000000010000000300000080
```

Switch the PostgreSQL 10 and 11 configuration to check
`s3_backups_bucket`, like the other versions.
2022-05-02 16:46:10 -07:00