news 2026/9/14 15:06:00

Windmill 实例配置即代码(IaC)完整指南:sync-config 与 Kubernetes Operator

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Windmill 实例配置即代码(IaC)完整指南:sync-config 与 Kubernetes Operator

Windmill 实例配置即代码(IaC)完整指南:sync-config 与 Kubernetes Operator

【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill

Windmill 官方在examples/deploy/instance-config-iac/目录中提供了完整的实例配置即代码(Infrastructure-as-Code)示例,允许通过声明式 YAML 文件统一管理实例级配置(全局设置 global settings 与 worker group 配置),并自动同步到数据库。本文以该目录下的 README.md 为主体,结合 docker-compose.yml、windmill-config.yaml、k8s-windmill-instance.yaml 等真实文件以及 backend 源码,详细讲解sync-configCLI 与 Kubernetes Operator 两种部署模型的配置方法、敏感字段引用机制、Replace 同步语义与底层实现原理。

为什么需要实例配置即代码

Windmill 实例的配置项(License Key、SMTP、OAuth、保留策略、Worker 标签等)默认是通过管理界面或数据库直接维护的,这带来两个问题:配置变更不可追溯、无法在多个环境(开发/预发/生产)间复用。实例配置即代码把整份配置收敛为一个版本可控的 YAML 文件,随部署流程自动应用,实现:

  • 配置版本化:所有变更通过 git 留痕、可 review、可回滚;
  • 环境可复现:同一份 YAML 在不同环境使用不同 secret 即可拉起等效实例;
  • 声明式收敛:数据库状态与文件声明的状态保持完全一致(Replace 语义)。

Windmill 为此提供了两种部署模型,它们共用同一份 YAML 模式(InstanceConfig)与同一套 secret 引用机制

方案适用场景前置要求
sync-configCLIDocker Compose、虚拟机、CI/CD 流水线数据库访问权限
Kubernetes OperatorKubernetes 集群operatorfeature flag、RBAC 权限

配置文件结构:InstanceConfig模式

一份 Windmill 实例配置文件包含两个顶层键,对应windmill_common::instance_config::InstanceConfig结构体的两个字段(见 backend/windmill-common/src/instance_config.rs):

global_settings: # 实例级全局设置,存储在 global_settings 数据表中 base_url: "https://windmill.example.com" retention_period_secs: 2592000 # ... worker_configs: # Worker group 配置,存储在 config 表中、键名为 worker__<name> default: worker_tags: ["deno", "python3", "bun", "go", "bash"] gpu: dedicated_worker: "ws:f/gpu_inference" # ...

所有字段均为可选,只同步你在文件中显式声明的字段。global_settings会被扁平化为name → value键值对写入global_settings表;而worker_configs中每个组名对应 config 表中一条worker__<name>记录(例如worker__defaultworker__native),Worker 启动时按WORKER_GROUP环境变量查找对应配置。

从源码结构看,InstanceConfig还实现了to_settings_map()/from_db()等序列化与数据库读取方法(instance_config.rs),支持将数据库当前状态反向导出为 YAML,便于审计或迁移。

敏感字段引用:三种格式

凡是包含敏感信息的字段(License Key、OAuth secret、SMTP 密码等)都支持三种写法:

# 1. 明文直写(生产环境不推荐) license_key: "my-license-key" # 2. 环境变量引用(所有环境通用) license_key: envRef: "WM_LICENSE_KEY" # 3. Kubernetes Secret 引用(仅 K8s 场景) license_key: secretKeyRef: name: windmill-secrets # Secret 资源名 key: license-key # Secret 内的键名

在源码中,这两种引用分别由EnvRefSecretRef两个结构体承载,并通过serdeuntagged反序列化自动识别:字符串值按明文处理,{ envRef: "VAR" }反序列化为EnvRef{ secretKeyRef: { name, key } }反序列化为SecretRef(instance_config.rs)。

支持envRefsecretKeyRef的字段清单:

  • license_key
  • hub_api_secret
  • scim_token
  • smtp_settings.smtp_password
  • oauths.<provider>.secret(每个 OAuth 客户端各自的 secret)
  • custom_instance_pg_databases.user_pwd

