1.Docker容器是什么

按照Docker官网,容器是运行在宿主机上的一个进程,但与宿主机上的其他进程相隔离;

2.容器实现原理

这种隔离机制使用了内核中的namespace和cgroups功能;

2.1.Linux namespace

Linux通过将系统的资源放置在不同的namespace下,实现资源的隔离;

类型 解释
Network 隔离网络资源
Mount 隔离文件系统的挂载点
UTS 隔离主机名和域名信息
IPC 隔离进程间通信
PID 隔离进程ID
User 隔离用户和用户组ID

clone系统调用:创建子进程

# flags: 控制新创建的进程隔离的资源
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
flag 隔离资源 描述
CLONE_NEWCGROUP 子进程的cgroup资源和当前进程隔离 隔离Cgroup根目录下不同层级目录的权限
CLONE_NEWIPC 子进程的ipc资源和当前进程隔离 隔离当前在不同进程间传递和交换信息的范围
CLONE_NEWNET …network… 隔离子进程的网络栈,路由表,防火墙规则等
CLONE_NEWNS …mount… 隔离文件系统的挂载点
CLONE_NEWPID …pid… 隔离进程的ID空间
CLONE_NEWUSER …user… 隔离用户uid,gid在宿主机中的权限
CLONE_NEWUTS …UTS… 隔离子进程的主机名,hostname和NIS域名

(NIS域名:Network information service,用共享网络信息的集中存储)

# 查看当前进程树
pstree -p

# 查看当前所有进程
# 在linux中一切皆文件,如下图我们可以看出进程本身其实也只是一个文件
ls /proc

# 查看pid=1的进程的namespace
ls -al /proc/1/ns

mount: 文件挂载

// source: 挂载源地址
// target: 挂载目标地址
// filesystemtype: 系统类型
// mountflags: 挂载源文件访问标志
// data: 文件系统特有的参数
int mount( const char* source, const char* target, const char* filesystemtype, unsigned long mountflags, const void * data);

// container.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.h>
#include <stdio.h>
#include <sched.h>
#include <linux/sched.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {"/bin/bash",NULL
};int container_main(void* arg){printf("container_main\n");sethostname("container0",10);mount("proc", "/proc", "proc", 0, NULL);mount("none", "/tmp", "tmpfs", 0, "");printf("Info: %s\n",strerror(errno));chroot("./");perror("chroot");chdir("/");perror("chdir");execv(container_args[0], container_args);return 1;
}int main(){int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS|CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWNET| SIGCHLD , NULL);waitpid(container_pid, NULL, 0);return 0;
}
# 进入自定义的容器中
gcc container.c -o container && ./conatiner# 检查不同命名空间下资源的隔离

宿主机上

容器中

通过比较上述宿主机以及容器间的这些资源,我们可以发现容器间的这些资源相互隔离了;

CLONE_NEWCGROUP

# CLONE_NEWNS
# 默认情况下clone方法会拷贝宿主机的所有挂载点
# 因此我们可以看到容器比宿主机多出一个新增的挂载点
mount | grep proc
# CLONE_NEWCGROUP
ls /sys/fs/cgroup
cat /sys/fs/cgroup/cpu/tasks
# CLONE_NEWIPC
# 宿主机和容器的ipcs队列均为空因此无法比较
# ipcs用于查看当前系统进程间通信的状况
ipcs
# CLONE_NEWUTS
# 主机名称
hostname
# CLONE_NEWPID
# 查看当前进程pid
ps
# CLONE_NEWUSER
# 查看当前用户
whoami
# CLONE_NEWNET
# 查看当前网络
ip a

2.2.Linux cgroups

对进程设置资源(cpu, memory, 磁盘,带宽)限制

cgroup可对进程使用的以下资源进行限制

