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")
}