方案一:Docker Compose 中使用sync-config

sync-config是 Windmill 二进制提供的一个子命令:读取 YAML 配置文件,从进程环境解析envRef引用,然后将结果同步到数据库。

底层工作原理

sync-config的完整执行链位于 backend/src/main.rs,大致分五步:

  1. 读取并解析 YAML 文件(serde_yml反序列化为InstanceConfig);
  2. 调用resolve_env_refsenvRef字段从容器环境变量解析为实际值,环境变量缺失会直接报错environment variable '<VAR>' not found
  3. 建立数据库连接(initial_connection);
  4. 读取数据库当前状态,计算 diff(使用Replace模式:文件中未声明的设置会被删除,ducklake_settingscustom_instance_pg_databases等受保护设置除外);
  5. 将变更写回数据库,输出Synced instance config from <path>

ApplyMode::Replace与受保护设置的过滤逻辑在diff_global_settings/diff_worker_configs中实现(instance_config.rs),受保护键列表位于 instance_config.rs,包括ducklake_user_pg_pwdducklake_settingscustom_instance_pg_databases——这些是运行时/凭据相关设置,配置同步永远不会将其清空。worker_configs也作为保留键被过滤,避免历史遗留的单 blob 记录干扰新式分组存储。

搭建步骤

仓库的 docker-compose.yml 提供了完整可运行的 Compose 示例,关键部分如下。

第一步:编写配置文件windmill-config.yaml(可直接参考仓库中的 windmill-config.yaml):

global_settings: base_url: "https://windmill.example.com" license_key: envRef: "WM_LICENSE_KEY" retention_period_secs: 2592000 # 30 天 job_default_timeout: 900 # 15 分钟 expose_metrics: false smtp_settings: smtp_host: "smtp.example.com" smtp_port: 587 smtp_from: "windmill@example.com" smtp_tls_implicit: false smtp_password: envRef: "SMTP_PASSWORD" oauths: google: id: "google-client-id" secret: envRef: "GOOGLE_OAUTH_SECRET" login_config: auth_url: "https://accounts.google.com/o/oauth2/v2/auth" token_url: "https://oauth2.googleapis.com/token" userinfo_url: "https://openidconnect.googleapis.com/v1/userinfo" scopes: ["openid", "profile", "email"] custom_tags: - gpu - high-mem worker_configs: default: worker_tags: ["deno", "python3", "bun", "go", "bash", "powershell"] init_bash: "echo 'Worker starting'" native: worker_tags: ["nativets"]

注意worker_tags中的值(denopython3bungobashpowershellnativets等)是 Windmill 内置的脚本运行语言标签,Worker 会按标签认领对应语言的任务;native组对应 nativets 运行时,可与docker-compose.ymlWORKER_GROUP=native的 worker 服务一一对应。

第二步:在docker-compose.yml中添加一次性 init 容器

services: windmill_config_sync: image: ${WM_IMAGE} # 启动时运行一次后退出 restart: "no" command: ["windmill", "sync-config", "/config/windmill-config.yaml"] environment: - DATABASE_URL=${DATABASE_URL} - WM_LICENSE_KEY=${WM_LICENSE_KEY} - SMTP_PASSWORD=${SMTP_PASSWORD} - GOOGLE_OAUTH_SECRET=${GOOGLE_OAUTH_SECRET} volumes: - ./windmill-config.yaml:/config/windmill-config.yaml:ro depends_on: db: condition: service_healthy

仓库示例中,windmill_server通过depends_on: windmill_config_sync: condition: service_completed_successfully保证「先同步配置、后启动服务」的先后顺序;数据库服务带健康检查(pg_isready -U postgres)。生产环境可将该服务替换为 Kubernetes InitContainer 或 CI 流水线中的一步。

第三步:在.env中设置密钥(不要提交到版本控制):

DATABASE_URL=postgres://postgres:changeme@db/windmill WM_IMAGE=ghcr.io/windmill-labs/windmill-ee:main WM_LICENSE_KEY=your-license-key-here SMTP_PASSWORD=your-smtp-password GOOGLE_OAUTH_SECRET=your-google-oauth-secret

