Docker 理论以及隔离

1. Docker

​ 容器是一个大的品类,就像操作系统一样,操作系统包括了 Windows 、linux、UNI、mac………… 起哄一个只是此操作系统的种类,Docker 也是容器中的一种,Docker 也算是容器中的 一个里程碑。

​ 主流的容器: Docker、Podman、CoreOS 、lxc、buildah7 skopo

​ Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包的一个可移植的镜像中,然后发布到任何流行的linux或其他机器中,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。

Docker的概念?

早在集装箱没有出现的时候,码头上还有许多搬运的工人在搬运货物,在集装箱出现以后,码头上看到更多的不是工人,而且集装箱的搬运模式更加单一,更加高效,还有其他的好处,比如:货物多打包在集装箱里面,可以防止货物之前相互影响。并且到了另外一个码头需要转运的话,有了在集装箱以后,直接把它运送到另一个容器内即可,完全可以保证里面的货物是整体的搬迁,并且不会损坏货物本身。那么docker 镜像在IT行业中也扮演着非常重要的形象

images + docker = container/

​ 镜像实例化之后也是一个 容器

​ podman 也是容器虚拟化的一种软件,podman 子命令与docker 相差不多

[root@localhost ~]# yum -y install podman
[root@localhost ~]# podman pull centos

2. Docker的虚拟化方式

1) 传统的虚拟化方式

传统的虚拟化方式,是模拟出一套硬件后,在其运行的一个完整的操作系统,在该操作系统上再次运行所需要的程序的应用进程。

在这里插入图片描述

  • 这种方式也称为 完全解耦方式

  • 完全解耦
    当我们在同一个操作系统中运行相同的软件程序时,它们会发生冲突。解除耦合则是为一个软件分配一个环境,使其独立运行。
    例如手机里的应用分身,还有经常使用的VMware虚拟机也是使用这种方式
    当我们想要同时运行多个web服务器时,需要创建两个操作系统,
    从指定硬件层面(CPU、MEM、DISK)→安装内核→安装系统→安装lib库→安装device(驱动程序)→安装应用软件

  • 这种方式是瓜分宿主机的硬件资源,在一定程度上浪费了硬件资源

2) Docker 虚拟化方式

​ Docker 虚拟化依靠于容器,而容器内的应用进程直接运行在宿主机的内核上,容器内并没有自己的内核,而且也没有进行硬件虚拟话,容器直接与宿主使用相同的硬件资源,因此容器比虚拟机更为轻便。

在这里插入图片描述

  • 这种方式也称为半解耦方式
  • 半解耦
    使得在同一个环境下可以同时运行两个相同的软件,分担软件服务器的压力。
    直接使用物理机上的硬件资源(CPU、MEM、DISK)和kernel(内核)
    例如:docker会直接使用系统镜像构建出一个容器,以文件夹的方式存放在操作系统上。
    而容器内部的运行环境比如lib库将通过ln -s(软链接)的形式使用。
    这样使得应用程序误以为一个文件夹就是一个操作系统。

3. Docker 特点

1) 高效的用系统资源

	-	由于容器不需要进行硬件虚拟化以及运行完整操作系统等额外的开销,Docker对系统资源的利用率跟高,无论是应用执行速度、内存损耗或者文件存储速度,都要比传统的虚拟机技术更高效,因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多的应用。

2) 快速的启动时间

-	相比较传统的 虚拟机技术,docker容器启动的速度十分快捷,直接运行于系统内核,不需要启动完整的操作系统,基本可以做到 秒单位开机。

3) 一致的运行环境

  • 在开发的过程中会遇到一个常见的问题,就是环境不一致导致的问题,由于开发环境、测试环境、生产环境不一致,导致了一些bug,并么有在开发过程中被开发人员发现。
  • 但是Docker 的镜像提供了除了内核完整的 运行环境,确保了应用运行环境一致性,从而不会再次出现 “这段代码在我设备上不会有问题” 这种问题

4) 持续交付和部署

  • 通过Docker 可以将当前的容器保存为镜像,来实现持续集成、持续交付、部署等。
  • 开发人员可以使用Dockerfile 来进行镜像的构建,并结合 持续集成系统进行集成测试、而运维的人员则可以直接在生产环境中快速的部署该镜像,甚至可以结合持续部署系统进行自动部署
  • 而且使用 Dockerfile 可以使镜像构建透明化。

