Docker核心技术

一、Docker概述

  1、Docker概述

    基于Linux内核的Cgroup,Namespace,以及Union FS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术,由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。

    最初实现是基于LXC,从0.7以后开始去除LXC,转而使用自行开发的Libcontainer,从1.1开始,则进一步演进为使用runC和Containerd。

    Docker在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护,使得Docker技术比虚拟机技术更为轻便、快捷。

  2、为什么要用 Docker

    更高效地利用系统资源

    更快速的启动时间

    一致的运行环境

    持续交付和部署

    更轻松地迁移

    更轻松地维护和扩展

  3、虚拟机 VS 容器化:

    虚拟机需要主机 Server、主机操作系统(Host OS)、Hypervisor(支持虚拟化)、虚拟机操作系统(Guest OS),然后再在 Guest OS 上运行系统

    容器不需要 Hypervisor,也不需要 Guest OS,其本身就是运行在 Host OS 上的一个进程

        

     因此虚拟化的优势:

    

二、Docker核心技术

(一)Namespace

  Linux Namespace是一种Linux Kernel提供的资源隔离方案:

    系统可以为进程分配不同的Namespace;

    并保证不同的Namespace资源独立分配、进程彼此隔离,即 不同的Namespace下的进程互不干扰。

  Linux 内核中,一个进程的数据结构是 task_struct,其中有 namespace 的数据结构 nsproxy,在nsproxy 中又存在着多种数据结构,例如 uts_ns、ipc_ns、mnt_ns等

        

  在 Linux 中,对Namespace操作方法有以下三种:

    clone:在 Linux 中,pid为1的进程为systemd,每一个进程都是由其他进程fork出来的,在 fork 进程时,会将父进程的 namespace 传递给子进程,因此我们一般感觉不到 namsespace的存在,但是在创建新进程的系统调用时,可以通过flags参数指定需要新建的Namespace类型∶// CLONE_NEWCGROUP/CLONE_NEWIPC/CLONE_NEWNET/CLONE_NEWNS/CLONE_NEWPID/CLONE_NEWUSER/CLONE_NEWUTS

        int clone(int (*fn)(void*), void *child_stack, int flags, void *arg)

    setns:该系统调用可以让调用进程加入某个已经存在的Namespace中∶

        Int setns(int fd, int nstype)

    unshare:该系统调用可以将调用进程移动到新的Namespace下∶

        int unshare(int flags)

  常见的 Namespace 类型及Linux开始支持的版本

        

    Pid namespace:不同用户的进程就是通过Pid namespace隔离开的,且不同namespace中可以有相同Pid。有了Pid namespace,每个namespace中的Pid能够相互隔离。对于PID Namespace有一个特殊点,由于所有的进程都是通过主机进程fork出来的,因此在主机上是可以看到所有的进程的pid的,在容器内部只能看到其自己创建的进程的pid,其实这两个是同一个进程,只不过从主机和从容器内部看到的pid是不一样的,这两个之间存在着关联关系

    net namespace:网络隔离是通过 net namespace 实现的,每个 net namespace 有独立的 network devices,IP addresses,IP routing tables, /proc/net目录。Docker默认采用veth的方式将container中的虚拟网卡同host上的一个dockerbridge∶docker0连接在一起。

    ipc namespace:Container 中进程交互还是采用 linux常见的进程间交互方法 (interprocess communication-IPC),包括常见的信号量、消息队列和共享内存。container的进程间交互实际上还是 host上 具有相同 Pid namespace 中的进程间交互,因此需要在IPC 资源申请时加入namespace信息(每个IPC资源有一个唯一的32位ID)。也就是说只有拥有相同的 ipc namespace才能使用ipc进行通讯。

    mnt namespace:mnt namespace允许不同namespace的进程看到的文件结构不同,这样每个namespace 中的进程所看到的文件目录就被隔离开了。

    uts namespace:UTS("UNIX Time-sharing System")namespace允许每个container拥有独立的hostname和 domain name,使其在网络上可以被视作一个独立的节点而非Host上的一个进程。

    user namespace:每个container 可以有不同的user和 group id,也就是说可以在 container 内部用 container 内部的用户执行程序而非Host上的用户。

  namespace 常用操作

