强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Git 服务器搭建完全指南 / 第 15 章 - 最佳实践

第 15 章 - 最佳实践

综合前 14 章内容,提炼企业级 Git 服务器的运维规范、备份策略、安全加固和团队协作最佳实践。

15.1 运维规范

15.1.1 日常巡检清单

检查项频率命令阈值
服务状态每日systemctl status gitea必须 running
磁盘使用率每日df -h /opt/git< 85%
内存使用每日free -h可用 > 1GB
CPU 负载每日uptimeload < 核心数 × 2
日志错误每日journalctl -p err --since "1 day ago"无严重错误
备份状态每日检查备份文件文件存在且大小合理
SSL 证书每周openssl s_client -connect ...30 天内不过期
安全更新每周apt list --upgradable及时更新

15.1.2 自动化巡检脚本

#!/bin/bash
# daily-check.sh - 每日巡检脚本

set -euo pipefail

REPORT_FILE="/var/log/git-daily-check-$(date +%Y%m%d).txt"
WEBHOOK_URL="${WEBHOOK_URL:-}"
WARNINGS=0

report() {
    echo "$1" | tee -a "$REPORT_FILE"
}

warn() {
    echo "⚠️  $1" | tee -a "$REPORT_FILE"
    WARNINGS=$((WARNINGS + 1))
}

fail() {
    echo "❌ $1" | tee -a "$REPORT_FILE"
    WARNINGS=$((WARNINGS + 1))
}

report "========================================"
report "Git 服务每日巡检报告"
report "时间: $(date '+%Y-%m-%d %H:%M:%S')"
report "========================================"
report ""

# === 服务状态 ===
report "--- 服务状态 ---"
services=("gitea" "nginx" "postgresql" "redis")
for svc in "${services[@]}"; do
    if systemctl is-active --quiet "$svc" 2>/dev/null; then
        report "✅ $svc: running"
    elif docker compose ps --services --filter "status=running" 2>/dev/null | grep -q "$svc"; then
        report "✅ $svc: running (docker)"
    else
        fail "$svc: not running"
    fi
done
report ""

