从零开始写 Docker(十二)---实现 mydocker stop 停止容器

mydocker-stop.png

本文为从零开始写 Docker 系列第十二篇,实现类似 docker stop 的功能,使得我们能够停止指定容器。


完整代码见:https://github.com/lixd/mydocker
欢迎 Star

推荐阅读以下文章对 docker 基本实现有一个大致认识:

  • 核心原理:深入理解 Docker 核心原理:Namespace、Cgroups 和 Rootfs
  • 基于 namespace 的视图隔离:探索 Linux Namespace:Docker 隔离的神奇背后
  • 基于 cgroups 的资源限制
    • 初探 Linux Cgroups:资源控制的奇妙世界
    • 深入剖析 Linux Cgroups 子系统:资源精细管理
    • Docker 与 Linux Cgroups:资源隔离的魔法之旅
  • 基于 overlayfs 的文件系统:Docker 魔法解密:探索 UnionFS 与 OverlayFS
  • 基于 veth pair、bridge、iptables 等等技术的 Docker 网络:揭秘 Docker 网络:手动实现 Docker 桥接网络


开发环境如下:

root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.2 LTS
Release:	20.04
Codename:	focal
root@mydocker:~# uname -r
5.4.0-74-generic

注意:需要使用 root 用户

1. 概述

之前实现了 mydocker run -d 让容器能够后台运行,但是没有实现停止功能,导致无法停止后台运行的容器。

本篇则是实现mydocker stop 命令,让我们能够直接停止后台运行的容器。

2. 实现

容器的本质是进程,那么停止容器就可以看做是结束进程。因此 mydocker stop 的实现思路就是先根据 containerId 查找到它的主进程 PID,然后 Kill 发送 SIGTERM 信号,等待进程结束就好。

整个流程如下图所示:

mydocker-stop-process.png

stopCommand

首先在 main_command.go 中增加 stopCommand:

var stopCommand = cli.Command{Name:  "stop",Usage: "stop a container,e.g. mydocker stop 1234567890",Action: func(context *cli.Context) error {// 期望输入是:mydocker stop 容器Id,如果没有指定参数直接打印错误if len(context.Args()) < 1 {return fmt.Errorf("missing container id")}containerName := context.Args().Get(0)stopContainer(containerName)return nil},
}

然后在 main 函数中加入该命令:

func main(){// 省略其他内容app.Commands = []cli.Command{initCommand,runCommand,commitCommand,listCommand,logCommand,execCommand,stopCommand,}
}

核心逻辑都在 stopContainer 中,command 这边只需要解析并传递参数即可。

stopContainer

stopContainer 中就是停止容器的具体实现了。实现也很简单,大致可以分为 3 步:

  • 1)首先根据 ContainerId 找到之前记录的容器信息的文件并拿到容器具体信息,主要是 PID
  • 2)然后调用 Kill 命令,给指定 PID 发送 SIGTERM
  • 3)最后更新容器状态为 stop 并写回记录容器信息的文件;

具体代码如下:

func stopContainer(containerId string) {// 1. 根据容器Id查询容器信息containerInfo, err := getInfoByContainerId(containerId)if err != nil {log.Errorf("Get container %s info error %v", containerId, err)return}pidInt, err := strconv.Atoi(containerInfo.Pid)if err != nil {log.Errorf("Conver pid from string to int error %v", err)return}// 2.发送SIGTERM信号if err = syscall.Kill(pidInt, syscall.SIGTERM); err != nil {log.Errorf("Stop container %s error %v", containerId, err)return}// 3.修改容器信息,将容器置为STOP状态,并清空PIDcontainerInfo.Status = container.STOPcontainerInfo.Pid = " "newContentBytes, err := json.Marshal(containerInfo)if err != nil {log.Errorf("Json marshal %s error %v", containerId, err)return}// 4.重新写回存储容器信息的文件dirPath := fmt.Sprintf(container.InfoLocFormat, containerId)configFilePath := path.Join(dirPath, container.ConfigName)if err := os.WriteFile(configFilePath, newContentBytes, constant.Perm0622); err != nil {log.Errorf("Write file %s error:%v", configFilePath, err)}
}

