实现自动创建容器及外部执行容器命令工具
1 工具设计概念
需求:
由于公司专门为其他业务部门提供了 paas 平台操作,但是有的业务部门的同事不太习惯使用 web 页面,所以我们不得不为他们提供后台的 kubectl 终端对他们的业务 NS 进行管理。
而领导的解决方式是通过在 K8S 上通过 UA 生成单独的用户来实现对业务的 NS 进行管理,并提供一个容器来实现对其的管理,所以每次容器运维同事都需要通过手动的方式在命令行创建 UA、docker 等操,这就显得比较造轮子,那么我就想通过直接编写一个命令工具来替代这些手动操作的问题
kubevw 自动创建容器工具设计:
- 在该命令行工具中能够自动获取 kubeconfig 文件
- 输入容器 name
- 输入使用 image
- 在容器中替换 config 文件
- 实现对容器中做 K8S 上下文切换
- 实现外部实现对容器内部的命令操作
由于需要自定义一个命令行工具,所以这里我采用的是 cobra 库来进行编写
2 Cobra 初体验
2.1 在 Golang 中使用 Cobra 创建 CLI 应用
虽然现在我们使用的大多数软件都是可视化的,很容易上手,但是这并不代表 CLI(命令行)应用就没有用武之地了,特别是对于开发人员来说,还是会经常和 CLI 应用打交道。而 Golang 就非常适合用来构建 CLI 应用,下面我们就将来介绍如何在 Golang 中构建一个 CLI 应用。
对于开发人员来说平时可能就需要使用到很多 CLI 工具,比如 npm、node、go、python、docker、kubectl 等等,因为这些工具非常小巧、没有依赖性、非常适合系统管理或者一些自动化任务等等。
我们这里选择使用 Golang 里面非常有名的 Cobra 库来进行 CLI 工具的开发。Cobra 是一个功能强大的现代化 CLI 应用程序库,有很多知名的 Go 项目使用 Cobra 进行构建,比如:Kubernetes、Docker、Hugo 等等
2.2 概念
Cobra 是构建在命令、参数和标识符之上的:
Commands
表示执行动作Args
就是执行参数Flags
是这些动作的标识符
基本的执行命令如下所示:
$ APPNAME Command Args --Flags
# 或者
$ APPNAME Command --Flags Args
比如我们平时使用的一些命令行工具:
- git clone URL -bare
- go get -u URL
- npm install package –save
- kubectl get pods -n kube-system -l app=cobra
2.3 示例
下面我们来看下 Cobra 的使用,这里我们使用的 go1.13.3 版本,使用 Go Modules 来进行包管理,如果对这部分知识点不熟悉的,可以查看前面我们的文章 Go Modules 基本使用(视频) 了解。
新建一个名为 my-calc
的目录作为项目目录,然后初始化 modules:
$ mkdir my-calc && cd my-calc
# 如果 go modules 默认没有开启,需要执行 export GO111MODULE=on 开启
$ go mod init my-calc
go: creating new go.mod: module my-calc
初始化完成后可以看到项目根目录下面多了一个 go.mod
的文件,现在我们还没有安装 cobra
库,执行下面的命令进行安装:
# 强烈推荐配置该环境变量
$ export GOPROXY=https://goproxy.cn
$ go get -u github.com/spf13/cobra/cobra
安装成功后,现在我们可以使用 cobra init
命令来初始化 CLI 应用的脚手架:
$ cobra init --pkg-name my-calc
Your Cobra applicaton is ready at
/Users/ych/devs/workspace/youdianzhishi/course/my-calc
需要注意的是新版本的 cobra 库需要提供一个 --pkg-name
参数来进行初始化,也就是指定上面我们初始化的模块名称即可。上面的 init 命令就会创建出一个最基本的 CLI 应用项目:
$ tree .
.
├── LICENSE
├── cmd
│ └── root.go
├── go.mod
├── go.sum
└── main.go
1 directory, 5 files
其中 main.go
是 CLI 应用的入口,在 main.go
里面调用好了 cmd/root.go
下面的 Execute
函数:
// main.go
package main
import "my-calc/cmd"
func main() {
cmd.Execute()
}
然后我们再来看下 cmd/root.go
文件。
2.3.1 rootCmd
root(根)命令是 CLI 工具的最基本的命令,比如对于我们前面使用的 go get URL
,其中 go
就是 root 命令,而 get
就是 go
这个根命令的子命令,而在 root.go
中就直接使用了 cobra 命令来初始化 rootCmd
结构,CLI 中的其他所有命令都将是 rootCmd
这个根命令的子命令了。
这里我们将 cmd/root.go
里面的 rootCmd
变量内部的注释去掉,并在 Run
函数里面加上一句 fmt.Println("Hello Cobra CLI")
:
var rootCmd = &cobra.Command{
Use: "my-calc",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello Cobra CLI")
},
}
这个时候我们在项目根目录下面执行如下命令进行构建:
$ go build -o my-calc
该命令会在项目根目录下生成一个名为 my-calc
的二进制文件,直接执行这个二进制文件可以看到如下所示的输出信息:
$ ./my-calc
Hello Cobra CLI
2.3.2 init
我们知道 init
函数是 Golang 中初始化包的时候第一个调用的函数。在 cmd/root.go
中我们可以看到 init
函数中调用了 cobra.OnInitialize(initConfig)
,也就是每当执行或者调用命令的时候,它都会先执行 init
函数中的所有函数,然后再执行 execute
方法。该初始化可用于加载配置文件或用于构造函数等等,这完全依赖于我们应用的实际情况。
在初始化函数里面 cobra.OnInitialize(initConfig)
调用了 initConfig
这个函数,所有,当 rootCmd
的执行方法 RUN: func
运行的时候,rootCmd
根命令就会首先运行 initConfig
函数,当所有的初始化函数执行完成后,才会执行 rootCmd
的 RUN: func
执行函数。
我们可以在 initConfig
函数里面添加一些 Debug 信息:
func initConfig() {
fmt.Println("I'm inside initConfig function in cmd/root.go")
...
}
然后同样重新构建一次再执行:
$ go build -o my-calc
$ ./my-calc
I'm inside initConfig function in cmd/root.go
Hello Cobra CLI
可以看到是首先运行的是 initConfig
函数里面的信息,然后才是真正的执行函数里面的内容。
为了搞清楚整个 CLI 执行的流程,我们在 main.go
里面也添加一些 Debug 信息:
// cmd/root.go
func init() {
fmt.Println("I'm inside init function in cmd/root.go")
cobra.OnInitialize(initConfig)
...
}
func initConfig() {
fmt.Println("I'm inside initConfig function in cmd/root.go")
...
}
// main.go
func main() {
fmt.Println("I'm inside main function in main.go")
cmd.Execute()
}
然后同样重新构建一次再执行:
$ go build -o my-calc
$ ./my-calc
I'm inside init function in cmd/root.go
I'm inside main function in main.go
I'm inside initConfig function in cmd/root.go
Hello Cobra CLI
根据上面的日志信息我们就可以了解到 CLI 命令的流程了。
init
函数最后处理的就是 flags
了,Flags
就类似于命令的标识符,我们可以把他们看成是某种条件操作,在 Cobra 中提供了两种类型的标识符:Persistent Flags
和 Local Flags
。
Persistent Flags
: 该标志可用于为其分配的命令以及该命令的所有子命令。Local Flags
: 该标志只能用于分配给它的命令。
2.3.3 initConfig
该函数主要用于在 home 目录下面设置一个名为 .my-calc
的配置文件,如果该文件存在则会使用这个配置文件。
// cmd/root.go
// initConfig 读取配置文件和环境变量
func initConfig() {
if cfgFile != "" {
// 使用 flag 标志中传递的配置文件
viper.SetConfigFile(cfgFile)
} else {
// 获取 Home 目录
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// 在 Home 目录下面查找名为 ".my-calc" 的配置文件
viper.AddConfigPath(home)
viper.SetConfigName(".my-calc")
}
// 读取匹配的环境变量
viper.AutomaticEnv()
// 如果有配置文件,则读取它
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
viper
是一个非常优秀的用于解决配置文件的 Golang 库,它可以从 JSON、TOML、YAML、HCL、envfile 以及 Java properties 配置文件中读取信息,功能非常强大,而且不仅仅是读取配置这么简单,了解更多相关信息可以查看 Git 仓库相关介绍:https://github.com/spf13/viper。
现在我们可以去掉前面我们添加的一些打印语句,我们已经创建了一个 my-calc
命令作为 rootCmd
命令,执行该根命令会打印 Hello Cobra CLI
信息,接下来为我们的 CLI 应用添加一些其他的命令。
2.3.4 添加数据
在项目根目录下面创建一个名为 add
的命令,Cobra
添加一个新的命令的方式为:cobra add <commandName>
,所以我们这里直接这样执行:
$ cobra add add
add created at /Users/ych/devs/workspace/youdianzhishi/course/my-calc
$ tree .
.
├── LICENSE
├── cmd
│ ├── add.go
│ └── root.go
├── go.mod
├── go.sum
├── main.go
└── my-calc
1 directory, 7 files
现在我们可以看到 cmd/root.go
文件中新增了一个 add.go
的文件,我们仔细观察可以发现该文件和 cmd/root.go
比较类似。首先是声明了一个名为 addCmd
的结构体变量,类型为 *cobra.Command
指针类型,*cobra.Command
有一个 RUN
函数,带有 *cobra.Command
指针和一个字符串切片参数。
然后在 init
函数中进行初始化,初始化后,将其添加到 rootCmd
根命令中 rootCmd.AddCommand(addCmd)
,所以我们可以把 addCmd
看成是 rootCmd
的子命令。
同样现在重新构建应用再执行:
$ go build -o my-calc
$ ./my-calc
Hello Cobra CLI
$ ./my-calc add
add called
可以看到 add
命令可以正常运行了,接下来我们来让改命令支持添加一些数字,我们知道在 RUN
函数中是用户字符串 slice 来作为参数的,所以要支持添加数字,我们首先需要将字符串转换为 int 类型,返回返回计算结果。
在 cmd/add.go
文件中添加一个名为 intAdd
的函数,定义如下所示:
// cmd/add.go
func intAdd(args []string) {
var sum int
// 循环 args 参数,循环的第一个值为 args 的索引,这里我们不需要,所以用 _ 忽略掉
for _, ival := range args {
// 将 string 转换成 int 类型
temp, err := strconv.Atoi(ival)
if err != nil {
panic(err)
}
sum = sum + temp
}
fmt.Printf("Addition of numbers %s is %d\n", args, sum)
}
然后在 addCmd
变量中,更新 RUN
函数,移除默认的打印信息,调用上面声明的 addInt
函数:
// addCmd
Run: func(cmd *cobra.Command, args []string) {
intAdd(args)
},
然后重新构建应用执行如下所示的命令:
$ go build -o my-calc
$ ./my-calc
Hello Cobra CLI
# 注意参数之间的空格
$ ./my-calc add 1 2 3
Addition of numbers [1 2 3] is 6
由于 RUN
函数中的 args
参数是一个字符串切片,所以我们可以传递任意数量的参数,但是确有一个缺陷,就是只能进行整数计算,不能计算小数,比如我们执行如下的计算就会直接 panic 了:
$ ./my-calc add 1 2 3.5
panic: strconv.Atoi: parsing "3.5": invalid syntax
goroutine 1 [running]:
my-calc/cmd.intAdd(0xc0000a5890, 0x3, 0x3)
......
因为在 intAdd
函数里面,我们只是将字符串转换成了 int,而不是 float32/64 类型,所以我们可以为 addCmd
命令添加一个 flag
标识符,通过该标识符来帮助 CLI 确定它是 int 计算还是 float 计算。
在 cmd/add.go
文件的 init
函数内部,我们创建一个 Bool 类型的本地标识符,命名成 float
,简写成 f
,默认值为 false。这个默认值是非常重要的,意思就是即使没有在命令行中调用 flag 标识符,该标识符的值就将为 false。
// cmd/add.go
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().BoolP("float", "f", false, "Add Floating Numbers")
}
然后创建一个 floatAdd
的函数:
func floatAdd(args []string) {
var sum float64
for _, fval := range args {
// 将字符串转换成 float64 类型
temp, err := strconv.ParseFloat(fval, 64)
if err != nil {
panic(err)
}
sum = sum + temp
}
fmt.Printf("Sum of floating numbers %s is %f\n", args, sum)
}
该函数和上面的 intAdd
函数几乎是相同的,除了是将字符串转换成 float64 类型。然后在 addCmd
的 RUN
函数中,我们根据传入的标识符来判断到底应该是调用 intAdd
还是 floatAdd
,如果传递了 --float
或者 -f
标志,就将会调用 floatAdd
函数。
// cmd/add.go
// addCmd
Run: func(cmd *cobra.Command, args []string) {
// 获取 float 标识符的值,默认为 false
fstatus, _ := cmd.Flags().GetBool("float")
if fstatus { // 如果为 true,则调用 floatAdd 函数
floatAdd(args)
} else {
intAdd(args)
}
},
现在重新编译构建 CLI 应用,按照如下方式执行:
$ go build -o my-calc
$ ./my-calc add 1 2 3
Addition of numbers [1 2 3] is 6
$ ./my-calc add 1 2 3.5 -f
Sum of floating numbers [1 2 3.5] is 6.500000
$./my-calc add 1 2 3.5 --float
Sum of floating numbers [1 2 3.5] is 6.500000
然后接下来我们在给 addCmd
添加一些子命令来扩展它。
2.3.5 添加偶数
同样在项目根目录下执行如下命令添加一个名为 even
的命令:
$ cobra add even
even created at /Users/ych/devs/workspace/youdianzhishi/course/my-calc
和上面一样会在 root
目录下面新增一个名为 even.go
的文件,修改该文件中的 init
函数,将 rootCmd
修改为 addCmd
,因为我们是为 addCmd
添加子命令:
// cmd/even.go
func init() {
addCmd.AddCommand(evenCmd)
}
然后更新 evenCmd
结构体参数的 RUN
函数:
// cmd/even.go
Run: func(cmd *cobra.Command, args []string) {
var evenSum int
for _, ival := range args {
temp, _ := strconv.Atoi(ival)
if temp%2 == 0 {
evenSum = evenSum + temp
}
}
fmt.Printf("The even addition of %s is %d\n", args, evenSum)
},
首先将字符串转换成整数,然后判断如果是偶数才进行累加。然后重新编译构建应用:
$ go build -o my-calc
$ ./my-calc add even 1 2 3 4 5 6
The even addition of [1 2 3 4 5 6] is 12
my-calc
是我们的根命令,add
是 rootCmd
的子命令,even
优势 addCmd
的子命令,所以按照上面的方式调用。可以用同样的方式再去添加一个奇数相加的子命令。
到这里我们就在 Golang 里面使用 Cobra
创建了一个简单的 CLI 应用。本文的内容虽然比较简单,但是是我们了解学习 Cobra
基础的一个很好的入门方式,后续我们也可以尝试添加一些更加复杂的使用案例。
以上就是对 cobra 库的入门使用,那么接下来我们需要对 docker 操作就需要使用到 docker 官方的 sdk
3 docker API 的 go 客户端
https://pkg.go.dev/github.com/docker/docker/client#section-readme
通过下面这个例子其实我们在二次开发一个东西的时候,需要对该服务本身的功能做了解,下面这个范例只是为了演示 go 的第三方包如何学习的一个过程
3.1 列出当前所有容器信息
可以通过 docker 的 sdk 实现对容器的列出
package main
import (
"context"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
func main() {
// 定义 docker 的 client
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
// 列出容器信息,返回一个切片
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
panic(err)
}
// 遍历切片输出容器id 和使用的镜像
for _, container := range containers {
fmt.Printf("%s %s\n", container.ID[:10], container.Image)
}
}
执行
[16:21:13 root@k8s-master ~]#./testdocker
container.ID=f14c5124c0,image=sha256:2297da0160d57032124efb335e38c52e20d252c5c494dbd8c88a9a2d02b41654
container.ID=aa0d42b638,image=sha256:181172b235b225c5644edae46296bd7c2305c05113e89a9b4274a952546743f4
container.ID=9202f8d8f9,image=sha256:296a6d5035e2d6919249e02709a488d680ddca91357602bd65e605eac967b899
container.ID=6d10a00ad5,image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.4.1
container.ID=63fee7086d,image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.4.1
container.ID=e6eadfccb8,image=sha256:296a6d5035e2d6919249e02709a488d680ddca91357602bd65e605eac967b899
container.ID=949870c1c0,image=sha256:ef4bce0a7569b4fa83a559717c608c076a2c9d30361eb059ea4e1b7a55424d68
3.2 创建容器
package main
import (
"context"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
)
func main() {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
// 创建容器
// context.TODO() 在我们不知道传递什么上下文的时候可以使用context.TODO()
body, err := cli.ContainerCreate(context.TODO(), &container.Config{
Tty: true,
OpenStdin: true,
Image: "nginx:1.16.1", // 该容器使用的镜像
}, &container.HostConfig{
// 容器端口 80 tcp 协议,暴露至宿主机的 8082,容器名为 testnginx
PortBindings: nat.PortMap{nat.Port("80/tcp"): []nat.PortBinding{{"0.0.0.0", "8082"}}},
}, nil, nil, "testnginx2")
if err != nil {
panic(err)
}
fmt.Println(body, err)
// 通过 body 获取到容器 id
containerID := body.ID
// 启动容器
err = cli.ContainerStart(context.TODO(), containerID, types.ContainerStartOptions{})
fmt.Println(err)
}
执行程序
[10:09:34 root@k8s-master ~]#./testdocker
{5aa50b5633a8c5fb510c687a94de29cced803f292cb5cb1495c4a806107e5edf []} <nil>
# 通过过滤查看该容器已经创建并启动
[10:09:39 root@k8s-master ~]#docker ps | grep testnginx
5aa50b5633a8 nginx:1.16.1 "nginx -g 'daemon of…" 13 seconds ago Up 11 seconds 127.0.0.1:8082->80/tcp testnginx2
3.2.1 验证
浏览器验证该容器已经被创建
但是由于我们还需要在 docker 容器进行操作,比如对容器内部的数据进行增删改查所以这个时候不能够用 os.exec 包,但是我们可以使用 dexec
4 如何在 docker 容器中执行命令
上面我已经讲过了如何创建容器,那么接下来我就需要带着大家演示如何在容器中执行命令,以便后续需要在容器中执行对应操作
1 启动 nginx 容器作为测试
$ docker run --name=nginx -d -p 4030:80 nginx
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e738187f1777 nginx "/docker-entrypoint.…" 3 seconds ago Up 1 second 0.0.0.0:4030->80/tcp, :::4030->80/tcp nginx
2 编写程序
package main
import (
"context"
"log"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
docker "github.com/fsouza/go-dockerclient"
)
// 获取容器 ID
func CodID() string {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatal("cli:", err)
}
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
log.Fatal(err)
}
cID := ""
// 获取需要执行命令的容器
for _, v := range containers {
for _, Cname := range v.Names {
if Cname == "/nginx" {
cID = v.ID
}
}
}
return cID
}
// 容器内执行命令
func CodExec() {
cl, err := docker.NewClient("unix:///run/docker.sock")
if err != nil {
log.Fatal("cl", err)
}
// 在容器中执行的命令
command := []string{"bash", "-c", "mkdir /root/test"}
exec, err := cl.CreateExec(docker.CreateExecOptions{
AttachStderr: true,
AttachStdin: false,
AttachStdout: true,
Tty: false,
Cmd: command,
Container: CodID(),
})
startOpts := docker.StartExecOptions{
Tty: true,
RawTerminal: true,
Detach: false,
ErrorStream: os.Stderr,
InputStream: os.Stdin,
OutputStream: os.Stdout,
}
err = cl.StartExec(exec.ID, startOpts)
if err != nil {
log.Fatal("startExec err", err)
}
}
func main() {
CodExec()
}
3 运行程序
root@consul-3:~/go/src/2022/testdocker# go run main.go
4 进入容器验证
root@consul-3:~/go/src/kube# docker exec -it nginx /bin/bash
root@e738187f1777:/# ls -l /root/
total 4
drwxr-xr-x 2 root root 4096 Jun 6 12:59 test # 创建成功
5 编写完整项目
现在我们已经知道如何在通过 go 程序来创建容器以及如何使用 cobra 工具、以及如何在容器中执行命令,那么我们就可以上手编写完整的代码
项目地址:https://github.com/As9530272755/kubevw
5.1 编写创建容器代码
1 创建项目
$ mkdir kube && cd kube
$ go mod init kube
go: creating new go.mod: module kube
$ cobra init --pkg-name kube
2 创建 create 参数
$ cobra add create
3 编写代码
// Copyright © 2022 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"fmt"
"log"
"os/exec"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/spf13/cobra"
)
// createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "Parameter to create a container (创建容器参数)",
Long: `example: kubevw create Name image HostPort
示例: create 容器名 容器使用镜像 宿主机映射端口
-f : Copy the host /host/kubeconfig directory as the /root/config file of the container when creating the container
(创建容器时将主机 /host/kubeconfig 目录拷贝为容器的 /root/config 文件)
example: kubecmd create Name image HostPort -f /host/kubeconfig
`,
Run: func(cmd *cobra.Command, args []string) {
file, _ := cmd.Flags().GetBool("file")
if file {
if len(args) == 0 || len(args) > 5 {
fmt.Println(`Command syntax error:
kubevw create -h view help`)
}
createCOD_copyfile(args)
} else {
if len(args) == 0 || len(args) > 3 {
fmt.Println(`Command syntax error:
kubevw create -h view help`)
}
CreateCOD(args)
}
},
}
func init() {
RootCmd.AddCommand(createCmd)
createCmd.Flags().BoolP("file", "f", false, "Copy config file to container ")
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// createCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// createCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
func cod(args []string) {
NAME := args[0]
image := args[1]
port := args[2]
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
body, err := cli.ContainerCreate(context.TODO(), &container.Config{
Tty: true,
OpenStdin: true,
Image: image,
}, &container.HostConfig{
PortBindings: nat.PortMap{nat.Port("22/tcp"): []nat.PortBinding{{"0.0.0.0", port}}},
}, nil, nil, NAME)
if err != nil {
panic(err)
}
containerID := body.ID
err = cli.ContainerStart(context.TODO(), containerID, types.ContainerStartOptions{})
fmt.Printf("%s Container Created Successfully!\n", NAME)
}
// 单独创建容器不 copy config 文件到
func CreateCOD(args []string) {
cod(args)
}
// 拷贝 config 文件到容器中
func createCOD_copyfile(args []string) {
cod(args)
NAME := args[0]
hostPath := args[3]
dockerCP := fmt.Sprintf("docker cp %s %s:/root/config", hostPath, NAME)
// fmt.Println(dockerCP)
// dockerCP := fmt.Sprintf("docker cp /root/config test:/root")
cmd := exec.Command("/bin/bash", "-c", dockerCP)
err := cmd.Run()
if err != nil {
log.Fatal(err)
return
}
}
5.2 编写容器执行命令代码
1 创建 cmd 参数
$ cobra add cmd
2 编写代码
// Copyright © 2022 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"fmt"
"log"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
docker "github.com/fsouza/go-dockerclient"
"github.com/spf13/cobra"
)
// adduserCmd represents the adduser command
var cmdCmd = &cobra.Command{
Use: "cmd",
Short: "Execute the corresponding command operation in the container (在容器中执行对应命令操作)",
Long: `example: kubevw cmd ContainerName CMD
Currently, the supported commands are:
udd (create user named after container)
kdir (prerequisite: you must first execute the UDD command to create the.Kube directory in the home directory)
mv (Prerequisite: the UDD command must be executed first. By default, the config file is moved to the business manager directory/ In Kube)
da (prerequisite: the UDD command must be executed first to authorize the business administrator file)
kcu (precondition: UDD, kdir, Da commands must be executed first, and k8s context switching is required).mmand must be executed,Create in home directory Kube directory)
目前支持的命令有:
udd(创建以容器命名的用户)
kdir(前提条件:必须先执行 udd 命令,在家目录中创建 .kube 目录)
mv (前提条件:必须先执行 udd 命令,默认将 config 文件移动到业务管理家目录 ./kube 中)
da(前提条件:必须先执行 udd 命令,授权业务管理员文件)
kcu(前提条件:必须先执行 udd、kdir、da 命令,k8s上下文切换)。
`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
fmt.Println(`Command syntax error:
kubevw cmd -h view help`)
} else {
CodExec(args)
}
},
}
func init() {
RootCmd.AddCommand(cmdCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// adduserCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// adduserCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
// 获取容器 ID
func CodID(name string) string {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatal("cli:", err)
}
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
log.Fatal(err)
}
cname := fmt.Sprintf("/" + name)
cID := ""
// 获取需要执行命令的容器
for _, v := range containers {
for _, Cname := range v.Names {
if Cname == cname {
cID = v.ID
}
}
}
return cID
}
// 容器内执行命令
func CodExec(agrs []string) {
cname := agrs[0]
cmd := agrs[1]
if cmd == "udd" {
cmd = fmt.Sprintf("useradd -s /bin/bash -m %s", cname)
}
if cmd == "kdir" {
cmd = fmt.Sprintf("mkdir /home/%s/.kube/", cname)
}
if cmd == "da" {
cmd = fmt.Sprintf("chown -R %s.%s /home/%s/", cname, cname, cname)
}
if cmd == "mv" {
cmd = fmt.Sprintf("mv /root/config /home/%s/.kube/config", cname)
}
if cmd == "kcu" {
cmd = fmt.Sprintf("kubectl config use-context kubernetes --kubeconfig=/home/%s/.kube/config ", cname)
}
cl, err := docker.NewClient("unix:///run/docker.sock")
if err != nil {
log.Fatal("cl", err)
}
command := []string{"bash", "-c", cmd}
exec, err := cl.CreateExec(docker.CreateExecOptions{
AttachStderr: true,
AttachStdin: false,
AttachStdout: true,
Tty: false,
Cmd: command,
Container: CodID(cname),
})
startOpts := docker.StartExecOptions{
Tty: true,
RawTerminal: true,
Detach: false,
ErrorStream: os.Stderr,
InputStream: os.Stdin,
OutputStream: os.Stdout,
}
err = cl.StartExec(exec.ID, startOpts)
if err != nil {
log.Fatal("startExec err", err)
}
}
6 工具验证
接下来开始验证代码
1 构建代码为二进制程序
$ go build -o kubevw
2 查看帮助
root@consul-3:~/go/src/kube# ./kubevw -h
The tool provides the following functions:
Automatically create containers and automatically obtain k8s certificates.
Enables a single business administrator to access and operate ns.
该工具提供如下功能:
自动创建容器并实现自动化获取 K8S 证书。
实现单个业务管理员对 NS 访问以及操作。
Usage:
kubevw [command]
Available Commands:
cmd Execute the corresponding command operation in the container (在容器中执行对应命令操作)
completion Generate the autocompletion script for the specified shell
create Parameter to create a container (创建容器参数)
help Help about any command
linkkube Parameters for obtaining k8s certificate implementation link
Flags:
--config string config file (default is $HOME/.kube.yaml)
-h, --help help for kubevw
-t, --toggle Help message for toggle
Use "kubevw [command] --help" for more information about a command.
3 查看 create 参数帮助
root@consul-3:~/go/src/kube# ./kubevw create -h
example: kubevw create Name image HostPort
示例: create 容器名 容器使用镜像 宿主机映射端口
-f : Copy the host /host/kubeconfig directory as the /root/config file of the container when creating the container
(创建容器时将主机 /host/kubeconfig 目录拷贝为容器的 /root/config 文件)
example: kubecmd create Name image HostPort -f /host/kubeconfig
Usage:
kubevw create [flags]
Flags:
-f, --file Copy config file to container
-h, --help help for create
Global Flags:
--config string config file (default is $HOME/.kube.yaml)
3 查看 cmd 参数帮助
root@consul-3:~/go/src/kube# ./kubevw cmd -h
example: kubevw cmd ContainerName CMD
Currently, the supported commands are:
udd (create user named after container)
kdir (prerequisite: you must first execute the UDD command to create the.Kube directory in the home directory)
mv (Prerequisite: the UDD command must be executed first. By default, the config file is moved to the business manager directory/ In Kube)
da (prerequisite: the UDD command must be executed first to authorize the business administrator file)
kcu (precondition: UDD, kdir, Da commands must be executed first, and k8s context switching is required).mmand must be executed,Create in home directory Kube directory)
目前支持的命令有:
udd(创建以容器命名的用户)
kdir(前提条件:必须先执行 udd 命令,在家目录中创建 .kube 目录)
mv (前提条件:必须先执行 udd 命令,默认将 config 文件移动到业务管理家目录 ./kube 中)
da(前提条件:必须先执行 udd 命令,授权业务管理员文件)
kcu(前提条件:必须先执行 udd、kdir、da 命令,k8s上下文切换)。
Usage:
kubevw cmd [flags]
Flags:
-h, --help help for cmd
Global Flags:
--config string config file (default is $HOME/.kube.yaml)
4 创建容器并拷贝文件至容器中
# 创建
root@consul-3:~/go/src/kube# ./kubevw create test kubectl-cmd:v3 1111 -f /root/test.config
test Container Created Successfully!
# 查看容器创建成功
root@consul-3:~/go/src/kube# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d19c50c4c99b kubectl-cmd:v3 "/bin/sh -c '/usr/sb…" 11 seconds ago Up 10 seconds 0.0.0.0:1111->22/tcp test
# 进入容器查看文件是否拷贝进去
root@consul-3:~/go/src/kube# docker exec -it test /bin/bash
root@d19c50c4c99b:/# ll /root/config
-rw------- 1 root root 4152 Mar 31 09:20 /root/config
5 退出容器执行 cmd 参数功能验证
root@d19c50c4c99b:/# exit
# 执行 cmd 参数使用默认命令
root@consul-3:~/go/src/kube# ./kubevw cmd test udd
root@consul-3:~/go/src/kube# ./kubevw cmd test kdir
root@consul-3:~/go/src/kube# ./kubevw cmd test mv
root@consul-3:~/go/src/kube# ./kubevw cmd test da
root@consul-3:~/go/src/kube# ./kubevw cmd test kcu
6 进入容器查看对应默认参数是否执行成功
# 进入容器
root@consul-3:~/go/src/kube# docker exec -it test /bin/bash
# 查看 test id
root@d19c50c4c99b:/# id test
uid=1000(test) gid=1000(test) groups=1000(test)
# 查看 test 目录已经创建 .kube 文件
root@d19c50c4c99b:/# ll /home/test/
total 24
drwxr-xr-x 3 test test 4096 Jun 6 14:56 ./
drwxr-xr-x 1 root root 4096 Jun 6 14:56 ../
-rw-r--r-- 1 test test 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 test test 3771 Apr 4 2018 .bashrc
drwxr-xr-x 2 test test 4096 Jun 6 14:56 .kube/
-rw-r--r-- 1 test test 807 Apr 4 2018 .profile
# 查看 config 已经移动
root@d19c50c4c99b:/# ll /home/test/.kube/
total 16
drwxr-xr-x 2 test test 4096 Jun 6 14:56 ./
drwxr-xr-x 3 test test 4096 Jun 6 14:56 ../
-rw------- 1 test test 4152 Mar 31 09:20 config
7 访问 K8S 验证
7.1 UA 方式实现 docker 远程调用 kubectl 命令
步骤如下:
先在 Linux 系统中创建一个对应的 user,我们在 yaml 文件中指定的是 devuser 所以我们就要创建一个 devuser 用户,但是这个用户现在肯定是访问不了我们的 K8S 集群的。
但是想让 devuser 这个用户对我们的 K8S 集群进行访问的话就需要给 devuser 创建出来一个证书信息。
7.1.1 在 master 节点操作
1、创建 devuser 用户的证书存储目录
# 创建一个 cert 目录用来存储我们的证书
[15:58:12 root@master-1 ~]#mkdir /cert
# 并且在这个 cert 目录下创建一个 devuser 的目录
[16:03:19 root@master-1 ~]#mkdir /cert/devuser
2、创建一个证书请求文件。这是一个 json 格式因为我们等会要用到一个 cfssl
的这么一个工具创建出来证书。
# 现在创建一个证书请求文件,当然这个文件可以随便命名,我这里就给他命名为 devuser-csr.json
[16:04:02 root@master-1 ~]#vim /cert/devuser/devuser-csr.json
{
"CN": "devuser",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"name": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}
3、下载 cfssl
工具,证书生成工具,并给这几个工具软件加上所有权限
# 进入到 /usr/local/bin 目录下
[16:12:05 root@master-1 ~]#cd /usr/local/bin/
# 下载 cfssl 工具
[16:13:56 root@master-1 bin]#wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
# 下载 cfssljson 工具
[16:16:02 root@master-1 bin]#wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
# 下载 cfssl 证书信息工具
[16:17:18 root@master-1 bin]#wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
# 加上所有权限
[16:31:40 root@master-1 bin]#chmod a+x *
4、到/etc/kubernetes/pki/
目录下因为这个目录下全是 K8S 的证书信息
[16:47:39 root@master-1 ~]#cd /etc/kubernetes/pki/
5、通过cfssl
工具创建,指定我们 CA 的证书、指定我们 CA 私钥,以及通过/cert/devuser/devuser-csr.json
文件创建
[16:49:26 root@master-1 pki]#/usr/local/bin/cfssl_linux-amd64 gencert -ca=ca.crt -ca-key=ca.key -profile=kubernetes /cert/devuser/devuser-csr.json | /usr/local/bin/cfssljson_linux-amd64 -bare devuser
# 通过 ls 查看 devuser 的所有信息就会出现它的私钥公钥还有 pem 证书
[16:49:53 root@master-1 pki]#ls devuser*
devuser.csr devuser-key.pem devuser.pem
6、这样的话我们 devuser 用户的证书和证书请求就已经创建完毕了,接下来就要为我们的 K8S 集群去设置认证信息了。
# 进入 /cert/devuser/ 目录下,因为等会要生成一些缓存信息
[16:54:34 root@master-1 pki]#cd /cert/devuser/
# 进行 ip 加端口声明,这个 IP 是我们 kubernetes 集群的 ip 和 K8S 的 6443 服务端口
[16:54:34 root@master-1 devuser]#export KUBE_APISERVER="https://172.16.18.188:6443"
# 声明完成之后我们就使用 kubectl config 设置我们的集群
[17:10:36 root@master-1 devuser]#kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=devuser.kubeconfig
7、我通过在当前目录下 ls 查看就会出现一个 kubeconfig 后缀的文件
[17:11:07 root@master-1 devuser]#ls
devuser-csr.json devuser.kubeconfig
8、通过查看devuser.kubeconfig
[17:12:02 root@master-1 devuser]#cat devuser.kubeconfig
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01Ea3dNakUwTkRFd00xb1hEVE13TURnek1URTBOREV3TTFvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTWpFCjNrTlNpZTdZcTJ6dHh5TWxsMXQ4bzBDSFNucmhMREd2dGswVFkrNFpqVHE0MytVMTZPRXNkRlRXa3FzcUNKaWMKU1ozdXB0eHIvRy8xYVdhV1lpd3VqL0M2RDQwOEl2U1VCWWRveWhIeHBXTHU4K1dPd2pWK1FSd2tTS0FiamQrNgo2T2dWbXVvZHZEcjhRY2xWbktmVWlMWnpDUm55NE1ieE1MdStIUW5aWXBZRWo5WVJkTXZ6NTdhYUJXdEdNNjJYCnFCMHozMW4zaEVrZVlZS2cxalZuRGpKMmNIOGtpay9sN1dJcEhJdU5UNUJsQ2EzanZhZkF0WkptRzFNK0laeFQKcnBVYVZ4VXpYUGJLQWJDZ3VOQWVyQVUwY1ZsQzJaZ1ZnUjJhbnplYVRaWE9iSUxkV3V1MjltRW1mNnppaG9FZwp1ejJNMGJTYU1rWDNRTXgrU2UwQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFER1Zsb1lvc2RhdDNnRHBCOGlIcmk5clV4MUcKd2pDMVR1ZjlsdEFkenpEZzV3Zm1oc0NkR2E5Q2xOa0Y1Q0Z5RWxuUVBkazVQMmIwK255aU03UHM2YzBBcGt0WAo4dTN0THJVOERrWkRhUTJuMXY1Q0kzSllpb3p4RlNCc2ZFaktuelprNVg2TStYUjRjVHIvTHlkODMxbTlrbGpKCmpvYmJ3dllTK3FkVHFLZ09PQjYzVnZZcnhWUHZOZ0NmaExHM2ZGMTByU2NqTHlkaXpZNnhTZWJjSDIzYnVqelYKRGsyNHo2dTBpdnl0NmJubmhIKzZVK0NaRXF0dHMwNUx0MEVxenZRTWdOeGNGajlhNmFZbU1GNHJRQTdERi9yYgorNTQydzRNSHlVYUxaTlJDZ2Y2c0ZWbk55NkJISzhISW5HT0Z2bENzbmtFS2VhNHYwTUVZVTBjUGVzdz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= # ca.crt后的内容
server: https://172.16.18.188:6443 # 当前集群的信息
name: kubernetes # 以及集群的名称
contexts: null
current-context: ""
kind: Config
preferences: {}
users: null
9、设置客户端认证参数
[17:17:08 root@master-1 devuser]#kubectl config set-credentials devuser \
--client-certificate=/etc/kubernetes/pki/devuser.pem \
--client-key=/etc/kubernetes/pki/devuser-key.pem \
--embed-certs=true \
--kubeconfig=devuser.kubeconfig
10、 再次查看devuser.kubeconfig
文件就会多了一些客户的信息,有用户的证书和私钥信息
[17:23:15 root@master-1 devuser]#cat devuser.kubeconfig
11、设置上下文参数,上下文的含义就是帮我们去绑定至某一个名称空间
[17:23:55 root@master-1 devuser]#kubectl config set-context kubernetes \
--cluster=kubernetes \
--user=devuser \
--namespace=dev \
--kubeconfig=devuser.kubeconfig
# 命令解释
kubectl config set-context kubernetes \
--cluster=kubernetes \ # 集群为 kubernetes 集群
--user=devuser \ # 用户名 devuers
--namespace=dev \ # 绑定至 dev 这个名称空间下
--kubeconfig=devuser.kubeconfig # 并且写入到devuser.kubeconfig文件中
12、创建一个 dev 的名称空间
[17:31:47 root@master-1 devuser]#kubectl create namespace dev
13、进行所谓的 role binding ,在我们的集群中默认就有了一个 cluster role 的角色就是我们的 admin ,这个 admin 的含义就是在我们的 kubernetes 中可以为所欲为。那我们将 admin 的 cluster role 进行 role binding 也就是下放层级,绑定至 dev 的名称空间。代表的含义就是 devuser 这个角色可以在 dev 的名称空间下为所欲为。
[20:32:30 root@master-1 devuser]# kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev
# 命令解释
kubectl create rolebinding: # 创建一个 rolebinding
devuser-admin-binding: # rolebinding 的名称为 devuser
--clusterrole=admin: # clusterrole 的名称
--user=devuser: # 绑定的用户名
--namespace=dev: # 以及放在我们的 dev namespace 中
14、将 devuser.kubeconfig 拷贝至远端节点
# 切换至 devuser 用户
[20:32:30 root@master-1 devuser]# scp devuser.kubeconfig 10.0.0.134:/root/kubecmd
7.2 在 go 节点上操作
1 创建以 devuser 命名的容器
root@consul-3:~/go/src/kube# ./kubevw create devuser kubectl-cmd:v3 1111 -f /root/kubecmd/devuser.kubeconfig
devuser Container Created Successfully!
2 执行 kubevw 命令的 cmd 参数
root@consul-3:~/go/src/kube# ./kubevw cmd devuser udd
root@consul-3:~/go/src/kube# ./kubevw cmd devuser kdir
root@consul-3:~/go/src/kube# ./kubevw cmd devuser mv
root@consul-3:~/go/src/kube# ./kubevw cmd devuser da
root@consul-3:~/go/src/kube# ./kubevw cmd devuser kcu
Switched to context "kubernetes".
7.3 进入容器验证
root@consul-3:~/go/src/kube# docker exec -it devuser /bin/bash
root@134d8159fcf3:/# su - devuser
devuser@134d8159fcf3:~$ kubectl get pod
NAME READY STATUS RESTARTS AGE
mysite-mariadb-0 0/1 Pending 0 45d
mysite-wordpress-5577557cf4-hqh4b 0/1 Pending 0 45d