Make writing a habit together! This is the 13th day of my participation in the “Gold Digging Day New Plan · April More Text Challenge”. Click here for more details.

Introduction to the

In the previous part, we implemented rm command to delete existing containers. In this part, we will improve the previous file system isolation and realize file system isolation between containers

The source code that

It is available on both Gitee and Github

  • Gitee: https://gitee.com/free-love/docker-demo
  • GitHub: https://github.com/lw1243925457/dockerDemo

The corresponding version label in this section is 5.7. You can switch to the label version to avoid too much code

Code implementation

The main idea of this function is as follows:

Previous article: Write Your Own Docker series — 4.2 Wrapping BusyBox with AUFS

The container file system is isolated from the host files, but so far, all containers are using the same directory, and there is interaction between containers

The purpose of this article is to eliminate this effect and implement file system isolation between containers as well

Implementation idea:

Previous file systems were as follows:

  • Read-only layer: Busybox system, this can only read, is the foundation of the system
  • Writable layer: writerLayer. This is the writable layer inside the container that can be modified accordingly
  • Mount layer: MNT mounts external file systems, similar to virtual machine file sharing

To achieve file system isolation between containers, add another layer of writable layer and mount layer to isolate by container name, namely:

  • Read-only layer: unchanged
  • Writable layer: Add a container named directory for isolation, namely writeLayer/{container name}
  • Mount layer: add a directory named MNT /{container name} for isolation

After the file system is isolated, the corresponding container writable layer process can be packaged at commit time

According to the train of thought, the code implementation is relatively simple, their clear thinking, soon can be transformed to achieve

Modify the writable layer and mount layer at container startup to isolate by container name

When the container is started, gets the current container name, which is used to build the associated quarantine directory

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

	pwd, err := os.Getwd()
	iferr ! =nil {
		log.Errorf("Run get pwd err: %v", err)
		return
	}
	mntUrl := pwd + "/mnt/"
	rootUrl := pwd + "/"
	// Pass in the initialization process to initialize the workspace
	parent, writePipe := container.NewParentProcess(tty, containerName, rootUrl, mntUrl, volume)
	iferr := parent.Start(); err ! =nil {
		log.Error(err)
		// If an exception occurs in the fork process, related files have been mounted and need to be cleared to avoid subsequent errors
		// Delete the container workspace for transformation
		deleteWorkSpace(rootUrl, mntUrl, volume, containerName)
		return
	}

    // Record the container information for modification
    containerName, err = recordContainerInfo(parent.Process.Pid, cmdArray, id, containerName)
    iferr ! =nil {
       log.Errorf("record contariner info err: %v", err)
       return}... log.Infof("parent process run")
	if! detach { _ = parent.Wait()// Delete the container workspace for transformation
		deleteWorkSpace(rootUrl, mntUrl, volume, containerName)
		// Delete the container information for modification
		deleteContainerInfo(containerName)
	}
	os.Exit(- 1)}Copy the code

Get the container name

func getContainerName(containerName string) (string.string) {
	id := randStringBytes(10)
	if containerName == "" {
		containerName = id
	}
	return id, containerName
}
Copy the code

Generating container information

func recordContainerInfo(pid int, cmdArray []string, id, containerName string) (string, error) {
	createTime := time.Now().Format("The 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)
	iferr ! =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()
	iferr ! =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
}
Copy the code

Deleting Container Information

func deleteContainerInfo(containerName string) {
	dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)
	iferr := os.RemoveAll(dirUrl); err ! =nil {
		log.Errorf("remove dir %s err: %v", dirUrl, err)
	}
}
Copy the code

Delete the container workspace

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
	}

	// Unmount the file system of the volume mount point in the container
	containerUrl := mntUrl + containerName + "/" + volumeUrls[1]
	cmd := exec.Command("umount", containerUrl)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	iferr := 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
	iferr := cmd.Run(); err ! =nil {
		log.Errorf("deleteMountPoint umount %s err : %v", mntUrl, err)
	}
	iferr := os.RemoveAll(mntUrl); err ! =nil {
		log.Errorf("deleteMountPoint remove %s err : %v", mntUrl, err)
	}
}

func deleteWriteLayer(rootUrl, containerName string) {
	writeUrl := rootUrl + "writeLayer/" + containerName
	iferr := os.RemoveAll(writeUrl); err ! =nil {
		log.Errorf("deleteMountPoint remove %s err : %v", writeUrl, err)
	}
}
Copy the code

The following is the transformation of the construction container workspace when the container is initialized

func NewParentProcess(tty bool, containerName, rootUrl, mntUrl, volume string) (*exec.Cmd, *os.File){...// Pass one end of the pipe into the fork process
	cmd.ExtraFiles = []*os.File{readPipe}
	iferr := 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 {
	iferr := createReadOnlyLayer(rootUrl); err ! =nil {
		return err
	}
	iferr := createWriteLayer(rootUrl, containerName); err ! =nil {
		return err
	}
	iferr := createMountPoint(rootUrl, mntUrl, containerName); err ! =nil {
		return err
	}
	iferr := mountExtractVolume(mntUrl, volume, containerName); err ! =nil {
		return err
	}
	return nil
}

// We put busyBox directly in the project directory as the read-only layer of the container
func createReadOnlyLayer(rootUrl string) error {
	busyboxUrl := rootUrl + "busybox/"
	exist, err := pathExist(busyboxUrl)
	iferr ! =nil {
		return err
	}
	if! exist {return fmt.Errorf("busybox dir don't exist: %s", busyboxUrl)
	}
	return nil
}

// Create a folder called writeLayer as the only writable layer of the container
func createWriteLayer(rootUrl, containerName string) error {
	writeUrl := rootUrl + "writeLayer/" + containerName + "/"
	exist, err := pathExist(writeUrl)
	iferr ! =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 {
	// Create the MNT folder as the mount point
	mountPath := mntUrl + containerName + "/"
	exist, err := pathExist(mountPath)
	iferr ! =nil && !os.IsNotExist(err) {
		return err
	}
	if! exist {if err := os.MkdirAll(mountPath, 0777); err ! =nil {
			return fmt.Errorf("mkdir faild: %v", err)
		}
	}
	// mount writeLayer and busybox to 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
	iferr := 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)
	iflength ! =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 {
	// If the host file directory does not exist
	parentUrl := volumeUrls[0]
	exist, err := pathExist(parentUrl)
	iferr ! =nil && !os.IsNotExist(err) {
		return err
	}
	if! exist {// use mkdir all to recursively create folders
		if err := os.MkdirAll(parentUrl, 0777); err ! =nil {
			return fmt.Errorf("mkdir parent dir err: %v", err)
		}
	}

	// Create mount points in the container file system
	containerUrl := mntUrl + volumeUrls[1]
	if err := os.MkdirAll(containerUrl, 0777); err ! =nil {
		return fmt.Errorf("mkdir container volume err: %v", err)
	}

	// Mount the host file directory to the container mount point
	dirs := "dirs=" + parentUrl
	cmd := exec.Command("mount"."-t"."aufs"."-o", dirs, "none", containerUrl)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	iferr := cmd.Run(); err ! =nil {
		return fmt.Errorf("mount volume err: %v", err)
	}
	return nil
}
Copy the code

This way, when the container is started, directories are isolated between the dependencies based on the container name

Modify the commit command to package the corresponding directory of the container

Modify the commit command to package the corresponding directory according to the container name passed in

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)
	},
}
Copy the code

Concrete packaging implementation

func CommitContainer(containerName string) error {
	pwd, err := os.Getwd()
	iferr ! =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
}
Copy the code