3d5bac66b4
IaC 面 (infra/) 与控制面 (server/internal/provision) 双产出,落地 doc/04 §4 「节点是牲口」弹性拓扑与 make-before-break 一键更换。 server/internal/provision: - CloudAdapter 适配层 + Registry(首发 vultr 消耗品池 / hetzner 精品池各一); 厂商凭证仅从 PROVISION_<VENDOR>_* env 注入,不入库不入 git。 - ProvisionService:CreateNode(幂等键重放不重复开机)、DestroyNode(幂等)、 RotateIP(换 IP 不换机 + version bump)、ListProviders。 - Replace 一键更换:先建后拆,新机 up 先于旧机 draining(容量不下降), replacement_uuid 幂等键 + replacements 表分步记录,崩溃可续跑不重复。 - RotatePool:池内滚动轮换,并发度 1–2。 - cmd/nodectl CLI:create/destroy/rotate-ip/replace/rotate-pool/providers。 - 单测(mock 厂商 API + 内存 Store):幂等重放、make-before-break 时序断言、 开机失败/探活超时→destroyed+失败计数+告警钩子、崩溃续跑、RotatePool。 infra/: - terraform/:探针机 + 控制面基线模块化(probe / control-plane)+ README, 低频基线进 state,节点不进 Terraform。 - cloud-init/node.yaml.tmpl:节点引导模板(注入一次性 bootstrap token,task #5)。 - identity-isolation.md:身份隔离登记表(doc/06 §2 红线,无任何凭证)。 migrations/000008:nodes 增 provider_instance_id/elastic_ip_id、node_events 增 ip_rotated、provision_idempotency / replacements 表(附加式,不动现网)。 红线:仅面向新厂商池,绝不纳管现网生产 EC2(deploy/ marzban)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
2.8 KiB
Go
98 lines
2.8 KiB
Go
package provision
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// Clock abstracts time for testability (Replace drains on a timer).
|
|
type Clock interface {
|
|
Now() time.Time
|
|
// Sleep blocks for d or until ctx is done, returning ctx.Err() on cancel.
|
|
Sleep(ctx context.Context, d time.Duration) error
|
|
}
|
|
|
|
// realClock is the production Clock.
|
|
type realClock struct{}
|
|
|
|
func (realClock) Now() time.Time { return time.Now().UTC() }
|
|
|
|
func (realClock) Sleep(ctx context.Context, d time.Duration) error {
|
|
t := time.NewTimer(d)
|
|
defer t.Stop()
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-t.C:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Prober verifies a node is reachable before it joins the directory.
|
|
//
|
|
// The full version (three-way probe cross-check, doc/04 §4.2) depends on the
|
|
// probe fleet (task #15). Until then a simplified Prober is injected:
|
|
// "heartbeat present + an overseas reachability check". Replace calls WaitReady
|
|
// after a node is created; on success the node is promoted to up.
|
|
type Prober interface {
|
|
// WaitReady blocks until the node passes simplified probing, or returns an
|
|
// error on timeout / ctx cancellation / probe failure.
|
|
WaitReady(ctx context.Context, node *Node) error
|
|
}
|
|
|
|
// AlertHook receives operational alerts (doc/04 §5.3 — TG bot in production).
|
|
// Replace / CreateNode call Fire on boot failure, probe timeout, and capacity
|
|
// protection breaches.
|
|
type AlertHook interface {
|
|
Fire(ctx context.Context, alert Alert)
|
|
}
|
|
|
|
// AlertKind classifies an alert.
|
|
type AlertKind string
|
|
|
|
const (
|
|
AlertBootFailed AlertKind = "boot_failed"
|
|
AlertProbeTimeout AlertKind = "probe_timeout"
|
|
AlertReplaceFail AlertKind = "replace_failed"
|
|
AlertCapacity AlertKind = "capacity_low"
|
|
)
|
|
|
|
// Alert is a single operational alert payload.
|
|
type Alert struct {
|
|
Kind AlertKind
|
|
NodeUUID string
|
|
Pool Pool
|
|
Message string
|
|
// FailCount is the running failure counter for capacity-protection alerts.
|
|
FailCount int
|
|
}
|
|
|
|
// noopAlert discards alerts; used when no hook is configured.
|
|
type noopAlert struct{}
|
|
|
|
func (noopAlert) Fire(context.Context, Alert) {}
|
|
|
|
// BootstrapIssuer issues one-time enrollment tokens (task #5).
|
|
// *mtls.BootstrapTokenManager satisfies this interface.
|
|
type BootstrapIssuer interface {
|
|
IssueToken(ctx context.Context, nodeUUID string) (string, error)
|
|
}
|
|
|
|
// CloudInitRenderer renders the node cloud-init document from a template,
|
|
// injecting the node UUID and bootstrap token (template lives at
|
|
// infra/cloud-init/node.yaml.tmpl, task #6).
|
|
type CloudInitRenderer interface {
|
|
Render(data CloudInitData) (string, error)
|
|
}
|
|
|
|
// CloudInitData is the template context for the node cloud-init document.
|
|
type CloudInitData struct {
|
|
NodeUUID string
|
|
BootstrapToken string
|
|
Region string
|
|
Role Role
|
|
Tier Tier
|
|
// ControlPlaneURL is the mTLS enrollment endpoint the agent registers to.
|
|
ControlPlaneURL string
|
|
}
|