Golang_demo/Jenkinsfile
2025-06-25 13:40:20 +08:00

515 lines
20 KiB
Groovy
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

pipeline {
agent any
// 声明使用Jenkins配置的Go工具
tools {
go 'go' // 修正:使用小写的'go'与Jenkins配置的工具名称一致
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 60, unit: 'MINUTES')
timestamps()
}
environment {
// 目标服务器配置
DEPLOY_SERVER = '116.62.163.84'
// Go相关环境变量
GO_VERSION = '1.21'
CGO_ENABLED = '0'
GOOS = 'linux'
// Docker相关环境变量
IMAGE_NAME = 'golang-demo'
IMAGE_TAG = "${BUILD_NUMBER}"
// SonarQube配置如果需要Go代码扫描
SONAR_HOST_URL = 'http://116.62.163.84:15010'
SONAR_PROJECT_KEY = 'golang-demo'
SONAR_TOKEN = 'squ_7e4217cabd0faae6f3b8ee359b3b8e2ac52eb69a'
}
stages {
stage('Checkout') {
steps {
echo '🔄 开始检出代码...'
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: "git rev-parse --short HEAD",
returnStdout: true
).trim()
env.BUILD_TIME = sh(
script: 'date -u +"%Y-%m-%dT%H:%M:%SZ"',
returnStdout: true
).trim()
}
echo "📋 Git提交ID: ${env.GIT_COMMIT_SHORT}"
echo "⏰ 构建时间: ${env.BUILD_TIME}"
}
}
stage('环境检查') {
steps {
echo '🔍 检查构建环境...'
script {
sh '''
echo "=== Go版本 ==="
go version
echo "=== Go环境信息 ==="
go env
echo "=== 验证Go工具路径 ==="
which go
echo "GOROOT: $GOROOT"
echo "GOPATH: $GOPATH"
echo "PATH: $PATH"
echo "=== Docker版本 ==="
docker --version
echo "=== 工作目录 ==="
pwd && ls -la
echo "=== Go模块信息 ==="
cat go.mod
echo "=== 验证Go模块可用性 ==="
go mod verify || echo "模块验证跳过(首次运行)"
'''
echo "✅ 构建环境检查完成"
}
}
}
stage('依赖管理') {
steps {
echo '📦 下载Go依赖...'
sh '''
echo "下载依赖..."
go mod download
echo "验证依赖..."
go mod verify
echo "整理依赖..."
go mod tidy
echo "✅ 依赖管理完成"
'''
}
}
stage('代码检查') {
steps {
echo '🔍 运行Go代码检查...'
sh '''
echo "运行go vet..."
go vet ./...
echo "运行go fmt检查..."
if [ "$(gofmt -l . | wc -l)" -gt 0 ]; then
echo "❌ 代码格式不正确,需要运行 go fmt"
gofmt -l .
exit 1
fi
echo "✅ 代码检查通过"
'''
}
}
stage('单元测试') {
steps {
echo '🧪 运行单元测试...'
sh '''
echo "运行测试并生成覆盖率报告..."
go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
echo "生成HTML覆盖率报告..."
go tool cover -html=coverage.out -o coverage.html
echo "显示覆盖率统计..."
go tool cover -func=coverage.out
'''
}
post {
always {
script {
// 发布覆盖率报告
if (fileExists('coverage.html')) {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'coverage.html',
reportName: 'Go Coverage Report'
])
echo '✅ Go覆盖率报告已发布'
}
// 归档覆盖率文件
if (fileExists('coverage.out')) {
archiveArtifacts artifacts: 'coverage.out', fingerprint: true
}
}
}
}
}
stage('代码质量扫描') {
steps {
echo '🔍 运行SonarQube代码扫描...'
script {
try {
// 使用sonar-scanner for Go项目
sh """
# 创建sonar-project.properties文件
cat > sonar-project.properties << EOF
sonar.projectKey=${SONAR_PROJECT_KEY}
sonar.projectName=golang-demo
sonar.projectVersion=1.0.0
sonar.sources=.
sonar.exclusions=vendor/**,**/*_test.go,**/testdata/**
sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.go.coverage.reportPaths=coverage.out
sonar.host.url=${SONAR_HOST_URL}
sonar.login=${SONAR_TOKEN}
EOF
# 运行sonar-scanner
sonar-scanner || echo "SonarQube扫描工具未安装跳过扫描"
"""
echo "✅ SonarQube代码扫描完成"
} catch (Exception e) {
echo "⚠️ SonarQube扫描失败继续构建流程: ${e.getMessage()}"
}
}
}
}
stage('编译构建') {
steps {
echo '🔨 编译Go应用程序...'
sh '''
echo "开始编译..."
CGO_ENABLED=0 GOOS=linux go build \\
-ldflags="-w -s -X main.buildTime=${BUILD_TIME} -X main.gitCommit=${GIT_COMMIT_SHORT}" \\
-o golang-demo .
echo "验证二进制文件..."
file golang-demo
ls -lh golang-demo
echo "✅ 编译完成"
'''
}
post {
success {
script {
if (fileExists('golang-demo')) {
archiveArtifacts artifacts: 'golang-demo', fingerprint: true
}
}
}
}
}
stage('构建Docker镜像') {
steps {
echo '🐳 构建Docker镜像...'
script {
try {
// 清理旧镜像
sh 'docker image prune -f || true'
echo "开始构建Docker镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
// 构建Docker镜像
timeout(time: 20, unit: 'MINUTES') {
sh """
# 构建Docker镜像传入构建参数
docker build \\
--build-arg BUILD_TIME="${BUILD_TIME}" \\
--build-arg GIT_COMMIT="${GIT_COMMIT_SHORT}" \\
-t ${IMAGE_NAME}:${IMAGE_TAG} \\
-t ${IMAGE_NAME}:latest .
echo "✅ Docker镜像构建完成"
# 验证镜像
docker images ${IMAGE_NAME}:${IMAGE_TAG}
"""
}
} catch (Exception e) {
echo "❌ Docker构建失败: ${e.getMessage()}"
// 显示更多调试信息
sh '''
echo "=== Docker系统信息 ==="
docker system info
echo "=== Docker磁盘使用情况 ==="
docker system df
echo "=== 当前镜像列表 ==="
docker images
'''
throw e
}
}
}
}
stage('镜像测试') {
steps {
echo '🧪 测试Docker镜像...'
script {
try {
sh """
# 启动测试容器
docker run -d --name test-${BUILD_NUMBER} -p 8081:8080 ${IMAGE_NAME}:${IMAGE_TAG}
# 等待容器启动
sleep 10
# 测试健康检查
curl -f http://localhost:8081/health || exit 1
curl -f http://localhost:8081/ping || exit 1
curl -f http://localhost:8081/version || exit 1
echo "✅ 镜像测试通过"
"""
} finally {
// 清理测试容器
sh """
docker stop test-${BUILD_NUMBER} || true
docker rm test-${BUILD_NUMBER} || true
"""
}
}
}
}
stage('部署应用') {
steps {
echo '🚀 部署应用到服务器...'
script {
// 根据分支决定部署端口和配置
def deployPort = '15021'
def containerName = 'golang-demo'
def environment = 'production'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15022' // 测试环境使用不同端口
containerName = 'golang-demo-test'
environment = 'test'
}
// 传输镜像并部署
sshagent(['deploy-server-ssh-key']) {
sh """
# 保存Docker镜像为tar文件
echo "📤 保存镜像为文件..."
docker save ${IMAGE_NAME}:${IMAGE_TAG} -o ${IMAGE_NAME}-${IMAGE_TAG}.tar
# 传输镜像文件到目标服务器
echo "📤 传输镜像到服务器..."
scp -o StrictHostKeyChecking=no ${IMAGE_NAME}-${IMAGE_TAG}.tar root@${DEPLOY_SERVER}:/tmp/
# 在目标服务器上部署
ssh -o StrictHostKeyChecking=no root@${DEPLOY_SERVER} << 'EOF'
echo "🔄 在服务器上部署应用..."
# 加载Docker镜像
docker load -i /tmp/${IMAGE_NAME}-${IMAGE_TAG}.tar
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
# 停止并删除现有容器
docker stop ${containerName} || true
docker rm ${containerName} || true
# 运行新容器
docker run -d --name ${containerName} \\
-p ${deployPort}:8080 \\
--restart unless-stopped \\
-e GIN_MODE=release \\
-e ENVIRONMENT=${environment} \\
${IMAGE_NAME}:${IMAGE_TAG}
# 清理临时文件
rm -f /tmp/${IMAGE_NAME}-${IMAGE_TAG}.tar
echo "✅ 应用部署完成,端口: ${deployPort}"
echo "🔗 访问地址: http://${DEPLOY_SERVER}:${deployPort}"
EOF
# 清理本地临时文件
rm -f ${IMAGE_NAME}-${IMAGE_TAG}.tar
"""
}
echo "✅ 应用部署完成!"
}
}
}
stage('健康检查') {
steps {
echo '🏥 执行应用健康检查...'
script {
// 等待应用启动
sleep(time: 30, unit: 'SECONDS')
def deployPort = '15020'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15021'
}
try {
// Go应用的健康检查端点
def healthCheckUrl = "http://${DEPLOY_SERVER}:${deployPort}/health"
def response = sh(
script: "curl -s -o /dev/null -w '%{http_code}' ${healthCheckUrl} || echo '000'",
returnStdout: true
).trim()
if (response == "200") {
echo "✅ 应用健康检查通过"
// 额外检查其他端点
def pingResponse = sh(
script: "curl -s -o /dev/null -w '%{http_code}' http://${DEPLOY_SERVER}:${deployPort}/ping || echo '000'",
returnStdout: true
).trim()
if (pingResponse == "200") {
echo "✅ Ping端点检查通过"
}
} else {
echo "⚠️ 应用健康检查失败HTTP状态码: ${response}"
echo "🔄 应用可能还在启动中..."
// 再等待一段时间重试
sleep(time: 30, unit: 'SECONDS')
response = sh(
script: "curl -s -o /dev/null -w '%{http_code}' ${healthCheckUrl} || echo '000'",
returnStdout: true
).trim()
if (response == "200") {
echo "✅ 重试后健康检查通过"
} else {
echo "⚠️ 健康检查仍未通过,但不阻止构建"
}
}
} catch (Exception e) {
echo "⚠️ 健康检查异常: ${e.getMessage()}"
}
}
}
}
}
post {
always {
script {
echo '🧹 清理工作空间...'
try {
// 清理Go构建产物
sh '''
rm -f golang-demo
rm -f coverage.out coverage.html
rm -f sonar-project.properties
'''
// 清理Docker资源
sh '''
# 清理未使用的镜像
docker image prune -f || true
# 清理构建缓存
docker builder prune -f || true
'''
} catch (Exception e) {
echo "⚠️ 清理失败: ${e.getMessage()}"
}
}
}
success {
script {
echo '✅ 流水线执行成功!'
def deployPort = '15020'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15021'
}
def message = """
🎉 Jenkins构建成功
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
⏰ 构建时间: ${env.BUILD_TIME ?: 'unknown'}
⏱️ 持续时间: ${currentBuild.durationString}
🔗 构建链接: ${env.BUILD_URL}
🌐 应用地址: http://${DEPLOY_SERVER}:${deployPort}
🏥 健康检查: http://${DEPLOY_SERVER}:${deployPort}/health
🏓 Ping测试: http://${DEPLOY_SERVER}:${deployPort}/ping
"""
echo message
}
}
failure {
script {
echo '❌ 流水线执行失败!'
def message = """
💥 Jenkins构建失败
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
⏰ 构建时间: ${env.BUILD_TIME ?: 'unknown'}
⏱️ 持续时间: ${currentBuild.durationString}
🔗 构建链接: ${env.BUILD_URL}
📄 查看日志: ${env.BUILD_URL}console
"""
echo message
// 清理可能的测试容器
sh '''
docker stop test-${BUILD_NUMBER} || true
docker rm test-${BUILD_NUMBER} || true
'''
}
}
cleanup {
script {
try {
// 清理工作空间
cleanWs()
echo "✅ 清理完成"
} catch (Exception e) {
echo "⚠️ 清理失败: ${e.getMessage()}"
}
}
}
}
}