5) 具有方便的迁移特性

  • 由于Docker 保证了环境的一致性,可以使镜像迁移变得更加方便,Docker 可以在很多平台运行,无论是物理机、虚拟机、公有云、私有云、甚至是笔记本都可以,运行结果是一致的。
  • 因此用户可以很轻松的将一个平台运行的应用迁移到另一个平台,且不需要担心运行环境的变化导致应用无法正常运行的问题

6) 方便维护和扩展

  • Docker 使用的分层存储以及镜像的技术,使得重复的部分复用更加的容易,也让维护的更加的简单,基于基础镜像进一步扩展镜像也变得非常简单

7) 相比较传统的虚拟机的总结对比

特点 Docker容器 虚拟机
启动 分钟
硬盘的使用 一般为MB 一般为GB
性能 接近原生 相比较弱一点
系统支持量 单机支持上千个容器 一般几十个差不多

4. Docker 的基本概念

  • Docker 的基本概念大致为三个:
    • 镜像
    • 容器
    • 仓库

1) Docker 镜像(image)

​ Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需要的程序、库、资源、配置文件等等,还包含了一些为运行时准备的一些配置参数(匿名卷、环境变量、用户) ,镜像内不包括任何静态程序,其中的内容在构建之后也不会发生变化。

​ 也可理解为一个面向Docker引擎的只读模板,包含了文件系统。如:一个镜像可以只包含一个完整的操作系统环境,也可以安装了其他的应用程序。通过版本管理和增量的文件系统,Docker提供了一套十分简单的机制来创建和更新现有的镜像。

2) Docker 容器(Container)

  • image+docker = container

​ 先交代一下 镜像和容器的关系,就像面向对象程序设计中的类和实例一样,镜像的静态的定义,容器是镜像运行时的实体,容器可以被创建、启动、停止、删除、暂停等等。

​ 一个容器为一个实例,通过拉取镜像,形成一个容器,也就是所谓的实例化

​ 容器的实质也是进程,但是与直接在宿主机执行的进程不同,容器的进程运行于属于自己的独立的命名空间,因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间、甚至有自己的用户ID空间。

​ 容器内的进程是运行在一个隔离的环境当中,使用起来,就像在一个独立于宿主的系统下操作一样。

3) Docker Registry

​ 镜像来自于仓库,一个 Docker Registry 中可以包含多个仓库,每个仓库可以包含多个标签(tag);每个标签对应一个镜像,tag也可以理解为版本

​ 在镜像构建完成后,可以在当前的宿主机上运西瓜,但是,如果需要在其他的服务器上使用这个镜像,我们就需要一个集中的存储、分布镜像的服务。

  • 仓库分为 公有仓库、私有仓库

  • 公有仓库

    • 是开放给用户使用,运西瓜用户管理镜像的 服务,一般这种服务运行用户免费上传、下载公开的镜像。
  • 私有仓库

    • 用户可以自行搭建本地的私有仓库。

5. 命名空间

​ 命名空间是 Linux 内核一个强大的特性,每个容器都有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统中一样运行,命名空间可以保证容器之间彼此互不影响。

​ /proc 目录下的数字为当前程序的进程,可根据进程查看占用的信息

  • 查看所有的命名空间
/proc/20/ns/

Namespace 隔离内容 系统调用参数
ipc 信号量,消息队列和共享内存 CLONE_NEWIPC
mnt 挂载点和文件系统 CLONE_NEWNS
net 网络协议栈,网络设备 CLONE_NEWNET
pid 进程编号 CLONE_NEWPID
user 用户和用户组 CLONE_NEWUSER
uts 主机名和域名 CLONE_NEWUTS
  • Namespace 命名空间

Docker 的运行和 namespace 有着密切的联系,Docker 中每个容器都有自己独立的运行位置

  • 空间
    • 对于我们来讲,一个空间是可以与外界隔离的,就像所谓的平行宇宙,就像是好几个雨中之间,每个宇宙中发生的 事情,不会影响到其他宇宙

1) ipc 命名空间

​ 容器中进程交互还是采用了 Linux 常见的进程之间的交互方式 (interprocess communication - IPC) ,包括信号量、消息队列和共享内存等等,然而与 VM 不同的是,容器的进程之间交互实际上还是Host 上具有相同的PID命名空间中的进程之间的 交互,因此需要在IPC 资源中申请时加入命名空间信息,每个IPC 资源都有一个 唯一的 32为ID

