Do-it-yourself Docker series -- 5.7 realiza la creación de imágenes a través de contenedores

¡Acostúmbrate a escribir juntos! Este es el día 13 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

Introducción

En el artículo anterior, implementamos el comando rm para eliminar contenedores existentes. En este artículo, mejoraremos el aislamiento del sistema de archivos anterior para lograr el aislamiento del sistema de archivos entre contenedores y contenedores.

Descripción del código fuente

Póngalo en Gitee y Github al mismo tiempo, ambos se pueden obtener

La etiqueta de versión correspondiente a este capítulo es: 5.7, para evitar demasiado código detrás, no es fácil de ver, puede cambiar a la versión de etiqueta para ver

Código

La idea principal de realizar esta función es la siguiente:

En una publicación anterior: Do-it-yourself Docker series -- 4.2 Empaquetado de busybox con AUFS

Se logra el aislamiento entre el sistema de archivos del contenedor y el archivo host, pero hasta ahora, todos los contenedores usan el mismo directorio y existe una influencia mutua entre el contenedor y el contenedor.

El propósito de este artículo es eliminar el impacto aquí y aislar el sistema de archivos entre el contenedor y el contenedor.

Ideas de implementación:

El sistema de archivos anterior era el siguiente:

  • Capa de solo lectura: sistema de caja ocupada, esto solo se puede leer, es la base del sistema
  • Capa de escritura: WriterLayer, esta es la capa de escritura dentro del contenedor, que se puede modificar en consecuencia.
  • Capa de montaje: mnt, monta un sistema de archivos externo, similar al uso compartido de archivos de una máquina virtual

Para lograr el aislamiento del sistema de archivos entre contenedores, agregue otra capa a la capa de escritura y la capa de montaje, y aíslelas por el nombre del contenedor, es decir:

  • Capa de solo lectura: sin cambios
  • Capa de escritura: agregue un directorio llamado nombre del contenedor para el aislamiento, es decir, writeLayer/{container name}
  • Capa de montaje: agregue un directorio llamado nombre del contenedor para el aislamiento, es decir, mnt/{nombre del contenedor}

Después de aislar el sistema de archivos, cuando nos comprometemos, podemos empaquetar el proceso de la capa de escritura del contenedor en consecuencia.

De acuerdo con la idea, la implementación del código también es relativamente simple.Después de aclarar la idea, puede implementar rápidamente la transformación.

Modifique la capa de escritura y la capa de montaje cuando se inicie el contenedor y aíslela por el nombre del contenedor

Cuando se inicie el contenedor, obtenga el nombre del contenedor actual, que se usa para crear el directorio de aislamiento relevante.

func Run(tty, detach bool, cmdArray []string, config *subsystem.ResourceConfig, volume, containerName string) {
	// 容器容器名
	id, containerName := getContainerName(containerName)

	pwd, err := os.Getwd()
	if err != nil {
		log.Errorf("Run get pwd err: %v", err)
		return
	}
	mntUrl := pwd + "/mnt/"
	rootUrl := pwd + "/"
	// 传入初始化进程,初始化工作空间
	parent, writePipe := container.NewParentProcess(tty, containerName, rootUrl, mntUrl, volume)
	if err := parent.Start(); err != nil {
		log.Error(err)
		// 如果fork进程出现异常,但有相关的文件已经进行了挂载,需要进行清理,避免后面运行报错时,需要手工清理
		// 删除容器工作空间进行改造
		deleteWorkSpace(rootUrl, mntUrl, volume, containerName)
		return
	}

    // 记录容器信息进行改造
    containerName, err = recordContainerInfo(parent.Process.Pid, cmdArray, id, containerName)
    if err != nil {
       log.Errorf("record contariner info err: %v", err)
       return
	}
	
	......

	log.Infof("parent process run")
	if !detach {
		_ = parent.Wait()
		// 删除容器工作空间进行改造
		deleteWorkSpace(rootUrl, mntUrl, volume, containerName)
		// 删除容器信息进行改造
		deleteContainerInfo(containerName)
	}
	os.Exit(-1)
}
复制代码

obtener el nombre del contenedor

