kicks_liveness 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -1
- data/README.md +21 -6
- data/docs/DESIGN.md +133 -188
- data/docs/KUBERNETES.md +40 -13
- data/docs/LIMITATIONS.md +154 -61
- data/docs/SETUP.md +30 -0
- data/docs/VERIFYING.md +269 -13
- data/lib/kicks_liveness/attempts.rb +21 -16
- data/lib/kicks_liveness/configuration.rb +24 -9
- data/lib/kicks_liveness/heartbeat.rb +241 -111
- data/lib/kicks_liveness/hooks.rb +11 -29
- data/lib/kicks_liveness/monitor.rb +50 -51
- data/lib/kicks_liveness/registry.rb +17 -19
- data/lib/kicks_liveness/topology_report.rb +110 -0
- data/lib/kicks_liveness/version.rb +1 -1
- data/lib/kicks_liveness.rb +3 -1
- metadata +3 -2
data/docs/KUBERNETES.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
## Manifest
|
|
4
4
|
|
|
5
5
|
```yaml
|
|
6
|
+
env:
|
|
7
|
+
- name: KICKS_LIVENESS_SHUTDOWN_GRACE
|
|
8
|
+
value: "60" # at least terminationGracePeriodSeconds
|
|
6
9
|
startupProbe:
|
|
7
10
|
exec:
|
|
8
11
|
command: ["bundle", "exec", "kicks-liveness"]
|
|
@@ -55,11 +58,16 @@ mark. A fresh mark from the previous container is rejected until every fork in
|
|
|
55
58
|
the current one has subscribed and published its own mark, so preserving the
|
|
56
59
|
cache cannot make `startupProbe` succeed early.
|
|
57
60
|
|
|
58
|
-
That guarantee
|
|
59
|
-
`shareProcessNamespace: true` or `hostPID: true
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
That guarantee needs the default container-private PID namespace. Do not set
|
|
62
|
+
`shareProcessNamespace: true` or `hostPID: true`: in either topology PID 1
|
|
63
|
+
survives a worker-container restart, so the generation repeats and an inherited
|
|
64
|
+
mark would be accepted. Since 0.1.3 the gem rejects that topology when either
|
|
65
|
+
the worker or probe can read and compare both namespace links. With Kubernetes'
|
|
66
|
+
default capability set those reads failed on the measured node: the shared-PID
|
|
67
|
+
pod was `unverifiable`, and the host-PID pod was `unavailable` because node init
|
|
68
|
+
reported a zero start time. Those states warn and retain the documented degraded
|
|
69
|
+
checks; they do not claim the topology is safe. See
|
|
70
|
+
[LIMITATIONS.md](LIMITATIONS.md#container-generations-need-linux-procfs-and-a-private-pid-namespace).
|
|
63
71
|
|
|
64
72
|
## Why the command looks like that
|
|
65
73
|
|
|
@@ -121,9 +129,12 @@ which is not something to rely on in a manifest.
|
|
|
121
129
|
|
|
122
130
|
Keep the worker and probe on the same gem version. Since 0.1.2 the heartbeat
|
|
123
131
|
protocol includes a container generation; a newer probe correctly rejects the
|
|
124
|
-
generation-less files written by an older worker.
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
generation-less files written by an older worker. Since 0.1.3 it also includes
|
|
133
|
+
the fail-closed topology state, which only a 0.1.3 probe reads: an older probe
|
|
134
|
+
beside a 0.1.3 worker would ignore a proven unsupported PID topology instead of
|
|
135
|
+
failing on it. `bundle exec` guarantees that both sides resolve from the same
|
|
136
|
+
bundle. If the faster form below installs a second copy in `GEM_HOME`, rebuild
|
|
137
|
+
that copy on every gem upgrade too.
|
|
127
138
|
|
|
128
139
|
### Where your image puts its gems
|
|
129
140
|
|
|
@@ -134,11 +145,17 @@ image. One line, no cluster needed:
|
|
|
134
145
|
docker run --rm your-image ruby -e "require 'kicks_liveness/probe'"
|
|
135
146
|
```
|
|
136
147
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
not
|
|
148
|
+
With no worker running, exit 1 and the following line mean the plain command
|
|
149
|
+
works — the probe loaded and found no declared worker count:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
no /opt/app/tmp/health/expected: worker has not started yet (is KicksLiveness.install! called before the runner starts? outside Rails it is not automatic)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The question is a [setup hint](SETUP.md#checking-that-the-hooks-are-in-place),
|
|
156
|
+
not proof of missing installation. A `LoadError` means the command does not
|
|
157
|
+
work: keep `bundle exec kicks-liveness`, or use the explicit-path optimisation
|
|
158
|
+
below. Do not put the plain command in a manifest without this check: its failure mode is
|
|
142
159
|
a pod that never passes `startupProbe` and lands in `CrashLoopBackOff`.
|
|
143
160
|
|
|
144
161
|
What decides it is whether Bundler installed into `GEM_HOME` or somewhere of
|
|
@@ -305,6 +322,15 @@ several consumers per worker these add up. If the grace period is shorter than
|
|
|
305
322
|
that, the container is SIGKILLed mid-shutdown and in-flight messages are
|
|
306
323
|
redelivered.
|
|
307
324
|
|
|
325
|
+
Set `KICKS_LIVENESS_SHUTDOWN_GRACE` (or `config.shutdown_grace`) to at least
|
|
326
|
+
`terminationGracePeriodSeconds`; both are 60 seconds in the example. The
|
|
327
|
+
monitor keeps the mark fresh only within that shutdown grace. For a SIGTERM
|
|
328
|
+
sent outside pod deletion, a stuck drain stops publishing at the deadline;
|
|
329
|
+
its last mark then has to exceed `max_age`, followed by the liveness probe's
|
|
330
|
+
period and failure threshold. Under normal clock progression, restart
|
|
331
|
+
eligibility is bounded by `shutdown_grace + max_age`, with the probe budget
|
|
332
|
+
additional. During pod deletion the kubelet still owns the termination deadline.
|
|
333
|
+
|
|
308
334
|
## Environment variables
|
|
309
335
|
|
|
310
336
|
| Variable | Default | Purpose | Invalid or blank value |
|
|
@@ -312,6 +338,7 @@ redelivered.
|
|
|
312
338
|
| `KICKS_LIVENESS_DIR` | `/opt/app/tmp/health` | marks directory, **must be on tmpfs** | uses the default |
|
|
313
339
|
| `KICKS_LIVENESS_MAX_AGE` | `45` | seconds after which a mark is stale | uses the default |
|
|
314
340
|
| `KICKS_LIVENESS_TICK` | `10` | interval between ticks | uses the default, then the safety fallback below if needed |
|
|
341
|
+
| `KICKS_LIVENESS_SHUTDOWN_GRACE` | `60` | seconds to keep publishing during shutdown; at least `terminationGracePeriodSeconds` | uses the default |
|
|
315
342
|
|
|
316
343
|
Keep `tick` well below `max_age`. A tick longer than half of `max_age` leaves no
|
|
317
344
|
room for a single missed write, and a tick longer than `max_age` guarantees a
|
data/docs/LIMITATIONS.md
CHANGED
|
@@ -39,25 +39,17 @@ aware that this probe no longer covers that failure mode.
|
|
|
39
39
|
|
|
40
40
|
## Broker unavailability is invisible by design
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
by itself, so a restart at that moment cures nothing.
|
|
47
|
-
|
|
48
|
-
The consequence, plainly: a pod whose broker is unreachable looks healthy to
|
|
49
|
-
this probe. Cover it with the alert.
|
|
42
|
+
The probe reports healthy while Bunny recovers an established connection and
|
|
43
|
+
re-subscribes consumers. This avoids restarting every replica during a broker
|
|
44
|
+
outage, but also means an unreachable broker is invisible to this probe. Cover
|
|
45
|
+
that case with the external consumer alert.
|
|
50
46
|
|
|
51
47
|
## The recovery exemption covers an established connection, not a failed subscribe
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
If the broker is unreachable at the moment a worker subscribes, there is no
|
|
58
|
-
connection at all: the registry stays empty and no mark is ever written. So the
|
|
59
|
-
cascade the exemption avoids for a live connection is still possible at startup
|
|
60
|
-
— broker down, every replica restarting.
|
|
49
|
+
The exemption requires a connection object. If the initial subscription fails,
|
|
50
|
+
the worker never registers and no mark is written. A broker outage at startup
|
|
51
|
+
can therefore restart every replica even though an established connection would
|
|
52
|
+
be exempt.
|
|
61
53
|
|
|
62
54
|
The budget here is the **startup** one, not the steady-state one:
|
|
63
55
|
|
|
@@ -78,6 +70,39 @@ flapping pod rather than a storm. And the supervisor respawning failing forks
|
|
|
78
70
|
inside the container — which is *not* throttled by anything — is now reported:
|
|
79
71
|
see the respawn escalation below.
|
|
80
72
|
|
|
73
|
+
## Graceful shutdown has a finite heartbeat exemption
|
|
74
|
+
|
|
75
|
+
`Sneakers::WorkerGroup#stop` removes consumers before waiting for their thread
|
|
76
|
+
pools to drain. `Registry.stopping!` therefore suspends the consumer predicate
|
|
77
|
+
before the first worker stops, allowing an orderly drain to keep its heartbeat
|
|
78
|
+
fresh without reporting a consumer failure on every deploy.
|
|
79
|
+
|
|
80
|
+
The exemption lasts for `shutdown_grace` seconds from the first stop callback
|
|
81
|
+
(default 60). Set `KICKS_LIVENESS_SHUTDOWN_GRACE` or `config.shutdown_grace` to at
|
|
82
|
+
least the deployment's `terminationGracePeriodSeconds`. During pod deletion the
|
|
83
|
+
kubelet still owns the outer termination deadline. The gem cannot determine
|
|
84
|
+
whether a SIGTERM came from the kubelet, an operator, or another process.
|
|
85
|
+
|
|
86
|
+
At the deadline, the monitor logs one ERROR and stops refreshing the slot mark.
|
|
87
|
+
It keeps declaring the expected fork count, so an existing mark eventually
|
|
88
|
+
fails as `worker-<slot> stale`, rather than claiming that the worker never
|
|
89
|
+
started. The monitor does not kill the process or resume checking consumers.
|
|
90
|
+
This bounds the formerly indefinite healthy result when an out-of-band SIGTERM
|
|
91
|
+
leaves a process stuck draining with no consumers.
|
|
92
|
+
|
|
93
|
+
Expiry is not an immediate probe failure: the last mark must still exceed
|
|
94
|
+
`max_age`. Under normal clock progression, the bound from stop notification to
|
|
95
|
+
restart eligibility is `shutdown_grace + max_age`; actual restart also depends
|
|
96
|
+
on the probe period and failure threshold. The first stop time and deadline are
|
|
97
|
+
process-local, so repeated stop callbacks do not renew them and a sibling
|
|
98
|
+
replacement fork starts without them.
|
|
99
|
+
|
|
100
|
+
The deadline uses the monotonic clock. A wall-clock jump cannot shorten or
|
|
101
|
+
extend the exemption, but it can change mark staleness: the probe still compares
|
|
102
|
+
filesystem mtime with wall time. Keep the external consumer alert enabled; the
|
|
103
|
+
shutdown grace and the probe budget deliberately allow a period with no
|
|
104
|
+
consumers before a restart.
|
|
105
|
+
|
|
81
106
|
## Changing the worker count at runtime is not supported
|
|
82
107
|
|
|
83
108
|
`ServerEngine` can re-read its configuration on SIGHUP and scale the fork set,
|
|
@@ -92,65 +117,133 @@ count and the retired slots eventually go stale. Neither direction is safe.
|
|
|
92
117
|
|
|
93
118
|
If you change `workers`, restart the runner. Runtime scaling is not supported.
|
|
94
119
|
|
|
95
|
-
## Container generations
|
|
120
|
+
## Container generations need Linux procfs and a private PID namespace
|
|
96
121
|
|
|
97
122
|
Kubernetes keeps an `emptyDir` across restarts of a container in the same pod.
|
|
98
|
-
To
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`shareProcessNamespace: true
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
0.1.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
To stop the next container inheriting a fresh heartbeat, the worker and its exec
|
|
124
|
+
probe independently identify their shared incarnation from Linux procfs: the
|
|
125
|
+
mount namespace plus PID 1 start time. No application cache is removed.
|
|
126
|
+
|
|
127
|
+
That is an incarnation only where the container owns PID 1. With
|
|
128
|
+
`shareProcessNamespace: true` PID 1 is the pod sandbox; with `hostPID: true` it
|
|
129
|
+
is the node's init process. Neither restarts when the worker container does.
|
|
130
|
+
If the replacement container also receives the same mount namespace inode,
|
|
131
|
+
the complete identifier repeats and an inherited fresh mark can satisfy the
|
|
132
|
+
0.1.2 startup probe.
|
|
133
|
+
|
|
134
|
+
Rejection in 0.1.3 depends on procfs access. On the measured Kubernetes node,
|
|
135
|
+
default capabilities left shared PID `unverifiable` and host PID `unavailable`;
|
|
136
|
+
node init reported start time zero. Adding `CAP_SYS_PTRACE` made shared PID
|
|
137
|
+
provably `unsafe`. This is not a recommendation to add the capability.
|
|
138
|
+
The full restart reproduction and Kubernetes measurement are in
|
|
139
|
+
[VERIFYING.md](VERIFYING.md#12-shared-and-host-pid-topology-outcomes).
|
|
140
|
+
|
|
141
|
+
### Proven unsafe: the probe fails, and keeps failing
|
|
142
|
+
|
|
143
|
+
Both links readable and different is proof that PID 1 belongs to something
|
|
144
|
+
outside this container. The probe exits 1 with
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
unsupported PID topology: pod must not set shareProcessNamespace or hostPID
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Procfs access can differ between the worker and probe. A worker that proves the
|
|
151
|
+
topology unsafe publishes `topology-unsafe` instead of a heartbeat; a probe
|
|
152
|
+
rejects either that file or its own unsafe result before checking marks.
|
|
153
|
+
Inherited generations and another fork's marks cannot override the verdict.
|
|
154
|
+
|
|
155
|
+
The worker logs the reason and recovery at ERROR once for the uninterrupted
|
|
156
|
+
unsafe state, across forks and container restarts sharing the volume. Later
|
|
157
|
+
containers in that pod therefore do not repeat the ERROR; read the verdict from
|
|
158
|
+
the kubelet's `Unhealthy` event or `topology-unsafe`.
|
|
159
|
+
|
|
160
|
+
This state is terminal for the marks volume. Neither PID setting can change on
|
|
161
|
+
a live pod, and its `emptyDir` dies with it. **Recovery is recreating the pod
|
|
162
|
+
without those settings.** Until then probes fail and the container restarts.
|
|
163
|
+
Unreadable namespace links do not establish this verdict; they take a degraded
|
|
164
|
+
branch below.
|
|
165
|
+
|
|
166
|
+
### Unverifiable: the 0.1.2 guarantee, and one warning
|
|
167
|
+
|
|
168
|
+
Reading `/proc/1/ns/mnt` is a ptrace-level operation and may be denied, including
|
|
169
|
+
when PID 1 runs as a different user. Denial cannot distinguish a valid private
|
|
170
|
+
PID namespace from an unsupported topology. If the other procfs entries yield a
|
|
171
|
+
generation, the probe retains its 0.1.2 check and output; the worker logs:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
WARN [liveness] slot 0: PID topology unverified (cannot read /proc/1/ns/mnt (Errno::EACCES)):
|
|
175
|
+
the container generation still scopes the heartbeat, but shareProcessNamespace and hostPID
|
|
176
|
+
cannot be ruled out
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Read this as unverifiable, not safe: an unsupported topology still has the
|
|
180
|
+
0.1.2 inherited-heartbeat weakness. The WARN is once per derived generation,
|
|
181
|
+
across fork respawns. The measured Kubernetes shared-PID case reached this state;
|
|
182
|
+
its credentials and capability comparison are recorded in VERIFYING.md above.
|
|
183
|
+
|
|
184
|
+
There is a narrower startup window when the worker and probe have asymmetric
|
|
185
|
+
access: the worker can prove the topology unsafe, but the probe cannot. Before
|
|
186
|
+
that worker reaches the monitor and publishes `topology-unsafe`, the probe can
|
|
187
|
+
accept inherited marks only if the volume already contains a fresh heartbeat
|
|
188
|
+
from an older worker, or from one that also could not prove the topology. Both
|
|
189
|
+
conditions are required. A normal Kubernetes exec probe inherits the
|
|
190
|
+
container's credentials and reaches the same procfs verdict itself; after a
|
|
191
|
+
0.1.3 worker has proved the topology, `topology-unsafe` also survives every
|
|
192
|
+
later restart in the same documented `emptyDir`. The residual window therefore
|
|
193
|
+
requires an in-place restart that also changes the image or effective access,
|
|
194
|
+
or a marks volume that outlives the pod contrary to this setup. In that case the
|
|
195
|
+
probe retains the explicitly degraded 0.1.2 guarantee until the current worker
|
|
196
|
+
publishes its verdict.
|
|
197
|
+
|
|
198
|
+
### No generation at all
|
|
199
|
+
|
|
200
|
+
If `/proc/self/ns/mnt` or `/proc/1/stat` cannot yield a generation, the probe
|
|
201
|
+
falls back to the 0.1.1 freshness-only check and may accept fresh inherited
|
|
202
|
+
marks. The worker warns once for the uninterrupted unavailable state. A verified
|
|
203
|
+
topology clears the report flag so a later degradation is reported again:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
WARN [liveness] slot 0: no container generation (cannot read /proc/1/stat (Errno::EACCES)):
|
|
207
|
+
falling back to freshness only, which cannot reject a heartbeat inherited from a previous
|
|
208
|
+
container
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
A zero start time uses the detail `PID 1 start time is zero; possible hostPID or
|
|
212
|
+
virtualized procfs` in that warning. The field counts clock ticks after boot,
|
|
213
|
+
so zero is valid, not a parse error. It was observed for node init in a host-PID
|
|
214
|
+
pod, but may also be exposed for a legitimate PID 1 by virtualized procfs.
|
|
215
|
+
Without measurements of those runtimes, zero alone remains `unavailable`.
|
|
216
|
+
|
|
217
|
+
Off Linux the same freshness-only fallback is **silent**, an intentional
|
|
218
|
+
compatibility mode. Degraded Linux warnings also stay off the probe's stdout:
|
|
219
|
+
they belong in the worker log, not repeated beside unrelated failures in
|
|
220
|
+
`Unhealthy` events. A proven unsafe topology is different: it is the probe's
|
|
221
|
+
verdict.
|
|
222
|
+
|
|
223
|
+
### The probe must run in the worker's container
|
|
119
224
|
|
|
120
225
|
The worker and its exec probe must run in the **same container**. A neighbouring
|
|
121
|
-
sidecar can mount the same `emptyDir`, but it has a different mount namespace
|
|
122
|
-
|
|
226
|
+
sidecar can mount the same `emptyDir`, but it has a different mount namespace and
|
|
227
|
+
therefore treats the worker's marks as belonging to another container.
|
|
123
228
|
Kubernetes exec probes already run inside the container they check; do not move
|
|
124
229
|
`kicks-liveness` into a separate health sidecar.
|
|
125
230
|
|
|
126
231
|
## A respawn loop is reported once per grace window, not once per respawn
|
|
127
232
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
The count is therefore kept in the marks directory, which survives the fork.
|
|
135
|
-
The start line is logged once, repeated starts within the grace window
|
|
136
|
-
(`startup_grace_ticks × tick`) are silent, and after that one ERROR per window
|
|
137
|
-
reports the elapsed time and the number of starts:
|
|
233
|
+
The supervisor respawns a fork that cannot subscribe after `start_worker_delay`,
|
|
234
|
+
potentially before its monitor reaches startup grace. `attempt-<slot>` therefore
|
|
235
|
+
keeps the count and window start outside process memory. The first incarnation
|
|
236
|
+
logs startup, further starts within `startup_grace_ticks × tick` are silent, and
|
|
237
|
+
each expired window produces one ERROR with elapsed time and cumulative starts:
|
|
138
238
|
|
|
139
239
|
```
|
|
140
240
|
[liveness] slot 0: not healthy 62s over 305 starts, expected 5 consumers
|
|
141
241
|
```
|
|
142
242
|
|
|
143
|
-
The
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
generation. If a container is restarted before its slot has ever become
|
|
148
|
-
healthy, the replacement continues the same count: its repeated `started:` line
|
|
149
|
-
is suppressed, and an expired grace window may immediately report an ERROR that
|
|
150
|
-
includes starts from the previous container. This is diagnostic state only; it
|
|
151
|
-
does not participate in the probe result, and the first healthy tick removes
|
|
152
|
-
it. Treat the count as "starts since this slot was last healthy in this pod",
|
|
153
|
-
not "starts in this container".
|
|
243
|
+
The generation in the file rejects an earlier container's count. A replacement
|
|
244
|
+
logs its own `started:` and cannot escalate over starts it did not take; the
|
|
245
|
+
first healthy tick clears the counter. Without a generation, the old volume-wide
|
|
246
|
+
scope remains: read it as starts since this slot was last healthy in the pod.
|
|
154
247
|
|
|
155
248
|
## Do not install both `kicks` and `sneakers`
|
|
156
249
|
|
data/docs/SETUP.md
CHANGED
|
@@ -148,10 +148,19 @@ config.logger = Rails.logger
|
|
|
148
148
|
| `enabled` | `true` | start the monitor thread | `true` or `false` |
|
|
149
149
|
| `tick` | `10` | seconds between checks | positive number smaller than `max_age` |
|
|
150
150
|
| `startup_grace_ticks` | `6` | unhealthy startup ticks before one ERROR | positive integer |
|
|
151
|
+
| `shutdown_grace` | `60` | seconds to keep publishing during shutdown | positive finite number |
|
|
151
152
|
|
|
152
153
|
Invalid combinations are rejected before the monitor starts instead of running
|
|
153
154
|
one that is guaranteed to publish stale marks or never report a stalled startup.
|
|
154
155
|
|
|
156
|
+
`shutdown_grace` defaults from `KICKS_LIVENESS_SHUTDOWN_GRACE`; the initializer
|
|
157
|
+
wins when both are set. Set it to at least your Kubernetes
|
|
158
|
+
`terminationGracePeriodSeconds`. It bounds the shutdown exemption, not the
|
|
159
|
+
restart time: after expiry the last mark still has to exceed `max_age` and the
|
|
160
|
+
probe's failure budget still applies. The environment accepts positive integer
|
|
161
|
+
seconds and silently uses 60 for invalid or blank values. Invalid initializer
|
|
162
|
+
values raise `ArgumentError`; fractional positive seconds are accepted.
|
|
163
|
+
|
|
155
164
|
`dir` and `max_age` are **not** here — they come from environment variables
|
|
156
165
|
only. See
|
|
157
166
|
[DESIGN.md](DESIGN.md#what-is-configurable-and-where) for why, and
|
|
@@ -169,6 +178,16 @@ Sneakers::WorkerGroup.ancestors.include?(KicksLiveness::Hooks::WorkerGroup) # =>
|
|
|
169
178
|
Both must be `true` before the runner starts. If either is `false`, either
|
|
170
179
|
`install!` has not run, or it ran before `kicks`/`sneakers` was loaded.
|
|
171
180
|
|
|
181
|
+
When `expected` is missing, the probe asks whether `KicksLiveness.install!` was
|
|
182
|
+
called before the runner started. This is a hint, not a diagnosis: a slow boot
|
|
183
|
+
or a removed marks directory can produce the same result. It does not install
|
|
184
|
+
hooks or change the failing exit status. The complete output is shown in the
|
|
185
|
+
[image check](KUBERNETES.md#where-your-image-puts-its-gems).
|
|
186
|
+
|
|
187
|
+
When migrating from another probe, confirm the `[liveness] slot 0: started:`
|
|
188
|
+
line and the `expected` file on dev before removing the old probe gem, its
|
|
189
|
+
initializer and its rake tasks.
|
|
190
|
+
|
|
172
191
|
Once workers are running, the marks directory is the other half of the answer:
|
|
173
192
|
|
|
174
193
|
```
|
|
@@ -184,3 +203,14 @@ One `worker-<slot>` file per fork, plus `expected` and the current container
|
|
|
184
203
|
have not finished subscribing. A slot that keeps restarting without ever
|
|
185
204
|
subscribing also leaves an `attempt-<slot>` file, which is removed as soon as
|
|
186
205
|
that slot becomes healthy.
|
|
206
|
+
|
|
207
|
+
The `generation` file is authoritative only while procfs generation detection is
|
|
208
|
+
available: where it is not, the worker writes no generation and a file left by
|
|
209
|
+
an earlier container stays visible, so `kubectl exec ... cat <dir>/generation`
|
|
210
|
+
can show a stale incarnation. It is not deleted in that state on purpose —
|
|
211
|
+
removing it could fail a concurrently running probe that does have a valid
|
|
212
|
+
generation, for a reason that has nothing to do with the worker's health. Two
|
|
213
|
+
further files appear only when something is wrong: `topology-unsafe`, which
|
|
214
|
+
fails every probe until the pod is recreated, and `topology-report`, which is
|
|
215
|
+
how the worker keeps what it says about the topology to one line. Both are explained in
|
|
216
|
+
[LIMITATIONS.md](LIMITATIONS.md#container-generations-need-linux-procfs-and-a-private-pid-namespace).
|