# 查看 namespace
lsns

# 使用
-t 查看指定类型的 namespace(下面样例 net 表示网络 namespace) root@lcl-virtual-machine:/home/lcl# lsns -t net NS TYPE NPROCS PID USER NETNSID NSFS COMMAND 4026531840 net 283 1 root unassigned /sbin/init splash 4026532598 net 1 823 root unassigned /usr/libexec/accounts-daemon 4026532764 net 1 1151 rtkit unassigned /usr/libexec/rtkit-daemon
# 在
/proc/pid/ns 目录中查看一个pid的不同类型namespace root@lcl-virtual-machine:/proc/1151/ns# ls -l /proc/1151/ns/ 总用量 0 lrwxrwxrwx 1 root root 0 10月 8 17:10 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx 1 root root 0 10月 8 17:10 ipc -> 'ipc:[4026531839]' lrwxrwxrwx 1 root root 0 10月 8 17:10 mnt -> 'mnt:[4026531841]' lrwxrwxrwx 1 root root 0 10月 8 13:46 net -> 'net:[4026532764]' lrwxrwxrwx 1 root root 0 10月 8 17:10 pid -> 'pid:[4026531836]' lrwxrwxrwx 1 root root 0 10月 8 17:12 pid_for_children -> 'pid:[4026531836]' lrwxrwxrwx 1 root root 0 10月 8 17:10 time -> 'time:[4026531834]' lrwxrwxrwx 1 root root 0 10月 8 17:12 time_for_children -> 'time:[4026531834]' lrwxrwxrwx 1 root root 0 10月 8 17:10 user -> 'user:[4026531837]' lrwxrwxrwx 1 root root 0 10月 8 17:10 uts -> 'uts:[4026531838]'
# 查看一个
namespace 详情,使用 -t 指定 namespace,使用 -n 表示 net namespace root@lcl-virtual-machine:/proc/1151/ns# nsenter -t 1151 -n ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever

  前面提到使用 clone、setns、unshare可以对 namespace 进行操作,下面模拟使用unshare创建一个进程(f表示去到一个新的namespace,n表示创建一个net namespace)

unshare -fn sleep 60

  然后打开一个新的窗口,查看namespace

# 查看 net namespace
root@lcl-virtual-machine:/home/lcl# lsns -t net
        NS TYPE NPROCS   PID USER     NETNSID NSFS COMMAND
4026531840 net     290     1 root  unassigned      /sbin/init splash
4026532598 net       1   823 root  unassigned      /usr/libexec/accounts-daemon
4026532664 net       2  4104 root  unassigned      unshare -fn sleep 60
4026532764 net       1  1151 rtkit unassigned      /usr/libexec/rtkit-daemon

# 查看新创建namespace的ip地址
root@lcl-virtual-machine:/home/lcl# nsenter -t 4104 -n ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

