第 15 章 - 最佳实践
第 15 章 - 最佳实践
综合前 14 章内容,提炼企业级 Git 服务器的运维规范、备份策略、安全加固和团队协作最佳实践。
15.1 运维规范
15.1.1 日常巡检清单
| 检查项 | 频率 | 命令 | 阈值 |
|---|
| 服务状态 | 每日 | systemctl status gitea | 必须 running |
| 磁盘使用率 | 每日 | df -h /opt/git | < 85% |
| 内存使用 | 每日 | free -h | 可用 > 1GB |
| CPU 负载 | 每日 | uptime | load < 核心数 × 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 加固
# 应用配置
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 |
| fix | Bug 修复 | 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 |
| ci | CI/CD | ci: 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 | < 50 | 2 核 | 4 GB | 50 GB | 50 GB/年 |
| 中 | 10-100 | 50-500 | 4 核 | 8 GB | 200 GB | 200 GB/年 |
| 大 | 100-500 | 500-2000 | 8 核 | 16 GB | 500 GB | 500 GB/年 |
| 企业 | 500+ | 2000+ | 16+ 核 | 32+ GB | 1 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 章 |
| 需要权限管理,无 Web | Gitolite | 第 3 章 |
| 轻量完整平台 | Gitea | 第 4 章 |
| 社区驱动平台 | Forgejo | 第 5 章 |
| 企业级 DevOps | GitLab CE | 第 6 章 |
下一步行动
- 选定方案:根据团队规模和需求选择合适方案
- 搭建环境:参考对应章节完成部署
- 安全加固:按第 15 章安全清单逐项落实
- 配置 CI/CD:参考第 10 章搭建自动化流水线
- 建立备份:按第 9 章配置备份并定期验证
- 团队协作:制定分支策略和 Code Review 规范
祝你的 Git 服务器运行稳定、团队协作高效!🎉