Docker exec 的实现原理介绍

我使用了 docker exec 命令进入到了容器当中。在了解了Linux Namespace 的隔离机制后,你应该会很自然地想到一个问题:docker exec 是怎么做到进入容器里的呢?
实际上,Linux Namespace 创建的隔离空间虽然看不见摸不着,但一个进程的 Namespace 信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在。

比如,通过如下指令,你可以看到当前正在运行的 Docker 容器的进程号(PID)是 25686:

$ docker inspect --format '{{ .State.Pid }}' 4ddf4638572d
25686

这时,你可以通过查看宿主机的 proc 文件,看到这个 25686 进程的所有 Namespace 对应的文件:

$ ls -l /proc/25686/ns
total 0
lrwxrwxrwx 1 root root 0 Aug 13 14:05 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 ipc -> ipc:[4026532278]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 mnt -> mnt:[4026532276]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 net -> net:[4026532281]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 pid -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 pid_for_children -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 uts -> uts:[4026532277]

可以看到,一个进程的每种Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,并且链接到一个真实的 Namespace 文件上。
有了这样一个可以“hold 住”所有 Linux Namespace 的文件,我们就可以对 Namespace 做一些很有意义事情了,比如:加入到一个已经存在的 Namespace 当中。

这也就意味着:一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。

而这个操作所依赖的,乃是一个名叫 setns() 的 Linux 系统调用。它的调用方法,我可以用如下一段小程序为你说明:

#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE);} while (0)

int main(int argc, char *argv[]) {
int fd;

fd = open(argv[1], O_RDONLY);
if (setns(fd, 0) == -1) {
errExit("setns");
}
execvp(argv[2], &argv[2]);
errExit("execvp");
}

这段代码功能非常简单:它一共接收两个参数,第一个参数是 argv[1],即当前进程要加入的 Namespace 文件的路径,比如/proc/25686/ns/net;而第二个参数,则是你要在这个 Namespace 里运行的进程,比如 /bin/bash。

这段代码的的核心操作,则是通过 open() 系统调用打开了指定的 Namespace 文件,并把这个文件的描述符 fd 交给 setns() 使用。在 setns() 执行后,当前进程就加入了这个文件对应的 Linux Namespace 当中了。

现在,你可以编译执行一下这个程序,加入到容器进程(PID=25686)的 Network Namespace 中:

$ gcc -o set_ns set_ns.c
$ ./set_ns /proc/25686/ns/net /bin/bash
$ ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:02
inet addr:172.17.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:12 errors:0 dropped:0 overruns:0 frame:0
TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:976 (976.0 B) TX bytes:796 (796.0 B)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

正如上所示,当我们执行 ifconfig 命令查看网络设备时,我会发现能看到的网卡“变少”了:只有两个。而我的宿主机则至少有四个网卡。这是怎么回事呢?
实际上,在 setns() 之后我看到的这两个网卡,正是我在前面启动的 Docker 容器里的网卡。也就是说,我新创建的这个 /bin/bash 进程,由于加入了该容器进程(PID=25686)的 Network Namepace,它看到的网络设备与这个容器里是一样的,即:/bin/bash 进程的网络设备视图,也被修改了。
而一旦一个进程加入到了另一个 Namespace 当中,在宿主机的 Namespace 文件上,也会有所体现。

在宿主机上,你可以用 ps 指令找到这个 set_ns 程序执行的 /bin/bash 进程,其真实的 PID 是 28499:

# 在宿主机上
ps aux | grep /bin/bash
root 28499 0.0 0.0 19944 3612 pts/0 S 14:15 0:00 /bin/bash

这时,如果按照前面介绍过的方法,查看一下这个 PID=28499 的进程的 Namespace,你就会发现这样一个事实:

$ ls -l /proc/28499/ns/net
lrwxrwxrwx 1 root root 0 Aug 13 14:18 /proc/28499/ns/net -> net:[4026532281]

$ ls -l /proc/25686/ns/net
lrwxrwxrwx 1 root root 0 Aug 13 14:05 /proc/25686/ns/net -> net:[4026532281]

/proc/[PID]/ns/net 目录下,这个 PID=28499 进程,与我们前面的 Docker 容器进程(PID=25686)指向的 Network Namespace 文件完全一样。这说明这两个进程,共享了这个名叫net:[4026532281] 的 Network Namespace。
此外,Docker 还专门提供了一个参数,可以让你启动一个容器并“加入”到另一个容器的 Network Namespace 里,这个参数就是 -net,比如:

