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
你的日志在哪里?错误信息是什么?
更多关于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内存,或者通过上述代码优化来减少内存使用。

