Create Your Own Container Using Linux Namespaces Part-1.

In this lock-down, everyone has to maintain a social distance and in this trying time, we can learn from docker to isolate ourselves. So before that, we need to learn how docker does it?
The best approach to learn is to simulate it. For that, we’ll be creating our own container tool for the application to isolate itself.

Self isolation | SpongeBob SquarePants | Know Your Meme

 

Technology docker uses

Linux Namespaces:-

The namespace is technology is behind most of the modern-day container’s tools like docker, rkt, LXC. For providing isolation for the process.

7 namespace we use to create container.:- 

  • Mount – isolate filesystem mount points
  • UTS – isolate hostname and domainname
  • IPC – isolate interprocess communication (IPC) resources
  • PID – isolate the PID number space
  • Network – isolate network interfaces
  • User – isolate UID/GID number spaces
  • Cgroup – limit a resource usage (CPU, memory, disk I/O, network, etc.).

Above namespace is to provide a high level of Isolation. 

Isolation by Kinsmen Collective on Dribbble

In This blog I’m trying to cover namespace to create a container in golang.

Why Golang ?

I pick golang because of the fact that Docker has written in golang and it provides low-level interface.  Why docker go with Golang? Check there.

Namespace System calls.

So we will use 3 system call: –

1. Clone:- creates a new process 

2. Setns:- allows the calling process to join an existing namespace 

3. Unshare:- moves the calling process to a new namespace

Golang code for creating container.

package main

import (
	"path/filepath"
	"fmt"
	"os"
	"os/exec"
	"syscall"
	"github.com/docker/docker/pkg/reexec"
)
func main() {
	type SysProcIDMap struct {
		ContainerID int 
		HostID      int 
		Size        int 
	}
	var rootfsPath string

	cmd := reexec.Command("nsInitialisation", rootfsPath)
	cmd = exec.Command("/bin/bash")
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr

	cmd.Env = []string{"PS1=-[ns-process]- # "}
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS |
			syscall.CLONE_NEWNS |
			syscall.CLONE_NEWIPC |
			syscall.CLONE_NEWNET |
			syscall.CLONE_NEWPID |
			syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getuid(),
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 0,
				HostID:      os.Getgid(),
				Size:        1,
			},
		},
	}

	if err := cmd.Run(); err != nil {
		fmt.Printf("Error running the /bin/bash command %s\n", err)
		os.Exit(1)
	}
}

 

CLONE_NEWUTS syscall:-

CLONE_NEWUTS is set, then create the process in a new UTS namespace. it create new hostname UTS namespaces only has Only a privileged process (CAP_SYS_ADMIN).

CLONE_NEWNS syscall:-

CLONE_NEWNS is started in a new mount namespace. we have to mount file system. For that we will use pivot_root I.e. it allows you to change what /.

func pivotRoot(newroot string) error {
	putold := filepath.Join(newroot, "/.pivot_root")
	if err := syscall.Mount(
		newroot,
		newroot,
		"",
		syscall.MS_BIND|syscall.MS_REC,
		"",
	); err != nil {
		return err
	}
	// Create old put Directory
	if err := os.MkdirAll(putold, 0700); err != nil {
		return err
	}

	if err := syscall.PivotRoot(newroot, putold); err != nil {
		return err
	}

	if err := os.Chdir("/"); err != nil {
		return err
	}

	putold = "/.pivot_root"
	if err := syscall.Unmount(
		putold,
		syscall.MNT_DETACH,
	); err != nil {
		return err
	}

	if err := os.RemoveAll(putold); err != nil {
		return err
	}

	return nil
}

CLONE_NEWUSER syscall:-

CLONE_NEWUSER is set, then create the process in a new user namespace. The User namespace provides isolation of UIDs and GIDs.

CLONE_NEWIPC syscall:-

CLONE_NEWIPC is set, then create the process in a new IPC namespace.

CLONE_NEWPID syscall:-

Mounting new /proc, running process inside the namespace. This is because ps relies on /proc to detect running processes and we were still referencing the host’s /proc.

func mountProc(newroot string) error {
	source := "proc"
	target := filepath.Join(newroot, "/proc")
	fstype := "proc"
	flag := 0
	data := ""

	os.MkdirAll(target, 0755)
	if err := syscall.Mount(
		source,
		target,
		fstype,
		uintptr(flag),
		data,
	); err != nil {
		return err
	}
	return nil
}

You can go with code: https://github.com/Deveshs23/mycontainer/

In the Next part, We will focus on the Namespace network and create some basic functions of docker and also talk about reexec.

Summary:-

In this blog we have some basic containerize tool.

Reference:-

https://knowyourmeme.com/photos/1789334-spongebob-squarepants

https://dribbble.com/shots/5823662-Isolation

medium.com/@teddyking/linux-namespaces-850489d3ccf

 

Opstree is an End to End DevOps solution provider

Leave a Reply