当前位置: 首页 > news >正文

郑州网站seo排名跨境电商全托管有哪些平台

郑州网站seo排名,跨境电商全托管有哪些平台,楼盘网站建设方案ppt,家庭 wordpress主题本文为从零开始写 Docker 系列第十一篇#xff0c;实现类似 docker exec 的功能#xff0c;使得我们能够进入到指定容器内部。 完整代码见#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识#xff1a; 核心原理实现类似 docker exec 的功能使得我们能够进入到指定容器内部。 完整代码见https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识 核心原理深入理解 Docker 核心原理Namespace、Cgroups 和 Rootfs基于 namespace 的视图隔离探索 Linux NamespaceDocker 隔离的神奇背后基于 cgroups 的资源限制 初探 Linux Cgroups资源控制的奇妙世界深入剖析 Linux Cgroups 子系统资源精细管理Docker 与 Linux Cgroups资源隔离的魔法之旅 基于 overlayfs 的文件系统Docker 魔法解密探索 UnionFS 与 OverlayFS基于 veth pair、bridge、iptables 等等技术的 Docker 网络揭秘 Docker 网络手动实现 Docker 桥接网络 开发环境如下 rootmydocker:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal rootmydocker:~# uname -r 5.4.0-74-generic注意需要使用 root 用户 1. 概述 上一篇已经实现了mydocker logs 命令可以查看容器日志了。本篇主要实现 mydocker exec,让我们可以直接进入到容器内部查看容器内部的文件、调试应用程序、执行命令等等。 下面这篇文章分析了 Docker 是如何使用 Linux Namespace 来实现视图隔离的那么 mydocker exec 也是需要在 Namespace 上做文章。 [探索 Linux NamespaceDocker 隔离的神奇背后] 2. 核心原理 docker exec 实则是将当前进程添加到指定容器对应的 namespace 中从而可以看到容器中的进程信息、网络信息等。 因此我们的 mydocker exec 具体实现包括两部分 根据容器 ID 找到对应 PID然后找到 Namespace将当前进程切换到对应 Namespace setns 将进程加入到对应的 Namespace 很简单Linux提供了 setns 系统调用给我们使用。 setns 是一个系统调用可以根据提供的 PID 再次进入到指定的 Namespace 中。它需要先打开/proc/[pid/ns/文件夹下对应的文件然后使当前进程进入到指定的 Namespace 中。 但是用 Go 来实现则存在一个致命问题setns 调用需要单线程上下文而 GoRuntime 是多线程的。 准确的说是 MountNamespace。 Linux 的 Namespace 是一种资源隔离机制它允许将一组进程的视图隔离到系统的不同部分比如 PID Namespace、Network Namespace 等。 setns 系统调用允许进程加入或重新进入到指定的 Namespace 中。由于 Namespace 涉及到整个进程的资源隔离因此需要在进程的上下文中执行以确保进程及其所有线程都在相同的 Namespace 中。 Go Runtime 是多线程的这意味着 Go 程序通常会有多个线程在同时运行。这种多线程模型与 setns 调用所需的单线程上下文不兼容。 Goroutine 会随机在底层 OS 线程之间切换而不是固定在某个线程因此在 Go 中执行 setns 不能准确的知道是操作到哪个线程了结果是不确定的因此需要特殊处理。 这个问题对 Go 本身来说没有太好的解决办法#14163 是 Github 上对一些解决方案的讨论不过最终还是被拒绝了。 不过好消息是 C 语言可以通过 gcc 的 扩展 attribute((constructor)) 来实现程序启动前执行特定代码因此 Go 就可以通过 cgo 嵌入 这样的一段 C 代码来完成 runtime 启动前执行特定的 C 代码。 runC 中的 nsenter 也是借助 cgo 实现的。 具体代码如下 //go:build linux !gccgo // build linux,!gccgopackage nsenter/* #cgo CFLAGS: -Wall extern void nsexec(); void __attribute__((constructor)) init(void) {// something } */ import C这段代码就会在 Go Runtime 启动前执行这里定义的 init() 函数我们只需要把 setns 的调用放在这个 init 方法中即可。 cgo cgo 是一个很炫酷的功能允许 Go 程序去调用 C 的函数与标准库。你只需要以一种特殊的方式在 Go 的源代码里写出需要调用的 C 的代码cgo 就可以把你的 C 源码文件和 Go 文件整合成一个包。 下面举一个最简单的例子在这个例子中有两个函数一Random 和 Seed,在 它们里面调用了 C 的 random 和 srandom 函数。 package main/* #include stdlib.h */ import C import (fmt )func main() {Seed(123)// OutputRandom: 128959393fmt.Println(Random: , Random()) }// Seed 初始化随机数产生器 func Seed(i int) {C.srandom(C.uint(i)) }// Random 产生一个随机数 func Random() int {return int(C.random()) }这段代码导入了一个叫 C 的包但是你会发现在 Go 标准库里面并没有这个包那是因为这根本就不是一个真正的包而只是 Cgo 创建的一个特殊命名空间用来与 C 的命名空间交流。 这两个函数都分别调用了 C 里面的 random 和 uint 函数然后对它们进行了类型转换。这就实现了 Go 代码里面调用 C 的功能。 3. 实现 首先自然是需要在 C 中实现 setns 核心逻辑根据 PID 实现 Namespace 切换。 其次由于使用 C 的 constructor 方式以 init 形式执行的 setns 这段代码意味这执行任何 mydocker 命令的时候这段代码都会执行因此需要限制只有 mydocker exec 时才切换 Namespace。 大致流程如下图所示 setns setns 的 C 实现具体如下 package nsenter/* #define _GNU_SOURCE #include unistd.h #include errno.h #include sched.h #include stdio.h #include stdlib.h #include string.h #include fcntl.h__attribute__((constructor)) void enter_namespace(void) {// 这里的代码会在Go运行时启动前执行它会在单线程的C上下文中运行char *mydocker_pid;mydocker_pid getenv(mydocker_pid);if (mydocker_pid) {fprintf(stdout, got mydocker_pid%s\n, mydocker_pid);} else {fprintf(stdout, missing mydocker_pid env skip nsenter);// 如果没有指定PID就不需要继续执行直接退出return;}char *mydocker_cmd;mydocker_cmd getenv(mydocker_cmd);if (mydocker_cmd) {fprintf(stdout, got mydocker_cmd%s\n, mydocker_cmd);} else {fprintf(stdout, missing mydocker_cmd env skip nsenter);// 如果没有指定命令也是直接退出return;}int i;char nspath[1024];// 需要进入的5种namespacechar *namespaces[] { ipc, uts, net, pid, mnt };for (i0; i5; i) {// 拼接对应路径类似于/proc/pid/ns/ipc这样sprintf(nspath, /proc/%s/ns/%s, mydocker_pid, namespaces[i]);int fd open(nspath, O_RDONLY);// 执行setns系统调用进入对应namespaceif (setns(fd, 0) -1) {fprintf(stderr, setns on %s namespace failed: %s\n, namespaces[i], strerror(errno));} else {fprintf(stdout, setns on %s namespace succeeded\n, namespaces[i]);}close(fd);}// 在进入的Namespace中执行指定命令然后退出int res system(mydocker_cmd);exit(0);return; } */ import C为什么要这么写前面 setns 部分已经解释了这里简单提一下这里主要使用了构造函数然后导入了 C 模块一旦这个包被引用它就会在所有 Go Runtime 启动之前执行这样就避免了 Go 多线程导致的无法执行 setns 的问题。 即这段程序执行完毕后Go 程序才会执行。 同时为了避免执行其他命令的时候这段 setns 的逻辑影响到其他功能因此在这段 C 代码前面一开始的位置就添加了环境变量检测没有对应的环境变量时就直接退出。 mydocker_cmd getenv(mydocker_cmd);if (mydocker_cmd) {// fprintf(stdout, got mydocker_cmd%s\n, mydocker_cmd);} else {// fprintf(stdout, missing mydocker_cmd env skip nsenter);// 如果没有指定命令也是直接退出return;}对于不使用 exec 功能的 Go 代码只要不设置对应的环境变量这段 C 代码就不会运行这样就不会影响原来的逻辑。 注意只有在你的 Go 应用程序中注册、导入了这个包才会调用这个构造函数。 就像这样 import (_ mydocker/nsenter )使用 cgo 我们无法直接获取传递给程序的参数可用的做法是通过 go exec 创建一个自身运行进程然后通过传递环境变量的方式传递给 cgo 参数值。 体现在 runc 中就是 runc create → runc init runc 中有很多细节他通过环境变量传递 netlink fd然后进行通信。 execCommand 在 main_command.go 中增加一个 execCommand具体如下 var execCommand cli.Command{Name: exec,Usage: exec a command into container,Action: func(context *cli.Context) error {// 如果环境变量存在说明C代码已经运行过了即setns系统调用已经执行了这里就直接返回避免重复执行if os.Getenv(EnvExecPid) ! {log.Infof(pid callback pid %v, os.Getgid())return nil}// 格式mydocker exec 容器名字 命令因此至少会有两个参数if len(context.Args()) 2 {return fmt.Errorf(missing container name or command)}containerName : context.Args().Get(0)// 将除了容器名之外的参数作为命令部分var commandArray []stringfor _, arg : range context.Args().Tail() {commandArray append(commandArray, arg)}ExecContainer(containerName, commandArray)return nil}, } 然后添加到 main 函数中去 func main(){// 省略其他内容app.Commands []cli.Command{initCommand,runCommand,commitCommand,listCommand,logCommand,execCommand,} }这里主要是将获取到的容器名和需要的命令处理完成后交给下面的函数下面看一下 ExecContainer 的实现。 ExecContainer exec 命令核心实现就是 ExecContainer 方法。 // nsenter里的C代码里已经出现mydocker_pid和mydocker_cmd这两个Key,主要是为了控制是否执行C代码里面的setns. const (EnvExecPid mydocker_pidEnvExecCmd mydocker_cmd )func ExecContainer(containerId string, comArray []string) {// 根据传进来的容器名获取对应的PIDpid, err : getPidByContainerId(containerId)if err ! nil {log.Errorf(Exec container getContainerPidByName %s error %v, containerId, err)return}cmd : exec.Command(/proc/self/exe, exec)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderr// 把命令拼接成字符串便于传递cmdStr : strings.Join(comArray, )log.Infof(container pid%s command%s, pid, cmdStr)_ os.Setenv(EnvExecPid, pid)_ os.Setenv(EnvExecCmd, cmdStr)if err cmd.Run(); err ! nil {log.Errorf(Exec container %s error %v, containerId, err)} }首先是通过ContainerId 找到进程 PID具体实现如下 因为之前已经记录了容器信息因此这里直接读取对应文件就可以找到了。 func getPidByContainerId(containerId string) (string, error) {// 拼接出记录容器信息的文件路径dirPath : fmt.Sprintf(container.InfoLocFormat, containerId)configFilePath : path.Join(dirPath, container.ConfigName)// 读取内容并解析contentBytes, err : os.ReadFile(configFilePath)if err ! nil {return , err}var containerInfo container.Infoif err json.Unmarshal(contentBytes, containerInfo); err ! nil {return , err}return containerInfo.Pid, nil }然后则是通过 exec 简单 fork 出了一个进程并把这个进程的标准输入输出都绑定到宿主机的 stdin、stdout、stderr 上。 cmd : exec.Command(/proc/self/exe, exec)cmd.Stdin os.Stdincmd.Stdout os.Stdoutcmd.Stderr os.Stderr// 把命令拼接成字符串便于传递cmdStr : strings.Join(comArray, )最关键的是设置环境变量的这两句 _ os.Setenv(EnvExecPid, pid)_ os.Setenv(EnvExecCmd, cmdStr)设置了这两个环境变量于是在新的进程里前面的 nsenter 部分的 C 代码就会执行到 setns 部分逻辑从而将进程加入到对应的 Namespace 中进行操作了。 C 代码中根据环境变量拿到 PID 和要执行的命令首先根据 PID 找到对应 Namespace然后将当前进程加入到该 Namespace 然后执行具体命令。 这也是 mydocker exec 命令要实现的效果。 而执行其他命令时由于没有指定这两个环境变量因此那段 C 代码不会执行到 setns 这里。 这时应该就可以明白前面一段 C 代码的意义了 。 mydocker_pid getenv(mydocker_pid); if (mydocker_pid) {// fprintf(stdout, got mydocker_pid%s\n, mydocker_pid); } else {// 如果没有指定PID就不需要继续执行直接退出return; }执行 exec 命令就会设置这两个环境变量那么问题来了执行 exec 之后环境变量就已经存在了C 代码也运行了那么再次执行 exec 命令岂不是会重复执行 setns 系统调用 为了避免重复执行在 execCommand 中加了如下判断如果对应环境变量已经存在了就直接返回啥也不执行。 因为环境变量存在就代表着 C 代码执行了即setns系统调用执行了也就是当前已经在这个 namespace 里了。 var execCommand cli.Command{Name: exec,Usage: exec a command into container,Action: func(context *cli.Context) error {// 如果环境变量存在说明C代码已经运行过了即setns系统调用已经执行了这里就直接返回避免重复执行if os.Getenv(EnvExecPid) ! {log.Infof(pid callback pid %v, os.Getgid())return nil}// 省略其他内容}, }至此 mydocker exec 命令实现就完成了核心就是 setns 系统调用 4. 测试 首先编译最新的 mydocker然后启动一个后台容器这里直接把 name 指定为 test方便观察。 这里要运行交互式命令例如 top保证容器能在后台一直运行。 rootmydocker:~/feat-exec/mydocker# go build . rootmydocker:~/feat-exec/mydocker# ./mydocker run -d -name test top {level:info,msg:createTty false,time:2024-01-30T09:48:3308:00} {level:info,msg:resConf:\u0026{ 0 },time:2024-01-30T09:48:3308:00} {level:info,msg:busybox:/root/busybox busybox.tar:/root/busybox.tar,time:2024-01-30T09:48:3308:00} {level:error,msg:mkdir dir /root/merged error. mkdir /root/merged: file exists,time:2024-01-30T09:48:3308:00} {level:error,msg:mkdir dir /root/upper error. mkdir /root/upper: file exists,time:2024-01-30T09:48:3308:00} {level:error,msg:mkdir dir /root/work error. mkdir /root/work: file exists,time:2024-01-30T09:48:3308:00} {level:info,msg:mount overlayfs: [/usr/bin/mount -t overlay overlay -o lowerdir/root/busybox,upperdir/root/upper,workdir/root/work /root/merged],time:2024-01-30T09:48:3308:00} {level:info,msg:command all is top,time:2024-01-30T09:48:3308:00}然后查看容器 ID rootmydocker:~/feat-exec/mydocker# ./mydocker ps ID NAME PID STATUS COMMAND CREATED 2147624410 test 180358 running top 2024-01-30 09:48:33然后执行 exec 命令并指定 Id 为 2147624410 进入该容器 rootmydocker:~/feat-exec/mydocker# ./mydocker exec 2147624410 sh {level:info,msg:container pid180358 commandsh,time:2024-01-30T09:48:4208:00} got mydocker_pid180358 got mydocker_cmdsh setns on ipc namespace succeeded setns on uts namespace succeeded setns on net namespace succeeded setns on pid namespace succeeded setns on mnt namespace succeeded / # ps -e PID USER TIME COMMAND1 root 0:00 top6 root 0:00 sh7 root 0:00 ps -e在容器内部执行 ps -ef 可以发现 PID 为 1 的进程为 top,这也就意味着已经成功进入到了容器内部。 说明我们的 mydocker exec 命令实现是成功了。 5. 小结 本篇主要实现 mydocker exec 命令和 docker 实现基本类似,通过 setns 系统调用将当前进程加入到容器所在 Namespace 即可。 比较关键的一点在于Go Runtime 是多线程的和 setns 冲突因此需要使用 Cgo 以constructor 方式在 Go Runtime 启动之前执行 setns 调用。 最后就是根据是否存在指定环境变量来防止重复执行。 **【从零开始写 Docker 系列】**持续更新中搜索公众号【探索云原生】订阅文章。 完整代码见https://github.com/lixd/mydocker 欢迎关注~ 相关代码见 feat-exec 分支,测试脚本如下 需要提前在 /root 目录准备好 busybox.tar 文件具体见第四篇第二节。 # 克隆代码 git clone -b feat-exec https://github.com/lixd/mydocker.git cd mydocker # 拉取依赖并编译 go mod tidy go build . # 测试 ./mydocker run -d -name c1 top # 查看容器 Id ./mydocker ps # 根据 Id 执行 exec 进入对应容器 ./mydocker exec ${containerId}
http://www.zqtcl.cn/news/564822/

