大师
Maestro从一个单一的YAML文件中为生产部署一个网络应用程序. 指着一个 maestro.yaml 描述您的域名、 服务器和应用程序容器, 它提供云基础设施( DNS、 液滴、 隧道、 管理过的 Postgres) , 并配置服务器( nginx、 Docker、 您的后端) —— 以一个命令结束.
盖子下面是结合的 普鲁米 用于云资源及 可解药 用于服务器配置,所以你不必将两者连接在一起.
一个 maestro.yaml 让你:
- DNS 和 TLS 数据 —— Cloudflare DNS 记录,区域 TLS 设置,以及您的域名的 Origin CA 证书.
- 服务器 - 数字海洋液滴/环境(
dev编号 :staging编号 :prod),每个环境在自己的子域上完全隔离. - 私人 SSH 访问 ——通过Cloudflare Zero Trust隧道到达服务器(
ssh0.example.com),从未通过生IP. - 网络级别 —— nginx服务于您的静态网站或倒置的网络容器.
- 后端分级 ——您应用的多克图像部署出蓝/绿色,并附有健康检查和可选的部署前数据库迁移,所以部署是零下架时间.
- 数据库分级( 可选) – 数字海洋管理 Postgres 集成每条环境,仅能覆盖于一个私有的VPC上,拥有最不优惠的应用用户.
Maestro是一个有CLI的版本npm包. 您的应用程序仓库拥有 maestro.yaml (在文件描述的应用代码旁边) 并运行大师反对它—— bunx @arthuroabrantes/maestro,或被钉入 devDependency升级Maestro是一个版本 在您的Repo; 没有关于您的应用 住在Maestro的仓库.
使用大师
1. 安装主机要求
大师继续前进 阿宝 并管理一些主机工具( 所有工具在启动时都得到验证) :
| 工具 | 用于 |
|---|---|
docker | 运行容器化的Ansible执行环境并提取图像源网站资产. |
pulumi | 普鲁米CLI. Maestro通过自动化API在进程中驱动;你从未自己运行过. 州住普鲁米云(无) pulumi login 需要 -- -- PULUMI_ACCESS_TOKEN 认证). |
bws | Bitwarden Secrets Manager CLI——所有秘密的来源. |
cloudflared | 通过Cloudflare SSH隧道到达服务器. |
pip/Python | 大师自动安装 ansible-navigator (和 ansible-builder)通过 pip install --user 如果他们失踪了 PATH。 。 。 . |
2. 设置账户(一次)
云浮华. 基础 domain 内 maestro.yaml 必须已经存在作为一个 活动区域 在您的 Cloudflare 账户中 : 在 Cloudflare 中添加域名, 并在 Cloudflare 指派的域名中标注您的注册员名服务员 。 大师在运行时按名称查看区域并失败 Cloudflare zone for <domain> not found. 如果它不见了——它从不创建区或验证所有权.
区域也必须是 没有相冲突的 DNS 记录大师创建自己的记录(apex) A, (中文(简体) ), www A,以及 api编号 :ssh* CNAME如果不同类型的记录已经占有其中之一,则Cloudflare以 A CNAME record with that host already exists (错误) 81054),运行部分失效. 删除( 或导入到 Pulumi) 顶端的任何相冲突记录, www, (中文(简体) ), api,以及 ssh* 名字第一个.
数字海洋. 添加 SSH 公开 用于数字海洋账户的密钥。 大师的药水滴 但不为您注册钥匙.
Bitwarden 机密管理器 . 在您机器账户可读的 BWS 项目中创建下面的秘密 。 大师在启动时取出它们并注入它自己的环境——它们从未被写入你的Repo.
| 秘诀 | 目的 | 所需范围 |
|---|---|---|
VPS_SSH_KEY | SSH 服务器 私营 键匹配在数字海洋注册的公钥. | 非 API 令牌; 不适用范围 . |
GHCR_TOKEN | GitHub Control Register Register 标记用于调取您的应用图像的服务器 . | GitHub 个人访问符( 经典) read:packages。不需要其他只读拉出. |
GHCR_USERNAME | 拥有的 GitHub 用户名 GHCR_TOKEN。 。 。 . | 非 API 令牌; 不适用范围 . |
PULUMI_ACCESS_TOKEN | Pulumi Cloud 访问符 (基础设施状态所在). | 标准个人准入标志;需要进入正在部署的组织/仓库. |
CLOUDFLARE_API_TOKEN | Cloudflare API 代币. | 区 □ 区:读取, (中文(简体) ), 区设置: 编辑, (中文(简体) ), DNS: 编辑页:1 SSL 和证书: 编辑 (欧里金 CA 证书—— a) 用户级别 权限; 没有权限创建失败 401 / code 1016(c) 账户 云浮隧道: 编辑. 范围扩大到正在管理的账户/区. |
DIGITALOCEAN_ACCESS_TOKEN | DigitalOcean API 代币. | droplet:create/read/update/delete· ; ssh_key:read· ; tag:create/read/delete。一个完整的读出+写出符号也有效。 启用了数据库层级,另外 database:create/read/update/delete 和 VPC 读取/写入. |
启用数据库级( E)pulumi.database.enabled: true),还需要两个秘密——您选择的稳定识别符,由Pulumi(创建用户/数据库)和您的后端阅读:
| 秘诀 | 目的 |
|---|---|
POSTGRES_USER | 专用最不优惠的应用程序数据库用户名(由普鲁米创建). |
POSTGRES_DB | 应用程序数据库名称(由Pulumi创建). |
POSTGRES_HOST, (中文(简体) ),POSTGRES_PORT,以及POSTGRES_PASSWORD为: 没有 Bitwarden secrets——它们来源于 DigitalOcean,表面为 Pulumi 堆栈输出,并自动注入到您的后端. 永远不要犯法.
3. 写你 maestro.yaml
在您的应用程序仓库中创建 maestro.yaml。一个最小的完整实例:
domain: example.com
pulumi:
enabled: true
command: up
projectName: example
cloudflareAccountId: "<your-cloudflare-account-id>"
sshPort: 22
stacks:
prod:
servers:
- roles: [backend, web]
ansible:
enabled: true
web:
static:
source: local
dir: ./website # relative paths resolve against this file's directory
dist: dist
backend:
image: ghcr.io/your-org/your-app
tag: latest
port: 3000
secrets:
provider: bws
见 example.maestro.yaml 用于包含每个选项的完整文件模板,以及 配置参考 见下文.
运行它
export BWS_ACCESS_TOKEN="your_bws_machine_account_token"
bunx @arthuroabrantes/maestro --dry-run # validate the config and preview settings
bunx @arthuroabrantes/maestro # provision everything
BWS_ACCESS_TOKEN 是您手动设定的唯一环境变量—— Bitwarden 机账符 读取 进入拥有上述机密的项目。 其他一切从比特华登来.
或者,针大师作为一个 devDependency 并运行它通过 bun run maestro. 一个在合并上运行大师的瘦CI工作流程同样工作——CLI是非交互的.
CLI 参考
maestro [--config <path>] [--dry-run]
| 旗帜 | 目的 |
|---|---|
--config <path> | 路径 maestro.yaml默认 : ./maestro.yaml 在当前工作目录中. |
--dry-run | 验证配置并显示解析的设置, 不提供任何条件 . |
--help | 显示用法 . |
配置内部的相对路径( 如 . ansible.web.static.dir: ./website总是决心反对 配置文件目录,而不是引用目录的maestro 从,所以同样的配置在本地,在CI,并通过 --config 从任何地方.
配置参考
所有配置都生活在一个YAML文件中. 整个结构:
domain: example.com # Base domain for DNS and nginx
pulumi:
enabled: true # Enable/disable cloud provisioning
command: up # up | refresh | cancel | output | destroy (destroy skips Ansible)
projectName: your-project-name # Pulumi project name
cloudflareAccountId: "" # Your Cloudflare account ID
sshPort: 22 # SSH port for tunnels
database: # Optional: Managed Postgres tier (global defaults)
enabled: false # Provision a DigitalOcean Managed Postgres cluster per stack
version: "16" # Postgres major version: "15", "16", or "17"
size: db-s-1vcpu-1gb # DigitalOcean DB node size
nodeCount: 1 # Node count (region always co-locates with the stack's droplets)
stacks: # One or more environments: dev, staging, prod
prod:
servers:
- roles: [backend, web] # Roles determine what gets provisioned/configured
# groups: [devops] # Optional: override global ansible.groups
# size: s-1vcpu-1gb # Optional: DigitalOcean droplet size
# region: nyc1 # Optional: DigitalOcean region
# database: # Optional: per-stack sizing override (override wins)
# size: db-s-2vcpu-4gb
ansible:
enabled: true # Enable/disable all server configuration
groups: [devops] # System groups (can be overridden per-server)
web: # Required if any server has the "web" role
static: # ...serve static files with nginx, or:
source: local # local | image
dir: ./website # Website source (relative to this config file)
build: bun run build # Optional build command
dist: dist # Subdirectory with built assets
# docker: # ...or reverse-proxy a web container
# image: ghcr.io/org/web
# tag: latest
# port: 3000
backend: # Required if any server has the "backend" role
image: ghcr.io/org/app # Container image
tag: latest # Image tag
port: 3000 # Backend port
env: # Non-sensitive environment variables for the container
SOME_VAR: value
secretEnv: # Sensitive vars: names of Bitwarden secrets, values injected at runtime
- STRIPE_SECRET_KEY # container var STRIPE_SECRET_KEY ← BWS secret of the same name
- BWS_ACCESS_TOKEN: APP_BWS_ACCESS_TOKEN # rename on inject: container var ← BWS secret
migrate: # Optional: DB migration before each deploy (omit = none)
command: ["npm", "run", "migrate"] # argv run inside the backend image
healthCheck: # Optional: blue/green readiness probe
path: /health # Polled for 200 before cutover (default: /health)
secrets:
provider: bws # Secrets provider (bws = Bitwarden Secrets Manager)
projectId: "" # Optional BWS project ID to scope the fetch
requiredVars: [] # Extra secrets to validate and forward to Ansible
服务器角色
供给是 基于角色:每个 Ansible 游戏本只运行在持有相应角色的服务器上,并且在没有服务器时完全跳过.
| 作用 | 游戏本 | 目的 |
|---|---|---|
backend | backend.yml | Docker + 后端容器部署 |
web | web.yml | nginx( 静态文件或反向代理) |
加强安全 (单位:千美元)security.yml) 总是运行在所有服务器上: UFW 防火墙规则(默认接收次数少,允许SSH,特定角色的开口)和系统组管理(英语:ansible.groups) (中文(简体) ).
环境(垃圾)
每个堆放在下面 pulumi.stacks 是一个孤立的环境,有自己的Pulumi状态、服务器和(可选的)数据库。 Maestro提供每个被定义的堆栈相继,然后为Ansible相位汇总所有主机. 服务器同时被标记为堆栈名和角色,因此游戏本可以根据环境瞄准. 见 ansible/README.md 用于主机目标细节.
非生产堆栈活在基部的前缀子域上 domain,用于每种资源:
| 资源 | dev | staging | prod |
|---|---|---|---|
| 网络域 | dev.example.com | staging.example.com | example.com |
| WWW 域名 | www.dev.example.com | www.staging.example.com | www.example.com |
| API 终点 | api.dev.example.com | api.staging.example.com | api.example.com |
| SSH 隧道 | ssh0.dev.example.com | ssh0.staging.example.com | ssh0.example.com |
DNS记录是在基域Cloudflare区下创建的;TLS证书是每个有效域签发的.
数据库(管理中的邮政)
何时 pulumi.database.enabled 实值 true,硕士规定a 数字海洋管理邮政 分组作为专门的数据库级别。 指导原则: 后端是牛; 数据库是皇冠珠宝 ——数据库存在于自有管理的故障域中,与可支配后端滴入相分离.
- 环境相互隔离. 每个启用的堆栈设置 拥有 集群、应用程序数据库和应用程序用户。 A 类
dev部署绝不触及prod数据库. - 私人 VPC 端点 + TLS. 每个堆栈都得到一个由后端滴入和集群组成的专用VPC. 后端连接到数据库的 私营 终点——交通从不穿越公共互联网——仍然使用
sslmode=require(单位:千美元)PGSSLMODE=require为libpq客户端服务. - 起码的特权. Pulumi 创建专用数据库用户(来自
POSTGRES_USER和数据库(来自POSTGRES_DB) (中文(简体) ). 应用程序从不使用集群管理员( E)doadmin) (中文(简体) ). - 防火墙通过标签. A 类
DatabaseFirewall相信后端的小滴 以他们的每桶 标签,不是"滴"ID,所以它能活过"滴"重建. 没有0.0.0.0/0规则. - 生命周期保障. 组装
retainOnDelete: true财务报告和审定财务报表protect: true; 应用程序数据库、用户和 VPC 携带retainOnDeleteA级pulumi destroy可支配后端在离开云数据库及其数据完好无损的情况下成功; 故意撕毁需要故意不保护并移除资源. - 杜尔比特. DigitalOcean的内置日备份外加点入时恢复(PITR)覆盖耐久性——PITR防止操作员/应用程序出错(不良迁移,意外)
DELETE不仅仅是基础设施的损失.
一次性地更换水滴. VPC成员在一款"数字海洋"(DigitalOcean droplet)上是不可改变的,因此允许数据库在首次应用时替换了已存在的后端droplet. 后端为牛;Ansible重接替换.
连接线. POSTGRES_USER编号 :POSTGRES_DB 来源于 Bitwarden(您选择的稳定值); POSTGRES_HOST, (中文(简体) ), POSTGRES_PORT,以及 POSTGRES_PASSWORD 来源于 DigitalOcean并读取已键入的 Pulumi 堆栈输出(密码为 Pulumi 密件)。 Maestro在堆栈后端主机上线程显示每个堆栈的值,因此多堆部署从不交叉连接一个堆栈后端到另一个堆栈的数据库. 后端容器最后会 POSTGRES_HOST, (中文(简体) ), POSTGRES_PORT, (中文(简体) ), POSTGRES_USER, (中文(简体) ), POSTGRES_DB, (中文(简体) ), POSTGRES_PASSWORD,以及 POSTGRES_SSLMODE=require 在环境.
零下限后端部署迁移( M)
后端部署为 零关闭时间 默认情况下:nginx作为稳定的倒置代理保持上行,而Maestro将容器取而代之的是蓝色/绿色.
后端作为两个集装箱之一运行—— backend-blue 联 合 国 ansible.backend.port和 backend-green 联 合 国 port + 1——两者都有义务 127.0.0.1。每次部署在闲置的端口启动新图像,而活的端口则继续服务,等待其通过健康检查,在新端口点出nginx并重新装入(nginx -s reload (b) 飞行中排水请求)。 直到那时,旧集装箱才停止。 如果新容器永远没有健康,老容器继续服务,部署失败——没有半部署状态.
ansible.backend.healthCheck.path(可选): HTTP 路径为200在新容器上,在切换前。 默认为/health。重试/超时预算是固定的.ansible.backend.migrate.command(可选):一个 argv 阵列(例如.["npm", "run", "migrate"],而不是外壳行)在后端图像中运行一次 在新容器开始前,基于实时数据库,环境与应用程序相同(包括每集)POSTGRES_*页:1 非零退出在应用容器被触碰前中止部署. 关闭块 跳过迁移。 DB 密码和其他机密都保留在no_log- 守护的路径,从来没有印刷.
转发额外机密
大师从来不在其中存储秘密值 maestro.yaml ——此文件意在被版本控制. 通过敏感度和目的地,有三种方式使配置进入系统:
ansible.backend.env——非敏感的后端-容器变量,作为字形键/值对(例如.NODE_ENV: production) (中文(简体) ).ansible.backend.secretEnv—— 说 敏感 后端- 容器变量 。 列出 名称 b. 存储在 Bitwarden 中的秘密; 要以不同的名称注入,请使用绘图条目——CONTAINER_VAR: BWS_SECRET_NAME(例如:.BWS_ACCESS_TOKEN: APP_BWS_ACCESS_TOKEN手持应用程序在一个不相冲突的 Bitwarden 名下存储的令牌。 启动时验证源代码密钥( 一个缺失的秘密在任何云调时前失败运行), 值运行相同no_log- 守护路径作为数据库密码 - 它们永远不会被写入您的 repo 或打印 。 集装箱 var 可能也不出现在env,以及PORT保留(自动从ansible.backend.port) (中文(简体) ).secrets.requiredVars- Ansible公司需要的秘密 游戏本 (不是应用容器). 它们在启动时被验证,并被传送到Ansible执行环境,游戏本通过该环境读取lookup('env', 'VAR_NAME')。 。 。 .
永远不要向前
BWS_ACCESS_TOKEN给您的应用程序. Maestro的部署符可以读取所有部署的秘密(Cloudflare, DigitalOcean, Pulumi);一个受损后端容器会继承你的全部基础设施. 大师拒绝它secretEnv来源。 如果您的应用程序本身需要 Bitwarden 访问, 请创建一个单独的机器账户符号, 范围为应用程序机密项目, 在 Bitwarden 中以不同的名称存储( 例如 。).APP_BWS_ACCESS_TOKEN),并使用映射表将其以所期望的名称交给应用程序:BWS_ACCESS_TOKEN: APP_BWS_ACCESS_TOKEN。 。 。 .
如何运作
输油管
index.ts (单位:千美元) maestro bin) 管线管线:
- 解析 CLI 旗 并对照当前工作目录解决配置路径.
- 校验
maestro.yaml对照一个打入的计数器(io-ts codecs外加语义检查:角色/web一致性,区域混合,数据库先决条件,路径存在).--dry-run在此停止并打印解析的设置 . - 获取机密 从 Bitwarden 保密管理器( R)
bws secret list)并注入过程环境;要求的秘密在前面被断言,因此在任何云调之前都会发生故障. - 写入 SSH 密钥 改为
0600临时文件(stable path ——它被插入了普鲁米资源,因此每个运行的随机路径都会在每次部署时将它们抖出). - 运行 Pulumi 每堆 正在通过 自动化 API没有Pulumi Docker的形象,也没有炮击到
pulumi up下方的类型脚本程序pulumi/提供 Cloudflare DNS 记录、 区 TLS 设置、 Origin CA 证书、 Zero Trust SSH 隧道、 Digital Ocean 液滴和(可选) VPC + 管理 Postgres 级。 州住普贤云. - 总主机 从所有堆栈的打出输出——主机名,角色,堆栈标记,以及每个堆栈的数据库端点.
- 等待隧道准备 ——民意调查 SSH-over-cloudflailed 直到每个主机接受连接.
- 运行解析器 与主敌:
web.yml, (中文(简体) ),backend.yml,则security.yml最后(它可能会收紧防火墙规则,从而阻碍早先的剧情).
Ansible 执行环境
游戏本不运行在你的主人的Ansible上——它们运行在一个集装箱内 执行环境 (EE) 驱动由 ansible-navigator。大师用 ansible-builder 从装有货包的船舶的定义(ansible/execution_environment/) (中文(简体) ). 已建图像被标记为定义的内容散列,因此之后运行再使用并完全跳过该构建,直到定义发生变化(比如在maestro升级上).
图像是 应用不可知论 ——所有特定应用程序在运行时到达容器,在图像构建时从不:
- 网站资产 编成临时目录(从
ansible.web.static.dir,或从容器图像中取出)和卷载只读于/opt/website。 。 。 . - SSH 密钥 临时文件是卷载只读的 .
- 配置和秘密 作为环境变量转发(
--penv),包括主机列表(SSH_HOSTS),这是一个动态目录脚本(ansible/inventory/hosts.py)变为Ansible主机,按角色和堆栈分组.
这就是让一个EE图像服务于每一个消耗性应用.
Maestro的安装目录在运行时被作为只读处理:网站中转,EE构建上下文,导航日志,以及秘密文件都活在系统临时目录中(带有 maestro_ 前缀, 0600 机密文件, 并清理出口, 加上启动时的 stale 扫描 .
软件包布局
| 路径 | 这是什么 |
|---|---|
index.ts | CLI 切入点bin),上面的输油管. |
lib/ | 管弦乐团:配置装入/验证,秘密,普鲁米司机,Ansible司机,隧道准备. |
pulumi/ | Pulumi方案(进口入厂):DNS,证书,地道,液滴,VPC,管理Postgres. |
ansible/ | 执行环境定义,动态库存,以及用于nginx, Docker,UFW,蓝色/绿色部署的游戏本/角色. |
释放
重放大师版是 version 突然间 package.json 加号 npm publish ——EE图像定义,游戏本,普鲁米程序都在包内出行,所以没有第二件文物可以保持同步.
发展大师
这个寄存器本身只是另一个消耗性应用 maestro.yaml 恰好坐在工具旁边:
bun index.ts --dry-run # or: bun run maestro:dry-run
bun index.ts # full run against the repo's own maestro.yaml
bun test # unit tests
路线图
- 独立逻辑备份流到数字海洋空间:除了DO的日常备份+PITR,一个自有的
pg_dump- 系统备份在日程安排上的空间桶,有寿命周期/保留规则和经过测试的恢复钻头——针对账户或提供者一级的问题进行深入的防御,以及便携式副本. - 数据库赠款紧缩: 应用程序用户为非超级用户且从未
doadmin,但将其权限范围限定为自身数据库(超越了DigitalOcean的默认值)内仅需要的,是后续. - 多云提供者支持: DigitalOcean是目前唯一的提供商;AWS/GCP/Azure可能跟随.
- 自动SSH 密钥提供:今天的公用密钥必须在第一次运行前手动添加到 DigitalOcean.
- 每个环境有多个网络/后端服务器 包含负载平衡;今天每个角色映射到每个堆栈的单个服务器.
- 通过 SDK 可解析:通过库界面驱动 Ansible ,而不是炮击到
ansible-navigator(Pulumi已经运行在进程中). - 强化安保后续行动:避免在主机文件系统中实现SSH密钥,从Pulumi输出中隐藏服务器IP地址.