2) mnt命名空间

​ 类似 chroot ,将一个进程放到一个特定的目录执行,mnt命令空间允许不同的命名空间的进程看到的文件结构不同,这样每个命名空间中的进程所看到文件目录就被隔离开了,同chroot 不同,每个命名空间中的容器在 /proc/mounts 的信息只包含所在的命名空间的mount point。

3) net 命令空间

​ 有了pid 命名空间,每个命名空间中的pid能够相互隔离,但是网络端口还是共享host的端口,网络隔离是通过net 命名空间实现的,每个 net命名空间有独立的网络设备、ip地址、路由表, /proc/net 目录,这样每个容器的网络就能隔离开来,Docker 默认采用veth的方式,将容器中的虚拟网卡同 Host 上的 一个Docker 网桥 docker0 连接在一起 。

4) pid命名空间

​ 不同用户的进程通过 pid 命名空间隔离开的,且不同的命名空间中可以有相同的 pid,所有的LXC 进程在 Docker 中的父进程为Docker 进程,每个LXC 进程具有不同的命名空间。同时由于允许嵌套,因此可以很方便的实现嵌套的Docker 容器。

5) user 命名空间

​ 每个容器可以有不同的用户和组ID,也就是说可以在容器内部的用户执行程序而非主机的上的用户。

6) uts 命名空间

​ UTS 命名空间允许每个容器拥有独立的Hostname 和 domain name, 使其在网路上可以被视作为一个独立的节点而非主机上的一个进程。

6. UTS 隔离

  • 通过 C语言脚本,模拟隔离过程

  • 编辑 C 语言脚本, 后缀 为 *.c

    • 将 “ CLONE_NEWUTS” 表识加入到 clone
    • 从 child 调用 “sethostname”
[root@localhost ~]# vim example.c

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};

int child_main(void* args) {
    printf("在子进程中| \n");
    sethostname("Changed Name", 12);
    execv(child_args[0],child_args);
    return 1;
}

int main() {
    printf("程序开始: \n");
    int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(child_pid, NULL, 0);
    printf("以退出\n");
    return 0;


[root@localhost ~]# gcc -Wall example.c -o test.o
[root@localhost ~]# ps
   PID TTY          TIME CMD
  1865 pts/1    00:00:00 bash
 13384 pts/1    00:00:00 ps
[root@localhost ~]# ./test.o 
程序开始: 
在子进程中| 
[root@Changed Name ~]# ps
   PID TTY          TIME CMD
  1865 pts/1    00:00:00 bash
 13385 pts/1    00:00:00 test.o
 13386 pts/1    00:00:00 bash
 13397 pts/1    00:00:00 ps
[root@Changed Name ~]# exit
exit
以退出

7. IPC 隔离

  • ipc 隔离,一旦激活,你就将能往常一样创建 IPC,甚至命名一个,而不会有与任何其他应用冲突的 风险
[root@localhost ~]# vim example.c

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};

int child_main(void* args) {
    printf("在子进程中| \n");
    sethostname("Changed Name", 12);
    execv(child_args[0],child_args);
    return 1;
}

int main() {
    printf("程序开始: \n");
    int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(child_pid, NULL, 0);
    printf("以退出\n");
    return 0;


gcc -Wall example.c -o ipc.o

8. PID 隔离

  • 一旦激活子进程 getpid() 的返回结果将会是不变的 “1”
  • 如果不进行隔离,容器相当于是 /bin/bash 中的 /bin/bash ,不是一个独立的 bash
[root@localhost ~]# vim example.c

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};

int child_main(void* args) {
    printf("在子进程中| \n");
    sethostname("Changed Name", 12);
    execv(child_args[0],child_args);
    return 1;
}

int main() {
    printf("程序开始: \n");
    int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE——NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(child_pid, NULL, 0);
    printf("以退出\n");
    return 0;

[root@localhost ~]# gcc -Wall example.c -o pid.o
[root@localhost ~]# ./pid.o 
程序开始: 
在子进程中| 
[root@Changed Name ~]# echo $$
1

9. mnt隔离

  • CLONE_NEWNS : mount 隔离,一旦任何子进程的挂载与卸载挂载操作都将只作用于本身,反之亦然
[root@localhost ~]# vim example.c

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];
char* const child_args[] = {
    "/bin/bash",
    NULL
};

