gin模板展示k8s命名空间的资源
- 这里学习如何在前端单页面,调用后端接口展示k8s的资源
技术栈
- 后端 -> go -> gin -> gin模板
- 前端 -> gin模板 -> html + js
- k8s -> k8s-go-client ,基本资源(deployment等)
环境
- go 1.19
- k8s 1.23
- go module
- github.com/gin-gonic/gin v1.6.3
- k8s.io/client-go v0.20.2
搭建环境
- 安装 k8s 、go 开发环境(此处省略)
- ide打开后创建项目(我的项目名叫gin_k8s_deploy),然后安装go module
go get github.com/gin-gonic/gin@v1.6.3
go get k8s.io/client-go@v0.20.2
- 复制k8s的kubeconfig(/root/.kube/config)到项目
- 注:假如是公网的环境,需特别处理。假如是内网(虚拟机等)可以直连的情况则无需处理
- 注:假如是公网的环境,需特别处理。假如是内网(虚拟机等)可以直连的情况则无需处理
- 以下是公网k8s的kubeconfig的处理,将里面的内网ip换成公网ip(因为你无法通过内网ip直连),并且ca认证这行删除
后端
- 初始化k8s的客户端
- client/K8sClient.go
package client
import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"log"
)
func InitK8sClient() *kubernetes.Clientset {
config, err := clientcmd.
BuildConfigFromFlags("", "config")
if err != nil {
log.Fatal(err)
}
// config.Insecure 假如k8s是内网可以走https则不需要设置
config.Insecure = true
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatal(err)
}
return clientset
}
- main.go 添加 k8sClient (可运行测试一下,能返回对象即成功)
package main
import (
"fmt"
"gin_k8s_deploy/client"
)
func main() {
k8sClient := client.InitK8sClient()
fmt.Println(k8sClient)
}
使用gin作为web后端
- main.go
package main
import (
"fmt"
"gin_k8s_deploy/client"
"github.com/gin-gonic/gin"
)
func main() {
k8sClient := client.InitK8sClient()
fmt.Println(k8sClient)
// gin实例
r := gin.New()
// 加载html模板
r.LoadHTMLGlob("templates/*")
// 后端返回页面
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{"data": "success"})
})
// 运行
r.Run(":8080")
}
- templates/index.html (此处渲染gin的 “data”)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
{{ .data }}
</body>
</html>
- 运行main.go,并测试访问首页 http://localhost:8080
编写gin请求k8s的接口
- 以下是gin请求k8s namespace api的接口
- main.go
// k8s 命名空间 接口
r.GET("/ns", func(c *gin.Context) {
// namespace 在 k8s 的核心(core)api组
ns, err := k8sClient.
CoreV1().Namespaces().
List(context.Background(), metav1.ListOptions{})
if err != nil {
log.Fatal(err)
}
nsRet := make([]string, 0)
for _, nsItem := range ns.Items {
nsRet = append(nsRet, nsItem.Name)
}
c.JSON(200, gin.H{"ns_list": nsRet})
})
- 请求测试 (运行如报错请 go mod tidy )
- gin请求k8s deployment api的接口
// k8s deployment 接口
r.GET("/:ns/deployment/list", func(c *gin.Context) {
ns := c.Param("ns")
// deployment在k8s的 appsv1 api组
deployment, err := k8sClient.
AppsV1().Deployments(ns).
List(context.Background(), metav1.ListOptions{})
if err != nil {
log.Fatal(err)
}
deploymentRet := make([]string, 0)
for _, item := range deployment.Items {
deploymentRet = append(deploymentRet, item.Name)
}
c.JSON(200, gin.H{"deployment_list": deploymentRet})
})
- 请求测试
前端
- 前端使用Promise与后端交互
- index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script>
// 调用后端 /ns 接口,返回ns的数组
function getK8sNsList() {
return fetch("/ns", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
}).then(response => {
return response.json()
}).then(data => {
return data.ns_list
})
}
// 使用dom将ns数组的值保存到select框中的option
function displayK8sNsList(ns_list) {
ns_list.forEach(ns => {
var ele = document.getElementById("selectNs")
var option = document.createElement("option")
option.name = ns
option.value = ns
option.text = ns
ele.add(option)
})
}
// 获取后端 /ns 返回的第一个命名空间
function getFirstK8sNsList() {
return fetch("/ns", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
}).then(response => {
return response.json()
}).then(data => {
return data.ns_list[0]
})
}
// 调用后盾 /deployment/:ns/list 接口,返回deployment数组
function getK8sDeployments(ns) {
return fetch("/deployment/" + ns + "/list", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
}).then(response => {
return response.json()
}).then(data => {
return data.deployment_list
})
}
//
function displayK8sDeploymentList(deployment_list) {
var ele = document.getElementById("deployment_list")
deployment_list.forEach(deployment => {
var li = document.createElement("li")
li.textContent= deployment
ele.appendChild(li)
})
}
// select框选中其他值时进行请求
function onNamespaceChange() {
var selectedNs = document.getElementById("selectNs").value;
var deploymentList = document.getElementById("deployment_list");
while (deploymentList.firstChild) {
deploymentList.removeChild(deploymentList.firstChild);
}
getK8sDeployments(selectedNs).then(deployment_list => {
displayK8sDeploymentList(deployment_list)
})
}
</script>
</head>
<body>
<div class="header">
<h1>K8s面板</h1>
<p style="display: inline-block">请选择命名空间: </p>
<select name="selectNs" id="selectNs" onchange="onNamespaceChange()">
</select>
</div>
<div class="content">
<p>deployments</p>
<ul id="deployment_list">
</ul>
</div>
<script>
// select框中填充ns数组
getK8sNsList().then(ns_list => {
displayK8sNsList(ns_list)
})
// 首先打印第一个命名空间的deployments
getFirstK8sNsList().then(ns => {
getK8sDeployments(ns).then(deployment_list => {
displayK8sDeploymentList(deployment_list)
})
})
</script>
</body>
</html>
效果