refactor: use nginx -s reload instead of systemd
- doc: add comment
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
// Package containerengine defines the container runtime boundary used by update executors.
|
||||
// Package containerengine 定义更新执行器所依赖的容器运行时边界。
|
||||
//
|
||||
// 该包只声明容器运行时所必需的最小抽象,包括引擎接口、镜像、容器、
|
||||
// 挂载等数据结构,不包含任何具体实现细节;具体实现见同包的 moby.go。
|
||||
package containerengine
|
||||
|
||||
import (
|
||||
@@ -7,81 +10,134 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrNotFound 表示在容器引擎中找不到目标对象(镜像或容器)。
|
||||
var ErrNotFound = errors.New("container engine object not found")
|
||||
|
||||
// Platform is an explicit OCI operating system and CPU platform.
|
||||
// Platform 描述一个显式的 OCI 操作系统与 CPU 平台。
|
||||
type Platform struct {
|
||||
OS string
|
||||
// OS 表示操作系统名称,例如 linux。
|
||||
OS string
|
||||
// Architecture 表示 CPU 架构,例如 amd64。
|
||||
Architecture string
|
||||
Variant string
|
||||
// Variant 表示架构变体,例如 v8,可为空字符串。
|
||||
Variant string
|
||||
}
|
||||
|
||||
// Image is the immutable image information returned by the engine.
|
||||
// Image 引擎返回的不可变镜像信息。
|
||||
type Image struct {
|
||||
ID string
|
||||
RepoDigests []string
|
||||
// ID 表示镜像的唯一标识。
|
||||
ID string
|
||||
// RepoDigests 表示镜像在仓库中的内容摘要列表。
|
||||
RepoDigests []string
|
||||
// DescriptorDigest 表示 OCI 描述符摘要,不可用时为空字符串。
|
||||
DescriptorDigest string
|
||||
Platform Platform
|
||||
// Platform 表示镜像的目标操作系统与 CPU 平台。
|
||||
Platform Platform
|
||||
}
|
||||
|
||||
// RestartPolicy is passed to the engine without an implicit default.
|
||||
// RestartPolicy 传递给引擎的重启策略,引擎不会为其附加隐式默认值。
|
||||
type RestartPolicy struct {
|
||||
Name string
|
||||
// Name 表示策略名称,例如 always、on-failure。
|
||||
Name string
|
||||
// MaximumRetryCount 表示策略为 on-failure 时的最大重试次数。
|
||||
MaximumRetryCount int
|
||||
}
|
||||
|
||||
// Mount is one explicit container mount.
|
||||
// Mount 表示一个显式的容器挂载。
|
||||
type Mount struct {
|
||||
Type string
|
||||
Source string
|
||||
Target string
|
||||
// Type 表示挂载类型,例如 bind、volume。
|
||||
Type string
|
||||
// Source 表示宿主机上的源路径或卷名。
|
||||
Source string
|
||||
// Target 表示容器内的目标路径。
|
||||
Target string
|
||||
// ReadOnly 表示挂载是否为只读。
|
||||
ReadOnly bool
|
||||
}
|
||||
|
||||
// ContainerSpec contains every property controlled by the backend executor.
|
||||
// ContainerSpec 包含后端执行器控制的容器全部属性。
|
||||
type ContainerSpec struct {
|
||||
Name string
|
||||
ImageReference string
|
||||
Platform Platform
|
||||
Environment []string
|
||||
Labels map[string]string
|
||||
NetworkMode string
|
||||
RestartPolicy RestartPolicy
|
||||
Mounts []Mount
|
||||
User string
|
||||
// Name 表示容器名称。
|
||||
Name string
|
||||
// ImageReference 表示要使用的镜像引用。
|
||||
ImageReference string
|
||||
// Platform 表示容器运行的目标平台。
|
||||
Platform Platform
|
||||
// Environment 表示以 KEY=VALUE 形式给出的环境变量列表。
|
||||
Environment []string
|
||||
// Labels 表示附加到容器的键值标签。
|
||||
Labels map[string]string
|
||||
// NetworkMode 表示容器的网络模式。
|
||||
NetworkMode string
|
||||
// RestartPolicy 表示容器的重启策略。
|
||||
RestartPolicy RestartPolicy
|
||||
// Mounts 表示容器的挂载列表。
|
||||
Mounts []Mount
|
||||
// User 表示容器内的运行用户。
|
||||
User string
|
||||
// StopTimeoutSeconds 表示停止容器的超时秒数。
|
||||
StopTimeoutSeconds int
|
||||
}
|
||||
|
||||
// Container is the runtime state required for idempotent inspection.
|
||||
// Container 执行幂等检查所需的容器运行时状态。
|
||||
type Container struct {
|
||||
ID string
|
||||
Name string
|
||||
ImageID string
|
||||
ImageReference string
|
||||
Platform string
|
||||
Running bool
|
||||
Dead bool
|
||||
Status string
|
||||
Environment []string
|
||||
Labels map[string]string
|
||||
NetworkMode string
|
||||
RestartPolicy RestartPolicy
|
||||
Mounts []Mount
|
||||
User string
|
||||
// ID 表示容器标识。
|
||||
ID string
|
||||
// Name 表示容器名称。
|
||||
Name string
|
||||
// ImageID 表示容器所基于的镜像标识。
|
||||
ImageID string
|
||||
// ImageReference 表示创建容器时使用的镜像引用。
|
||||
ImageReference string
|
||||
// Platform 表示容器的运行平台。
|
||||
Platform string
|
||||
// Running 表示容器是否正在运行。
|
||||
Running bool
|
||||
// Dead 表示容器是否已经停止且不可重启。
|
||||
Dead bool
|
||||
// Status 表示容器的原始状态字符串。
|
||||
Status string
|
||||
// Environment 表示容器的环境变量列表。
|
||||
Environment []string
|
||||
// Labels 表示容器的键值标签。
|
||||
Labels map[string]string
|
||||
// NetworkMode 表示容器的网络模式。
|
||||
NetworkMode string
|
||||
// RestartPolicy 表示容器的重启策略。
|
||||
RestartPolicy RestartPolicy
|
||||
// Mounts 表示容器的挂载列表。
|
||||
Mounts []Mount
|
||||
// User 表示容器内的运行用户。
|
||||
User string
|
||||
// StopTimeoutSeconds 表示停止容器的超时秒数。
|
||||
StopTimeoutSeconds int
|
||||
}
|
||||
|
||||
// Engine is the smallest container runtime API required by an update executor.
|
||||
// Engine 更新执行器所需的最小容器运行时 API。
|
||||
//
|
||||
// 所有方法都通过 context 传递取消与超时控制;
|
||||
// 各方法的具体行为由实现决定,详见 MobyEngine。
|
||||
type Engine interface {
|
||||
// Ping 探测引擎是否可达。
|
||||
Ping(context.Context) error
|
||||
// PullImage 从远端仓库拉取指定镜像。
|
||||
PullImage(context.Context, string) error
|
||||
// LoadImage 从归档读取流加载镜像。
|
||||
LoadImage(context.Context, io.Reader) error
|
||||
// InspectImage 检查镜像并返回其不可变信息。
|
||||
InspectImage(context.Context, string) (Image, error)
|
||||
// CreateContainer 依据规格创建容器并返回其状态。
|
||||
CreateContainer(context.Context, ContainerSpec) (Container, error)
|
||||
// StartContainer 启动指定容器。
|
||||
StartContainer(context.Context, string) error
|
||||
// ContainerLogs 读取指定容器的日志流。
|
||||
ContainerLogs(context.Context, string) (io.ReadCloser, error)
|
||||
// StopContainer 停止指定容器。
|
||||
StopContainer(context.Context, string) error
|
||||
// InspectContainer 检查容器并返回其运行时状态。
|
||||
InspectContainer(context.Context, string) (Container, error)
|
||||
// RemoveContainer 移除指定容器,force 决定是否强制移除。
|
||||
RemoveContainer(context.Context, string, bool) error
|
||||
// Close 释放引擎底层资源。
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -17,13 +17,15 @@ import (
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// MobyEngine adapts the official Docker Engine Go client.
|
||||
// MobyEngine 适配官方 Docker Engine Go 客户端,实现 Engine 接口。
|
||||
type MobyEngine struct {
|
||||
// client 底层 Docker Engine 客户端。
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// NewMobyEngine creates a client from Docker's documented environment variables.
|
||||
// API negotiation remains enabled, including when DOCKER_HOST selects a non-default socket.
|
||||
// NewMobyEngine 依据 Docker 官方文档定义的环境变量创建客户端。
|
||||
// 即使 DOCKER_HOST 指向非默认 socket,API 版本协商仍然保持启用。
|
||||
// 创建失败时返回包装后的错误。
|
||||
func NewMobyEngine() (*MobyEngine, error) {
|
||||
apiClient, err := client.New(client.FromEnv)
|
||||
if err != nil {
|
||||
@@ -32,6 +34,7 @@ func NewMobyEngine() (*MobyEngine, error) {
|
||||
return &MobyEngine{client: apiClient}, nil
|
||||
}
|
||||
|
||||
// Ping 探测 Docker Engine 是否可达,不可达时返回包装后的错误。
|
||||
func (e *MobyEngine) Ping(ctx context.Context) error {
|
||||
if _, err := e.client.Ping(ctx, client.PingOptions{NegotiateAPIVersion: true}); err != nil {
|
||||
return fmt.Errorf("ping Docker Engine: %w", err)
|
||||
@@ -39,6 +42,8 @@ func (e *MobyEngine) Ping(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PullImage 从远端仓库拉取指定镜像引用。
|
||||
// 拉取完成后逐条读取并校验引擎返回的 JSON 流,确保过程中无错误。
|
||||
func (e *MobyEngine) PullImage(ctx context.Context, imageReference string) error {
|
||||
response, err := e.client.ImagePull(ctx, imageReference, client.ImagePullOptions{})
|
||||
if err != nil {
|
||||
@@ -51,6 +56,8 @@ func (e *MobyEngine) PullImage(ctx context.Context, imageReference string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadImage 从归档读取流加载镜像。
|
||||
// input 为空时返回错误;加载完成后同样校验引擎返回的 JSON 流。
|
||||
func (e *MobyEngine) LoadImage(ctx context.Context, input io.Reader) error {
|
||||
if input == nil {
|
||||
return errors.New("image archive reader is required")
|
||||
@@ -66,6 +73,8 @@ func (e *MobyEngine) LoadImage(ctx context.Context, input io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeImageLoadResponse 逐条解码 Docker 引擎的 JSON 消息流。
|
||||
// 读到 EOF 表示成功;遇到消息中的错误字段时立即返回该错误。
|
||||
func decodeImageLoadResponse(input io.Reader) error {
|
||||
decoder := json.NewDecoder(input)
|
||||
for {
|
||||
@@ -82,6 +91,8 @@ func decodeImageLoadResponse(input io.Reader) error {
|
||||
}
|
||||
}
|
||||
|
||||
// InspectImage 检查指定镜像引用并返回不可变镜像信息。
|
||||
// 找不到镜像时返回的错误会包装 ErrNotFound。
|
||||
func (e *MobyEngine) InspectImage(ctx context.Context, reference string) (Image, error) {
|
||||
response, err := e.client.ImageInspect(ctx, reference)
|
||||
if err != nil {
|
||||
@@ -103,6 +114,8 @@ func (e *MobyEngine) InspectImage(ctx context.Context, reference string) (Image,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateContainer 依据给定规格创建容器。
|
||||
// 成功创建后立即通过 InspectContainer 返回容器的完整运行时状态。
|
||||
func (e *MobyEngine) CreateContainer(ctx context.Context, spec ContainerSpec) (Container, error) {
|
||||
apiMounts := make([]mount.Mount, 0, len(spec.Mounts))
|
||||
for _, item := range spec.Mounts {
|
||||
@@ -143,6 +156,7 @@ func (e *MobyEngine) CreateContainer(ctx context.Context, spec ContainerSpec) (C
|
||||
return e.InspectContainer(ctx, result.ID)
|
||||
}
|
||||
|
||||
// StartContainer 启动指定容器,参数可为容器 ID 或名称。
|
||||
func (e *MobyEngine) StartContainer(ctx context.Context, idOrName string) error {
|
||||
if _, err := e.client.ContainerStart(ctx, idOrName, client.ContainerStartOptions{}); err != nil {
|
||||
return engineError("start container", err)
|
||||
@@ -150,6 +164,8 @@ func (e *MobyEngine) StartContainer(ctx context.Context, idOrName string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerLogs 读取指定容器的 stdout 与 stderr 日志,
|
||||
// 将引擎的多路复用流解码后合并为一个只读流返回。
|
||||
func (e *MobyEngine) ContainerLogs(ctx context.Context, idOrName string) (io.ReadCloser, error) {
|
||||
stream, err := e.client.ContainerLogs(ctx, idOrName, client.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Tail: "all"})
|
||||
if err != nil {
|
||||
@@ -163,6 +179,7 @@ func (e *MobyEngine) ContainerLogs(ctx context.Context, idOrName string) (io.Rea
|
||||
return io.NopCloser(bytes.NewReader(output.Bytes())), nil
|
||||
}
|
||||
|
||||
// StopContainer 停止指定容器,参数可为容器 ID 或名称。
|
||||
func (e *MobyEngine) StopContainer(ctx context.Context, idOrName string) error {
|
||||
if _, err := e.client.ContainerStop(ctx, idOrName, client.ContainerStopOptions{}); err != nil {
|
||||
return engineError("stop container", err)
|
||||
@@ -170,6 +187,8 @@ func (e *MobyEngine) StopContainer(ctx context.Context, idOrName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InspectContainer 检查指定容器并返回其运行时状态。
|
||||
// 各字段按引擎返回的嵌套结构逐层提取,缺失的可选部分保持零值。
|
||||
func (e *MobyEngine) InspectContainer(ctx context.Context, idOrName string) (Container, error) {
|
||||
result, err := e.client.ContainerInspect(ctx, idOrName, client.ContainerInspectOptions{})
|
||||
if err != nil {
|
||||
@@ -214,6 +233,7 @@ func (e *MobyEngine) InspectContainer(ctx context.Context, idOrName string) (Con
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// RemoveContainer 移除指定容器,force 决定是否强制移除正在运行的容器。
|
||||
func (e *MobyEngine) RemoveContainer(ctx context.Context, idOrName string, force bool) error {
|
||||
_, err := e.client.ContainerRemove(ctx, idOrName, client.ContainerRemoveOptions{Force: force})
|
||||
if err != nil {
|
||||
@@ -222,10 +242,13 @@ func (e *MobyEngine) RemoveContainer(ctx context.Context, idOrName string, force
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close 关闭底层 Docker Engine 客户端连接。
|
||||
func (e *MobyEngine) Close() error {
|
||||
return e.client.Close()
|
||||
}
|
||||
|
||||
// engineError 包装引擎操作错误。
|
||||
// 当底层错误属于“未找到”类别时,额外包装 ErrNotFound,以便调用方通过 errors.Is 识别。
|
||||
func engineError(action string, err error) error {
|
||||
if cerrdefs.IsNotFound(err) {
|
||||
return fmt.Errorf("%s: %w: %v", action, ErrNotFound, err)
|
||||
@@ -233,6 +256,8 @@ func engineError(action string, err error) error {
|
||||
return fmt.Errorf("%s: %w", action, err)
|
||||
}
|
||||
|
||||
// cloneMap 浅拷贝一个字符串映射,避免调用方后续修改影响内部数据。
|
||||
// 若 source 为 nil 则返回 nil。
|
||||
func cloneMap(source map[string]string) map[string]string {
|
||||
if source == nil {
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDecodeImageLoadResponseConsumesCompleteSuccessStream 验证成功流中的多条消息能被完整消费且不产生错误。
|
||||
func TestDecodeImageLoadResponseConsumesCompleteSuccessStream(t *testing.T) {
|
||||
t.Parallel()
|
||||
input := strings.NewReader("{\"stream\":\"Loaded image: repository:20260814-093609-d7ed70f0-v1.1.8.1\\n\"}\n" +
|
||||
@@ -14,6 +15,7 @@ func TestDecodeImageLoadResponseConsumesCompleteSuccessStream(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecodeImageLoadResponseReturnsStreamError 验证包含错误详情字段的消息会被解析为对应错误返回。
|
||||
func TestDecodeImageLoadResponseReturnsStreamError(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := decodeImageLoadResponse(strings.NewReader(`{"errorDetail":{"code":500,"message":"load failed"}}`))
|
||||
@@ -22,6 +24,7 @@ func TestDecodeImageLoadResponseReturnsStreamError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecodeImageLoadResponseRejectsMalformedJSON 验证格式错误的 JSON 输入会返回解码错误。
|
||||
func TestDecodeImageLoadResponseRejectsMalformedJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := decodeImageLoadResponse(strings.NewReader(`{"stream":`))
|
||||
|
||||
Reference in New Issue
Block a user