中小型企业通用k8s发版Pipline模板,开箱即用
做云原生开发运维的小伙伴,几乎每天都在做一件事:服务打包、镜像推送、K8s 上线发版。
很多团队的现状都是:每个服务一套 Jenkins/GitLab CI 流水线、脚本杂乱、步骤不统一、回滚麻烦、新人上手成本极高。
其实绝大多数 Java/Go/Node 微服务的 K8s 发版流程完全可以标准化。
今天给大家分享一套企业级通用 K8s 发版 Pipeline 模板,适配绝大多数业务场景:普通微服务、无状态服务、后端接口服务均可直接套用,支持自动构建、镜像推送、K8s 滚动更新、版本记录、失败回滚。
全程无特殊定制,开箱即用,复制改参数就能直接上线。
一、模版适用场景
这套pipline是通用标准版,不绑定特殊业务、但是需要适配一些中间件比如我们的配置中心可以有主流nacos来代替,适合大多数企业微服务:
①java服务:(基于阿里云云效进行CICD)且环境分为 dev/test/pre/
pipeline {
agent any
tools {
jdk 'jdk17'
maven 'maven3.8.8'
}
environment {
BUILDVERSION = sh(script: "date +%Y%m%d_%H%M", returnStdout: true).trim()
HARBOR_USER = 'userpush'
HARBOR_PASSWD = 'xxxxxxx' #harbor仓库密码
}
stages {
stage('Init Build Workspace') {
steps {
script {
env.BUILD_WORKSPACE = "${env.WORKSPACE}/${env.BUILD_NUMBER}"
echo "当前构建目录: ${env.BUILD_WORKSPACE}"
}
}
}
stage('Clear workspace') {
steps {
deleteDir()
}
}
stage('Parse Parameters') {
steps {
ws("${env.BUILD_WORKSPACE}") {
script {
def paramRegex = /^(\w+:[^;]+;)+$/
if (!params.APP_CONFIG || !params.APP_CONFIG.matches(paramRegex)) {
error("Invalid APP_CONFIG format. Expected format: 'key1:value1;key2:value2;'")
}
def configMap = [:]
params.APP_CONFIG.split(';').each { entry ->
def pair = entry.split(':', 2)
if (pair.size() == 2) {
configMap[pair[0].trim()] = pair[1].trim()
}
}
env.APP_NAME = configMap['app_name']?: 'default-app'
env.GIT_VER = configMap['git_ver']?: 'master_jdk17'
env.GIT_GROUP = configMap['git_group']?: 'default-group'
env.APP_REPLICAS = configMap['app_replicas']?: '1'
env.MEM = configMap['mem']?: '2G'
env.MAVEN_API = configMap['maven_api']?: 'false'
env.DEPLOY_ENV = configMap['deploy_env']?: 'dev'
}
}
}
}
stage('Check out project branches') {
steps {
ws("${env.BUILD_WORKSPACE}") {
script {
checkout([
$class: 'GitSCM',
branches: [[name: "*/${GIT_VER}"]],
userRemoteConfigs: [[
url: "https://xxxxxx.cn/${GIT_GROUP}/${APP_NAME}.git",
credentialsId: '323a0e5a-497a-4816-b344-12dba8s56saf5'
]],
extensions: [[$class: 'CleanBeforeCheckout']]
])
}
}
}
}
stage('maven api') {
steps {
ws("${env.BUILD_WORKSPACE}") {
script {
if (env.MAVEN_API == 'true') {
sh '''
api_dir=$(find . -maxdepth 1 -type d -name "*-api" | head -n 1)
cd $api_dir
mvn clean deploy -Dmaven.test.skip=true
'''
} else {
echo "Skipping maven API deployment because MAVEN_API is set to false"
}
}
}
}
}
stage('maven') {
steps {
ws("${env.BUILD_WORKSPACE}") {
sh '''
java --version
echo $JAVA_HOME
main_dir=$(find . -maxdepth 1 -type d -name "*-web" | head -n 1)
if [ -z "$main_dir" ]; then
echo "Error: No web module directory found."
exit 1
fi
rm -f $main_dir/src/main/resources/bootstrap.yml
rm -f $main_dir/src/main/resources/application.yml
mvn clean package -Dmaven.test.skip=true -P${DEPLOY_ENV}
'''
}
}
}
stage('Build Docker Image') {
steps {
ws("${env.BUILD_WORKSPACE}") {
sh '''
main_dir=$(find . -maxdepth 1 -type d -name "*-web" | head -n 1)
cd ${main_dir}/target
wget http://dockerfile.xxxx.com/download/a${DEPLOY_ENV}/public/Dockerfile
sed -i "s?app-name?${APP_NAME}?g" ./Dockerfile
if [ "${MEM}" != "" ]; then
sed -i "s?2g?${MEM}?g" ./Dockerfile
fi
docker login http://harbor.xxxx.com/ -u ${HARBOR_USER} -p ${HARBOR_PASSWD}
export DOCKER_BUILDKIT=0
docker build --no-cache --pull -t harbor.clx.com/${DEPLOY_ENV}/${APP_NAME}-jdk17:${DEPLOY_ENV}_${BUILDVERSION} .
docker push harbor.xxxx.com/${DEPLOY_ENV}/${APP_NAME}-jdk17:${DEPLOY_ENV}_${BUILDVERSION}
'''
}
}
}
stage('Deploy to K8S') {
steps {
ws("${env.BUILD_WORKSPACE}") {
sh """
wget http://dockerfile.xxxx.com/yaml/a${DEPLOY_ENV}/public/ackdeployment.yaml -O ./ackdeployment${APP_NAME}.yaml
sed -i 's?BUILD_TAG?${DEPLOY_ENV}_${BUILDVERSION}?' ./ackdeployment${APP_NAME}.yaml
sed -i 's?app-name?${APP_NAME}?' ./ackdeployment${APP_NAME}.yaml
sed -i 's?default?${DEPLOY_ENV}?' ./ackdeployment${APP_NAME}.yaml
cat ./ackdeployment${APP_NAME}.yaml
kubectl apply -f ./ackdeployment${APP_NAME}.yaml
rm -rf ./ackdeployment${APP_NAME}.yaml
"""
}
}
}
}
}
这里我弄了一个nginx 作为本地拉取公司内部文件地址下载通用模板这个过程



