Project

General

Profile

Efficient live access to container logs » History » Version 3

Tom Clegg, 08/08/2023 06:48 PM

1 1 Tom Clegg
h1. Efficient live access to container logs
2
3
{{toc}}
4
5
h2. Goals
6
7
* Low latency: deliver live logs to clients (workbench/cli) as quickly as possible
8
* Scalable: don't generate significant load in RailsAPI
9
* Scalable: don't generate significant load in PostgreSQL
10
* Efficient: don't waste resources on event streams that nobody is listening to
11
* Robust: Don't let a badly behaved container (writing many gigabytes of logs) impede others
12
* Dependable: Retain complete logs (container/crunch-run/crunchstat) _in the log collection_ even in the event that crunch-run or the compute instance itself dies unexpectedly (_e.g.,_ bug, out of memory, hardware failure)
13
14
h2. Proposed architecture
15
16
* crunch-run offers a “container logs” API at its container gateway server
17
* controller offers a “container logs” API that works whether the container is finished or not
18
** proxies to crunch-run _or_ keep-web (depends on whether the container is running)
19
* “container logs” API has two parts:
20
** webdav: read file content from the log collection
21
** server-sent-events stream: get notifications as logs appear (see below)
22
* dispatcher provides “plan B” to save logs when crunch-run crashes, instance dies, etc.
23
** dispatcher routinely retrieves live logs from container gateway, and buffers content to local disk
24
** dispatcher discards buffered logs when crunch-run commits to log collection
25
** when crunch-run exits, dispatcher checks whether crunch-run managed to save the full log collection and, if needed, appends the latest buffered logs
26
* command-line client can “tail” and “tail -f” logs from a running container
27
* workbench2 uses a single code path to retrieve logs (instead of choosing webdav or logs table based on whether container is running)
28
29
h2. API details
30
31 2 Tom Clegg
@GET /arvados/v1/container_requests/{uuid}/log/{container_uuid}/stderr.txt@
32 1 Tom Clegg
33
** client will use Range headers to request partial content
34 3 Tom Clegg
** client will add the @{container_uuid}/@ part explicitly using the @container_uuid@ field from the container request record -- @/arvados/v1/container_requests/{uuid}/log/@ is a valid WebDAV endpoint but the client cannot rely on that directory listing to return the @{container_uuid}/@ subdirectory
35 1 Tom Clegg
36 2 Tom Clegg
@GET /arvados/v1/container_requests/{uuid}/log_events@
37 1 Tom Clegg
38
** response is a "server-sent events":https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events stream, data is JSON-encoded
39
** a “file_sizes” event indicates the current size of new/updated files in the log collection
40
@event: file_sizes@
41 2 Tom Clegg
@data: {"{container_uuid}/stderr.txt":1234,"{container_uuid}/stdout.txt":0,"{container_uuid}/crunch-run.txt":2345}@
42 1 Tom Clegg
** after the first message, unchanged files may be omitted:
43
@event: file_sizes@
44 2 Tom Clegg
@data: {"{container_uuid}/stderr.txt":1444,"{container_uuid}/stdout.txt":72}@
45 1 Tom Clegg
** a “final” message indicates that the log collection is finalized and no further file_sizes events will be sent:
46
@event: final@
47
** a “retry” message indicates how long, in milliseconds, the client should wait before reconnecting after a disconnect (per SSE spec):
48
@retry: 2000@
49
** empty comments are sent periodically to keep the connection alive during idle periods
50
@:@
51
** after the “final” message, the connection either remains open and silent, or closes after specifying a long reconnect delay in a “retry” message (the idea is to avoid sending an inattentive client into a reconnect cycle  -  this doesn't affect well-behaved clients because they will hang up after receiving the “final” message)
52
** @?maxInterval=X@ sets the maximum interval between messages (i.e., keepalive) to X seconds, default 15
53
** @?minInterval=X@ increases the minimum interval between messages (i.e., throttle) to X seconds, default 1
54
55
h2. Authentication
56
57
58
The browser's EventSource API allows the caller to enable existing cached credentials/cookies, but not to explicitly set an Authorization header or another custom header.
59
* @?api_token=v2/...@ in the URL is easy to support on the client and server
60
** this is not ideal: the token is likely to be logged by HTTP servers/proxies
61
** we already accept this situation in our websocket usage
62
63
Server also accepts the usual Authorization header, and this should be used by clients that have the capability (e.g., arvados-client).
64
65
Future version may also support a “copy Authorization header into cookie” endpoint
66
** client requests @GET /arvados/v1/cookie@ request with the token in the Authorization request header
67
** response sets a cookie with the provided token
68
** client sets up EventSource using @{withCredentials: true}@
69
** this approach is inherently flawed in that applications in different tabs (e.g., wb2.clusterA and wb2.clusterB, where A and B are in a federation) will unknowingly fight over which cookie should be sent to cluster A.
70
71
Future version may also support a “provide short-lived token” endpoint
72
** client requests @GET /arvados/v1/api_client_authorizations/:uuid/slt@ with the real API token in an Authorization request header
73
** response contains a new token that will only be recognized for the next 15 seconds
74
** client sets up EventSource using @?slt={newtoken}@
75
** event stream sends a message with the real token in the “id” field
76
** when reconnecting, the browser automatically sends the real token in the Last-Event-Id header
77
** server uses the token in the Last-Event-Id field, if provided
78
79
h2. Client pseudocode
80
81
to reliably display logs for container with uuid C:
82
83
<pre>
84 2 Tom Clegg
connect to server-sent-events endpoint /arvados/v1/container_requests/{CR}/log_events
85 1 Tom Clegg
  on unexpected disconnect:
86
    delay for duration indicated in last “retry” message
87
    reconnect
88
  on final:
89
    disconnect
90
  on file_sizes:
91
    decode message data as json
92
    compare each entry to last size received for corresponding file, if any
93
    if desired, retrieve file data via webdav endpoint and present to user
94
</pre>
95
96
h2. Client (workbench2 / CLI) implementation notes
97
98
* optionally, if container state is complete/cancelled or CR state is final, client _may_ skip the log&#95;events steps and just retrieve file data via webdav endpoint
99
* the client can rely on the server&#45;sent&#45;events stream to return a message, with sizes of all available log files, upon first connection, even if there are no available sizes yet (i.e., message.files may have zero keys) or the container is already finished
100
* message.files may have keys for newly added files that have not appeared in any previous message
101
* client should have logic to skip some content if the log file size grows faster than expected
102
103
h2. Crunch&#45;run implementation notes
104
105
* buffer logs and periodically flush to keep (as before)
106
* container gateway has a new webdav endpoint that reads the (live) log collection
107
** stdlib webdav server implements range requests for us
108
* container gateway has a new endpoint that returns a server&#45;sent&#45;events stream
109
** upon connection, return one event per log file: [filename, current&#45;size, last&#45;saved&#45;size]
110
** when a file is updated, wait until the last returned event is more than 1s old, then return an event [filename, current&#45;size, last&#45;saved&#45;size]
111
*** “current&#45;size” enables client to construct a suitable range request to retrieve recent data
112
*** “current&#45;size” enables client to skip ahead if appropriate
113
*** “last&#45;saved&#45;size” enables dispatcher to discard buffered data once it's safely written to storage and accessible via log collection
114
* crunch&#45;run updates gateway&#95;address as soon as the gateway service is up (instead of waiting until state=Running)
115
** railsapi should allow gateway&#95;address to be set when state=Locked
116
** this allows clients to access early logs (e.g., while preparing the docker image)
117
118
h2. Controller implementation notes for log&#95;events
119
120
* additional logic is needed here beyond a simple proxy, in order that a single client&#45;to&#45;controller connection can receive all log updates through the entire container lifecycle, including one with the final file sizes, even though
121
** crunch&#45;run is not running yet when state=Queued
122
** crunch&#45;run may exit/disconnect before returning the final update (many race possibilities, including container finishing while controller is trying to reconnect)
123
** additional logs may be added by dispatcher if crunch&#45;run exits before finalizing the container
124
* if state=Queued or container gateway&#95;address is empty, send an empty file&#95;sizes message, then send keepalive messages while waiting/polling for the gateway to come up
125
* if state=Complete or Cancelled, get the final log file sizes, send a file&#95;sizes message, a long retry message, and a “final” message; delay a few seconds; and hang up
126
* connect to crunch&#45;run's log&#95;events endpoint (through controller tunnel if applicable), propagate client's last&#45;id and min/maxInterval params, and relay messages (including keepalive) to client
127
* when crunch&#45;run disconnects (or does not answer), wait a second, re&#45;fetch the container record, and repeat from top
128
129
h2. Controller implementation notes for log data
130
131
* if container log is not empty, a specific range is requested, and the size of the requested file is compatible with the requested range, proxy to keep&#45;web
132
* if state=Complete/Cancelled, proxy to keep&#45;web
133
* if gateway&#95;address is empty, return 404
134
* if state=Locked/Running and gateway&#95;address is not empty, proxy to gateway&#95;address
135
136
h2. Dispatcher implementation (for retaining the last log entries when a worker instance crashes)
137
138
* routinely connect/reconnect to crunch&#45;run gateway server of containers that are being supervised
139
* when unflushed logs appear, save them in a temporary buffer on local disk, up to a configurable size limit
140
* when crunch&#45;run exits unexpectedly, check the latest saved log collection, and (if needed) append the buffered logs to the respective truncated files before finalizing the container
141
* in HPC environment where dispatcher→worker connections are not supported, connect through the worker→controller tunnel (see #19166)