getInfoByContainerId 如下,根据 containerId 拼接出具体 path,读取文件内容拿到启动时记录的容器信息,其中就包括 PID。

func getInfoByContainerId(containerId string) (*container.Info, error) {dirPath := fmt.Sprintf(container.InfoLocFormat, containerId)configFilePath := path.Join(dirPath, container.ConfigName)contentBytes, err := os.ReadFile(configFilePath)if err != nil {return nil, errors.Wrapf(err, "read file %s", configFilePath)}var containerInfo container.Infoif err = json.Unmarshal(contentBytes, &containerInfo); err != nil {return nil, err}return &containerInfo, nil
}

3. 测试

测试流程为:

  • 1)mydocker run -d创建一个 detach 的后台容器
  • 2)mydocker stop 该容器
  • 3)mydocker ps 查看容器状态是否变更,ps 查看容器进程是否消失

创建一个 detach 容器:

root@mydocker:~/feat-stop/mydocker# go build .
root@mydocker:~/feat-stop/mydocker# ./mydocker run -d -name bird top
{"level":"info","msg":"createTty false","time":"2024-01-30T14:04:13+08:00"}
{"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-30T14:04:13+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-30T14:04:13+08:00"}
{"level":"error","msg":"mkdir dir /root/merged error. mkdir /root/merged: file exists","time":"2024-01-30T14:04:13+08:00"}
{"level":"error","msg":"mkdir dir /root/upper error. mkdir /root/upper: file exists","time":"2024-01-30T14:04:13+08:00"}
{"level":"error","msg":"mkdir dir /root/work error. mkdir /root/work: file exists","time":"2024-01-30T14:04:13+08: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-30T14:04:13+08:00"}
{"level":"info","msg":"command all is top","time":"2024-01-30T14:04:13+08:00"}

分别使用 ps 命令和 mydocker ps 命令查询一下 PID

root@mydocker:~/feat-stop/mydocker# ./mydocker ps
ID           NAME        PID         STATUS      COMMAND     CREATED
3184421796   bird        180831      running     top         2024-01-30 14:04:1
root@mydocker:~/feat-stop/mydocker# ps -ef|grep top
root      180831       1  0 14:04 pts/10   00:00:00 top

可以看到,PID 为 180831 的进程就是我们的容器进程。

现在执行 stop 命令停止该容器

root@mydocker:~/feat-stop/mydocker# ./mydocker stop 3184421796

再通过 mydocker ps 命令查看一下

root@mydocker:~/feat-stop/mydocker# ./mydocker ps
ID           NAME        PID         STATUS      COMMAND     CREATED
3184421796   bird                    stopped     top         2024-01-30 14:04:13

可以看到,状态变成了 stopped,并且 PID 一栏也是空的。

最后执行 ps 查看一下是不是真的停掉了

root@mydocker:~/feat-stop/mydocker# ps -ef|grep top
root      180869  177607  0 14:06 pts/10   00:00:00 grep --color=auto top

可以看到,原来容器的进程已经退出了,说明 stop 是成功的。

4. 小结

本篇主要实现 mydocker stop 命令,根据 ContainerId 找到容器进程 PID,然后 Kill 并更新容器状态信息。


完整代码见:https://github.com/lixd/mydocker
欢迎关注~


**【从零开始写 Docker 系列】**持续更新中,搜索公众号【探索云原生】订阅,文章。


相关代码见 feat-stop 分支,测试脚本如下:

需要提前在 /root 目录准备好 busybox.tar 文件,具体见第四篇第二节。

# 克隆代码
git clone -b feat-stop https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 
./mydocker run -d -name c1 top
# 查看容器 Id
./mydocker ps
# stop 停止指定容器
./mydocker stop ${containerId}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/3048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[韭]第一次参加护网行动要注意什么??

