容器化时代,部署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中完全无需担心这些内容,只需要引入两个包即可将两种依赖打包到可执行文件中。
1import (
2 _ "time/tzdata"
3 _ "golang.org/x/crypto/x509roots/fallback"
4)
5// 根据环境变量设置时区
6func init() {
7 models.Init("./db/llmio.db")
8 location, err := time.LoadLocation(os.Getenv("TZ"))
9 if err != nil {
10 slog.Warn("failed to load timezone, location reset to UTC", "error", err)
11 location = time.UTC
12 }
13 _ = location
14}
github action自动构建
受限于中国大陆地区gfw网络环境,在本地构建并推送往往是一件麻烦事,所以可以使用github action自动帮我们构建多平台镜像并推送到docker hub.
1name: ci build and push docker image
2
3on:
4 push:
5 tags:
6 - 'v*.*.*'
7
8# 1. 设置动态的镜像名称
9env:
10 # 自动构建镜像名称,格式为:<dockerhub-username>/<repo-name>
11 # 例如:'my-docker-user/my-awesome-app'
12 IMAGE_NAME: ${{ github.repository }}
13
14jobs:
15 docker:
16 runs-on: ubuntu-latest
17 steps:
18 - name: Checkout repository
19 uses: actions/checkout@v4
20
21 # 2. 动态生成 Docker 标签 (version 和 latest)
22 - name: Extract metadata (tags, labels) for Docker
23 id: meta
24 uses: docker/metadata-action@v5
25 with:
26 images: ${{ env.IMAGE_NAME }} # 使用我们动态生成的镜像名
27 tags: |
28 type=semver,pattern={{version}}
29 type=raw,value=latest
30
31 - name: Login to Docker Hub
32 uses: docker/login-action@v3
33 with:
34 username: ${{ github.repository_owner }}
35 password: ${{ secrets.DOCKERHUB_TOKEN }}
36
37 - name: Set up QEMU
38 uses: docker/setup-qemu-action@v3
39
40 - name: Set up Docker Buildx
41 uses: docker/setup-buildx-action@v3
42
43 # 3. 构建并推送最终镜像 (使用 meta 步骤生成的动态标签)
44 - name: Build and push final image
45 if: success() # 确保测试通过后才执行
46 uses: docker/build-push-action@v6
47 with:
48 context: .
49 platforms: linux/amd64,linux/arm64
50 push: true
51 tags: ${{ steps.meta.outputs.tags }}
52 labels: ${{ steps.meta.outputs.labels }}
低成本部署
现在一个零依赖的docker镜像已经打包好了,只需要找一家容器托管平台即可部署,这里我选择sealos。
得益于golang程序的低内存占用,我们可以将资源拉到最低,每天仅需8分钱!成本遥遥领先于传统服务器部署。