《自己动手写Docker》学习笔记2

《自己动手写Docker》学习笔记2

1 前言

由于本人毕业设计与云原生领域相关,因此最近在学习Docker相关知识,《自己动手写Docker》涵盖了Docker底层的各类知识,还提供了各类实验及代码Demo,是入门云原生的不错之书。本文主要是记录本人的实验过程以及一些总结感悟。

本文所涉及的实验环境:

Vmware Workstation 16 搭建Ubuntu20.04环境

Linux内核为5.10.x

Go版本1.17.1

2 第二章 基础技术

2.1 Linux Namespace

Linux Namespace 是Kernel的一个功能,它可以隔离一系列的系统资源,比如 PID ( Process ID )、User ID、Network 等。

当前 Linux 共实现了6种不同类型的 Namespace。

在这里插入图片描述

Namespace API 主要使用如下 个系统调用

  • clone ()创建新进程。根据系统调用参数来判断哪些类型的 Namespace 被创建,而且它们的子进程也会被包含到这些 Namespace 中。

  • unshare()将进程移出某个 Namespace。

  • setns ()将进程加入到 Namespace 中。

UTS NameSpace
#在/src 目录下创建main.go文件进行实验
vim main.go

#写入
package main

import (
    "os/exec"
    "syscall"
    "os"
    "log"
)

func main() {
    cmd := exec.Command("sh")//指定被fork()出来的新进程内的初始化进程
    cmd.SysProcAttr = &syscall.SysProcAttr{
        //已经封装clone,直接进行调用就好
        Cloneflags: syscall.CLONE_NEWUTS,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

#退出,并保存
#输入go run main.go 进入namespace 空间
go run main.go

在这里插入图片描述

IPC NameSpace

IPC Namespace 是用来隔离 System V IPC 和POSIX message queues.每一个IPC Namespace都有他们自己的System V IPC 和POSIX message queue。

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)
func main()  {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS|
            syscall.CLONE_NEWIPC, #此处添加IPC命名空间
    }
    cmd.Stdin=os.Stdin
    cmd.Stdout=os.Stdout
    cmd.Stderr=os.Stderr

    if err :=cmd.Run(); err!=nil{
        log.Fatal(err)
    }
}
PID NameSpacce

PID namespace是用来隔离进程 id。同样的一个进程在不同的 PID Namespace 里面可以拥有不同的 PID。

可以这样理解,在 docker container 里面,我们使用ps -ef 发现,容器内在前台跑着的那个init进程的 PID 是1,但是我们在容器外,使用ps -ef会发现同样的进程却有不同的 PID,这就是PID namespace 干的事情。

修改前

在这里插入图片描述

修改

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main()  {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags : syscall.CLONE_NEWUTS|syscall.CLONE_NEWIPC|
            syscall.CLONE_NEWPID, #此处添加PID命名空间
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run();err!=nil{
        log.Fatal(err)
    }
}

修改后,进入命名空间

在这里插入图片描述

Mount NameSpace

之前的Domo中,暂时不能使用top和ps查看,因为其会使用/proc内容

Linux系统上的/proc目录是一种文件系统,即proc文件系统。与其它常见的文件系统不同的是,/proc是一种伪文件系统(也即虚拟文件系统),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态。
基于/proc文件系统如上所述的特殊性,其内的文件也常被称作虚拟文件

修改代码,添加MNT命名空间

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main()  {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS|syscall.CLONE_NEWIPC|
            syscall.CLONE_NEWPID|syscall.CLONE_NEWNS,#此处添加命名空间mnt
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run();err!=nil{
        log.Fatal(err)
    }
}

未挂载/proc前

在这里插入图片描述

挂载/proc

mount -t proc proc/proc
ps -ef

在这里插入图片描述

User NameSpace

User namespace 主要是隔离用户的用户组ID。也就是说,一个进程的User ID 和Group ID 在User namespace 内外可以是不同的。
比较常用的是,在宿主机上以一个非root用户运行创建一个User namespace,然后在User namespace里面却映射成root 用户。
这个进程在User namespace里面有root权限,但是在User namespace外面却没有root的权限。