前言 为什么要写这篇文章呢&#xff0c;一是对当下安全环境进行一个分析&#xff0c;二是以过来人的身份留下一点经验。 当下安全环境分析 现在这个时代&#xff0c;大家都已经踏上了信息高速公路&#xff0c;可以自己拿搜索引擎搜一下自己知道的大厂裁员情况&#xff0c;有…

油猴脚本:bing 搜索结果居中

文章目录 效果预览脚本使用步骤安装油猴脚本添加脚本 效果预览 脚本 // UserScript // name bing居中 // namespace http://tampermonkey.net/ // version 2024-04-24 // description try to take over the world! // author You // match http…

rust 卸载重新安装 安装

原因&#xff1a;接触区块链时报错 linking with x86_64-w64-mingw32-gcc failed: exit code: 1 Rust编译需要C环境&#xff0c;如果你没有&#xff0c;Rust也能安装成功&#xff0c;只是无法编译代码 C的编译工具有两个&#xff0c;一个是msvc&#xff0c;也就是visual studi…

CTFshow-PWN-栈溢出(pwn44)

64位的 system(); 但是好像没"/bin/sh" 上面的办法不行了&#xff0c;想想办法 检查&#xff1a; 是 64 位程序 ida 反编译 main 函数&#xff1a; 跟进 ctfshow 函数&#xff1a; 存在栈溢出 offset&#xff1a;0xAh8 在前面经验的基础上&#xff0c;这里我们直…

Python-VBA函数之旅-issubclass函数

目录 一、issubclass函数的常见应用场景&#xff1a; 二、issubclass函数使用注意事项&#xff1a; 三、如何用好issubclass函数&#xff1f; 1、issubclass函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff…

Linux下载及安装OpenSSL

文章目录 前言一、OpenSSL下载二、OpenSSL安装1.上传下载好的安装包到服务器2.解压3.切换目录4.配置config5.编译6.安装7.备份旧版本OpenSSL7.创建软链接8.添加OpenSSL动态链接库9.更新库缓存10.查看OpenSSL版本验证安装是否成功 前言 一般系统会自带有OpenSSL&#xff0c;我们…

ubuntu 24.04 beta server NAT模式上网设置

在Ubuntu 24.04 Beta上设置网络通常涉及使用命令行工具。以下是设置静态IP地址和动态IP地址的步骤&#xff1a; 动态IP设置&#xff1a; 查找你的网络接口名称&#xff1a; ip a ens37是我NAT模型的一张网卡&#xff0c;此时是没有ip的。 下面介绍如何NAT模式下添加DHCP动态…

【数据结构与算法】:带你手搓顺序表(C/C++篇)

文章目录 一、顺序表1.1 线性表 二、顺序表2.1 概念及结构2.2 实现方法1. 顺序表的初始化2. 顺序表的销毁3. 扩容4. 尾部插入/尾部删除5. 头部插入/头部删除6. 指定位置插入/删除7. 顺序表的查找8. 顺序表元素的索引9. 顺序表元素的修改10. SeqList.c完整代码 一、顺序表 1.1 …

FL Studio21中文版值不值得下载?2024优点和缺点测评介绍

FL Studio21的优点和缺点分析如下&#xff1a; 优点&#xff1a; 功能全面&#xff1a; FL Studio 21提供了从音频录制、编辑到混音和母带处理的全套工具&#xff0c;满足音乐制作的全流程需求。内置多种虚拟乐器和效果器&#xff0c;覆盖了广泛的音色和风格。直观易用&#…

粒子群算法(PSO)matlab代码实现

粒子群算法(PSO)matlab代码实现 %% 清空命令行和工作区 clc clear %% 目标函数&#xff1a;求解函数在[0,50]区间上的最小值 f(x) x.*sin(x).*cos(2*x) - 2.*sin(3*x)3*x.*sin(4*x); %% 初始化算法参数 population50; % 粒子的数量 dimension1; …

