k8s java集群接入istio案例詳解(完整步驟)
支持灰度v1/v2的版本
? 第一步:完整的操作步驟
?? 目標(biāo)
在不修改應(yīng)用代碼的前提下,將現(xiàn)有 Jenkins 流水線與 Istio 服務(wù)網(wǎng)格集成,支持通過 Ingress Gateway 訪問服務(wù),并實現(xiàn)基于 v1/v2 的灰度發(fā)布。
?? 操作流程
1. 確保 Istio 已安裝并啟用注入(已完成)
# cat istio-custom.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
components:
pilot:
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
ingressGateways:
- name: istio-ingressgateway
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 512Mi
values:
global:
hub: swr.cn-east-3.myhuaweicloud.com/bocheng-test/istio
tag: 1.27.3
proxy:
image: proxyv2
imagePullPolicy: IfNotPresent
gateways:
istio-ingressgateway:
type: NodePort
ports:
- port: 80
targetPort: 8080
name: http2
- port: 443
targetPort: 8443
name: https# 安裝 Istio(使用你的私有鏡像) istioctl install -f istio-custom.yaml -y # 啟用命名空間自動注入 kubectl label namespace bc-feature-202509-testcce istio-injection=enabled --overwrite
驗證:
kubectl get pod -n bc-feature-202509-testcce -l app=bc-gateway kubectl describe pod <pod-name> -n bc-feature-202509-testcce | grep istio-proxy
應(yīng)看到 istio-proxy 容器和兩個 initContainers。
2. 創(chuàng)建 Istio 入口網(wǎng)關(guān)(只需一次)
創(chuàng)建文件 gateway.yaml:
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: global-ingress-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"應(yīng)用一次即可:
kubectl apply -f gateway.yaml -n istio-system
3. 創(chuàng)建 Istio 配置模板目錄
在項目根目錄下創(chuàng)建 istio/ 目錄,包含兩個模板文件:
文件:istio/virtualservice.yaml.tpl
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
meta
name: ${MODULE_LOWERCASE}-vs
namespace: ${CCE_NAMESPACE}
spec:
hosts:
- "${MODULE_LOWERCASE}.example.com"
gateways:
- istio-system/global-ingress-gateway
http:
- route:
- destination:
host: ${MODULE_LOWERCASE}.${CCE_NAMESPACE}.svc.cluster.local
subset: ${DEPLOY_VERSION}
weight: 100文件:istio/destinationrule.yaml.tpl
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
meta
name: ${MODULE_LOWERCASE}-dr
namespace: ${CCE_NAMESPACE}
spec:
host: ${MODULE_LOWERCASE}.${CCE_NAMESPACE}.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v24. 修改 Jenkinsfile 實現(xiàn)自動部署 Istio 規(guī)則
在 stage('?? 部署到 CCE') 中,在更新鏡像后添加生成和部署 Istio 配置的步驟。
5. 外部訪問方式
獲取 Ingress Gateway 的 NodePort:
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[0].nodePort}'訪問示例:
curl -H "Host: bc-gateway.example.com" http://192.168.122.190:31902/actuator/health
? 第二步:完整的適配后 Jenkinsfile
pipeline {
agent any
parameters {
choice(
name: 'MODULE',
choices: [
'bc-gateway',
'bc-admin',
'bc-invoice',
'bc-job',
'bc-oldBusiness',
'bc-open',
'bc-resource',
'bc-system',
'bc-third',
'bc-wallet'
],
description: '選擇要構(gòu)建的模塊'
)
string(
name: 'VERSION',
defaultValue: '2.2.2',
description: '構(gòu)建版本號(必須與 pom.xml 中 <revision> 一致)'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: true,
description: '是否跳過單元測試'
)
booleanParam(
name: 'CLEAN_M2_CACHE',
defaultValue: false,
description: '是否清理 .m2 中 org.dromara 緩存(首次構(gòu)建建議勾選)'
)
choice(
name: 'DEPLOY_VERSION',
choices: ['v1', 'v2'],
description: '選擇部署版本(v1 或 v2),用于灰度發(fā)布'
)
booleanParam(
name: 'DEPLOY_TO_CCE',
defaultValue: false,
description: '是否部署到華為云 CCE 集群'
)
booleanParam(
name: 'ROLLBACK_LAST_VERSION',
defaultValue: false,
description: '是否回滾上一版本(跳過構(gòu)建)'
)
booleanParam(
name: 'WAIT_FOR_ALL_REPLICAS',
defaultValue: false,
description: '是否等待所有副本完成滾動更新(否則僅等待首個新 Pod Ready)'
)
}
environment {
MVN = '/usr/maven/apache-maven-3.9.9/bin/mvn'
JAVA_HOME = '/usr/java/jdk-17.0.12'
PATH = "${env.JAVA_HOME}/bin:${env.MVN}:${env.PATH}"
SWR_REGISTRY = 'swr.cn-east-3.myhuaweicloud.com'
IMAGE_REPO = 'dbb-java-micro-test'
CCE_NAMESPACE = 'bc-feature-202509-testcce'
CONTAINER_NAME = 'container-1'
KUBECTL_PATH = '/usr/local/bin/kubectl'
KUBECONFIG_FILE = '/root/.kube/config'
PORT_MAPPING_STR = '''
bc-gateway=8901
bc-admin=8909
bc-invoice=8902
bc-job=8905
bc-oldBusiness=8903
bc-open=8910
bc-resource=8904
bc-system=8903
bc-third=8906
bc-wallet=8908
'''
HEALTH_PATH = '/actuator/health'
}
stages {
stage('?? 是否執(zhí)行回滾') {
when { expression { params.ROLLBACK_LAST_VERSION } }
steps {
script {
def ns = env.CCE_NAMESPACE
def deployName = params.MODULE.toLowerCase()
sh """
set -e
export KUBECONFIG=${env.KUBECONFIG_FILE}
${env.KUBECTL_PATH} rollout undo deployment/${deployName} -n ${ns}
echo "? 等待回滾完成..."
${env.KUBECTL_PATH} rollout status deployment/${deployName} -n ${ns} --timeout=120s
echo "? 回滾成功"
"""
}
}
}
stage('?? 拉取代碼') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
checkout scm
}
}
stage('?? 準(zhǔn)備工作') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
def portMap = [:]
env.PORT_MAPPING_STR.stripIndent().split('\n').each {
def pair = it.trim().split('=')
if (pair.size() == 2) {
portMap[pair[0].trim()] = pair[1].trim()
}
}
def module = params.MODULE
def modulePath = module == 'bc-gateway' ? 'bc-gateway' : "bc-modules/${module}"
def port = portMap[module]
if (!port) error("? 未配置模塊 ${module} 的服務(wù)端口")
def timestamp = new Date().format('yyyyMMddHHmmss')
def imageTag = "${params.DEPLOY_VERSION}-${timestamp}"
def imageName = "${env.SWR_REGISTRY}/${env.IMAGE_REPO}/${module.toLowerCase()}"
def fullImageName = "${imageName}:${imageTag}"
env.MODULE = module
env.MODULE_PATH = modulePath
env.SERVICE_JAR = "${modulePath}/target/${module}.jar"
env.IMAGE_NAME = imageName
env.FULL_IMAGE_NAME = fullImageName
env.SERVICE_PORT = port
}
}
}
stage('?? 清理本地緩存(可選)') {
when {
allOf {
expression { !params.ROLLBACK_LAST_VERSION }
expression { params.CLEAN_M2_CACHE }
}
}
steps {
script {
def dromaraDir = "$HOME/.m2/repository/org/dromara"
sh "rm -rf ${dromaraDir} && echo '??? 已清理 .m2 緩存'"
}
}
}
stage('?? 構(gòu)建模塊') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
def skipTestsFlag = params.SKIP_TESTS ? '-Dmaven.test.skip=true' : ''
sh """
set -e
cd \$WORKSPACE
\$MVN clean install \\
-pl \${MODULE_PATH} \\
-am \\
-P test \\
-T 4C \\
${skipTestsFlag} \\
-Drevision=${params.VERSION} \\
-U --no-transfer-progress -B
echo "? 構(gòu)建成功"
"""
}
}
}
stage('? 測試報告') {
when {
allOf {
expression { !params.ROLLBACK_LAST_VERSION }
expression { !params.SKIP_TESTS }
}
}
steps {
junit testResults: "${env.MODULE_PATH}/target/surefire-reports/*.xml"
}
}
stage('?? 構(gòu)建鏡像') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
sh '''
set -e
CONTEXT_DIR="$WORKSPACE/$MODULE_PATH"
JAR_PATH="$CONTEXT_DIR/target/$MODULE.jar"
DOCKERFILE="$CONTEXT_DIR/Dockerfile"
[ -f "$JAR_PATH" ] || { echo "? JAR 文件不存在"; exit 1; }
[ -f "$DOCKERFILE" ] || { echo "? Dockerfile 不存在"; exit 1; }
cd "$CONTEXT_DIR" && docker build -t "$FULL_IMAGE_NAME" .
echo "? 鏡像構(gòu)建成功: $FULL_IMAGE_NAME"
'''
}
}
}
stage('?? 登錄 SWR') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
withCredentials([usernamePassword(
credentialsId: 'cba87dfc-e05d-4d08-b059-5dc83c79e7ec',
usernameVariable: 'SWR_USER',
passwordVariable: 'SWR_PASS'
)]) {
sh '''
set -e
echo "$SWR_PASS" | docker login $SWR_REGISTRY -u "$SWR_USER" --password-stdin
echo "? 登錄 SWR 成功"
'''
}
}
}
}
stage('?? 推送鏡像') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
sh '''
set -e
docker push $FULL_IMAGE_NAME
echo "? 鏡像推送成功: $FULL_IMAGE_NAME"
'''
}
}
stage('?? 部署到 CCE') {
when {
allOf {
expression { !params.ROLLBACK_LAST_VERSION }
expression { params.DEPLOY_TO_CCE }
}
}
steps {
script {
def ns = env.CCE_NAMESPACE
def container = env.CONTAINER_NAME
def deployName = env.MODULE.toLowerCase()
def timeoutSeconds = 300
def intervalSeconds = 45
def maxAttempts = timeoutSeconds / intervalSeconds
// === 1. 更新鏡像 ===
sh """
set -e
export KUBECONFIG=${env.KUBECONFIG_FILE}
${env.KUBECTL_PATH} set image deployment/${deployName} ${container}=$FULL_IMAGE_NAME -n ${ns}
echo "? 鏡像已更新為: $FULL_IMAGE_NAME"
"""
// === 2. 注入 version 標(biāo)簽到 Deployment(關(guān)鍵?。?==
sh """
set -e
export KUBECONFIG=${env.KUBECONFIG_FILE}
${env.KUBECTL_PATH} patch deployment ${deployName} -n ${ns} -p='{"spec":{"template":{"metadata":{"labels":{"version":"${params.DEPLOY_VERSION}"}}}}}'
echo "? Deployment 已打 version=${params.DEPLOY_VERSION} 標(biāo)簽"
"""
// === 3. 生成并部署 Istio VirtualService 和 DestinationRule ===
echo "?? 生成 Istio 配置文件..."
sh '''
set -e
export MODULE_LOWERCASE=''' + deployName + '''
export CCE_NAMESPACE=''' + ns + '''
export DEPLOY_VERSION=''' + params.DEPLOY_VERSION + '''
# 替換模板
cat istio/virtualservice.yaml.tpl | envsubst > /tmp/virtualservice.yaml
cat istio/destinationrule.yaml.tpl | envsubst > /tmp/destinationrule.yaml
# 應(yīng)用配置
export KUBECONFIG=/root/.kube/config
kubectl apply -f /tmp/virtualservice.yaml -n $CCE_NAMESPACE
kubectl apply -f /tmp/destinationrule.yaml -n $CCE_NAMESPACE
echo "? Istio 路由規(guī)則已更新"
'''
// === 4. 等待驗證策略 ===
if (params.WAIT_FOR_ALL_REPLICAS) {
echo "? 等待所有副本完成滾動更新..."
sh """
set -e
export KUBECONFIG=${env.KUBECONFIG_FILE}
${env.KUBECTL_PATH} rollout status deployment/${deployName} -n ${ns} --timeout=${timeoutSeconds}s
echo "? 所有副本已成功更新"
"""
} else {
echo "? 等待首個新 Pod 進(jìn)入 READY 狀態(tài)..."
def success = false
for (int i = 0; i < maxAttempts; i++) {
try {
def output = sh(
script: """
set +e
export KUBECONFIG=${env.KUBECONFIG_FILE}
kubectl get pods \\
-l app=${deployName},version=${params.DEPLOY_VERSION} \\
-n ${ns} \\
--field-selector=status.phase=Running \\
-o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[0].ready,IMAGE:.spec.containers[0].image \\
--no-headers
""",
returnStdout: true
).trim()
if (output && output.contains("${FULL_IMAGE_NAME}") && output.contains("true")) {
echo "?? 新版本 Pod 已就緒"
success = true
break
}
} catch (Exception e) {
echo "?? 檢查異常: ${e}"
}
sleep(intervalSeconds)
}
if (!success) {
error("? 超時:未能看到新版本 Pod 就緒")
}
echo "? 部署驗證通過"
}
}
}
}
stage('?? 完成通知') {
steps {
script {
if (params.ROLLBACK_LAST_VERSION) {
echo "? 已完成回滾操作"
} else {
echo "? 構(gòu)建 & 部署成功"
if (params.DEPLOY_TO_CCE) {
echo "?? 已部署至 CCE: deployment/${env.MODULE.toLowerCase()} in ${env.CCE_NAMESPACE}"
echo "?? 鏡像: ${env.FULL_IMAGE_NAME}"
echo "?? 提示: 支持基于 v1/v2 的灰度發(fā)布"
}
}
}
}
}
}
post {
success {
echo "?【流水線成功】構(gòu)建與部署已完成"
}
failure {
echo "?【流水線失敗】請檢查上述日志"
}
always {
cleanWs()
}
}
}? 總結(jié)
你現(xiàn)在擁有了一個完全適配 Istio 的 Jenkins 流水線,實現(xiàn)了:
- 自動注入 Sidecar
- 動態(tài)生成
VirtualService和DestinationRule - 基于
v1/v2的灰度發(fā)布 - 外部通過統(tǒng)一入口訪問
外部訪問只需:
curl -H "Host: bc-gateway.example.com" http://<INGRESS_IP>:<NODEPORT>/actuator/health
不要灰度的版本
你可能在某些模塊(如 bc-job、bc-third)上不需要灰度,但仍希望接入 Istio 以獲得 mTLS、可觀測性等能力。這種情況下,我們簡化 DestinationRule 和 VirtualService 的配置。
? 第一步:完整的操作步驟(非灰度場景)
?? 目標(biāo)
將現(xiàn)有 Jenkins 流水線適配 Istio,支持:
- Sidecar 自動注入
- 外部通過 Ingress Gateway 訪問服務(wù)
- 啟用 mTLS 加密通信
- 不使用 v1/v2 灰度,直接路由到默認(rèn)版本
- 不修改應(yīng)用代碼
?? 操作流程
1. 確保 Istio 已安裝并啟用注入(已完成)
root@k8s-master:/data/service/ISTIO# cat istio-custom.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
components:
pilot:
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
ingressGateways:
- name: istio-ingressgateway
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 512Mi
values:
global:
hub: swr.cn-east-3.myhuaweicloud.com/bocheng-test/istio
tag: 1.27.3
proxy:
image: proxyv2
imagePullPolicy: IfNotPresent
gateways:
istio-ingressgateway:
type: NodePort
ports:
- port: 80
targetPort: 8080
name: http2
- port: 443
targetPort: 8443
name: https# 安裝 Istio istioctl install -f istio-custom.yaml -y # 啟用命名空間自動注入 kubectl label namespace bc-feature-202509-testcce istio-injection=enabled --overwrite
驗證:
kubectl get pod -n bc-feature-202509-testcce -l app=bc-gateway kubectl describe pod <pod-name> -n bc-feature-202509-testcce | grep istio-proxy
應(yīng)看到 istio-proxy 容器。
2. 創(chuàng)建 Istio 入口網(wǎng)關(guān)(只需一次)
創(chuàng)建文件 gateway.yaml:
apiVersion: networking.istio.io/v1beta1
kind: Gateway
meta
name: global-ingress-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"應(yīng)用一次即可:
kubectl apply -f gateway.yaml -n istio-system
3. 創(chuàng)建 Istio 配置模板目錄(非灰度版)
在項目根目錄下創(chuàng)建 istio/ 目錄,包含兩個模板文件:
文件:istio/virtualservice-nogray.yaml.tpl
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
meta
name: ${MODULE_LOWERCASE}-vs
namespace: ${CCE_NAMESPACE}
spec:
hosts:
- "${MODULE_LOWERCASE}.example.com"
gateways:
- istio-system/global-ingress-gateway
http:
- route:
- destination:
host: ${MODULE_LOWERCASE}.${CCE_NAMESPACE}.svc.cluster.local
port:
number: ${SERVICE_PORT}
weight: 100文件:istio/destinationrule-nogray.yaml.tpl
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
meta
name: ${MODULE_LOWERCASE}-dr
namespace: ${CCE_NAMESPACE}
spec:
host: ${MODULE_LOWERCASE}.${CCE_NAMESPACE}.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
loadBalancer:
simple: ROUND_ROBIN
?? 注意:沒有
subsets,直接路由到服務(wù)后端。
4. 修改 Jenkinsfile 實現(xiàn)自動部署 Istio 規(guī)則
在 stage('?? 部署到 CCE') 中,在更新鏡像后添加生成和部署 Istio 配置的步驟。
5. 外部訪問方式
獲取 Ingress Gateway 的 NodePort:
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[0].nodePort}'
訪問示例:
curl -H "Host: bc-gateway.example.com" http://192.168.122.190:31902/actuator/health
? 第二步:完整的適配后 Jenkinsfile(非灰度版)
pipeline {
agent any
parameters {
choice(
name: 'MODULE',
choices: [
'bc-gateway',
'bc-admin',
'bc-invoice',
'bc-job',
'bc-oldBusiness',
'bc-open',
'bc-resource',
'bc-system',
'bc-third',
'bc-wallet'
],
description: '選擇要構(gòu)建的模塊'
)
string(
name: 'VERSION',
defaultValue: '2.2.2',
description: '構(gòu)建版本號(必須與 pom.xml 中 <revision> 一致)'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: true,
description: '是否跳過單元測試'
)
booleanParam(
name: 'CLEAN_M2_CACHE',
defaultValue: false,
description: '是否清理 .m2 中 org.dromara 緩存(首次構(gòu)建建議勾選)'
)
booleanParam(
name: 'DEPLOY_TO_CCE',
defaultValue: false,
description: '是否部署到華為云 CCE 集群'
)
booleanParam(
name: 'ROLLBACK_LAST_VERSION',
defaultValue: false,
description: '是否回滾上一版本(跳過構(gòu)建)'
)
booleanParam(
name: 'WAIT_FOR_ALL_REPLICAS',
defaultValue: false,
description: '是否等待所有副本完成滾動更新(否則僅等待首個新 Pod Ready)'
)
}
environment {
MVN = '/usr/maven/apache-maven-3.9.9/bin/mvn'
JAVA_HOME = '/usr/java/jdk-17.0.12'
PATH = "${env.JAVA_HOME}/bin:${env.MVN}:${env.PATH}"
SWR_REGISTRY = 'swr.cn-east-3.myhuaweicloud.com'
IMAGE_REPO = 'dbb-java-micro-test'
CCE_NAMESPACE = 'bc-feature-202509-testcce'
CONTAINER_NAME = 'container-1'
KUBECTL_PATH = '/usr/local/bin/kubectl'
KUBECONFIG_FILE = '/root/.kube/config'
PORT_MAPPING_STR = '''
bc-gateway=8901
bc-admin=8909
bc-invoice=8902
bc-job=8905
bc-oldBusiness=8903
bc-open=8910
bc-resource=8904
bc-system=8903
bc-third=8906
bc-wallet=8908
'''
HEALTH_PATH = '/actuator/health'
}
stages {
stage('?? 是否執(zhí)行回滾') {
when { expression { params.ROLLBACK_LAST_VERSION } }
steps {
script {
def ns = env.CCE_NAMESPACE
def deployName = params.MODULE.toLowerCase()
sh """
set -e
export KUBECONFIG=${env.KUBECONFIG_FILE}
${env.KUBECTL_PATH} rollout undo deployment/${deployName} -n ${ns}
echo "? 等待回滾完成..."
${env.KUBECTL_PATH} rollout status deployment/${deployName} -n ${ns} --timeout=120s
echo "? 回滾成功"
"""
}
}
}
stage('?? 拉取代碼') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
checkout scm
}
}
stage('?? 準(zhǔn)備工作') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
def portMap = [:]
env.PORT_MAPPING_STR.stripIndent().split('\n').each {
def pair = it.trim().split('=')
if (pair.size() == 2) {
portMap[pair[0].trim()] = pair[1].trim()
}
}
def module = params.MODULE
def modulePath = module == 'bc-gateway' ? 'bc-gateway' : "bc-modules/${module}"
def port = portMap[module]
if (!port) error("? 未配置模塊 ${module} 的服務(wù)端口")
def timestamp = new Date().format('yyyyMMddHHmmss')
def imageTag = "latest-${timestamp}"
def imageName = "${env.SWR_REGISTRY}/${env.IMAGE_REPO}/${module.toLowerCase()}"
def fullImageName = "${imageName}:${imageTag}"
env.MODULE = module
env.MODULE_PATH = modulePath
env.SERVICE_JAR = "${modulePath}/target/${module}.jar"
env.IMAGE_NAME = imageName
env.FULL_IMAGE_NAME = fullImageName
env.SERVICE_PORT = port
}
}
}
stage('?? 清理本地緩存(可選)') {
when {
allOf {
expression { !params.ROLLBACK_LAST_VERSION }
expression { params.CLEAN_M2_CACHE }
}
}
steps {
script {
def dromaraDir = "$HOME/.m2/repository/org/dromara"
sh "rm -rf ${dromaraDir} && echo '??? 已清理 .m2 緩存'"
}
}
}
stage('?? 構(gòu)建模塊') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
def skipTestsFlag = params.SKIP_TESTS ? '-Dmaven.test.skip=true' : ''
sh """
set -e
cd \$WORKSPACE
\$MVN clean install \\
-pl \${MODULE_PATH} \\
-am \\
-P test \\
-T 4C \\
${skipTestsFlag} \\
-Drevision=${params.VERSION} \\
-U --no-transfer-progress -B
echo "? 構(gòu)建成功"
"""
}
}
}
stage('? 測試報告') {
when {
allOf {
expression { !params.ROLLBACK_LAST_VERSION }
expression { !params.SKIP_TESTS }
}
}
steps {
junit testResults: "${env.MODULE_PATH}/target/surefire-reports/*.xml"
}
}
stage('?? 構(gòu)建鏡像') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
sh '''
set -e
CONTEXT_DIR="$WORKSPACE/$MODULE_PATH"
JAR_PATH="$CONTEXT_DIR/target/$MODULE.jar"
DOCKERFILE="$CONTEXT_DIR/Dockerfile"
[ -f "$JAR_PATH" ] || { echo "? JAR 文件不存在"; exit 1; }
[ -f "$DOCKERFILE" ] || { echo "? Dockerfile 不存在"; exit 1; }
cd "$CONTEXT_DIR" && docker build -t "$FULL_IMAGE_NAME" .
echo "? 鏡像構(gòu)建成功: $FULL_IMAGE_NAME"
'''
}
}
}
stage('?? 登錄 SWR') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
script {
withCredentials([usernamePassword(
credentialsId: 'cba87dfc-e05d-4d08-b059-5dc83c79e7ec',
usernameVariable: 'SWR_USER',
passwordVariable: 'SWR_PASS'
)]) {
sh '''
set -e
echo "$SWR_PASS" | docker login $SWR_REGISTRY -u "$SWR_USER" --password-stdin
echo "? 登錄 SWR 成功"
'''
}
}
}
}
stage('?? 推送鏡像') {
when { expression { !params.ROLLBACK_LAST_VERSION } }
steps {
sh '''
set -e
docker push $FULL_IMAGE_NAME
echo "? 鏡像推送成功: $FULL_IMAGE_NAME"
'''
}
}
stage('?? 部署到 CCE') {
when {
allOf {
expression { !params.ROLLBACK_LAST_VERSION }
expression { params.DEPLOY_TO_CCE }
}
}
steps {
script {
def ns = env.CCE_NAMESPACE
def container = env.CONTAINER_NAME
def deployName = env.MODULE.toLowerCase()
def timeoutSeconds = 300
def intervalSeconds = 45
def maxAttempts = timeoutSeconds / intervalSeconds
// === 1. 更新鏡像 ===
sh """
set -e
export KUBECONFIG=${env.KUBECONFIG_FILE}
${env.KUBECTL_PATH} set image deployment/${deployName} ${container}=$FULL_IMAGE_NAME -n ${ns}
echo "? 鏡像已更新為: $FULL_IMAGE_NAME"
"""
// === 2. 生成并部署 Istio VirtualService 和 DestinationRule(非灰度版)===
echo "?? 生成 Istio 配置文件..."
sh '''
set -e
export MODULE_LOWERCASE=''' + deployName + '''
export CCE_NAMESPACE=''' + ns + '''
export SERVICE_PORT=''' + env.SERVICE_PORT + '''
# 替換模板
cat istio/virtualservice-nogray.yaml.tpl | envsubst > /tmp/virtualservice.yaml
cat istio/destinationrule-nogray.yaml.tpl | envsubst > /tmp/destinationrule.yaml
# 應(yīng)用配置
export KUBECONFIG=/root/.kube/config
kubectl apply -f /tmp/virtualservice.yaml -n $CCE_NAMESPACE
kubectl apply -f /tmp/destinationrule.yaml -n $CCE_NAMESPACE
echo "? Istio 路由規(guī)則已更新(非灰度模式)"
'''
// === 3. 等待驗證策略 ===
if (params.WAIT_FOR_ALL_REPLICAS) {
echo "? 等待所有副本完成滾動更新..."
sh """
set -e
export KUBECONFIG=${env.KUBECONFIG_FILE}
${env.KUBECTL_PATH} rollout status deployment/${deployName} -n ${ns} --timeout=${timeoutSeconds}s
echo "? 所有副本已成功更新"
"""
} else {
echo "? 等待首個新 Pod 進(jìn)入 READY 狀態(tài)..."
def success = false
for (int i = 0; i < maxAttempts; i++) {
try {
def output = sh(
script: """
set +e
export KUBECONFIG=${env.KUBECONFIG_FILE}
kubectl get pods \\
-l app=${deployName} \\
-n ${ns} \\
--field-selector=status.phase=Running \\
-o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[0].ready,IMAGE:.spec.containers[0].image \\
--no-headers
""",
returnStdout: true
).trim()
if (output && output.contains("${FULL_IMAGE_NAME}") && output.contains("true")) {
echo "?? 新版本 Pod 已就緒"
success = true
break
}
} catch (Exception e) {
echo "?? 檢查異常: ${e}"
}
sleep(intervalSeconds)
}
if (!success) {
error("? 超時:未能看到新版本 Pod 就緒")
}
echo "? 部署驗證通過"
}
}
}
}
stage('?? 完成通知') {
steps {
script {
if (params.ROLLBACK_LAST_VERSION) {
echo "? 已完成回滾操作"
} else {
echo "? 構(gòu)建 & 部署成功"
if (params.DEPLOY_TO_CCE) {
echo "?? 已部署至 CCE: deployment/${env.MODULE.toLowerCase()} in ${env.CCE_NAMESPACE}"
echo "?? 鏡像: ${env.FULL_IMAGE_NAME}"
echo "?? 提示: 非灰度模式,流量直接路由到服務(wù)后端"
}
}
}
}
}
}
post {
success {
echo "?【流水線成功】構(gòu)建與部署已完成"
}
failure {
echo "?【流水線失敗】請檢查上述日志"
}
always {
cleanWs()
}
}
}? 總結(jié)
你現(xiàn)在擁有了一個完全適配 Istio 的非灰度發(fā)布流水線,實現(xiàn)了:
- 自動注入 Sidecar
- 啟用 mTLS 加密通信
- 外部通過統(tǒng)一入口訪問
- 無需 v1/v2 標(biāo)簽,直接路由到服務(wù)
適用于 bc-job、bc-third 等無需灰度的模塊。
將 Istio Ingress Gateway 的NodePort模式*。*
? 第一步:istio-custom.yaml(NodePort 模式)
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
components:
pilot:
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
ingressGateways:
- name: istio-ingressgateway
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 512Mi
values:
global:
hub: swr.cn-east-3.myhuaweicloud.com/bocheng-test/istio
tag: 1.27.3
proxy:
image: proxyv2
imagePullPolicy: IfNotPresent
gateways:
istio-ingressgateway:
# 使用 NodePort 模式
type: NodePort
ports:
- port: 80
targetPort: 8080
name: http2
- port: 443
targetPort: 8443
name: https?? 提示:NodePort 端口會自動分配在
30000-32767范圍內(nèi)。
? 第二步:安裝 Istio
# 卸載舊版本(如有) istioctl uninstall -y --purge # 安裝 NodePort 配置 istioctl install -f istio-custom.yaml -y
? 第三步:驗證 NodePort 是否創(chuàng)建成功
kubectl get svc -n istio-system istio-ingressgateway
輸出示例:
NAME TYPE CLUSTER-IP PORT(S) AGE istio-ingressgateway NodePort 10.96.123.45 80:31234/TCP,443:30987/TCP 2m
TYPE為NodePort- 記錄
31234(HTTP)和30987(HTTPS),用于外部訪問
? 第四步:創(chuàng)建 Gateway(入口網(wǎng)關(guān)規(guī)則)
# gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
meta
name: global-ingress-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"kubectl apply -f gateway.yaml
? 第五步:創(chuàng)建 VirtualService(路由規(guī)則)
# virtualservice-nogray.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
meta
name: bc-gateway-vs
namespace: bc-feature-202509-testcce
spec:
hosts:
- "bc-gateway.example.com"
gateways:
- istio-system/global-ingress-gateway
http:
- route:
- destination:
host: bc-gateway.bc-feature-202509-testcce.svc.cluster.local
port:
number: 8901
weight: 100kubectl apply -f virtualservice-nogray.yaml
? 第六步:外部訪問方式(使用任意節(jié)點(diǎn) IP + NodePort)
獲取 NodePort:
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath='{.spec.ports[0].nodePort}'
# 輸出:31234
使用任意 worker 節(jié)點(diǎn)的 IP 訪問:
export NODE_IP=192.168.122.190 export NODE_PORT=31234 curl -H "Host: bc-gateway.example.com" http://$NODE_IP:$NODE_PORT/actuator/health
? 成功!
? 總結(jié):NodePort 模式特點(diǎn)
| 特性 | 說明 |
|---|---|
| 類型 | NodePort |
| 外部訪問 | <worker-node-ip>:<nodeport> |
| 端口范圍 | 30000-32767 |
| 是否需要云廠商支持 | ? 不需要 |
| 適合場景 | 測試、開發(fā)、無 ELB 的環(huán)境 |
適配目前的k8s環(huán)境需要變更嗎
# cat istio-custom.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
components:
pilot:
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
ingressGateways:
- name: istio-ingressgateway
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 512Mi
values:
global:
hub: swr.cn-east-3.myhuaweicloud.com/bocheng-test/istio
tag: 1.27.3
proxy:
image: proxyv2
imagePullPolicy: IfNotPresent
gateways:
istio-ingressgateway:
type: NodePort
ports:
- port: 80
targetPort: 8080
name: http2
- port: 443
targetPort: 8443
name: https? 為什么說它“不需要變更”?
| 配置項 | 狀態(tài) | 說明 |
|---|---|---|
profile: default | ? 合理 | 使用默認(rèn) profile,功能完整 |
pilot 資源限制 | ? 合理 | 在小集群中降低了 CPU/Mem 請求(50m/64Mi),避免調(diào)度失敗 |
ingressGateways 資源限制 | ? 合理 | 設(shè)置了合理的 limits 和 requests |
hub + tag 指定鏡像 | ? 最佳實踐 | 使用 swr.cn-east-3.myhuaweicloud.com/bocheng-test/istio 統(tǒng)一管理鏡像,比手動寫 PILOT_IMAGE 更可靠 |
image: proxyv2 | ? 正確 | 明確指定 sidecar 鏡像名稱 |
type: NodePort | ? 適合你的環(huán)境 | 在非云 LB 環(huán)境下使用 NodePort 是標(biāo)準(zhǔn)做法 |
targetPort: 8080/8443 | ? 正確 | 匹配 Istio ingressgateway 容器內(nèi) Envoy 監(jiān)聽端口 |
??? 這個配置的作用總結(jié)
你通過這個 YAML 實現(xiàn)了:
- 使用華為云 SWR 私有鏡像倉庫,解決拉取問題
- 降低資源請求,適應(yīng)測試/開發(fā)集群
- 開啟 NodePort,暴露入口網(wǎng)關(guān)
- 所有配置清晰、可維護(hù)、可納入 Git 版本控制
?? 什么時候才需要修改它?
除非遇到以下情況,否則無需改動:
| 場景 | 是否需要修改 |
|---|---|
| 新增 Egress Gateway | ? 需要在 components 中添加 |
| 改用 LoadBalancer 類型 | ? 修改 gateways.istio-ingressgateway.type: LoadBalancer |
| 啟用 mTLS STRICT 全局默認(rèn) | ? 在 values.global 中添加策略 |
| 升級 Istio 版本 | ? 修改 tag: 1.28.0 |
| 添加自定義證書 | ? 添加 secretVolumes 或更新 tls 配置 |
? 建議操作
你現(xiàn)在應(yīng)該:
# 確保只安裝一次(避免重復(fù)) istioctl uninstall -y --purge # 使用當(dāng)前 yaml 安裝 istioctl install -f istio-custom.yaml -y
然后按之前的流程創(chuàng)建 Gateway、VirtualService、DestinationRule。
將 Istio Ingress Gateway 從NodePort模式適配為LoadBalancer模式的完整配置和操作流程。
? 第一步:修改istio-custom.yaml啟用 LoadBalancer
保持其他配置不變,只修改 values.gateways.istio-ingressgateway.type 為 LoadBalancer。
更新后的istio-custom.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
components:
pilot:
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
ingressGateways:
- name: istio-ingressgateway
enabled: true
k8s:
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 512Mi
values:
global:
hub: swr.cn-east-3.myhuaweicloud.com/bocheng-test/istio
tag: 1.27.3
proxy:
image: proxyv2
imagePullPolicy: IfNotPresent
gateways:
istio-ingressgateway:
# 改為 LoadBalancer
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
name: http2
- port: 443
targetPort: 8443
name: https
# 可選:指定服務(wù)注解,用于云廠商 ELB 配置
serviceAnnotations:
# 華為云 ELB 示例注解(按需啟用)
# service.beta.kubernetes.io/huawei-elb-vpcid: your-vpc-id
# service.beta.kubernetes.io/huawei-elb-classic-listener-port-protocol: tcp?? 提示:
serviceAnnotations可用于指定 ELB 類型、帶寬、公網(wǎng)IP等,具體參考 華為云 CCE 文檔。
? 第二步:重新安裝 Istio
# 卸載舊版本 istioctl uninstall -y --purge # 安裝新配置 istioctl install -f istio-custom.yaml -y
? 第三步:驗證 LoadBalancer 是否創(chuàng)建成功
kubectl get svc -n istio-system istio-ingressgateway
你應(yīng)該看到:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE istio-ingressgateway LoadBalancer 10.96.123.45 121.36.88.123 80:31234/TCP,443:30987/TCP 2m
TYPE是LoadBalancerEXTERNAL-IP顯示公網(wǎng) IP(可能需要幾分鐘)
? 第四步:創(chuàng)建 Gateway + VirtualService(非灰度版示例)
創(chuàng)建gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
meta
name: global-ingress-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"kubectl apply -f gateway.yaml
創(chuàng)建virtualservice-nogray.yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
meta
name: bc-gateway-vs
namespace: bc-feature-202509-testcce
spec:
hosts:
- "bc-gateway.example.com"
gateways:
- istio-system/global-ingress-gateway
http:
- route:
- destination:
host: bc-gateway.bc-feature-202509-testcce.svc.cluster.local
port:
number: 8901
weight: 100kubectl apply -f virtualservice-nogray.yaml
? 第五步:外部訪問方式(使用 LB 公網(wǎng) IP)
獲取公網(wǎng)地址:
export LB_IP=$(kubectl get svc -n istio-system istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $LB_IP
訪問服務(wù):
curl -H "Host: bc-gateway.example.com" http://$LB_IP/actuator/health
? 成功!
? Jenkinsfile 中如何適配?
如果你希望在 Jenkins 中動態(tài)判斷是否使用 LoadBalancer,可以加一個參數(shù):
choice(
name: 'INGRESS_TYPE',
choices: ['NodePort', 'LoadBalancer'],
description: '選擇入口類型'
)然后在部署 Istio 時傳入不同配置,或直接使用固定 LoadBalancer。
? 總結(jié):NodePortvsLoadBalancer
| 特性 | NodePort | LoadBalancer |
|---|---|---|
| 是否需要云廠商支持 | ? 不需要 | ? 需要(如華為云 ELB) |
| 外部訪問方式 | <node-ip>:<port> | <lb-ip>:80/443 |
| 端口范圍 | 30000-32767 | 標(biāo)準(zhǔn)端口(80/443) |
| 適合場景 | 測試環(huán)境 | 生產(chǎn)環(huán)境 |
| 自動創(chuàng)建公網(wǎng) IP | ? 否 | ? 是 |
你現(xiàn)在可以將 istio-custom.yaml 中的 type: NodePort 改為 LoadBalancer,并立即獲得一個公網(wǎng)可訪問的 Istio 入口網(wǎng)關(guān)。
需要我為你生成一份 華為云 ELB 注解完整模板 或 自動獲取 LB IP 并測試連通性的 Shell 腳本 嗎?
到此這篇關(guān)于k8s java集群接入istio案例的文章就介紹到這了,更多相關(guān)k8s集群接入istio內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot排除自動加載數(shù)據(jù)源方式
這篇文章主要介紹了SpringBoot排除自動加載數(shù)據(jù)源方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
java生成json實現(xiàn)隱藏掉關(guān)鍵屬性
這篇文章主要介紹了java生成json實現(xiàn)隱藏掉關(guān)鍵屬性,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
java后端如何調(diào)用第三方接口(往header和body中的參數(shù)傳參)
這篇文章主要介紹了java后端如何調(diào)用第三方接口(往header和body中的參數(shù)傳參),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
升級springboot3之自動配置導(dǎo)入失效問題及解決
這篇文章主要介紹了升級springboot3之自動配置導(dǎo)入失效問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
SpringBoot項目打包運(yùn)行jar包的實現(xiàn)示例
本文主要介紹了SpringBoot項目打包運(yùn)行jar包的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02

