|
75 | 75 | ) config.mcl.services; |
76 | 76 | in |
77 | 77 | { |
78 | | - assertions = lib.mapAttrsToList ( |
79 | | - serviceName: serviceConfig: |
80 | | - let |
81 | | - cfg = serviceConfig.healthcheck; |
82 | | - in |
83 | | - lib.mkIf (cfg != null && cfg.readiness-probe.enable) { |
84 | | - assertion = cfg.exec != null; |
85 | | - message = "When healthcheck.readiness-probe is enabled, you must define `healthcheck.exec` with the service command. (${serviceName})"; |
86 | | - } |
87 | | - ) servicesWithHealthcheck; |
88 | 78 | systemd = { |
89 | 79 | timers = lib.mapAttrs' ( |
90 | 80 | mainServiceName: serviceConfig: |
|
120 | 110 | # If the TimeoutStartSec is not infinity, it can cause the service to fail, because the readiness probe is considered part of the startup. |
121 | 111 | serviceConfig.TimeoutStartSec = lib.mkForce "infinity"; |
122 | 112 |
|
123 | | - # We replace the ExecStart with a script that runs the readiness probe in the background, and the original service command in the foreground. |
124 | | - serviceConfig.ExecStart = |
| 113 | + # We add a ExecStartPost with a script that runs the readiness probe |
| 114 | + serviceConfig.ExecStartPost = |
125 | 115 | let |
126 | 116 | scriptPath = lib.makeBinPath ( |
127 | 117 | [ |
|
133 | 123 | ++ (serviceConfig.path or [ ]) |
134 | 124 | ); |
135 | 125 | in |
136 | | - lib.mkForce ( |
137 | 126 | pkgs.writeShellScript "${mainServiceName}-readiness-check" '' |
138 | 127 | #!${pkgs.runtimeShell} |
139 | 128 | set -o nounset |
140 | 129 | export PATH="${scriptPath}:$PATH" |
141 | 130 |
|
142 | | - check() { |
143 | | - echo "Health check: starting background readiness probe for ${mainServiceName}." |
144 | | - sleep ${toString probeCfg.initialDelay} |
145 | | - retryCount=${toString probeCfg.retryCount} |
146 | | - while true; do |
147 | | - if (timeout ${toString probeCfg.timeout}s ${probeCfg.command} &> /dev/null); then |
148 | | - echo "Health check: probe successful. Notifying systemd that the service is ready." |
149 | | - systemd-notify --ready --status="${probeCfg.statusReadyMessage}" |
150 | | - return 0 |
151 | | - else |
152 | | - echo "Health check: probe not successful. Notifying systemd that the service is still waiting. Retrying in ${toString probeCfg.interval} seconds..." |
153 | | - systemd-notify --status="${probeCfg.statusWaitingMessage}" |
154 | | - if [[ ''${retryCount} -ne -1 ]]; then |
155 | | - retryCount=$((retryCount - 1)) |
156 | | - if [[ ''${retryCount} -le 0 ]]; then |
157 | | - echo "Health check: probe failed after maximum retries. Exiting." |
158 | | - exit 1 |
159 | | - fi |
| 131 | + echo "Health check: starting background readiness probe for ${mainServiceName}." |
| 132 | + sleep ${toString probeCfg.initialDelay} |
| 133 | + retryCount=${toString probeCfg.retryCount} |
| 134 | + while true; do |
| 135 | + if (timeout ${toString probeCfg.timeout}s ${probeCfg.command} &> /dev/null); then |
| 136 | + echo "Health check: probe successful. Notifying systemd that the service is ready." |
| 137 | + systemd-notify --ready --status="${probeCfg.statusReadyMessage}" |
| 138 | + exit 0 |
| 139 | + else |
| 140 | + echo "Health check: probe not successful. Notifying systemd that the service is still waiting. Retrying in ${toString probeCfg.interval} seconds..." |
| 141 | + systemd-notify --status="${probeCfg.statusWaitingMessage}" |
| 142 | + if [[ ''${retryCount} -ne -1 ]]; then |
| 143 | + retryCount=$((retryCount - 1)) |
| 144 | + if [[ ''${retryCount} -le 0 ]]; then |
| 145 | + echo "Health check: probe failed after maximum retries. Exiting." |
| 146 | + exit 1 |
160 | 147 | fi |
161 | 148 | fi |
162 | | - sleep ${toString probeCfg.interval} |
163 | | - done |
164 | | - } |
165 | | -
|
166 | | - if [[ -n "''${NOTIFY_SOCKET:-}" ]]; then |
167 | | - check & |
168 | | - else |
169 | | - echo "Health check: NOTIFY_SOCKET not set. Cannot run readiness probe." >&2 |
170 | | - exit 1 |
171 | | - fi |
172 | | -
|
173 | | - ${cfg.exec} |
174 | | - '' |
175 | | - ); |
| 149 | + fi |
| 150 | + sleep ${toString probeCfg.interval} |
| 151 | + done |
| 152 | + ''; |
176 | 153 | } |
177 | 154 | )) |
178 | 155 | ) servicesWithHealthcheck; |
|
246 | 223 | description = "Additional programs to add to the PATH for health checks."; |
247 | 224 | }; |
248 | 225 |
|
249 | | - # The main command for the service, required when readiness-probe is on. |
250 | | - exec = lib.mkOption { |
251 | | - type = lib.types.str; |
252 | | - description = '' |
253 | | - The actual command to run for the service. |
254 | | - This MUST be used instead of `script` or `serviceConfig.ExecStart` |
255 | | - when `readiness-probe.enable` is true. |
256 | | - ''; |
257 | | - }; |
258 | | - |
259 | 226 | # The new readiness probe that uses the notify pattern. |
260 | 227 | readiness-probe = lib.mkOption { |
261 | 228 | type = lib.types.submodule readinessProbeOptions; |
|
0 commit comments