go应用注册到kong

发布于:2025-08-16 ⋅ 阅读:(18) ⋅ 点赞:(0)

1、描述

go web应用在启动后通过kong api注册到upstreamname,在服务关闭取消注册。

2、部署测试环境

部署postgresql数据库

cat > pg.sh << 'EOF'
docker run -itd --name postgres  --net host --restart=always\
  -e "POSTGRES_USER=kong" \
  -e "POSTGRES_DB=kong" \
  -e "POSTGRES_PASSWORD=123456" \
  -v /data1/postgresql/data:/var/lib/postgresql/data \
  -v /etc/localtime:/etc/localtime \
     postgres:9.6
EOF

###########注释
POSTGRES_USER=连接数据用户
POSTGRES_DB=数据库名
POSTGRES_PASSWORD=数据库密码
/var/lib/postgresql/data 数据库存本地存储路径
##############################

部署kong

初始化pg数据库表结构
docker run -itd  --rm  --user=root\
        -v /data1/kong/log:/usr/local/kong/logs \
        -v /etc/localtime:/etc/localtime \
        -e KONG_DATABASE="postgres" \
        -e KONG_PG_DATABASE="kong" \
        -e KONG_PG_HOST="本机IP地址" \
        -e KONG_PG_PORT="5432" \
        -e KONG_PG_USER="kong" \
        -e KONG_PG_PASSWORD="123456" \
        -e KONG_PG_SSL=off \
        -e KONG_ADMIN_LISTEN="0.0.0.0:8001" \
        -e KONG_PROXY_LISTEN="0.0.0.0:80,0.0.0.0:443 ssl" \
        -e KONG_LOG_LEVEL=error \
        -e KONG_PROXY_ERROR_LOG=/usr/local/kong/logs/error.log \
        -e KONG_ADMIN_GUI_LISTEN=off \
        -e KONG_STATUS_LISTEN=off \
         kong/kong-gateway:3.6.1.4  kong migrations bootstrap 

启动kong
docker run -itd --name=kong --restart=always  --net=host  --user=root\
        -v /data1/kong/log:/usr/local/kong/logs \
        -v /etc/localtime:/etc/localtime \
        -e KONG_DATABASE="postgres" \
        -e KONG_PG_DATABASE="kong" \
        -e KONG_PG_HOST="本机IP地址" \
        -e KONG_PG_PORT="5432" \
        -e KONG_PG_USER="kong" \
        -e KONG_PG_PASSWORD="123456" \
        -e KONG_PG_SSL=off \
        -e KONG_ADMIN_LISTEN="0.0.0.0:8001" \
        -e KONG_PROXY_LISTEN="0.0.0.0:80,0.0.0.0:443 ssl" \
        -e KONG_LOG_LEVEL=error \
        -e KONG_PROXY_ERROR_LOG=/usr/local/kong/logs/error.log \
        -e KONG_ADMIN_GUI_LISTEN=off \
        -e KONG_STATUS_LISTEN=off \
         kong/kong-gateway:3.6.1.4
docker logs -f kong

部署konga
konga是kong的一个开源管理台界面

cat > konga.sh << 'EOF'
docker run -itd --name=konga  --net=host \
-v "/data1/konga/kongadata:/app/kongadata" \
-e "HOST=0.0.0.0" \
pantsel/konga:0.14.9
EOF

访问konga:http://本机IP:1337

2、项目结构

newtest
├── main.go
└── initkong
└── kong.go

kong.go

package initkong

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"
)

// 获取upstreamname
func Getupstream(upstreamname, kongaadminurl string) string {

    // kongadmin api地址
    url := fmt.Sprintf(kongaadminurl + "upstreams/" + upstreamname)

    // 创建GET请求
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
       log.Fatalf("创建GET请求失败: %v", err)
    }

    // 设置请求头
    req.Header.Add("Accept", "application/json")
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("Kong-Admin-Token", " ")

    // 发送请求
    res, err := http.DefaultClient.Do(req)
    if err != nil {
       log.Fatalf("发送请求失败: %v", err)
    }

    // 将响应体字节流转换为字节数组([]byte),方便后续处理。
    body, err := io.ReadAll(res.Body)
    if err != nil {
       log.Fatalf("字节流转换为字节数组失败: %v", err)
    }

    // 请求结束关闭连接
    defer res.Body.Close()

    // 返回upstream信息用于判断upstreamname name是否存在
    return string(body)
}

// 创建upstreamname
func Createupstream(upstreamname, kongaadminurl string) string {
    // kongadmin api地址
    url := fmt.Sprintf(kongaadminurl + "upstreams")

    // 定义请求体
    payload := strings.NewReader(fmt.Sprintf(`{
       "name": "%s",
       "healthchecks": {
          "active": {
             "https_verify_certificate": true,
             "healthy": {
                "http_statuses": [200, 302],
                "successes": 3,
                "interval": 3
             },
             "unhealthy": {
                "http_failures": 3,
                "http_statuses": [429, 404, 500, 501, 502, 503, 504, 505],
                "timeouts": 3,
                "tcp_failures": 3,
                "interval": 3
             },
             "type": "http",
             "concurrency": 10,
             "timeout": 3,
             "http_path": "/check"
          }
       }
    }`, upstreamname))

    // 创建 POST 请求
    req, err := http.NewRequest("POST", url, payload)
    if err != nil {
       log.Fatalf("创建post请求失败: %v", err)
    }

    // 设置请求头(Header)
    req.Header.Add("Accept", "application/json")
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("Kong-Admin-Token", " ")

    // 发送请求并获取响应
    res, err := http.DefaultClient.Do(req)
    if err != nil {
       log.Fatalf("发送post请求失败: %v", err)
    }

    // 处理响应
    body, err := io.ReadAll(res.Body)
    if err != nil {
       log.Fatalf("处理响应失败: %v", err)
    }

    // 关闭连接
    defer res.Body.Close()

    return string(body)
}

