+
62
-

go如何编写一个类似docker的linux的虚拟容器?

go如何编写一个类似docker的linux的虚拟容器?


网友回复

+
6
-

p-run-hostnet.go (完整代码)

package main

import (
    "archive/tar"
    "compress/gzip"
    "flag"
    "fmt"
    "io"
    "net/http"
    "os"
    "os/exec"
    "os/user"
    "path/filepath"
    "strings"
    "syscall"

    "github.comcom/google/uuid"
)

// --- 配置和常量 ---

const (
    alpineURL     = "https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-minirootfs-3.18.3-x86_64.tar.gz"
    alpineTarName = "alpine-minirootfs-3.18.3-x86_64.tar.gz"
)

// 定义终端颜色
const (
    ColorRed   = "\033[0;31m"
    ColorGreen = "\033[0;32m"
    ColorBlue  = "\033[0;34m"
    ColorNone  = "\033[0m"
)

// --- 辅助函数 (日志) ---

// Info 打印蓝色信息日志
func Info(format string, a ...interface{}) {
    fmt.Printf("%sINFO:%s %s\n", ColorBlue, ColorNone, fmt.Sprintf(format, a...))
}

// Error 打印红色错误日志并退出程序
func Error(format string, a ...interface{}) {
    fmt.Fprintf(os.Stderr, "%sERROR:%s %s\n", ColorRed, ColorNone, fmt.Sprintf(format, a...))
    os.Exit(1)
}

// --- 容器核心逻辑 ---

// prepareBaseImage 检查基础镜像是否存在,如果不存在则下载并解压
func prepareBaseImage(baseDir, imagesDir, imageName string) error {
    imagePath := filepath.Join(imagesDir, imageName)
    if _, err := os.Stat(filepath.Join(imagePath, "bin")); err == nil {
        return nil // 镜像已存在
    }

    Info("未找到镜像 '%s',正在准备...", imageName)
    if err := os.MkdirAll(imagePath, 0755); err != nil {
        return fmt.Errorf("创建镜像目录失败: %w", err)
    }

    tarPath := filepath.Join(baseDir, alpineTarName)
    if _, err := os.Stat(tarPath); os.IsNotExist(err) {
        Info("正在下载 Alpine Mini RootFS...")
        if err := downloadFile(tarPath, alpineURL); err != nil {
            return fmt.Errorf("下载 Alpine 失败: %w", err)
        }
    }

    Info("正在解压 rootfs 到 %s...", imagePath)
    if err := untar(tarPath, imagePath); err != nil {
        return fmt.Errorf("解压 rootfs 失败: %w", err)
    }

    return nil
}

// createContainerInstance 创建一个新的容器文件系统实例
func createContainerInstance(imagesDir, containersDir, imageName string) (string, string, error) {
    containerID := uuid.New().String()[:8]
    containerRootfs := filepath.Join(containersDir, containerID)
    Info("正在创建容器实例: %s", containerID)

    if err := os.MkdirAll(containerRootfs, 0755); err != nil {
        return "", "", fmt.Errorf("创建容器根目录失败: %w", err)
    }

    imagePath := filepath.Join(imagesDir, imageName)
    // 使用 cp -a 命令来保留所有文件属性,比 Go 原生实现更简单可靠
    cmd := exec.Command("cp", "-a", filepath.Join(imagePath, "/."), containerRootfs)
    if err := cmd.Run(); err != nil {
        return "", "", fmt.Errorf("复制镜像文件失败: %w", err)
    }

    return containerID, containerRootfs, nil
}

// cleanupContainer 在程序退出时清理容器资源
func cleanupContainer(containerRootfs string, autoRemove bool) {
    if !autoRemove {
        return
    }
    Info("正在移除容器文件系统: %s", containerRootfs)

    // 递归卸载挂载点
    exec.Command("umount", "-R", containerRootfs).Run()

    if err := os.RemoveAll(containerRootfs); err != nil {
        fmt.Fprintf(os.Stderr, "警告: 移除 %s 失败: %v\n", containerRootfs, err)
    }
}

// buildMountCommands 为数据卷构建挂载命令字符串
func buildMountCommands(volumes []string, containerRootfs string) (string, error) {
    var mountCommands []string
    for _, vol := range volumes {
        parts := strings.SplitN(vol, ":", 2)
        if len(parts) != 2 {
            return "", fmt.Errorf("无效的卷格式: '%s',请使用 'host:container'", vol)
        }
        hostPath, containerPath := parts[0], parts[1]

        absHostPath, err := filepath.Abs(hostPath)
        if err != nil {
            return "", fmt.Errorf("获取绝对路径失败 '%s': %w", hostPath, err)
        }

        if _, err := os.Stat(absHostPath); os.IsNotExist(err) {
            return "", fmt.Errorf("主机路径不...

点击查看剩余70%

我知道答案,我要回答