wip v1.0.0

This commit is contained in:
loveuer
2025-12-08 22:23:45 +08:00
commit bece440c47
35 changed files with 4893 additions and 0 deletions

25
internal/cmd/root.go Normal file
View File

@@ -0,0 +1,25 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "go-alived",
Short: "Go-Alived - VRRP High Availability Service",
Long: `go-alived is a lightweight, dependency-free VRRP implementation in Go.
It provides high availability for IP addresses with health checking support.`,
Version: "1.0.0",
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.CompletionOptions.DisableDefaultCmd = true
}

133
internal/cmd/run.go Normal file
View File

@@ -0,0 +1,133 @@
package cmd
import (
"os"
"os/signal"
"syscall"
"github.com/loveuer/go-alived/internal/health"
"github.com/loveuer/go-alived/internal/vrrp"
"github.com/loveuer/go-alived/pkg/config"
"github.com/loveuer/go-alived/pkg/logger"
"github.com/spf13/cobra"
)
var (
configFile string
debug bool
)
var runCmd = &cobra.Command{
Use: "run",
Short: "Run the VRRP service",
Long: `Start the go-alived VRRP service with health checking.`,
Run: runService,
}
func init() {
rootCmd.AddCommand(runCmd)
runCmd.Flags().StringVarP(&configFile, "config", "c", "/etc/go-alived/config.yaml", "path to configuration file")
runCmd.Flags().BoolVarP(&debug, "debug", "d", false, "enable debug mode")
}
func runService(cmd *cobra.Command, args []string) {
log := logger.New(debug)
log.Info("starting go-alived...")
log.Info("loading configuration from: %s", configFile)
cfg, err := config.Load(configFile)
if err != nil {
log.Error("failed to load configuration: %v", err)
os.Exit(1)
}
log.Info("configuration loaded successfully")
log.Debug("config: %+v", cfg)
healthMgr, err := health.LoadFromConfig(cfg, log)
if err != nil {
log.Error("failed to load health check configuration: %v", err)
os.Exit(1)
}
vrrpMgr := vrrp.NewManager(log)
if err := vrrpMgr.LoadFromConfig(cfg); err != nil {
log.Error("failed to load VRRP configuration: %v", err)
os.Exit(1)
}
setupHealthTracking(vrrpMgr, healthMgr, log)
healthMgr.StartAll()
if err := vrrpMgr.StartAll(); err != nil {
log.Error("failed to start VRRP instances: %v", err)
os.Exit(1)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
for {
sig := <-sigChan
switch sig {
case syscall.SIGHUP:
log.Info("received SIGHUP, reloading configuration...")
newCfg, err := config.Load(configFile)
if err != nil {
log.Error("failed to reload configuration: %v", err)
continue
}
if err := vrrpMgr.Reload(newCfg); err != nil {
log.Error("failed to reload VRRP: %v", err)
continue
}
cfg = newCfg
log.Info("configuration reloaded successfully")
case syscall.SIGINT, syscall.SIGTERM:
log.Info("received signal %v, shutting down...", sig)
cleanup(log, vrrpMgr, healthMgr)
os.Exit(0)
}
}
}
func cleanup(log *logger.Logger, vrrpMgr *vrrp.Manager, healthMgr *health.Manager) {
log.Info("cleaning up resources...")
healthMgr.StopAll()
vrrpMgr.StopAll()
}
func setupHealthTracking(vrrpMgr *vrrp.Manager, healthMgr *health.Manager, log *logger.Logger) {
instances := vrrpMgr.GetAllInstances()
for _, inst := range instances {
for _, trackScript := range inst.TrackScripts {
monitor, ok := healthMgr.GetMonitor(trackScript)
if !ok {
log.Warn("[%s] track_script '%s' not found in health checkers", inst.Name, trackScript)
continue
}
instanceName := inst.Name
monitor.OnStateChange(func(checkerName string, oldHealthy, newHealthy bool) {
vrrpInst, ok := vrrpMgr.GetInstance(instanceName)
if !ok {
return
}
if newHealthy && !oldHealthy {
log.Info("[%s] health check '%s' recovered, resetting priority", instanceName, checkerName)
vrrpInst.ResetPriority()
} else if !newHealthy && oldHealthy {
log.Warn("[%s] health check '%s' failed, decreasing priority", instanceName, checkerName)
vrrpInst.AdjustPriority(-10)
}
})
log.Info("[%s] tracking health check: %s", inst.Name, trackScript)
}
}
}

470
internal/cmd/test.go Normal file
View File

@@ -0,0 +1,470 @@
package cmd
import (
"fmt"
"net"
"os"
"os/exec"
"strings"
"time"
"github.com/loveuer/go-alived/pkg/logger"
"github.com/loveuer/go-alived/pkg/netif"
"github.com/spf13/cobra"
)
type TestResult struct {
Name string
Pass bool
Message string
Fatal bool
}
type EnvironmentTest struct {
log *logger.Logger
results []TestResult
errors int
warns int
}
func NewEnvironmentTest(log *logger.Logger) *EnvironmentTest {
return &EnvironmentTest{
log: log,
results: make([]TestResult, 0),
}
}
func (t *EnvironmentTest) AddResult(name string, pass bool, message string, fatal bool) {
t.results = append(t.results, TestResult{
Name: name,
Pass: pass,
Message: message,
Fatal: fatal,
})
if !pass {
if fatal {
t.errors++
} else {
t.warns++
}
}
}
func (t *EnvironmentTest) TestRootPermission() {
t.log.Info("检查运行权限...")
if os.Geteuid() != 0 {
t.AddResult("Root权限", false, "需要root权限运行请使用sudo", true)
} else {
t.AddResult("Root权限", true, "以root用户运行", false)
}
}
func (t *EnvironmentTest) TestNetworkInterface(ifaceName string) string {
t.log.Info("检查网络接口...")
if ifaceName == "" {
interfaces, err := net.Interfaces()
if err != nil {
t.AddResult("网络接口", false, "无法获取网络接口列表", true)
return ""
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 {
addrs, err := iface.Addrs()
if err == nil && len(addrs) > 0 {
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() != nil {
ifaceName = iface.Name
t.log.Info("自动选择网卡: %s", ifaceName)
break
}
}
}
if ifaceName != "" {
break
}
}
}
if ifaceName == "" {
t.AddResult("网络接口", false, "未找到可用的网络接口", true)
return ""
}
}
iface, err := netif.GetInterface(ifaceName)
if err != nil {
t.AddResult("网络接口", false, fmt.Sprintf("网卡 %s 不存在", ifaceName), true)
return ""
}
if !iface.IsUp() {
t.AddResult("网络接口状态", false, fmt.Sprintf("网卡 %s 未启动", ifaceName), true)
return ""
}
t.AddResult("网络接口", true, fmt.Sprintf("网卡 %s 存在且已启动", ifaceName), false)
return ifaceName
}
func (t *EnvironmentTest) TestVIPOperations(ifaceName, testVIP string) {
t.log.Info("测试VIP添加/删除功能...")
if ifaceName == "" || testVIP == "" {
t.AddResult("VIP操作", false, "网卡名或测试VIP为空", true)
return
}
iface, err := netif.GetInterface(ifaceName)
if err != nil {
t.AddResult("VIP操作", false, fmt.Sprintf("获取网卡失败: %v", err), true)
return
}
if !strings.Contains(testVIP, "/") {
testVIP = testVIP + "/32"
}
exists, _ := iface.HasIP(testVIP)
if exists {
t.AddResult("VIP操作", false, fmt.Sprintf("VIP %s 已存在请使用其他IP测试", testVIP), true)
return
}
err = iface.AddIP(testVIP)
if err != nil {
t.AddResult("VIP添加", false, fmt.Sprintf("VIP添加失败: %v", err), true)
return
}
t.AddResult("VIP添加", true, fmt.Sprintf("成功添加VIP %s", testVIP), false)
time.Sleep(100 * time.Millisecond)
exists, _ = iface.HasIP(testVIP)
if !exists {
t.AddResult("VIP验证", false, "VIP添加后无法在网卡上找到", true)
iface.DeleteIP(testVIP)
return
}
t.AddResult("VIP验证", true, "VIP已成功添加到网卡", false)
vipAddr := strings.Split(testVIP, "/")[0]
cmd := exec.Command("ping", "-c", "1", "-W", "1", vipAddr)
err = cmd.Run()
if err != nil {
t.AddResult("VIP可达性", false, "VIP ping失败可能需要路由配置", false)
} else {
t.AddResult("VIP可达性", true, "VIP可以ping通", false)
}
err = iface.DeleteIP(testVIP)
if err != nil {
t.AddResult("VIP删除", false, fmt.Sprintf("VIP删除失败: %v", err), false)
} else {
t.AddResult("VIP删除", true, "VIP删除成功", false)
}
}
func (t *EnvironmentTest) TestMulticast(ifaceName string) {
t.log.Info("检查组播支持...")
if ifaceName == "" {
t.AddResult("组播支持", false, "网卡名为空,跳过检查", false)
return
}
cmd := exec.Command("ip", "maddr", "show", ifaceName)
output, err := cmd.CombinedOutput()
if err != nil {
t.AddResult("组播支持", false, "无法查询组播配置", false)
return
}
if len(output) > 0 {
t.AddResult("组播支持", true, "网卡支持组播", false)
} else {
t.AddResult("组播支持", false, "网卡组播支持未知", false)
}
}
func (t *EnvironmentTest) TestFirewall() {
t.log.Info("检查防火墙设置...")
cmd := exec.Command("iptables", "-L", "INPUT", "-n")
output, err := cmd.CombinedOutput()
if err != nil {
t.AddResult("防火墙检查", false, "无法查询iptables规则可能未安装", false)
return
}
if strings.Contains(string(output), "112") || strings.Contains(string(output), "vrrp") {
t.AddResult("防火墙VRRP", true, "防火墙已配置VRRP规则", false)
} else {
t.AddResult("防火墙VRRP", false, "防火墙未配置VRRP规则建议添加: iptables -A INPUT -p 112 -j ACCEPT", false)
}
cmd = exec.Command("systemctl", "is-active", "firewalld")
err = cmd.Run()
if err == nil {
cmd = exec.Command("firewall-cmd", "--list-protocols")
output, err = cmd.CombinedOutput()
if err == nil {
if strings.Contains(string(output), "vrrp") {
t.AddResult("Firewalld VRRP", true, "firewalld已允许VRRP协议", false)
} else {
t.AddResult("Firewalld VRRP", false, "firewalld未配置VRRP建议: firewall-cmd --permanent --add-protocol=vrrp", false)
}
}
}
}
func (t *EnvironmentTest) TestKernelParameters() {
t.log.Info("检查内核参数...")
params := map[string]string{
"/proc/sys/net/ipv4/ip_forward": "1",
"/proc/sys/net/ipv4/conf/all/arp_ignore": "0",
"/proc/sys/net/ipv4/conf/all/arp_announce": "0",
}
for path, expected := range params {
data, err := os.ReadFile(path)
if err != nil {
continue
}
value := strings.TrimSpace(string(data))
name := strings.TrimPrefix(path, "/proc/sys/net/ipv4/")
if value == expected {
t.AddResult(name, true, fmt.Sprintf("%s = %s (正常)", name, value), false)
} else {
if name == "ip_forward" && value != "1" {
t.AddResult(name, false, fmt.Sprintf("%s = %s (建议设置为1)", name, value), false)
}
}
}
}
func (t *EnvironmentTest) TestConflictingServices() {
t.log.Info("检查冲突服务...")
services := []string{"keepalived"}
hasConflict := false
for _, service := range services {
cmd := exec.Command("systemctl", "is-active", service)
err := cmd.Run()
if err == nil {
t.AddResult("服务冲突", false, fmt.Sprintf("发现运行中的%s服务可能冲突", service), false)
hasConflict = true
}
}
cmd := exec.Command("pgrep", "-x", "keepalived")
err := cmd.Run()
if err == nil {
t.AddResult("进程冲突", false, "发现运行中的keepalived进程", false)
hasConflict = true
}
if !hasConflict {
t.AddResult("服务冲突", true, "未发现冲突的服务", false)
}
}
func (t *EnvironmentTest) TestVirtualization() {
t.log.Info("检查虚拟化环境...")
productFile := "/sys/class/dmi/id/product_name"
data, err := os.ReadFile(productFile)
if err != nil {
cmd := exec.Command("systemd-detect-virt")
output, err := cmd.CombinedOutput()
if err == nil {
virt := strings.TrimSpace(string(output))
if virt != "none" {
t.AddResult("虚拟化", true, fmt.Sprintf("检测到虚拟化环境: %s", virt), false)
t.log.Warn("虚拟化环境可能需要特殊配置(如启用混杂模式)")
} else {
t.AddResult("虚拟化", true, "物理机环境", false)
}
}
return
}
product := strings.TrimSpace(string(data))
switch {
case strings.Contains(product, "VMware"):
t.AddResult("虚拟化", true, "VMware虚拟机需要启用混杂模式", false)
t.log.Warn("VMware需要配置: 虚拟机设置 -> 网络适配器 -> 高级 -> 混杂模式: 允许全部")
case strings.Contains(product, "VirtualBox"):
t.AddResult("虚拟化", true, "VirtualBox虚拟机需要桥接模式+混杂模式)", false)
t.log.Warn("VirtualBox需要配置: 网络 -> 桥接网卡 -> 高级 -> 混杂模式: 全部允许")
case strings.Contains(product, "KVM") || strings.Contains(product, "QEMU"):
t.AddResult("虚拟化", true, "KVM/QEMU虚拟机通常支持良好", false)
case strings.Contains(product, "Amazon") || strings.Contains(product, "EC2"):
t.AddResult("虚拟化", false, "AWS EC2环境 - 不支持VRRP", true)
t.log.Error("AWS不支持组播协议无法运行VRRP请使用Elastic IP或负载均衡")
default:
t.AddResult("虚拟化", true, fmt.Sprintf("环境: %s", product), false)
}
}
func (t *EnvironmentTest) TestCloudEnvironment() {
t.log.Info("检查云环境...")
cloudTests := []struct {
name string
url string
headers map[string]string
isFatal bool
solution string
}{
{
name: "AWS",
url: "http://169.254.169.254/latest/meta-data/instance-id",
solution: "AWS不支持VRRP请使用: Elastic IP、ALB或NLB",
isFatal: true,
},
{
name: "阿里云",
url: "http://100.100.100.200/latest/meta-data/instance-id",
solution: "阿里云ECS不支持VRRP请使用: 负载均衡SLB或高可用虚拟IP(HaVip)",
isFatal: true,
},
{
name: "Azure",
url: "http://169.254.169.254/metadata/instance?api-version=2021-02-01",
headers: map[string]string{"Metadata": "true"},
solution: "Azure建议使用: Azure Load Balancer或Traffic Manager",
isFatal: false,
},
{
name: "Google Cloud",
url: "http://metadata.google.internal/computeMetadata/v1/instance/id",
headers: map[string]string{"Metadata-Flavor": "Google"},
solution: "GCP建议使用: Cloud Load Balancing",
isFatal: false,
},
}
cloudDetected := false
for _, test := range cloudTests {
cmd := exec.Command("curl", "-s", "-m", "1", test.url)
if len(test.headers) > 0 {
for k, v := range test.headers {
cmd.Args = append(cmd.Args, "-H", fmt.Sprintf("%s: %s", k, v))
}
}
err := cmd.Run()
if err == nil {
cloudDetected = true
t.AddResult("云环境", !test.isFatal, fmt.Sprintf("检测到%s环境", test.name), test.isFatal)
t.log.Warn(test.solution)
}
}
if !cloudDetected {
t.AddResult("云环境", true, "未检测到公有云环境限制", false)
}
}
func (t *EnvironmentTest) PrintResults() {
fmt.Println()
fmt.Println("=== 测试结果 ===")
fmt.Println()
for _, result := range t.results {
status := "✓"
if !result.Pass {
if result.Fatal {
status = "✗"
} else {
status = "⚠"
}
}
fmt.Printf("%s %-20s %s\n", status, result.Name, result.Message)
}
fmt.Println()
fmt.Println("=== 总结 ===")
fmt.Println()
if t.errors == 0 && t.warns == 0 {
fmt.Println("✓ 环境完全支持 go-alived")
fmt.Println(" 可以正常使用所有功能")
} else if t.errors == 0 {
fmt.Printf("⚠ 环境基本支持,但有 %d 个警告\n", t.warns)
fmt.Println(" 建议修复警告项以获得更好的稳定性")
} else {
fmt.Printf("✗ 发现 %d 个错误, %d 个警告\n", t.errors, t.warns)
fmt.Println(" 请修复错误后再使用 go-alived")
}
fmt.Println()
}
func (t *EnvironmentTest) HasErrors() bool {
return t.errors > 0
}
var (
testIface string
testVIP string
)
var testCmd = &cobra.Command{
Use: "test",
Short: "Test environment for VRRP support",
Long: `Test the current environment to verify if it supports VRRP functionality.
This includes checking permissions, network interfaces, VIP operations, multicast support, and more.`,
Run: runTest,
}
func init() {
rootCmd.AddCommand(testCmd)
testCmd.Flags().StringVarP(&testIface, "interface", "i", "", "network interface to test (auto-detect if not specified)")
testCmd.Flags().StringVarP(&testVIP, "vip", "v", "", "test VIP address (e.g., 192.168.1.100/24)")
}
func runTest(cmd *cobra.Command, args []string) {
log := logger.New(false)
fmt.Println("=== go-alived 环境测试 ===")
fmt.Println()
test := NewEnvironmentTest(log)
test.TestRootPermission()
selectedIface := test.TestNetworkInterface(testIface)
if selectedIface != "" && testVIP != "" {
test.TestVIPOperations(selectedIface, testVIP)
}
if selectedIface != "" {
test.TestMulticast(selectedIface)
}
test.TestFirewall()
test.TestKernelParameters()
test.TestConflictingServices()
test.TestVirtualization()
test.TestCloudEnvironment()
test.PrintResults()
if test.HasErrors() {
os.Exit(1)
}
}