frp 实现 http / tcp 内网穿透(穿透 wordpress )

frp 实现 http / tcp 内网穿透&#xff08;穿透 wordpress &#xff09; 1. 背景简介与软件安装2. 服务端配置2.1 配置文件2.2 wordpress 配置文件2.3 frps 自启动 3.客户端配置3.1 配置文件3.2 frpc 自启动 同步发布在个人笔记frp 实现 http / tcp 内网穿透&#xff08;穿透 w…

详细分析mysqlslap的基本知识 | 压力测试(附Demo)

目录 前言1. 基本知识2. 参数解读2.1 auto-generate-sql2.2 only-print2.3 iterations2.4 并发处理参数 前言 对数据库进行压力测试&#xff0c;对此补充这方面的详细知识点 1. 基本知识 mysqlslap 是 MySQL 自带的用于模拟数据库负载的压力测试工具 可以模拟多个客户端并发…

C++—DAY2

定义一个矩形类Rec&#xff0c;包含私有属性length&#xff0c;width&#xff0c;有以下成员函数: void set length(int l);//设置长度 void set width(int w); //设置宽度 int get length(); //获取长度 int get_width(); //获取宽度 void show(); //输出…

《本能》我们为什么管不住自己 - 三余书屋 3ysw.net

本能&#xff1a;我们为什么管不住自己 大家好&#xff0c;今天我们来解读一本名为《本能》的书。这本书有两位作者&#xff0c;第一位是特里伯纳姆&#xff0c;他是哈佛大学商学院的访问学者&#xff0c;在1997年获得哈佛大学商业经济学博士学位&#xff0c;也是自1997年起一…

无重复最长子串

专栏持续更新50道算法题&#xff0c;都是大厂高频算法题&#xff0c;建议关注, 一起巧‘背’算法! 文章目录 题目解法 滑动窗口总结 题目 解法 滑动窗口 采取滑动窗口的方法降低时间复杂度定义一个 map 数据结构存储 (k, v)&#xff0c;其中 key 值为字符&#xff0c;value 值…

Shader for Quest 2: 自定义shader在Unity Editor中可以使用,但是在Quest 2中却不可以

GameObject segment GameObject.Find("DisplayArea_" i); MeshRenderer renderer segment.GetComponent<MeshRenderer>(); Material mat new Material(Shader.Find("Custom/MyShader")); mat.mainTexture option.Image360;上面这份代码&#x…

Qt中常用对话框

Qt中的对话框&#xff08;QDialog&#xff09;是用户交互的重要组件&#xff0c;用于向用户提供特定的信息、请求输入、或进行决策。Qt提供了多种标准对话框以及用于自定义对话框的类。以下将详细介绍几种常用对话框的基本使用、使用技巧以及注意事项&#xff0c;并附带C示例代…

vmp入门(一):android dex vmp还原和安全性论述

看了一下&#xff0c;目前市面上关于dex vmp还原就我兄弟写的这一个&#xff0c;我不得不佩服他巨强的二级制分析能力。关于dex vmp 他的大部分都写了&#xff0c;但是&#xff0c;他搞的实在是太复杂了&#xff0c;他的分析基本都是基于静态的数据流向分析&#xff0c;这种对于…

Sylar C++高性能服务器学习记录05 【线程模块-知识储备篇】

早在19年5月就在某站上看到sylar的视频了&#xff0c;一直认为这是一个非常不错的视频&#xff0c;还有幸加了sylar本人的wx&#xff0c;由于本人一直是自学编程&#xff0c;基础不扎实&#xff0c;也没有任何人的督促&#xff0c;没能坚持下去&#xff0c;每每想起倍感惋惜。恰…

FineBi中创建自定义的图表

FineBi中增加自己的自定义图表组件,比如: 的相关笔记: 1 获取有哪些BI自定义图表组件:http://localhost:8080/webroot/decision/v5/plugin/custom/component/list?_=1713667435473[{"name": "图表DEMO_EK","chartType": "amap_demo&q…