# === 磁盘空间 ===
report "--- 磁盘空间 ---"
disk_usage=$(df /opt/git 2>/dev/null | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$disk_usage" -lt 85 ]; then
    report "✅ 磁盘使用率: ${disk_usage}%"
elif [ "$disk_usage" -lt 95 ]; then
    warn "磁盘使用率偏高: ${disk_usage}%"
else
    fail "磁盘即将满: ${disk_usage}%"
fi
report ""

# === 内存 ===
report "--- 内存使用 ---"
mem_available=$(free -m | awk '/^Mem:/ {print $7}')
if [ "$mem_available" -gt 1024 ]; then
    report "✅ 可用内存: ${mem_available}MB"
elif [ "$mem_available" -gt 512 ]; then
    warn "可用内存偏低: ${mem_available}MB"
else
    fail "可用内存不足: ${mem_available}MB"
fi
report ""

# === 备份检查 ===
report "--- 备份状态 ---"
BACKUP_DIR="/var/backups/git"
latest_backup=$(find "$BACKUP_DIR" -name "*.tar.gz" -o -name "*.zip" 2>/dev/null | sort -r | head -1)
if [ -n "$latest_backup" ]; then
    backup_age=$(( ($(date +%s) - $(stat -c %Y "$latest_backup")) / 3600 ))
    backup_size=$(du -sh "$latest_backup" | cut -f1)
    if [ "$backup_age" -lt 25 ]; then
        report "✅ 最新备份: $(basename $latest_backup) (${backup_size}, ${backup_age}h ago)"
    else
        warn "备份可能过期: ${backup_age}h ago"
    fi
else
    fail "未找到备份文件"
fi
report ""

# === SSL 证书 ===
report "--- SSL 证书 ---"
cert_expiry=$(echo | openssl s_client -connect git.example.com:443 2>/dev/null | \
    openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$cert_expiry" ]; then
    days_left=$(( ($(date -d "$cert_expiry" +%s) - $(date +%s)) / 86400 ))
    if [ "$days_left" -gt 30 ]; then
        report "✅ SSL 证书: ${days_left} 天后过期"
    else
        warn "SSL 证书即将过期: ${days_left} 天"
    fi
else
    report "ℹ️  SSL 证书: 无法检查"
fi
report ""

# === 最近错误日志 ===
report "--- 最近错误 ---"
error_count=$(journalctl --since "1 day ago" -p err --no-pager 2>/dev/null | wc -l)
if [ "$error_count" -lt 10 ]; then
    report "✅ 错误日志: ${error_count} 条"
else
    warn "错误日志偏多: ${error_count} 条"
    report "最近错误:"
    journalctl --since "1 day ago" -p err --no-pager | tail -5 | while read line; do
        report "  $line"
    done
fi
report ""

# === 汇总 ===
report "========================================"
if [ "$WARNINGS" -eq 0 ]; then
    report "✅ 巡检完成,所有项目正常"
else
    report "⚠️  巡检完成,发现 $WARNINGS 个问题"
fi
report "========================================"

# 发送通知
if [ "$WARNINGS" -gt 0 ] && [ -n "$WEBHOOK_URL" ]; then
    curl -sf -X POST "$WEBHOOK_URL" \
        -H "Content-Type: application/json" \
        -d "{\"text\":\"Git 服务巡检: 发现 $WARNINGS 个问题\n详见: $REPORT_FILE\"}" > /dev/null &
fi
# Cron 配置:每天上午 9 点执行
# crontab -e
0 9 * * * /opt/scripts/daily-check.sh

15.1.3 变更管理流程

变更请求 → 影响评估 → 审批 → 实施 → 验证 → 记录
    │          │        │      │      │      │
    │          │        │      │      │      └── 更新文档/CMDB
    │          │        │      │      └── 功能验证、监控观察
    │          │        │      └── 在维护窗口执行
    │          │        └── 团队 Lead 或管理员
    │          └── 评估影响范围和回滚方案
    └── 记录变更内容、原因、预期效果

15.2 备份策略

15.2.1 3-2-1 备份原则

3 份副本
├── 本地存储(原始数据)
├── 本地备份(外接硬盘/NAS)
└── 异地备份(远程服务器/云存储)

2 种存储介质
├── 磁盘
└── 对象存储/磁带

1 份异地
└── 远程位置

15.2.2 备份架构

生产服务器 (Git)
    │
    ├── 实时同步 → 备用服务器 (rsync/inotify)
    │
    ├── 每日备份 → NAS/本地磁盘
    │   └── 保留 30 天
    │
    ├── 每周全量 → 异地存储 (S3/OSS)
    │   └── 保留 90 天
    │
    └── 每月归档 → 离线存储
        └── 保留 1 年

15.2.3 自动化备份策略

#!/bin/bash
# comprehensive-backup.sh

set -euo pipefail

# 配置
GIT_ROOT="/opt/git"
BACKUP_BASE="/var/backups/git"
NAS_MOUNT="/mnt/nas-backup"
S3_BUCKET="s3://git-backups-bucket"
DATE=$(date +%Y%m%d)
DAY_OF_WEEK=$(date +%u)
DAY_OF_MONTH=$(date +%d)

mkdir -p "$BACKUP_BASE/daily" "$BACKUP_BASE/weekly" "$BACKUP_BASE/monthly"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# === 每日增量备份 ===
log "Starting daily backup..."
DAILY_FILE="$BACKUP_BASE/daily/git_backup_${DATE}.tar.gz"

# 使用 rsync 增量备份
rsync -a --delete --link-dest="$BACKUP_BASE/daily/latest" \
    "$GIT_ROOT/" "$BACKUP_BASE/daily/${DATE}/"
ln -sfn "$BACKUP_BASE/daily/${DATE}" "$BACKUP_BASE/daily/latest"

# 创建 tar 归档
tar czf "$DAILY_FILE" -C "$BACKUP_BASE/daily" "${DATE}/"
log "Daily backup: $DAILY_FILE ($(du -sh "$DAILY_FILE" | cut -f1))"

# 清理 30 天前的每日备份
find "$BACKUP_BASE/daily" -maxdepth 1 -name "git_backup_*" -mtime +30 -delete

# === 每周全量备份 ===
if [ "$DAY_OF_WEEK" = "7" ]; then
    log "Starting weekly backup..."
    WEEKLY_FILE="$BACKUP_BASE/weekly/git_backup_week_${DATE}.tar.gz"
    cp "$DAILY_FILE" "$WEEKLY_FILE"
    
    # 同步到 NAS
    if mountpoint -q "$NAS_MOUNT"; then
        cp "$WEEKLY_FILE" "$NAS_MOUNT/"
        log "Weekly backup synced to NAS"
    fi
    
    # 清理 12 周前的每周备份
    find "$BACKUP_BASE/weekly" -name "*.tar.gz" -mtime +84 -delete
fi

# === 每月归档 ===
if [ "$DAY_OF_MONTH" = "01" ]; then
    log "Starting monthly archive..."
    MONTHLY_FILE="$BACKUP_BASE/monthly/git_archive_$(date +%Y%m).tar.gz"
    cp "$DAILY_FILE" "$MONTHLY_FILE"
    
    # 上传到 S3
    if command -v aws &>/dev/null; then
        aws s3 cp "$MONTHLY_FILE" "$S3_BUCKET/monthly/"
        log "Monthly archive uploaded to S3"
    fi
    
    # 清理 12 个月前的每月备份
    find "$BACKUP_BASE/monthly" -name "*.tar.gz" -mtime +365 -delete
fi

log "Backup completed"

15.2.4 备份恢复演练

#!/bin/bash
# restore-drill.sh - 备份恢复演练

set -euo pipefail

DRILL_DIR="/tmp/restore-drill-$(date +%Y%m%d)"
BACKUP_FILE="$1"

echo "=== 备份恢复演练 ==="
echo "备份文件: $BACKUP_FILE"
echo "恢复目录: $DRILL_DIR"
echo ""

mkdir -p "$DRILL_DIR"

# 恢复备份
echo "1. 恢复备份..."
tar xzf "$BACKUP_FILE" -C "$DRILL_DIR"

# 验证仓库完整性
echo "2. 验证仓库..."
errors=0
for repo in $(find "$DRILL_DIR" -name "*.git" -type d); do
    repo_name=$(basename "$repo")
    
    if git --git-dir="$repo" fsck --no-dangling 2>/dev/null; then
        echo "   ✅ $repo_name"
    else
        echo "   ❌ $repo_name"
        errors=$((errors + 1))
    fi
done

# 尝试克隆验证
echo ""
echo "3. 测试克隆..."
test_repo=$(find "$DRILL_DIR" -name "*.git" -type d | head -1)
if [ -n "$test_repo" ]; then
    if git clone "$test_repo" "$DRILL_DIR/test-clone" 2>/dev/null; then
        echo "   ✅ 克隆成功"
        rm -rf "$DRILL_DIR/test-clone"
    else
        echo "   ❌ 克隆失败"
        errors=$((errors + 1))
    fi
fi

# 汇总
echo ""
echo "=== 演练结果 ==="
echo "仓库总数: $(find "$DRILL_DIR" -name "*.git" -type d | wc -l)"
echo "错误数量: $errors"

if [ "$errors" -eq 0 ]; then
    echo "✅ 备份恢复演练通过"
else
    echo "❌ 发现问题,请检查"
fi

# 清理
rm -rf "$DRILL_DIR"

建议每月执行一次恢复演练,验证备份有效性。


15.3 安全加固

15.3.1 安全加固清单

类别措施优先级
SSH禁用密码登录,仅使用密钥认证
SSH修改默认端口
SSH使用 SSH 证书替代个人密钥
访问启用 HTTPS,强制 HTTP → HTTPS 重定向
访问配置防火墙,仅开放必要端口
认证启用 2FA(双因素认证)
认证集成 LDAP/OAuth2 统一身份管理
权限最小权限原则
权限分支保护规则
审计启用操作审计日志
数据定期备份并验证
数据敏感信息检查(pre-receive 钩子)
更新及时更新系统和软件
网络内网访问限制(VPN/白名单)

15.3.2 SSH 加固

# /etc/ssh/sshd_config

# 禁用密码认证
PasswordAuthentication no
PermitRootLogin no

# 仅允许指定用户
AllowUsers git deploy admin

# 使用强加密算法
KexAlgorithms curve25519-sha256,[email protected]
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]

# 限制登录尝试
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30

# 超时设置
ClientAliveInterval 300
ClientAliveCountMax 2

# 使用非默认端口
Port 2222
# 应用配置
sudo sshd -t  # 测试配置
sudo systemctl restart sshd

15.3.3 防火墙配置

#!/bin/bash
# setup-firewall.sh

# 重置规则
sudo ufw --force reset

# 默认策略
sudo ufw default deny incoming
sudo ufw default allow outgoing

# SSH(自定义端口)
sudo ufw allow 2222/tcp comment "SSH"

# HTTP/HTTPS
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"

# Git SSH(如果使用单独端口)
sudo ufw allow 2222/tcp comment "Git SSH"

# 内网访问(管理端口)
sudo ufw allow from 192.168.1.0/24 to any port 3000 comment "Gitea Internal"
sudo ufw allow from 10.0.0.0/8 to any port 9090 comment "Monitoring Internal"

# 启用防火墙
sudo ufw enable
sudo ufw status verbose

15.3.4 敏感信息扫描

#!/bin/bash
# scan-secrets.sh - 扫描仓库中的敏感信息

REPO_PATH="$1"
PATTERNS=(
    'AKIA[0-9A-Z]{16}'                      # AWS Access Key
    'ghp_[a-zA-Z0-9]{36}'                   # GitHub Token
    'glpat-[a-zA-Z0-9\-]{20}'               # GitLab Token
    '-----BEGIN (RSA |EC |DSA )?PRIVATE KEY' # 私钥
    'password\s*[:=]\s*.+'                   # 密码
    'secret\s*[:=]\s*.+'                     # Secret
    'api[_-]?key\s*[:=]\s*.+'               # API Key
)

echo "扫描仓库: $REPO_PATH"
echo ""

cd "$REPO_PATH"

for pattern in "${PATTERNS[@]}"; do
    results=$(git log -p --all -S "$pattern" -- . 2>/dev/null | grep -E "$pattern" | head -5)
    if [ -n "$results" ]; then
        echo "⚠️  发现模式匹配: $pattern"
        echo "$results" | head -3
        echo ""
    fi
done

echo "扫描完成"

15.3.5 Gitea 安全配置

# /etc/gitea/app.ini

[security]
INSTALL_LOCK     = true
SECRET_KEY       = <随机生成 40+ 字符>
INTERNAL_TOKEN   = <随机生成 40+ 字符>
PASSWORD_COMPLEX = spec
MIN_PASSWORD_LENGTH = 12

[service]
REQUIRE_SIGNIN_VIEW               = true
ENABLE_NOTIFY_MAIL                = true
ENABLE_REVERSE_PROXY_AUTHENTICATION = false
DISABLE_REGISTRATION              = true  # 禁止公开注册
REGISTRATION_CONFIRM              = true  # 需要邮箱确认

[webhook]
ALLOWED_HOST_LIST = *.example.com  # 限制 Webhook 目标

[openid]
ENABLE_OPENID_SIGNIN = false

15.4 团队协作规范

15.4.1 分支策略

Git Flow

main ─────●─────────●─────────●─────────●─── (生产)
           \       /           \       /
release/1.0 ●─────●             \     /
           /       \             \   /
develop ──●────●────●────●────●───●─●── (开发)
              /         \
feature/x ──●───●────●───┘

GitHub Flow(推荐)

main ──────────●─────────●─────────●─── (生产 + 部署)
                \       / \       /
feature/a ──────●───●─┘   \     /
                           \   /
feature/b ──────────────────●─●─┘

Trunk-Based Development

main ──●──●──●──●──●──●──●──●──●── (主干)
        \  /   \  /   \  /
short-lived branches (1-2 days)

15.4.2 提交规范

<type>(<scope>): <description>

[optional body]

[optional footer]

类型(type)

类型说明示例
feat新功能feat(auth): add OAuth2 login
fixBug 修复fix(api): handle null response
docs文档docs(readme): update install guide
style格式style: fix indentation
refactor重构refactor(db): optimize queries
test测试test(auth): add unit tests
chore杂项chore(deps): update dependencies
perf性能perf(api): add response caching
ciCI/CDci: add test stage
build构建build: update Dockerfile

15.4.3 Code Review 规范

## Code Review 检查清单

### 功能
- [ ] 代码实现符合需求描述
- [ ] 边界情况已处理
- [ ] 错误处理完善

### 代码质量
- [ ] 命名清晰、有意义
- [ ] 函数职责单一
- [ ] 无重复代码(DRY)
- [ ] 注释充分但不过度

### 安全
- [ ] 无硬编码凭据
- [ ] 输入已验证和清理
- [ ] SQL 查询使用参数化
- [ ] 权限检查完整

### 测试
- [ ] 有单元测试
- [ ] 测试覆盖率合理
- [ ] 测试用例包含边界情况

### 文档
- [ ] API 变更有文档更新
- [ ] README 已更新(如需要)
- [ ] 变更日志已更新

15.4.4 Issue 模板

<!-- .gitea/ISSUE_TEMPLATE/bug_report.md -->
---
name: Bug 报告
about: 报告一个问题
labels: bug
assignees: ''
---

## 描述
<!-- 清晰描述问题 -->

## 复现步骤
1. 步骤一
2. 步骤二
3. ...

## 预期行为
<!-- 描述你期望的正确行为 -->

## 实际行为
<!-- 描述实际发生的情况 -->

## 环境
- OS: [如 Ubuntu 22.04]
- 浏览器: [如 Chrome 120]
- 版本: [如 v1.2.3]

## 日志/截图
<!-- 如适用,粘贴相关日志或截图 -->

15.4.5 Pull Request 模板

<!-- .gitea/PULL_REQUEST_TEMPLATE.md -->
## 变更说明
<!-- 简要描述此 PR 的变更内容 -->

## 关联 Issue
<!-- 关联的 Issue 编号,如 #123 -->

## 变更类型
- [ ] 新功能
- [ ] Bug 修复
- [ ] 重构
- [ ] 文档更新
- [ ] 测试
- [ ] 其他

## 测试说明
<!-- 描述如何测试这些变更 -->

## 截图/录屏
<!-- 如适用 -->

## Checklist
- [ ] 代码符合团队编码规范
- [ ] 已添加/更新单元测试
- [ ] 已更新相关文档
- [ ] CI 流水线通过

15.5 容量规划

15.5.1 资源估算

规模用户数仓库数CPU内存磁盘(初始)磁盘(年增长)
1-10< 502 核4 GB50 GB50 GB/年
10-10050-5004 核8 GB200 GB200 GB/年
100-500500-20008 核16 GB500 GB500 GB/年
企业500+2000+16+ 核32+ GB1 TB+1 TB+/年

15.5.2 磁盘增长监控

#!/bin/bash
# disk-growth-monitor.sh

GIT_ROOT="/opt/git"
LOG_FILE="/var/log/disk-growth.log"

current_usage=$(du -sb "$GIT_ROOT" | cut -f1)
current_date=$(date +%Y%m%d)

# 记录到日志
echo "$current_date $current_usage" >> "$LOG_FILE"

# 计算增长率(与 30 天前对比)
old_date=$(date -d "30 days ago" +%Y%m%d)
old_usage=$(grep "^$old_date" "$LOG_FILE" | awk '{print $2}')

if [ -n "$old_usage" ]; then
    growth_mb=$(( (current_usage - old_usage) / 1024 / 1024 ))
    echo "30 天增长: ${growth_mb}MB"
    
    # 预警阈值
    if [ "$growth_mb" -gt 10240 ]; then  # > 10GB/月
        echo "⚠️  磁盘增长过快: ${growth_mb}MB/月"
        # 发送告警...
    fi
fi

15.6 文档管理

15.6.1 运维文档结构

docs/
├── runbook/
│   ├── backup-restore.md     # 备份恢复操作手册
│   ├── incident-response.md  # 事故响应流程
│   ├── scaling-guide.md      # 扩容指南
│   └── upgrade-guide.md      # 升级指南
├── architecture/
│   ├── system-overview.md    # 系统架构概览
│   ├── network-diagram.md    # 网络拓扑
│   └── data-flow.md          # 数据流图
├── procedures/
│   ├── new-user-onboard.md   # 新用户接入流程
│   ├── repo-creation.md      # 仓库创建流程
│   └── access-request.md     # 权限申请流程
└── contacts/
    └── escalation.md         # 升级联系人

15.6.2 事故记录模板

## 事故记录: INC-20260510-001

**日期**: 2026-05-10
**严重程度**: P2 - 高
**影响范围**: Git 服务不可用,影响全部用户
**持续时间**: 14:00 - 15:30 (90 分钟)

### 事件时间线
- 14:00 监控告警: Gitea 服务无响应
- 14:05 运维人员确认: 服务器内存耗尽
- 14:10 重启 Gitea 服务,问题未解决
- 14:20 发现: 某 CI Job 内存泄漏导致 OOM
- 14:30 终止异常 CI Job,重启服务
- 14:45 服务恢复,功能验证通过
- 15:30 确认稳定,关闭事件

### 根因分析
CI Runner 执行某任务时发生内存泄漏,耗尽服务器内存。

### 修复措施
1. 为 CI Runner 设置内存限制
2. 添加 OOM Killer 配置
3. 增加服务器内存监控告警

### 经验教训
- CI Runner 需要资源限制
- 监控告警需要覆盖内存使用

15.7 持续改进

15.7.1 定期评审

评审内容频率参与者
安全审计每季度安全团队、运维
备份验证每月运维
权限清理每季度团队 Lead、运维
容量规划每半年运维、架构师
流程优化每季度全团队

15.7.2 技术债务跟踪

## 技术债务记录

### TD-001: GitLab 内存优化
- **发现日期**: 2026-03-15
- **影响**: 服务器频繁 OOM
- **优先级**: 高
- **解决方案**: 升级内存到 32GB 或迁移到 Gitea
- **状态**: 进行中

### TD-002: 备份自动化
- **发现日期**: 2026-04-01
- **影响**: 手动备份易出错
- **优先级**: 中
- **解决方案**: 实施 cron 自动备份 + 验证
- **状态**: 已完成

15.8 扩展阅读


本章小结

学到了什么关键要点
运维规范日常巡检清单、自动化巡检脚本、变更管理流程
备份策略3-2-1 原则、分层备份、定期恢复演练
安全加固SSH 加固、防火墙、2FA、敏感信息扫描
团队协作分支策略、提交规范、Code Review、Issue/PR 模板
容量规划资源估算、磁盘增长监控、技术债务跟踪

全书总结

恭喜你完成了 Git 服务器搭建完全指南的全部 15 章学习!

快速选型指南

你的需求推荐方案参考章节
最简单,1-5 人裸仓库 + SSH第 1、2 章
需要权限管理,无 WebGitolite第 3 章
轻量完整平台Gitea第 4 章
社区驱动平台Forgejo第 5 章
企业级 DevOpsGitLab CE第 6 章

下一步行动

  1. 选定方案:根据团队规模和需求选择合适方案
  2. 搭建环境:参考对应章节完成部署
  3. 安全加固:按第 15 章安全清单逐项落实
  4. 配置 CI/CD:参考第 10 章搭建自动化流水线
  5. 建立备份:按第 9 章配置备份并定期验证
  6. 团队协作:制定分支策略和 Code Review 规范

祝你的 Git 服务器运行稳定、团队协作高效!🎉