$ docker run -it --net container:4ddf4638572d busybox ifconfig

这样,我们新启动的这个容器,就会直接加入到 ID=4ddf4638572d 的容器,也就是我们前面的创建的应用容器(PID=25686)的Network Namespace 中。所以,这里 ifconfig 返回的网卡信息,跟我前面那个小程序返回的结果一模一样,你也可以尝试一下。
而如果我指定–net=host,就意味着这个容器不会为进程启用 Network Namespace。这就意味着,这个容器拆除了 Network Namespace 的“隔离墙”,所以,它会和宿主机上的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提供了一个渠道。

到此这篇关于Docker exec 的实现原理介绍的文章就介绍到这了,更多相关Docker exec 的实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Docker容器下运行Nginx并实现反向代理

    目录 一.前言 二.运行Nginx容器 1.获取Nginx镜像 2.运行Nginx容器 三.运行ASP.NET Core应用程序 1.构建ASP.NET Core应用程序镜像 2.运行应用容器 三.设置反向代理 一.前言 我们知道,为了安全考虑,我们一般会设置反向代理,用来屏蔽应用程序真实的IP和端口号.在Linux系统上最常用的反向代理就是Nginx.本篇文章中,我们会通过Docker容器分别运行一个Nginx容器和一个ASP.NET Core应用程序的容器,然后设置反向代理. 二.运行Ngi

  • Docker镜像与容器的导入导出操作实践

    目录 一.前言 二.docker镜像的导入和导出 1.docker镜像的导出 2.docker镜像的导入 三.docker容器的导入和导出 1.docker容器的导出 2.docker容器的导入 四.总结 一.前言 随着容器技术的发展,现在很多的应用程序系统都会选择使用docker容器进行部署,但是有时候使用docker容器进行部署的时候会遇到问题,比如说我们的应用程序里面需要依赖其他第三方的镜像,如果这时候服务器是在内网不能连接外网的情况下,那么就无法部署了.基于这种情况,docker官方支持

  • Docker部署项目完全使用指南(小结)

    目录 环境准备 Docker安装启动 Java环境安装 Docker远程访问配置 Docker重启 IDEA配置Docker 项目配置 配置Dockerfile文件 Docker配置 Maven打包生成Docker镜像 总结 Linux操作命令 Docker操作命令 环境准备 Docker安装启动 检查系统内核是否满足3.10及以上版本: uname -r 升级内核软件包: yum update 使用yum安装Docker: yum install docker 启动Docker: system

  • 为Centos安装指定版本的Docker

    安装 Docker 从 2017 年 3 月开始 docker 在原来的基础上分为两个分支版本: Docker CE 和 Docker EE. Docker CE 即社区免费版,Docker EE 即企业版,强调安全,但需付费使用. 本文介绍 Docker CE 的安装使用. 移除旧的版本: $ sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docke

  • 关于docker启动jenkins环境的问题

    [注意:]jenkins的docker镜像,需要从官网进入直接获取,其他地方获取到的docker镜像,可能因为Jenkins版本过低,导致后续插件安装失败等问题!!! jenkins基本工作原理 1.拉取镜像 需要从官网进入:Jenkins download and deployment 命令: docker pull jenkins/jenkins:lts 2.创建目录 由于防止jenkins中重要文件因为容器损毁或删除导致文件丢失,因此创建文件对外挂载 mkdir /data/jenkins

  • docker exec执行多个命令的操作

    docker exec命令能够在运行着的容器中执行命令. docker exec命令的使用格式: docker exec [OPTIONS] container_name COMMAND [ARG...] OPTIONS说明: -d,以后台方式执行命令: -e,设置环境变量 -i,交互模式 -t,设置TTY -u,用户名或UID,例如myuser:myusergroup 通常COMMAND只能是一条语句,为了支持多个命令的执行,需要将多个命令连接起来交给Shell, docker exec命令的

  • docker基本命令及使用实例详解

    docker基本命令 docker文件系统 docker镜像为分层设计,相比于全量的虚拟机镜像,少了引导程序bootfs,共用系统内核rootfs. 配置层(容器层) commit镜像 docker commit -a="" -m="" docker_id image_name:tag 类似于给镜像打个快照. 容器数据卷 解决问题 ->数据持久化 本质是目录挂载 使用数据卷 方式一: 直接使用命令挂载 -v docker run -it -v 主机目录:容器内

  • Docker exec 的实现原理介绍

    我使用了 docker exec 命令进入到了容器当中.在了解了Linux Namespace 的隔离机制后,你应该会很自然地想到一个问题:docker exec 是怎么做到进入容器里的呢?实际上,Linux Namespace 创建的隔离空间虽然看不见摸不着,但一个进程的 Namespace 信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在. 比如,通过如下指令,你可以看到当前正在运行的 Docker 容器的进程号(PID)是 25686: $ docker inspect --fo

  • Docker 隔离与限制原理介绍

    目录 为什么 Docker 比虚拟机受欢迎 优点 不足 资源限制 总结 为什么 Docker 比虚拟机受欢迎 在上一篇文章中,详细介绍了 Linux 容器中用来实现“隔离”的技术手段:Namespace.而通过这些讲解,你应该能够明白,Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容.但对于宿主机来说,这些被“隔离”了的进程跟其他进程并没有太大区别.说到这一点,在之前虚拟机与容器技术的对比图里,不应该把 Docker

  • Docker 文件系统-AUFS 原理介绍

    目录 什么是联合文件系统 如何配置 Docker 的 AUFS 模式 AUFS 工作原理 AUFS 是如何存储文件的? AUFS 是如何工作的? 1. 读取文件 修改文件或目录 AUFS 演示 准备演示目录和文件 创建 AUFS 联合文件系统 验证 AUFS 的写时复制 结语 前言: 我们知道,Docker 主要是基于 Namespace.cgroups 和联合文件系统这三大核心技术实现的.前面的课时我详细讲解了 Namespace 和 cgroups 的相关原理,那么你知道联合文件系统是什么吗

  • Docker 常用命令整理并介绍

    什么是Docker? Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的.可移植的.自给自足的容器.开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机).bare metal.OpenStack 集群和其他的基础应用平台. Docker通常用于如下场景: web应用的自动化打包和发布: 自动化测试和持续集成.发布: 在服务型环境中部署和调整数据库或其他的后台应用: 从头编译或者扩展现有的OpenShift或Cloud Foundry平台来搭建自己的P

  • Docker镜像加载原理

    目录 Docker镜像 镜像是什么? Docker镜像加载原理 Commit镜像 Docker镜像(Images)总结 Docker镜像 镜像是什么? 镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码.运行时.库.环境变量和配置文件. 所有的应用,直接打包docker镜像,就可以直接跑起来! 如何得到镜像? 远程仓库下载 朋友拷贝 自己制作一个镜像 DockerFile Docker镜像加载原理 UnionFS(联合文

  • Docker快速部署SpringBoot项目介绍

    1.安装 Docker 首先打开linux环境,输入以下命令进行安装: 安装 yum install docker 检查是否安装成功 docker --version #启动 systemctl start docker 如果下载很慢,可以切换到国内的阿里云镜像,进行下载: 换镜像源 sudo vim /etc/docker/daemon.json 内容如下: { "registry-mirrors": ["https://m9r2r2uj.mirror.aliyuncs.c

  • Docker中的镜像详细介绍

    Docker中的镜像详细介绍 Docker镜像可以理解为运行在Docker容器中的一个组件,本节将会带领大家学会Docker镜像的下载.搜索.查看.添加删除等一系列操作.Docker在运行容器前需要本地存在对应的镜像,如果镜像不存在本地,Docker会尝试从默认的远程仓库Docker Hub下载. 一.获取镜像:  1.Docker从网络上下载镜像的格式为: docker pull [NAME]:[TAG] 其中TAG即为标签,如果不指定标签的话,那么默认会下载最新版本的镜像. 比如从Docke

  • Docker容器数据卷原理及使用方法解析

    什么是容器数据卷 如果数据都在容器中,一旦容器删除,数据就会丢失! eg : mysql容器删了,就是我们常说的删库跑路. 需求:数据可以持久化,即时删掉容器,我们的数据还在 容器直接可以有一个数据共享的技术!Docker容器产生的数据,同步到本地! 这就是卷技术!目录的挂载,将我们容器的目录挂载到linux上面! 总结:卷技术就是为了实现数据的持久化和同步操作,容器间也是可以数据共享的 使用数据卷 方式一:直接使用命令来挂载 -v # 命令 docker run -it -v 主机的目录:容器

  • Java多线程Condition接口原理介绍

    Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的 Condition接口详解 Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁.Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的. Lock lock = new ReentrantL

随机推荐