Files
novalon-website/docs/plans/2026-04-01-jenkins-migration.md
T

24 KiB
Raw Blame History

Jenkins CI/CD迁移实施计划

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: 将novalon-website项目从Woodpecker CI迁移到Jenkins,实现稳定可靠的CI/CD流程。

Architecture: 在生产服务器(139.155.109.62)上使用Docker部署Jenkins Master,通过Shell模式直接在宿主机执行构建和部署,避免创建临时Docker容器,实现零网络传输的高效构建流程。

Tech Stack: Jenkins LTS, Docker, Nginx, Node.js 20, npm, Git, Shell Script


前置条件

  • 生产服务器:139.155.109.62
  • 域名:ci.f.novalon.cn
  • 项目路径:/home/novalon/docker-app/novalon-website
  • SSH访问权限:root用户
  • Docker已安装并运行

Phase 1: 环境准备(第1天)

Task 1: 创建Jenkins部署目录结构

Files:

  • Create: /home/novalon/docker-app/jenkins/docker-compose.yml
  • Create: /home/novalon/docker-app/jenkins/.env
  • Create: /home/novalon/docker-app/jenkins/backup.sh

Step 1: SSH连接到生产服务器

ssh root@139.155.109.62

Expected: 成功登录到服务器

Step 2: 创建Jenkins目录

mkdir -p /home/novalon/docker-app/jenkins
cd /home/novalon/docker-app/jenkins

Expected: 目录创建成功

Step 3: 创建docker-compose.yml文件

version: '3.8'

services:
  jenkins:
    image: jenkins/jenkins:lts
    container_name: jenkins
    restart: unless-stopped
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/novalon/docker-app:/home/novalon/docker-app
      - /root/.ssh:/root/.ssh:ro
    environment:
      - JENKINS_OPTS=--prefix=/jenkins
      - JAVA_OPTS=-Xmx2g -Xms512m -Duser.timezone=Asia/Shanghai
    user: root
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/jenkins/login"]
      interval: 30s
      timeout: 10s
      retries: 5

volumes:
  jenkins_home:
    driver: local

Step 4: 创建环境变量文件

cat > .env << 'EOF'
JENKINS_VERSION=lts
JAVA_OPTS=-Xmx2g -Xms512m -Duser.timezone=Asia/Shanghai
EOF

Step 5: 创建备份脚本

cat > backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/home/novalon/backups/jenkins"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

docker exec jenkins tar czf - /var/jenkins_home > $BACKUP_DIR/jenkins_backup_$DATE.tar.gz

find $BACKUP_DIR -name "jenkins_backup_*.tar.gz" -mtime +7 -delete

echo "Backup completed: jenkins_backup_$DATE.tar.gz"
EOF

chmod +x backup.sh

Step 6: 提交配置

git add /home/novalon/docker-app/jenkins/
git commit -m "feat: add Jenkins deployment configuration"

Task 2: 启动Jenkins容器

Files:

  • Modify: /home/novalon/docker-app/jenkins/docker-compose.yml

Step 1: 启动Jenkins容器

cd /home/novalon/docker-app/jenkins
docker-compose up -d

Expected:

Creating jenkins ... done

Step 2: 检查容器状态

docker ps | grep jenkins

Expected:

CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS         PORTS                                              NAMES
<container_id> jenkins/jenkins:lts   "/usr/local/bin/jenk…"   1 minute ago    Up 1 minute    0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp   jenkins

Step 3: 查看Jenkins初始密码

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Expected: 输出一个32位的初始管理员密码

Step 4: 保存初始密码

INITIAL_PASSWORD=$(docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword)
echo "Jenkins Initial Password: $INITIAL_PASSWORD" > /home/novalon/docker-app/jenkins/initial_password.txt

Step 5: 验证Jenkins启动

curl -I http://localhost:8080/jenkins/

Expected:

HTTP/1.1 302 Found
Location: http://localhost:8080/jenkins/login

