From 480dcd46c34bee3a5035036f4e13cac2357a3f70 Mon Sep 17 00:00:00 2001 From: dxin Date: Wed, 17 Dec 2025 17:32:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile/go/Dockerfile | 12 +- Dockerfile/python/apex/apex_dockerfile | 62 ++++ Dockerfile/web/apex/aqex_web_Dockerfile | 42 +++ Dockerfile/web/apex/nginx.conf | 65 ++++ Dockerfile/web/aqex_web_Dockerfile | 41 +++ .../{ => v1}/build_image_flymoon_admin.groovy | 0 .../{ => v1}/build_image_flymoon_admin_web.groovy | 0 .../{ => v1}/build_image_flymoon_agent.groovy | 0 .../{ => v1}/build_image_flymoon_email.groovy | 0 .../{ => v1}/build_image_flymoon_payment.groovy | 0 .../{ => v1}/build_image_lessie_agents.groovy | 0 .../{ => v1}/build_image_lessie_ai_web.groovy | 0 .../{ => v1}/build_image_lessie_go_api.groovy | 0 .../build_image_lessie_official_web.groovy | 0 SCM/构建镜像/v2/build_image_apex_v2.groovy | 322 ++++++++++++++++++ .../v2/build_image_apex_web_v2.groovy | 322 ++++++++++++++++++ .../{ => v2}/build_image_flymoon_admin_v2.groovy | 0 .../build_image_flymoon_admin_web_v2.groovy | 0 .../{ => v2}/build_image_flymoon_agent_v2.groovy | 0 .../{ => v2}/build_image_flymoon_email_v2.groovy | 0 .../build_image_flymoon_payment_v2.groovy | 0 .../{ => v2}/build_image_lessie_agents_v2.groovy | 0 .../{ => v2}/build_image_lessie_ai_web_v2.groovy | 0 .../{ => v2}/build_image_lessie_go_api_v2.groovy | 0 .../build_image_lessie_official_web_v2.groovy | 0 .../ELK/filebast/02-filebeat-configmap.yaml | 2 +- .../ELK/filebast/022-filebeat-configmap.yaml | 48 ++- k8s_yaml/ELK/filebast/filebeat.yaml | 38 +++ 28 files changed, 934 insertions(+), 20 deletions(-) create mode 100644 Dockerfile/python/apex/apex_dockerfile create mode 100644 Dockerfile/web/apex/aqex_web_Dockerfile create mode 100644 Dockerfile/web/apex/nginx.conf create mode 100644 Dockerfile/web/aqex_web_Dockerfile rename SCM/构建镜像/{ => v1}/build_image_flymoon_admin.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_flymoon_admin_web.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_flymoon_agent.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_flymoon_email.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_flymoon_payment.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_lessie_agents.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_lessie_ai_web.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_lessie_go_api.groovy (100%) rename SCM/构建镜像/{ => v1}/build_image_lessie_official_web.groovy (100%) create mode 100644 SCM/构建镜像/v2/build_image_apex_v2.groovy create mode 100644 SCM/构建镜像/v2/build_image_apex_web_v2.groovy rename SCM/构建镜像/{ => v2}/build_image_flymoon_admin_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_flymoon_admin_web_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_flymoon_agent_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_flymoon_email_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_flymoon_payment_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_lessie_agents_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_lessie_ai_web_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_lessie_go_api_v2.groovy (100%) rename SCM/构建镜像/{ => v2}/build_image_lessie_official_web_v2.groovy (100%) create mode 100644 k8s_yaml/ELK/filebast/filebeat.yaml diff --git a/Dockerfile/go/Dockerfile b/Dockerfile/go/Dockerfile index cf77163..fd2bec0 100644 --- a/Dockerfile/go/Dockerfile +++ b/Dockerfile/go/Dockerfile @@ -5,17 +5,19 @@ FROM alpine:latest ENV APP_ENV=local \ APP_PORT=8100 -# 安装必要依赖(若Go二进制依赖系统C库,无依赖可删除此步) -RUN apk add --no-cache libc6-compat - # 创建工作目录(统一存放二进制和配置文件) WORKDIR /app -# 1. 复制配置文件到 /app 目录(与二进制文件同目录) +# 设置时区为北京时间(东八区) +RUN apk add --no-cache tzdata && \ + cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo "Asia/Shanghai" > /etc/timezone + +# 复制配置文件到 /app 目录(与二进制文件同目录) # 本地 configs/ 目录下的所有文件,会直接复制到容器 /app/ 下 COPY configs/* /app/ -# 2. 复制Go二进制文件到 /app 目录 +# 复制Go二进制文件到 /app 目录 # 本地二进制文件路径替换为实际路径(如 ./s3-lessie-sourcing-api) COPY build/lessie-sourcing-api /app/ diff --git a/Dockerfile/python/apex/apex_dockerfile b/Dockerfile/python/apex/apex_dockerfile new file mode 100644 index 0000000..77220a7 --- /dev/null +++ b/Dockerfile/python/apex/apex_dockerfile @@ -0,0 +1,62 @@ +# =============================== +# 1) 构建依赖阶段 +# =============================== +FROM python:3.12.9-alpine AS build + +WORKDIR /app + +# 替换 Alpine 国内源 +RUN echo "https://mirrors.aliyun.com/alpine/v3.21/main/" > /etc/apk/repositories && \ + echo "https://mirrors.aliyun.com/alpine/v3.21/community/" >> /etc/apk/repositories + +# 构建依赖 +RUN apk add --no-cache \ + gcc \ + musl-dev \ + libffi-dev \ + openssl-dev \ + curl + +# 安装 uv +RUN pip install -i https://mirrors.aliyun.com/pypi/simple/ --no-cache-dir uv + +# uv 使用的 PyPI 镜像 +ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ + +# 拷贝依赖定义(用于缓存) +COPY uv.lock pyproject.toml ./ + +# 安装依赖到 .venv +RUN uv sync --no-dev + +# =============================== +# 2) 运行阶段 +# =============================== +FROM python:3.12.9-alpine AS runtime + +WORKDIR /app + +# 替换 Alpine 国内源 +RUN echo "https://mirrors.aliyun.com/alpine/v3.21/main/" > /etc/apk/repositories && \ + echo "https://mirrors.aliyun.com/alpine/v3.21/community/" >> /etc/apk/repositories + +# 时区 +RUN apk add --no-cache tzdata && \ + ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo "Asia/Shanghai" > /etc/timezone + +# 拷贝 uv、虚拟环境、Python 运行库 +COPY --from=build /usr/local/bin/uv /usr/local/bin/uv +COPY --from=build /app/.venv /app/.venv +COPY --from=build /usr/local/lib/python3.12 /usr/local/lib/python3.12 + +# 拷贝代码 +COPY . . + +ENV APP_ENV=dev \ + PYTHONUNBUFFERED=1 \ + PATH="/app/.venv/bin:$PATH" + +EXPOSE 8200 + +CMD ["uv", "run", "main.py", "--port", "8200"] diff --git a/Dockerfile/web/apex/aqex_web_Dockerfile b/Dockerfile/web/apex/aqex_web_Dockerfile new file mode 100644 index 0000000..4b7a661 --- /dev/null +++ b/Dockerfile/web/apex/aqex_web_Dockerfile @@ -0,0 +1,42 @@ +# =============================== +# 1) 依赖安装 + 构建阶段 +# =============================== +FROM registry.cn-hangzhou.aliyuncs.com/docker_mirror/node:20.15.0-alpine AS build + +WORKDIR /app + +# 只拷贝 package.json,加快缓存命中 +COPY package.json ./ + +# 预先安装依赖(利用缓存) +RUN pnpm install --registry=https://registry.npmmirror.com + +# 拷贝全部代码 +COPY . . + +# 要求 package.json 中的 script 形如:build:sit, build:test, build:prod +RUN pnpm build + +RUN mv /app/dist/main/index.html /app/dist/index.html + +# =============================== +# 2) 生产镜像阶段 +# =============================== +FROM registry.cn-hangzhou.aliyuncs.com/docker_mirror/nginx:1.25-alpine AS runtime + +# 清理默认 nginx 静态内容 +RUN rm -rf /usr/share/nginx/html/* + +# 拷贝自定义 nginx 配置文件 +COPY nginx.conf /etc/nginx/nginx.conf + +# 拷贝 build 结果 +COPY --from=build /app/dist /usr/share/nginx/html/ + +# 权限 +RUN chmod -R 755 /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] + diff --git a/Dockerfile/web/apex/nginx.conf b/Dockerfile/web/apex/nginx.conf new file mode 100644 index 0000000..3e3ba3a --- /dev/null +++ b/Dockerfile/web/apex/nginx.conf @@ -0,0 +1,65 @@ +# nginx.conf +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 日志格式 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + # 性能优化 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # 默认服务器配置 + server { + listen 80; + server_name localhost; + + # 网站根目录 + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # 静态资源缓存优化 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + # 错误页面 + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} \ No newline at end of file diff --git a/Dockerfile/web/aqex_web_Dockerfile b/Dockerfile/web/aqex_web_Dockerfile new file mode 100644 index 0000000..0cfa9b9 --- /dev/null +++ b/Dockerfile/web/aqex_web_Dockerfile @@ -0,0 +1,41 @@ +# =============================== +# 1) 依赖安装 + 构建阶段 +# =============================== +FROM node:20.15.0-alpine AS build + +WORKDIR /app + +RUN npm install -g pnpm + +# 只拷贝 package.json,加快缓存命中 +COPY package.json pnpm-lock.yaml* ./ + +# 预先安装依赖(利用缓存) +RUN pnpm install --registry=https://registry.npmmirror.com + +# 拷贝全部代码 +COPY . . + +# 要求 package.json 中的 script 形如:build:sit, build:test, build:prod +RUN pnpm build + +RUN mv /app/dist/main/index.html /app/dist/index.html + +# =============================== +# 2) 生产镜像阶段 +# =============================== +FROM nginx:1.25-alpine AS runtime + +# 清理默认 nginx 静态内容 +RUN rm -rf /usr/share/nginx/html/* + +# 拷贝 build 结果 +COPY --from=build /app/dist /usr/share/nginx/html/ + +# 权限 +RUN chmod -R 755 /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] + diff --git a/SCM/构建镜像/build_image_flymoon_admin.groovy b/SCM/构建镜像/v1/build_image_flymoon_admin.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_admin.groovy rename to SCM/构建镜像/v1/build_image_flymoon_admin.groovy diff --git a/SCM/构建镜像/build_image_flymoon_admin_web.groovy b/SCM/构建镜像/v1/build_image_flymoon_admin_web.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_admin_web.groovy rename to SCM/构建镜像/v1/build_image_flymoon_admin_web.groovy diff --git a/SCM/构建镜像/build_image_flymoon_agent.groovy b/SCM/构建镜像/v1/build_image_flymoon_agent.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_agent.groovy rename to SCM/构建镜像/v1/build_image_flymoon_agent.groovy diff --git a/SCM/构建镜像/build_image_flymoon_email.groovy b/SCM/构建镜像/v1/build_image_flymoon_email.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_email.groovy rename to SCM/构建镜像/v1/build_image_flymoon_email.groovy diff --git a/SCM/构建镜像/build_image_flymoon_payment.groovy b/SCM/构建镜像/v1/build_image_flymoon_payment.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_payment.groovy rename to SCM/构建镜像/v1/build_image_flymoon_payment.groovy diff --git a/SCM/构建镜像/build_image_lessie_agents.groovy b/SCM/构建镜像/v1/build_image_lessie_agents.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_agents.groovy rename to SCM/构建镜像/v1/build_image_lessie_agents.groovy diff --git a/SCM/构建镜像/build_image_lessie_ai_web.groovy b/SCM/构建镜像/v1/build_image_lessie_ai_web.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_ai_web.groovy rename to SCM/构建镜像/v1/build_image_lessie_ai_web.groovy diff --git a/SCM/构建镜像/build_image_lessie_go_api.groovy b/SCM/构建镜像/v1/build_image_lessie_go_api.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_go_api.groovy rename to SCM/构建镜像/v1/build_image_lessie_go_api.groovy diff --git a/SCM/构建镜像/build_image_lessie_official_web.groovy b/SCM/构建镜像/v1/build_image_lessie_official_web.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_official_web.groovy rename to SCM/构建镜像/v1/build_image_lessie_official_web.groovy diff --git a/SCM/构建镜像/v2/build_image_apex_v2.groovy b/SCM/构建镜像/v2/build_image_apex_v2.groovy new file mode 100644 index 0000000..ece36d5 --- /dev/null +++ b/SCM/构建镜像/v2/build_image_apex_v2.groovy @@ -0,0 +1,322 @@ +// --- 辅助函数:深拷贝对象以确保可序列化 --- +def deepCopyForSerialization(obj) { + if (obj instanceof Map) { + // 创建新的 LinkedHashMap,递归拷贝值 + return obj.collectEntries { k, v -> [(k): deepCopyForSerialization(v)] } + } else if (obj instanceof List) { + // 创建新的 ArrayList,递归拷贝元素 + return obj.collect { item -> deepCopyForSerialization(item) } + } else if (obj instanceof String || obj instanceof Number || obj instanceof Boolean || obj == null) { + return obj + } else { + return obj.toString() + } +} +// --- 结束辅助函数 --- + +pipeline { + agent any + + parameters { + gitParameter( + branchFilter: 'origin/(.*)', + defaultValue: 'dxin', + name: 'Code_branch', + type: 'PT_BRANCH_TAG', + selectedValue: 'DEFAULT', + sortMode: 'NONE', + description: '选择代码分支: ', + quickFilterEnabled: true, + tagFilter: '*', + listSize: "1" + ) + choice( + name: 'NAME_SPACES', + choices: ['sit', 'test', 'prod'], + description: '选择存放镜像的仓库命名空间:' + ) + string( + name: 'CUSTOM_TAG', + defaultValue: '', + description: '可选:自定义镜像 Tag (字母、数字、点、下划线、短横线), 如 v0.0.1, 留空则自动生成 “ v+构建次数_分支名_短哈希_构建时间 ”' + ) + } + environment { + REGISTRY = "uswccr.ccs.tencentyun.com" // 镜像仓库地址 + NAMESPACE = "lessie${params.NAME_SPACES}" // 命名空间根据choices的选择拼接 + IMAGE_NAME = "apex" // 镜像名(固定前缀) + CREDENTIALS_ID = "dxin_img_hub_auth" // 容器仓库凭证ID + } + + stages { + stage('拉取代码') { + steps { + git branch: "${params.Code_branch}", + credentialsId: 'fly_gitlab_auth', + url: 'http://172.24.16.20/root/apex.git' + } + } + + stage('获取信息') { + steps { + script { + // 获取分支名 + env.Code_branch = "${params.Code_branch}" + // 获取最近一次提交的哈希值(短格式,前8位) + env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD',returnStdout: true).trim() + // 获取最近一次提交的哈希值(全格式) + env.GIT_COMMIT_LONG = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() + // 获取最近一次提交的作者 + env.GIT_AUTHOR = sh(script: 'git log -1 --pretty=format:%an',returnStdout: true).trim() + // 获取最近一次提交的时间(格式化) + env.GIT_COMMIT_TIME = sh( + script: 'git log -1 --pretty=format:%ct | xargs -I {} date -d @{} +%Y%m%d-%H%M%S', + returnStdout: true + ).trim() + // 获取最近一次提交的备注信息(转义特殊字符,避免构建失败) + env.GIT_COMMIT_MSG = sh(script: 'git log -1 --pretty=format:%s | sed -e \'s/"/\\"/g\'', returnStdout: true).trim() + + // Jenkins构建次数 + def buildNumber = env.BUILD_NUMBER // Jenkins内置变量,直接获取当前Job的构建序号 + // 当前分支名(处理/为-,如feature/docker_1015 → feature-docker_1015) + def branchName = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim() + def formattedBranch = branchName.replace('/', '-').replace('_', '-') // 替换分支名中的/和_为- + // 构建时间(格式:202510181215,年-月-日-时-分,无分隔符) + def buildTime = sh(script: 'date +%Y%m%d%H%M', returnStdout: true).trim() + def defaultTag = "v${buildNumber}_${formattedBranch}_${GIT_COMMIT_SHORT}_${buildTime}" + + def customTag = params.CUSTOM_TAG?.trim() + def tagPattern = ~/^[a-zA-Z0-9._-]+$/ + + // 判断最终Tag + if (customTag && customTag ==~ tagPattern) { + echo "✅ 使用自定义镜像 Tag: ${customTag}" + env.IMAGE_TAG = customTag + } else if (customTag) { + echo "⚠️ 自定义 Tag '${customTag}' 不符合规范,将使用默认生成的 Tag: ${defaultTag}" + + def confirmed = true + timeout(time: 1, unit: 'MINUTES') { + try { + input( + message: """⚠️ Tag 命名不规范: + ${customTag} + + 将使用自动生成的 Tag: + ${defaultTag} + + 是否继续构建?""", + ok: '确认' + ) + } catch (err) { + // 用户点击“取消”或中断 + echo "🚫 用户取消构建" + confirmed = false + } + } + + if (confirmed) { + echo "✅ 用户确认使用自动生成的 Tag:${defaultTag}" + env.IMAGE_TAG = defaultTag + } else { + error("流水线已终止。") + } + } else { + env.IMAGE_TAG = defaultTag + echo "未输入自定义 Tag, 使用自动生成规则: ${env.IMAGE_TAG}" + } + } + } + } + + stage('登录仓库') { + steps { + withCredentials([usernamePassword( + credentialsId: env.CREDENTIALS_ID, + usernameVariable: 'REGISTRY_USER', + passwordVariable: 'REGISTRY_PWD' + )]) { + sh ''' + echo "$REGISTRY_PWD" | docker login ${REGISTRY} -u ${REGISTRY_USER} --password-stdin + ''' + } + } + } + + stage('构建镜像') { + steps { + script { + // 构建镜像,添加标签信息 + sh """ + docker build -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG} \ + --label "git-branch='${Code_branch}'" \ + --label "git-commit='${GIT_COMMIT_SHORT}'" \ + --label "git-author='${GIT_AUTHOR}'" \ + --label "git-message='${GIT_COMMIT_MSG}'" \ + --label "build-time='${GIT_COMMIT_TIME}'" \ + . + """ + } + } + } + + stage('推送仓库') { + steps { + script { + sh "docker push ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + echo "推送镜像成功:${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + } + } + } + } + + post { + always { + script { + def keepCount = 3 + echo "开始清理本地旧镜像,仅保留最近 ${keepCount} 个构建版本" + def imagePrefix = "${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}" + + // 获取所有镜像(按创建时间排序,越新的越前) + // 格式:Repository:Tag ImageID CreatedAt + def allImagesRaw = sh(script: "docker images ${imagePrefix} --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.CreatedAt}}' | sort -rk3", returnStdout: true).trim() + if (!allImagesRaw) { + echo "未找到任何镜像,无需清理" + return + } + + def allImages = allImagesRaw.split('\n') + if (allImages.size() <= keepCount) { + echo "当前镜像数未超过 ${keepCount} 个,无需清理" + return + } + + def oldImages = allImages.drop(keepCount) + echo "发现 ${oldImages.size()} 个旧镜像需要清理" + oldImages.each { line -> + echo " ${line}" + } + + oldImages.each { line -> + def parts = line.split(' ') + def imageTag = parts[0] + def imageId = parts.size() > 1 ? parts[1] : "" + + // 对于标签为的无效镜像,使用镜像ID删除 + if (imageTag.contains("") && imageId) { + echo "删除无效镜像: ${imageId}" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else if (imageId) { + // 对于有标签的有效镜像,优先使用镜像ID删除 + echo "删除旧镜像: ${imageTag} (${imageId})" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else { + // 兜底方案,使用标签删除 + echo "删除旧镜像: ${imageTag}" + sh(returnStatus: true, script: "docker rmi -f ${imageTag} || true") + } + } + + echo "清理完成,当前镜像状态:" + sh """ + docker images ${imagePrefix} --format 'table {{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}\\t{{.Size}}' + """ + + sh "docker logout ${REGISTRY}" + echo "容器仓库已登出,本地凭证已清理" + } + } + success { + script { + // 1. 准备元数据 (转换所有环境变量为 String) + def metadataDir = '/var/lib/jenkins/metadata' + def metadataFileRelativePath = "${env.NAMESPACE}-${env.IMAGE_NAME}.json" // 相对于 metadataDir 的文件名 + def fullMetadataPath = "${metadataDir}/${metadataFileRelativePath}" + + // --- 转换为 String --- + def registry = env.REGISTRY as String + def namespace = env.NAMESPACE as String + def imageName = env.IMAGE_NAME as String + def imageTag = env.IMAGE_TAG as String + def codeBranch = params.Code_branch as String // 使用 params,因为 Code_branch 是参数 + def gitCommit = env.GIT_COMMIT_LONG as String + def gitAuthor = env.GIT_AUTHOR as String + def gitCommitMsg = env.GIT_COMMIT_MSG as String + def gitCommitTime = env.GIT_COMMIT_TIME as String + def buildNumber = env.BUILD_NUMBER as String + // --- 转换为 String --- + + // 2. 准备新数据 + def newImageData = [ + image_tag: imageTag, // 使用转换后的变量 + full_image_name: "${registry}/${namespace}/${imageName}:${imageTag}", // 使用转换后的变量 + labels: [ + "git-branch": codeBranch, + "git-commit": gitCommit, + "git-author": gitAuthor, + "git-message": gitCommitMsg, + "build-time": gitCommitTime + ], + build_job_number: buildNumber, + build_time: new Date().format('yyyy-MM-dd HH:mm:ss') // Jenkins 构建完成时间 + ] + + // 2. 读取现有数据(如果文件存在) + def existingDataList = [] + try { + // 使用 readJSON 步骤读取文件内容 (readJSON 会自动处理 LazyMap 问题) + def rawExistingData = readJSON file: fullMetadataPath, default: [] // 如果文件不存在,则返回空列表 [] + + // --- ✅ 修复:深拷贝 rawExistingData (修正内联代码) --- + if (rawExistingData instanceof List) { + existingDataList = rawExistingData.collect { item -> + if (item instanceof Map) { + // 递归深拷贝 Map (使用辅助函数) + return deepCopyForSerialization(item) + } else { + return item + } + } + } else { + echo "警告: 元数据文件 ${fullMetadataPath} 格式不正确(非 List 类型),将被覆盖。" + existingDataList = [] + } + // --- 结束修复 --- + + } catch (Exception e) { + // readJSON 在文件不存在时通常会返回 default 值,但如果文件存在但格式错误,会抛出异常 + echo "警告: 读取元数据文件 ${fullMetadataPath} 失败或格式错误: ${e.getMessage()},将被覆盖。" + // 确保目录存在 + sh "mkdir -p ${metadataDir}" + existingDataList = [] // 重置为新列表 + } + + // 3. 将新数据添加到列表开头(最新的在前) + existingDataList.add(0, newImageData) + + // 4. 限制列表大小为 20 + if (existingDataList.size() > 20) { + existingDataList = existingDataList.take(20) + } + + // 5. 使用 writeJSON 步骤写入文件 (writeJSON 会自动处理 Map 的序列化) + writeJSON file: fullMetadataPath, json: existingDataList, pretty: 2 // pretty: 2 表示格式化 JSON (2 个空格缩进) + + echo "镜像元数据已存储到: ${fullMetadataPath}" + + // 输出构建结果 + echo """ + 镜像地址:${registry}/${namespace}/${imageName}:${imageTag} + 对应代码提交哈希:${gitCommit} + 对应代码分支:${codeBranch} + 代码提交者:${gitAuthor} + 提交备注:${gitCommitMsg} + """.stripIndent() + } + } + failure { + // 输出构建结果 + echo "部署有错误,请检查!" + } + } +} \ No newline at end of file diff --git a/SCM/构建镜像/v2/build_image_apex_web_v2.groovy b/SCM/构建镜像/v2/build_image_apex_web_v2.groovy new file mode 100644 index 0000000..2e67775 --- /dev/null +++ b/SCM/构建镜像/v2/build_image_apex_web_v2.groovy @@ -0,0 +1,322 @@ +// --- 辅助函数:深拷贝对象以确保可序列化 --- +def deepCopyForSerialization(obj) { + if (obj instanceof Map) { + // 创建新的 LinkedHashMap,递归拷贝值 + return obj.collectEntries { k, v -> [(k): deepCopyForSerialization(v)] } + } else if (obj instanceof List) { + // 创建新的 ArrayList,递归拷贝元素 + return obj.collect { item -> deepCopyForSerialization(item) } + } else if (obj instanceof String || obj instanceof Number || obj instanceof Boolean || obj == null) { + return obj + } else { + return obj.toString() + } +} +// --- 结束辅助函数 --- + +pipeline { + agent any + + parameters { + gitParameter( + branchFilter: 'origin/(.*)', + defaultValue: 'feat-tag', + name: 'Code_branch', + type: 'PT_BRANCH_TAG', + selectedValue: 'DEFAULT', + sortMode: 'NONE', + description: '选择代码分支: ', + quickFilterEnabled: true, + tagFilter: '*', + listSize: "1" + ) + choice( + name: 'NAME_SPACES', + choices: ['sit', 'test', 'prod'], + description: '选择存放镜像的仓库命名空间:' + ) + string( + name: 'CUSTOM_TAG', + defaultValue: '', + description: '可选:自定义镜像 Tag (字母、数字、点、下划线、短横线), 如 v0.0.1, 留空则自动生成 “ v+构建次数_分支名_短哈希_构建时间 ”' + ) + } + environment { + REGISTRY = "uswccr.ccs.tencentyun.com" // 镜像仓库地址 + NAMESPACE = "lessie${params.NAME_SPACES}" // 命名空间根据choices的选择拼接 + IMAGE_NAME = "apex-web" // 镜像名(固定前缀) + CREDENTIALS_ID = "dxin_img_hub_auth" // 容器仓库凭证ID + } + + stages { + stage('拉取代码') { + steps { + git branch: "${params.Code_branch}", + credentialsId: 'fly_gitlab_auth', + url: 'http://172.24.16.20/web/apex-platform-fe.git' + } + } + + stage('获取信息') { + steps { + script { + // 获取分支名 + env.Code_branch = "${params.Code_branch}" + // 获取最近一次提交的哈希值(短格式,前8位) + env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD',returnStdout: true).trim() + // 获取最近一次提交的哈希值(全格式) + env.GIT_COMMIT_LONG = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() + // 获取最近一次提交的作者 + env.GIT_AUTHOR = sh(script: 'git log -1 --pretty=format:%an',returnStdout: true).trim() + // 获取最近一次提交的时间(格式化) + env.GIT_COMMIT_TIME = sh( + script: 'git log -1 --pretty=format:%ct | xargs -I {} date -d @{} +%Y%m%d-%H%M%S', + returnStdout: true + ).trim() + // 获取最近一次提交的备注信息(转义特殊字符,避免构建失败) + env.GIT_COMMIT_MSG = sh(script: 'git log -1 --pretty=format:%s | sed -e \'s/"/\\"/g\'', returnStdout: true).trim() + + // Jenkins构建次数 + def buildNumber = env.BUILD_NUMBER // Jenkins内置变量,直接获取当前Job的构建序号 + // 当前分支名(处理/为-,如feature/docker_1015 → feature-docker_1015) + def branchName = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim() + def formattedBranch = branchName.replace('/', '-').replace('_', '-') // 替换分支名中的/和_为- + // 构建时间(格式:202510181215,年-月-日-时-分,无分隔符) + def buildTime = sh(script: 'date +%Y%m%d%H%M', returnStdout: true).trim() + def defaultTag = "v${buildNumber}_${formattedBranch}_${GIT_COMMIT_SHORT}_${buildTime}" + + def customTag = params.CUSTOM_TAG?.trim() + def tagPattern = ~/^[a-zA-Z0-9._-]+$/ + + // 判断最终Tag + if (customTag && customTag ==~ tagPattern) { + echo "✅ 使用自定义镜像 Tag: ${customTag}" + env.IMAGE_TAG = customTag + } else if (customTag) { + echo "⚠️ 自定义 Tag '${customTag}' 不符合规范,将使用默认生成的 Tag: ${defaultTag}" + + def confirmed = true + timeout(time: 1, unit: 'MINUTES') { + try { + input( + message: """⚠️ Tag 命名不规范: + ${customTag} + + 将使用自动生成的 Tag: + ${defaultTag} + + 是否继续构建?""", + ok: '确认' + ) + } catch (err) { + // 用户点击“取消”或中断 + echo "🚫 用户取消构建" + confirmed = false + } + } + + if (confirmed) { + echo "✅ 用户确认使用自动生成的 Tag:${defaultTag}" + env.IMAGE_TAG = defaultTag + } else { + error("流水线已终止。") + } + } else { + env.IMAGE_TAG = defaultTag + echo "未输入自定义 Tag, 使用自动生成规则: ${env.IMAGE_TAG}" + } + } + } + } + + stage('登录仓库') { + steps { + withCredentials([usernamePassword( + credentialsId: env.CREDENTIALS_ID, + usernameVariable: 'REGISTRY_USER', + passwordVariable: 'REGISTRY_PWD' + )]) { + sh ''' + echo "$REGISTRY_PWD" | docker login ${REGISTRY} -u ${REGISTRY_USER} --password-stdin + ''' + } + } + } + + stage('构建镜像') { + steps { + script { + // 构建镜像,添加标签信息 + sh """ + docker build -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG} \ + --label "git-branch='${Code_branch}'" \ + --label "git-commit='${GIT_COMMIT_SHORT}'" \ + --label "git-author='${GIT_AUTHOR}'" \ + --label "git-message='${GIT_COMMIT_MSG}'" \ + --label "build-time='${GIT_COMMIT_TIME}'" \ + . + """ + } + } + } + + stage('推送仓库') { + steps { + script { + sh "docker push ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + echo "推送镜像成功:${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + } + } + } + } + + post { + always { + script { + def keepCount = 3 + echo "开始清理本地旧镜像,仅保留最近 ${keepCount} 个构建版本" + def imagePrefix = "${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}" + + // 获取所有镜像(按创建时间排序,越新的越前) + // 格式:Repository:Tag ImageID CreatedAt + def allImagesRaw = sh(script: "docker images ${imagePrefix} --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.CreatedAt}}' | sort -rk3", returnStdout: true).trim() + if (!allImagesRaw) { + echo "未找到任何镜像,无需清理" + return + } + + def allImages = allImagesRaw.split('\n') + if (allImages.size() <= keepCount) { + echo "当前镜像数未超过 ${keepCount} 个,无需清理" + return + } + + def oldImages = allImages.drop(keepCount) + echo "发现 ${oldImages.size()} 个旧镜像需要清理" + oldImages.each { line -> + echo " ${line}" + } + + oldImages.each { line -> + def parts = line.split(' ') + def imageTag = parts[0] + def imageId = parts.size() > 1 ? parts[1] : "" + + // 对于标签为的无效镜像,使用镜像ID删除 + if (imageTag.contains("") && imageId) { + echo "删除无效镜像: ${imageId}" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else if (imageId) { + // 对于有标签的有效镜像,优先使用镜像ID删除 + echo "删除旧镜像: ${imageTag} (${imageId})" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else { + // 兜底方案,使用标签删除 + echo "删除旧镜像: ${imageTag}" + sh(returnStatus: true, script: "docker rmi -f ${imageTag} || true") + } + } + + echo "清理完成,当前镜像状态:" + sh """ + docker images ${imagePrefix} --format 'table {{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}\\t{{.Size}}' + """ + + sh "docker logout ${REGISTRY}" + echo "容器仓库已登出,本地凭证已清理" + } + } + success { + script { + // 1. 准备元数据 (转换所有环境变量为 String) + def metadataDir = '/var/lib/jenkins/metadata' + def metadataFileRelativePath = "${env.NAMESPACE}-${env.IMAGE_NAME}.json" // 相对于 metadataDir 的文件名 + def fullMetadataPath = "${metadataDir}/${metadataFileRelativePath}" + + // --- 转换为 String --- + def registry = env.REGISTRY as String + def namespace = env.NAMESPACE as String + def imageName = env.IMAGE_NAME as String + def imageTag = env.IMAGE_TAG as String + def codeBranch = params.Code_branch as String // 使用 params,因为 Code_branch 是参数 + def gitCommit = env.GIT_COMMIT_LONG as String + def gitAuthor = env.GIT_AUTHOR as String + def gitCommitMsg = env.GIT_COMMIT_MSG as String + def gitCommitTime = env.GIT_COMMIT_TIME as String + def buildNumber = env.BUILD_NUMBER as String + // --- 转换为 String --- + + // 2. 准备新数据 + def newImageData = [ + image_tag: imageTag, // 使用转换后的变量 + full_image_name: "${registry}/${namespace}/${imageName}:${imageTag}", // 使用转换后的变量 + labels: [ + "git-branch": codeBranch, + "git-commit": gitCommit, + "git-author": gitAuthor, + "git-message": gitCommitMsg, + "build-time": gitCommitTime + ], + build_job_number: buildNumber, + build_time: new Date().format('yyyy-MM-dd HH:mm:ss') // Jenkins 构建完成时间 + ] + + // 2. 读取现有数据(如果文件存在) + def existingDataList = [] + try { + // 使用 readJSON 步骤读取文件内容 (readJSON 会自动处理 LazyMap 问题) + def rawExistingData = readJSON file: fullMetadataPath, default: [] // 如果文件不存在,则返回空列表 [] + + // --- ✅ 修复:深拷贝 rawExistingData (修正内联代码) --- + if (rawExistingData instanceof List) { + existingDataList = rawExistingData.collect { item -> + if (item instanceof Map) { + // 递归深拷贝 Map (使用辅助函数) + return deepCopyForSerialization(item) + } else { + return item + } + } + } else { + echo "警告: 元数据文件 ${fullMetadataPath} 格式不正确(非 List 类型),将被覆盖。" + existingDataList = [] + } + // --- 结束修复 --- + + } catch (Exception e) { + // readJSON 在文件不存在时通常会返回 default 值,但如果文件存在但格式错误,会抛出异常 + echo "警告: 读取元数据文件 ${fullMetadataPath} 失败或格式错误: ${e.getMessage()},将被覆盖。" + // 确保目录存在 + sh "mkdir -p ${metadataDir}" + existingDataList = [] // 重置为新列表 + } + + // 3. 将新数据添加到列表开头(最新的在前) + existingDataList.add(0, newImageData) + + // 4. 限制列表大小为 20 + if (existingDataList.size() > 20) { + existingDataList = existingDataList.take(20) + } + + // 5. 使用 writeJSON 步骤写入文件 (writeJSON 会自动处理 Map 的序列化) + writeJSON file: fullMetadataPath, json: existingDataList, pretty: 2 // pretty: 2 表示格式化 JSON (2 个空格缩进) + + echo "镜像元数据已存储到: ${fullMetadataPath}" + + // 输出构建结果 + echo """ + 镜像地址:${registry}/${namespace}/${imageName}:${imageTag} + 对应代码提交哈希:${gitCommit} + 对应代码分支:${codeBranch} + 代码提交者:${gitAuthor} + 提交备注:${gitCommitMsg} + """.stripIndent() + } + } + failure { + // 输出构建结果 + echo "部署有错误,请检查!" + } + } +} \ No newline at end of file diff --git a/SCM/构建镜像/build_image_flymoon_admin_v2.groovy b/SCM/构建镜像/v2/build_image_flymoon_admin_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_admin_v2.groovy rename to SCM/构建镜像/v2/build_image_flymoon_admin_v2.groovy diff --git a/SCM/构建镜像/build_image_flymoon_admin_web_v2.groovy b/SCM/构建镜像/v2/build_image_flymoon_admin_web_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_admin_web_v2.groovy rename to SCM/构建镜像/v2/build_image_flymoon_admin_web_v2.groovy diff --git a/SCM/构建镜像/build_image_flymoon_agent_v2.groovy b/SCM/构建镜像/v2/build_image_flymoon_agent_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_agent_v2.groovy rename to SCM/构建镜像/v2/build_image_flymoon_agent_v2.groovy diff --git a/SCM/构建镜像/build_image_flymoon_email_v2.groovy b/SCM/构建镜像/v2/build_image_flymoon_email_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_email_v2.groovy rename to SCM/构建镜像/v2/build_image_flymoon_email_v2.groovy diff --git a/SCM/构建镜像/build_image_flymoon_payment_v2.groovy b/SCM/构建镜像/v2/build_image_flymoon_payment_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_flymoon_payment_v2.groovy rename to SCM/构建镜像/v2/build_image_flymoon_payment_v2.groovy diff --git a/SCM/构建镜像/build_image_lessie_agents_v2.groovy b/SCM/构建镜像/v2/build_image_lessie_agents_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_agents_v2.groovy rename to SCM/构建镜像/v2/build_image_lessie_agents_v2.groovy diff --git a/SCM/构建镜像/build_image_lessie_ai_web_v2.groovy b/SCM/构建镜像/v2/build_image_lessie_ai_web_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_ai_web_v2.groovy rename to SCM/构建镜像/v2/build_image_lessie_ai_web_v2.groovy diff --git a/SCM/构建镜像/build_image_lessie_go_api_v2.groovy b/SCM/构建镜像/v2/build_image_lessie_go_api_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_go_api_v2.groovy rename to SCM/构建镜像/v2/build_image_lessie_go_api_v2.groovy diff --git a/SCM/构建镜像/build_image_lessie_official_web_v2.groovy b/SCM/构建镜像/v2/build_image_lessie_official_web_v2.groovy similarity index 100% rename from SCM/构建镜像/build_image_lessie_official_web_v2.groovy rename to SCM/构建镜像/v2/build_image_lessie_official_web_v2.groovy diff --git a/k8s_yaml/ELK/filebast/02-filebeat-configmap.yaml b/k8s_yaml/ELK/filebast/02-filebeat-configmap.yaml index 8a8f0fa..7943fa3 100644 --- a/k8s_yaml/ELK/filebast/02-filebeat-configmap.yaml +++ b/k8s_yaml/ELK/filebast/02-filebeat-configmap.yaml @@ -84,7 +84,7 @@ data: - /var/log/containers/*-${data.kubernetes.container.id}.log fields: application: ${data.kubernetes.labels.app} - log_type: "go.log" # 显式设置 log_type 字段,用于后续 processors 中的 'when' 条件 + log_type: "go.log" environment: ${data.kubernetes.labels.environment} instance: ${data.kubernetes.host} diff --git a/k8s_yaml/ELK/filebast/022-filebeat-configmap.yaml b/k8s_yaml/ELK/filebast/022-filebeat-configmap.yaml index 404a8b9..430cf76 100644 --- a/k8s_yaml/ELK/filebast/022-filebeat-configmap.yaml +++ b/k8s_yaml/ELK/filebast/022-filebeat-configmap.yaml @@ -10,29 +10,48 @@ data: filebeat.autodiscover: providers: - # 配置 Provider - type: kubernetes - node: ${NODE_NAME} - hints.enabled: false - templates: - # ---------- go语言的中转服务的Pod, go项目json格式日志 ---------- + # ---------- ↓ go语言的中转服务的Pod, go项目json格式日志 ↓ ---------- - condition: - equals: - kubernetes.namespace: kube-system + and: + - equals: + kubernetes.namespace: sit + - equals: + kubernetes.labels.app: "lessie-go-api" config: - type: filestream - id: "k8s-go-json-log-${data.kubernetes.container.id}" + id: "container-${data.kubernetes.container.id}" prospector.scanner.symlinks: true + close.on_state_change.removed: false parsers: - container: ~ paths: - /var/log/containers/*-${data.kubernetes.container.id}.log - fields: - application: ${data.kubernetes.labels.app} - log_type: "goho.log" - environment: ${data.kubernetes.labels.environment} - instance: ${data.kubernetes.host} + + processors: + - add_kubernetes_metadata: + host: ${NODE_NAME} + include_fields: + - "kubernetes.node.hostname" + - "kubernetes.container.name" + - "kubernetes.pod.name" + - "kubernetes.pod.ip" + - "kubernetes.namespace" + - "kubernetes.labels.app" + - "kubernetes.labels.environment" + - "kubernetes.labels.project" + + - decode_json_fields: + fields: ["message"] + target: "mylog" + overwrite_keys: true + add_error_key: true + + - drop_fields: + fields: + - "" + # ---- 输出到 Elasticsearch ---- @@ -41,6 +60,7 @@ data: username: "admin" password: "G7ZSKFM4AQwHQpwA" index: "k8s-%{[kubernetes.labels.environment]}-%{[kubernetes.labels.app]}-%{+yyyy.MM.dd}" + # index: "k8s-%{[kubernetes.labels.app]}-%{+yyyy.MM.dd}" logging.level: debug - logging.selectors: ["*"] + logging.selectors: ["*"] \ No newline at end of file diff --git a/k8s_yaml/ELK/filebast/filebeat.yaml b/k8s_yaml/ELK/filebast/filebeat.yaml new file mode 100644 index 0000000..4cfcf3d --- /dev/null +++ b/k8s_yaml/ELK/filebast/filebeat.yaml @@ -0,0 +1,38 @@ +setup.ilm.enabled: false +setup.template.enabled: false + +filebeat.autodiscover: + providers: + - type: kubernetes + node: ${NODE_NAME} + # hints.enabled: false + templates: + # ---------- go语言的中转服务的Pod, go项目json格式日志 ---------- + - condition: + equals: + kubernetes.labels.app: lessie-go-api + config: + - type: filestream + id: "k8s-go-json-log-${data.kubernetes.container.id}" + prospector.scanner.symlinks: true + close.on_state_change.removed: false + parsers: + - container: ~ + paths: + - /var/log/containers/*-${data.kubernetes.container.id}.log + fields: + application: ${data.kubernetes.labels.app} + log_type: "goho.log" + environment: ${data.kubernetes.labels.environment} + instance: ${data.kubernetes.host} + + +# ---- 输出到 Elasticsearch ---- +output.elasticsearch: + hosts: ["http://10.0.0.38:9200"] + username: "admin" + password: "G7ZSKFM4AQwHQpwA" + index: "k8s-%{[kubernetes.labels.environment]}-%{[kubernetes.labels.app]}-%{+yyyy.MM.dd}" + +logging.level: debug +logging.selectors: ["autodiscover", "input"] \ No newline at end of file