资源 解释
blkio 磁盘吞吐量
cpu cpu使用量
cpuacct cpu使用率的统计报告
cpuset 分配独立CPU和内存节点
devices 控制设备访问
freezer 挂起或恢复进程
memory 内存使用量
net_cls 标记网络数据包从而限制带宽
net_prio 网络数据包优先级
perf_event 使用perf工具监控进程
pids 任务数量
systemd 控制管理系统资源

Cgroup net_cls&net_prio

使用cgroup给某个进程可以生成的任务数量进行限制(pids)

mkdir /sys/fs/cgroup/pids/container
cd /sys/fs/cgroup/pids/container# 最多只能运行一个进程
echo 1 > pids.max# 对当前进程进行限制
echo $$ > cgroup.procs
# 再次运行ls发现无法执行子进程
ls


Cgroup Usage
Cgroup Memory & Pids Usage

2.3.文件挂载隔离实践

当我们进入docker容器时,发现其文件与宿主机中的相互独立…
这使用的是mount namespace,使得容器文件和宿主机文件相隔离

chroot: 将指定的目录设为新进程的根目录;

  • 限制用户权限
  • 用户环境与宿主机环境隔离

(1) 执行以下脚本在 ~/test 目录下拷入基本命令

# test.sh
basedir=~/test
commands=(/bin/ls /bin/bash /bin/cat /bin/chmod)
mkdir -p $basedir
cd $basedir
for(( i=0;i<${#commands[@]};i++ ));dolist=$(ldd ${commands[i]} | egrep -o '/(.*?) ')mkdir -p $basedir`dirname ${commands[i]}`;cp ${commands[i]} $basedir${commands[i]};for dependency in $list;do# echo ${commands[i]} - $dependency;mkdir -p $basedir`dirname $dependency`;cp -v $dependency $basedir$dependencydone;
done;

(2)


mkdir -p ~/test && cd ~/test
# 下载hello world项目,里面已经打好了一个helloworld程序
# 目前支持 linux-arm, linux-arm64, linux-arm64
git clone https://gitee.com/Liyuan-1/helloworld.git# 运行上述test.sh脚本
chmod +x test.sh && ./test.sh

到这一步,我们就将应用在容器中运行所需的文件准备好了;

如下图我们可以看到新创建的进程将~/test作为了根目录,且与宿主机文件隔离;

# 以 ~/test 作为新进程(容器)的根目录
chroot ~/test
/bin/ls -al

2.4.容器运行实践

容器只是一个进程,不过容器中文件与宿主机文件相隔离

# 将上述准备好的~/test作为新进程的根目录
chroot ~/test
/bin/ls -al /helloworld/HelloWorld-Golang/# 为你的宿主机所能执行的文件添加可执行权限
/bin/chmod +x /helloworld/HelloWorld-Golang/main-linux-arm# 运行
/helloworld/HelloWorld-Golang/main-linux-arm


由于此处我们并未做网络相关的隔离,因此该进程(容器)共享宿主机网络资源;

3.容器资源隔离运行实践

接下来我们使用clone函数创建一个新的进程(容器),对其进行资源隔离,并运行上述HelloWorld程序;

3.1. 准备可执行文件

(1) 首先我们拷贝一些基础命令以及启动HelloWorld应用所需要的文件到~/test目录下

# init_commands.sh
basedir=~/test
commands=(/usr/sbin/ip /bin/ps /bin/hostname /bin/whoami /bin/ls /bin/bash /bin/cat /bin/chmod)
mkdir -p $basedir
cd $basedir
for(( i=0;i<${#commands[@]};i++ ));dolist=$(ldd ${commands[i]} | egrep -o '/(.*?) ')mkdir -p $basedir`dirname ${commands[i]}`;cp ${commands[i]} $basedir${commands[i]};for dependency in $list;do# echo ${commands[i]} - $dependency;mkdir -p $basedir`dirname $dependency`;cp -v $dependency $basedir$dependencydone;
done;
mkdir -p ~/test && cd ~/test# 执行上述shell脚本
# 拷贝基础命令到~/test目录下
chmod +x ./init_commands.sh && ./init_commands.sh# 拷贝HelloWorld程序
git clone https://gitee.com/Liyuan-1/helloworld.git

3.2. 运行容器

(2) 使用clone创建一个和宿主机资源相隔离的进程,并将上述生成的dir作为容器运行的根目录
// container.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/capability.h>
#include <stdio.h>
#include <sched.h>
#include <linux/sched.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {"/bin/bash",NULL
};int container_main(void* arg){printf("container_main\n");sethostname("container0",10);mount("proc", "/proc", "proc", 0, NULL);mount("none", "/tmp", "tmpfs", 0, "");chroot("./");perror("chroot");chdir("/");perror("chdir");printf("mount:  %s\n",strerror(errno));execv(container_args[0], container_args);return 1;
}int main(){int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS|CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWNET| SIGCHLD , NULL);waitpid(container_pid, NULL, 0);return 0;
}
gcc container.c && ./a.out/bin/chmod +x main-linux-arm && ./main-linux-arm# 检查该进程(容器)资源隔离是否生效
/bin/hostname
/usr/sbin/ip a


4.docker容器运行分析

当我们运行一个nginx容器时,总共分为以下几个步骤;

4.1. docker pull nginx

在该步骤,我们会将nginx镜像拉取下来,其实这个镜像就是一个文件夹,里面包含运行nginx所需要的文件,然后使用chroot将该文件设置为新进程的根目录,通过docker inspect 我们也可以看出;

docker pull nginx
docker run -itd nginx
# 查看该nginx容器详细信息,如下图所示
docker inspect ${containerID} | grep MergedDir
# 该目录即为当前运行的nginx容器的根目录
ls -al ${MergedDir}

(1) UnionFS (Union File System)

UnionFS: 将多个文件夹挂载到同一个目录上

假设我有2个目录,下面分别有一些文件;

# 将 ./test00  ./test01 ./test02 挂载至 ./liyuan 目录下
# 上层的目录的文件会覆盖下层的目录
mkdir -p ./liyuan &&  mount -t overlay overlay -o lowerdir=./test00,upperdir=./test01,workdir=./test02  ./liyuan


docker使用了UnionFS,可以将多个不同位置的目录联合挂载到同一个目录下,如下图,nginx运行的容器由多个lowerDir,UpperDir,WorkDir一起被合并挂载到了 MergeDir上;

4.2. docker run -it nginx

在该步骤,我们相当于首先将lowerDir, upperDir, workDir通过UnionFS方式挂载至MergedDir中,然后运行 chroot 将该MergedDir作为新进程根目录, 然后主动运行应用;

3. 总结

容器只是运行在宿主机上的进程,其本质只是将文件等资源与宿主机相隔离,但共享整个操作系统内核,因此你对操作系统内核配置的修改会影响到运行在该宿主机上的所有容器;

tip: 如果你有任何疑问,欢迎留言,也欢迎关注我的公众号 “从零开始的Go学习”

运维开发实践 - Docker - 容器实现原理相关推荐

  1. 运维开发实践 - helm

    1. helm介绍 helm 是一个用于管理部署在kubernetes上的应用的工具 使用要求:一个Kubernetes集群 2.下载安装 Helm Github Download Helm Huaw ...

  2. 运维之道 | Docker 容器连接

    Docker 容器连接 前面我们实现了通过网络端口来访问运行在 docker 容器内的服务. 容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过-P或-p参数来指定端口映射. 下面我们来 ...

  3. 运维(18) 解决Docker容器内无法访问外网问题

    文章目录 一.问题 二.解决 法一:重建网络`docker0` 法二:开启宿主机的ipv4转发功能 法三:重装docker 一.问题 宿主机能正常访问外网,但通过docker创建的容器里无法访问外网 ...

  4. Docker容器的原理与实践(上)

    本文来自网易云社区. 虚拟化 是一种资源管理技术,将计算机的各种资源予以抽象.转换后呈现出来, 打破实体结构间的不可切割的障碍,使用户可以比原本更好的方式来应用这些资源. Hypervisor 一种运 ...

  5. Docker容器虚拟化技术---Docker运维管理(Docker Compose)4

    Docker容器虚拟化技术-Docker运维管理(Docker Compose)4 Docker Compose 通过前面的讲解我们知道使用一个Dockerfile模板文件,可以很方便地定义一个单独的 ...

  6. 云时代运维转型必读:容器运维模式的五大场景

    来自:DBAplus社群 作者介绍 温峥峰,小鹏汽车互联网中心运维高级经理,专注于运维自动化.DevOps实践.运维服务体系建设与容器运维时代下的价值挖掘.知乎专栏:HiPhone运维之道 其实我挺早 ...

  7. 企业运维容器之 docker仓库

    企业运维容器之 docker 仓库 1. 什么是仓库? 2. Docker hub 3. Registry 工作原理 4. 配置镜像加速器 5. 搭建私有仓库 5. 总结 1. 什么是仓库? Dock ...

  8. 智能化运维最佳实践-自动化

    伴随着互联网以及大数据时代的到来,IT信息系统已经成为最重要的数据载体和信息来源, IT系统在企业内部的重要性日益突出:但是随着企业信息化程度的提高.IT环境规模的扩大和IT环境复杂度的增加.行业内服 ...

  9. 对于运维以及运维开发工程师的一些了解

    Linux运行是什么 什么是Linux运维 运维是指大型组织已经建立好的网络软硬件的维护,就是要保证业务的上线与运作的正常 在他运转的过程中,对他进行维护,它集合了网络.系统.数据库.开发.安全.监控 ...

最新文章

  1. 普通程序员和优秀程序员的距离究竟有多大?
  2. LeetCode: 104. Maximum Depth of Binary Tree
  3. python中的选择结构语句是语句_python3控制语句---选择结构语句
  4. 如何成为一个合格的 Java程序员
  5. OpenCV2:应用篇 QT+OpenCV实现图片编辑器
  6. 21、 TRUNCATE:清空表记录
  7. AbsListView.LayoutParams
  8. 图论--二分图最佳完美匹配(KM模板)
  9. java.sql.SQLException: ORA-01438: 值大于此列指定的允许精确度
  10. python 计算协方差矩阵_opencv2学习:计算协方差矩阵
  11. java 类 属性数量_跟我学java编程—Java类的属性与成员变量
  12. .net oracle 参数化,.NET参数化Oracle查询参数
  13. Php 安装 zend_loader,在Linux系统中为PHP5.x安装Zend Guard Loader
  14. 单片机课程设计——《基于AT89S52单片机和DS1302时钟芯片的电子时钟(可蓝牙校准)》... 1
  15. 精选 | 2018年3月R新包推荐
  16. SpringApplication run方法第四步解析(三)[(未完结,暂搁置)]
  17. UI设计师如何通过兼职月入过万?
  18. 对鸿蒙操作系统和AI Camera Hi3516dDV300 开发过程的记录和理解
  19. Vue之如何动态渲染.vue文件
  20. 使用WinRAR制作属于自己的单文件版软件

热门文章

  1. vue获取input的属性_Vue中自动获取input焦点
  2. Java和c++的区别!
  3. Java向下转型以及如何判断能否向下转型
  4. 设计模式之组合模式详解(附应用举例实现)
  5. 国际WTI原油价格日、周、月、年度数据(1986.01-2019.02)
  6. 基于Node的Promise使用介绍
  7. CCC之基本绝缘、加强绝缘、双重绝缘举例
  8. PR 如何设置素材持续时间
  9. turtle---->基础使用
  10. 关于重装系统与Visual Studio 2015