func getContainerName(containerName string) (string, string) {
	id := randStringBytes(10)
	if containerName == "" {
		containerName = id
	}
	return id, containerName
}
复制代码

Generar información de contenedores

func recordContainerInfo(pid int, cmdArray []string, id, containerName string) (string, error) {
	createTime := time.Now().Format("2000-01-01 00:00:00")
	command := strings.Join(cmdArray, " ")
	containerInfo := &container.ContainerInfo{
		ID:         id,
		Pid:        strconv.Itoa(pid),
		Command:    command,
		CreateTime: createTime,
		Status:     container.RUNNING,
		Name:       containerName,
	}

	jsonBytes, err := json.Marshal(containerInfo)
	if err != nil {
		return "", fmt.Errorf("container info to json string err: %v", err)
	}
	jsonStr := string(jsonBytes)

	dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)
	if err := os.MkdirAll(dirUrl, 0622); err != nil {
		return "", fmt.Errorf("mkdir %s err: %v", dirUrl, err)
	}
	fileName := dirUrl + "/" + container.ConfigName
	file, err := os.Create(fileName)
	defer file.Close()
	if err != nil {
		return "", fmt.Errorf("create file %s, err: %v", fileName, err)
	}

	if _, err := file.WriteString(jsonStr); err != nil {
		return "", fmt.Errorf("file write string err: %v", err)
	}
	return containerName, nil
}
复制代码

Eliminar información del contenedor

func deleteContainerInfo(containerName string) {
	dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)
	if err := os.RemoveAll(dirUrl); err != nil {
		log.Errorf("remove dir %s err: %v", dirUrl, err)
	}
}
复制代码

eliminar el espacio de trabajo del contenedor

func deleteWorkSpace(rootUrl, mntUrl, volume, containerName string) {
	unmountVolume(mntUrl, volume, containerName)
	deleteMountPoint(mntUrl + containerName + "/")
	deleteWriteLayer(rootUrl, containerName)
}

func unmountVolume(mntUrl, volume, containerName string) {
	if volume == "" {
		return
	}
	volumeUrls := strings.Split(volume, ":")
	if len(volumeUrls) != 2 || volumeUrls[0] == "" || volumeUrls[1] == "" {
		return
	}

	// 卸载容器内的 volume 挂载点的文件系统
	containerUrl := mntUrl + containerName + "/" + volumeUrls[1]
	cmd := exec.Command("umount", containerUrl)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Errorf("ummount volume failed: %v", err)
	}
}

func deleteMountPoint(mntUrl string) {
	cmd := exec.Command("umount", mntUrl)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Errorf("deleteMountPoint umount %s err : %v", mntUrl, err)
	}
	if err := os.RemoveAll(mntUrl); err != nil {
		log.Errorf("deleteMountPoint remove %s err : %v", mntUrl, err)
	}
}

func deleteWriteLayer(rootUrl, containerName string) {
	writeUrl := rootUrl + "writeLayer/" + containerName
	if err := os.RemoveAll(writeUrl); err != nil {
		log.Errorf("deleteMountPoint remove %s err : %v", writeUrl, err)
	}
}
复制代码

La siguiente es la transformación del espacio de trabajo del contenedor cuando se inicializa el contenedor

func NewParentProcess(tty bool, containerName, rootUrl, mntUrl, volume string) (*exec.Cmd, *os.File) {
	......
	
	// 将管道的一端传入fork的进程中
	cmd.ExtraFiles = []*os.File{readPipe}
	if err := newWorkSpace(rootUrl, mntUrl, volume, containerName); err != nil {
		log.Errorf("new work space err: %v", err)
		return nil, nil
	}
	cmd.Dir = mntUrl
	return cmd, writePipe
}

func newWorkSpace(rootUrl, mntUrl, volume, containerName string) error {
	if err := createReadOnlyLayer(rootUrl); err != nil {
		return err
	}
	if err := createWriteLayer(rootUrl, containerName); err != nil {
		return err
	}
	if err := createMountPoint(rootUrl, mntUrl, containerName); err != nil {
		return err
	}
	if err := mountExtractVolume(mntUrl, volume, containerName); err != nil {
		return err
	}
	return nil
}

