为Go web程序构建并发布docker镜像

容器化时代,部署web项目早已不需要购买一台完整的服务器。 我们只需要将程序打包为docker镜像并上传docker hub,容器托管平台(claw run,sealos,zeabur等)便可以通过k8s运行我们的程序,并高度细致分配cpu,内存,硬盘,网络等资源,需要多少用多少,按量付费,相比于购买一台完整的服务器成本更低的同时,也省去了部署的烦琐。 golang作为一门编译型语言,同时可以完全不依赖libc,使得其构建docker镜像时拥有得天独厚的优势。 Dockerfile 这是llmio的DockerFile文件,相比于其他语言可以说是十分简单清晰了。 llmio所使用的数据库是纯go实现的sqlite,没有任何C依赖。 因为需要备份导出sqlite的db文件,需要镜像有一个文件系统,所以我没有使用scratch,而是alpine。 1# Build stage for the frontend 2FROM node:20 AS frontend-build 3WORKDIR /app 4COPY webui/package.json webui/pnpm-lock.yaml ./ 5RUN npm install -g pnpm 6RUN pnpm install 7COPY webui/ . 8RUN pnpm run build 9 10# Build stage for the backend 11FROM golang:latest AS backend-build 12WORKDIR /app 13COPY go.mod go.sum ./ 14RUN GOPROXY=https://goproxy.io,direct go mod download 15COPY . . 16RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o llmio . 17 18# Final stage 19FROM alpine:latest 20 21WORKDIR /app/db 22WORKDIR /app 23 24# Copy the binary from backend build stage 25COPY --from=backend-build /app/llmio . 26 27# Copy the built frontend from frontend build stage 28COPY --from=frontend-build /app/dist ./webui/dist 29 30EXPOSE 7070 31 32# Command to run the application 33CMD ["./llmio"] 时区与TLS证书 当程序需要设置时区或者对外发出https请求时,很多语言会调用操作系统提供的时区配置或openssl,这同样加入了不必要的依赖,然而golang中完全无需担心这些内容,只需要引入两个包即可将两种依赖打包到可执行文件中。 ...

September 5, 2025

Go1.25值得关注的三点

更新到go1.25 只需这三行命令即可 1wget https://dl.google.com/go/go1.25.0.linux-amd64.tar.gz 2rm -rf /usr/local/go 3tar -C /usr/local -xzf go1.25.0.linux-amd64.tar.gz sync.WaitGroup 1func (wg *WaitGroup) Go(f func()) { 2 wg.Add(1) 3 go func() { 4 defer wg.Done() 5 f() 6 }() 7} 新增了一个Go方法,简单封装了原来略显麻烦的使用方式。 synctest 全新的专用于测试并发代码的包。 GOMAXPROCS 现在,GOMAXPROCS会优先根据 cgroup CPU 设置的带宽限制设置数量。

August 13, 2025

React Agent与MCP

2025年的今天,大语言模型的能力早已不再限于chatbot(只会聊天),各家模型都在文本预测的基础上扩展LLM的能力,例如Structured Outputs(结构化输出),Function calling(函数调用),MCP(模型上下文协议),同时,有人提出一种结合LLM思考与行动的协同机制-React。 React Agent 通过让LLM循环执行 推理(Reasoning)->行动(Action)->观察(Observation) 来完成任务。 本质上,可以用下面这段最小代码解释: 1for { 2 response := callLLM(context) 3 if response.ToolCalls { 4 context = executeTools(response.ToolCalls) 5 } 6 if response.Finished { return } 7} ReAct: Synergizing Reasoning and Acting in Language Models eino-React实现 Function calling LLM自身训练的结构化输出能力以及通过openai-api服务实现,让LLM能够按照指定格式输出json格式的数据,来表明自己需要去使用什么函数,参数是什么,同时支持流式生成。 例如: 之后你应该在输入的messages里面加入除user,assistant的另一个角色,名为tool,tool的content内容为函数调用结果,至于函数是如何调用,LLM和openai-api服务并不参与,由开发者执行。此时再次对话,LLM会根据上下文中函数调用结果生成回答。 openai-function-calling MCP MCP (Model Context Protocol)是一种开放协议,用于标准化应用程序如何向大型语言模型(LLMs)提供上下文。可以将 MCP 想象为 AI 应用的 typec 接口。正如 typec 提供了一种标准化的方式将您的设备连接到各种外设和配件,MCP 也提供了一种标准化的方式,将 AI 模型连接到不同的数据源和工具。 ...

July 22, 2025

用GO+Ebiten写一个飞机大战

Ebitengine介绍 Ebitengine (旧称 Ebiten) 是一款由Go 语言开发的开源游戏引擎。Ebitengine 的简单 API 可以让您的 2D 游戏开发更加简单快捷,并支持同时发布到多平台。 安装 1$ go get -u github.com/hajimehoshi/ebiten/v2 示例代码 1// Game implements ebiten.Game interface. 2type Game struct{} 3 4// Update proceeds the game state. 5// Update is called every tick (1/60 [s] by default). 6func (g *Game) Update() error { 7 // Write your game's logical update. 8 return nil 9} 10 11// Draw draws the game screen. 12// Draw is called every frame (typically 1/60[s] for 60Hz display). 13func (g *Game) Draw(screen *ebiten.Image) { 14 // Write your game's rendering. 15} 16 17// Layout takes the outside size (e.g., the window size) and returns the (logical) screen size. 18// If you don't have to adjust the screen size with the outside size, just return a fixed size. 19func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { 20 return 320, 240 21} 22 23func main() { 24 game := &Game{} 25 // Specify the window size as you like. Here, a doubled size is specified. 26 ebiten.SetWindowSize(640, 480) 27 ebiten.SetWindowTitle("Your game's title") 28 // Call ebiten.RunGame to start your game loop. 29 if err := ebiten.RunGame(game); err != nil { 30 log.Fatal(err) 31 } 32} 框架结构 从上面的示例代码可以看出,Ebitengine的使用十分简单,我们只需要一个实现ebiten.Game这个接口的对象,将其传入RunGame即可。 ...

April 6, 2023