构建与部署

代码写好了,接下来就是构建和部署。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 组合:

目标平台GOOSGOARCH
Linux 64位linuxamd64
Linux ARMlinuxarm
Windows 64位windowsamd64
macOS Inteldarwinamd64
macOS M1/M2darwinarm64

Makefile

用 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

Systemd 服务

在 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 反向代理

通常用 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;
    }
}

CI/CD 集成

GitHub Actions

.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 应用的部署相对简单:

  • 编译生成单一二进制文件
  • 用 Makefile 管理构建流程
  • 用 systemd 管理服务
  • 用 Nginx 做反向代理和 HTTPS
  • 用 CI/CD 自动化部署

关键是建立可重复、可回滚的部署流程,让发布变得轻松可靠。