Task 3: 配置Nginx反向代理

Files:

  • Create: /etc/nginx/sites-available/ci.f.novalon.cn.conf
  • Modify: /etc/nginx/sites-enabled/ (创建软链接)

Step 1: 创建Nginx配置文件

cat > /etc/nginx/sites-available/ci.f.novalon.cn.conf << 'EOF'
upstream jenkins {
    server localhost:8080;
}

server {
    listen 80;
    server_name ci.f.novalon.cn;
    
    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ci.f.novalon.cn;

    # SSL证书配置(假设已有证书)
    ssl_certificate /etc/nginx/ssl/novalon.cn.crt;
    ssl_certificate_key /etc/nginx/ssl/novalon.cn.key;
    
    # SSL优化配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # 安全头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Jenkins反向代理
    location / {
        proxy_pass http://jenkins;
        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_set_header X-Forwarded-Port $server_port;
        
        # 增大上传限制
        client_max_body_size 100m;
        
        # 超时配置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # WebSocket支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # 访问日志
    access_log /var/log/nginx/jenkins-access.log;
    error_log /var/log/nginx/jenkins-error.log;
}
EOF

Step 2: 测试Nginx配置

nginx -t

Expected:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Step 3: 启用站点配置

ln -sf /etc/nginx/sites-available/ci.f.novalon.cn.conf /etc/nginx/sites-enabled/

Step 4: 重载Nginx

systemctl reload nginx

Expected:

Job completed successfully

Step 5: 验证HTTPS访问

curl -I https://ci.f.novalon.cn/jenkins/

Expected:

HTTP/2 302 
location: https://ci.f.novalon.cn/jenkins/login

Task 4: 初始化Jenkins

Files:

  • None (通过Web界面操作)

Step 1: 访问Jenkins Web界面

打开浏览器访问:https://ci.f.novalon.cn/jenkins/

Expected: 显示Jenkins解锁页面

Step 2: 输入初始密码

cat /home/novalon/docker-app/jenkins/initial_password.txt

复制密码并粘贴到Web界面

Step 3: 安装推荐插件

在Web界面选择"Install suggested plugins"

Expected: 自动安装以下插件:

  • Git Plugin
  • Pipeline
  • Workspace Cleanup
  • Timestamper
  • Credentials Binding
  • SSH Build Agents
  • Docker Pipeline

Step 4: 创建管理员账户

在Web界面填写:

  • Username: admin
  • Password: <设置强密码>
  • Full name: Jenkins Admin
  • E-mail address: admin@novalon.cn

Step 5: 配置Jenkins URL

确认Jenkins URL为:https://ci.f.novalon.cn/jenkins/

Step 6: 保存配置

点击"Save and Finish",然后点击"Start using Jenkins"

Expected: 进入Jenkins主界面


Task 5: 安装必要插件

Files:

  • None (通过Web界面操作)

Step 1: 进入插件管理

导航到:Manage Jenkins → Manage Plugins → Available

Step 2: 搜索并安装以下插件

勾选以下插件:

  • NodeJS Plugin
  • Pipeline Utility Steps
  • Git Parameter
  • Build Timeout
  • Email Extension
  • AnsiColor

Step 3: 点击"Install without restart"

Expected: 插件安装完成

Step 4: 验证插件安装

导航到:Manage Jenkins → Manage Plugins → Installed

确认所有插件都已安装


Task 6: 配置全局工具

Files:

  • None (通过Web界面操作)

Step 1: 配置Node.js

导航到:Manage Jenkins → Global Tool Configuration

找到"NodeJS"部分,点击"Add NodeJS"

  • Name: NodeJS-20
  • Install automatically: 勾选
  • Version: NodeJS 20.x (选择最新的20.x版本)

Step 2: 配置Git

找到"Git"部分:

  • Name: Default
  • Path to Git executable: git (使用系统Git)

Step 3: 配置Maven(可选)

如果需要Java项目支持:

  • Name: Maven-3.9
  • Install automatically: 勾选
  • Version: 3.9.x

Step 4: 保存配置

点击"Save"


Phase 2: 配置CI/CD流水线(第2天)

Task 7: 创建Jenkins凭据

Files:

  • None (通过Web界面操作)

Step 1: 进入凭据管理

导航到:Manage Jenkins → Credentials → System → Global credentials

Step 2: 添加Git凭据

点击"Add Credentials"

  • Kind: Username with password
  • Scope: Global
  • Username: <Git用户名>
  • Password: <Git密码或Token>
  • ID: git-credentials
  • Description: Git Repository Credentials

Step 3: 添加SSH凭据

点击"Add Credentials"

  • Kind: SSH Username with private key
  • Scope: Global
  • Username: root
  • Private Key: Enter directly
  • Key: <粘贴SSH私钥内容>
  • ID: ssh-credentials
  • Description: SSH Key for Deployment

Step 4: 验证凭据

确认两个凭据都已出现在列表中


Task 8: 创建Jenkinsfile

Files:

  • Create: /home/novalon/docker-app/novalon-website/Jenkinsfile

Step 1: 切换到项目目录

cd /home/novalon/docker-app/novalon-website

Step 2: 创建Jenkinsfile

pipeline {
    agent any
    
    environment {
        NODE_ENV = 'production'
        PROJECT_PATH = '/home/novalon/docker-app/novalon-website'
        BACKUP_PATH = '/home/novalon/backups/novalon-website'
    }
    
    tools {
        nodejs 'NodeJS-20'
    }
    
    options {
        timeout(time: 30, unit: 'MINUTES')
        timestamps()
        ansiColor('xterm')
        buildDiscarder(logRotator(numToKeepStr: '10'))
    }
    
    stages {
        stage('Checkout') {
            steps {
                echo '📥 Checking out code...'
                checkout scm
                sh 'git log -1 --pretty=format:"%h - %an, %ar : %s"'
            }
        }
        
        stage('Install Dependencies') {
            steps {
                echo '📦 Installing dependencies...'
                sh '''
                    npm ci --legacy-peer-deps --prefer-offline || \
                    npm install --legacy-peer-deps
                '''
            }
        }
        
        stage('Quality Gates') {
            parallel {
                stage('Lint') {
                    steps {
                        echo '🔍 Running linter...'
                        sh 'npm run lint'
                    }
                }
                stage('Type Check') {
                    steps {
                        echo '🔍 Running type checker...'
                        sh 'npm run type-check'
                    }
                }
            }
        }
        
        stage('Build') {
            steps {
                echo '🏗️ Building production artifacts...'
                sh '''
                    npm run build
                    ls -la dist/
                '''
            }
        }
        
        stage('Backup Current Version') {
            steps {
                echo '💾 Backing up current version...'
                sh '''
                    mkdir -p ${BACKUP_PATH}
                    if [ -d "${PROJECT_PATH}/dist" ]; then
                        tar czf ${BACKUP_PATH}/backup_$(date +%Y%m%d_%H%M%S).tar.gz \
                            -C ${PROJECT_PATH} dist/ public/ package.json
                        echo "✅ Backup created"
                    else
                        echo "⚠️ No existing dist to backup"
                    fi
                '''
            }
        }
        
        stage('Deploy') {
            steps {
                echo '🚀 Deploying to production...'
                sh '''
                    cd ${PROJECT_PATH}
                    
                    # 停止现有容器
                    docker-compose down || true
                    
                    # 启动新容器
                    docker-compose -f docker-compose.server.yml up -d --build
                    
                    # 等待容器启动
                    sleep 10
                    
                    # 检查容器状态
                    docker-compose -f docker-compose.server.yml ps
                '''
            }
        }
        
        stage('Health Check') {
            steps {
                echo '🏥 Checking application health...'
                sh '''
                    # 等待应用启动
                    sleep 5
                    
                    # 检查HTTP响应
                    for i in {1..10}; do
                        if curl -f http://localhost:3000 > /dev/null 2>&1; then
                            echo "✅ Application is healthy"
                            exit 0
                        fi
                        echo "Waiting for application to start... ($i/10)"
                        sleep 3
                    done
                    
                    echo "❌ Application health check failed"
                    exit 1
                '''
            }
        }
    }
    
    post {
        success {
            echo '''
            ✅ ========================================
            ✅ Deployment Successful!
            ✅ ========================================
            ✅ Project: novalon-website
            ✅ Branch: ${GIT_BRANCH}
            ✅ Commit: ${GIT_COMMIT}
            ✅ Build: #${BUILD_NUMBER}
            ✅ ========================================
            '''
        }
        
        failure {
            echo '''
            ❌ ========================================
            ❌ Deployment Failed!
            ❌ ========================================
            ❌ Project: novalon-website
            ❌ Branch: ${GIT_BRANCH}
            ❌ Commit: ${GIT_COMMIT}
            ❌ Build: #${BUILD_NUMBER}
            ❌ ========================================
            ❌ Check the logs for details
            ❌ ========================================
            '''
        }
        
        always {
            cleanWs()
        }
    }
}

Step 3: 提交Jenkinsfile

git add Jenkinsfile
git commit -m "feat: add Jenkinsfile for CI/CD pipeline"
git push origin main

Task 9: 创建Jenkins Pipeline Job

Files:

  • None (通过Web界面操作)

Step 1: 创建新Job

在Jenkins主界面点击"New Item"

Step 2: 配置Job

  • Enter an item name: novalon-website
  • Select: Pipeline
  • Click: OK

Step 3: 配置General

  • Description: Novalon Website CI/CD Pipeline
  • Discard old builds: 勾选
    • Max # of builds to keep: 10

Step 4: 配置Build Triggers

  • Poll SCM: 勾选
    • Schedule: H/5 * * * * (每5分钟检查一次)

Step 5: 配置Pipeline

Step 6: 保存配置

点击"Save"


Task 10: 配置Git Webhook

Files:

  • None (通过Web界面操作)

Step 1: 获取Jenkins Webhook URL

https://ci.f.novalon.cn/jenkins/git/notifyCommit?url=https://git.f.novalon.cn/novalon/novalon-website.git

Step 2: 在Git仓库配置Webhook

访问Git仓库设置页面:

  • Webhook URL: https://ci.f.novalon.cn/jenkins/git/notifyCommit?url=https://git.f.novalon.cn/novalon/novalon-website.git
  • Content type: application/json
  • Trigger: Push events
  • Active: 勾选

Step 3: 测试Webhook

在Git仓库页面点击"Test Webhook" → "Push"

Expected: Jenkins触发新的构建


Phase 3: 测试和优化(第3天)

Task 11: 执行完整构建测试

Files:

  • None (通过Web界面操作)

Step 1: 手动触发构建

在Jenkins Job页面点击"Build Now"

Step 2: 观察构建过程

点击构建编号,然后点击"Console Output"

Expected:

  • 所有阶段依次执行
  • 没有错误信息
  • 最终显示"Deployment Successful!"

Step 3: 验证部署

访问生产网站:https://novalon.cn

Expected: 网站正常运行,显示最新内容

Step 4: 检查容器状态

ssh root@139.155.109.62
cd /home/novalon/docker-app/novalon-website
docker-compose -f docker-compose.server.yml ps

Expected:

NAME                COMMAND             SERVICE             STATUS              PORTS
novalon-website     "node dist/server"  app                 running             0.0.0.0:3000->3000/tcp

Task 12: 性能优化

Files:

  • Modify: /home/novalon/docker-app/novalon-website/Jenkinsfile

Step 1: 添加npm缓存配置

在Jenkinsfile的"Install Dependencies"阶段添加:

stage('Install Dependencies') {
    steps {
        echo '📦 Installing dependencies...'
        sh '''
            npm config set cache /tmp/npm-cache --global
            npm ci --legacy-peer-deps --prefer-offline --cache /tmp/npm-cache || \
            npm install --legacy-peer-deps --cache /tmp/npm-cache
        '''
    }
}

Step 2: 配置构建缓存

在Jenkinsfile的options部分添加:

options {
    timeout(time: 30, unit: 'MINUTES')
    timestamps()
    ansiColor('xterm')
    buildDiscarder(logRotator(numToKeepStr: '10'))
    // 添加缓存配置
    skipDefaultCheckout(false)
    disableConcurrentBuilds()
}

Step 3: 提交优化

git add Jenkinsfile
git commit -m "perf: optimize Jenkins pipeline with caching"
git push origin main

Step 4: 测试优化效果

再次触发构建,观察构建时间

Expected: 构建时间缩短20-30%


Task 13: 配置邮件通知

Files:

  • None (通过Web界面操作)

Step 1: 配置邮件服务器

导航到:Manage Jenkins → Configure System

找到"Extended E-mail Notification"

  • SMTP server: smtp.novalon.cn
  • Default Content Type: HTML
  • Default Recipients: team@novalon.cn

Step 2: 在Jenkinsfile添加邮件通知

post {
    success {
        emailext (
            subject: "✅ Jenkins Build Success: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
            body: """
                <h2>Build Successful</h2>
                <p><strong>Project:</strong> ${env.JOB_NAME}</p>
                <p><strong>Build Number:</strong> ${env.BUILD_NUMBER}</p>
                <p><strong>Branch:</strong> ${env.GIT_BRANCH}</p>
                <p><strong>Commit:</strong> ${env.GIT_COMMIT}</p>
                <p><a href="${env.BUILD_URL}">View Build Details</a></p>
            """,
            to: 'team@novalon.cn'
        )
    }
    
    failure {
        emailext (
            subject: "❌ Jenkins Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
            body: """
                <h2>Build Failed</h2>
                <p><strong>Project:</strong> ${env.JOB_NAME}</p>
                <p><strong>Build Number:</strong> ${env.BUILD_NUMBER}</p>
                <p><strong>Branch:</strong> ${env.GIT_BRANCH}</p>
                <p><strong>Commit:</strong> ${env.GIT_COMMIT}</p>
                <p><a href="${env.BUILD_URL}console">View Console Output</a></p>
            """,
            to: 'team@novalon.cn'
        )
    }
}

Step 3: 测试邮件通知

触发一次构建,检查邮件是否正常发送


Task 14: 编写操作文档

Files:

  • Create: /home/novalon/docker-app/novalon-website/docs/jenkins/JENKINS_GUIDE.md

Step 1: 创建文档目录

mkdir -p docs/jenkins

Step 2: 编写操作指南

# Jenkins CI/CD 操作指南

## 访问Jenkins

- URL: https://ci.f.novalon.cn/jenkins/
- 用户名: admin
- 密码: <存储在安全位置>

## 触发构建

### 方式1: 自动触发
- 代码推送到main分支时自动触发
- Webhook配置在Git仓库设置中

### 方式2: 手动触发
1. 登录Jenkins
2. 选择Job: novalon-website
3. 点击"Build Now"

## 查看构建日志

1. 点击构建编号
2. 点击"Console Output"

## 回滚操作

如果部署失败,可以回滚到上一个版本:

\`\`\`bash
cd /home/novalon/docker-app/novalon-website
# 查看备份列表
ls -lt /home/novalon/backups/novalon-website/

# 恢复备份
tar xzf /home/novalon/backups/novalon-website/backup_YYYYMMDD_HHMMSS.tar.gz -C ./

# 重启容器
docker-compose -f docker-compose.server.yml restart
\`\`\`

## 故障排查

### 构建失败
1. 检查Console Output查看错误信息
2. 检查依赖是否正确安装
3. 检查代码是否有语法错误

### 部署失败
1. 检查容器日志: `docker-compose logs`
2. 检查端口占用: `netstat -tlnp | grep 3000`
3. 检查磁盘空间: `df -h`

### Webhook不触发
1. 检查Git仓库Webhook配置
2. 检查Jenkins是否可达
3. 查看Jenkins系统日志

## 维护操作

### 备份Jenkins数据
\`\`\`bash
cd /home/novalon/docker-app/jenkins
./backup.sh
\`\`\`

### 更新Jenkins
\`\`\`bash
cd /home/novalon/docker-app/jenkins
docker-compose pull
docker-compose up -d
\`\`\`

### 查看Jenkins日志
\`\`\`bash
docker logs jenkins -f
\`\`\`

Step 3: 提交文档

git add docs/jenkins/
git commit -m "docs: add Jenkins operation guide"
git push origin main

Phase 4: 清理和收尾

Task 15: 停用Woodpecker CI

Files:

  • Modify: /home/novalon/docker-app/novalon-website/.woodpecker.yml

Step 1: 备份Woodpecker配置

cp .woodpecker.yml .woodpecker.yml.backup

Step 2: 重命名Woodpecker配置

mv .woodpecker.yml .woodpecker.yml.disabled

Step 3: 提交更改

git add .woodpecker.yml.disabled
git rm .woodpecker.yml
git commit -m "chore: disable Woodpecker CI, switch to Jenkins"
git push origin main

Step 4: 验证Woodpecker不再触发

推送代码后,确认Woodpecker不再创建新的Pipeline


Task 16: 最终验证

Files:

  • None

Step 1: 执行完整构建流程

# 在本地修改代码
echo "<!-- Test Jenkins CI/CD -->" >> src/app/layout.tsx

# 提交并推送
git add .
git commit -m "test: verify Jenkins CI/CD pipeline"
git push origin main

Step 2: 验证自动触发

  • 检查Jenkins是否自动触发构建
  • 检查构建是否成功
  • 检查网站是否更新

Step 3: 验证邮件通知

检查是否收到构建成功邮件

Step 4: 验证回滚功能

# 恢复之前的代码
git reset --hard HEAD~1
git push -f origin main

确认Jenkins触发构建并成功回滚


风险评估和缓解措施

风险1: Jenkins容器故障

缓解措施:

  • 定期备份Jenkins数据(每天自动备份)
  • 使用Docker Volume持久化数据
  • 监控容器健康状态

风险2: 构建失败

缓解措施:

  • 自动备份当前版本
  • 提供快速回滚机制
  • 详细的错误日志和通知

风险3: 网络问题

缓解措施:

  • Jenkins部署在同一台服务器,避免网络传输
  • 配置超时和重试机制
  • 使用本地缓存加速构建

风险4: 安全问题

缓解措施:

  • 使用HTTPS加密通信
  • 配置强密码和访问控制
  • 定期更新Jenkins和插件
  • 限制网络访问(仅允许必要IP

成功标准

  1. Jenkins成功部署并运行
  2. CI/CD流水线正常工作
  3. 代码推送自动触发构建
  4. 构建成功率 > 95%
  5. 平均构建时间 < 10分钟
  6. 部署成功率 = 100%
  7. 回滚功能正常
  8. 邮件通知正常
  9. 文档完善
  10. 团队成员培训完成

后续优化建议

  1. 性能优化

    • 配置npm缓存加速依赖安装
    • 使用并行构建减少总时间
    • 优化Docker镜像构建
  2. 功能增强

    • 添加单元测试和集成测试
    • 配置代码质量扫描
    • 添加性能测试
  3. 监控告警

    • 集成Prometheus监控
    • 配置Grafana可视化
    • 设置告警规则
  4. 安全加固

    • 配置RBAC权限控制
    • 启用审计日志
    • 定期安全扫描

参考资源