Golang中简单的HTTP服务器导致Linux网络栈崩溃的问题

Golang中简单的HTTP服务器导致Linux网络栈崩溃的问题 一个非常简单的HTTP服务器,在打包到Docker中后,会在启动几秒钟后导致Linux网络栈崩溃。

正是Go的HTTP服务器导致了这个问题:当注释掉该代码块时,Docker可以正常运行,不会发生挂起。

以下是 main.go 文件:

package main

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

func main() {
/*      http.HandleFunc("/hello", HelloHandler)
        fmt.Printf("Server running (port=8080), route: http://localhost:8080/hello\n")
        if err := http.ListenAndServe(":8080", nil); err != nil {
                log.Fatal(err)
        }*/
        log.Println("hello")
}

func HelloHandler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, World!")
}

以下是 Dockerfile 文件:

# ---- Build stage ----
FROM golang:1.23-alpine AS builder
RUN apk add --no-cache build-base
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o hello_go_http

# ---- Runtime stage ----
FROM alpine:3.20
# Install shell and any useful tools for debugging
RUN apk add --no-cache bash curl
WORKDIR /
# Copy binary from builder
COPY --from=builder /app/hello_go_http .
EXPOSE 8080
ENTRYPOINT ["/hello_go_http"]

使用的命令:

docker build -t hello_go .
docker run -p 8080:8080 --memory=256m --cpus=1  hello_go

更多关于Golang中简单的HTTP服务器导致Linux网络栈崩溃的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

你的日志在哪里?错误信息是什么?

更多关于Golang中简单的HTTP服务器导致Linux网络栈崩溃的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果反过来操作,在运行Web服务器但不将端口暴露给外部世界的情况下运行(即移除 -p 8080:8080),会发生什么?此外,你可以尝试这样做:

docker run -it --entrypoint=sh hello_go

然后手动运行 ./hello_go_http,看看是否能通过这种方式获得更详细的错误信息,或者了解发生了什么情况。

感谢你为我指明了正确的方向!将容器精简到最基础的 Alpine 并使用你的命令启动,它同样崩溃了。

解决方案:正如这里所描述的,使用以下内容编辑 /etc/connman/main.conf

[General]
NetworkInterfaceBlacklist=vmnet,vboxnet,virbr,ifb,docker,veth,eth,wlan

所以这不是 Go 的问题,而是 Docker 的问题。抱歉。

=> 已解决

几乎没有任何发现。只是SSH连接在大约20秒后中断了。这种情况有规律地发生。

只在执行 journalctl -u docker 时发现了以下信息:

Handler for POST /v1.42/containers/57193...6ab/kill returned error: Cannot kill container: 57193...6ab: 
Cannot kill container 57193...6ab: 
unknown error after kill: 
runc did not terminate successfully: 
exit status 1: 
unable to signal init: 
permission denied\n: unknown

是的,直接运行二进制文件并不会导致网络崩溃,所以问题很可能与 Docker 相关。

另一方面,当注释掉那段代码块时,容器运行正常,因此这确实与 HTTP 服务器的启动有关。

docker 日志没有显示任何信息,只有预期的 “Server running (port=8080), …” 消息。

这台 Debian 系统上没有运行 SELinux。

关于暴露端口本身,即使在命令中暴露了端口,当注释掉代码块时,挂起也不会发生。

docker run -p 8080:8080 ...

(从容器打开了端口,但 Go 代码并未监听该端口)

我在本地运行得很好。错误信息中关于容器的提示让我觉得这可能与Docker的某种问题有关。不确定它为什么要尝试终止那个容器。

你能执行 docker logs <container id> 吗?另外,根据你的环境,如果你试图暴露端口或进行类似操作,可能会遇到SELinux或类似的问题。但我认为这很明显是一个Linux/Docker问题。

// 代码示例:获取容器日志
func getContainerLogs(containerID string) (string, error) {
    cmd := exec.Command("docker", "logs", containerID)
    output, err := cmd.Output()
    if err != nil {
        return "", err
    }
    return string(output), nil
}

这是一个典型的容器资源限制导致的网络栈问题。问题在于你的容器内存限制(256MB)可能不足以支持Go的HTTP服务器运行时所需的内存,特别是在处理网络连接时。

根本原因是:当HTTP服务器启动时,Go运行时需要为goroutine、网络缓冲区等分配内存。在内存受限的环境中,这可能导致OOM(内存不足)或网络栈资源耗尽。

以下是修复后的代码示例,增加了内存监控和连接限制:

package main

import (
    "fmt"
    "log"
    "net/http"
    "runtime"
    "time"
)

func main() {
    // 监控内存使用
    go monitorMemory()
    
    // 创建自定义HTTP服务器,限制资源使用
    server := &http.Server{
        Addr:         ":8080",
        Handler:      http.HandlerFunc(HelloHandler),
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  30 * time.Second,
        MaxHeaderBytes: 1 << 20, // 1MB
    }
    
    fmt.Printf("Server running (port=8080), route: http://localhost:8080/hello\n")
    if err := server.ListenAndServe(); err != nil {
        log.Fatal(err)
    }
}

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func monitorMemory() {
    var m runtime.MemStats
    for {
        runtime.ReadMemStats(&m)
        log.Printf("Alloc = %v MiB, TotalAlloc = %v MiB, Sys = %v MiB, NumGC = %v",
            bToMb(m.Alloc), bToMb(m.TotalAlloc), bToMb(m.Sys), m.NumGC)
        time.Sleep(30 * time.Second)
    }
}

func bToMb(b uint64) uint64 {
    return b / 1024 / 1024
}

同时,需要调整Docker运行参数,增加内存限制:

# 增加内存限制到512MB或更多
docker run -p 8080:8080 --memory=512m --cpus=1 hello_go

# 或者完全移除内存限制进行测试
docker run -p 8080:8080 hello_go

如果问题仍然存在,可以尝试以下Dockerfile修改,使用更小的基础镜像并设置适当的系统参数:

# ---- Build stage ----
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o hello_go_http

# ---- Runtime stage ----
FROM gcr.io/distroless/static-debian12
# 使用distroless镜像,更小的攻击面
WORKDIR /
COPY --from=builder /app/hello_go_http .
EXPOSE 8080
ENTRYPOINT ["/hello_go_http"]

对于生产环境,还需要考虑设置以下Linux内核参数:

# 在宿主机上设置
sudo sysctl -w net.core.somaxconn=1024
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=1024
sudo sysctl -w vm.overcommit_memory=1

# 或者在docker run时设置
docker run -p 8080:8080 \
  --sysctl net.core.somaxconn=1024 \
  --sysctl net.ipv4.tcp_max_syn_backlog=1024 \
  hello_go

问题的核心是256MB内存限制对于Go HTTP服务器可能过于严格,特别是在容器环境中还需要考虑系统开销。建议至少提供512MB内存,或者通过上述代码优化来减少内存使用。

回到顶部