Implementing namespace isolation

← Back to Index

Implementing namespace isolation

The next natural step towards something that resembles a real container is isolating the process from the other processes in the system. In Linux this is done thanks to namespaces, there are different namespaces provided by the Linux kernel:

We will only look at the UTS and PID namespace in this exercise.

Step 1: prepare the child

To make sure we are really isolated, first add some logs to the child process, print:

Hints

Use the os package to get the pid of the current process: pid := os.Getpid()

Step 2: add namespace isolation

  1. Modify the parent process creation to include namespace flags:
func run() error {
	cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args...)...)

	//TODO:
	// 1. Add namespace flags for PID and UTS namespaces

	if err := cmd.Wait(); err != nil {
		return fmt.Errorf("wait %w", err)
	}

	fmt.Printf("Container exited with exit code %d\n", cmd.ProcessState.ExitCode())
}
Hint

Look at the SysProcAttr property of the exec.Cmd structure

Hint 2

You need to set the Cloneflags to the cmd.

Hint 3 / Solution
cmd.SysProcAttr = &syscall.SysProcAttr {
    Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID,
}

Step 3: set the hostname

Now that the child lives in its own new host and pid namespaces, we can set the hostname for that namespace and also take a look at our pid, if everything went well, the child pid should be 1.

  1. Add hostname configuration to the child process:
func child() error {
	//TODO:
	// 1. Set the container hostname
	// 2. Print the hostname to verify the change
}
Hint Look at `syscall.Sethostname` function

Step 4: test

  1. Build and run your program:
# Build the program
make

# Run with sudo
sudo ./bin/devoxx-docker

Summary

We have now implemented PID and UTS namespace isolation, providing process isolation and custom hostname configuration for containers.
This is a crucial step towards building a fully functional container runtime.

Additional Resources

Previous step Next step

Solution

Click to see the complete solution
func main() {
    if len(os.Args) < 2 {
        if err := run(); err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    }
    switch os.Args[1] {
    case "child":
        if err := child(); err != nil {
            log.Fatal(err)
        }
    case "run":
        if err := run(); err != nil {
            log.Fatal(err)
        }
    default:
        log.Fatal("Unknown command", os.Args[1])
    }
}

func child() error {
    // Print the PID of the current process
    fmt.Println("CHILD: Hello from child, my pid is", os.Getpid())

    // Print a simple message
    fmt.Println("Hello from child")
	
    // Set container hostname
    if err := syscall.Sethostname([]byte("container")); err != nil {
        return err
    }

    // Print new hostname to verify the change
    hostname, err := os.Hostname()
    if err != nil {
        return err
    }
    fmt.Printf("CHILD Hostname: %s\n", hostname)
    
    return nil
}

func run() error {
    // Print the PID of the current process
    fmt.Println("PARENT: Hello from parent, my pid is", os.Getpid())
    
	// Print hostname
    hostname, err := os.Hostname()
    if err != nil {
        return err
    }
    fmt.Printf("PARENT Hostname: %s\n", hostname)
	
    cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[1:]...)...)
    
    // Set up stdin/stdout/stderr
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    // Add namespace flags for PID and UTS namespaces
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID,
    }

    if err := cmd.Start(); err != nil {
        return fmt.Errorf("start: %w", err)
    }

    if err := cmd.Wait(); err != nil {
        return fmt.Errorf("wait: %w", err)
    }

    fmt.Printf("Container exited with exit code %d\n", cmd.ProcessState.ExitCode())
    return nil
}