启动:docker compose up -d

配置变更后重新同步

sync-config容器运行一次即退出。编辑 YAML 后重新应用:

docker compose run --rm windmill_config_sync

在 CI/CD 流水线中,可以直接以二进制方式运行:

windmill sync-config ./windmill-config.yaml

Replace 语义:数据库状态与文件严格一致

sync-config采用Replace(替换)模式:数据库中存在但 YAML 中未声明的任何全局设置都会被删除ducklake_settingscustom_instance_pg_databases等受保护设置除外)。这保证数据库状态与文件声明完全一致,但也意味着:如果你只想管理一部分设置,就必须把希望保留的所有设置都写进 YAML 文件,否则未声明的设置会被清理掉。对应测试用例见 backend/tests/instance_config.rs,其中覆盖了受保护设置不被误删、worker_configs禁止以 global setting 形式写入等回归场景。

方案二:Kubernetes Operator

Windmill Kubernetes operator 持续 watch 一个 ConfigMap,并不断调和(reconcile)数据库以匹配声明状态;同时原生支持通过secretKeyRef从 Kubernetes Secret 拉取值。

前置条件

  • operatorfeature flag 构建的 Windmill(OSS 构建中该能力不可用,windmill_operator::run在非private特性下会直接报错 "K8s operator is not available in this build",见 backend/windmill-operator/src/operator_oss.rs);
  • operator Pod 具备读取 ConfigMap / Secret 与创建 Event 的 RBAC 权限(见下文);
  • 一个名为windmill-instance的 ConfigMap(可通过OPERATOR_CONFIGMAP环境变量自定义名称)。

搭建步骤

第一步:为敏感值创建 Kubernetes Secret(仓库中的 k8s-secrets.yaml):

apiVersion: v1 kind: Secret metadata: name: windmill-secrets namespace: windmill type: Opaque stringData: license-key: "your-license-key-here" smtp-password: "your-smtp-password-here" google-oauth-secret: "your-google-oauth-secret-here"

生产环境建议用 sealed-secrets、external-secrets 或你偏好的密钥管理方案管理该 Secret。

第二步:创建 ConfigMap(仓库中的 k8s-windmill-instance.yaml):

apiVersion: v1 kind: ConfigMap metadata: name: windmill-instance namespace: windmill data: spec: | global_settings: base_url: "https://windmill.example.com" license_key: secretKeyRef: name: windmill-secrets key: license-key retention_period_secs: 2592000 job_default_timeout: 900 expose_metrics: true smtp_settings: smtp_host: "smtp.example.com" smtp_port: 587 smtp_from: "windmill@example.com" smtp_tls_implicit: false smtp_password: secretKeyRef: name: windmill-secrets key: smtp-password oauths: google: id: "your-google-client-id" secret: secretKeyRef: name: windmill-secrets key: google-oauth-secret login_config: auth_url: "https://accounts.google.com/o/oauth2/v2/auth" token_url: "https://oauth2.googleapis.com/token" userinfo_url: "https://openidconnect.googleapis.com/v1/userinfo" scopes: ["openid", "profile", "email"] custom_tags: - gpu - high-mem worker_configs: default: worker_tags: ["deno", "python3", "bun", "go", "bash", "powershell"] init_bash: "echo 'Worker starting'" native: worker_tags: ["nativets"]

配置放在data.spec下、作为一段 YAML 字符串,sync-config使用完全相同的模式

第三步:应用

kubectl apply -f windmill-instance.yaml

第四步:通过 Event 查看同步状态

kubectl get events --field-selector involvedObject.name=windmill-instance

License Key 的幂等处理

如果 ConfigMap 中license_key缺失或为空、但数据库中已存在,operator 会保留数据库中的现有值。这意味着你可以把 License Key 交给 UI 单独管理,而不用担心 operator 的周期性调和把它覆盖掉——调和逻辑天然对 license_key 做了保护。

Operator 环境变量

变量默认值说明
OPERATOR_NAMESPACEPod 自身所在 namespaceConfigMap 所在的 namespace
OPERATOR_CONFIGMAPwindmill-instance要 watch 的 ConfigMap 名称