相关文章:

  • 长沙市网站推广公司wordpress 弹窗登录插件
  • 网站策划怎么做内容朔州网站建设公司
  • 宁波拾谷网站建设蚌埠网站建设中心
  • 青岛专业设计网站公司加拿大广播公司
  • 盘锦市建设局网站地址八桂职教网技能大赛
  • 投资建设一个网站多少钱和淘宝同时做电商的网站
  • 做动物网站的素材icp备案 网站备案
  • 找人建网站唐山网络运营推广
  • 福建省住房建设厅网站6网站简历模板
  • 医疗网站模版杭州工商注册
  • 正保建设工程网站logo创意
  • 简洁个人博客网站模板下载用自己电脑做网站服务器-phpstudy+花生壳
  • 网页模板下载哪个网站好多个域名指定同一个网站好处
  • 北京网站建设有哪些公司微网站的案例
  • 常德经开区网站官网域名备案关闭网站吗
  • 做宠物网站的工作室做网站租服务器
  • 2017做那个网站致富网站换源码如何保留以前的文章
  • php网站开发实例教程书wordpress博客页面显示文章在哪
  • 地方o2o同城网站源码微信app开发价格表
  • 花木公司网站源码双语外贸网站源码
  • 什么公司做网站会提供源代码创业做招商加盟类网站赚钱
  • 东莞网站建设排名基因数据库网站开发价格
  • 天河区营销型网站建设科技自立自强
  • 网站域名账号江苏百度推广代理商
  • 专题网站建站对网站分析
  • 外贸出口网站建设如何搭建自己的网站服务器
  • 云南省建设厅网站职称评审房地产推广方案和推广思路
  • 湘潭建设路街道网站app的设计与开发
  • 《网站开发实践》 实训报告广告策划书案例完整版
  • 一级 爰做片免费网站做中学学中做网站