mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-06 16:02:19 +00:00
refactor(worker): auto-select podman vs provided Docker daemon; drop container_runtime config
Remove the per-worker-group container_runtime option entirely and decide the docker runtime automatically: for a `# docker` job, if a Docker daemon is already provided (DOCKER_HOST set or /var/run/docker.sock mounted) use it (backwards compatible, unchanged); otherwise start a per-job rootless podman (its own ephemeral daemon, torn down with the job). podman must be present (the *-full images) — else a clear error. Removed: container_runtime from WorkerConfigOpt/WorkerConfig/load_worker_config/ WORKER_CONFIG/monitor, the CE config allowlist entry, and the frontend "Container runtime" toggle. docker-compose / README: the windmill_worker_docker example no longer sets CONTAINER_RUNTIME (podman is automatic when no daemon is provided). Verified e2e: a worker with no CONTAINER_RUNTIME and no DOCKER_HOST auto-runs a # docker job via per-job podman; the provided-daemon (legacy) path is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
40a387a17b
commit
cccd8b9847
@@ -208,14 +208,14 @@ docker compose up -d
|
||||
Go to http://localhost - default credentials: `admin@windmill.dev` / `changeme`
|
||||
|
||||
> [!NOTE]
|
||||
> To run `# docker` scripts (bash scripts with the `# docker` annotation), add a
|
||||
> dedicated worker group with the rootless **podman** container runtime: each docker
|
||||
> job runs in its own ephemeral rootless podman, torn down with the job — no
|
||||
> privileged daemon and no host Docker socket, with your scripts unchanged. Use a
|
||||
> `*-full` image (ships podman), set `CONTAINER_RUNTIME=podman`, and tag your docker
|
||||
> scripts to route them to that group. See the commented `windmill_worker_docker`
|
||||
> service in [docker-compose.yml](./docker-compose.yml), or the **Container runtime**
|
||||
> toggle on a worker group in the UI (Workers → worker group config).
|
||||
> To run `# docker` scripts (bash scripts with the `# docker` annotation): on a worker
|
||||
> with **no Docker daemon provided** (no `DOCKER_HOST`, no mounted `/var/run/docker.sock`),
|
||||
> Windmill automatically runs each docker job in its own ephemeral **rootless podman**,
|
||||
> torn down with the job — no privileged daemon, no host socket, scripts unchanged. Just
|
||||
> use a `*-full` image (ships podman) and tag your docker scripts to a dedicated worker
|
||||
> group; see the commented `windmill_worker_docker` service in
|
||||
> [docker-compose.yml](./docker-compose.yml). To use an external/host Docker daemon
|
||||
> instead (legacy), provide `DOCKER_HOST` or mount `/var/run/docker.sock`.
|
||||
|
||||
**Using an external database**: Set `DATABASE_URL` in `.env` to point to your managed Postgres (AWS RDS, GCP Cloud SQL, Azure, Neon, etc.) and set db replicas to 0.
|
||||
|
||||
|
||||
@@ -315,9 +315,6 @@ pub async fn initial_load(
|
||||
additional_python_paths: None,
|
||||
pip_local_dependencies: None,
|
||||
native_mode,
|
||||
container_runtime: std::env::var("CONTAINER_RUNTIME")
|
||||
.ok()
|
||||
.filter(|x| !x.is_empty()),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,14 +143,12 @@ async fn update_config(
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let config = if name.starts_with("worker__") {
|
||||
// In CE, only allow setting worker_tags, cache_clear, init_bash, native_mode,
|
||||
// and container_runtime
|
||||
// In CE, only allow setting worker_tags, cache_clear, init_bash, and native_mode
|
||||
serde_json::json!({
|
||||
"worker_tags": config.get("worker_tags"),
|
||||
"cache_clear": config.get("cache_clear"),
|
||||
"init_bash": config.get("init_bash"),
|
||||
"native_mode": config.get("native_mode"),
|
||||
"container_runtime": config.get("container_runtime")
|
||||
"native_mode": config.get("native_mode")
|
||||
})
|
||||
} else {
|
||||
config
|
||||
|
||||
@@ -234,7 +234,6 @@ lazy_static::lazy_static! {
|
||||
pip_local_dependencies: Default::default(),
|
||||
env_vars: Default::default(),
|
||||
native_mode: false,
|
||||
container_runtime: None,
|
||||
});
|
||||
|
||||
pub static ref WORKER_PULL_QUERIES: arc_swap::ArcSwap<Vec<String>> = arc_swap::ArcSwap::from_pointee(vec![]);
|
||||
@@ -1961,10 +1960,6 @@ pub async fn load_worker_config(
|
||||
.or_else(|| load_additional_python_paths_from_env()),
|
||||
env_vars: resolved_env_vars,
|
||||
native_mode,
|
||||
container_runtime: config
|
||||
.container_runtime
|
||||
.or_else(|| std::env::var("CONTAINER_RUNTIME").ok())
|
||||
.and_then(|x| if x.is_empty() { None } else { Some(x) }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2054,11 +2049,6 @@ pub struct WorkerConfigOpt {
|
||||
pub env_vars_static: Option<HashMap<String, String>>,
|
||||
pub env_vars_allowlist: Option<Vec<String>>,
|
||||
pub native_mode: Option<bool>,
|
||||
/// Container runtime for docker-mode jobs on this worker group. When set to
|
||||
/// "podman", the worker starts a rootless podman service and points
|
||||
/// DOCKER_HOST at it (see start_container_runtime). None = no managed runtime
|
||||
/// (legacy dind/host-socket via DOCKER_HOST/socket still works if present).
|
||||
pub container_runtime: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for WorkerConfigOpt {
|
||||
@@ -2077,7 +2067,6 @@ impl Default for WorkerConfigOpt {
|
||||
env_vars_static: Default::default(),
|
||||
env_vars_allowlist: Default::default(),
|
||||
native_mode: Default::default(),
|
||||
container_runtime: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2096,13 +2085,12 @@ pub struct WorkerConfig {
|
||||
pub pip_local_dependencies: Option<Vec<String>>,
|
||||
pub env_vars: HashMap<String, String>,
|
||||
pub native_mode: bool,
|
||||
pub container_runtime: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WorkerConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "WorkerConfig {{ worker_tags: {:?}, priority_tags_sorted: {:?}, dedicated_worker: {:?}, dedicated_workers: {:?}, init_bash: {:?}, periodic_script_bash: {:?}, periodic_script_interval_seconds: {:?}, cache_clear: {:?}, additional_python_paths: {:?}, pip_local_dependencies: {:?}, env_vars: {:?}, native_mode: {:?}, container_runtime: {:?} }}",
|
||||
self.worker_tags, self.priority_tags_sorted, self.dedicated_worker, self.dedicated_workers, self.init_bash, self.periodic_script_bash, self.periodic_script_interval_seconds, self.cache_clear, self.additional_python_paths, self.pip_local_dependencies, self.env_vars.iter().map(|(k, v)| format!("{}: {}{} ({} chars)", k, &v[..3.min(v.len())], "***", v.len())).collect::<Vec<String>>().join(", "), self.native_mode, self.container_runtime)
|
||||
write!(f, "WorkerConfig {{ worker_tags: {:?}, priority_tags_sorted: {:?}, dedicated_worker: {:?}, dedicated_workers: {:?}, init_bash: {:?}, periodic_script_bash: {:?}, periodic_script_interval_seconds: {:?}, cache_clear: {:?}, additional_python_paths: {:?}, pip_local_dependencies: {:?}, env_vars: {:?}, native_mode: {:?} }}",
|
||||
self.worker_tags, self.priority_tags_sorted, self.dedicated_worker, self.dedicated_workers, self.init_bash, self.periodic_script_bash, self.periodic_script_interval_seconds, self.cache_clear, self.additional_python_paths, self.pip_local_dependencies, self.env_vars.iter().map(|(k, v)| format!("{}: {}{} ({} chars)", k, &v[..3.min(v.len())], "***", v.len())).collect::<Vec<String>>().join(", "), self.native_mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,27 +96,6 @@ pub async fn handle_bash_job(
|
||||
let mut logs1 = "\n\n--- BASH CODE EXECUTION ---\n".to_string();
|
||||
if annotation.docker {
|
||||
logs1.push_str("docker mode\n");
|
||||
// If neither DOCKER_HOST nor the host socket is available, the docker CLI
|
||||
// in the script would fail with a generic "cannot connect to the Docker
|
||||
// daemon" error. Surface a Windmill-specific hint instead. Skipped when
|
||||
// container_runtime=podman is set: a per-job rootless podman is started
|
||||
// below and provides the daemon.
|
||||
if std::env::var("DOCKER_HOST").is_err()
|
||||
&& !std::path::Path::new("/var/run/docker.sock").exists()
|
||||
&& windmill_common::worker::WORKER_CONFIG
|
||||
.load()
|
||||
.container_runtime
|
||||
.as_deref()
|
||||
!= Some("podman")
|
||||
{
|
||||
logs1.push_str(
|
||||
"WARNING: docker mode is set but no Docker daemon is reachable from this worker \
|
||||
(DOCKER_HOST is unset and /var/run/docker.sock is not mounted). Give this worker \
|
||||
Docker access: with docker-compose, enable the dind sidecar (`docker compose \
|
||||
--profile dind up -d` and uncomment DOCKER_HOST in windmill_worker); with the Helm \
|
||||
chart, set `exposeHostDocker: true`; otherwise set DOCKER_HOST or mount a Docker socket.\n",
|
||||
);
|
||||
}
|
||||
}
|
||||
if annotation.sandbox {
|
||||
logs1.push_str("sandbox mode (nsjail)\n");
|
||||
@@ -214,20 +193,18 @@ exit $exit_status
|
||||
// Use nsjail if globally enabled OR if script has #sandbox annotation
|
||||
let nsjail = (is_sandboxing_enabled() || annotation.sandbox) && is_regular_job;
|
||||
|
||||
// Every docker job gets its OWN ephemeral rootless podman instance (started here,
|
||||
// torn down when this guard drops at the end of the function) so no container it
|
||||
// spawns can outlive the job, in any sandbox mode. Gated strictly on
|
||||
// container_runtime=podman: legacy docker access (an externally-provided
|
||||
// DOCKER_HOST or a mounted /var/run/docker.sock, without container_runtime=podman)
|
||||
// is left exactly as before.
|
||||
// Docker runtime selection: if a Docker daemon is already provided — DOCKER_HOST
|
||||
// set, or the host socket mounted at /var/run/docker.sock — use it (backwards
|
||||
// compatible, unchanged). Otherwise start a per-job rootless podman instance for
|
||||
// this docker job (its own ephemeral daemon, torn down when this guard drops at
|
||||
// the end of the function, so no container it spawns can outlive the job, in any
|
||||
// sandbox mode). Requires podman in the image (the *-full images) — else
|
||||
// start_per_job_podman returns a clear error.
|
||||
#[cfg(feature = "dind")]
|
||||
let per_job_podman: Option<PerJobPodman> = if annotation.docker
|
||||
&& windmill_common::worker::WORKER_CONFIG
|
||||
.load()
|
||||
.container_runtime
|
||||
.as_deref()
|
||||
== Some("podman")
|
||||
{
|
||||
let docker_daemon_provided = std::env::var("DOCKER_HOST").is_ok()
|
||||
|| std::path::Path::new("/var/run/docker.sock").exists();
|
||||
#[cfg(feature = "dind")]
|
||||
let per_job_podman: Option<PerJobPodman> = if annotation.docker && !docker_daemon_provided {
|
||||
Some(start_per_job_podman(job_dir).await?)
|
||||
} else {
|
||||
None
|
||||
@@ -254,7 +231,7 @@ exit $exit_status
|
||||
};
|
||||
|
||||
// Forward DOCKER_HOST to the bash script in docker mode: the per-job podman
|
||||
// socket when container_runtime=podman, else the legacy DOCKER_HOST / docker socket.
|
||||
// socket when one was started, else the provided DOCKER_HOST / docker socket.
|
||||
let docker_envs: Vec<(&str, String)> = if annotation.docker {
|
||||
if let Some(dh) = &docker_host_for_script {
|
||||
vec![("DOCKER_HOST", dh.clone())]
|
||||
@@ -543,8 +520,9 @@ async fn start_per_job_podman(job_dir: &str) -> Result<PerJobPodman, Error> {
|
||||
.spawn()
|
||||
.map_err(|e| {
|
||||
Error::ExecutionErr(format!(
|
||||
"container_runtime=podman but failed to start the per-job podman service: {e}. \
|
||||
Use a windmill *-full image (ships podman)."
|
||||
"no Docker daemon provided (DOCKER_HOST/socket) and failed to start the per-job \
|
||||
podman runtime: {e}. Use a windmill *-full image (ships podman), or provide a \
|
||||
Docker daemon via DOCKER_HOST or a mounted /var/run/docker.sock."
|
||||
))
|
||||
})?;
|
||||
// Wait (up to ~10s) for the rootless podman service socket to appear.
|
||||
|
||||
+3
-1
@@ -106,7 +106,9 @@ services:
|
||||
# - MODE=worker
|
||||
# - WORKER_GROUP=docker
|
||||
# - WORKER_TAGS=docker # only serve jobs tagged "docker" (set that tag on your docker scripts)
|
||||
# - CONTAINER_RUNTIME=podman
|
||||
# # No DOCKER_HOST / docker socket is provided, so docker jobs automatically use
|
||||
# # the bundled rootless podman (per job). Provide a DOCKER_HOST or mount
|
||||
# # /var/run/docker.sock instead to use an external/host Docker daemon (legacy).
|
||||
# depends_on:
|
||||
# db:
|
||||
# condition: service_healthy
|
||||
|
||||
@@ -100,7 +100,6 @@
|
||||
min_alive_workers_alert_threshold?: number
|
||||
autoscaling?: AutoscalingConfig
|
||||
native_mode?: boolean
|
||||
container_runtime?: string
|
||||
} = $state({})
|
||||
|
||||
function loadNConfig() {
|
||||
@@ -206,7 +205,6 @@
|
||||
periodic_script_bash?: string
|
||||
periodic_script_interval_seconds?: number
|
||||
native_mode?: boolean
|
||||
container_runtime?: string
|
||||
}
|
||||
activeWorkers: number
|
||||
customTags: string[] | undefined
|
||||
@@ -643,40 +641,6 @@
|
||||
</Label>
|
||||
{/if}
|
||||
|
||||
{#if nconfig !== undefined}
|
||||
<div class="mt-8"></div>
|
||||
<Label label="Container runtime">
|
||||
{#snippet header()}
|
||||
<Tooltip>
|
||||
{#snippet text()}
|
||||
When set, docker-mode jobs (bash scripts with the "# docker" annotation)
|
||||
run against a rootless podman daemon started by the worker, instead of a
|
||||
privileged docker-in-docker sidecar or the host Docker socket. Requires a
|
||||
worker image that ships podman (the *-full images). Run the worker group as
|
||||
a non-root user for an unprivileged (rootless) runtime.
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{/snippet}
|
||||
<Toggle
|
||||
size="sm"
|
||||
options={{ right: 'Enable container runtime (rootless podman)' }}
|
||||
checked={nconfig?.container_runtime === 'podman'}
|
||||
on:change={(ev) => {
|
||||
if (nconfig !== undefined) {
|
||||
nconfig.container_runtime = ev.detail ? 'podman' : undefined
|
||||
}
|
||||
}}
|
||||
disabled={!canEditConfig}
|
||||
/>
|
||||
{#if nconfig?.container_runtime === 'podman' && (nconfig?.worker_tags == undefined || nconfig.worker_tags.length === 0)}
|
||||
<Alert size="xs" type="warning" title="No tags on this worker group">
|
||||
The container runtime is enabled but this worker group has no tags, so no
|
||||
docker job can be routed to it. Add the tag(s) your docker scripts use.
|
||||
</Alert>
|
||||
{/if}
|
||||
</Label>
|
||||
{/if}
|
||||
|
||||
{#if nconfig !== undefined}
|
||||
<div class="mt-8"></div>
|
||||
<Label
|
||||
|
||||
Reference in New Issue
Block a user