这里需要注意: CentOS 默认关闭了User namespace,而且内核版本太高会导致代码无法运行,请确认内核版本之后再修改。

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main() {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
            syscall.CLONE_NEWUSER, #此处增加User namespace
    }
    cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(1), Gid: uint32(1)}
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
    os.Exit(-1)
}

在这里插入图片描述
在这里插入图片描述

NetWork NameSpace

Network namespace 是用来隔离网络设备,IP地址端口等网络栈的namespace。Network namespace 可以让每个容器拥有自己独立的网络设备(虚拟的),而且容器内的应用可以绑定到自己的端口,每个 namesapce 内的端口都不会互相冲突。在宿主机上搭建网桥后,就能很方便的实现容器之间的通信,而且每个容器内的应用都可以使用相同的端口。

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func main() {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
            syscall.CLONE_NEWUSER | syscall.CLONE_NEWNET,
    }
    cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(1), Gid: uint32(1)}
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
    os.Exit(-1)
}

宿主机网络

在这里插入图片描述

命名空间内网络

在这里插入图片描述

可以看到宿主机上有lo, eth0, eth1 等网络设备,而在Namespace 里面什么网络设备都没有。这样就能展现 Network namespace 与宿主机之间的网络隔离。

2.2 Cgroups

Linux Cgroups (Control Groups )提供了对一组进程及将来子进程的资源限制、控制和统计的能力,这些资源包括 CPU、内存、存储、网络等。

Cgroups 中的3个组件
  • cgroup : 对一组经常进行管理,将一组进程和一组subsystem关联起来。
  • subsystem:一组资源控制模块,主要包含对内存和CPU的限制
  • hierarchy:将一组cgroup串成一个树状结构,通过树状结构cgroup可以做到继承。

在这里插入图片描述

用go语言实现通过cgroup限制容器的资源
package main

import (
    "os/exec"
    "path"
    "os"
    "fmt"
    "io/ioutil"
    "syscall"
    "strconv"
)

const cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory"

func main() {
    if os.Args[0] == "/proc/self/exe" {
        //容器进程
        fmt.Printf("current pid %d", syscall.Getpid())
        fmt.Println()
        cmd := exec.Command("sh", "-c", `stress --vm-bytes 200m --vm-keep -m 1`)
        cmd.SysProcAttr = &syscall.SysProcAttr{
        }
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr

        if err := cmd.Run(); err != nil {
            fmt.Println(err)
            os.Exit(1)
        }
    }

    cmd := exec.Command("/proc/self/exe")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Start(); err != nil {
        fmt.Println("ERROR", err)
        os.Exit(1)
    } else {
        //得到fork出来进程映射在外部命名空间的pid
        fmt.Printf("%v", cmd.Process.Pid)

        // 在系统默认创建挂载了memory subsystem的Hierarchy上创建cgroup
        os.Mkdir(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit"), 0755)
        // 将容器进程加入到这个cgroup中
        ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "tasks") , []byte(strconv.Itoa(cmd.Process.Pid)), 0644)
        // 限制cgroup进程使用
        ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "memory.limit_in_bytes") , []byte("100m"), 0644)
    }
    cmd.Process.Wait()
}

通过对Cgroups虚拟文件系统的配置,我们让容器中的把stress进程的内存占用限制到了100m

2.3 Union File System

主要涉及以下几个重点内容:

  1. 写时复制( copy-on-write ,下文简称 CoW ),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术,通俗的讲,就是每次写都是对原文件的副本进行操作,原文件并不会被修改,而且之后的修改都是对这一个副本进行操作,即只在第一次修改时进行复制。
  2. Docker镜像的分层文件系统,该结构是利用AUFS机制,AUFS具有快速启动容器、高效利用存储和内存的优点。每个镜像具有树状结构,上层镜像利用底层镜像。
  3. Docker 使用 CoW 技术来实现 image layer和减少磁盘空间占用。Cow 意味着一旦某个文件只有很小的部分有改动,也需要复制整个文件。不过也不用过度担心,对于每个容器而言 ,每个 image layer 最多只需要复制1次。后续的改动都会在第一次拷贝的 container layer 上进行。
