Docker 简介
- Docker 简介
- 什么是虚拟化、容器化
- 案例
- 为什么要虚拟化、容器化?
- - 资源利用率高
- - 环境标准化
- - 资源弹性伸缩
- - 差异化环境提供
- - 沙箱安全
- - 容器对比虚拟机更轻量,启动更快
- - 维护和扩展容易
- 虚拟化实现方式
- 应用程序执行环境分层
- - 硬件层:提供硬件抽象,包括指令集架构、硬件设备及硬件访问接口
- - 操作系统层 :提供系统调用接口,管理硬件资源
- - 程序库层:提供数据结构定义及函数调用接口
- 虚拟化常见类别
- 虚拟机
- 容器
- JVM 之类的虚拟机
- 常见虚拟化实现
- 主机虚拟化(虚拟机)实现
- 容器虚拟化实现
- 容器虚拟化实现原理
- 容器虚拟化基础之 NameSpace
- 什么是 Namespace(命名空间)
- 容器虚拟化基础之 cgroups
- 容器虚拟化基础之 LXC
- NameSpace 隔离实战
- 实战目的
- 了解隔离能力并不是 docker 提供的,而是操作系统内核提供基本能力。
- 基础知识
- dd 命令详解
- mkfs 命令详解
- df 命令详解
- mount 命令详解
- unshare 命令详解
- 实战操作一(PID 隔离)
- 1. 在主机上执行 ps -ef,可以看到进程列表如下,其中启动进程 PID 1 为 init 进程
- 2. 我们打开另外一个 shell ,执行下面命令创建一个 bash 进程,并且新建一个 PID
- 3. 执行 ps -ef 查看进程信息,我们可以看到此时进程空间内的内容已经变了,而且启动进程也变成了我们的 bash 进程。说明我们已经看不到主机上的进程空间了,我们的进程空间发生了隔离。
- 4.执行 exit 退出进程
- 实战操作二(Mount 隔离)
- 步骤和代码示例
- 注意事项:
- CGroups 资源控制实战
- 实战目的
- 能够查看 cgroups 基本信息,了解容器化中,操作系统是真正的资源控制层,对 cpu和内存的控制有一定的理解。
- 基础知识
- 基础知识
- 常用命令
- LXC 容器操作实战
- 实战目的
Docker 简介
什么是虚拟化、容器化
- 物理机:实际的服务器或者计算机。相对于虚拟机而言的对实体计算机的称呼。物理机提供给虚拟机以硬件环境,有时也称为“寄主”或“宿主”。
- 虚拟化:是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机。在一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在相互独立的空间内运行而互不影响,从而显著提高计算机的工作效率
- 容器化:容器化是一种虚拟化技术,又称操作系统层虚拟化(Operating system level virtualization),这种技术将操作系统内核虚拟化,可以允许用户空间软件实例(instances)被分割成几个独立的单元,在内核中运行,而不是只有一个单一实例运行。这个软件实例,也被称为是一个容器(containers)。对每个实例的拥有者与用户来说,他们使用的服务器程序,看起来就像是自己专用的。容器技术是虚拟化的一种。docker 是现今容器技术的事实标准
案例
举个生活中的例子。
- 物理机如下,就像一个庄园,独立占用了一块土地,花园都是自己的,其他人无法共享使用。
- 虚拟机相当于开发商的一个楼盘,一栋楼一套房子一户人家,共享一块宅基地,共享小区的花园,共享小区的游乐设施。
- 容器相当于在 1 个房子里面,开辟出来一个又一个的胶囊公寓,共享这套房子的卫生间、共享厨房、共享 WiFi,只有衣服、电脑等私人物品是你自己的
为什么要虚拟化、容器化?
我们从上面的历史发展来看,虚拟化和容器化的最主要目的就是资源隔离,随着资源隔离的实现逐渐也带来了更大的收益。
- 资源利用率高
将利用率较低的服务器资源进行整合,用更少硬件资源运行更多业务,降低 IT 支出和运维管理成本。
比如上图中我们的土地直接复用,使用这块土地的人多了,但是成本还是庄园那块地。
- 环境标准化
一次构建,随处执行。实现执行环境的标准化发布,部署和运维。开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 「这段代码在我机器上没问题啊」 这类问题。
- 资源弹性伸缩
根据业务情况,动态调整计算、存储、网络等硬件及软件资源。比如遇到双 11 了,把服务扩容 100 个,双 11 过去了, 把扩容的 100 个收回去
- 差异化环境提供
同时提供多套差异化的执行环境,限制环境使用资源。
比如我的服务一个以来 Ubuntu 操作系统,一个服务依赖 CentOS 操作系统,但是没有预算购买两个物理机,这个时候容器化就能很好的提供多种不同的环境。
- 沙箱安全
为避免不安全或不稳定软件对系统安全性、稳定性造成影响,可使用虚拟化技术构建虚拟执行环境。
比如我在容器里面执行 rm -rf /* 不会把整个服务器搞死,也不影响其他人部署的程序使用。
- 容器对比虚拟机更轻量,启动更快
传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。docker 不需要虚拟内核,所以启动可以更快,相当于 windows 的开机时间省去了
- 维护和扩展容易
Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,
Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。比如 docker hub 提供了很多镜像,各个系统的一个命令就可以拿到了,研发也可以自己定制镜像分享给各个产品。
虚拟化实现方式
应用程序执行环境分层
- 硬件层:提供硬件抽象,包括指令集架构、硬件设备及硬件访问接口
- 操作系统层 :提供系统调用接口,管理硬件资源
- 程序库层:提供数据结构定义及函数调用接口
虚拟化常见类别
虚拟机
存在于硬件层和操作系统层间的虚拟化技术。虚拟机通过“伪造”一个硬件抽象接口,将一个操作系统以及操作系统层以上的层嫁接到硬件上,实现和真实物理机几乎一样的功能。比如我们在一台 Windows 系统的电脑上使用 Android 虚拟机,就能够用这台电脑打开 Android 系统上的应用。
容器
存在于操作系统层和函数库层之间的虚拟化技术。容器通过“伪造”操作系统的接口,将函数库层以上的功能置于操作系统上。以 Docker 为例,其就是一个基于 Linux 操作系统的Namespace 和 Cgroup 功能实现的隔离容器,可以模拟操作系统的功能。简单来说,==如果虚拟机是把整个操作系统封装隔离,从而实现跨平台应用的话,那么容器则是把一个个应用单独封装隔离,从而实现跨平台应用。==所以容器体积比虚拟机小多,理论上占用资源更少。容器化就是应用程序级别的虚拟化技术。容器提供了将应用程序的代码、运行时、系统工具、系统库和配置打包到一个实例中的标准方法。容器共享一个内核(操作系统),它安装在硬件上。
JVM 之类的虚拟机
存在于函数库层和应用程序之间的虚拟化技术。Java 虚拟机同样具有跨平台特性,所谓跨平台特性实际上也就是虚拟化的功劳。我们知道 Java 语言是调用操作系统函数库的,JVM 就是在应用层与函数库层之间建立一个抽象层,对下通过不同的版本适应不同的操作系统函数库,对上提供统一的运行环境交给程序和开发者,使开发者能够调用不同操作系统的函数库。
常见虚拟化实现
主机虚拟化(虚拟机)实现
主机虚拟化的原理是通过在物理服务器上安装一个虚拟化层来实现。这个虚拟化层可以在物理服务器和客户操作系统之间建立虚拟机,使得它们可以独立运行。
从软件框架的角度上,根据虚拟化层是直接位于硬件之上还是在一个宿主操作系统之上,将虚拟化划分为 Type1 和 Type2.Type1 类的 Hypervisor(Hypervisor 是一种系统软件,它充当计算机硬件和虚拟机之间的中介,负责有效地分配和利用由各个虚拟机使用的硬件资源,这些虚拟机在物理主机上单独工作,因此,Hypervisor 也称为虚拟机管理器。)直接运行在硬件之上,没有宿主机操作系统,Hypervisor 直接控制硬件资源和客户机。典型框架为 Xen、Vmware ESX
容器虚拟化实现
容器虚拟化实现原理
容器虚拟化,有别于主机虚拟化,是操作系统层的虚拟化。通过 namespace 进行各程序的隔离,加上 cgroups 进行资源的控制,以此来进行虚拟化。
容器虚拟化基础之 NameSpace
什么是 Namespace(命名空间)
namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。
Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前namespace 里的进程,对其他 namespace 中的进程没有影响。Linux 提供了多个 API 用来操作 namespace,它们是 clone()、setns() 和unshare() 函数,为了确定隔离的到底是哪项 namespace,在使用这些 API 时,通常需要指定一些调用参数:CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、
CLONE_NEWPID、CLONE_NEWUSER、CLONE_NEWUTS 和CLONE_NEWCGROUP。如果要同时隔离多个namespace,可以使用 | (按位或)组合这些参数。
举个例子
三年一班的小明和三年二班的小明,虽说他们名字是一样的,但是所在班级不一样,那么,在全年级排行榜上面,即使出现两个名字一样的小明,也会通过各自的学号来区分。对于学校来说,每个班级就相当于是一个命名空间,这个空间的名称是班级号。
班级号用于描述逻辑上的学生分组信息,至于什么学生分配到 1 班,什么学生分配到2 班,那就由学校层面来统一调度。
以上命名空间在容器环境下的隔离效果:
- UTS:每个容器能看到自己的 hostname,拥有独立的主机名和域名。
- IPC:同一个 IPC namespace 的进程之间能互相通讯,不同的 IPC namespace 之间不能通信。
- PID:每个 PID namespace 中的进程可以有其独立的 PID,每个容器可以有其 PID 为1 的 root 进程。
- Network:每个容器用有其独立的网络设备,IP 地址,IP 路由表,/proc/net 目录,端口号。
- Mount:每个容器能看到不同的文件系统层次结构。
- User:每个 container 可以有不同的 user 和 group id。
想想以下如果我们要隔离两个进程需要怎么办?
(1)首先容器进程与进程之间需要隔离,所以需要 PID 隔离
(2)首先容器 A 进程不能读取容器 B 进程通讯内容需要隔离信号量等,所以需要 IPC
隔离
(3)首先容器 A 进程不能读取容器 B 进程的文件,所以需要 Mount 隔离
(4)首先容器 A 进程不能读取容器 B 进程的 socket,所以需要网络隔离、主机隔离
(5)Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,这就容易让容器突破资源限制。需要借助用户空间来完成用户之间的隔离。
容器虚拟化基础之 cgroups
- 什么是 cgroups
cgroups(Control Groups) 是 linux 内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的 - 为什么使用 cgroups
其可以做到对 cpu,内存等资源实现精细化的控制,目前越来越火的轻量级容器
Docker 及 k8s 中的 pod 就使用了 cgroups 提供的资源限制能力来完成 cpu,内存等部分的资源控制。
比如在一个既部署了前端 web 服务,也部署了后端计算模块的八核服务器上,可以使用 cgroups 限制 web server 仅可以使用其中的六个核,把剩下的两个核留给后端计算模块。 - cgroups 的用途
Resource limitation: 限制资源使用,例:内存使用上限/cpu 的使用限制Prioritization: 优先级控制,例:CPU 利用/磁盘 IO 吞吐Accounting: 一些审计或一些统计Control: 挂起进程/恢复执行进程 - cgroups 可以控制的子系统
容器虚拟化基础之 LXC
LXC(Linux Containers)是一种操作系统级别的虚拟化技术,允许在单个 Linux 主机上运行多个独立的 Linux 容器。它通过利用 Linux 内核的命名空间和控制组(cgroups)功能来实现虚拟化。
LXC 提供了一个轻量级的虚拟化解决方案,与传统的虚拟机相比,它更为轻便和高效。每个 LXC 容器都是一个独立的环境,拥有自己的文件系统、网络、进程空间等,但与宿主机共享同一个内核。这使得 LXC 能够更高效地利用硬件资源,同时提供良好的隔离性和安全性。
要开始使用 LXC,通常需要在宿主机上安装 LXC 软件包,并配置网络、存储等参数。然后可以使用类似 lxc-create
、lxc-start
、lxc-stop
等命令来管理和操作容器。
NameSpace 隔离实战
实战目的
了解隔离能力并不是 docker 提供的,而是操作系统内核提供基本能力。
基础知识
dd 命令详解
Linux dd 命令用于读取、转换并输出数据。
dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。
语法
Shell
dd OPTION
参数
○ if=文件名:输入文件名,默认为标准输入。即指定源文件。
○ of=文件名:输出文件名,默认为标准输出。即指定目的文件。
○ ibs=bytes:一次读入 bytes 个字节,即指定一个块大小为 bytes 个字节。
obs=bytes:一次输出 bytes 个字节,即指定一个块大小为 bytes 个字节。
bs=bytes:同时设置读入/输出的块大小为 bytes 个字节。
○ cbs=bytes:一次转换 bytes 个字节,即指定转换缓冲区大小。
○ skip=blocks:从输入文件开头跳过 blocks 个块后再开始复制。
○ seek=blocks:从输出文件开头跳过 blocks 个块后再开始复制。
○ count=blocks:仅拷贝 blocks 个块,块大小等于 ibs 指定的字节数。
○ conv=<关键字>,关键字可以有以下 11 种:
▪ conversion:用指定的参数转换文件。
▪ ascii:转换 ebcdic 为 ascii
▪ ebcdic:转换 ascii 为 ebcdic
▪ ibm:转换 ascii 为 alternate ebcdic
▪ block:把每一行转换为长度为 cbs,不足部分用空格填充
▪ unblock:使每一行的长度都为 cbs,不足部分用空格填充
▪ lcase:把大写字符转换为小写字符
▪ ucase:把小写字符转换为大写字符
▪ swap:交换输入的每对字节
▪ noerror:出错时不停止
▪ notrunc:不截短输出文件
▪ sync:将每个输入块填充到 ibs 个字节,不足部分用空(NUL)字符补齐。
○ --help:显示帮助信息
○ --version:显示版本信息
Shell
生成 1 个镜像文件
dd if=/dev/zero of=fdimage.img bs=8k count=10240
#将 testfile 文件中的所有英文字母转换为大写,然后转成为 testfile_1 文件
dd if=testfile_2 of=testfile_1 conv=ucase
mkfs 命令详解
用于在设备上创建 Linux 文件系统,俗称格式化,比如我们使用 U 盘的时候可以格式化。
df 命令详解
mount 命令详解
unshare 命令详解
实战操作一(PID 隔离)
1. 在主机上执行 ps -ef,可以看到进程列表如下,其中启动进程 PID 1 为 init 进程
2. 我们打开另外一个 shell ,执行下面命令创建一个 bash 进程,并且新建一个 PID
Namespace:
–fork 新建了一个 bash 进程,是因为如果不建新进程,新的 namespace 会用 unshare的 PID 作为新的空间的父进程,而这个 unshare 进程并不在新的 namespace 中,所以会报个错 Cannot allocate memory
–pid 表示我们的进程隔离的是 pid,而其他命名空间没有隔离
mount-proc 是因为 Linux 下的每个进程都有一个对应的/proc/PID 目录,该目录包含了大量的有关当前进程的信息。 对一个 PID namespace 而言,/proc 目录只包含当前namespace 和它所有子孙后代 namespace 里的进程的信息。创建一个新的 PID namespace 后,如果想让子进程中的 top、ps 等依赖 /proc 文件系统的命令工作,还需要挂载 /proc 文件系统。而文件系统隔离是 mount namespace 管理的,所以 linux特意提供了一个选项–mount-proc 来解决这个问题。如果不带这个我们看到的进程还是系统的进程信息。
Shell
unshare --fork --pid --mount-proc /bin/bash
3. 执行 ps -ef 查看进程信息,我们可以看到此时进程空间内的内容已经变了,而且启动进程也变成了我们的 bash 进程。说明我们已经看不到主机上的进程空间了,我们的进程空间发生了隔离。
4.执行 exit 退出进程
Shell
exit
实战操作二(Mount 隔离)
在实际操作中,"Mount 隔离"通常指的是将文件系统挂载到另一个位置,以便对其进行操作或修改,而不影响原始文件系统。这在处理故障排除、修复损坏的文件系统或进行数据恢复时特别有用。以下是一个简单的示例,演示如何在 Linux 环境下进行 Mount 隔离。
步骤和代码示例
-
创建一个目录用于挂载点:
sudo mkdir /mnt/mydrive
这里
/mnt/mydrive
是你将要挂载文件系统的目标目录。 -
查找要挂载的设备或分区:
使用
lsblk
或fdisk -l
命令查看系统中的存储设备和分区,找到你想要挂载的目标设备或分区。假设目标设备为/dev/sdb1
。 -
挂载目标设备到挂载点:
sudo mount /dev/sdb1 /mnt/mydrive
如果是需要读写权限的情况,可以加上
-o rw
参数:sudo mount -o rw /dev/sdb1 /mnt/mydrive
如果是 NTFS 文件系统,可能需要额外的参数,比如:
sudo mount -t ntfs-3g /dev/sdb1 /mnt/mydrive
这里
-t ntfs-3g
指定了文件系统类型为 NTFS。 -
操作完成后,卸载挂载点:
当完成对文件系统的操作后,可以卸载挂载点:
sudo umount /mnt/mydrive
如果挂载点仍然被使用,可以添加
-l
参数进行强制卸载:sudo umount -l /mnt/mydrive
注意事项:
- 确保在操作文件系统前备份重要数据,特别是在进行写操作时。
- 使用
mount
和umount
命令需要超级用户(root)权限,可以通过sudo
命令来提升权限。 - 挂载点的选择应当是一个空目录,用于临时存储挂载的文件系统,避免影响到系统中已有的文件结构。
以上是一个简单的 Mount 隔离操作示例,可以根据具体需求和文件系统类型进行调整和扩展。
CGroups 资源控制实战
实战目的
能够查看 cgroups 基本信息,了解容器化中,操作系统是真正的资源控制层,对 cpu和内存的控制有一定的理解。
基础知识
CGroups(Control Groups)是Linux系统中的一种机制,用于限制、控制和监视进程组的资源使用(如CPU、内存、磁盘IO等)。它允许系统管理员在层次化的层级结构中组织进程,并为每个组分配特定的资源限制和优先级。
基础知识
-
CGroups的层级结构:
- CGroups以层级树状结构组织,每个层级称为一个控制组。
- 每个CGroup都可以包含子CGroup,形成树状结构。
-
主要特性:
- 资源限制:可以限制CPU、内存、磁盘IO等资源的使用。
- 优先级调整:可以为不同的CGroup设置不同的优先级。
- 进程控制:可以控制和监视属于CGroup的进程的行为。
-
CGroups的子系统:
- cpu:控制CPU的使用情况。
- memory:控制内存的使用情况。
- blkio:控制块设备(磁盘)的输入输出。
- cpuset:控制进程可以使用的CPU核心和节点。
- net_cls, net_prio:控制网络流量的分类和优先级。
- 等等,具体取决于Linux内核版本和配置。
常用命令
-
查看CGroups层级结构:
systemd-cgls # 使用systemd管理的系统上查看 cgclassify # 手动指定CGroup
-
创建和管理CGroups:
cgcreate -g cpu,memory:mygroup # 创建名为mygroup的CGroup,包含cpu和memory子系统 cgexec -g cpu,memory:mygroup /path/to/executable # 运行进程在指定的CGroup中 cgdelete -g cpu,memory:mygroup # 删除CGroup
-
设置资源限制:
cgset -r cpu.shares=512 mygroup # 设置CPU份额为512 cgset -r memory.limit_in_bytes=1G mygroup # 设置内存限制为1GB
这些命令和知识可以帮助你开始理解和使用CGroups来管理和限制Linux系统中的进程资源使用。
LXC 容器操作实战
实战目的
通过 lxc 来完成容器的创建,体会容器,并了解 docker 并不是容器的唯一实现。自docker 0.9 版本起,docker 除了继续支持 LXC 外,还开始引入自家的 libcontainer,试图打造更通用的底层容器虚拟化库。如今的 docker 基本上都已经是使用libcontainer 而非 LXC 了
LXC(Linux Containers)是一个操作系统级虚拟化技术,以下是一些常用的 LXC 命令:
-
创建容器:
lxc launch <image> <container-name>
-
启动容器:
lxc start <container-name>
-
停止容器:
lxc stop <container-name>
-
查看容器状态:
lxc list
-
进入容器:
lxc exec <container-name> -- <command>
-
删除容器:
lxc delete <container-name>
-
导出容器:
lxc export <container-name> <file-path>
-
导入容器:
lxc import <file-path> --alias <container-name>
这些命令可以帮助你管理和操作 LXC 容器。
LXC 容器操作实战
- 检查 lxc 是否运行
要检查 LXC 是否在运行,可以使用以下命令:
sudo systemctl status lxc.service
这条命令会显示 LXC 服务的运行状态及相关信息。如果服务正在运行,你会看到类似以下的输出:
● lxc.service - LXC Container Initialization and Autoboot Code
Loaded: loaded (/lib/systemd/system/lxc.service; enabled; vendor preset: enabled)
Active: active (exited) since Tue 2024-06-18 10:55:12 UTC; 2 days ago
如果服务未运行,你会看到相应的状态信息,例如:
● lxc.service - LXC Container Initialization and Autoboot Code
Loaded: loaded (/lib/systemd/system/lxc.service; enabled; vendor preset: enabled)
Active: inactive (dead)
这时可以尝试启动 LXC 服务,使用以下命令:
sudo systemctl start lxc.service
然后再次检查状态确认服务是否成功启动。