目录
目录结构
Main
Command
Namespace
UFS
Cgroups
其中, ubuntu的基础包来源于清华源
Index of /ubuntu-cdimage/ubuntu-base/releases/23.04/release/ | 清华大学开源软件镜像站 | Tsinghua Open Source MirrorIndex of /ubuntu-cdimage/ubuntu-base/releases/23.04/release/ | 清华大学开源软件镜像站,致力于为国内和校内用户提供高质量的开源软件镜像、Linux 镜像源服务,帮助用户更方便地获取开源软件。本镜像站由清华大学 TUNA 协会负责运行维护。https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cdimage/ubuntu-base/releases/23.04/release/
main.go
package main
import (
"gocker/command"
"os"
)
func main() {
switch os.Args[1] {
case "run":
err := command.Run()
if err != nil {
panic(err)
}
case "init":
err := command.Init(os.Args[2], os.Args[3], os.Args[3:])
if err != nil {
panic(err)
}
}
}
command/init.go
package command
import (
"fmt"
"gocker/ufs"
"syscall"
"os"
)
func Init(containerName string, cmd string, args []string) (err error) {
err = ufs.SetMntNamespace(containerName)
if err != nil {
return fmt.Errorf("set mnt namespace fail %s", err)
}
// 改变当前工作目录
syscall.Chdir("/")
// 设置挂载
defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
err = syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
if err != nil {
return fmt.Errorf("mount proc fail %s", err)
}
// 执行命令
err = syscall.Exec(cmd, args, os.Environ())
if err != nil {
return fmt.Errorf("exec proc fail %s", err)
}
return nil
}
command/run.go
package command
import (
"fmt"
"gocker/cgroups"
"gocker/namespace"
"gocker/ufs"
"os"
"os/exec"
"time"
)
func Run() error {
os.Args[1] = "init"
cmd := exec.Command("/proc/self/exe", os.Args[1:]...)
namespace.SetNamespace(cmd)
err := cmd.Start()
if err != nil {
return fmt.Errorf("cmd start fail %s", err)
}
containerName := os.Args[2]
time.Sleep(time.Second)
err = cgroups.ConfigDefaultCgroups(cmd.Process.Pid, containerName)
if err != nil {
fmt.Printf("config cgroup fail %s\n", err)
}
cmd.Wait()
cgroups.CleanCgroupsPath(containerName)
ufs.DelMntNamespace(containerName)
return nil
}
Unamespace/namespace.go
package namespace
import (
"os"
"os/exec"
"syscall"
)
func SetNamespace(cmd *exec.Cmd) {
// 设置容器进程的命名空间
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
}
// 设置容器进程的环境
cmd.Env = os.Environ()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
ufs/ufs.go
package ufs
import (
"fmt"
"os"
"os/exec"
"syscall"
)
const (
// 合并后逻辑上的完整文件系统
mntLayerPath = "/root/gocker/mnt"
// 工作目录
workLayerPath = "/root/gocker/work"
// 叠加层
overLayerPath = "/root/gocker/over"
// 只读的根基础文件系统
imagePath = "ubuntu-base-23.04-base-amd64"
oldPath = ".old"
)
// 获取容器中work目录路径
func workerLayer(containerName string) string {
return fmt.Sprintf("%s/%s", workLayerPath, containerName)
}
// 获取容器中逻辑根文件系统路径
func mntLayer(containerName string) string {
return fmt.Sprintf("%s/%s", mntLayerPath, containerName)
}
// 获取容器中over目录路径
func overLayer(containerName string) string {
return fmt.Sprintf("%s/%s", overLayerPath, containerName)
}
func mntOldLayer(containerName string) string {
return fmt.Sprintf("%s/%s", mntLayer(containerName), oldPath)
}
func delMntNamespace(path string) error {
_, err := exec.Command("umount", path).CombinedOutput()
if err != nil {
return fmt.Errorf("umount fail path=%s err=%s", path, err)
}
err = os.RemoveAll(path)
if err != nil {
return fmt.Errorf("remove dir fail path=%s err=%s", path, err)
}
return err
}
func DelMntNamespace(containerName string) error {
err := delMntNamespace(mntLayer(containerName))
if err != nil {
return err
}
err = delMntNamespace(workerLayer(containerName))
if err != nil {
return err
}
err = delMntNamespace(overLayer(containerName))
if err != nil {
return err
}
return nil
}
func SetMntNamespace(containerName string) error {
containerMntPath := mntLayer(containerName)
containerWorkPath := workerLayer(containerName)
containerOverPath := overLayer(containerName)
containerMntOldPath := mntOldLayer(containerName)
// 创建挂载点
err := os.MkdirAll(containerMntPath, 0700)
if err != nil {
return err
}
// 创建工作目录
err = os.MkdirAll(containerWorkPath, 0700)
if err != nil {
return err
}
// 创建叠加目录
err = os.MkdirAll(containerOverPath, 0700)
if err != nil {
return err
}
// 挂载overlay的联合文件系统
err = syscall.Mount("overlay", containerMntPath, "overlay", 0,
fmt.Sprintf("upperdir=%s,lowerdir=%s,workdir=%s", containerOverPath, imagePath, containerWorkPath))
if err != nil {
return fmt.Errorf("mount overlay fail err=%s", err)
}
// 设置为私有文件系统
err = syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "")
if err != nil {
return fmt.Errorf("reclare rootfs private fail err=%s", err)
}
// 挂载
err = syscall.Mount(containerMntPath, containerMntPath, "bind", syscall.MS_BIND|syscall.MS_REC, "")
if err != nil {
return fmt.Errorf("mount rootfs in new mnt space fail err=%s", err)
}
err = os.MkdirAll(containerMntOldPath, 0700)
if err != nil {
return fmt.Errorf("mkdir mnt old layer fail err=%s", err)
}
// 设置私有的根文件系统
err = syscall.PivotRoot(containerMntPath, containerMntOldPath)
if err != nil {
return fmt.Errorf("pivot root fail err=%s", err)
}
return nil
}
cgroups/cgroups.go
package cgroups
import (
"fmt"
"os"
"os/exec"
"path"
"strconv"
)
const (
cgroupsPath = "/sys/fs/cgroup"
dockerName = "gocker"
)
func ConfigDefaultCgroups(pid int, containerName string) error {
var (
cpuPath = path.Join(cgroupsPath, "cpu", dockerName, containerName)
memoryPath = path.Join(cgroupsPath, "memory", dockerName, containerName)
)
// 创建容器的控制目录
if err := os.MkdirAll(cpuPath, 0700); err != nil {
return fmt.Errorf("create cgroup path fail err=%s", err)
}
if err := os.MkdirAll(memoryPath, 0700); err != nil {
return fmt.Errorf("create cgroup path fail err=%s", err)
}
// 设置cpu
if err := os.WriteFile(path.Join(cpuPath, "cpu.cfs_quota_us"), []byte("50000"), 0700); err != nil {
return fmt.Errorf("write cpu quota us fail err=%s", err)
}
if err := os.WriteFile(path.Join(cpuPath, "tasks"), []byte(strconv.Itoa(pid)), 0644); err != nil {
return fmt.Errorf("write cpu tasks fail err=%s", err)
}
// 设置内存
if err := os.WriteFile(path.Join(memoryPath, "memory.limit_in_bytes"), []byte("200m"), 0700); err != nil {
return fmt.Errorf("write memory limit bytes fail err=%s", err)
}
if err := os.WriteFile(path.Join(memoryPath, "tasks"), []byte(strconv.Itoa(pid)), 0644); err != nil {
return fmt.Errorf("write memory tasks fail err=%s", err)
}
return nil
}
func CleanCgroupsPath(containerName string) error {
var err error
output, e := exec.Command("cgdelete", "-r", fmt.Sprintf("memory:%s/%s", dockerName, containerName)).Output()
if e != nil {
err = fmt.Errorf("cgdelete fail err=%s output=%s", e, string(output))
}
output, err = exec.Command("cgdelete", "-r", fmt.Sprintf("cpu:%s/%s", dockerName, containerName)).Output()
if e != nil {
err = fmt.Errorf("%s, cgdelete fail err=%s output=%s", err, e, string(output))
}
return err
}