// 我们直接把busybox放到了工程目录下,直接作为容器的只读层
func createReadOnlyLayer(rootUrl string) error {
	busyboxUrl := rootUrl + "busybox/"
	exist, err := pathExist(busyboxUrl)
	if err != nil {
		return err
	}
	if !exist {
		return fmt.Errorf("busybox dir don't exist: %s", busyboxUrl)
	}
	return nil
}

// 创建一个名为writeLayer的文件夹作为容器的唯一可写层
func createWriteLayer(rootUrl, containerName string) error {
	writeUrl := rootUrl + "writeLayer/" + containerName + "/"
	exist, err := pathExist(writeUrl)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if !exist {
		if err := os.MkdirAll(writeUrl, 0777); err != nil {
			return fmt.Errorf("create write layer failed: %v", err)
		}
	}
	return nil
}

func createMountPoint(rootUrl, mntUrl, containerName string) error {
	// 创建mnt文件夹作为挂载点
	mountPath := mntUrl + containerName + "/"
	exist, err := pathExist(mountPath)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if !exist {
		if err := os.MkdirAll(mountPath, 0777); err != nil {
			return fmt.Errorf("mkdir faild: %v", err)
		}
	}
	// 把writeLayer和busybox目录mount到mnt目录下
	dirs := "dirs=" + rootUrl + "writeLayer:" + rootUrl + "busybox"
	cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mountPath)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("mmt dir err: %v", err)
	}
	return nil
}

func mountExtractVolume(mntUrl, volume, containerName string) error {
	if volume == "" {
		return nil
	}
	volumeUrls := strings.Split(volume, ":")
	length := len(volumeUrls)
	if length != 2 || volumeUrls[0] == "" || volumeUrls[1] == "" {
		return fmt.Errorf("volume parameter input is not corrent")
	}
	return mountVolume(mntUrl+containerName+"/", volumeUrls)
}

func mountVolume(mntUrl string, volumeUrls []string) error {
	// 如果宿主机文件目录不存在则创建
	parentUrl := volumeUrls[0]
	exist, err := pathExist(parentUrl)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if !exist {
		// 使用mkdir all 递归创建文件夹
		if err := os.MkdirAll(parentUrl, 0777); err != nil {
			return fmt.Errorf("mkdir parent dir err: %v", err)
		}
	}

	// 在容器文件系统内创建挂载点
	containerUrl := mntUrl + volumeUrls[1]
	if err := os.MkdirAll(containerUrl, 0777); err != nil {
		return fmt.Errorf("mkdir container volume err: %v", err)
	}

	// 把宿主机文件目录挂载到容器挂载点
	dirs := "dirs=" + parentUrl
	cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", containerUrl)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("mount volume err: %v", err)
	}
	return nil
}
复制代码

De esta forma, cuando se inicia el contenedor, los directorios se aíslan según el nombre del contenedor.

Modifique el comando de confirmación para empaquetar el directorio correspondiente del contenedor

Modifique el comando de confirmación para empaquetar el directorio correspondiente de acuerdo con el nombre del contenedor entrante

var CommitCommand = cli.Command{
	Name:  "commit",
	Usage: "commit a container into image",
	Action: func(context *cli.Context) error {
		if len(context.Args()) < 1 {
			return fmt.Errorf("Missing container name")
		}
		containerName := context.Args().Get(0)
		return run.CommitContainer(containerName)
	},
}
复制代码

Implementación de empaques específicos

func CommitContainer(containerName string) error {
	pwd, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("Run get pwd err: %v", err)
	}
	mntUrl := pwd + "/mnt/" + containerName
	imageTar := pwd + "/" + containerName + ".tar"
	log.Infof("commit file path: %s", imageTar)
	if _, err := exec.Command("tar", "-czf", imageTar, "-C", mntUrl, ".").CombinedOutput(); err != nil {
		return fmt.Errorf("tar folder err: %s, %v", mntUrl, err)
	}
	log.Infof("end commit file: %s", imageTar)
	return nil
}
复制代码

Supongo que te gusta

Origin juejin.im/post/7086069688664326157
Recomendado
Clasificación