refactor: use nginx -s reload instead of systemd
- doc: add comment
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
// Package backendupdate orchestrates one native backend update through commit or compensation.
|
||||
// Package backendupdate 负责在单台服务器上编排一次原生后端(或容器后端)更新,
|
||||
// 覆盖从构件准备、切换流量到提交或补偿的完整事务流程。它通过 transaction 包维护
|
||||
// 可恢复的更新事务,并在失败时执行补偿以回滚到更新前的状态。
|
||||
package backendupdate
|
||||
|
||||
import (
|
||||
@@ -27,49 +29,86 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
serviceBackend = "backend"
|
||||
sourceLocalCLI = "local-cli"
|
||||
drainDuration = 5 * time.Second
|
||||
// serviceBackend 本更新服务在事务中的服务标识名。
|
||||
serviceBackend = "backend"
|
||||
// sourceLocalCLI 事务来源标识,表示更新由本地命令行发起。
|
||||
sourceLocalCLI = "local-cli"
|
||||
// drainDuration 切换后旧单元/旧容器的默认排空时长。
|
||||
drainDuration = 5 * time.Second
|
||||
// directReleaseDigestLength 直接部署构件在 release 目录下的摘要路径截取长度。
|
||||
directReleaseDigestLength = 12
|
||||
inputTypeCurrentRelease = "current-native-release"
|
||||
legacyUnit8080 = "yms.service"
|
||||
legacyUnit8081 = "ymsback.service"
|
||||
// inputTypeCurrentRelease 标识以当前运行发行版执行重启的输入类型。
|
||||
inputTypeCurrentRelease = "current-native-release"
|
||||
// legacyUnit8080 端口 8080 对应的遗留 systemd 单元名。
|
||||
legacyUnit8080 = "yms.service"
|
||||
// legacyUnit8081 端口 8081 对应的遗留 systemd 单元名。
|
||||
legacyUnit8081 = "ymsback.service"
|
||||
)
|
||||
|
||||
// Updater executes the current native backend contract on one server.
|
||||
// Updater 在单台服务器上执行当前原生(或容器)后端契约。它持有部署配置、事务
|
||||
// 存储、协调器以及各类执行器,是后端更新流程的核心入口与状态载体。
|
||||
type Updater struct {
|
||||
config deploymentconfig.Config
|
||||
workRoot string
|
||||
store *transaction.Store
|
||||
coordinator *transaction.Coordinator
|
||||
releaseStore *filestore.Store
|
||||
units systemd.Manager
|
||||
gateway gatewayController
|
||||
executor nativeExecutor
|
||||
containerExecutor containerExecutor
|
||||
engine containerengine.Engine
|
||||
// config 部署配置,描述后端类型、槽位与路径等信息。
|
||||
config deploymentconfig.Config
|
||||
// workRoot 事务工作目录的根路径。
|
||||
workRoot string
|
||||
// store 事务存储,用于创建、读取与推进更新事务。
|
||||
store *transaction.Store
|
||||
// coordinator 事务协调器,用于以可恢复方式执行单个步骤。
|
||||
coordinator *transaction.Coordinator
|
||||
// releaseStore 发行版文件存储,供执行器使用。
|
||||
releaseStore *filestore.Store
|
||||
// units systemd 管理器,用于检查与停止后端单元。
|
||||
units systemd.Manager
|
||||
// gateway 宿主 Nginx 配置控制器,用于读取与应用上游配置。
|
||||
gateway gatewayController
|
||||
// executor 原生后端执行器,负责运行与健康检查原生后端。
|
||||
executor nativeExecutor
|
||||
// containerExecutor 容器后端执行器,负责运行与健康检查容器后端。
|
||||
containerExecutor containerExecutor
|
||||
// engine 容器引擎,仅在容器后端更新时使用。
|
||||
engine containerengine.Engine
|
||||
// containerConfigSource 容器后端配置文件在宿主机上的源路径。
|
||||
containerConfigSource string
|
||||
// containerConfigTarget 容器后端配置文件在容器内的目标路径。
|
||||
containerConfigTarget string
|
||||
containerTmpSource string
|
||||
containerTmpTarget string
|
||||
logger *slog.Logger
|
||||
drain time.Duration
|
||||
// containerTmpSource 容器后端临时目录在宿主机上的源路径。
|
||||
containerTmpSource string
|
||||
// containerTmpTarget 容器后端临时目录在容器内的目标路径。
|
||||
containerTmpTarget string
|
||||
// logger 结构化日志记录器。
|
||||
logger *slog.Logger
|
||||
// drain 切换后旧单元/旧容器的排空时长。
|
||||
drain time.Duration
|
||||
}
|
||||
|
||||
// gatewayController 抽象宿主 Nginx 配置的读取与应用,便于测试与替换实现。
|
||||
type gatewayController interface {
|
||||
// Read 返回当前宿主 Nginx 配置快照。
|
||||
Read() (hostnginx.Snapshot, error)
|
||||
// Apply 将给定快照应用到宿主 Nginx。
|
||||
Apply(context.Context, hostnginx.Snapshot) error
|
||||
}
|
||||
|
||||
// nativeExecutor 抽象原生后端执行器的运行能力。
|
||||
type nativeExecutor interface {
|
||||
// Run 执行原生后端运行流程,request 携带构件与目标槽位等信息。
|
||||
Run(context.Context, string, nativebackendexecutor.Request) error
|
||||
}
|
||||
|
||||
// containerExecutor 抽象容器后端执行器的运行能力。
|
||||
type containerExecutor interface {
|
||||
// Run 执行容器后端运行流程,request 携带镜像与容器参数等信息。
|
||||
Run(context.Context, string, backendexecutor.Request) error
|
||||
}
|
||||
|
||||
// New creates the complete native backend update orchestrator.
|
||||
// New 创建完整的原生后端更新编排器。
|
||||
//
|
||||
// 参数 config 为部署配置且必须校验通过且后端类型为 native;workRoot 必须是干净
|
||||
// 的绝对路径;store 与 coordinator 提供事务能力;releaseStore 提供发行版存储;
|
||||
// units 提供 systemd 管理;gateway 提供 Nginx 控制;httpClient 供执行器进行健康
|
||||
// 检查;logger 可为 nil,缺省使用默认日志器。返回构造完成的 Updater,若参数非法
|
||||
// 或执行器创建失败则返回错误。
|
||||
func New(
|
||||
config deploymentconfig.Config,
|
||||
workRoot string,
|
||||
@@ -114,7 +153,10 @@ func New(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewContainer creates the Docker standalone backend update orchestrator.
|
||||
// NewContainer 创建 Docker 独立容器后端更新编排器。
|
||||
//
|
||||
// 参数与 New 类似,但要求后端类型为 container 且 daemon 环境为 dev,engine 提供
|
||||
// 容器引擎能力。返回构造完成的 Updater,若参数非法或执行器创建失败则返回错误。
|
||||
func NewContainer(
|
||||
config deploymentconfig.Config,
|
||||
workRoot string,
|
||||
@@ -157,7 +199,10 @@ func NewContainer(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateRepack applies one repack ZIP selected by an absolute local path.
|
||||
// UpdateRepack 应用由绝对本地路径指定的 repack ZIP 更新。
|
||||
//
|
||||
// 参数 packagePath 是 repack ZIP 的绝对路径;report 用于回传进度,可为 nil。
|
||||
// 返回本次更新对应的事务记录以及错误。容器后端不支持该更新方式。
|
||||
func (u *Updater) UpdateRepack(ctx context.Context, packagePath string, report ProgressReporter) (transaction.Transaction, error) {
|
||||
if u.config.Backend.Type == deploymentconfig.BackendTypeContainer {
|
||||
return transaction.Transaction{}, errors.New("repack ZIP update is not implemented for container backend")
|
||||
@@ -184,7 +229,10 @@ func (u *Updater) UpdateRepack(ctx context.Context, packagePath string, report P
|
||||
}, report)
|
||||
}
|
||||
|
||||
// UpdateNativeJAR applies one JAR copied directly to the server.
|
||||
// UpdateNativeJAR 应用一个直接复制到服务器的 JAR 更新。
|
||||
//
|
||||
// 参数 jarPath 是 JAR 的本地路径;report 用于回传进度,可为 nil。返回本次更新
|
||||
// 对应的事务记录以及错误。仅原生后端支持该更新方式。
|
||||
func (u *Updater) UpdateNativeJAR(ctx context.Context, jarPath string, report ProgressReporter) (transaction.Transaction, error) {
|
||||
if u.config.Backend.Type == deploymentconfig.BackendTypeContainer {
|
||||
return transaction.Transaction{}, errors.New("--native-jar requires backend.type = native")
|
||||
@@ -207,6 +255,8 @@ func (u *Updater) UpdateNativeJAR(ctx context.Context, jarPath string, report Pr
|
||||
}, report)
|
||||
}
|
||||
|
||||
// update 原生后端更新(含重启)的统一执行入口:创建或恢复事务、物化构件、
|
||||
// 运行执行器、再切换与提交。input 描述本次更新输入,report 用于回传进度。
|
||||
func (u *Updater) update(ctx context.Context, input updateInput, report ProgressReporter) (transaction.Transaction, error) {
|
||||
existing, request, created, err := u.createOrResume(ctx, input)
|
||||
if err != nil {
|
||||
@@ -269,6 +319,8 @@ func (u *Updater) update(ctx context.Context, input updateInput, report Progress
|
||||
return u.store.Transaction(ctx, existing.ID)
|
||||
}
|
||||
|
||||
// terminalResult 根据终态事务记录返回结果:已提交则正常返回,否则返回带有状态
|
||||
// 信息的错误。
|
||||
func terminalResult(record transaction.Transaction) (transaction.Transaction, error) {
|
||||
if record.State == transaction.StateCommitted {
|
||||
return record, nil
|
||||
@@ -276,6 +328,11 @@ func terminalResult(record transaction.Transaction) (transaction.Transaction, er
|
||||
return record, fmt.Errorf("backend update transaction %s is terminal in state %s", record.ID, record.State)
|
||||
}
|
||||
|
||||
// createOrResume 为给定更新输入创建新事务,或在幂等键命中时恢复已存在事务。
|
||||
//
|
||||
// 该函数先读取当前 Nginx 快照以确定目标端口与槽位,再在事务工作目录下固化网关
|
||||
// 快照与兼容性链接备份,最后创建持久化请求并写入事务存储。返回值为事务记录、
|
||||
// 持久化请求、是否新建以及错误。
|
||||
func (u *Updater) createOrResume(ctx context.Context, input updateInput) (transaction.Transaction, persistedRequest, bool, error) {
|
||||
gatewayBefore, err := u.gateway.Read()
|
||||
if err != nil {
|
||||
@@ -372,6 +429,9 @@ func (u *Updater) createOrResume(ctx context.Context, input updateInput) (transa
|
||||
return record, persisted, false, nil
|
||||
}
|
||||
|
||||
// switchAndCommit 执行原生后端更新的切换与提交:切换 Nginx 上游到目标端口、更新
|
||||
// 兼容性 JAR 链接、排空并停止前一单元,最终把事务迁入 Committed 状态。任何切换
|
||||
// 阶段失败都会触发补偿。
|
||||
func (u *Updater) switchAndCommit(ctx context.Context, transactionID string, request persistedRequest, report ProgressReporter) error {
|
||||
operation := operationLabel(request.InputType)
|
||||
before, err := readGatewaySnapshot(request.GatewayBeforePath, request.PreviousGatewayPort)
|
||||
@@ -440,12 +500,16 @@ func (u *Updater) switchAndCommit(ctx context.Context, transactionID string, req
|
||||
}
|
||||
}
|
||||
|
||||
// reportProgress 在 report 非空时向其投递一条进度事件,report 为 nil 时静默忽略。
|
||||
func reportProgress(report ProgressReporter, progress Progress) {
|
||||
if report != nil {
|
||||
report(progress)
|
||||
}
|
||||
}
|
||||
|
||||
// rollbackAfterPreparation 在切换阶段失败后执行原生后端补偿:恢复兼容性链接、
|
||||
// 恢复 Nginx 上游、停止目标单元并恢复目标槽位,最后迁入 RolledBack 状态。cause
|
||||
// 为触发补偿的原始错误,会与补偿过程中的错误合并返回。
|
||||
func (u *Updater) rollbackAfterPreparation(ctx context.Context, transactionID string, request persistedRequest, before hostnginx.Snapshot, after hostnginx.Snapshot, cause error) error {
|
||||
record, readErr := u.store.Transaction(ctx, transactionID)
|
||||
if readErr != nil {
|
||||
@@ -490,6 +554,8 @@ func (u *Updater) rollbackAfterPreparation(ctx context.Context, transactionID st
|
||||
return errors.Join(cause, transitionErr)
|
||||
}
|
||||
|
||||
// resolveCurrentUnit 确定当前活动端口实际运行的后端单元名。它同时检查配置单元与
|
||||
// 遗留单元,要求二者恰好一个在运行,并返回运行中的那个。
|
||||
func (u *Updater) resolveCurrentUnit(ctx context.Context, port int, configuredUnit string) (string, error) {
|
||||
legacyUnit, err := legacyUnitForPort(port)
|
||||
if err != nil {
|
||||
@@ -514,16 +580,20 @@ func (u *Updater) resolveCurrentUnit(ctx context.Context, port int, configuredUn
|
||||
return legacyUnit, nil
|
||||
}
|
||||
|
||||
// fail 把事务迁入 Failed 状态并返回带错误的事务记录。
|
||||
func (u *Updater) fail(ctx context.Context, transactionID string, cause error) (transaction.Transaction, error) {
|
||||
_, transitionErr := u.store.Transition(ctx, transactionID, transaction.StateFailed, cause.Error())
|
||||
return u.currentWithError(ctx, transactionID, errors.Join(cause, transitionErr))
|
||||
}
|
||||
|
||||
// currentWithError 读取事务当前记录并把给定错误与读取错误合并返回,便于调用方在
|
||||
// 出错时仍拿到最新事务状态。
|
||||
func (u *Updater) currentWithError(ctx context.Context, transactionID string, cause error) (transaction.Transaction, error) {
|
||||
record, err := u.store.Transaction(ctx, transactionID)
|
||||
return record, errors.Join(cause, err)
|
||||
}
|
||||
|
||||
// otherPort 返回给定后端端口的对侧端口:8080 与 8081 互换。
|
||||
func otherPort(port int) int {
|
||||
if port == deploymentconfig.BackendPort8080 {
|
||||
return deploymentconfig.BackendPort8081
|
||||
@@ -531,6 +601,8 @@ func otherPort(port int) int {
|
||||
return deploymentconfig.BackendPort8080
|
||||
}
|
||||
|
||||
// legacyUnitForPort 返回给定后端端口对应的遗留 systemd 单元名,仅支持 8080 与
|
||||
// 8081 两个端口。
|
||||
func legacyUnitForPort(port int) (string, error) {
|
||||
switch port {
|
||||
case deploymentconfig.BackendPort8080:
|
||||
@@ -542,10 +614,13 @@ func legacyUnitForPort(port int) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// unitRunning 判断单元是否处于运行状态:既非 inactive 也非 failed 即视为运行。
|
||||
func unitRunning(unit systemd.Unit) bool {
|
||||
return unit.ActiveState != "inactive" && unit.ActiveState != "failed"
|
||||
}
|
||||
|
||||
// readOptionalSymlink 读取指定路径的符号链接目标;若路径不存在则返回空字符串,
|
||||
// 若路径存在但不是符号链接则报错。
|
||||
func readOptionalSymlink(path string) (string, error) {
|
||||
info, err := os.Lstat(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
@@ -564,6 +639,8 @@ func readOptionalSymlink(path string) (string, error) {
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// ensureTransactionArtifact 确保构件已物化到事务工作路径:若已存在则校验其为直接
|
||||
// 普通文件且身份匹配,否则调用 input.Materialize 进行物化。
|
||||
func ensureTransactionArtifact(input updateInput, request persistedRequest) error {
|
||||
info, err := os.Lstat(request.ArtifactPath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
@@ -578,6 +655,8 @@ func ensureTransactionArtifact(input updateInput, request persistedRequest) erro
|
||||
return verifyFileIdentity(request.ArtifactPath, request.ArtifactIdentity)
|
||||
}
|
||||
|
||||
// decodePersistedRequest 将持久化的原生后端更新请求 JSON 反序列化到目标结构体,
|
||||
// 并禁止出现未知字段。
|
||||
func decodePersistedRequest(content json.RawMessage, request *persistedRequest) error {
|
||||
decoder := json.NewDecoder(bytes.NewReader(content))
|
||||
decoder.DisallowUnknownFields()
|
||||
@@ -587,6 +666,7 @@ func decodePersistedRequest(content json.RawMessage, request *persistedRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitContext 等待指定时长,或直到 ctx 被取消。返回 ctx 取消错误或 nil。
|
||||
func waitContext(ctx context.Context, duration time.Duration) error {
|
||||
timer := time.NewTimer(duration)
|
||||
defer timer.Stop()
|
||||
|
||||
Reference in New Issue
Block a user