Skip to content

Commit

Permalink
增加批量重启pod和操作限制功能
Browse files Browse the repository at this point in the history
  • Loading branch information
FlowerBirds committed Dec 26, 2023
1 parent d4b65d8 commit 303ae60
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 60 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,12 @@ export MANAGE_PASSWORD=1Fx98ksOa23GHapo0
- [x] 支持文件夹压缩
- [x] 支持文件下载
- [x] 支持设置安全模式,可以定时更新登录token
- [x] 增加k8s服务功能(基于RBAC角色权限控制)
- [x] 服务功能查询所有命名空间
- [x] 服务功能展示当前命名空间下的pod列表
- [x] 服务功能中可对pod进行重启
- [x] 服务功能中可查看pod对应deployment的yaml信息
- [x] 服务功能中可查看pod对应的日志
- [x] 服务功能中增加批量重启pod功能
- [x] 服务功能中禁止重启kube-system命名空间和fm本身pod

56 changes: 34 additions & 22 deletions file-online-manager-web/src/components/K8sServicePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@
<el-button type="primary" size="small" icon="el-icon-refresh" class="refresh-btn"
@click="refresh()"
title="刷新"></el-button>
<el-button type="primary" size="small" icon="el-icon-refresh-right" class="refresh-btn"
@click="restartPodAll()" v-show="hasSelected"
title="全部重启"></el-button>
</div>
<div>
<el-table :data="tableData">
<el-table :data="tableData"
:selection="selection"
@select="handleSelectionChange"
@select-all="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="name" label="名称" width="440"></el-table-column>
<el-table-column prop="ready" label="实例数" width="80"></el-table-column>
<el-table-column prop="status" label="状态" ></el-table-column>
Expand Down Expand Up @@ -65,28 +72,15 @@ export default {
isMaximized: false,
selectedNamespace: 'default', // 默认选中的值
logStreamData: "",
podYaml: "aaa\n\n\naaa",
podYaml: "",
logEventSource: null,
logTitle: "查看日志",
podYamlTitle: "查看YAML",
selection: [],
options: [
{ value: 'default', label: 'default' },
{ value: 'tempo611', label: 'tempo611' },
{ value: 'openfaas', label: 'openfaas' }
],
tableData: [
{
"name": "file-manage-df4856c55-n2b2r",
"ready": "1/1",
"status": "Running",
"restarts": "24 (3h34m ago)",
"age": "84d",
"ip": "10.42.0.248",
"node": "laptop-tc4a0scv",
"nominatedNode": "<none>",
"readinessGates": "<none>"
}
]
tableData: []
};
},
components: {
Expand All @@ -95,27 +89,45 @@ export default {
mounted() {
//
},
computed: {
hasSelected() {
return this.selection.length > 0;
}
},
methods: {
init() {
this.listNamespace()
this.listPods()
},
handleSelectionChange(selection) {
this.selection = selection;
},
restartPod(row) {
this.restartPodAll([row])
},
restartPodAll(rows) {
if (!rows) {
rows = this.selection
}
let name = ""
for (let i = 0; i < rows.length; i++) {
name += rows[i].name + ","
}
let $this = this;
const formData = new FormData();
formData.append('namespace', this.selectedNamespace);
formData.append('name', row.name);
this.$confirm("是否重启该pod:" + row.name, "确认").then(function () {
formData.append('name', name);
this.$confirm("是否重启pod", "确认").then(function () {
$this.$http.postForm('./api/manager/k8s/restart-pod', formData, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(response => {
console.log(response)
$this.listPods()
}, response => {
console.log(response)
$this.$alert(response.message, '错误', {
}, axiosError => {
console.log(axiosError)
$this.$alert(axiosError.response.data.message, '错误', {
confirmButtonText: '确定',
type: 'error'
})
Expand Down
70 changes: 42 additions & 28 deletions handler/k8sservice/pod_handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,22 @@ func InitK8sClient() *kubernetes.Clientset {
if kubeconfig != "" {
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
log.Println("load config failed: ", err)
util.Println("load config failed: ", err)
config = nil
}
}
if config == nil {
config, err = rest.InClusterConfig()
if err != nil {
log.Println("load in cluster config failed: ", err)
util.Println("load in cluster config failed: ", err)
config = nil
}
}

if config != nil {
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Println(err)
util.Println(err)
return nil
}
return clientset
Expand All @@ -61,23 +61,37 @@ func RestartPodHandler(w http.ResponseWriter, r *http.Request) {
util.Error(w, errors.New("empty pod params"))
return
}
log.Println("restart:", namespace, name)
if namespace == "kube-system" {
util.Error(w, errors.New("forbidden to restart kube-system pod"))
return
}
util.Println("restart:", namespace, name)
hostname := os.Getenv("HOSTNAME")
clientset := InitK8sClient()
if clientset != nil {
ctx := context.Background()
pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
util.Error(w, err)
return
}
log.Println("found pod", pod.Name)
// newPod := pod.DeepCopy()
// newPod.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)

err = clientset.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{})
if err != nil {
util.Error(w, err)
return
for _, podName := range strings.Split(name, ",") {
if podName == "" {
continue
}
_, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
util.Error(w, err)
return
}
// forbidden to restart itself
if hostname == podName {
util.Println("Ignore restart pod: ", podName)
continue
}

util.Println("Found and delete pod: ", podName)
err = clientset.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
if err != nil {
util.Error(w, err)
return
}
}
response := model.Response{Code: 200, Message: "Restart pod successfully", Data: true}
jsonResponse, _ := json.Marshal(response)
Expand All @@ -99,7 +113,7 @@ func ListPodHandler(w http.ResponseWriter, r *http.Request) {
if clientset != nil {
pods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
log.Println(err)
util.Println(err)
util.Error(w, err)
return
}
Expand Down Expand Up @@ -142,7 +156,7 @@ func calculateAge(status v1.PodStatus) string {
}
now := metav1.Now()
duration := now.Sub(creationTime.Time)
// log.Println(duration.String())
// util.Println(duration.String())
duration, err := time.ParseDuration(duration.String())
if err != nil {
return ""
Expand Down Expand Up @@ -195,7 +209,7 @@ func ListNamespaceHandler(w http.ResponseWriter, r *http.Request) {
if clientset != nil {
namespaces, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
log.Println(err)
util.Println(err)
util.Error(w, err)
return
}
Expand Down Expand Up @@ -235,7 +249,7 @@ func PodStreamLogHandler(w http.ResponseWriter, r *http.Request) {
util.Error(w, errors.New("can't view itself logs due to cause to recursive access and leads to an infinite loop"))
return
}
log.Println("read logs: ", namespace, name)
util.Println("read logs: ", namespace, name)

flusher, ok := w.(http.Flusher)
if !ok {
Expand Down Expand Up @@ -270,7 +284,7 @@ func PodStreamLogHandler(w http.ResponseWriter, r *http.Request) {
full := true
reverseStr := ""
for {
// log.Println("read data")
// util.Println("read data")
size, err := logs.Read(buf)
if size > 0 {
if buf[size-1] != 10 {
Expand All @@ -285,12 +299,12 @@ func PodStreamLogHandler(w http.ResponseWriter, r *http.Request) {
}
if !full && i == len(readlogs)-1 {
reverseStr += text
// log.Println("reverse " + reverseStr)
// util.Println("reverse " + reverseStr)
continue
}
data := []byte("data: " + text + "\n\n")
if i == 0 && len(reverseStr) > 0 {
// log.Println("add reverse " + reverseStr + text)
// util.Println("add reverse " + reverseStr + text)
data = []byte("data: " + reverseStr + text + "\n\n")
reverseStr = ""
}
Expand Down Expand Up @@ -321,7 +335,7 @@ func ViewPodYamlHandler(w http.ResponseWriter, r *http.Request) {
util.Error(w, errors.New("invalid query param"))
return
}
log.Println("view yaml: ", namespace, name)
util.Println("view yaml: ", namespace, name)
clientset := InitK8sClient()
if clientset != nil {
pod, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
Expand All @@ -331,15 +345,15 @@ func ViewPodYamlHandler(w http.ResponseWriter, r *http.Request) {
return
}
ownerReferences := pod.ObjectMeta.OwnerReferences
log.Println(ownerReferences)
util.Println(ownerReferences)
if len(ownerReferences) == 0 {
util.Error(w, errors.New("Pod does not have an owner"))
return
}
replicaSetName := ownerReferences[0].Name
replicaSet, err := clientset.AppsV1().ReplicaSets(namespace).Get(context.TODO(), replicaSetName, metav1.GetOptions{})
if err != nil {
log.Println(err)
util.Println(err)
util.Error(w, err)
return
}
Expand All @@ -348,7 +362,7 @@ func ViewPodYamlHandler(w http.ResponseWriter, r *http.Request) {
ownerReferences = replicaSet.ObjectMeta.OwnerReferences
if len(ownerReferences) == 0 {
err = errors.New("ReplicaSet does not have an owner")
log.Println(err)
util.Println(err)
util.Error(w, err)
return
}
Expand All @@ -361,7 +375,7 @@ func ViewPodYamlHandler(w http.ResponseWriter, r *http.Request) {
}

deploymentYAML, err := deploymentToYAML(deployment)
// log.Println(deploymentYAML)
// util.Println(deploymentYAML)
if err != nil {
fmt.Printf("Failed to convert deployment to YAML: %v", err)
util.Error(w, err)
Expand Down
18 changes: 9 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func main() {
return
}
}
log.Println("server manage root path: " + root)
log.Println("server use context path: " + contextPath)
util.Println("server manage root path: " + root)
util.Println("server use context path: " + contextPath)

initAuth()
// 增加拦截器
Expand Down Expand Up @@ -75,7 +75,7 @@ func main() {
router.HandleFunc(contextPath+"api/manager/k8s/view-pod-yaml", k8sservice.ViewPodYamlHandler).Methods("GET")

router.PathPrefix(contextPath + "").Handler(http.StripPrefix(contextPath, http.FileServer(http.Dir("./static/"))))
log.Println("server started at port 8080")
util.Println("server started at port 8080")
http.ListenAndServe(":8080", router)
}

Expand All @@ -88,13 +88,13 @@ func initAuth() {
manageSecurity := os.Getenv("MANAGE_SECURITY")
if manageUsername == "" || manageSecurity == "true" || manageSecurity == "" {
loginUsername = util.GenToken(32)
log.Println("use security user: " + loginUsername)
util.Println("use security user: " + loginUsername)
} else {
loginUsername = manageUsername
}
if managePassword == "" || manageSecurity == "true" || manageSecurity == "" {
loginPassword = util.GenToken(128)
log.Println("use security token: " + loginPassword)
util.Println("use security token: " + loginPassword)
} else {
loginPassword = managePassword
}
Expand All @@ -112,9 +112,9 @@ func initAuth() {
case <-ticker.C:
// 更新token
loginUsername = util.GenToken(32)
log.Println("use security user: " + loginUsername)
util.Println("use security user: " + loginUsername)
loginPassword = util.GenToken(128)
log.Println("use security token: " + loginPassword)
util.Println("use security token: " + loginPassword)
}
}
}()
Expand All @@ -139,7 +139,7 @@ func authenticationMiddleware(next http.Handler) http.Handler {
func accessLogMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
uri := r.URL.Path
log.Println("access uri:", uri)
util.Println("access uri:", uri)

// 继续执行下一个处理函数
next.ServeHTTP(w, r)
Expand Down Expand Up @@ -412,7 +412,7 @@ func zipFileHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
w.Write(jsonResponse)
log.Println("zip failed", cmdErr)
util.Println("zip failed", cmdErr)
return
}

Expand Down
23 changes: 22 additions & 1 deletion util/response_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,36 @@ package util
import (
"encoding/json"
"file-online-manager/model"
"fmt"
"log"
"net/http"
"runtime"
"strings"
)

func Error(w http.ResponseWriter, err error) {
log.Println(err)
_, file, line, ok := runtime.Caller(1)
if ok {
i := strings.LastIndex(file, "/")
// 将调用栈信息与日志消息一起输出
log.Printf("[%s line:%d]: %s", file[i+1:], line, err)
} else {
log.Println(err)
}
response := model.Response{Code: 500, Message: err.Error(), Data: nil}
jsonResponse, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
w.Write(jsonResponse)
}

func Println(v ...any) {
_, file, line, ok := runtime.Caller(1)
if ok {
i := strings.LastIndex(file, "/")
// 将调用栈信息与日志消息一起输出
log.Printf("[%s line:%d]: %s", file[i+1:], line, fmt.Sprintln(v...))
} else {
log.Println(v)
}
}

0 comments on commit 303ae60

Please sign in to comment.