我的Dockerfile和yaml如下
详细请参考我之前的定制化Dockerfile介绍
运维小邵,公众号:运维小邵微服务部署极简实战-企业配置定制化Dockerfile
[root@one-tech-prod-jump-06-240 public]# cat Dockerfile
FROM harbor.xxxx.com/public/java-jdk-17-0-6:v1.2.0
ENV NAME="app-name"
ENV JAVA_OPTS="\
-Xms2g -Xmx2g -Xmn1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0 \
--add-opens java.base/java.lang=ALL-UNNAMED \
"
# 配置nacos地址
ENV NACOS_ADDR="nacos-0.nacos-headless.pre.svc.cluster.local:8848 nacos-1.nacos-headless.pre.svc.cluster.local:8848 nacos-2.nacos-headless.pre.svc.cluster.local:8848"
ENV ACTIVE="dev" # 根据环境去配置
ENV ENV_ARGS="--server.port=80 \
--server.servlet.context-path=/${NAME} \
--spring.main.allow-bean-definition-overriding=true \
--spring.profiles.active=${ACTIVE} \
--spring.application.name=${NAME} \
--spring.cloud.nacos.discovery.server-addr=${NACOS_ADDR} \
--spring.cloud.nacos.discovery.username=nacos \
--spring.cloud.nacos.discovery.password=&umS74RkrTnsAhnD \
--spring.cloud.nacos.config.server-addr=${NACOS_ADDR} \
--spring.cloud.nacos.config.username=nacos \
--spring.cloud.nacos.config.password=&umS74RkrTnsAhnD \
--spring.cloud.nacos.config.file-extension=yml \
--spring.main.allow-circular-references=true \
--spring.cloud.refresh.never-refreshable=com.zaxxer.hikari.HikariDataSource,com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceWrapper \
--spring.cloud.nacos.config.shared-configs[0].dataId=common-redis-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[0].refresh=true \
--spring.cloud.nacos.config.shared-configs[1].dataId=common-mq-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[1].refresh=true \
--spring.cloud.nacos.config.shared-configs[2].dataId=common-satoken-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[2].refresh=true \
--spring.cloud.nacos.config.shared-configs[3].dataId=common-druid-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[3].refresh=true \
--spring.cloud.nacos.config.shared-configs[4].dataId=common-swagger-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[4].refresh=true \
--spring.cloud.nacos.config.shared-configs[5].dataId=common-easy-es-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[5].refresh=true \
--spring.cloud.nacos.config.shared-configs[6].dataId=common-esign-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[6].refresh=true \
--spring.cloud.nacos.config.shared-configs[7].dataId=common-xxl-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[7].refresh=true \
--spring.cloud.nacos.config.shared-configs[8].dataId=common-mongo-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[8].refresh=true \
--spring.cloud.nacos.config.shared-configs[9].dataId=common-snail-job-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[9].refresh=true \
--spring.cloud.nacos.config.shared-configs[10].dataId=common-prometheus-${ACTIVE}.yml \
--spring.cloud.nacos.config.shared-configs[10].refresh=true \
"
COPY ./web.jar /app
ENTRYPOINT cd / && java ${JAVA_OPTS} -jar /app/web.jar ${ENV_ARGS}
当然开发环境还可简化下yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-name-jdk17
namespace: default
labels:
name: app-name-jdk17
spec:
replicas: 1
selector:
matchLabels:
name: app-name-jdk17
template:
metadata:
labels:
app: app-name-jdk17
name: app-name-jdk17
spec:
imagePullSecrets:
- name: harbor-secret
containers:
- name: app-name
image: harbor.xxx.com/dev/app-name-jdk17:BUILD_TAG
imagePullPolicy: Always
resources:
limits:
memory: 3072Mi
requests:
memory: 1024Mi
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
revisionHistoryLimit: 7
progressDeadlineSeconds: 600
测试预发布生产可以优化yaml
[root@one-tech-prod-jump-06-240 public]# cat ackdeployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-name-jdk17
namespace: default
labels:
name: app-name-jdk17
spec:
replicas: 1
selector:
matchLabels:
name: app-name-jdk17
template:
metadata:
labels:
app: app-name-jdk17
name: app-name-jdk17
version: v1
spec:
containers:
- name: app-name
image: harbor.xxxx.com/default/app-name-jdk17:BUILD_TAG
imagePullPolicy: Always
env:
- name: ACCESS_KEY
valueFrom:
secretKeyRef:
name: app-name-jdk17
key: ACCESS_KEY
optional: true
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: app-name-jdk17
key: SECRET_KEY
optional: true
livenessProbe:
httpGet:
path: /app-name/actuator/health/liveness
port: 80
scheme: HTTP
initialDelaySeconds: 240
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /app-name/actuator/health/readiness
port: 80
scheme: HTTP
initialDelaySeconds: 240
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 10
failureThreshold: 3
lifecycle:
preStop:
exec:
command:
- /bin/bash
- '-c'
- >-
curl 'http://localhost/app-name/appServer/deregister' && sleep 30 && PID=`pidof java` && kill -SIGTERM $PID && while ps -p $PID > /dev/null; do sleep 1; done;
resources:
limits:
memory: 3072Mi
requests:
memory: 1024Mi
volumeMounts:
- mountPath: /logs
name: logm
- name: filebeat
image: harbor.xxx.com/public/filebeat:v1.1.7
imagePullPolicy: Always
env:
- name: ENV
value: default
- name: PROJ_NAME
value: app-name
- name: REDIS_ADDR
value: '192.168.10.61:7008'
volumeMounts:
- mountPath: /logm
name: logm
volumes:
- emptyDir: {}
name: logm
imagePullSecrets:
- name: harbor
terminationGracePeriodSeconds: 240
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
revisionHistoryLimit: 7
progressDeadlineSeconds: 600

