refactor: use nginx -s reload instead of systemd
- doc: add comment
This commit is contained in:
@@ -20,27 +20,48 @@ import (
|
||||
"yms-daemon/internal/transaction"
|
||||
)
|
||||
|
||||
// inputTypeContainerImage 标识以容器镜像方式执行后端更新的输入类型。
|
||||
const inputTypeContainerImage = "container-image"
|
||||
|
||||
// persistedContainerRequest 容器后端更新请求的持久化形态,以 JSON 存入事务
|
||||
// 记录。事务在提交、重试或补偿时依赖这些字段重建执行上下文。
|
||||
type persistedContainerRequest struct {
|
||||
InputType string `json:"inputType"`
|
||||
ImageReference string `json:"imageReference"`
|
||||
ImmutableReference string `json:"immutableReference"`
|
||||
ImageDigest string `json:"imageDigest"`
|
||||
Platform containerengine.Platform `json:"platform"`
|
||||
TargetPort int `json:"targetPort"`
|
||||
TargetContainer string `json:"targetContainer"`
|
||||
PreviousPort int `json:"previousPort"`
|
||||
PreviousContainer string `json:"previousContainer"`
|
||||
TargetHealthEndpoint string `json:"targetHealthEndpoint"`
|
||||
StartLog bool `json:"startLog"`
|
||||
GatewayBeforePath string `json:"gatewayBeforePath"`
|
||||
GatewayAfterPath string `json:"gatewayAfterPath"`
|
||||
GatewayReceiptPath string `json:"gatewayReceiptPath"`
|
||||
// InputType 标识输入来源类型。
|
||||
InputType string `json:"inputType"`
|
||||
// ImageReference 用户提供的镜像引用(带标签)。
|
||||
ImageReference string `json:"imageReference"`
|
||||
// ImmutableReference 解析出的不可变镜像引用(带摘要)。
|
||||
ImmutableReference string `json:"immutableReference"`
|
||||
// ImageDigest 解析出的镜像仓库摘要。
|
||||
ImageDigest string `json:"imageDigest"`
|
||||
// Platform 镜像的精确平台信息。
|
||||
Platform containerengine.Platform `json:"platform"`
|
||||
// TargetPort 本次更新要切换到的目标端口。
|
||||
TargetPort int `json:"targetPort"`
|
||||
// TargetContainer 目标槽位的容器名。
|
||||
TargetContainer string `json:"targetContainer"`
|
||||
// PreviousPort 更新前 Nginx 上游指向的端口。
|
||||
PreviousPort int `json:"previousPort"`
|
||||
// PreviousContainer 更新前活动槽位的容器名,首次安装时为空。
|
||||
PreviousContainer string `json:"previousContainer"`
|
||||
// TargetHealthEndpoint 目标容器的健康检查端点。
|
||||
TargetHealthEndpoint string `json:"targetHealthEndpoint"`
|
||||
// StartLog 表示是否在启动阶段转发容器日志。
|
||||
StartLog bool `json:"startLog"`
|
||||
// GatewayBeforePath 切换前 Nginx 配置快照的文件路径。
|
||||
GatewayBeforePath string `json:"gatewayBeforePath"`
|
||||
// GatewayAfterPath 切换后 Nginx 配置快照的文件路径。
|
||||
GatewayAfterPath string `json:"gatewayAfterPath"`
|
||||
// GatewayReceiptPath Nginx 切换成功后的回执文件路径。
|
||||
GatewayReceiptPath string `json:"gatewayReceiptPath"`
|
||||
}
|
||||
|
||||
// UpdateContainerImage pulls one development image, freezes its repository
|
||||
// digest, and updates the inactive Docker backend slot.
|
||||
// UpdateContainerImage 拉取一个开发镜像,冻结其仓库摘要,并更新非活动的 Docker
|
||||
// 后端槽位。
|
||||
//
|
||||
// 参数 imageReference 是必须带标签且不含多余空白的精确镜像引用;startLog 表示
|
||||
// 是否转发容器启动日志;report 用于回传实时进度,可为 nil。返回值为本次更新
|
||||
// 对应的事务记录以及错误。若已存在同镜像的活动事务则复用续跑。
|
||||
func (u *Updater) UpdateContainerImage(ctx context.Context, imageReference string, startLog bool, report ProgressReporter) (transaction.Transaction, error) {
|
||||
if u.containerExecutor == nil || u.engine == nil {
|
||||
return transaction.Transaction{}, errors.New("container backend updater is not configured")
|
||||
@@ -162,11 +183,13 @@ func (u *Updater) UpdateContainerImage(ctx context.Context, imageReference strin
|
||||
return u.runContainerUpdate(ctx, record, request, created, report)
|
||||
}
|
||||
|
||||
// resolveContainerSlots reconciles the committed deployment, gateway and both
|
||||
// exact container names before selecting a target. A fresh installation has no
|
||||
// deployment row, no committed container transaction history and no slot
|
||||
// containers. It starts on the non-routed slot so traffic is exposed only after
|
||||
// health passes.
|
||||
// resolveContainerSlots 在选择目标槽位前,核对已提交的部署、网关以及两个精确
|
||||
// 容器名是否一致。全新安装没有部署记录、没有已提交的容器事务历史,也没有槽位
|
||||
// 容器,会从非路由槽位启动,这样只有健康检查通过后才会暴露流量。
|
||||
//
|
||||
// 返回值依次为目标端口、目标槽位以及上一容器名。activePort 为当前路由端口,
|
||||
// activeSlot 为当前路由槽位,deployment 为已提交的部署记录,hasDeployment 与
|
||||
// hasHistory 分别表示是否存在部署记录与容器事务历史。
|
||||
func (u *Updater) resolveContainerSlots(
|
||||
ctx context.Context,
|
||||
activePort int,
|
||||
@@ -224,12 +247,19 @@ func (u *Updater) resolveContainerSlots(
|
||||
return inactivePort, inactiveSlot, "", nil
|
||||
}
|
||||
|
||||
// resolvedImage 拉取并解析后得到的镜像信息。
|
||||
type resolvedImage struct {
|
||||
// ImmutableReference 带摘要的不可变镜像引用。
|
||||
ImmutableReference string
|
||||
Digest string
|
||||
Platform containerengine.Platform
|
||||
// Digest 镜像仓库摘要。
|
||||
Digest string
|
||||
// Platform 镜像的精确平台信息。
|
||||
Platform containerengine.Platform
|
||||
}
|
||||
|
||||
// pullAndResolveImage 解析镜像引用、确保其为带标签引用,拉取镜像并检查其平台
|
||||
// 信息,随后从仓库摘要中解析出唯一的不可变引用。若仓库存在多个匹配摘要则报错,
|
||||
// 以保证后续部署所用的引用是确定且唯一的。
|
||||
func (u *Updater) pullAndResolveImage(ctx context.Context, imageReference string) (resolvedImage, error) {
|
||||
named, err := reference.ParseNormalizedNamed(imageReference)
|
||||
if err != nil {
|
||||
@@ -276,6 +306,9 @@ func (u *Updater) pullAndResolveImage(ctx context.Context, imageReference string
|
||||
return resolvedImage{}, errors.New("repository digest resolution produced no result")
|
||||
}
|
||||
|
||||
// runContainerUpdate 根据事务当前状态执行容器后端更新的核心流程:启动目标容器,
|
||||
// 再执行切换与提交。created 表示本调用是否新建了事务;report 用于回传进度。执行
|
||||
// 失败时若事务尚未进入失败态则触发容器补偿。
|
||||
func (u *Updater) runContainerUpdate(ctx context.Context, record transaction.Transaction, request persistedContainerRequest, created bool, report ProgressReporter) (transaction.Transaction, error) {
|
||||
message := "Resuming container backend transaction " + record.ID
|
||||
if created {
|
||||
@@ -326,6 +359,9 @@ func (u *Updater) runContainerUpdate(ctx context.Context, record transaction.Tra
|
||||
return u.store.Transaction(ctx, record.ID)
|
||||
}
|
||||
|
||||
// switchAndCommitContainer 执行容器后端的流量切换与提交:切换 Nginx 上游到目标
|
||||
// 端口、进入排空阶段、确认目标容器运行且镜像引用匹配,最后提交容器部署记录。
|
||||
// 切换失败时触发容器补偿。
|
||||
func (u *Updater) switchAndCommitContainer(ctx context.Context, transactionID string, request persistedContainerRequest, report ProgressReporter) error {
|
||||
before, err := readGatewaySnapshot(request.GatewayBeforePath, request.PreviousPort)
|
||||
if err != nil {
|
||||
@@ -399,6 +435,9 @@ func (u *Updater) switchAndCommitContainer(ctx context.Context, transactionID st
|
||||
}
|
||||
}
|
||||
|
||||
// rollbackContainer 执行容器后端补偿:恢复 Nginx 上游、停止被补偿的目标容器,并
|
||||
// 将事务迁入 RolledBack 状态。cause 为触发补偿的原始错误,会与补偿过程中的错误
|
||||
// 合并后返回。
|
||||
func (u *Updater) rollbackContainer(ctx context.Context, transactionID string, request persistedContainerRequest, before hostnginx.Snapshot, after hostnginx.Snapshot, cause error) error {
|
||||
record, err := u.store.Transaction(ctx, transactionID)
|
||||
if err != nil {
|
||||
@@ -420,11 +459,15 @@ func (u *Updater) rollbackContainer(ctx context.Context, transactionID string, r
|
||||
return errors.Join(cause, transitionErr)
|
||||
}
|
||||
|
||||
// containerStopOperation 停止某个后端容器的事务操作。
|
||||
type containerStopOperation struct {
|
||||
// engine 容器引擎。
|
||||
engine containerengine.Engine
|
||||
name string
|
||||
// name 待停止的容器名。
|
||||
name string
|
||||
}
|
||||
|
||||
// Apply 停止指定容器,若容器不存在则视为已满足(幂等成功)。
|
||||
func (o *containerStopOperation) Apply(ctx context.Context) error {
|
||||
err := o.engine.StopContainer(ctx, o.name)
|
||||
if errors.Is(err, containerengine.ErrNotFound) {
|
||||
@@ -433,6 +476,7 @@ func (o *containerStopOperation) Apply(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inspect 检查容器状态:不存在或已停止视为已应用,仍运行视为未应用。
|
||||
func (o *containerStopOperation) Inspect(ctx context.Context) (transaction.Inspection, error) {
|
||||
record, err := o.engine.InspectContainer(ctx, o.name)
|
||||
if errors.Is(err, containerengine.ErrNotFound) {
|
||||
@@ -448,6 +492,8 @@ func (o *containerStopOperation) Inspect(ctx context.Context) (transaction.Inspe
|
||||
return transaction.Inspection{Status: transaction.InspectionNotApplied, Result: result}, nil
|
||||
}
|
||||
|
||||
// containerGatewaySwitchIntent 构造容器后端 Nginx 上游切换步骤的意图,记录切换
|
||||
// 前后的端口。
|
||||
func containerGatewaySwitchIntent(request persistedContainerRequest) transaction.StepIntent {
|
||||
return stepIntent("backend.container.gateway.switch", "switch host Nginx to container backend", struct {
|
||||
BeforePort int `json:"beforePort"`
|
||||
@@ -455,24 +501,30 @@ func containerGatewaySwitchIntent(request persistedContainerRequest) transaction
|
||||
}{request.PreviousPort, request.TargetPort})
|
||||
}
|
||||
|
||||
// containerGatewayRestoreIntent 构造容器后端失败后恢复 Nginx 上游步骤的意图,记录
|
||||
// 需恢复到的端口。
|
||||
func containerGatewayRestoreIntent(request persistedContainerRequest) transaction.StepIntent {
|
||||
return stepIntent("backend.container.gateway.restore", "restore host Nginx after container backend failure", struct {
|
||||
Port int `json:"port"`
|
||||
}{request.PreviousPort})
|
||||
}
|
||||
|
||||
// stopPreviousContainerIntent 构造排空后停止前一容器步骤的意图,记录容器名。
|
||||
func stopPreviousContainerIntent(request persistedContainerRequest) transaction.StepIntent {
|
||||
return stepIntent("backend.previous-container.stop", "stop previous backend container after drain", struct {
|
||||
ContainerName string `json:"containerName"`
|
||||
}{request.PreviousContainer})
|
||||
}
|
||||
|
||||
// stopTargetContainerIntent 构造停止被补偿容器步骤的意图,记录容器名。
|
||||
func stopTargetContainerIntent(request persistedContainerRequest) transaction.StepIntent {
|
||||
return stepIntent("backend.target-container.stop", "stop compensated backend container", struct {
|
||||
ContainerName string `json:"containerName"`
|
||||
}{request.TargetContainer})
|
||||
}
|
||||
|
||||
// decodeContainerRequest 将持久化的容器后端更新请求 JSON 反序列化到目标结构体,
|
||||
// 并禁止出现未知字段。
|
||||
func decodeContainerRequest(content json.RawMessage, request *persistedContainerRequest) error {
|
||||
decoder := json.NewDecoder(bytes.NewReader(content))
|
||||
decoder.DisallowUnknownFields()
|
||||
|
||||
Reference in New Issue
Block a user