前端: Vue+element plus
后端: go+gin
GOPROXY=https://goproxy.cn
go get k8s.io/client-go/tools/clientcmd
go get k8s.io/api/core/v1
go get k8s.io/apimachinery/pkg/apis/meta/v1
go get github.com/gin-gonic/gin
go get github.com/wonderivan/logger
go get gorm.io/gorm
go get gorm.io/driver/sqlite
go get gorm.io/driver/mysql
目录名 | 作用 |
---|---|
config | 定义全局配置,如:监听地址,管理员账号等 |
controller | controller层,定义路由规则及接口入参和相应 |
service | 服务层,处理接口的业务逻辑 |
dao | 数据库操作,包含数据的增删改查 |
model | 定义数据库表的字段 |
db | 用于初始化数据库连接及配置 |
middle | 中间件层,添加全局的逻辑处理,如跨域,jwt验证等 |
utils | 工具目录,定义常用工具,如token解析,文件操作等 |
go.mod | 定义项目的依赖包以及版本 |
main.go | 项目主入口,main函数 |
D:\golang\k8s-plantform> mkdir controller config service dao db model middle utils
D:\golang\k8s-plantform>tree /f
卷 新加卷 的文件夹 PATH 列表
卷序列号为 7463-6B24
D:.
│ go.mod
│ go.sum
│ main.go
│
├─.idea
│ .gitignore
│ k8s-plantform.iml
│ modules.xml
│ watcherTasks.xml
│ workspace.xml
│
├─config
│ config
│
├─controller
├─dao
├─db
├─middle
├─model
├─service
└─utils
controller/router.go
// 初始化router类型的对象,首字母大写,用于跨包调用
var Router router
// 声明一个router的结构体
type router struct{}
func (r *router) InitApiRouter(router *gin.Engine) {
router.GET("/", Index)
}
func Index(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"code": 200,
"msg": "In index",
})
}
config/config.go
package config
const (
ListenAddr = "0.0.0.0:9090"
)
main.go
func main() {
// 初始化gin
r := gin.Default()
controller.Router.InitApiRouter(r)
// gin 程序启动
//r.Run(config.ListenAdd)
r.Run(config.ListenAddr)
}
启动服务后可以正常访问到url
root@harbor-1:~# curl 192.168.31.1:9090
{"code":200,"msg":"In index"}
service/init.go
import (
"k8s-plantform/config"
"github.com/wonderivan/logger"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
// 用于初始化k8s client
var K8s k8s
type k8s struct {
ClientSet *kubernetes.Clientset
}
// 初始化k8s
func (k *k8s) Init() {
// 将kuberconfig文件转换成rest.config对象
conf, err := clientcmd.BuildConfigFromFlags("", config.KubeConfig)
if err != nil {
panic("获取K8s配置失败:" + err.Error())
} else {
logger.Info("获取K8s配置 成功!")
}
// 根据rest.config对象,new一个clientset出来
clientset, err := kubernetes.NewForConfig(conf)
if err != nil {
panic("创建K8s client失败:" + err.Error())
} else {
logger.Info("创建K8s client 成功!")
}
k.ClientSet = clientset
}
config/config.go
const (
ListenAddr = "0.0.0.0:9090"
KubeConfig = "D:\\golang\\k8s-plantform\\config\\config"
)
main.go
func main() {
// 初始化k8s client
service.K8s.Init() // <<-----这行 可以使用service.K8s.clientset 进行跨包调用
// 初始化gin
r := gin.Default()
controller.Router.InitApiRouter(r)
// gin 程序启动
//r.Run(config.ListenAdd)
fmt.Println("http://192.168.31.1:9090/")
r.Run(config.ListenAddr)
}
重启服务没有报错就是成功
service/dataselector.go用来处理数组的排序,过滤,分页
// dataselector 用于排序,过滤,分页的数据类型
type dataSelector struct {
GenericDataList []DataCell
DataSelect *DataSelectQuery
}
// DataCell 接口,用于各种资源List的类型转换,转换后可以使用dataselector的排序,过滤,分页方法
type DataCell interface {
GetCreation() time.Time
GetName() string
}
// DataSelectQuery 定义过滤和分页的结构体,过滤:Name 分页:Limit和Page
type DataSelectQuery struct {
Filter *FilterQuery
Paginate *PaginateQuery
}
// FilterQuery 用于查询 过滤:Name
type FilterQuery struct {
Name string
}
// 分页:Limit和Page Limit是单页的数据条数,Page是第几页
type PaginateQuery struct {
Page int
Limit int
}
// 实现自定义的排序方法,需要重写Len,Swap,Less方法
// Len用于获取数组的长度
func (d *dataSelector) Len() int {
return len(d.GenericDataList)
}
// Swap用于数据比较大小后的位置变更
func (d *dataSelector) Swap(i, j int) {
d.GenericDataList[i], d.GenericDataList[j] = d.GenericDataList[j], d.GenericDataList[i]
}
// Less用于比较大小
func (d *dataSelector) Less(i, j int) bool {
return d.GenericDataList[i].GetCreation().Before(d.GenericDataList[j].GetCreation())
}
// 重写以上三个方法,用sort.Sort 方法触发排序
func (d *dataSelector) Sort() *dataSelector {
sort.Sort(d)
return d
}
// Filter方法用于过滤,比较数据Name属性,若包含则返回
func (d *dataSelector) Filter() *dataSelector {
if d.DataSelect.Filter.Name == "" {
return d
}
filtered := []DataCell{}
for _, value := range d.GenericDataList {
// 定义是否匹配的标签变量,默认是匹配的
matches := true
objName := value.GetName()
if !strings.Contains(objName, d.DataSelect.Filter.Name) {
matches = false
continue
}
if matches {
filtered = append(filtered, value)
}
}
d.GenericDataList = filtered
return d
}
// Paginate 分页,根据Limit和Page的传参,取一定范围内的数据返回
func (d *dataSelector) Paginate() *dataSelector {
limit := d.DataSelect.Paginate.Limit
page := d.DataSelect.Paginate.Page
//验证参数合法,若参数不合法,则返回所有数据
if limit <= 0 || page <= 0 {
return d
}
//举例:25个元素的数组,limit是10,page是3,startIndex是20,endIndex是30(实际上endIndex是25)
startIndex := limit * (page - 1)
endIndex := limit * page
//处理最后一页,这时候就把endIndex由30改为25了
if len(d.GenericDataList) < endIndex {
endIndex = len(d.GenericDataList)
}
d.GenericDataList = d.GenericDataList[startIndex:endIndex]
return d
}
定义podCell类型,实现DataCell接口用于类型转换
// 定义podCell, 重写GetCreation和GetName 方法后,可以进行数据转换
// covev1.Pod --> podCell --> DataCell
// appsv1.Deployment --> deployCell --> DataCell
type podCell corev1.Pod
// 重写DataCell接口的两个方法
func (p podCell) GetCreation() time.Time {
return p.CreationTimestamp.Time
}
func (p podCell) GetName() string {
return p.Name
}
service/pod.go
var Pod pod
// 定义列表的返回内容,Items是pod元素列表,Total是元素数量
type PodsResp struct {
Total int `json:"total"`
Items []corev1.Pod `json:"items"`
}
type pod struct{}
// 获取pod列表,支持过滤,排序,分页
func (p *pod) GetPods(filterName, namespace string, limit, page int) (podsResp *PodsResp, err error) {
//context.TODO() 用于声明一个空的context上下文,用于List方法内设置这个请求超时
//metav1.ListOptions{} 用于过滤List数据,如label,field等
podList, err := K8s.ClientSet.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
logger.Info("获取Pod列表失败," + err.Error())
// 返回给上一层,最终返回给前端,前端捕获到后打印出来
return nil, errors.New("获取Pod列表失败," + err.Error())
}
// 实例化dataselector结构体,组装数据
selectableData := &dataselector{
GenericDataList: p.toCells(podList.Items),
DataSelect: &DataSelectQuery{
Filter: &FilterQuery{Name: filterName},
Paginate: &PaginateQuery{
Limit: limit,
Page: page,
},
},
}
// 先过滤
filtered := selectableData.Filter()
// 这句搞错了.第一次写成了total := len(data.GenericDataList)结果到前端获取清单时就出问题了.
total := len(filtered.GenericDataList)
// 排序和分页
data := filtered.Sort().Paginate()
// 将DataCell类型转成Pod
pods := p.fromCells(data.GenericDataList)
return &PodsResp{
Total: total,
Items: pods,
},nil
}
// 类型转换方法corev1.Pod --> DataCell,DataCell-->corev1.Pod
func (p *pod) toCells(pods []corev1.Pod) []DataCell {
cells := make([]DataCell, len(pods))
for i := range pods {
cells[i] = podCell(pods[i])
}
return cells
}
func (p *pod) fromCells(cells []DataCell) []corev1.Pod {
pods := make([]corev1.Pod, len(cells))
for i := range cells {
// cells[i].(podCell)是将DataCell类型转换成podCell
pods[i] = corev1.Pod(cells[i].(podCell))
}
return pods
}
获取pod列表,支持分页,过滤,排序
controller/pod.go
package controller
import (
"k8s-plantform/service"
"net/http"
"github.com/gin-gonic/gin"
"github.com/wonderivan/logger"
)
var Pod pod
type pod struct{}
// 获取pod列表,支持分页,过滤,排序
func (p *pod) GetPods(ctx *gin.Context) {
// 处理入参
// 匿名结构体用于定义入参,get请求为form格式,其他为json格式
params := new(struct {
FilterName string `form:"filter_name"`
Namespace string `form:"namespace"`
Limit int `form:"limit"`
Page int `form:"page"`
})
// form 格式使用Bind方法,json格式使用ShouldBindJson方法
if err := ctx.Bind(params); err != nil {
logger.Error("Bind绑定参数失败," + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": "Bind绑定参数失败," + err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPods(params.FilterName, params.Namespace, params.Limit, params.Page)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod列表成功",
"data": data,
})
}
import (
"github.com/gin-gonic/gin"
)
// 初始化router类型的对象,首字母大写,用于跨包调用
var Router router
// 声明一个router的结构体
type router struct{}
func (r *router) InitApiRouter(router *gin.Engine) {
router.
GET("/api/k8s/pods", Pod.GetPods)
}
此时启动服务后可以通过postman获取到所有pod的信息
如果不加参数就是获取到所有pod信息
加了参数就可以对namespace,分页等进行过滤
可以只过滤某一个pod
service/pod.go
func (p *pod) GetDetail(podName string, namespace string) (pod *corev1.Pod, err error) {
pod, err = K8s.ClientSet.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
logger.Error("获取Pod详情失败," + err.Error())
return nil, errors.New("获取Pod详情失败," + err.Error())
}
return pod, nil
}
func (p *pod) DeletePod(podName string, namespace string) (err error) {
err = K8s.ClientSet.CoreV1().Pods(namespace).Delete(context.TODO(), podName, metav1.DeleteOptions{})
if err != nil {
logger.Error("删除Pod详情失败," + err.Error())
return errors.New("删除Pod详情失败," + err.Error())
}
return nil
}
func (p *pod) UpdatePod(podName string, namespace, content string) (err error) {
var pod = &corev1.Pod{}
// 反序列化为Pod对象
err = json.Unmarshal([]byte(content), pod)
if err != nil {
logger.Error("反序列化失败," + err.Error())
return errors.New("反序列化失败," + err.Error())
}
// 更新pod
_, err = K8s.ClientSet.CoreV1().Pods(namespace).Update(context.TODO(), pod, metav1.UpdateOptions{})
if err != nil {
logger.Error("更新Pod失败," + err.Error())
return errors.New("更新Pod失败," + err.Error())
}
return nil
}
func (p *pod) GetPodContainer(podName string, namespace string) (containers []string, err error) {
pod, err := p.GetPodDetail(podName, namespace)
if err != nil {
return nil, err
}
for _, container := range pod.Spec.Containers {
containers = append(containers, container.Name)
}
return containers, nil
}
// 获取Pod内容器日志
func (p *pod) GetPodLog(containerName string, podName string, namespace string) (log string, err error) {
//设置日志配置,容器名,获取内容的配置
lineLimit := int64(config.PodLogTailLine)
option := &corev1.PodLogOptions{
Container: containerName,
TailLines: &lineLimit,
}
// 获取一个request实例
req := K8s.ClientSet.CoreV1().Pods(namespace).GetLogs(podName, option)
// 发起stream连接,获取到Response.body
podLogs, err := req.Stream(context.TODO())
if err != nil {
logger.Error("更新Pod失败," + err.Error())
return "", errors.New("更新Pod失败," + err.Error())
}
defer podLogs.Close()
// 将Response.body 写入到缓存区,目的为了转换成string类型
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
logger.Error("复制podLog失败," + err.Error())
return "", errors.New("复制podLog失败," + err.Error())
}
return buf.String(), nil
}
// 获取每个namespace中pod的数量
func(p *pod) GetPodNumPerNp() (podsNps []*PodsNp, err error) {
//获取namespace列表
namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, namespace := range namespaceList.Items {
//获取pod列表
podList, err := K8s.ClientSet.CoreV1().Pods(namespace.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
//组装数据
podsNp := &PodsNp{
Namespace: namespace.Name,
PodNum: len(podList.Items),
}
//添加到podsNps数组中
podsNps = append(podsNps, podsNp)
}
return podsNps, nil
}
controller/pod.go
// 获取pod详情
func (p *pod) GetPodDetail(ctx *gin.Context) {
// 处理入参
// 匿名结构体用于定义入参,get请求为form格式,其他为json格式
params := new(struct {
PodName string `form:"pod_name"`
Namespace string `form:"namespace"`
})
// form 格式使用Bind方法,json格式使用ShouldBindJson方法
if err := ctx.Bind(params); err != nil {
logger.Error("Bind绑定参数失败," + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": "Bind绑定参数失败," + err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPodDetail(params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod列表成功",
"data": data,
})
}
controller/router.go
type router struct{}
func (r *router) InitApiRouter(router *gin.Engine) {
router.
GET("/api/k8s/pods", Pod.GetPods).
GET("/api/k8s/pods/detail", Pod.GetPodDetail).
POST("/api/k8s/pods", Pod.DeletePod)
}
// 删除Pod
func (p *pod) DeletePod(ctx *gin.Context) {
// 处理入参
// 匿名结构体用于定义入参,get请求为form格式,其他为json格式
params := new(struct {
PodName string `json:"pod_name"`
Namespace string `json:"namespace"`
})
// form 格式使用Bind方法,json格式使用ShouldBindJson方法
if err := ctx.ShouldBindJSON(params); err != nil {
logger.Error("Bind绑定参数失败," + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": "Bind绑定参数失败," + err.Error(),
"data": nil,
})
return
}
err := service.Pod.DeletePod(params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "删除Pod成功",
})
}
// 更新pod
func (p *pod) UpdatePod(ctx *gin.Context) {
params := new(struct {
PodName string `json:"pod_name"`
Namespace string `json:"namespace"`
Content string `json:"content"`
})
//PUT请求,绑定参数方法改为ctx.ShouldBindJSON
if err := ctx.ShouldBindJSON(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
err := service.Pod.UpdatePod(params.PodName, params.Namespace, params.Content)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "更新Pod成功",
"data": nil,
})
}
// 获取pod容器
func (p *pod) GetPodContainer(ctx *gin.Context) {
params := new(struct {
PodName string `form:"pod_name"`
Namespace string `form:"namespace"`
})
//GET请求,绑定参数方法改为ctx.Bind
if err := ctx.Bind(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPodContainer(params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod容器成功",
"data": data,
})
}
// 获取pod中容器日志
func (p *pod) GetPodLog(ctx *gin.Context) {
params := new(struct {
ContainerName string `form:"container_name"`
PodName string `form:"pod_name"`
Namespace string `form:"namespace"`
})
//GET请求,绑定参数方法改为ctx.Bind
if err := ctx.Bind(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPodLog(params.ContainerName, params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod中容器日志成功",
"data": data,
})
}
// 获取每个namespace的pod数量
func (p *pod) GetPodNumPerNp(ctx *gin.Context) {
data, err := service.Pod.GetPodNumPerNp()
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取每个namespace的pod数量成功",
"data": data,
})
}
type router struct{}
func (r *router) InitApiRouter(router *gin.Engine) {
router.
GET("/api/k8s/pods", Pod.GetPods).
GET("/api/k8s/pods/detail", Pod.GetPodDetail).
POST("/api/k8s/pods", Pod.DeletePod).
DELETE("/api/k8s/pod/del", Pod.DeletePod).
PUT("/api/k8s/pod/update", Pod.UpdatePod).
GET("/api/k8s/pod/container", Pod.GetPodContainer).
GET("/api/k8s/pod/log", Pod.GetPodLog).
GET("/api/k8s/pod/numnp", Pod.GetPodNumPerNp)
}
package service
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"k8s-plantform/config"
"github.com/wonderivan/logger"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var Pod pod
type pod struct{}
// 定义列表的返回内容,Items是pod元素列表,Total是元素数量
type PodsResp struct {
Total int `json:"total"`
Items []corev1.Pod `json:"items"`
}
type PodsNp struct {
Namespace string `json:"namespace"`
PodNum int `json:"pod_num"`
}
// 获取pod列表,支持过滤,排序,分页
func (p *pod) GetPods(filterName, namespace string, limit, page int) (podsResp *PodsResp, err error) {
//context.TODO() 用于声明一个空的context上下文,用于List方法内设置这个请求超时
//metav1.ListOptions{} 用于过滤List数据,如label,field等
podList, err := K8s.ClientSet.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
logger.Info("获取Pod列表失败," + err.Error())
// 返回给上一层,最终返回给前端,前端捕获到后打印出来
return nil, errors.New("获取Pod列表失败," + err.Error())
}
// 实例化dataselector结构体,组装数据
selectableData := &dataSelector{
GenericDataList: p.toCells(podList.Items),
DataSelect: &DataSelectQuery{
Filter: &FilterQuery{Name: filterName},
Paginate: &PaginateQuery{
Limit: limit,
Page: page,
},
},
}
// 先过滤
filtered := selectableData.Filter()
total := len(filtered.GenericDataList)
// 排序和分页
data := filtered.Sort().Paginate()
println("Pod total: ", total)
// 将DataCell类型转成Pod
pods := p.fromCells(data.GenericDataList)
return &PodsResp{
Items: pods,
Total: total,
}, nil
}
// 类型转换方法corev1.Pod --> DataCell,DataCell-->corev1.Pod
func (p *pod) toCells(pods []corev1.Pod) []DataCell {
cells := make([]DataCell, len(pods))
for i := range pods {
cells[i] = podCell(pods[i])
}
return cells
}
func (p *pod) fromCells(cells []DataCell) []corev1.Pod {
pods := make([]corev1.Pod, len(cells))
for i := range cells {
// cells[i].(podCell)是将DataCell类型转换成podCell
pods[i] = corev1.Pod(cells[i].(podCell))
}
return pods
}
// 获取pod详情
func (p *pod) GetPodDetail(podName, namespace string) (pod *corev1.Pod, err error) {
pod, err = K8s.ClientSet.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
logger.Error("获取Pod详情失败," + err.Error())
return nil, errors.New("获取Pod详情失败," + err.Error())
}
return pod, nil
}
// 删除Pod
func (p *pod) DeletePod(podName string, namespace string) (err error) {
err = K8s.ClientSet.CoreV1().Pods(namespace).Delete(context.TODO(), podName, metav1.DeleteOptions{})
if err != nil {
logger.Error("删除Pod详情失败," + err.Error())
return errors.New("删除Pod详情失败," + err.Error())
}
return nil
}
// 更新Pod
func (p *pod) UpdatePod(namespace, content string) (err error) {
var pod = &corev1.Pod{}
// 反序列化为Pod对象
err = json.Unmarshal([]byte(content), pod)
if err != nil {
logger.Error("反序列化失败," + err.Error())
return errors.New("反序列化失败," + err.Error())
}
// 更新pod
_, err = K8s.ClientSet.CoreV1().Pods(namespace).Update(context.TODO(), pod, metav1.UpdateOptions{})
if err != nil {
logger.Error("更新Pod失败," + err.Error())
return errors.New("更新Pod失败," + err.Error())
}
return nil
}
// 获取Pod中的容器名
func (p *pod) GetPodContainer(podName string, namespace string) (containers []string, err error) {
pod, err := p.GetPodDetail(podName, namespace)
if err != nil {
return nil, err
}
for _, container := range pod.Spec.Containers {
containers = append(containers, container.Name)
}
return containers, nil
}
// 获取Pod内容器日志
func (p *pod) GetPodLog(containerName string, podName string, namespace string) (log string, err error) {
//设置日志配置,容器名,获取内容的配置
lineLimit := int64(config.PodLogTailLine)
option := &corev1.PodLogOptions{
Container: containerName,
TailLines: &lineLimit,
}
// 获取一个request实例
req := K8s.ClientSet.CoreV1().Pods(namespace).GetLogs(podName, option)
// 发起stream连接,获取到Response.body
podLogs, err := req.Stream(context.TODO())
if err != nil {
logger.Error("更新Pod失败," + err.Error())
return "", errors.New("更新Pod失败," + err.Error())
}
defer podLogs.Close()
// 将Response.body 写入到缓存区,目的为了转换成string类型
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
logger.Error("复制podLog失败," + err.Error())
return "", errors.New("复制podLog失败," + err.Error())
}
return buf.String(), nil
}
// 获取每个namespace中pod的数量
func (p *pod) GetPodNumPerNp() (podsNps []*PodsNp, err error) {
//获取namespace列表
namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, namespace := range namespaceList.Items {
//获取pod列表
podList, err := K8s.ClientSet.CoreV1().Pods(namespace.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
//组装数据
podsNp := &PodsNp{
Namespace: namespace.Name,
PodNum: len(podList.Items),
}
//添加到podsNps数组中
podsNps = append(podsNps, podsNp)
}
return podsNps, nil
}
package controller
import (
"k8s-plantform/service"
"net/http"
"github.com/gin-gonic/gin"
"github.com/wonderivan/logger"
)
var Pod pod
type pod struct{}
// Controller中的方法入参是gin.Context 用于从上下文中获取请求参数及定义响应内容
// 流程: 绑定参数 --> 调用service代码 --> 根据调用结果响应具体内容
// 获取pod列表,支持分页,过滤,排序
func (p *pod) GetPods(ctx *gin.Context) {
// 处理入参
// 匿名结构体用于定义入参,get请求为form格式,其他为json格式
params := new(struct {
FilterName string `form:"filter_name"`
Namespace string `form:"namespace"`
Limit int `form:"limit"`
Page int `form:"page"`
})
// form 格式使用Bind方法,json格式使用ShouldBindJson方法
if err := ctx.Bind(params); err != nil {
logger.Error("Bind绑定参数失败," + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": "Bind绑定参数失败," + err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPods(params.FilterName, params.Namespace, params.Limit, params.Page)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod列表成功",
"data": data,
})
}
// 获取pod详情
func (p *pod) GetPodDetail(ctx *gin.Context) {
// 处理入参
// 匿名结构体用于定义入参,get请求为form格式,其他为json格式
params := new(struct {
PodName string `form:"pod_name"`
Namespace string `form:"namespace"`
})
// form 格式使用Bind方法,json格式使用ShouldBindJson方法
if err := ctx.Bind(params); err != nil {
logger.Error("Bind绑定参数失败," + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": "Bind绑定参数失败," + err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPodDetail(params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod列表成功",
"data": data,
})
}
// 删除Pod
func (p *pod) DeletePod(ctx *gin.Context) {
// 处理入参
// 匿名结构体用于定义入参,get请求为form格式,其他为json格式
params := new(struct {
PodName string `json:"pod_name"`
Namespace string `json:"namespace"`
})
// form 格式使用Bind方法,json格式使用ShouldBindJson方法
if err := ctx.ShouldBindJSON(params); err != nil {
logger.Error("Bind绑定参数失败," + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": "Bind绑定参数失败," + err.Error(),
"data": nil,
})
return
}
err := service.Pod.DeletePod(params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "删除Pod成功",
})
}
// 更新pod
func (p *pod) UpdatePod(ctx *gin.Context) {
params := new(struct {
Namespace string `json:"namespace"`
Content string `json:"content"`
})
//PUT请求,绑定参数方法改为ctx.ShouldBindJSON
if err := ctx.ShouldBindJSON(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
err := service.Pod.UpdatePod(params.Namespace, params.Content)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "更新Pod成功",
"data": nil,
})
}
// 获取pod容器
func (p *pod) GetPodContainer(ctx *gin.Context) {
params := new(struct {
PodName string `form:"pod_name"`
Namespace string `form:"namespace"`
})
//GET请求,绑定参数方法改为ctx.Bind
if err := ctx.Bind(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPodContainer(params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod容器成功",
"data": data,
})
}
// 获取pod中容器日志
func (p *pod) GetPodLog(ctx *gin.Context) {
params := new(struct {
ContainerName string `form:"container_name"`
PodName string `form:"pod_name"`
Namespace string `form:"namespace"`
})
//GET请求,绑定参数方法改为ctx.Bind
if err := ctx.Bind(params); err != nil {
logger.Error("Bind请求参数失败, " + err.Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
data, err := service.Pod.GetPodLog(params.ContainerName, params.PodName, params.Namespace)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取Pod中容器日志成功",
"data": data,
})
}
// 获取每个namespace的pod数量
func (p *pod) GetPodNumPerNp(ctx *gin.Context) {
data, err := service.Pod.GetPodNumPerNp()
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
"data": nil,
})
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "获取每个namespace的pod数量成功",
"data": data,
})
}
依次测试以下端口
GET("/api/k8s/pods/detail", Pod.GetPodDetail).
POST("/api/k8s/pods", Pod.DeletePod).
DELETE("/api/k8s/pod/del", Pod.DeletePod).
PUT("/api/k8s/pod/update", Pod.UpdatePod).
GET("/api/k8s/pod/container", Pod.GetPodContainer).
GET("/api/k8s/pod/log", Pod.GetPodLog).
GET("/api/k8s/pod/numnp", Pod.GetPodNumPerNp)
/api/k8s/pods/detail
/api/k8s/pods
# kubectl get pods
NAME READY STATUS RESTARTS AGE
centos7 2/2 Running 0 19d
el-gitlab-event-listener-75497dbb79-kpjmj 2/2 Running 19 (8d ago) 19d
el-s2i-listener-7c78cc48c-tvtpq 2/2 Running 18 (4m25s ago) 19d
sleep-557747455f-mvgbb 2/2 Running 4 (19d ago) 19d
虽然还是3个pod但明显sleep-557747455f-l5hrg deployment是刚创建的.
# kubectl get pods
NAME READY STATUS RESTARTS AGE
centos7 2/2 Running 0 19d
el-gitlab-event-listener-75497dbb79-kpjmj 2/2 Running 19 (8d ago) 19d
el-s2i-listener-7c78cc48c-tvtpq 2/2 Running 18 (8m20s ago) 19d
sleep-557747455f-l5hrg 2/2 Running 0 20s
# kubectl get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
el-gitlab-event-listener 1/1 1 1 19d
el-s2i-listener 1/1 1 1 19d
sleep 1/1 1 1 19d
/api/k8s/pod/del
# kubectl get pods
NAME READY STATUS RESTARTS AGE
centos7 2/2 Running 0 19d
el-gitlab-event-listener-75497dbb79-kpjmj 2/2 Running 19 (8d ago) 19d
el-s2i-listener-7c78cc48c-tvtpq 2/2 Running 18 (8m20s ago) 19d
sleep-557747455f-l5hrg 2/2 Running 0 20s
这里明显可以看到centos7这个pod被删除了
# kubectl get pods
NAME READY STATUS RESTARTS AGE
centos7 2/2 Terminating 0 19d
el-gitlab-event-listener-75497dbb79-kpjmj 2/2 Running 19 (8d ago) 19d
el-s2i-listener-7c78cc48c-tvtpq 2/2 Running 18 (12m ago) 19d
sleep-557747455f-6zcpj 2/2 Running 0 82s
# kubectl get pods
NAME READY STATUS RESTARTS AGE
el-gitlab-event-listener-75497dbb79-kpjmj 2/2 Running 19 (8d ago) 19d
el-s2i-listener-7c78cc48c-tvtpq 2/2 Running 18 (13m ago) 19d
sleep-557747455f-6zcpj 2/2 Running 0 108s
/api/k8s/pod/update
先去获得一段json
将内容复制处理来略做改动
再次请求就可以看到这个新追加的test label
/api/k8s/pod/container
# kubectl describe pods spring-boot-helloworld-5f77c6ff9f-k8z6v -n hello
Name: spring-boot-helloworld-5f77c6ff9f-k8z6v
Namespace: hello
Priority: 0
Node: 192.168.31.111/192.168.31.111
Start Time: Wed, 23 Nov 2022 11:00:31 +0800
Labels: app=spring-boot-helloworld
pod-template-hash=5f77c6ff9f
Annotations: <none>
Status: Running
IP: 172.100.109.74
IPs:
IP: 172.100.109.74
Controlled By: ReplicaSet/spring-boot-helloworld-5f77c6ff9f
Containers:
spring-boot-helloworld:
Container ID: docker://1f692a565ff4c94cb811bfb5b3ac9c624841f3527bb703d269566f73f11a365f
Image: kurtqiu1979/spring:v0.9-20221118-081546
Image ID: docker-pullable://kurtqiu1979/spring@sha256:03b555ee834e27d03743ce221af839e6b81ff868bb23526c6d94501921354549
Port: 80/TCP
得到的信息和命令行是相符的
/api/k8s/pod/log
/api/k8s/pod/numnp
这个不需要传参,直接返回所有命名空间pod的数量