后续发版我们可以在这去choose你的环境去打包了比较方便,开发测试预发布也可搭配云效去操作 加入审核人 开发测试等可以自己打包发版 不需要去麻烦运维同事帮忙 是不是很方便 我们运维只管生产即可 大大减少我们工作量
②前端项目pipline(基于阿里云oss部署前端项目)
pipeline {
agent any
tools {
nodejs 'node18'
}
options {
ansiColor('xterm') // 启用 AnsiColor,支持颜色输出
}
stages {
stage('Parse Parameters') {
steps {
script {
// 检查参数格式的正则表达式
def paramRegex = /^(\w+:[^;]+;)+$/
// 判断参数是否符合正则
if (!params.APP_CONFIG || !params.APP_CONFIG.matches(paramRegex)) {
error("Invalid APP_CONFIG format. Expected format: 'key1:value1;key2:value2;'")
}
// 将参数按分号拆分成键值对
def configMap = [:]
params.APP_CONFIG.split(';').each { entry ->
def pair = entry.split(':', 2) // 按冒号拆分
if (pair.size() == 2) {
configMap[pair[0].trim()] = pair[1].trim()
}
}
// 提取具体变量并设置到环境变量中
env.APP_NAME = configMap['app_name']?: 'default-app'
env.GIT_VER = configMap['git_ver']?: 'master_jdk17'
env.GIT_GROUP = configMap['git_group']?: 'default-group'
env.REGION = configMap['region']?: '1'
env.OSS_BUCKET = configMap['oss_bucket']?: 'pm-pc-html-dev'
def suffix = env.OSS_BUCKET.tokenize('-')[-1]
env.OSS_BUCKET_SUFFIX = suffix.capitalize()
}
}
}
stage('Check out project branches') {
steps {
script {
def scm = [
$class: 'GitSCM',
branches: [[name: "*/${GIT_VER}"]],
userRemoteConfigs: [[
url: "https://t.xxxxx.cn/${GIT_GROUP}/${APP_NAME}.git",
credentialsId: '323a0e5a-497a-4816-b344-1assasassd29f5'
]],
extensions: [
[$class: 'CleanBeforeCheckout'], // 清理旧代码
[$class: 'SubmoduleOption',
recursiveSubmodules: true, // 递归更新子模块
trackingSubmodules: true, // 跟踪子模块分支
parentCredentials: true // 使用主项目的凭据
]
]
]
checkout(scm)
}
}
}
stage('npm build') {
steps {
sh '''
npm -v
node -v
export NODE_OPTIONS="--max-old-space-size=4096"
npm install
npm run lint || true
npm run build${OSS_BUCKET_SUFFIX}
'''
}
}
stage('oss push') {
steps {
sh '''
/var/lib/jenkins/ossutil64 rm -e ${REGION} oss://${OSS_BUCKET} -rf
/var/lib/jenkins/ossutil64 cp -e ${REGION} -r -f dist oss://${OSS_BUCKET}
'''
}
}
}
}

这里前端项目也创建好了,这样手动改来改去也比较麻烦 怎么办呢,我们可以使用云效的流水线 去传参 把参数传进去,开发传开发 测试传测试就行,这里不做详细介绍
这样做的目的是什么?
-
环境区分:通过分支区分测试/预发/生产环境,不同环境推送不同仓库、不同命名空间
-
手动确认卡点:生产环境新增人工确认步骤,防止自动误发
-
发版钉钉/企业微信通知:流水线结束推送结果,成功/失败实时提醒
-
自动回滚机制:Pod 启动失败、就绪检测失败,自动触发上一版本回滚
-
镜像清理:定时清理仓库冗余镜像,节省存储资源
总结:
K8s 发版的核心不是写复杂脚本,而是标准化、统一化、可追溯、可回滚。
统一团队发版规范、减少人为事故、提升迭代效率。
需要带手动确认、自动回滚、消息通知的增强版模板,可以留言,后续更新进阶版本!
更多推荐


所有评论(0)