(二)CGroup

  Cgroups (Control Groups)是Linux下用于对一个或一组进程进行资源控制和监控的机制;可以对诸如CPU使用时间、内存、磁盘I/O等进程所需的资源进行限制;不同资源的具体管理工作由相应的Cgroup子系统(Subsystem)来实现;

  针对不同类型的资源限制,只要将限制策略在不同的的子系统上进行关联即可;

  Cgroups在不同的系统资源管理子系统中以层级树(Hierarchy)的方式来组织管理:每个Cgroup 都可以包含其他的子Cgroup,因此子Cgroup能使用的资源除了受本Cgroup配置的资源参数限制,还受到父Cgroup设置的资源限制。

  Linux 进程数据结构 task_struct 中除了有 nsproxy 外,还有 CGroup 的数据结构 css_set,而 css_set中是 cgroup_subsys_state 对象的集合。

        

   cgroups实现了对资源的配额和度量:

    blkio:这个子系统设置限制每个块设备的输入输出控制。例如∶磁盘,光盘以及USB等等的读写速度

    cpu:这个子系统使用调度程序为 cgroup任务提供CPU的访问

    cpuacct:产生cgroup任务的CPU资源报告

    cpuset:如果是多核心的CPU,这个子系统会为 cgroup 任务分配单独的CPU和内存

    devices:允许或拒绝cgroup任务对设备的访问

    freezer:暂停和恢复cgroup任务

    memory:设置每个cgroup的内存限制以及产生内存资源报告

    net_cls:标记每个网络包以供cgroup方便使用

    nS:名称空间子系统

    pid:进程标识子系统 

        

   1、CPU子系统

      cpu.shares:可出让的能获得CPU使用时间的相对值

      Cpu.cfs_period_us:cfs_period_us用来配置时间周期长度,单位为us(微秒)。

      cpu.cfs_quota_us:cfs_quota_us用来配置当前Cgroup在cfs_period_us时间内最多能使用的CPU时间数,单位为us(微秒)

      cpu.stat:Cgroup 内的进程使用的CPU时间统计。

      nr_periods:经过cpu.cfs_period_us的时间周期数量

      nr_throttled:在经过的周期内,有多少次因为进程在指定的时间周期内用光了配额时间而受到限制。

      throttled_time:Cgroup 中的进程被限制使用CPU的总用时,单位是ns(纳秒)。

    cpu.shares是一个相对值,如果使用比例为50%,那么没有资源竞争情况下,是可以使用到100%的;而Cpu.cfs_period_us和cpu.cfs_quota_us是一个相对值,就算没有资源竞争,也不能超过设置的阈值。

  (1)Linux调度器

    Linux内核使用调度器来控制 cpu.shares,内核默认提供了5个调度器,使用struct sched_class来对调度器进行抽象:

      Stop调度器, stop_sched_class:优先级最高的调度类, 可以抢占其他所有进程, 不能被其他进程抢占;

      Deadline调度器,dL_sched_class:使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;

      RT调度器,rt_sched_class:实时调度器,为每个优先级维护一个队列;

      CFS调度器,cfs_sched_class:完全公平调度器,采用完全公平调度算法,引入虚拟运行时间概念;

      IDLE-Task调度器, idle sched class:空闲调度器,每个CPU都会有一个idle线程,当没有其他进程可以调度时,调度运行idle线程;

    其中优先级最高的是RT调度器,采用轮询算法,也就是说如果多个进程采用了RT调度器进行控制,其会轮询调用来保证其时效性;其次是CFS调度器,一般的用户进程都是使用的CFS调度器;

    CFS调度器是完全公平调度器,采用完全公平调度算法,引入虚拟运行时间概念vruntime;

  (2)CFS调度器

    CFS是Completely Fair Scheduler简称,即完全公平调度器

    CFS 实现的主要思想是维护为任务提供处理器时间方面的平衡,这意味着应给进程分配相当数量的处理器。

    分给某个任务的时间失去平衡时,应给失去平衡的任务分配时间,让其执行。

    CFS通过虚拟运行时间(vruntime)来实现平衡,维护提供给某个任务的时间量。

      vruntime=实际运行时间*1024/进程权重

    进程按照各自不同的速率在物理时钟节拍内前进,优先级高则权重大,其虚拟时钟比真实时钟跑得慢,但获得比较多的运行时间

  (3)Vruntime红黑树

    CFS调度器没有将进程维护在运行队列中,而是维护了一个以虚拟运行时间为顺序的红黑树。红黑树的主要特点有

    (1)自平衡,树上没有一条路径会比其他路径长出俩倍。

    (2)0(log n) 时间复杂度,能够在树上进行快速高效地插入或删除进程。

    Linux Kernel 将 Vruntime最小的放在红黑树的最左侧,每次调度时会从最左侧获取最小值进行调度。

  (4)CFS进程调度细节

    在时钟周期开始时,调度器调用_schedule()函数来开始调度的运行。

    _schedule()函数调用pick next task()让进程调度器从就绪队列中选择一个最合适的进程next,即红黑树最左边的节点(Vruntime最小的节点)。

    通过context switch()切换到新的地址空间,从而保证next进程运行。

    在时钟周期结束时,调度器调用entity_tick()函数来更新进程负载、进程状态以及vruntime(当前vruntime+该时钟周期内运行的时间)。

    最后,将该进程的虚拟时间与就绪队列红黑树中最左边的调度实体的虚拟时间做比较,如果小于坐左边的时间,则不用触发调度,继续调度当前调度实体。 

    因此如果权重越大(cpu.shares),则vruntime就越小,那么就会在红黑树的左侧,因此就会被优先调度,同时实际运行时间也会越长。

  (5)验证CPU子系统

    进入cgroup目录,就可以看到有所有的可控制资源,进入cpu目录,可以看到控制CPU的配置文件,在cpu目录中创建一个文件夹,相关的配置文件也会自动生成

