【SOP实操】一台高配服务器,用 k3s + Gitea 从零搭建 Golang 应用的轻量级生产发布环境
一、对于云部署解决方案,为什么选 k3s,而不是标准 k8s
虽然AI时代,但作为软件工程的重要技术工程部分,还是要深入掌握云的使用。
在“一台高配服务器”这个约束下,标准 Kubernetes 的控制面本身就是一种负担。kubeadm 部署的 k8s 控制面包含 API Server、Scheduler、Controller Manager、etcd 等独立组件,仅控制面就需要至少 2 vCPU + 2 GB RAM,且各组件独立部署、独立排障。k3s 则把这些打包成一个不到 70 MB 的单一二进制,默认使用 SQLite 作为单节点数据存储,并内置 containerd、Flannel、Traefik Ingress 和 Klipper LB。关键事实:k3s 是 CNCF 认证的 Kubernetes 发行版,不是 fork,也不是“简化版 API”——任何合法的 Kubernetes manifest 在 k3s 上同样生效。
| 维度 | 标准 k8s(kubeadm) | k3s(单节点模式) |
|---|---|---|
| 控制面形态 | 多组件独立部署 | 单一二进制 |
| 默认数据存储 | etcd(必须) | SQLite(单节点);etcd 可选 |
| 控制面最低资源 | 2 vCPU + 2 GB RAM(不含 etcd 和工作负载) | 官方资源剖析:Server 带工作负载约 6% CPU + 1.6 GB RAM(x86) |
| 内置组件 | 需自行安装 CNI、Ingress、LB | 内置 Flannel、Traefik、Klipper LB、containerd |
| API 兼容性 | 标准 Kubernetes API | 100% 兼容 |
| 适用场景 | 多节点、大规模、企业级高可用 | 边缘、单机/小集群、轻量生产 |
需要特别说明一个常见的误读:k3s 官方“最低 512 MB”指的是 agent 节点的最低门槛,且只是“能启动”级别。真实生产工作负载下,官方资源剖析显示 Server 带工作负载的基线约为 1.6 GB RAM(SQLite/Kine)或 1.6 GB RAM(嵌入式 etcd),CPU 占用约 6%(以 Intel 8375C 为基准)。因此,“一台高配服务器”的正确配置区间是 4–8 vCPU、8–16 GB RAM、NVMe SSD,而不是贴着最低线走。
单节点的根本限制在于控制面单点:Server 一旦重启或宕机,Agent、Ingress、API 全部失联,业务随之中断。本文的方案定位是“轻量级生产环境”——用单节点承载中小流量业务,通过流程规范、备份和资源隔离来管理风险,而不是追求控制面高可用。当业务增长到无法容忍控制面中断时,再按 k3s 的演进路径将 SQLite 平滑迁移到三节点嵌入式 etcd。
二、硬件配置建议与技术栈总览
硬件配置
单台高配服务器建议配置:
| 资源 | 建议值 | 说明 |
|---|---|---|
| CPU | 4–8 vCPU | k3s 控制面开销低,主要留给 Gitea、Runner 构建和应用负载 |
| 内存 | 8–16 GB | Gitea 小团队约 2–4 GB;Runner 构建峰值额外 2–4 GB;k3s 控制面约 1.6 GB;剩余给应用 |
| 磁盘 | 200 GB+ NVMe SSD | 镜像层、Gitea 仓库、SQLite 数据库、构建缓存都需要低延迟存储 |
| 操作系统 | Ubuntu 22.04 / 24.04 LTS | 社区验证充分,k3s 兼容性好 |
如果同时运行 Gitea、act_runner 和一个简单的 Golang Web 应用,4 vCPU / 8 GB RAM 是舒适起点;如果 Runner 构建频繁,8 vCPU / 16 GB 留出足够余量。
架构与数据流
1 | 开发者 git push |
这个方案将 Gitea Registry、act_runner、k3s 压缩在同一台机器上,镜像拉取不出节点,构建和部署链路极短。核心决策是 用 Gitea 自带的 Container Registry 作为唯一镜像仓库,避免引入外部 registry 带来的认证和网络复杂度。
三、基础环境准备
以下步骤在目标服务器上以具有 sudo 权限的用户执行。假设服务器 IP 为 SERVER_IP,文中以 192.0.2.10 示例。
3.1 系统基础配置
1 | # 更新系统 |
3.2 安装 k3s(单节点)
1 | curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \ |
这里显式禁用了内置的 Traefik。原因:本方案中 Gitea 和应用都通过 Service/Ingress 暴露,如果开发者希望完全掌控 Ingress 层,可以自行部署 ingress-nginx;如果不想额外管理,保留 Traefik 也能工作。禁用 Traefik 是为了减少一个需要理解和调试的组件,但开发者需要自行解决 Ingress 或使用 NodePort 暴露服务。
**为简化首次跑通,建议保留 Traefik,去掉 --disable=traefik**:
1 | curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --write-kubeconfig-mode=644" sh - |
等待节点就绪:
1 | sudo k3s kubectl get nodes |
为方便后续操作,将 kubeconfig 放到当前用户可读位置:
1 | mkdir -p ~/.kube |
现在 kubectl 可以直接使用。
四、部署 Gitea(代码托管 + 镜像仓库 + Actions 源)
Gitea 以 Deployment + PVC 的方式部署在 k3s 内。使用 SQLite 作为 Gitea 自身数据库,进一步降低依赖;对于 ~10 人团队,2–4 GB RAM 和 2–4 核 CPU 是官方推荐区间。
4.1 创建 namespace 和 PVC
1 | # gitea-namespace.yaml |
1 | kubectl apply -f gitea-namespace.yaml |
k3s 默认提供 local-path StorageClass,PVC 会自动绑定到节点本地路径,适合单节点场景。
4.2 部署 Gitea
1 | # gitea-deployment.yaml |
ROOT_URL 使用了 nip.io 通配 DNS,省去修改 hosts 的麻烦;生产环境如果有真实域名,替换为真实域名即可。
1 | kubectl apply -f gitea-deployment.yaml |
浏览器访问 http://192.0.2.10:30300,首次进入安装页面。保持默认 SQLite 配置,设置管理员账号,完成安装。安装完成后,登录 Gitea,进入 设置 → 应用,生成一个 API Token 或直接使用管理员账号的密码用于后续 Runner 注册和 Registry 认证。
4.3 启用 Gitea Container Registry
Gitea 内置 Container Registry,无需额外部署 Harbor 或其他 registry。在 app.ini 中确认或添加:
1 | [packages] |
通过环境变量注入更符合 Kubernetes 原生方式。在 Deployment 的 env 中追加:
1 | - name: GITEA__packages__ENABLED |
重新 apply 后,Gitea Registry 的访问地址为:
1 | gitea.192.0.2.10.nip.io:30300/<owner>/<image>:<tag> |
后续 act_runner 构建镜像后推送至此,k3s 的 containerd 从同一地址拉取。
五、配置 k3s 从 Gitea Registry 拉取镜像
k3s 的 containerd 需要知道 Gitea Registry 是“私有但可信任”的仓库。根据 k3s 文档,启动时会检查 /etc/rancher/k3s/registries.yaml,如果存在则据此生成 containerd 配置。
由于 Gitea Registry 使用 HTTP(NodePort 未配置 TLS),需要在每个需要拉取镜像的节点上配置 registries.yaml。单节点场景只需在唯一节点上配置:
1 | # /etc/rancher/k3s/registries.yaml |
写入后重启 k3s:
1 | sudo systemctl restart k3s |
验证 containerd 配置已生效:
1 | sudo cat /var/lib/rancher/k3s/agent/etc/containerd/config.toml | grep -A5 "gitea" |
这一步是整条链路中最容易出问题的环节。如果后续 Pod 拉取镜像时报 x509 或 401,优先回到 registries.yaml 检查 endpoint、认证和 insecure_skip_verify。
六、部署 act_runner(Gitea Actions Runner)
act_runner 是 Gitea Actions 的执行器,以 Deployment 形式运行在 k3s 内。它需要访问 Gitea 的 API 获取任务,并且需要 kubectl 能力来执行部署步骤。
6.1 在 Gitea 中获取 Runner 注册 Token
Gitea 管理员界面 → 站点管理 → Actions → Runner,点击“创建 Runner”,获取 registration token。
6.2 部署 act_runner
1 | # act-runner.yaml |
act_runner 需要执行 kubectl 才能完成部署。为它创建 ServiceAccount 和 RBAC:
1 | # runner-rbac.yaml |
生产环境中不建议直接绑定 cluster-admin,应创建一个仅对目标 namespace 具有 deployments、services、pods 等必要权限的 Role 和 RoleBinding。这里为了首次跑通简化处理,后续在“最佳实践”中说明收窄权限的方式。
1 | kubectl apply -f runner-rbac.yaml |
Runner 启动后,回到 Gitea 的 Runner 管理页面,应看到 Runner 状态为 Idle 或 Active。如果显示离线,检查 kubectl -n gitea logs deploy/act-runner,通常是 registration token 过期或 Gitea 地址不可达。
七、准备 Golang 示例应用
7.1 应用代码(最小 Web 应用)
1 | go-web-demo/ |
main.go:
1 | package main |
关键细节:Go 服务必须监听 0.0.0.0 或 :8080,不能绑定 127.0.0.1,否则 Service 流量无法进入。上面的 ":8080" 是正确的。
go.mod:
1 | module go-web-demo |
7.2 Dockerfile(多阶段构建,静态二进制)
1 | FROM golang:1.23-alpine AS builder |
CGO_ENABLED=0 生成静态二进制,避免 Alpine 上的 glibc 兼容问题;非 root 用户降低容器逃逸风险。
7.3 Kubernetes 清单
k8s/deployment.yaml:
1 | apiVersion: apps/v1 |
k8s/service.yaml:
1 | apiVersion: v1 |
Service 的 selector 必须与 Deployment 的 matchLabels 完全一致,否则 endpoints 为空,这是 Golang 应用部署中最常见的“部署成功但访问不通”的原因。
7.4 在 Gitea 中创建仓库并推送代码
在 Gitea 中创建一个名为 go-web-demo 的仓库(不要初始化 README),然后在本地:
1 | cd go-web-demo |
八、编写 Gitea Actions 工作流,实现 push 即部署
在仓库中创建 .gitea/workflows/deploy.yml。Gitea Actions 的语法兼容 GitHub Actions,工作流放在 .gitea/workflows/ 下即可被 Runner 识别。
1 | name: Build and Deploy |
需要在 Gitea 仓库中配置 Secrets:
进入仓库 设置 → Actions → Secrets,添加:
REGISTRY_USER:Gitea 用户名REGISTRY_PASSWORD:Gitea 密码或 Token
关于 Runner 中的 kubectl 和 docker: 上述工作流使用了 docker login 和 docker/build-push-action,要求 act_runner 的执行环境中有 Docker 或兼容的构建工具。act_runner 默认镜像不包含 kubectl 和 Docker daemon。有两种解决路径:
路径 A(推荐用于本 SOP):使用 Kaniko 替代 Docker-in-Docker。 Kaniko 在容器内无需 daemon 即可构建镜像,更适合 Kubernetes 环境。将 Build 步骤替换为:
1 | - name: Build and push with Kaniko |
如果希望严格按 Kaniko 方式,将 build 步骤写为:
1 | - name: Build and push (Kaniko) |
这要求 act_runner 的容器镜像中包含 Kaniko executor,或者在该 step 中使用 container: 指定 Kaniko 镜像。首次跑通的最短路径是给 act_runner 使用一个预装了 kubectl、docker(dind)和 Kaniko 的定制镜像,或者接受在 Runner 所在节点上安装 Docker socket 挂载。考虑到“从零开始”的可操作性,更简单的选择是:在 Runner 容器中安装 kubectl,并将构建步骤改为在 Runner 宿主机上用 Docker 构建——但这会牺牲 Runner 的隔离性。
权衡后的建议: 对于本文“轻量级生产”的定位,使用 Docker-in-Docker(dind)模式的 act_runner 是复杂度最低的方案。Runner Deployment 使用 gitea/act_runner:0.2.13-dind 镜像,并在容器内挂载 Docker socket 或运行 dind sidecar。这超出了最小示例的范围,但开发者需要知道:如果不解决 Runner 内的 docker 和 kubectl 可用性,工作流会在 build 或 deploy 步骤失败。一个务实的折中方案是在 Runner 启动后进入 Pod 安装 kubectl 和 docker 客户端,但这不具备声明式可重复性。生产级做法是构建一个包含 act_runner + kubectl + docker-cli + kaniko 的私有 Runner 镜像,推送到 Gitea Registry,然后用该镜像部署 act_runner。
触发部署:
1 | git add .gitea/workflows/deploy.yml |
推送后,Gitea 仓库的 Actions 标签页应出现运行记录。Runner 拉取代码、构建镜像、推送 Registry、执行 kubectl set image,Deployment 滚动更新。通过以下命令观察:
1 | kubectl -n default rollout status deployment/go-web-demo |
访问 http://192.0.2.10:30080,应看到 Hello from k3s + Gitea。
九、生产注意事项与最佳实践
9.1 单节点的风险必须被明确管理
单节点 k3s 的控制面是单点。一旦 Server 进程崩溃或节点重启,API、Ingress、已调度 Pod 的健康检查都可能受影响。可以做的缓解措施:
- 备份 SQLite 数据:k3s 单节点默认数据存储在
/var/lib/rancher/k3s/server/db/,但 k3s 官方明确说明不支持对 SQLite 做在线快照,建议直接复制/var/lib/rancher/k3s/server目录来备份。将备份任务做成 cron,复制到另一块磁盘或远端对象存储。 - 为未来迁移 etcd 留好路径:不要在早期把 Server 地址硬编码到各处。Agent 注册地址如果写死单机 IP,后续迁移到多 Server 时需要逐台修改。尽量在证书中加入 SAN 或使用可切换的 DNS 名称。
- 接受“计划内维护窗口”:单节点方案不适合零停机要求极高的业务。把 k3s 升级、内核补丁安排在低峰窗口,并提前验证备份可恢复。
9.2 资源限制不是可选项
轻量不等于不需要观测。每个 Deployment 都应设置 requests 和 limits,防止构建任务或应用内存泄漏拖垮 k3s 和 Gitea。至少部署一个轻量监控(node-exporter + 简单告警,或 netdata),在内存打满之前收到通知。
9.3 Runner 权限最小化
本文为了快速跑通给 act-runner 绑定了 cluster-admin。生产环境中应收窄为仅对目标 namespace 的 Deployment、Service、Pod 的 get/list/watch/patch/update 权限。Runner 是 CI 执行环境,一旦仓库的 Actions 被恶意修改,过大的权限就是安全缺口。
9.4 镜像 tag 策略
工作流同时推送了 SHORT_SHA 和 latest,但 kubectl set image 使用的是 SHORT_SHA。不要在生产部署中使用 latest 作为实际拉取 tag,因为 containerd 的 imagePullPolicy: IfNotPresent 可能导致节点不拉取新镜像。每次 commit 使用唯一 tag 是滚动更新可靠生效的前提。
9.5 Gitea Registry 的 TLS
本文使用 HTTP NodePort 作为 Registry 端点,适用于单机内网或实验环境。生产环境应配置 TLS:可以在 Traefik 或 ingress-nginx 上为 gitea.<domain> 配置证书,然后将 registries.yaml 中的 endpoint 改为 https://,并去掉 insecure_skip_verify: true。Gitea 官方镜像支持通过反向代理终止 TLS,后端 HTTP 保持不变。
9.6 不要在 Server 节点上运行不受控的构建负载
act_runner 的构建任务可能产生 CPU 和内存尖峰。单节点场景下 Runner 和应用共享同一台机器,必须给 Runner 设置比应用更宽松但仍有上限的 resources.limits,避免 docker build 吃满内存导致 k3s 的 kubelet 被 OOM killer 终止。如果构建频率升高,最有效的优化不是升级服务器,而是把 Runner 拆到独立节点,或者使用 Kaniko 并限制其并发度。
十、验证清单(SOP 收尾)
按以下顺序逐项确认,即可判定环境“可发布”:
kubectl get nodes→Ready。- Gitea Pod
Running,浏览器可登录,Registry 已启用。 act-runner在 Gitea Runner 页面显示在线。registries.yaml已写入并重启 k3s,containerd 配置中包含 Gitea Registry 端点。- 在仓库中推送一次代码,Actions 运行成功,镜像出现在 Gitea 的 Packages 页面。
kubectl get pods -l app=go-web-demo显示新 ReplicaSet 的 Pod 为Running,kubectl rollout history可追溯。- 通过
NodePort访问应用,/healthz返回ok。 - SQLite 备份 cron 已配置,并手动执行一次恢复演练(在非生产副本上) 。
这套方案的价值不在于它“用了 k3s 和 Gitea”,而在于它把 代码推送 → 镜像构建 → 镜像推送 → Kubernetes 滚动更新 压缩到一台机器上的单条流水线中,每一步都有明确的配置文件和验证方法。它适合中小团队在没有专职平台工程师的情况下,用最低的运维表面积获得可重复、可追溯的发布能力。当单节点的控制面单点成为真实痛点时,再按 k3s 的演进路径扩展为三节点 etcd 高可用——那时开发者已经有了稳定的 CI/CD 流程和镜像仓库,迁移的代价是可控的。
【SOP实操】一台高配服务器,用 k3s + Gitea 从零搭建 Golang 应用的轻量级生产发布环境


