简介
在上篇中我们实现了将容器后台运行,本篇中我们将实现docker的ps命令,查看当前正在运行中的容器列表
源码说明
同时放到了Gitee和Github上,都可进行获取
本章节对应的版本标签是:5.4,防止后面代码过多,不好查看,可切换到标签版本进行查看
代码实现
这一部分实现起来就有点麻烦了,其中的一个nsenter始终不能运行正常,折腾了好一阵子发现,需要导出包才行,相关的会在代码中详细的说明
首先我们是需要使用setns去再次进入到我们容器的namespace中:
setns是一个系统调用,可以根据提供的PID再次进入到指定的Namespace 中。它需要先打开/proc/[pid]/ns/文件夹下对应的文件,然后使当前进程进入到指定的Namespace 中
但是一个具有多线程的进程是无法使用setns调用进入到对应的命名空间的,而Go启动一个程序就会进入多线程状态,所以无法简单使用命令调用去实现这个功能,需要借助C来实现
Cgo是一个很炫酷的功能,允许Go程序去调用C的函数与标准库。你只需要以一种特殊的方式在Go的源代码里写出需要调用的C的代码,Cgo就可以把你的C源码文件和Go文件整合成一个包
Cgo代码实现
新建一个文件夹 nsenter,新建文件:nsenter.go
编写Cgo的进入命名空间的代码,如下:
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)) static void enter_namespace(void) {
char *mydocker_pid;
// 从环境变量中获取需要进入的PID
// 如果没有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");
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];
char *namespaces[] = { "ipc", "uts", "net", "pid", "mnt" };
for (i=0; i<5; i++) {
sprintf(nspath, "/proc/%s/ns/%s", mydocker_pid, namespaces[i]);
int fd = open(nspath, O_RDONLY);
/ 调用setns进入对应的namespace
if (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);
}
// 进入后执行指定的命令
int res = system(mydocker_cmd);
exit(0);
return;
}
*/
import "C"
如上所示,这样就把进入命名空间的Cgo文件写好了,具体使用在后面会详细说明
Exec命令实现
我们在main中增加exec命令:
func main() {
......
app.Commands = []cli.Command{
command.InitCommand,
command.RunCommand,
command.CommitCommand,
command.ListCommand,
command.LogCommand,
command.ExecCommand,
}
......
}
在main_command.go文件,增加Exec指令解析
var ExecCommand = cli.Command{
Name: "exec",
Usage: "exec a command into container",
Action: func