int child_main(void* args) {
    printf("在子进程中| \n");
    sethostname("Changed Name", 12);
    execv(child_args[0],child_args);
    return 1;
}

int main() {
    printf("程序开始: \n");
    int child_pid = clone(child_main, child_stack + STACK_SIZE, CLONE——NEWNS | CLONE——NEWPID | CLONE_NEWIPC | CLONE_NEWUTS | SIGCHLD, NULL);
    waitpid(child_pid, NULL, 0);
    printf("以退出\n");
    return 0;
[root@localhost ~]# gcc -Wall example.c -o mount.o

[root@localhost ~]# ./mount.o 
程序开始: 
在子进程中| 
[root@Changed Name ~]# mount --make-private -t proc proc /proc/

mount 支持的挂载操作

mount --make-以下选项

  • shared 表示允许创建镜像,一个镜像内的挂载和卸载操作都会被自动传播到其他的镜像中

  • slave 表示自动继承主挂载点和卸载操作,但是自身的挂载和卸载操作不会反向传播到主挂载点上

  • private 表示既不继承主挂载点中挂载和卸载的操作,自身的挂载和写早操作也不会反向传播到主挂载点。

  • unbindable 表示禁止对该挂载点进行人格绑定 ( --bind | --rbind)

  • 查看当前进程号

echo $$
  • 查看当前的消息队列
ipcs -q
  • Docker 中容器不能启动 yum 安装的程序,需要使用 --privileged进行提权
[root@localhost ~]# docker run -itd --name http --privileged centos /sbin/init 
6e824942c5e21ae5a56c3ee92ddbe3ab7c19bde6a038ecbb444763cd88918b66
[root@localhost ~]# docker exec -it http /bin/bash
[root@6e824942c5e2 /]# yum -y install httpd

[root@6e824942c5e2 /]# systemctl start httpd
[root@6e824942c5e2 /]# exit
exit
[root@localhost ~]# docker rm -f http


10. net 网络隔离

  • 当 宿主机已经运行了httpd 的进程,在进入命名空间运行一个 httpd 进程,会进行报错,这时候就需要进行网路隔离了
  • NET 隔离包括 IP地址、防火墙、路由表、端口、socket
[root@localhost ~]# ip netns add test_ns			// 创建命名空间
[root@localhost ~]# ip netns exec test_ns ip link list		//  列出link 列表
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

[root@localhost ~]# ip netns exec test_ns ping 127.0.0.1	// ping 一下本地
connect: 网络不可达
[root@localhost ~]# ip netns exec test_ns ip link set dev lo up		// 开启对应设备
[root@localhost ~]# ip netns exec test_ns 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

[root@localhost ~]# ip netns exec test_ns ping 127.0.0.1		
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.043 ms

[root@localhost ~]# ip link add veth0 type veth peer name veth1		// 创建虚拟网卡
[root@localhost ~]# ip a

[root@localhost ~]# ip link set veth1 netns test_ns		// 为指定命名空间分发一个设备
[root@localhost ~]# ip a

[root@localhost ~]# ip netns exec test_ns ip a		// 在命名空间执行
[root@localhost ~]# ip netns exec test_ns ifconfig veth1 10.1.1.1/24 up
	// 在命名空间修改IP地址
[root@localhost ~]# ip netns exec test_ns ip a

[root@localhost ~]# ifconfig veth0 10.1.1.2/24 up
[root@localhost ~]# ip a

[root@localhost ~]# ping 10.1.1.1
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.049 ms


[root@localhost ~]# ip netns exec test_ns ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.052 ms

xec test_ns ip a // 在命名空间执行


```shell
[root@localhost ~]# ip netns exec test_ns ifconfig veth1 10.1.1.1/24 up
	// 在命名空间修改IP地址
[root@localhost ~]# ip netns exec test_ns ip a

[root@localhost ~]# ifconfig veth0 10.1.1.2/24 up
[root@localhost ~]# ip a

[root@localhost ~]# ping 10.1.1.1
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.049 ms


[root@localhost ~]# ip netns exec test_ns ping 10.1.1.2
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.052 ms

发布了68 篇原创文章 · 获赞 11 · 访问量 3337
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览