Golang中使用go1.22.7访问无头k8s服务时HTTP请求未实现负载均衡

Golang中使用go1.22.7访问无头k8s服务时HTTP请求未实现负载均衡 你好,

我有一个无头 Kubernetes 服务,本地 DNS 解析器会返回该服务背后所有 Pod 的 IP 地址。使用 go1.22.7 版本时,对该服务发出的请求没有得到负载均衡,所有请求都发往了同一个 Pod。HTTP 客户端是通过以下代码片段调用的:

	for {
		client := &http.Client{
			Timeout: 95 * time.Second,
		}

		req, err := http.NewRequest("GET", "http://<service-name>:<port>", nil)
		if err != nil {
			fmt.Println("Error creating request:", err)
			return
		}

		resp, err := client.Do(req)
		if err != nil {
			fmt.Println("Error making request:", err)
			return
		}
		defer resp.Body.Close()
		time.Sleep(5 * time.Second)
	}

使用 go1.23.1 版本时,上述代码发出的请求得到了适当的负载均衡(虽然不确定是否是轮询)。我比较了两个版本的传输层逻辑,但没有找到可能导致此问题的具体原因。各位专家能否帮我解惑?


更多关于Golang中使用go1.22.7访问无头k8s服务时HTTP请求未实现负载均衡的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

如果你知道存在DNS解析缓存,你可以传入一个新的DNS解析器,或者编写一个简单的DNS处理程序来解析域名。

更多关于Golang中使用go1.22.7访问无头k8s服务时HTTP请求未实现负载均衡的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


嗯,Go版本差异导致问题是很常见的(我自己就遇到过好几次)。我认为除了查看源代码中的差异之外,没有其他办法。

我认为必须避免如此严重的版本差异(或者更新你自己的Go开发环境)。

差异是由于这个PR造成的,在1.22.7版本中,由于调用了cgo模式,我们当时正在调用getaddrinfo系统调用。

由于无头服务返回的是各个 Pod 的 IP 地址,客户端(在本例中,即您的 Go 应用程序)需要负责实现负载均衡。如果 Go 应用程序始终只访问其中一个 Pod,就可能看起来负载均衡没有生效。

据我理解,DNS缓存(如果确实被使用)应该只存在于库中。我试图理解两个版本之间存在差异的原因。需要澄清的是,我在相同的设置上运行了完全相同的代码(如上所述),并观察到不同Go版本在解析方面的差异。

在 Go 1.22.7 中,HTTP 客户端默认使用 TransportDialContext 进行 DNS 解析,但不会对无头服务返回的多个 IP 地址进行负载均衡。这是因为默认的 Transport 在建立连接时,会使用 DNS 解析结果中的第一个 IP 地址,并在连接池中复用该连接,导致所有请求都发往同一个 Pod。

以下是一个示例,展示如何在 Go 1.22.7 中通过自定义 Transport 实现负载均衡:

package main

import (
    "context"
    "fmt"
    "net"
    "net/http"
    "time"
)

type loadBalancedTransport struct {
    base *http.Transport
}

func (t *loadBalancedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    // 每次请求都重新解析 DNS 并选择 IP
    host, port, err := net.SplitHostPort(req.URL.Host)
    if err != nil {
        host = req.URL.Host
        port = "80"
    }

    addrs, err := net.DefaultResolver.LookupIPAddr(context.Background(), host)
    if err != nil {
        return nil, err
    }

    if len(addrs) == 0 {
        return nil, fmt.Errorf("no addresses found for %s", host)
    }

    // 简单的轮询选择 IP
    staticIndex := 0 // 实际使用时可能需要原子计数器或更复杂的逻辑
    selectedAddr := addrs[staticIndex%len(addrs)]
    req.URL.Host = net.JoinHostPort(selectedAddr.String(), port)

    return t.base.RoundTrip(req)
}

func main() {
    transport := &loadBalancedTransport{
        base: &http.Transport{
            MaxIdleConns:        100,
            IdleConnTimeout:     90 * time.Second,
            TLSHandshakeTimeout: 10 * time.Second,
        },
    }

    client := &http.Client{
        Transport: transport,
        Timeout:   95 * time.Second,
    }

    for {
        req, err := http.NewRequest("GET", "http://<service-name>:<port>", nil)
        if err != nil {
            fmt.Println("Error creating request:", err)
            return
        }

        resp, err := client.Do(req)
        if err != nil {
            fmt.Println("Error making request:", err)
            return
        }
        defer resp.Body.Close()

        time.Sleep(5 * time.Second)
    }
}

在 Go 1.23.1 中,Transport 的默认行为可能已经优化,对 DNS 解析结果中的多个 IP 地址进行了负载均衡。如果你需要继续使用 Go 1.22.7,可以通过上述自定义 Transport 的方式实现负载均衡。

回到顶部