[root@pc131 cpu]# ls /sys/fs/cgroup
blkio  cpu  cpuacct  cpu,cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  net_cls,net_prio  net_prio  perf_event  pids  systemd

[root@pc131 cpu]#
ls /sys/fs/cgroup/cpu cgroup.clone_children cgroup.procs cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat release_agent tasks cgroup.event_control cgroup.sane_behavior cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release system.slice user.slice
[root@pc131 cpu]#
mkdir mydemo [root@pc131 cpu]# cd mydemo/ [root@pc131 mydemo]# ls cgroup.clone_children cgroup.event_control cgroup.procs cpuacct.stat cpuacct.usage cpuacct.usage_percpu cpu.cfs_period_us cpu.cfs_quota_us cpu.rt_period_us cpu.rt_runtime_us cpu.shares cpu.stat notify_on_release tasks

    以下面代码为例,启动两个线程死循环,在不做控制的情况下,CPU会达到200%左右

package main
func main() {
    go func() {
        for {
        }
    }()
    for {
    }
}

    CPU占用情况

top - 09:51:19 up 12:28,  3 users,  load average: 1.95, 1.00, 0.43
Tasks: 129 total,   2 running, 127 sleeping,   0 stopped,   0 zombie
%Cpu(s): 50.0 us,  0.1 sy,  0.0 ni, 49.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  7990056 total,  6351832 free,   550992 used,  1087232 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  7168544 avail Mem

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  2512 root      20   0  710552   4556    292 R 199.7  0.1   6:19.56 main
     1 root      20   0  191028   4052   2580 S   0.0  0.1   0:01.74 systemd

    可以看到进程号 2512 已经占用到了 199.7%

    然后将该进程添加到cgroup的控制中

echo 2512 > cgroup.procs

    上面提到 cpu.shares(默认1024)、Cpu.cfs_period_us(默认100000)、cpu.cfs_quota_us(默认-1,不做控制),可以修改这些值对CPU做控制,例如将cpu.cfs_quota_us改为50000,说明只给半个CPU,那么CPU的占用率就会到50%

[root@pc131 mydemo]# echo 50000 > cpu.cfs_quota_us

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
  2512 root      20   0  710552   4556    292 R  49.8  0.1  64:30.37 main

  2、cpuacct子系统

    用于统计Cgroup及其子Cgroup下进程的CPU的使用情况。

    cpuacct.usage:包含该Cgroup及其子Cgroup下进程使用CPU的时间,单位是ns(纳秒)。

    cpuacct.stat:包含该Cgroup及其子Cgroup下进程使用的CPU时间,以及用户态和内核态的时间。

  3、内存子系统

    memory.usage_in_bytes:cgroup下进程使用的内存,包含cgroup及其子cgroup下的进程使用的内存

    memory.max_usage_in_bytes:cgroup下进程使用内存的最大值,包含子cgroup的内存使用量。

    memory.limit_in_bytes:设置Cgroup下进程最多能使用的内存。如果设置为-1,表示对该cgroup的内存使用不做限制。

    memory.oom_control:设置是否在Cgroup中使用OOM(Out of Memory)Killer,默认为使用。当属于该cgroup的进程使用的内存超过最大的限定值时,会立刻被OOMKiller处理。

    演示:

    (1)执行一段代码

