尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

一个简单的Go项目生产部署流程

一个简单的Go项目生产部署流程 Ubuntu 服务器完整部署流程1. 安装 Go 环境# 下载 Go 1.23wgethttps://go.dev/dl/go1.23.1.linux-amd64.tar.gzsudotar-C/usr/local-xzfgo1.23.1.linux-amd64.tar.gzechoexport PATH$PATH:/usr/local/go/bin~/.bashrcsource~/.bashrc go version# 验证国内服务器必看默认proxy.golang.org被墙go mod tidy会报i/o timeout需切换 GOPROXY 为国内镜像goenv-wGOPROXYhttps://goproxy.cn,direct# 七牛云推荐# 或 go env -w GOPROXYhttps://goproxy.aliyun.com,direct # 阿里云goenv-wGOSUMDBoff2. 部署项目文件mkdir-p/opt/项目目录chownwww:www /opt/项目目录# 运行用户原则上不允许用root运行项目cd/opt/项目目录# 上传项目源码文件*.go、config.jsonc、go.mod、go.sum 到此目录go mod tidy3. 修改配置文件修改项目配置文件本项目为config.jsonc改为正式服的连接信息Mysql、Redis等数据库修改账号密码、连接地址、连接库外网链接改内网链接HTTP 端口、鉴权密钥等具体改哪些项参考项目的上线注意.txt或配置文件内的注释。4. 数据库索引优化检查数据库索引。5. 编译验证go build-o可执行文件名../可执行文件名# 前台试跑确认启动正常、日志无报错后 CtrlC 退出6. 配置 systemd 守护进程开机自启 让服务崩溃自动重启6.1 创建 service 文件新建文件/etc/systemd/system/服务名.service服务名按实际项目取如pushmsg-sender内容如下把项目名和路径换成你的实际值[Unit] Description项目描述 # 服务描述随便写用于 systemctl status 显示 Afternetwork.target # 启动顺序网络就绪后再启动连数据库/MQTT 依赖网络。如需等待网络真正可用改为 Afternetwork-online.target 并加 Wantsnetwork-online.target [Service] Typesimple # 进程类型simple 表示 ExecStart 启动的进程就是主进程 Userroot # 运行用户root 部署就用 root用其他用户先 useradd -r 用户名 再填 WorkingDirectory/opt/项目目录 # 工作目录决定 config.jsonc 和 runtimelogs/ 的相对路径位置 ExecStart/opt/项目目录/可执行文件 # 可执行文件绝对路径 Restartalways # 崩溃后自动重启无论正常退出还是异常也可用 on-failure仅异常退出才重启 RestartSec5 # 重启前等待 5 秒避免崩溃-重启高频循环打满 CPU LimitNOFILE65536 # 最大文件描述符数高并发需要系统默认 1024 可能不够 [Install] WantedBymulti-user.target # 开机自启触发点对应多用户命令行模式可选追加项按需加到[Service]段EnvironmentGO_ENVproduction给进程注入环境变量StandardOutputjournal/StandardErrorjournal显式指定日志走 journald默认即如此一般不用写ExecStartPre/bin/sh -c echo starting...主进程启动前执行的预备命令6.2 加载并启动把服务名换成你在 6.1 中取的服务名systemctl daemon-reload# 让 systemd 读取新创建的 service 文件systemctlenable服务名# 设置开机自启systemctl start服务名# 启动服务systemctl status服务名# 确认状态为 active (running)journalctl-u服务名-f# 实时systemd运行日志# 临时调试也可用 nohup ./服务名 app.log 21 6.3 日常运维命令systemctl restart服务名# 重启systemctl stop服务名# 停止systemctl status服务名# 查看状态重要修改了 service 文件后必须先执行systemctl daemon-reload再restart否则改动不生效(也可一条过systemctl daemon-reload systemctl restart 服务名)。7. 运行日志按天记录 自动清理在项目代码中加入以下日志函数实现按天写日志文件 自动删除 30 天前的旧日志无需 logrotate/cron。7.1 在 main.go 中加入日志函数第一步在import中补充io和path/filepath两个包若已有则跳过import(iologospath/filepathtime// ... 其他已有包)第二步在main()函数第一行调用日志初始化funcmain(){// 初始化日志按天写文件 自动删除30天前的旧日志initDailyLogger()// ... 原有代码}第三步在main.go末尾加入以下函数直接复制// initDailyLogger 按天写日志文件 runtimelogs/app-YYYYMMDD.log启动时删除30天前的旧日志。// 同时输出到文件和控制台控制台供 systemd/journalctl 查看。funcinitDailyLogger(){logDir:runtimelogsos.MkdirAll(logDir,0755)today:time.Now().Format(20060102)logFile:filepath.Join(logDir,app-today.log)f,err:os.OpenFile(logFile,os.O_CREATE|os.O_APPEND|os.O_WRONLY,0644)iferr!nil{// 打不开就退回控制台不影响启动log.Printf([WARN] 无法打开日志文件 %s: %v使用控制台输出,logFile,err)return}// 同时输出到文件和控制台log.SetOutput(io.MultiWriter(f,os.Stdout))log.Printf([INFO] 日志写入 %s,logFile)// 删除30天前的旧日志文件cutoff:time.Now().AddDate(0,0,-30)entries,_:os.ReadDir(logDir)for_,e:rangeentries{info,err:e.Info()iferr!nil{continue}ifinfo.ModTime().Before(cutoff){os.Remove(filepath.Join(logDir,e.Name()))}}// 限制日志目录总大小不超过20G超出则从最旧的文件开始删除constmaxLogDirSizeint6420*1024*1024*1024// 20GBentries,_os.ReadDir(logDir)typefileInfostruct{namestringsizeint64modTime time.Time}varfiles[]fileInfovartotalSizeint64for_,e:rangeentries{info,err:e.Info()iferr!nil{continue}filesappend(files,fileInfo{e.Name(),info.Size(),info.ModTime()})totalSizeinfo.Size()}iftotalSizemaxLogDirSize{// 按修改时间从旧到新排序sort.Slice(files,func(i,jint)bool{returnfiles[i].modTime.Before(files[j].modTime)})for_,f:rangefiles{iftotalSizemaxLogDirSize{break}os.Remove(filepath.Join(logDir,f.name))totalSize-f.size}log.Printf([INFO] 日志目录超过20G已清理旧文件)}}7.2 日志效果说明项目说明日志目录runtimelogs/程序启动时自动创建日志文件按当天日期命名如runtimelogs/app-20260720.log自动清理每次启动时删除修改时间超过 30 天的旧日志文件双路输出同时写入文件和控制台控制台供journalctl查看目录位置由 service 文件的WorkingDirectory决定相对该目录7.3 查看日志# 查看当天文件日志实时滚动CtrlC 退出tail-f/opt/项目目录/runtimelogs/app-$(date%Y%m%d).log# 查看历史日志文件列表ls-lh/opt/项目目录/runtimelogs/# 通过 journald 查看走控制台输出systemd 方式sudojournalctl-u服务名-f# 查看最近 100 行日志sudojournalctl-u服务名-n100日志目录位置由 service 文件的WorkingDirectory决定。若WorkingDirectory/opt/项目目录则日志在/opt/项目目录/runtimelogs/。8. 部署脚本 deploy.sh编译 重启一键操作8.1 创建脚本在项目目录下新建deploy.sh文件内容如下把PROJECT_DIR、SERVICE换成你的实际值#!/bin/bashset-ePROJECT_DIR/opt/项目目录# ← 改成你的项目路径SERVICE服务名# ← 改成你的 service 名BIN$PROJECT_DIR/可执行文件# ← 改成你的二进制文件名MODE${1:-restart}cd$PROJECT_DIR# ---------- 编译 ----------if[$MODEbuild]||[$MODEbuildonly];thenecho 编译 go build-o$BIN.new.echo编译成功fi# ---------- 停止 systemd 服务 ----------echo 停止 systemd 服务 systemctl stop$SERVICE2/dev/null||truesleep1# ---------- 清理残留进程 ----------echo 清理残留进程 PIDS$(pgrep-f$BIN2/dev/null||true)if[-n$PIDS];thenecho发现残留进程:$PIDS正在清理...forpidin$PIDS;dokill$pid2/dev/null||true;donesleep2PIDS$(pgrep-f$BIN2/dev/null||true)[-n$PIDS]kill-9$PIDS2/dev/null||trueecho清理完成elseecho无残留进程fi# ---------- 替换二进制 ----------if[$MODEbuild]||[$MODEbuildonly];thenecho 替换二进制 mv-f$BIN.new$BINecho替换完成fi# ---------- 仅编译模式退出 ----------if[$MODEbuildonly];thenecho 仅编译模式不启动 exit0fi# ---------- 启动服务 ----------echo 启动服务systemd 托管 systemctl start$SERVICEsleep2systemctl status$SERVICE--no-pager||trueechoecho 完成 echo实时日志 journalctl -u$SERVICE-fecho文件日志 tail -f$PROJECT_DIR/runtimelogs/app-$(date%Y%m%d).logecho停止服务 systemctl stop$SERVICE创建后赋权chmodx deploy.sh8.2 使用方法# 改完代码后——编译并重启sudo./deploy.sh build# 改完配置后——只重启不编译默认sudo./deploy.sh# 只想验证代码能否编译通过——只编译不重启sudo./deploy.sh buildonly8.3 脚本做了什么build模式步骤动作说明1编译输出到可执行文件.new临时文件编译失败不影响旧二进制2停止服务systemctl stop停掉 systemd 托管的进程3清理残留杀掉可能存在的 nohup/手动启动的旧进程避免端口占用4替换二进制mv原子操作瞬间完成5启动服务systemctl start启动systemd 守护9. 验证启动后按项目接口验证以下为本项目示例其他项目换成实际接口curlhttp://127.0.0.1:8085/health# 健康检查curl-XPOST http://127.0.0.1:8085/api/push/send-HContent-Type: application/json-d{pushmsg_id: 123}curlhttp://127.0.0.1:8085/api/push/status?pushmsg_id123# 查询进度日常运维重启/停止sudo systemctl restart/stop 服务名更新代码后用sudo ./deploy.sh build编译重启改完配置后用sudo ./deploy.sh只重启修改了 service 文件后先sudo systemctl daemon-reload再restart
返回列表