refactor: use nginx -s reload instead of systemd

- doc: add comment
This commit is contained in:
2026-08-17 10:10:14 +08:00
parent 79f18fcdea
commit f536987a7e
62 changed files with 2216 additions and 598 deletions
+108 -37
View File
@@ -1,5 +1,5 @@
// Package backendexecutor prepares and starts one explicitly named backend container.
// Gateway switching is deliberately outside this package.
// Package backendexecutor 负责准备并启动一个显式命名的后端容器。
// 网关流量切换被刻意排除在本包职责之外。
package backendexecutor
import (
@@ -25,56 +25,95 @@ import (
)
const (
healthPath = "/yms/actuator/health"
healthTimeout = 120 * time.Second
healthInterval = time.Second
// healthPath 后端 Actuator 健康检查端点的固定路径。
healthPath = "/yms/actuator/health"
// healthTimeout 等待后端容器健康的整体超时时间。
healthTimeout = 120 * time.Second
// healthInterval Actuator 健康检查的轮询间隔。
healthInterval = time.Second
// containerStopTimeoutSeconds 停止后端容器时允许的最长等待秒数。
containerStopTimeoutSeconds = 150 * 60
hostNetworkMode = "host"
bindMountType = "bind"
stepLoadImage = "backend.image.load"
stepPullImage = "backend.image.pull"
stepRemoveContainer = "backend.container.remove-inactive"
stepCreateContainer = "backend.container.create"
stepStartContainer = "backend.container.start"
stepCheckHealth = "backend.container.health"
// hostNetworkMode 表示后端容器使用宿主机网络命名空间。
hostNetworkMode = "host"
// bindMountType 表示后端容器的挂载类型为绑定挂载。
bindMountType = "bind"
// stepLoadImage 从归档加载后端镜像的持久化步骤键。
stepLoadImage = "backend.image.load"
// stepPullImage 从仓库拉取后端镜像的持久化步骤键。
stepPullImage = "backend.image.pull"
// stepRemoveContainer 移除非活动后端容器的持久化步骤键。
stepRemoveContainer = "backend.container.remove-inactive"
// stepCreateContainer 创建非活动后端容器的持久化步骤键。
stepCreateContainer = "backend.container.create"
// stepStartContainer 启动非活动后端容器的持久化步骤键。
stepStartContainer = "backend.container.start"
// stepCheckHealth 等待后端 Actuator 健康的持久化步骤键。
stepCheckHealth = "backend.container.health"
)
const (
// ImageAcquisitionLoad 表示通过本地归档加载方式获取后端镜像。
ImageAcquisitionLoad = "load"
// ImageAcquisitionPull 表示通过远程仓库拉取方式获取后端镜像。
ImageAcquisitionPull = "pull"
)
// Request contains exact values supplied by the update package and local deployment configuration.
// ImageReference is opaque: the executor never extracts meaning from its tag.
// Request 携带 update 包与本地部署配置提供的精确取值。
// ImageReference 不透明值:执行器从不解析其标签中的任何含义。
type Request struct {
ImageAcquisition string
ArchivePath string
ImageReference string
ExpectedImageDigest string
Platform containerengine.Platform
ContainerName string
Port int
PortEnvironmentKey string
ConfigSource string
ConfigTarget string
TmpSource string
TmpTarget string
// ImageAcquisition 指定镜像获取方式,取值为 ImageAcquisitionLoad 或 ImageAcquisitionPull。
ImageAcquisition string
// ArchivePath 当 ImageAcquisition 为 ImageAcquisitionLoad 时本地镜像归档的绝对路径。
ArchivePath string
// ImageReference 后端镜像的精确引用,执行器不解析其语义。
ImageReference string
// ExpectedImageDigest 后端镜像期望的清单摘要。
ExpectedImageDigest string
// Platform 后端镜像显式指定的操作系统与架构。
Platform containerengine.Platform
// ContainerName 后端容器的精确名称。
ContainerName string
// Port 后端容器的监听端口,仅允许 8080 或 8081。
Port int
// PortEnvironmentKey 注入端口值的环境变量键。
PortEnvironmentKey string
// ConfigSource 宿主机上后端配置文件的绝对路径。
ConfigSource string
// ConfigTarget 后端配置在容器内的绝对挂载路径。
ConfigTarget string
// TmpSource 宿主机上后端临时目录的绝对路径。
TmpSource string
// TmpTarget 后端临时目录在容器内的绝对挂载路径。
TmpTarget string
// ConfigEnvironmentKey 注入配置位置的环境变量键。
ConfigEnvironmentKey string
ConfigLocation string
RestartPolicy containerengine.RestartPolicy
HealthEndpoint string
StartLog bool
LogReporter func(string)
// ConfigLocation 后端配置在容器内的位置取值。
ConfigLocation string
// RestartPolicy 后端容器的重启策略。
RestartPolicy containerengine.RestartPolicy
// HealthEndpoint 后端 Actuator 健康检查的 HTTP 端点。
HealthEndpoint string
// StartLog 表示是否读取并回传容器启动日志。
StartLog bool
// LogReporter 用于回传容器启动日志的每一行。
LogReporter func(string)
}
// Executor drives the persisted transaction up to SWITCHING after the new container is healthy.
// Executor 在新容器恢复健康后,将持久化事务推进到 StateSwitching 状态。
type Executor struct {
store *transaction.Store
// store 持久化事务的存储。
store *transaction.Store
// coordinator 负责事务的独占执行与步骤执行。
coordinator *transaction.Coordinator
engine containerengine.Engine
checker *healthcheck.ActuatorChecker
// engine 底层容器引擎。
engine containerengine.Engine
// checker Actuator 健康检查器。
checker *healthcheck.ActuatorChecker
}
// New 构造一个 Executor,并校验所有必要依赖非空。
// store 为事务存储,coordinator 为事务协调器,engine 为容器引擎,httpClient 用于健康检查。
// 任一必要依赖为 nil 时返回错误;健康检查器构造失败时返回该错误。
func New(store *transaction.Store, coordinator *transaction.Coordinator, engine containerengine.Engine, httpClient *http.Client) (*Executor, error) {
if store == nil {
return nil, errors.New("transaction store is required")
@@ -92,7 +131,9 @@ func New(store *transaction.Store, coordinator *transaction.Coordinator, engine
return &Executor{store: store, coordinator: coordinator, engine: engine, checker: checker}, nil
}
// Run resumes from the transaction's persisted state. It does not switch gateway traffic.
// Run 从事务的持久化状态恢复执行。它不会切换网关流量。
// transactionID 待执行的事务标识,request 是本次执行携带的请求参数。
// transactionID 为空时返回错误;其余错误来自事务协调器的独占执行。
func (e *Executor) Run(ctx context.Context, transactionID string, request Request) error {
if strings.TrimSpace(transactionID) == "" {
return errors.New("transaction ID is required")
@@ -102,6 +143,8 @@ func (e *Executor) Run(ctx context.Context, transactionID string, request Reques
})
}
// run 在独占执行上下文内的实现,循环读取事务状态并按状态推进,
// 直到事务进入 StateSwitching 状态后返回。每个状态分支处理失败或推进错误时立即返回。
func (e *Executor) run(ctx context.Context, transactionID string, request Request) error {
for {
record, err := e.store.Transaction(ctx, transactionID)
@@ -147,6 +190,9 @@ func (e *Executor) run(ctx context.Context, transactionID string, request Reques
}
}
// failUnlessRecoverable 判断 prepare 阶段的错误是否可恢复。
// 若 cause 是 UncertainStepError 或 ErrStepConflict,则直接原样返回(保留不确定性以便重放恢复);
// 否则将事务标记为 StateFailed 并合并返回 cause 与状态迁移错误。
func (e *Executor) failUnlessRecoverable(ctx context.Context, transactionID string, cause error) error {
var uncertain *transaction.UncertainStepError
if errors.As(cause, &uncertain) || errors.Is(cause, transaction.ErrStepConflict) {
@@ -156,6 +202,8 @@ func (e *Executor) failUnlessRecoverable(ctx context.Context, transactionID stri
return errors.Join(cause, transitionErr)
}
// validate 对请求输入做运行时校验:先校验请求字段,再根据镜像获取方式校验镜像归档,
// 随后校验配置文件为普通文件、临时目录为直接目录,最后对容器引擎执行 Ping 探活。
func (e *Executor) validate(ctx context.Context, request Request) error {
if err := validateRequest(request); err != nil {
return err
@@ -177,6 +225,8 @@ func (e *Executor) validate(ctx context.Context, request Request) error {
return nil
}
// prepare 根据镜像获取方式执行对应的加载或拉取步骤,随后核对镜像、移除旧的非活动容器,
// 并创建新的非活动后端容器。所有步骤均通过事务协调器持久化执行以保证可恢复。
func (e *Executor) prepare(ctx context.Context, transactionID string, request Request) error {
switch request.ImageAcquisition {
case ImageAcquisitionLoad:
@@ -208,6 +258,8 @@ func (e *Executor) prepare(ctx context.Context, transactionID string, request Re
return err
}
// startAndCheck 先启动非活动后端容器,若请求要求读取启动日志则逐行回传,
// 最后执行健康检查步骤等待后端 Actuator 健康。
func (e *Executor) startAndCheck(ctx context.Context, transactionID string, request Request) error {
startOperation := &startContainerOperation{engine: e.engine, name: request.ContainerName}
if _, err := e.coordinator.ExecuteStep(ctx, transactionID, startIntent(request), startOperation); err != nil {
@@ -239,6 +291,8 @@ func (e *Executor) startAndCheck(ctx context.Context, transactionID string, requ
return err
}
// validateRequest 对请求字段做静态校验,确保所有取值精确且自洽。
// 任一字段不符合要求时返回描述性错误。
func validateRequest(request Request) error {
switch request.ImageAcquisition {
case ImageAcquisitionLoad:
@@ -295,6 +349,8 @@ func validateRequest(request Request) error {
return nil
}
// validateRestartPolicy 校验重启策略名称与其最大重试次数的组合是否合法。
// "no"、"always"、"unless-stopped" 不接受最大重试次数;"on-failure" 允许非负次数;其余名称不受支持。
func validateRestartPolicy(policy containerengine.RestartPolicy) error {
switch policy.Name {
case "no", "always", "unless-stopped":
@@ -311,6 +367,7 @@ func validateRestartPolicy(policy containerengine.RestartPolicy) error {
return nil
}
// regularFile 校验 path 指向一个普通文件,description 用于构造错误信息。
func regularFile(path, description string) error {
info, err := os.Stat(path)
if err != nil {
@@ -322,6 +379,7 @@ func regularFile(path, description string) error {
return nil
}
// directDirectory 校验 path 指向一个目录且不是符号链接(即“直接目录”)。
func directDirectory(path, description string) error {
info, err := os.Lstat(path)
if err != nil {
@@ -333,6 +391,8 @@ func directDirectory(path, description string) error {
return nil
}
// containerSpec 根据请求构造后端容器的完整规格,包括名称、镜像引用、平台、环境变量、
// 宿主机网络模式、重启策略、绑定挂载、用户与停止超时。
func containerSpec(request Request) containerengine.ContainerSpec {
return containerengine.ContainerSpec{
Name: request.ContainerName,
@@ -353,6 +413,7 @@ func containerSpec(request Request) containerengine.ContainerSpec {
}
}
// pullIntent 构造拉取并校验后端镜像步骤的持久化意图。
func pullIntent(request Request) transaction.StepIntent {
return intent(stepPullImage, "pull and verify backend image", struct {
ImageReference string `json:"imageReference"`
@@ -361,6 +422,7 @@ func pullIntent(request Request) transaction.StepIntent {
}{request.ImageReference, request.ExpectedImageDigest, request.Platform})
}
// loadIntent 构造从归档加载并校验后端镜像步骤的持久化意图。
func loadIntent(request Request) transaction.StepIntent {
return intent(stepLoadImage, "load and verify backend image", struct {
ArchivePath string `json:"archivePath"`
@@ -370,6 +432,7 @@ func loadIntent(request Request) transaction.StepIntent {
}{request.ArchivePath, request.ImageReference, request.ExpectedImageDigest, request.Platform})
}
// createIntent 构造创建非活动后端容器步骤的持久化意图,imageID 为已核对镜像的标识。
func createIntent(request Request, imageID string) transaction.StepIntent {
return intent(stepCreateContainer, "create inactive backend container", struct {
Spec containerengine.ContainerSpec `json:"spec"`
@@ -377,18 +440,21 @@ func createIntent(request Request, imageID string) transaction.StepIntent {
}{containerSpec(request), imageID})
}
// removeIntent 构造移除非活动后端容器步骤的持久化意图。
func removeIntent(request Request) transaction.StepIntent {
return intent(stepRemoveContainer, "remove inactive backend container", struct {
ContainerName string `json:"containerName"`
}{request.ContainerName})
}
// startIntent 构造启动非活动后端容器步骤的持久化意图。
func startIntent(request Request) transaction.StepIntent {
return intent(stepStartContainer, "start inactive backend container", struct {
ContainerName string `json:"containerName"`
}{request.ContainerName})
}
// healthIntent 构造等待后端 Actuator 健康步骤的持久化意图。
func healthIntent(request Request) transaction.StepIntent {
return intent(stepCheckHealth, "wait for backend Actuator health", struct {
ContainerName string `json:"containerName"`
@@ -397,6 +463,7 @@ func healthIntent(request Request) transaction.StepIntent {
}{request.ContainerName, request.HealthEndpoint, healthTimeout})
}
// intent 将任意值序列化为 JSON 后封装成事务步骤意图。序列化失败视为内部错误并直接 panic。
func intent(key, name string, value any) transaction.StepIntent {
payload, err := json.Marshal(value)
if err != nil {
@@ -405,6 +472,9 @@ func intent(key, name string, value any) transaction.StepIntent {
return transaction.StepIntent{Key: key, Name: name, Intent: payload}
}
// imageMatches 判断已核对镜像是否与期望摘要及期望平台完全匹配。
// 当镜像平台与期望平台不一致时直接返回 false;否则依据镜像的描述符摘要或仓库摘要
// 中是否存在与期望摘要相等的证据来判定匹配。
func imageMatches(image containerengine.Image, expectedDigest string, expectedPlatform containerengine.Platform) (bool, error) {
expected, err := opencontainersdigest.Parse(expectedDigest)
if err != nil {
@@ -438,6 +508,7 @@ func imageMatches(image containerengine.Image, expectedDigest string, expectedPl
return false, nil
}
// resultJSON 将任意值序列化为 JSON RawMessage。序列化失败视为内部错误并直接 panic。
func resultJSON(value any) json.RawMessage {
payload, err := json.Marshal(value)
if err != nil {