代码写好了,接下来就是构建和部署。Go 语言的编译型特性让部署变得简单——一个二进制文件就能跑起来。但实际项目中,还是有不少细节需要注意。
最简单的方式:
go build -o myapp
这会在当前目录生成可执行文件 myapp。但这种方式有几个问题:
推荐的生产环境编译参数:
go build -ldflags="-s -w" -o myapp
-ldflags 参数说明:
-s:去掉符号表-w:去掉 DWARF 调试信息这样编译出来的文件会小 20-30%。
在编译时注入版本信息:
package main
var (
Version = "dev"
BuildTime = "unknown"
GitCommit = "unknown"
)
func main() {
fmt.Printf("Version: %s\n", Version)
fmt.Printf("Build Time: %s\n", BuildTime)
fmt.Printf("Git Commit: %s\n", GitCommit)
// ...
}
编译时传入变量:
VERSION=$(git describe --tags --always)
BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S')
GIT_COMMIT=$(git rev-parse --short HEAD)
go build -ldflags="-s -w \
-X main.Version=$VERSION \
-X main.BuildTime=$BUILD_TIME \
-X main.GitCommit=$GIT_COMMIT" \
-o myapp
Go 支持交叉编译,可以在一个平台上编译其他平台的可执行文件:
# Linux AMD64
GOOS=linux GOARCH=amd64 go build -o myapp-linux
# Windows
GOOS=windows GOARCH=amd64 go build -o myapp.exe
# macOS
GOOS=darwin GOARCH=amd64 go build -o myapp-darwin
# ARM (树莓派等)
GOOS=linux GOARCH=arm GOARM=7 go build -o myapp-arm
常用 GOOS/GOARCH 组合:
| 目标平台 | GOOS | GOARCH |
|---|---|---|
| Linux 64位 | linux | amd64 |
| Linux ARM | linux | arm |
| Windows 64位 | windows | amd64 |
| macOS Intel | darwin | amd64 |
| macOS M1/M2 | darwin | arm64 |
用 Makefile 管理构建流程:
APP_NAME := myapp
VERSION := $(shell git describe --tags --always)
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
GIT_COMMIT := $(shell git rev-parse --short HEAD)
LDFLAGS := -ldflags="-s -w -X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME) -X main.GitCommit=$(GIT_COMMIT)"
.PHONY: all build clean test run
all: clean build
build:
go build $(LDFLAGS) -o $(APP_NAME)
build-linux:
GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(APP_NAME)-linux
build-windows:
GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(APP_NAME).exe
build-all: build-linux build-windows
go build $(LDFLAGS) -o $(APP_NAME)-darwin
test:
go test -v ./...
run:
go run main.go
clean:
rm -f $(APP_NAME) $(APP_NAME)-*
docker:
docker build -t $(APP_NAME):$(VERSION) .
使用方式:
make build # 本地构建
make build-linux # 构建 Linux 版本
make build-all # 构建所有平台
make test # 运行测试
生产环境的配置通常与代码分离。常见的配置方式:
package config
import "os"
type Config struct {
Port string
DBUrl string
RedisUrl string
}
func Load() *Config {
return &Config{
Port: getEnv("PORT", "8080"),
DBUrl: getEnv("DATABASE_URL", "localhost:5432"),
RedisUrl: getEnv("REDIS_URL", "localhost:6379"),
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
使用 Viper 读取配置:
package config
import (
"github.com/spf13/viper"
)
type Config struct {
Server struct {
Port int `mapstructure:"port"`
Mode string `mapstructure:"mode"`
} `mapstructure:"server"`
Database struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
} `mapstructure:"database"`
}
func Load(path string) (*Config, error) {
v := viper.New()
v.SetConfigFile(path)
if err := v.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
配置文件 config.yaml:
server:
port: 8080
mode: release
database:
host: localhost
port: 5432
user: postgres
password: secret
dbname: myapp
在 Linux 上,可以用 systemd 管理服务:
创建服务文件 /etc/systemd/system/myapp.service:
[Unit]
Description=My Gin Application
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=always
RestartSec=5
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp
# 环境变量
Environment=PORT=8080
Environment=GIN_MODE=release
EnvironmentFile=/opt/myapp/.env
# 安全设置
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
管理服务:
# 重载配置
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start myapp
# 开机自启
sudo systemctl enable myapp
# 查看状态
sudo systemctl status myapp
# 查看日志
sudo journalctl -u myapp -f
通常用 Nginx 作为反向代理:
upstream myapp {
server 127.0.0.1:8080;
keepalive 64;
}
server {
listen 80;
server_name example.com;
# 重定向到 HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
location / {
proxy_pass http://myapp;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 静态文件
location /static/ {
alias /opt/myapp/static/;
expires 30d;
}
}
.github/workflows/deploy.yml:
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run tests
run: go test -v ./...
- name: Build
run: |
VERSION=$(git describe --tags --always)
go build -ldflags="-s -w -X main.Version=$VERSION" -o myapp
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: myapp
path: myapp
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: myapp
- name: Deploy to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
source: myapp
target: /opt/myapp
- name: Restart service
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
sudo systemctl restart myapp
部署前检查:
部署后检查:
Go 应用的部署相对简单:
关键是建立可重复、可回滚的部署流程,让发布变得轻松可靠。