Jenkins踩坑记录
去年我们团队引入Jenkins做持续集成,从安装到配置走了很多弯路,这里记录一下过程。
Jenkins基础安装与启动
Jenkins启动方式
Jenkins支持多种部署方式,最常用的方式是使用内置的Jetty服务器直接运行:
1 2 3 4 5
| java -jar jenkins.war --httpPort=8080 &
java -jar jenkins.war --httpPort=8080 --webroot=/var/lib/jenkins/war &
|
常用启动参数:
| 参数 |
说明 |
示例 |
--httpPort |
HTTP服务端口 |
--httpPort=8080 |
--httpsPort |
HTTPS服务端口 |
--httpsPort=8443 |
--prefix |
URL前缀 |
--prefix=/jenkins |
--webroot |
Web根目录 |
--webroot=/data/jenkins |
后台运行配置
在生产环境中,建议使用systemd管理服务:
1 2
| sudo vim /etc/systemd/system/jenkins.service
|
服务文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [Unit] Description=Jenkins CI Server After=network.target
[Service] Type=simple User=jenkins Group=jenkins ExecStart=/usr/bin/java -jar /opt/jenkins/jenkins.war --httpPort=8080 Restart=always RestartSec=10
[Install] WantedBy=multi-user.target
|
启动并启用服务:
1 2 3
| sudo systemctl daemon-reload sudo systemctl start jenkins sudo systemctl enable jenkins
|
Jenkins插件管理
核心插件安装
Jenkins的功能主要通过插件扩展,以下是常用插件清单:
版本控制插件
| 插件名 |
用途 |
推荐场景 |
| Subversion |
SVN版本控制支持 |
使用SVN的团队 |
| Git |
Git版本控制支持 |
使用Git的团队 |
| GitHub Branch Source |
GitHub多分支管理 |
GitHub项目 |
构建工具插件
| 插件名 |
用途 |
推荐场景 |
| NodeJS |
Node.js环境管理 |
前端/Node项目 |
| Gradle |
Gradle构建支持 |
Android/Gradle项目 |
| Maven Integration |
Maven构建支持 |
Java Maven项目 |
部署与通知插件
| 插件名 |
用途 |
推荐场景 |
| Publish Over SSH |
SSH远程部署 |
服务器部署 |
| Slack Notification |
Slack消息通知 |
团队协作 |
| Email Extension |
邮件通知 |
传统通知方式 |
插件安装步骤
- 进入Jenkins管理界面
- 点击”Manage Jenkins” - “Manage Plugins”
- 选择”Available”标签页
- 搜索需要的插件并勾选
- 点击”Install without restart”
SVN集成配置
安装Subversion插件
1 2
| Manage Jenkins → Manage Plugins → Available 搜索 "Subversion" → 勾选安装
|
创建SVN项目
新建自由风格项目
- 点击”New Item”
- 输入项目名称
- 选择”Freestyle project”
- 点击”OK”
源码管理配置
SVN配置参数:
1 2 3 4
| Repository URL: svn://your-svn-server/project/trunk Credentials: 添加SVN用户名密码 Local module directory: .(检出到工作目录根) Repository depth: infinity(完整检出)
|
触发器配置
1 2 3 4 5 6 7
| Build Triggers: ☑ Poll SCM Schedule: H/5 * * * * (每5分钟轮询)
或
☑ Build when a change is pushed to SVN
|
SVN强制提交注释
为了保证代码提交质量,可以配置SVN提交前强制要求填写注释。
服务端配置
1. 准备钩子脚本
1 2 3
| cd /path/to/svn/repo/hooks cp pre-commit.tmpl pre-commit chmod +x pre-commit
|
2. 编写pre-commit脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #!/bin/bash
REPOS="$1" TXN="$2"
SVNLOOK=/usr/bin/svnlook
LOGMSG=`$SVNLOOK log -t $TXN $REPOS | wc -m`
if [ "$LOGMSG" -lt 48 ] then echo "提交失败:至少输入4个汉字的描述信息" >&2 exit 1 fi
exit 0
|
3. 设置可执行权限
配置说明:
- 一个汉字占用3个字节(UTF-8)
wc -m 统计字符数,4个汉字约为12个字符(不含换行)
- 根据需要调整最小长度阈值
Node.js与TypeScript编译配置
安装NodeJS插件
1 2
| Manage Jenkins → Manage Plugins → Available 搜索 "NodeJS" → 勾选安装
|
配置Node.js环境
全局工具配置:
1 2 3 4 5
| Manage Jenkins → Global Tool Configuration → NodeJS
Name: NodeJS-16 Version: 16.x.x (选择需要的版本) Global npm packages to install: typescript@latest
|
TypeScript项目构建
项目结构示例
1 2 3 4 5 6 7
| my-project/ ├── src/ │ ├── index.ts │ └── utils/ ├── dist/ # 编译输出目录 ├── package.json └── tsconfig.json
|
tsconfig.json配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }
|
Jenkins构建配置
构建环境:
1 2
| Provide Node & npm bin/ folder to PATH NodeJS Installation: NodeJS-16
|
构建步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| npm ci
./node_modules/.bin/tsc -p tsconfig.json
npx tsc -p tsconfig.json
npm test
npm run build cp -r dist/* /var/www/html/
|
多环境配置
对于需要构建多个环境(开发/测试/生产)的项目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| case $BUILD_ENV in "dev") cp config/dev.config.ts src/config.ts ;; "test") cp config/test.config.ts src/config.ts ;; "prod") cp config/prod.config.ts src/config.ts ;; esac
npx tsc -p tsconfig.json
|
Jenkins Pipeline流水线
Pipeline基础概念
Jenkins Pipeline使用Groovy DSL定义完整的构建流程,支持:
声明式Pipeline示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| pipeline { agent any
environment { NODE_VERSION = '16' DEPLOY_PATH = '/var/www/app' }
stages { stage('Checkout') { steps { checkout scm } }
stage('Install Dependencies') { steps { nodejs(nodeJSInstallationName: 'NodeJS-16') { sh 'npm ci' } } }
stage('Compile TypeScript') { steps { nodejs(nodeJSInstallationName: 'NodeJS-16') { sh 'npx tsc -p tsconfig.json' } } }
stage('Run Tests') { steps { nodejs(nodeJSInstallationName: 'NodeJS-16') { sh 'npm test' } } }
stage('Deploy') { when { branch 'main' } steps { sh ''' rsync -avz --delete dist/ user@server:${DEPLOY_PATH} ''' } } }
post { always { cleanWs() } success { echo '构建成功!' } failure { echo '构建失败!' } } }
|
Pipeline脚本式示例
对于更灵活的场景,可以使用脚本式Pipeline:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| node { try { stage('准备') { checkout scm }
stage('构建') { nodejs(nodeJSInstallationName: 'NodeJS-16') { sh 'npm ci' sh 'npx tsc' } }
stage('测试') { nodejs(nodeJSInstallationName: 'NodeJS-16') { sh 'npm test' } }
if (env.BRANCH_NAME == 'main') { stage('部署') { sshPublisher( publishers: [ sshPublisherDesc( configName: 'production-server', transfers: [ sshTransfer( sourceFiles: 'dist/**', removePrefix: 'dist', remoteDirectory: '/var/www/app' ) ] ) ] ) } } } catch (e) { currentBuild.result = 'FAILURE' throw e } finally { cleanWs() } }
|
自动化部署策略
部署方式对比
| 方式 |
工具 |
适用场景 |
特点 |
| SSH命令 |
ssh/sshPublisher |
单服务器 |
简单直接 |
| Rsync |
rsync |
多服务器 |
增量同步 |
| Docker |
docker/docker-compose |
容器化部署 |
环境一致 |
| Kubernetes |
kubectl/helm |
大规模部署 |
自动扩缩容 |
SSH部署配置
Publish Over SSH插件配置:
1 2 3 4 5 6 7 8 9 10 11
| Manage Jenkins → Configure System → Publish over SSH
SSH Servers: Name: production-server Hostname: 192.168.1.100 Username: deploy Remote Directory: /var/www
Advanced: ☑ Use password authentication, or use a different key Passphrase / Password: ********
|
构建后操作:
1 2 3 4 5 6 7 8 9
| Send build artifacts over SSH SSH Server: production-server Source files: dist/**/* Remove prefix: dist Remote directory: app Exec command: | cd /var/www/app npm install --production pm2 restart app
|
Docker部署
Dockerfile示例:
1 2 3 4 5 6 7 8 9 10 11 12
| FROM node:16-alpine
WORKDIR /app
COPY package*.json ./ RUN npm ci --only=production
COPY dist/ ./dist/
EXPOSE 3000
CMD ["node", "dist/index.js"]
|
Pipeline中的Docker构建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| stage('Docker Build & Push') { steps { script { def image = docker.build("myapp:${BUILD_NUMBER}") docker.withRegistry('https://registry.example.com', 'docker-credentials') { image.push() image.push('latest') } } } }
stage('Docker Deploy') { steps { sshPublisher( publishers: [ sshPublisherDesc( configName: 'docker-host', execCommand: ''' docker pull registry.example.com/myapp:latest docker-compose up -d ''' ) ] ) } }
|
最佳实践与优化
构建优化策略
依赖缓存
1 2 3 4 5
| npm ci
yarn install --offline
|
增量构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| stage('TypeScript Compile') { steps { script { def changed = sh( script: 'git diff --name-only HEAD~1 | grep -E "\\.ts$"', returnStatus: true ) if (changed == 0) { sh 'npx tsc' } else { echo 'No TypeScript files changed, skipping compile' } } } }
|
安全建议
- 凭证管理:使用Jenkins Credentials存储密码、密钥
- 权限控制:基于角色的访问控制(Role-based Authorization)
- 安全更新:定期更新Jenkins和插件版本
- 备份策略:定期备份Jenkins主目录
监控与告警
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| post { failure { emailext ( subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}", body: """构建失败! 项目: ${env.JOB_NAME} 构建号: ${env.BUILD_NUMBER} 详情: ${env.BUILD_URL} """, to: "${env.CHANGE_AUTHOR_EMAIL}" ) } fixed { slackSend( color: 'good', message: "✅ 构建恢复: ${env.JOB_NAME} #${env.BUILD_NUMBER}" ) } }
|
总结
Jenkins配置虽然繁琐,但一旦跑起来能节省很多重复工作。建议从小规模项目开始,逐步完善自动化流程。
参考资源: