warn when a host's kernel has cgroup memory accounting disabled
This commit is contained in:
6 files changed
+121
-6
No files matched your search
@@ -12505,6 +12505,9 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"memoryLimitSupported": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12523,7 +12526,8 @@
|
||||
"serverVersion": "string",
|
||||
"containers": 0,
|
||||
"images": 0,
|
||||
"name": "string"
|
||||
"name": "string",
|
||||
"memoryLimitSupported": true
|
||||
},
|
||||
"hawser": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Whether the daemon's kernel supports per-container memory accounting, read from
|
||||
* Docker `/info`. When the cgroup memory controller is disabled in the kernel boot
|
||||
* config (common on Raspberry Pi and some ARM boards), the daemon reports
|
||||
* `MemoryLimit: false` and every container's memory usage shows as 0. Docker itself
|
||||
* detects this - we surface it so the user isn't left guessing why memory is blank.
|
||||
*/
|
||||
|
||||
export interface MemorySupportInfo {
|
||||
/** true = the UI can show a warning that memory accounting is off. */
|
||||
warn: boolean;
|
||||
/** Docker's own `/info.MemoryLimit`. */
|
||||
memoryLimitSupported: boolean;
|
||||
/** Docker's own `/info.SwapLimit`. */
|
||||
swapLimitSupported: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the memory-support flags from a Docker `/info` response. `MemoryLimit`/
|
||||
* `SwapLimit` are booleans; a missing/undefined field is treated as SUPPORTED (no
|
||||
* warning) so an older daemon or a partial info payload never produces a false alarm.
|
||||
*/
|
||||
export function memorySupportFromInfo(info: { MemoryLimit?: boolean; SwapLimit?: boolean } | null | undefined): MemorySupportInfo {
|
||||
const memoryLimitSupported = info?.MemoryLimit !== false;
|
||||
const swapLimitSupported = info?.SwapLimit !== false;
|
||||
return { warn: !memoryLimitSupported, memoryLimitSupported, swapLimitSupported };
|
||||
}
|
||||
|
||||
/** Link to the manual section explaining how to enable cgroup memory accounting. */
|
||||
export const MEMORY_SUPPORT_DOC_URL = 'https://dockhand.pro/manual/#troubleshooting-rpi-memory';
|
||||
@@ -4,6 +4,7 @@ import type { DockerClientConfig } from '$lib/server/docker';
|
||||
import { getEnvironment } from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { isSafeNotificationUrl } from '$lib/server/url-safety';
|
||||
import { memorySupportFromInfo } from '$lib/utils/memory-support';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
interface TestConnectionRequest {
|
||||
@@ -58,7 +59,7 @@ function buildDockerClientConfig(config: TestConnectionRequest): DockerClientCon
|
||||
* description: Pass environmentId to fall back to that saved environment's stored secrets (hawserToken, tlsKey) for any secret field left blank in the body, so Test connection works when editing without re-entering the token.
|
||||
* body: {connectionType:string!, socketPath:string, host:string, port:integer, protocol:string, tlsCa:string, tlsCert:string, tlsKey:string, tlsSkipVerify:boolean, hawserToken:string, environmentId:integer}
|
||||
* body-example: {"connectionType":"socket","socketPath":"/var/run/docker.sock"}
|
||||
* resp-200: {success:boolean!, info:{serverVersion:string, containers:integer, images:integer, name:string}, hawser:{}}
|
||||
* resp-200: {success:boolean!, info:{serverVersion:string, containers:integer, images:integer, name:string, memoryLimitSupported:boolean}, hawser:{}}
|
||||
* resp-200-desc: success:false with a human-readable error message is also returned as HTTP 200 (connection failures are not transport errors)
|
||||
* resp-400: Host is required for direct/hawser-standard connection types
|
||||
* resp-403: The caller cannot access the environment named by environmentId
|
||||
@@ -177,13 +178,17 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const memSupport = memorySupportFromInfo(info);
|
||||
return json({
|
||||
success: true,
|
||||
info: {
|
||||
serverVersion: info.ServerVersion,
|
||||
containers: info.Containers,
|
||||
images: info.Images,
|
||||
name: info.Name
|
||||
name: info.Name,
|
||||
// The kernel's cgroup memory controller: false => per-container memory
|
||||
// shows 0 (common on Raspberry Pi). The modal warns on this at create time.
|
||||
memoryLimitSupported: memSupport.memoryLimitSupported
|
||||
},
|
||||
hawser: hawserInfo
|
||||
});
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
import { licenseStore } from '$lib/stores/license';
|
||||
import { formatDateTime, formatDate } from '$lib/stores/settings';
|
||||
import { getLabelColor, getLabelBgColor, parseLabels, MAX_LABELS } from '$lib/utils/label-colors';
|
||||
import { MEMORY_SUPPORT_DOC_URL } from '$lib/utils/memory-support';
|
||||
import { labelColorOverrides } from '$lib/stores/label-colors';
|
||||
import EventTypesEditor from './EventTypesEditor.svelte';
|
||||
import UpdatesTab from './tabs/UpdatesTab.svelte';
|
||||
@@ -494,6 +495,9 @@
|
||||
// Test connection state
|
||||
let testingConnection = $state(false);
|
||||
let testResult = $state<{ success: boolean; info?: any; error?: string; isEdgeMode?: boolean } | null>(null);
|
||||
// Shown when a successful Test connection reveals the daemon's kernel has cgroup
|
||||
// memory accounting disabled (per-container memory shows 0 - common on Raspberry Pi).
|
||||
let memWarnOpen = $state(false);
|
||||
|
||||
// Socket detection state
|
||||
let detectingSockets = $state(false);
|
||||
@@ -758,6 +762,9 @@
|
||||
toast.info('Edge mode - connection will be tested when agent connects');
|
||||
} else {
|
||||
toast.success(`Connected! Docker ${result.info.serverVersion} - ${result.info.containers} containers`);
|
||||
// Docker reports the kernel's cgroup memory controller is off -> container
|
||||
// memory will show 0. Surface it now so the user isn't left guessing.
|
||||
if (result.info.memoryLimitSupported === false) memWarnOpen = true;
|
||||
}
|
||||
} else {
|
||||
toast.error(result.error || 'Connection failed');
|
||||
@@ -2005,10 +2012,11 @@
|
||||
A direct daemon shares no filesystem with Dockhand. When set, Dockhand copies each
|
||||
stack's folder to
|
||||
<code class="bg-muted px-1 rounded"><this path>/<stack></code>
|
||||
<span class="font-medium text-foreground">on the remote host</span> so the backup
|
||||
<span class="font-medium text-foreground">on the remote host</span>
|
||||
<span class="font-medium text-foreground">on each deploy</span> so the backup
|
||||
helper can read the compose and config, and rewrites relative binds
|
||||
(<code class="bg-muted px-1 rounded">./data</code>) to that host path so they resolve
|
||||
on the remote daemon.
|
||||
on the remote daemon. After setting this, redeploy a stack once so its files are staged there.
|
||||
</p>
|
||||
<p class="text-muted-foreground">
|
||||
Leave empty to skip this: the stack won't be backupable and a relative bind resolves
|
||||
@@ -2034,6 +2042,8 @@
|
||||
Absolute path on the remote host where Dockhand keeps this stack's files, so its compose
|
||||
and config are backupable and relative binds resolve on the remote daemon. Leave empty to
|
||||
use only absolute paths or named volumes.
|
||||
<span class="text-foreground font-medium">Takes effect on the next deploy</span> - after
|
||||
setting this, redeploy each stack so Dockhand stages its files there.
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
@@ -3146,6 +3156,36 @@
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<!-- Kernel cgroup memory accounting is disabled on this daemon (container memory shows 0). -->
|
||||
<Dialog.Root bind:open={memWarnOpen}>
|
||||
<Dialog.Content class="max-w-md">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title class="flex items-center gap-2 text-amber-600 dark:text-amber-400">
|
||||
<AlertTriangle class="w-5 h-5" />
|
||||
Container memory won't be shown
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<div class="text-sm text-muted-foreground space-y-3">
|
||||
<p>
|
||||
This host's kernel has cgroup memory accounting disabled, so Docker can't report
|
||||
per-container memory - it will show <span class="font-medium text-foreground whitespace-nowrap">0 B</span>
|
||||
for every container. This is common on Raspberry Pi and some ARM boards.
|
||||
</p>
|
||||
<p>
|
||||
It's fixed in the kernel boot config, not in Dockhand. See the guide:
|
||||
<a href={MEMORY_SUPPORT_DOC_URL} target="_blank" rel="noopener noreferrer"
|
||||
class="text-primary hover:underline inline-flex items-center gap-1">
|
||||
enabling container memory accounting
|
||||
<ExternalLink class="w-3 h-3" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button onclick={() => (memWarnOpen = false)}>OK</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<!-- Env-rename confirmation: only fires for socket/direct envs where the
|
||||
server actually moves $DATA_DIR/stacks/<oldName> → <newName>. -->
|
||||
<Dialog.Root bind:open={showRenameConfirm}>
|
||||
|
||||
Reference in new issue
Block a user