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:
- UTS namespace (for setting a new hostname without it being global)
- PID namespace, the processes inside a PID namespaces can only see the processes inside that namespace
- Network namespace ...
- etc.
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:
- the hostname
- the pid of the process
Hints
Use the os package to get the pid of the current process: pid := os.Getpid()
Step 2: add namespace isolation#
- 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.
- 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` functionStep 4: test#
- 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#
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
}