### 回答1: 《自己动手Docker》是一本由架构师嵩天和Docker社区联合编的开源技术书籍。书中介绍了Docker的基本概念、背景知识和原理,并且通过实践的方式帮助读者了解Docker的具体应用。以下是对这本书的回答: 《自己动手Docker》这本书是作者嵩天和Docker社区共同编的一本开源技术书籍。通过这本书,读者可以了解到Docker的基本概念、背景知识和工作原理。 首先,书中介绍了Docker的概念和由来。Docker是一个开源的容器化技术,通过容器化的方式,能够将应用程序和其依赖的资源打包在一起,以便于在任何地方都能够运行。Docker的诞生解决了传统虚拟化技术的运行效率问题,并且能够提供更好的资源利用率和应用程序的可移植性。 其次,书中详细介绍了Docker的工作原理和组成部分。Docker的核心是Docker引擎,它负责创建、运行和管理容器Docker镜像是Docker的核心概念之一,它是一个只读的模板,包含了运行应用程序所需的全部依赖项。通过Docker镜像,我们可以快速创建和部署应用程序。此外,书中还介绍了容器和镜像的关系、Docker的网络和存储管理方式等重要内容。 最后,书中通过实际操作引导读者理解和使用Docker。通过一些具体的例子和实践,读者可以学习如何构建自己的Docker镜像、创建和运行容器、使用Docker网络和存储等技术。通过这些实践,读者不仅能够清晰地了解Docker的工作机制,还能够掌握一些实际应用场景下的技巧和经验。 总而言之,《自己动手Docker》这本书通过理论和实践相结合的方式,以简洁明了的语言和实例详细解释了Docker的基本概念、工作原理和实际应用。是一本适合开发者、运维人员和对Docker感兴趣的读者了解和学习Docker的好书籍。 ### 回答2: 《自己动手docker》是一本以Docker为重点的技术书籍,通过学习本书可以了解Docker的原理和使用方法,掌握构建、部署和管理容器化应用的技巧。 这本书首先介绍了Docker的背景和发展,解释了为什么要使用Docker以及它的优势。接着,书中详细讲解了Docker的核心概念,包括镜像、容器、仓库等,帮助读者理解Docker的基本原理和组成部分。 随后,本书介绍了如何在本地环境搭建Docker,并通过实例演示了如何构建自定义镜像、运行容器以及管理容器的生命周期。这些实例涵盖了常见的应用场景,如部署Web应用、数据库容器化等。 此外,本书还介绍了Docker Swarm和Kubernetes等容器编排工具,让读者能够了解如何使用这些工具高效地管理和扩展容器化应用。 总体来说,《自己动手docker》是一本很实用的技术书籍,适合那些想要系统学习和掌握Docker技术的开发者和运维人员。无论是初学者还是有一定经验的人,都可以从这本书中获得价值。通过亲自动手实践各种实例,读者能够深入理解Docker的原理和使用方法,提升自己的技术水平。 ### 回答3: 《自己动手docker》是一本讲述如何编Docker的书籍。Docker是一种流行的容器化技术,可以将应用程序及其依赖项打包到一个可移植的容器中,从而实现应用程序在不同环境中的快速部署和扩展。 这本书主要讲解了Docker的核心原理和技术实现。首先,它介绍了Linux容器的基本概念和原理,包括命名空间、控制组、镜像和容器等。然后,它详细解释了Docker的架构和基本组件,例如Docker引擎、镜像仓库和网络管理等。 接下来,这本书逐步引导读者编自己的Docker。它从编一个简单的容器隔离工具开始,逐渐引入更多功能,例如容器的生命周期管理、网络隔离和卷管理等。这样,读者能够逐步了解容器化技术的实现细节。 此外,这本书还介绍了一些与Docker相关的技术和工具,例如容器编排工具Docker Compose和容器编排平台Kubernetes。这些技术可以帮助读者更好地管理和扩展自己的Docker应用程序。 总而言之,《自己动手docker》是一本编Docker容器的实践指南。通过学习这本书,读者可以深入了解Docker的原理和实现,并掌握使用Docker构建和管理容器的技能。无论是对于开发人员还是系统管理员来说,这本书都是一本不可多得的学习资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值