Golang_demo/Jenkinsfile
2025-06-25 16:52:58 +08:00

518 lines
21 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
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 60, unit: 'MINUTES')
timestamps()
}
tools {
go 'go' // 使用Jenkins手动配置的Go工具
hudson.plugins.sonar.SonarRunnerInstallation 'sonarQube' // 使用Jenkins配置的SonarQube工具
}
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()
}
echo "📋 Git提交ID: ${env.GIT_COMMIT_SHORT}"
}
}
stage('环境检查') {
steps {
echo '🔍 检查构建环境...'
script {
sh '''
echo "=== Go版本 ==="
go version
echo "=== Go环境信息 ==="
go env
echo "=== Docker版本 ==="
docker --version
echo "=== 工作目录 ==="
pwd && ls -la
'''
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 -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 {
// 使用withSonarQubeEnv包裹自动配置sonar-scanner环境
withSonarQubeEnv('sonarQube') {
sh '''
echo "检查SonarQube环境变量..."
echo "SONAR_SCANNER_HOME: $SONAR_SCANNER_HOME"
echo "PATH: $PATH"
# 直接使用配置的sonar-scanner
if [ -n "$SONAR_SCANNER_HOME" ] && [ -f "$SONAR_SCANNER_HOME/bin/sonar-scanner" ]; then
SCANNER_PATH="$SONAR_SCANNER_HOME/bin/sonar-scanner"
else
echo "❌ SONAR_SCANNER_HOME未正确配置"
exit 1
fi
echo "✅ 使用sonar-scanner: $SCANNER_PATH"
# 运行SonarQube扫描
"$SCANNER_PATH" \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.projectName="Golang Demo" \
-Dsonar.projectVersion=${BUILD_NUMBER} \
-Dsonar.sources=. \
-Dsonar.exclusions=**/*_test.go,**/vendor/**,**/*.mod,**/*.sum \
-Dsonar.tests=. \
-Dsonar.test.inclusions=**/*_test.go \
-Dsonar.test.exclusions=**/vendor/** \
-Dsonar.go.coverage.reportPaths=coverage.out \
-Dsonar.sourceEncoding=UTF-8 \
-Dsonar.language=go
'''
}
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.gitCommit=${GIT_COMMIT_SHORT}" \\
-o golang-demo .
echo "验证二进制文件..."
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构建参考Java项目的成功模式
timeout(time: 20, unit: 'MINUTES') {
sh """
# 确保二进制文件存在并有执行权限
ls -la golang-demo
chmod +x golang-demo
# 使用传统Docker构建无需构建参数直接复制已编译文件
docker build -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 """
echo "启动测试容器..."
docker run -d --name test-${BUILD_NUMBER} -p 8081:8080 golang-demo:${BUILD_NUMBER}
echo "等待应用启动..."
sleep 20
echo "检查容器状态..."
docker ps | grep test-${BUILD_NUMBER} || true
docker logs test-${BUILD_NUMBER} || true
echo "测试健康检查端点..."
for i in \$(seq 1 5); do
echo "尝试连接 \$i/5..."
if curl -f http://localhost:8081/health; then
echo "✅ 健康检查通过"
break
elif curl -f http://localhost:8081/ping; then
echo "✅ Ping检查通过"
break
else
echo "⚠️ 连接失败等待5秒后重试..."
sleep 5
fi
if [ \$i -eq 5 ]; then
echo "❌ 镜像测试失败,但不中断构建"
docker logs test-${BUILD_NUMBER}
exit 1
fi
done
"""
echo "✅ 镜像测试完成"
} catch (Exception e) {
echo "⚠️ 镜像测试失败: ${e.getMessage()}"
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 \\
${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 = '15021'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15022'
}
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 coverage.out coverage.html sonar-project.properties || true'
// 清理Docker资源
sh 'docker image prune -f || true'
sh 'docker builder prune -f || true'
} catch (Exception e) {
echo "⚠️ 清理失败: ${e.getMessage()}"
}
}
}
success {
script {
echo '✅ 流水线执行成功!'
def deployPort = '15021'
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME?.startsWith('feature/')) {
deployPort = '15022'
}
def message = """
🎉 Jenkins构建成功
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
⏱️ 持续时间: ${currentBuild.durationString}
🔗 构建链接: ${env.BUILD_URL}
🌐 应用地址: http://${DEPLOY_SERVER}:${deployPort}
"""
echo message
}
}
failure {
script {
echo '❌ 流水线执行失败!'
def message = """
💥 Jenkins构建失败
📋 项目: ${env.JOB_NAME}
🔢 构建号: ${env.BUILD_NUMBER}
🌿 分支: ${env.BRANCH_NAME ?: 'unknown'}
📝 提交: ${env.GIT_COMMIT_SHORT ?: 'unknown'}
⏱️ 持续时间: ${currentBuild.durationString}
🔗 构建链接: ${env.BUILD_URL}
📄 查看日志: ${env.BUILD_URL}console
"""
echo message
// 清理可能的测试容器
sh "docker stop test-${BUILD_NUMBER} || true"
sh "docker rm test-${BUILD_NUMBER} || true"
}
}
cleanup {
script {
try {
// 清理工作空间
cleanWs()
echo "✅ 清理完成"
} catch (Exception e) {
echo "⚠️ 清理失败: ${e.getMessage()}"
}
}
}
}
}