Make writing a habit together! This is the 8th 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 the container running in the background. In this part, we will implement the ps command of docker to view the list of containers currently running

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.2. You can switch to the label version to avoid too much code

Code implementation

The main idea of this function is as follows:

1. When the container is started, the information about the container is written to the file in the specified directory

2. When viewing the running containers, read the directory where the container information files are stored and obtain all the container information files to obtain the running container list

3. When the container exits, the files in the specified directory are deleted

The core idea is shown above, generally speaking is relatively simple, let’s start to look at the specific code implementation

Container information is stored when the container is started and deleted when the container exits

In the startup function, we add logic to store container information at startup

First, we define the container information structure class as follows:

ContainerInfo is a container information class that stores container information

DefaultInfoLocation is the specified directory where convention container information is stored, and ConfigName is the file name where convention container information is stored

type ContainerInfo struct {
	Pid        string `json:"pid"` // The PID of the container's init process on the host
	ID         string `json:"id"`
	Name       string `json:"name"`
	Command    string `json:"command"`
	CreateTime string `json:"createTime"`
	Status     string `json:"status"`
}

var (
	RUNNING             = "running"
	STOP                = "stop"
	EXIT                = "exited"
	DefaultInfoLocation = "/var/run/mydocker/%s/"
	ConfigName          = "config.json"
)
Copy the code

In the run command, we add the optional -name parameter to set the name of the container

var RunCommand = cli.Command{
	Name:  "run",
	Usage: `Create a container with namespace and cgroups limit mydocker run -ti [command]`,
	Flags: []cli.Flag{
		......
		// Provide the -name after run to specify the container name argument
		cli.StringFlag{
			Name:  "name",
			Usage: "container name",
		},
	},
	Action: func(context *cli.Context) error{...// Pass the fetched container name, null if none is present
		containerName := context.String("name")
		run.Run(tty, detach, cmdArray, resConfig, volume, containerName)
		return nil}},Copy the code

The logic of the Run function is as follows:

func Run(tty, detach bool, cmdArray []string, config *subsystem.ResourceConfig, volume, containerName string){...// Record the container information
	containerName, err = recordContainerInfo(parent.Process.Pid, cmdArray, containerName)
	iferr ! =nil {
		log.Errorf("record contariner info err: %v", err)
		return}... log.Infof("parent process run")
	if! detach { _ = parent.Wait() deleteWorkSpace(rootUrl, mntUrl, volume)// Delete the container information when the container exits
		deleteContainerInfo(containerName)
	}
	os.Exit(- 1)}Copy the code

The function logic for storing container information is as follows:

1. The first step is to generate the basic information of the runtime container

2. Serialize it to JSON and store it in the specified file

If no container name is passed in, it is randomly named


func recordContainerInfo(pid int, cmdArray []string, containerName string) (string, error) {
	id := randStringBytes(10)
	createTime := time.Now().Format("The 2000-01-01 00:00:00")
	command := strings.Join(cmdArray, "")
	if containerName == "" {
		containerName = id
	}
	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
}

func randStringBytes(n int) string {
	letterBytes := "1234567890"
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}
Copy the code

The logic for deleting the container on exit is simple:

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

Reads the list of files to show running containers

In the code above, we can get the container information stored: /var/run/mydock/{containerName}/config.json

/var/run/mydocker gets all the container directories, and config.json gets the container information

Let’s first add the ps command to main.go

func main(a) {
	app := cli.NewApp()
	app.Name = "mydocker"
	app.Usage = usage

	app.Commands = []cli.Command{
		command.InitCommand,
		command.RunCommand,
		command.CommitCommand,
		command.ListCommand,
	}

	app.Before = func(context *cli.Context) error {
		log.SetFormatter(&log.JSONFormatter{})
		log.SetOutput(os.Stdout)
		return nil
	}

	iferr := app.Run(os.Args); err ! =nil {
		log.Fatal(err)
	}
}
Copy the code

Add ps command to main_command

var ListCommand = cli.Command{
	Name:  "ps",
	Usage: "list all the container",
	Action: func(context *cli.Context) error {
		return run.ListContainers()
	},
}
Copy the code

The ps command is implemented as follows:

/var/run/mydocker gets all container directories, and config.json gets container information

Then print on the console

func ListContainers(a) error {
	dirUrl := fmt.Sprintf(container.DefaultInfoLocation, "")
	dirUrl = dirUrl[:len(dirUrl)- 1]
	files, err := ioutil.ReadDir(dirUrl)
	iferr ! =nil {
		return fmt.Errorf("read dir %s err: %v", dirUrl, err)
	}

	var containers []*container.ContainerInfo
	for _, file := range files {
		tmpContainer, err := getContainerInfo(file)
		iferr ! =nil {
			return err
		}
		containers = append(containers, tmpContainer)
	}

	w := tabwriter.NewWriter(os.Stdout, 12.1.3.' '.0)
	_, _ = fmt.Fprint(w, "ID\tNAME\tPID\tSTATUS\tCOMMAND\tCREATED\n")
	for _, item := range containers {
		_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", item.ID, item.Name, item.Pid, item.Status, item.Command, item.CreateTime)
	}
	iferr := w.Flush(); err ! =nil {
		return fmt.Errorf("flush ps write err: %v", err)
	}
	return nil
}

func getContainerInfo(file fs.FileInfo) (*container.ContainerInfo, error) {
	containerName := file.Name()
	configFileDir := fmt.Sprintf(container.DefaultInfoLocation, containerName)
	configFilePath := configFileDir + container.ConfigName
	content, err := ioutil.ReadFile(configFilePath)
	iferr ! =nil {
		return nil, fmt.Errorf("read file %s err: %v", configFilePath, err)
	}
	var containerInfo container.ContainerInfo
	iferr := json.Unmarshal(content, &containerInfo); err ! =nil {
		return nil, fmt.Errorf("json unmarshal err: %v", err)
	}
	return &containerInfo, nil
}
Copy the code

Run the test

We start two background containers, one with a name and one without, and the result is nice

➜ dockerDemo git:(main)./main run -d sh {"level":"info"."msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup"."time":"2022-03-22T20:36:06+08:00"}
{"level":"info"."msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup"."time":"2022-03-22T20:36:06+08:00"}
{"level":"info"."msg":"all command is : sh"."time":"2022-03-22T20:36:06+08:00"}
{"level":"info"."msg":"parent process run"."time":"2022-03-22T20:36:06+08:00"} ➜ dockerDemo git:(main)./main run -d name test1 sh {"level":"info"."msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup"."time":"2022-03-22T20:36:14+08:00"}
{"level":"info"."msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup"."time":"2022-03-22T20:36:14+08:00"}
{"level":"info"."msg":"all command is : sh"."time":"2022-03-22T20:36:14+08:00"}
{"level":"info"."msg":"parent process run"."time":"2022-03-22T20:36:14+08:00"➜ dockerDemo git:(main)./main ps ID NAME PID STATUS COMMAND CREATED7475097580   7475097580   21886       running     sh          22000- 03- 03 00:00:00
3160412281   test1        21912       running     sh          22000- 03- 03 00:00:00➜ dockerDemo git: (main)Copy the code

However, there is still a problem. If it is the container running in the foreground, the container information file will be deleted after exit, but the container running in the background will not be deleted. As a result, the ps command is still abnormal, and the container that has exited will be displayed

There may be something wrong with my own writing, which needs to be adjusted and repaired later