在 Kubernetes 中使用envRef

envRef在 operator 场景下同样可用,值从operator Pod 的环境变量解析。当 secret 通过 Pod 环境变量注入(例如来自 vault sidecar 或 external-secrets 的 env 注入)时非常有用:

data: spec: | global_settings: license_key: envRef: "WM_LICENSE_KEY" # 从 operator Pod 环境读取

operator Pod 的 Deployment 中对应:

env: - name: WM_LICENSE_KEY valueFrom: secretKeyRef: name: windmill-secrets key: license-key

这与在 ConfigMap 中直接使用secretKeyRef功能等价,区别在于它不要求 operator 具备 Secret 读取权限,且可以接入集群支持的任意 secret 注入机制(external-secrets、vault-agent 等)。

RBAC 最小权限

operator Pod 需要读取 ConfigMap、Secret 并创建 Event。最小 Role 如下(注意:因为没有 CRD 需要管理,所以是namespace 级 Role而非 ClusterRole):

apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: windmill-operator namespace: windmill rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"]

运行 operator

# 作为独立进程运行(适合开发调试) DATABASE_URL=postgres://... windmill operator # 生产环境:以 Kubernetes Deployment 形式部署

从源码看,windmill operator子命令由operatorfeature 编译(backend/src/main.rs),建立独立的 operator 数据库连接后调用windmill_operator::run(db)启动 ConfigMap watcher;在 enterprise + private 构建下还会注册 Ctrl-C 信号以优雅退出。

如何选择:envRef还是secretKeyRef

特性envRefsecretKeyRef
Docker Compose 可用
Kubernetes 可用
vault sidecar 注入否(改用envRef
数据来源进程环境变量K8s Secrets API
是否需要 Secret 读取 RBAC

建议:追求跨部署目标的可移植性时优先使用envRef;希望不经过中间环境变量、直接绑定 Kubernetes 原生 Secret 时使用secretKeyRef

总结与最佳实践

  • 两种方案共用InstanceConfigYAML 模式与 secret 引用机制:sync-config适合 Docker Compose / VM / CI 的一次性同步,operator 适合 K8s 的持续调和;
  • 敏感字段一律使用envRefsecretKeyRef,明文仅用于本地调试;
  • 理解 Replace 语义:文件即真相(source of truth),未声明的设置会被删除,受保护设置(ducklake_settingscustom_instance_pg_databases等)除外;
  • License Key 可脱离配置文件单独管理,operator 不会覆盖数据库已有值;
  • 所有配置变更走版本控制与代码 review,配合 CI 中的windmill sync-config即可实现从「手工改库」到「声明式交付」的完整升级。

【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 15:04:15

招聘岗位数据爬虫与可视化分析:从Scrapy到pyecharts的完整实现

简介&#xff1a;一套基于Python的招聘岗位数据爬虫与可视化分析完整项目&#xff0c;面向正在准备毕业设计或期末大作业的计算机相关专业学生&#xff0c;也适合需要实战练习的Python数据分析初级开发者。包内含五十九个文件&#xff0c;主要包含9个.py源码、12个.pyc编译文件…

作者头像 李华
网站建设 2026/9/14 15:03:50

陈氏超混沌系统与DNA编码图像加密MATLAB实现

简介&#xff1a;本资源是一套基于陈氏超混沌系统与DNA编码理论实现的位级图像加密算法MATLAB仿真源码&#xff0c;面向计算机、人工智能、电子信息、通信工程等专业的本科生、研究生及课程设计实践者&#xff0c;解决图像信息安全中的高安全性加密建模与仿真实现问题。压缩包共…

作者头像 李华
网站建设 2026/9/14 15:03:37

Ubuntu 26.04 LTS 裸机安装全流程:从分区避坑到开发环境搭建

玩 Linux 这么多年&#xff0c;我一直觉得“装系统”这件事最容易被低估。尤其那种从空白硬盘开始的裸机安装&#xff0c;看起来就是插个 U 盘、点几下下一步&#xff0c;可真正操作起来&#xff0c;几乎每一台机器都能给你整点不一样的幺蛾子。我最近给一台新机器从头装 Ubunt…

作者头像 李华