# 创建应用程序目录
[root@hub myapp]# cd ../../memory/
[root@hub memory]# mkdir myapp
[root@hub memory]# cd myapp/
# 执行耗内存程序
[root@hub ~]# str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
[root@hub ~]# while true;do str="$str$str";sleep 5;done&

    (2)待一段时间后,内存资源会耗尽

[root@pc131 go]# -bash: xrealloc: 无法分配 18446744073441116288 字节 (352256 字节已分配)

[1]+  退出 2                while true; do
    str="$str$str"; sleep 5;
done

    (3)对资源控制

      重新启动上述代码,然后将进程号添加到cgroup中,并设置内存限制

[root@pc131 mydemo]# echo 2787 > cgroup.procs
[root@pc131 mydemo]# echo 102400000 > memory.limit_in_bytes

  4、CGroup Driver

    systemd:

      当操作系统使用systemd作为init system时,初始化进程生成一个根cgroup目录结构并作为cgroup管理器。

      systemd与cgroup紧密结合,并且为每个systemd unit分配cgroup。

    cgroupfs:

      docker默认用cgroupfs作为cgroup驱动。

    存在问题:

      因此,在systemd作为init system的系统中,默认并存着两套groupdriver。

      这会使得系统中docker和kubelet管理的 进程被cgroupfs驱动管,而systemd拉起的服务由systemd驱动管,让cgroup管理混乱且容易在资源紧张时引发问题。

      因此kubelet会默认--cgroup-driver=systemd,若运行时cgroup不一致时,kubelet会报错

(三)文件系统

  Union FS 是指将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem)的文件系统,支持为每一个成员目录(类似Git Branch)设定 readonly、readwrite 和 whiteout-able 权限;文件系统分层,对 readonly权限的 branch 可以逻辑上进行修改(增量地,不影响 readonly 部分的)。

  通常Union FS有两个用途,一方面可以将多个disk挂到同一个目录下,另一个更常用的就是将一个readonly的branch和一个writeable的branch联合在一起。

        

   典型的Linux文件系统由Bootfs和Rootfs组成。Bootfs (boot file system) 负责引导加载kernel,当kernel被加载到内存中后会卸载 bootfs;rootfs (root file system)是/dev、/proc、/bin、/etc等标准目录和文件。对于不同的linux发行版,bootfs 基本是一致的,但rootfs会有差别。

  Linux在启动后,首先将rootfs 设置为 readonly,进行一系列检查,然后将其切换为“readwrite”供用户使用。

  Docker的启动,初始化时也是将rootfs 以 readonly方式加载并检查,然而接下来利用union mount 的方式将一个readwrite 文件系统挂载在readonly的rootfs之上;并且允许再次将下层的FS(file system)设定为readonly并且向上叠加。这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态,每一个FS被称作一个FS层。

  写操作:

    由于镜像具有共享特性,所以对容器可写层的操作需要依赖存储驱动提供的写时复制和用时分配机制,以此来支持对容器可写层的修改,进而提高对存储和内存资源的利用率。

    写时复制:写时复制,即Copy-On-Write。一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多个拷贝。在需要对镜像提供的文件进行修改时,该文件会从镜像的文件系统被复制到容器的可写层的文件系统进行修改,而镜像里面的文件不会改变。不同容器对文件的修改都相互独立、互不影响。

    用时分配:按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。

  常见的存储驱动包括AUFS、OverlayFS、Device Mapper等,由于OverlyFS的性能好,因此目前大多数使用的都是OverlyFS。

                

  OverlayFS也是一种与AUFS类似的联合文件系统,同样属于文件级的存储驱动,包含了最初的Overlay和更新更稳定的 overlay2。Overlay只有两层∶upper层和lower层,Lower层代表镜像层,upper层代表容器可写层。

  演示 overly 作为存储驱动

