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