func Createtarget(upstreamname, hostnetwork, kongaadminurl string) string {
    // 请求地址
    url := kongaadminurl + "upstreams/" + upstreamname + "/targets"

    // 定义请求体
    payload := strings.NewReader(fmt.Sprintf(`{"target": "%s"}`, hostnetwork))

    // 发送post请求
    req, err := http.NewRequest("POST", url, payload)
    if err != nil {
       log.Fatalf("创建post请求失败: %v", err)
    }

    // 设置请求头(Header)
    req.Header.Add("Accept", "application/json")
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("Kong-Admin-Token", " ")

    // 发送请求并获取响应
    res, err := http.DefaultClient.Do(req)
    if err != nil {
       log.Fatalf("发送post请求失败: %v", err)
    }

    // 关闭连接
    defer res.Body.Close()

    // 处理响应
    body, err := io.ReadAll(res.Body)
    if err != nil {
       log.Fatalf("处理响应失败: %v", err)
    }
    return string(body)
}

// 取消本机服务注册到kong
func Deletetarget(upstreamname, hostnetwork, kongaadminurl string) string {
    // 请求地址
    url := kongaadminurl + "upstreams/" + upstreamname + "/targets/" + hostnetwork

    // 创建post请求
    req, err := http.NewRequest("DELETE", url, nil)
    if err != nil {
       log.Fatalf("创建post请求失败: %v", err)
    }

    // 设置请求头(Header)
    req.Header.Add("Content-Type", "application/json")
    req.Header.Add("Kong-Admin-Token", " ")

    // 发送post请求
    res, err := http.DefaultClient.Do(req)
    if err != nil {
       log.Fatalf("发送post请求失败: %v", err)
    }

    // 关闭连接
    defer res.Body.Close()

    // 处理响应
    body, err := io.ReadAll(res.Body)
    if err != nil {
       log.Fatalf("处理响应失败: %v", err)
    }

    return string(body)
}

main.go

  • 根据自己的情况进行修改 kong api地址:kongaadminurl = “http://xx.xx.xx.xx:8001/”
  • http端口:webport = “8080”
  • upstream名字:getupstreamdata = “test”
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/gin-gonic/gin"
    "log"
    "net"
    "net/http"
    // 引用自定义服务对接kong api函数
    "newtest/initkong"
    "os"
    "os/signal"
    "syscall"
    "time"
)

// kong api地址
const kongaadminurl = "http://xx.xx.xx.xx:8001/"

// http端口
const webport = "8080"

// upstream名字
var getupstreamdata = "test"

func main() {

    // 获取主机ip
    hostip, err := GetLocalIP()
    if err != nil {
       log.Fatalf("主机ip获取失败: %v", err)
    }
    hostnetwork := hostip + ":" + webport

    // 启动http服务
    r := gin.Default()
    r.GET("/healthchecks", ping)

    //注册服务到kong
    go func() {
       konginit(hostnetwork)
    }()

    // 启动服务(非阻塞方式)
    srv := &http.Server{
       Addr:    hostnetwork,
       Handler: r,
    }

    go func() {
       log.Printf("Gin服务启动,监听地址: %s", hostnetwork)
       // 启动服务,注意Gin的Run方法本质也是调用ListenAndServe
       if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
          log.Fatalf("服务启动失败: %v", err)
       }
    }()

    // 监听中断信号(Ctrl+C 或 kill 命令)
    quit := make(chan os.Signal, 1)
    // 接收 SIGINT 和 SIGTERM 信号
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit // 阻塞等待信号
    log.Println("收到关闭信号,准备停止服务...")

    // 执行Kong目标删除操作
    deleteResult := initkong.Deletetarget(getupstreamdata, hostnetwork, kongaadminurl)
    log.Printf("删除Kong目标结果: %v", deleteResult)

    // 优雅关闭服务:设置60秒超时,处理完当前请求后关闭
    ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
       log.Fatalf("服务关闭失败: %v", err)
    }

    log.Println("Gin服务已完全关闭")

}

func ping(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
       "message": "ok",
    })
}

func konginit(hostnetwork string) {

    // 获取upstreamname 信息
    upstreamname := initkong.Getupstream(getupstreamdata, kongaadminurl)

    // 解析upstreamname 信息获取name字段值
    var dataname struct {
       Name string `json:"name"`
    }
    err := json.Unmarshal([]byte(upstreamname), &dataname)
    if err != nil {
       log.Fatalf("upstreamname获取失败: %v", err)
       return
    }

    // 判断是否存在upstreamname,如果不存在则创建
    switch {
    case dataname.Name == getupstreamdata:
       fmt.Println("upstreamname存在")
    case dataname.Name != getupstreamdata:
       creatdata := initkong.Createupstream(getupstreamdata, kongaadminurl)
       fmt.Println(creatdata)
    default:
       fmt.Println("其他问题")
    }

    // 注册服务到kong
    createkongtarget := initkong.Createtarget(getupstreamdata, hostnetwork, kongaadminurl)
    fmt.Println(createkongtarget)

}

// 获取主机ip
func GetLocalIP() (string, error) {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
       return "", err
    }
    for _, addr := range addrs {
       // 检查地址类型,并排除 loopback
       if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
          // 只要 IPv4 地址
          if ipNet.IP.To4() != nil {
             return ipNet.IP.String(), nil
          }
       }
    }
    return "", fmt.Errorf("no local IP found")
}

网站公告

今日签到

点亮在社区的每一天
去签到