# 创建目录
[root@pc131 testoverlyfs]# mkdir upper lower merged work
[root@pc131 testoverlyfs]# ls
lower  merged  upper  work

# 分别的 upper 和 lower 目录中创建相同和不同的文件
[root@pc131 testoverlyfs]# echo "from lower" > lower/in_lower.txt
[root@pc131 testoverlyfs]# echo "from upper" > upper/in_upper.txt
[root@pc131 testoverlyfs]# echo "from lower" > lower/in_both.txt
[root@pc131 testoverlyfs]# echo "from upper" > upper/in_both.txt

# 查看目录结构
[root@pc131 testoverlyfs]# tree .
.
├── lower
│   ├── in_both.txt
│   └── in_lower.txt
├── merged
├── upper
│   ├── in_both.txt
│   └── in_upper.txt
└── work

4 directories, 4 files

# 使用overly创建联合文件系统:
[root@pc131 testoverlyfs]# mount -t overlay overlay -o lowerdir=`pwd`/lower,upperdir=`pwd`/upper,workdir=`pwd`/work `pwd`/merged

# 验证文件
[root@pc131 testoverlyfs]# cat merged/in_both.txt
from upper
[root@pc131 testoverlyfs]# cat merged/in_lower.txt
from lower
[root@pc131 testoverlyfs]# cat merged/in_upper.txt

  OCI容器标准:Open Container Initiative

    OCI组织于2015年创建,是一个致力于定义容器镜像标准和运行时标准的开放式组织。OCI定义了镜像标准(Image Specification)、运行时标准(Runtime Specification)和分发标准(Distribution Specification)

     镜像标准定义应用如何打包;运行时标准定义如何解压应用包并运行;分发标准定义如何分发容器镜像

  Docker 引擎架构:

    最早的 Docker是直接由 daemon 创建进程的,也就是当我们执行docker命令时,会发送一个请求给daemon,daemon会拉起新的进程,但是这样就有一个致命问题,不能轻易升级docker,一旦升级,docker进程就会停止,而又daemon fork的业务进程也都会停止。

    后面引入了container d 和shim,在daemon收到请求后,通过GRPC调用container d,而container d去调用shim进行拉起进程,而shim作为业务进程的生命周期管理,同时将shim交由systemd来负责。

        

三、容器网络

  Docker默认支持多种网络模式,如 none、Host、Container、Bridge

    none:把容器放入独立的网络空间但不做任何网络配置;用户需要通过运行docker network命令来完成网络配置。可以理解为会创建namespace,但是不做任何网络配置,希望用户自己进行配置,例如上层有K8S等容器编排工具,其本身是希望可以自己进行设置网络的。

    Host:使用主机网络名空间,复用主机网络。在K8S中,可以使用这种方式,通过容器进入,然后修改主机的网络配置。

    Container:重用其他容器的网络。

    Bridge(--net=bridge):docker默认的网络类型,当我们不做设置时,默认使用bridge网络。bridge网络使用Linux 网桥和iptables 提供容器互联,Docker在每台主机上创建一个名叫 docker0的网桥,通过veth pair来连接该主机的每一个EndPoint。

  上面都是单主机下的网络互联,对于多主机的容器网络互联包括Overly和Underly

    Overlay(libnetwork,libky):通过网络封包实现。

    Underlay:使用现有底层网络,为每一个容器配置可路由的网络IP。使用这种方式,容器会消耗很多ip,一个网络的ip段是有限的,如果使用underly的话,就需要提前规划好 ip。

  如果主机上安装了docker,那么docker会在主机上安装一个bridge的设备,这个桥接设备是用来连接主机上所有的容器,我们可以通过命令查看

  1、none 

    创建一个 none 网络模式的进程

