监控 K8S 节点内存使用
场景
在Kubernetes
集群中,节点的内存使用情况至关重要。
内存使用过高可能导致Pod
无法正常运行,从而影响整个集群的性能。
因此,实时监控节点的内存使用情况,并根据需要动态调整节点的调度策略,是保障集群稳定性的关键。
正文
程序概述
本文将使用一个简单的Go
语言程序,并设置每10
分钟监控一次Kubernetes
集群中所有节点的内存使用情况。当某个节点的内存使用率达到90%
时,程序会自动停止该节点的调度,待内存使用率降低后再恢复调度。同时,程序还会通过钉钉和邮件通知相关人员,以确保大家及时了解集群的状态。
程序功能
- 定时监控:每
10
分钟自动检查集群中所有节点的内存使用情况。 - 动态调度控制:当节点内存使用率超过
90%
时,自动停止该节点的调度;内存使用降低后,再次恢复调度。 - 通知机制:一旦节点状态发生变化,程序会通过钉钉和邮件发送通知,确保相关人员及时获得信息。
程序代码
package main
// 引入必要的包
import (
"context" // 用于创建上下文环境,常在API请求中使用
"encoding/json" // 提供JSON的编码和解码功能
"fmt" // 实现格式化的I/O操作
"log"
"math" // 提供基本的数学函数
"os" // 提供系统级的文件操作和环境变量访问
// 用于对文件路径进行操作
"strconv" // 提供字符串与其他基本类型之间的转换功能
"strings" // 提供简单的字符串操作功能
"time"
"github.com/wanghuiyt/ding" // 第三方包,用于发送钉钉消息
mail "github.com/xhit/go-simple-mail/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // Kubernetes的元数据API
"k8s.io/client-go/kubernetes" // Kubernetes的客户端库,用于与集群交互
"k8s.io/client-go/tools/clientcmd" // 用于处理Kubernetes配置文件
// 用于获取用户的主目录路径
"k8s.io/metrics/pkg/client/clientset/versioned" // Kubernetes的度量指标API
)
// 定义全局常量
const statusFilePath = "node_status.json" // 节点状态信息保存文件的路径
const Threshold = 5 // 内存使用率的变化阈值设置为5%
// NodeStatus 结构体定义,用于保存节点的状态信息
type NodeStatus struct {
MemoryUsagePercentage float64 // 节点内存使用率百分比
Unschedulable bool // 节点是否停止调度的标志
}
func timeNow() string {
// 获取当前时间。
now := time.Now()
// 根据这个模板,可以构造各种时间格式。
formattedTime := now.Format("2006-01-02 15:04:05")
// 输出格式化的时间,例如 "Formatted time: 2023-03-10 15:04:05"。
return formattedTime
}
// 准备邮件内容,包括加粗和换行
func prepareHtmlMessage(originalMessage string) string {
// 使用HTML标签构建消息内容
// 在消息前添加"您好!",后面跟上"产生以下告警:",然后是加粗的消息内容,最后是"请关注!"
htmlMessage := "您好!<br>产生以下告警:<br><b>" + originalMessage + "</b><br>请关注!"
return htmlMessage
}
// dingSend 函数用于发送钉钉消息
func dingSend(message string) error {
// 获取当前时间字符串
currentTime := timeNow()
// 将时间戳拼接到消息内容中
dingmessage := fmt.Sprintf("时间:%s\n信息:\n%s", currentTime, message)
// 初始化钉钉Webhook配置
d := ding.Webhook{
AccessToken: "钉钉AccessToken", // 钉钉AccessToken
Secret: "钉钉Secret", // 钉钉Secret
}
// 调用SendMessageText方法发送文本消息
err := d.SendMessageText(dingmessage)
if err != nil {
panic(err.Error()) // 发生错误时抛出异常
}
return nil
}
func mailSend(message string) error {
// 配置SMTP服务器
server := mail.NewSMTPClient()
server.Host = "smtp.mxhichina.com" // 设置SMTP服务器地址,我这里是阿里企业邮箱
server.Port = 80 // 设置SMTP服务器端口
server.Username = "SMTP用户名" // 设置发件人SMTP用户名(发件人邮箱地址)
server.Password = "SMTP密码" // 设置发件人SMTP密码(发件人邮箱密码)
server.KeepAlive = false // 设置是否保持长连接
server.ConnectTimeout = 10 * time.Second // 设置连接超时时间
server.SendTimeout = 10 * time.Second // 设置发送超时时间
// 连接到SMTP服务器
smtpClient, err := server.Connect()
if err != nil {
log.Fatal(err) // 如果连接失败,则记录错误并退出
}
// 创建邮件消息
email := mail.NewMSG()
subject := fmt.Sprintf("告警-K8S集群内存告警(%s)", timeNow())
email.SetFrom("发件人邮箱地址"). // 设置发件人
AddTo("收件人1邮箱地址"). // 添加收件人1
AddCc("抄送人2邮箱地址"). // 添加抄送人2
SetSubject(subject). // 设置邮件主题
SetListUnsubscribe(" ") // 设置邮件退订链接,这里为空
// 调用prepareHtmlMessage来格式化原始消息
prepareHtmlMessage := prepareHtmlMessage(message)
htmlMessage := strings.ReplaceAll(prepareHtmlMessage, "\n", "<br>") // 将换行符替换为HTML的换行标签
if htmlMessage != "" {
email.SetBody(mail.TextHTML, htmlMessage) // 设置邮件正文,使用HTML格式
}
// 设置DSN(Delivery Status Notification,投递状态通知)
email.SetDSN([]mail.DSN{mail.SUCCESS, mail.FAILURE}, false)
// 检查构建邮件过程中是否有错误
if email.Error != nil {
log.Fatal(email.Error) // 如果有错误,则记录并退出
}
// 发送邮件
err = email.Send(smtpClient)
if err != nil {
log.Println(err) // 如果发送失败,记录错误
} else {
log.Println("邮件已发送") // 发送成功,记录提示信息
}
return nil
}
// readNodeStatusFromFile 函数从指定的文件中读取节点的状态信息
func readNodeStatusFromFile(filePath string) (map[string]NodeStatus, error) {
// 打开文件进行读取操作
file, err := os.Open(filePath)
if err != nil {
// 文件不存在时,返回空映射和nil错误
if os.IsNotExist(err) {
return make(map[string]NodeStatus), nil
}
// 其他错误时,返回nil映射和错误信息
return nil, err
}
defer file.Close() // 确保在函数退出时关闭文件
var status map[string]NodeStatus // 定义用于存储节点状态的映射
// 使用JSON解码器从文件读取节点状态
err = json.NewDecoder(file).Decode(&status)
if err != nil {
// 读取错误时,返回nil映射和错误信息
return nil, err
}
return status, nil // 正常情况下返回节点状态和nil错误
}
// writeNodeStatusToFile 函数将节点的状态信息写入指定的文件
func writeNodeStatusToFile(filePath string, status map[string]NodeStatus) error {
// 创建或覆盖文件进行写入操作
file, err := os.Create(filePath)
if err != nil {
return err // 文件创建错误时,返回错误信息
}
defer file.Close() // 确保在函数退出时关闭文件
// 创建JSON编码器并设置格式化缩进
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
// 将节点状态编码为JSON格式写入文件
return encoder.Encode(status)
}
func main() {
// 获取Kubernetes配置文件的路径
//kubeconfig := filepath.Join(homedir.HomeDir(), ".kube", "config")
// 定义Kubeconfig文件的绝对路径
kubeconfig := "/root/.kube/config"
// 基于配置文件建立Kubernetes客户端配置
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
panic(err.Error()) // 配置构建错误时抛出异常
}
// 创建Kubernetes客户端
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error()) // 客户端创建错误时抛出异常
}
// 创建Kubernetes度量指标客户端
metricsClientset, err := versioned.NewForConfig(config)
if err != nil {
panic(err.Error()) // 度量指标客户端创建错误时抛出异常
}
// 获取Kubernetes集群中所有节点的列表
nodes, err := clientset.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
if err != nil {
panic(err.Error()) // 节点列表获取错误时抛出异常
}
// 从文件中读取之前保存的节点状态
previousStatus, err := readNodeStatusFromFile(statusFilePath)
if err != nil {
panic(err.Error()) // 节点状态读取错误时抛出异常
}
// 创建map来存储当前的节点状态
currentStatus := make(map[string]NodeStatus)
var message strings.Builder // 用于构建通知消息的字符串构建器
// 遍历从Kubernetes集群获取的所有节点
for _, node := range nodes.Items {
// 检查节点名称是否包含"master",如果是则跳过此节点
// 通常,主节点(master nodes)不用于调度普通工作负载
if strings.Contains(node.Name, "master") {
continue
}
// 获取节点的度量指标(例如,内存和CPU使用情况)
metrics, err := metricsClientset.MetricsV1beta1().NodeMetricses().Get(context.Background(), node.Name, metav1.GetOptions{})
if err != nil {
// 如果获取度量指标时出错,则将错误信息添加到消息字符串,并继续下一个节点
message.WriteString(fmt.Sprintf("查询节点信息发生错误 %s: %v\n", node.Name, err))
continue
}
// 计算节点当前的内存使用量和总内存容量
currentMemoryUsage := metrics.Usage.Memory().Value() // 当前内存使用量
totalMemoryCapacity := node.Status.Capacity.Memory().Value() // 总内存容量
// 计算内存使用率并四舍五入到两位小数
memoryUsagePercentage := math.Round((float64(currentMemoryUsage)/float64(totalMemoryCapacity)*100)*100) / 100
formattedPercentage := fmt.Sprintf("%.0f", memoryUsagePercentage) // 格式化为百分比
// 将格式化后的百分比转换回浮点数
percentage, err := strconv.ParseFloat(formattedPercentage, 64)
if err != nil {
panic(err.Error()) // 转换出错时抛出异常
}
// 获取之前保存的节点状态,检查是否存在
prevNodeStatus, exists := previousStatus[node.Name]
// 判断节点是否需要设置为不可调度(当内存使用率超过90%时)
unschedulable := percentage >= 90
// 判断是否需要更新节点状态:当节点是新的或其调度状态发生变化时
shouldUpdate := !exists || prevNodeStatus.Unschedulable != unschedulable
// 如果需要更新节点状态
if shouldUpdate {
// 更新节点的调度状态
node.Spec.Unschedulable = unschedulable
// 向Kubernetes API发送更新请求
_, err = clientset.CoreV1().Nodes().Update(context.Background(), &node, metav1.UpdateOptions{})
if err != nil {
panic(err.Error()) // 更新失败时抛出异常
}
// 构造消息字符串,包含节点名称、内存使用率和调度状态
status := "已启用调度"
if unschedulable {
status = "已停止调度"
}
message.WriteString(fmt.Sprintf("节点名称: %s,内存使用率: %.0f%%,%s\n", node.Name, percentage, status))
}
// 更新当前节点状态的映射
currentStatus[node.Name] = NodeStatus{
MemoryUsagePercentage: percentage,
Unschedulable: unschedulable,
}
}
// 如果有节点状态发生变化,则发送消息
if message.Len() > 0 {
fmt.Print(message.String()) // 在控制台打印消息内容
// 发送钉钉消息
errDingSend := dingSend(message.String())
if errDingSend != nil {
fmt.Printf("钉钉消息发送失败: %v\n", errDingSend)
}
// 发送邮件信息
errMailSend := mailSend(message.String())
if errMailSend != nil {
fmt.Printf("邮件发送失败: %v\n", errMailSend)
}
}
// 将当前的节点状态写入文件
if err := writeNodeStatusToFile(statusFilePath, currentStatus); err != nil {
panic(err.Error()) // 节点状态写入错误时抛出异常
}
}
如何应用
设置为系统启动项,10
分钟运行一次
vim /usr/lib/systemd/system/node-monitor.service
[Install]
WantedBy=multi-user.target
[Unit]
Description=Node Monitor Service
After=network.target
[Service]
User=root
WorkingDirectory=/data/go
ExecStart=/data/go/Node_Monitor
# 总是自动重启,执行完等待10分钟重启一次
Restart=always
RestartSec=600
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl start node-monitor.service
效果展示
总结
通过这个程序,能够有效监控Kubernetes
集群的节点内存使用情况,并根据实时数据动态调整调度策略,从而提升集群的稳定性和可用性。定期的内存监控和及时的调度调整,对于保障生产环境的正常运行至关重要。
本文链接:
/archives/Node_Monitor
版权声明:
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自
Linux 小白鼠!
觉得文章不错,打赏一点吧,1分也是爱~
打赏
微信
支付宝