docker run --network=none -d nginx

    查看容器的 net namespace,以及关联进程和 net namespace

# 查看容器详情
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# docker inspect 451b515843b5 | grep -i pid
            "Pid": 6411,
            "PidMode": "",
            "PidsLimit": null,

# 根据pid进入namespace 查看网络
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# nsenter -t 6411 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

# 将新创建的 net namespace 和 进程绑定
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ln -s /proc/6411/ns/net /var/run/netns/6411

# 可以查看 net namespace 列表
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip netns list
6411

    创建一个虚拟链路,放在不同的net namespace,并设置两端的ip信息

# 创建一个虚拟链路,两端分别是 A 和 B
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip link add A type veth peer name B

# 将 A 插入 docker0
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# brctl addif docker0 A

# 给容器配置 ip、子网掩码、网关
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# SETIP=172.17.0.10
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# SETMASK=16
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# GATEWAY=172.17.0.1

# 启动 A
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip link set A up

# 将网线的 B 口放在6411的ns中
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip link set B netns 6411

# 进度6411的ns,并把 B 口的名称改为eth0
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip netns exec 6411 ip link set dev B name eth0

# 启动eth0
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip netns exec 6411 ip link set eth0 up

# 对eth0配置 ip、子网掩码、网关
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip netns exec 6411 ip addr add $SETIP/$SETMASK dev eth0
root@lcl-virtual-machine:/sys/fs/cgroup/cpu# ip netns exec 6411 ip route add default via $GATEWAY

    验证:可以看到已经在nginx中添加了一个网络,使用curl访问 172.17.0.10也是可以访问的

root@lcl-virtual-machine:/sys/fs/cgroup/cpu# nsenter -t 6411 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ae:3a:82:d5:b7:ac brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.10/16 scope global eth0
       valid_lft forever preferred_lft forever

  2、网桥和net

  上面演示了当network设置为null时如何操作让容器和主机互联互通,docker默认的网络模式是网桥和net,那么docker也是这么做的,只是将这些操作做了封装。

        

   为主机eth0分配ip192.168.0.101;启动 docker daemon,查看主机 iptables:POSTROUTING-A POSTROUTING-s 172.17.0.0/16!-o docker0-j MASQUERADE

  在主机启动容器:

    docker run-d --name nginx-p 8080:80 nginx

  Docker会以标准模式配置网络:

    创建veth pair;

    将veth pair的一端连接到docker0网桥;

    veth pair的另外一端设置为容器名空间的eth0;

    为容器名空间的eth0分配ip;

    主机上的Iptables 规则∶PREROUTING-A DOCKER!-i docker0-p tcp -m tcp --dport 23-j DNAT---to-destination 172.17.0.2:22。

  3、Underlay

  实际上就是将网桥的ip与主机ip同等级,那么容器ip也是与主机ip同等级。

    采用Linux 网桥设备(sbrctl),通过物理网络连通容器;

    创建新的网桥设备mydr0;

    将主机网卡加入网桥;

    把主机网卡的地址配置到网桥,并把默认路由规则转移到网桥mydr0;

    启动容器;

    创建veth对,并且把一个peer添加到网桥mydro;

    配置容器把veth的另一个peer分配给容器网卡;

        

   4、Docker Libnetwork Overlay

    实际上就是对于网络的多层封装技术。

    Docker overlay 网络驱动原生支持多主机网络,Libnetwork是一个内置的基于VXLAN的网络驱动。VXLAN是其一种实现

        

   5、网络查件(Flannel、calico)

    同一主机内的Pod可以使用网桥进行通信。不同主机上的Pod将通过,flanneld将其流量封装在UDP数据包中。     

        

 

posted @ 2022-10-09 18:46  李聪龙  阅读(743)  评论(0编辑  收藏  举报