本文将从核心概念拆解、环境准备、迁移部署全流程、Jenkins自动化配置、常见踩坑指南五个维度,详细分享迁移实战经验,帮助更多“跨界”开发者快速上手云原生部署。
一、核心概念精讲:云原生部署的“基础语法”
迁移前必须吃透核心概念,避免因术语混淆导致操作失误。以下是云原生部署(以阿里云为例)的关键概念解析,结合实战场景说明其作用:
1. 镜像仓库与容器服务核心
- ACR(容器镜像服务):本质是私有Docker镜像仓库,用于存储项目打包后的Docker镜像。与公共仓库(如Docker Hub)相比,私有仓库更安全,可避免公司代码/配置泄露,是云原生部署的“镜像中转站”。
- ACK(容器服务Kubernetes版):托管式K8s集群服务,云厂商已完成集群搭建、控制面维护、节点管理等底层工作,开发者无需关注K8s集群运维,仅需聚焦服务部署与运行。
2. K8s核心组件(实战视角)
- 命名空间(Namespace):用于隔离不同环境(如开发、测试、生产)或不同项目的资源,避免资源命名冲突。例如用
dev命名空间部署开发环境服务,prod部署生产环境服务。 - 工作负载(Workload):
- 无状态工作负载(Deployment):适用于不存储数据、可随时销毁重建的服务(如微服务API、前端应用),支持自动扩缩容、滚动更新,是最常用的工作负载类型。
- 有状态工作负载(StatefulSet):适用于需要持久化数据的服务(如数据库、缓存),支持稳定的网络标识和数据持久化。
- 容器组(Pod):K8s的最小部署单元,一个Pod可包含一个或多个容器,共享网络和存储资源。实际部署中,一个服务通常对应多个Pod副本(用于负载均衡)。
3. 网络访问:从集群内部到公网
- Service:管理同一服务的多个Pod副本,提供统一访问入口,支持三种核心类型:
- ClusterIP:仅集群内部可访问,适用于集群内服务间通信(如微服务调用)。
- NodePort:将服务端口映射到集群节点的物理端口,需节点有公网IP或打通内网路由才能外部访问,适用于测试环境。
- LoadBalancer:云厂商自动分配公网负载均衡IP,支持公网直接访问,适用于生产环境。
- Ingress:相当于K8s集群的“反向代理”,通过域名或路径将流量路由到不同Service,实现多服务共享一个公网IP和端口。例如通过
/api/user路由到用户服务,/api/order路由到订单服务。
4. 配置与安全管理
- ConfigMap:存储非敏感配置信息(如应用配置文件、环境变量),相当于K8s的“配置文件仓库”。适用于存储公共配置(如xxl-job、Nacos的基础配置),支持动态更新,无需重启服务。
- Secret:存储敏感信息(如数据库密码、镜像仓库登录凭证),数据以Base64编码存储(非加密,需配合RBAC权限控制保障安全)。核心类型:
- Opaque:通用类型,存储键值对形式的敏感数据(如数据库账号密码)。
- kubernetes.io/dockerconfigjson:专门用于存储Docker镜像仓库的登录凭证,需通过特定格式生成。
5. 存储管理:K8s的“数据持久化”
K8s不支持直接挂载本地目录,需通过“存储类→PV→PVC”的层级结构申请和使用存储资源,类比Java的“类→对象→属性赋值”:
- 存储类(StorageClass):定义存储类型(如阿里云SSD、高效云盘),由云厂商预置或自定义,相当于“存储模板”。
- PV(PersistentVolume):集群级别的存储资源,相当于“存储实例”,定义存储容量、访问模式(如ReadWriteOnce仅单节点读写)、存储类型。
- PVC(PersistentVolumeClaim):Pod对存储资源的“申请单”,绑定到指定PV后,Pod即可挂载使用,实现数据持久化。
二、前置环境准备:迁移前的“基础设施搭建”
迁移前需完成跳板机配置、K8s客户端、Docker环境、Jenkins部署等基础准备,确保后续操作顺畅。
1. 跳板机配置:云原生操作的“桥梁”
由于云环境服务多部署在内网,需通过跳板机(具备内网访问权限的服务器)操作ACR、ACK、Jenkins等服务,核心配置步骤:
- 申请跳板机:需开通与GitLab、ACR、ACK的网络通信权限(防火墙放行对应IP和端口),建议同时放行Maven/Gradle镜像仓库(如阿里云镜像),加速依赖拉取。
-
安装必备工具:
# 安装Docker(用于构建镜像) curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun # 安装kubectl(K8s客户端) curl -LO "https://dl.k8s.io/release/$(curl -LSs https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl # 安装Git(拉取代码) yum install -y git
2. K8s环境配置:连接ACK集群
- 获取KubeConfig:登录阿里云ACK控制台,在集群详情页下载KubeConfig文件(包含集群访问凭证)。
-
配置kubectl:
# 创建.kube目录 mkdir -p ~/.kube # 将下载的KubeConfig文件内容复制到config文件 vim ~/.kube/config # 验证连接(查看集群节点) kubectl get nodes
3. Docker与ACR配置:镜像的“构建与存储”
-
登录ACR:在ACR控制台获取登录命令(含仓库地址、用户名、密码),执行登录:
# ACR登录命令(阿里云控制台复制) docker login --username=xxx registry.cn-hangzhou.aliyuncs.com # 输入密码后登录成功,后续可推送/拉取镜像 - 配置镜像加速器:为避免Docker拉取镜像缓慢,配置阿里云镜像加速器(控制台获取专属加速器地址):
tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://xxx.mirror.aliyuncs.com"] } EOF systemctl daemon-reload && systemctl restart docker
4. Jenkins部署:自动化流水线的“核心引擎”
采用Docker Compose部署Jenkins,确保具备Docker、kubectl、Maven等工具权限:
# docker-compose.yml
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts-jdk17 # 适配Java项目的JDK版本
container_name: jenkins-cloud
restart: always
privileged: true
networks:
- jenkins-net
environment:
- JAVA_OPTS=-Duser.timezone=Asia/Shanghai # 配置时区
- MAVEN_OPTS=-Dmaven.repo.local=/root/.m2/repository
volumes:
- /data/jenkins/data:/var/jenkins_home # Jenkins数据持久化
- /data/maven/repository:/root/.m2/repository # Maven仓库共享
- /var/run/docker.sock:/var/run/docker.sock # 挂载Docker套接字
- /usr/bin/docker:/usr/bin/docker # 映射Docker命令
- /usr/local/bin/kubectl:/usr/local/bin/kubectl # 映射kubectl命令
- /root/.kube:/root/.kube # 映射K8s配置
- /data/jenkins/.npm:/root/.npm # npm缓存
ports:
- "8088:8080" # Jenkins Web端口
- "50000:50000" # 代理通信端口
user: root # 以root用户运行,避免权限不足
networks:
jenkins-net:
driver: bridge
-
启动Jenkins:
docker-compose up -d # 查看初始密码(用于首次登录) docker logs jenkins-cloud 2>&1 | grep "initialAdminPassword" - 安装必备插件:
- 代码管理:Git Plugin、Git Client Plugin
- 构建工具:Maven Integration Plugin、Gradle Plugin
- 容器相关:Docker Plugin、Docker Pipeline
- K8s相关:Kubernetes Plugin
- 流水线:Pipeline Plugin、Pipeline Utility Steps
- 通知工具:Email Extension Plugin、DingTalk Plugin
三、迁移部署全流程:从代码到云原生服务
迁移核心流程:代码改造→镜像构建→K8s资源配置→部署验证,每个环节需严格遵循云原生规范。
1. 代码改造:适配云原生环境
- 配置外置:将应用配置(如数据库连接、服务端口)抽离为外部配置,通过ConfigMap/Secret注入,避免硬编码。
- 无状态改造:若服务为有状态(如本地存储数据),需改为数据持久化到云存储(如阿里云RDS、NAS),确保Pod重建后数据不丢失。
- 健康检查:添加Spring Boot Actuator端点(
/actuator/health),用于K8s的存活探针(livenessProbe)和就绪探针(readinessProbe)。
2. 镜像构建:Dockerfile编写与推送
编写适配Java项目的Dockerfile(以Spring Boot为例):
# 基础镜像(轻量JDK镜像)
FROM openjdk:17-jdk-slim
# 维护者信息
LABEL maintainer="dev@example.com"
# 暴露服务端口
EXPOSE 8080
# 挂载日志目录
VOLUME /tmp/logs
# 复制jar包(Jenkins构建产物)
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
# 启动命令(支持传入环境变量)
ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=${SPRING_PROFILES_ACTIVE}"]
-
构建并推送镜像(Jenkins流水线中执行):
# 定义镜像标签(建议包含版本号,避免覆盖) IMAGE_TAG=registry.cn-hangzhou.aliyuncs.com/dev-namespace/user-service:v1.0.0 # 构建镜像 docker build -t ${IMAGE_TAG} --build-arg JAR_FILE=target/user-service.jar . # 推送镜像到ACR docker push ${IMAGE_TAG}
3. K8s资源配置:完整YAML示例
(1)ConfigMap:应用配置
apiVersion: v1
kind: ConfigMap
metadata:
name: user-service-config
namespace: dev
data:
application-dev.yml: |
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://rm-xxx.mysql.aliyuncs.com:3306/user_db
username: ${DB_USERNAME} # 敏感信息从Secret注入
cloud:
nacos:
server-addr: nacos-service.dev.svc.cluster.local:8848
(2)Secret:敏感信息
apiVersion: v1
kind: Secret
metadata:
name: user-service-secret
namespace: dev
type: Opaque
data:
# 需Base64编码(echo -n "xxx" | base64)
DB_USERNAME: dXNlcjE2OA==
DB_PASSWORD: cGFzc3dvcmQxMjM=
ACR_USERNAME: YWNyX3VzZXI=
ACR_PASSWORD: YWNyX3Bhc3N3b3Jk
(3)PV/PVC:数据持久化(日志存储)
# PV(集群级存储资源)
apiVersion: v1
kind: PersistentVolume
metadata:
name: user-service-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce # 仅单节点读写
persistentVolumeReclaimPolicy: Retain # 回收策略:保留数据
storageClassName: alicloud-disk-ssd # 存储类型(阿里云SSD)
csi:
driver: diskplugin.csi.alibabacloud.com
volumeHandle: d-xxx # 阿里云磁盘ID(控制台获取)
---
# PVC(应用存储申请)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: user-service-pvc
namespace: dev
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: alicloud-disk-ssd
volumeName: user-service-pv
(4)Deployment:无状态服务部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: dev
labels:
app: user-service
spec:
replicas: 2 # 2个副本,保证高可用
selector:
matchLabels:
app: user-service
strategy:
rollingUpdate:
maxSurge: 1 # 滚动更新时最大可新增副本数
maxUnavailable: 0 # 滚动更新时最大不可用副本数
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: registry.cn-hangzhou.aliyuncs.com/dev-namespace/user-service:v1.0.0
resources:
limits:
cpu: "1"
memory: "1Gi"
requests:
cpu: "500m"
memory: "512Mi"
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "dev"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: user-service-secret
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: user-service-secret
key: DB_PASSWORD
volumeMounts:
- name: config-volume
mountPath: /config # 挂载ConfigMap到容器目录
- name: log-volume
mountPath: /tmp/logs # 挂载PVC存储日志
# 存活探针(检查服务是否运行)
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60 # 启动后60秒开始检查
periodSeconds: 10 # 每10秒检查一次
# 就绪探针(检查服务是否可用)
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
volumes:
- name: config-volume
configMap:
name: user-service-config
items:
- key: application-dev.yml
path: application.yml
- name: log-volume
persistentVolumeClaim:
claimName: user-service-pvc
# 镜像拉取密钥(从ACR拉取镜像时使用)
imagePullSecrets:
- name: acr-secret
(5)Service:服务访问入口
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: dev
spec:
selector:
app: user-service
ports:
- port: 80
targetPort: 8080
type: LoadBalancer # 生产环境使用,分配公网IP
(6)Ingress:路由配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: user-service-ingress
namespace: dev
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2 # 路径重写
nginx.ingress.kubernetes.io/ssl-redirect: "false" # 测试环境关闭HTTPS
spec:
ingressClassName: nginx
rules:
- host: api-dev.example.com
http:
paths:
- path: /user(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: user-service
port:
number: 80
4. 部署验证:确保服务正常运行
# 查看Deployment状态
kubectl get deployments -n dev
# 查看Pod状态
kubectl get pods -n dev -l app=user-service
# 查看Service(获取LoadBalancer IP)
kubectl get svc -n dev user-service
# 查看Ingress
kubectl get ingress -n dev
# 测试服务访问(通过Ingress域名)
curl http://api-dev.example.com/user/health
四、Jenkins自动化流水线:实现一键部署
通过Jenkins Pipeline实现“拉取代码→构建打包→镜像构建推送→K8s部署”全自动化,支持参数化构建和多环境部署。
1. 凭据管理:统一管理敏感信息
在Jenkins中配置以下凭据(Credentials → System → Global credentials):
- Git凭据:Username with password(GitLab账号密码)。
- ACR凭据:Username with password(ACR登录账号密码)。
- K8s凭据:Secret file(KubeConfig文件)。
- Maven凭据:Secret file(Maven settings.xml,含私服配置)。
2. 流水线脚本(Jenkinsfile)
pipeline {
agent any
tools {
maven 'Maven-3.9' # 全局工具配置中定义的Maven名称
jdk 'JDK-17' # 全局工具配置中定义的JDK名称
}
parameters {
// 参数化构建:选择部署环境
choice(name: 'ENV', choices: ['dev', 'test', 'prod'], description: '部署环境')
// 参数化构建:镜像版本号
string(name: 'IMAGE_VERSION', defaultValue: 'v1.0.0', description: '镜像版本号')
// 参数化构建:Pod副本数
integer(name: 'REPLICAS', defaultValue: 2, description: 'Pod副本数')
}
environment {
// 环境变量(按部署环境动态切换)
ACR_REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
ACR_NAMESPACE = "${params.ENV == 'prod' ? 'prod-namespace' : 'dev-namespace'}"
APP_NAME = 'user-service'
GIT_URL = 'https://gitlab.example.com/dev/user-service.git'
GIT_BRANCH = "${params.ENV == 'prod' ? 'main' : 'develop'}"
KUBECONFIG_CRED_ID = "${params.ENV == 'prod' ? 'kubeconfig-prod' : 'kubeconfig-dev'}"
FULL_IMAGE_NAME = "${ACR_REGISTRY}/${ACR_NAMESPACE}/${APP_NAME}:${params.IMAGE_VERSION}"
}
stages {
stage('Checkout Code') {
steps {
echo "拉取代码:${GIT_URL},分支:${GIT_BRANCH}"
git credentialsId: 'git-credentials', url: GIT_URL, branch: GIT_BRANCH
}
}
stage('Maven Build') {
steps {
echo "Maven打包,跳过测试"
withCredentials([file(credentialsId: 'maven-settings', variable: 'MVN_SETTINGS')]) {
sh """
mvn -s ${MVN_SETTINGS} clean package \
-Dmaven.test.skip=true \
-Dspring.profiles.active=${params.ENV}
"""
}
}
}
stage('Build & Push Image') {
steps {
echo "构建镜像:${FULL_IMAGE_NAME}"
withCredentials([usernamePassword(
credentialsId: 'acr-credentials',
usernameVariable: 'ACR_USER',
passwordVariable: 'ACR_PWD'
)]) {
sh """
# 登录ACR
docker login -u ${ACR_USER} -p ${ACR_PWD} ${ACR_REGISTRY}
# 构建镜像
docker build -t ${FULL_IMAGE_NAME} --build-arg JAR_FILE=target/${APP_NAME}.jar .
# 推送镜像
docker push ${FULL_IMAGE_NAME}
# 登出ACR
docker logout ${ACR_REGISTRY}
"""
}
}
}
stage('Deploy to K8s') {
steps {
echo "部署到K8s ${params.ENV}环境,副本数:${params.REPLICAS}"
withCredentials([file(credentialsId: "${KUBECONFIG_CRED_ID}", variable: 'KUBECONFIG_FILE')]) {
sh """
# 设置K8s配置文件
export KUBECONFIG=${KUBECONFIG_FILE}
# 替换K8s配置文件中的镜像版本和副本数
sed -i "s#IMAGE_VERSION#${params.IMAGE_VERSION}#g" k8s/${params.ENV}/deployment.yaml
sed -i "s#REPLICAS#${params.REPLICAS}#g" k8s/${params.ENV}/deployment.yaml
# 部署K8s资源
kubectl apply -f k8s/${params.ENV}/configmap.yaml
kubectl apply -f k8s/${params.ENV}/secret.yaml
kubectl apply -f k8s/${params.ENV}/pv.yaml
kubectl apply -f k8s/${params.ENV}/pvc.yaml
kubectl apply -f k8s/${params.ENV}/deployment.yaml
kubectl apply -f k8s/${params.ENV}/service.yaml
kubectl apply -f k8s/${params.ENV}/ingress.yaml
# 等待部署完成(最多等待5分钟)
kubectl rollout status deployment/${APP_NAME} -n ${ACR_NAMESPACE} --timeout=5m
"""
}
}
}
stage('Verify Deployment') {
steps {
echo "验证${params.ENV}环境部署结果"
withCredentials([file(credentialsId: "${KUBECONFIG_CRED_ID}", variable: 'KUBECONFIG_FILE')]) {
sh """
export KUBECONFIG=${KUBECONFIG_FILE}
# 查看Pod状态
kubectl get pods -n ${ACR_NAMESPACE} -l app=${APP_NAME}
# 测试服务健康状态
if [ "${params.ENV}" == "dev" ]; then
curl -s http://api-dev.example.com/${APP_NAME}/health | jq
elif [ "${params.ENV}" == "test" ]; then
curl -s http://api-test.example.com/${APP_NAME}/health | jq
else
curl -s https://api.example.com/${APP_NAME}/health | jq
fi
"""
}
}
}
}
post {
success {
// 部署成功通知(钉钉/邮件)
dingtalkSend(
robot: 'dingtalk-robot',
type: 'MARKDOWN',
title: "【${APP_NAME}】${params.ENV}环境部署成功",
text: "### 部署信息\n- 环境:${params.ENV}\n- 版本:${params.IMAGE_VERSION}\n- 副本数:${params.REPLICAS}\n- 状态:成功"
)
}
failure {
// 部署失败通知
dingtalkSend(
robot: 'dingtalk-robot',
type: 'MARKDOWN',
title: "【${APP_NAME}】${params.ENV}环境部署失败",
text: "### 部署信息\n- 环境:${params.ENV}\n- 版本:${params.IMAGE_VERSION}\n- 状态:失败\n- 查看日志:${BUILD_URL}"
)
}
always {
// 清理工作空间
cleanWs()
}
}
}
3. 流水线配置:关联Jenkinsfile
- 创建流水线项目:Jenkins → New Item → Pipeline。
- 配置流水线来源:选择“Pipeline script from SCM”,关联Git仓库和Jenkinsfile路径。
- 触发方式:支持手动触发、GitLab WebHook触发(代码提交后自动构建)。
五、常见踩坑指南:避坑与解决方案
1. 网络问题
- 问题1:跳板机无法访问ACK集群。
解决方案:检查防火墙规则,确保跳板机IP在ACK集群的公网白名单中;验证KubeConfig文件的集群地址和端口是否正确。 - 问题2:Pod无法拉取ACR镜像。
解决方案:确认imagePullSecrets配置正确;检查ACR仓库的权限(是否允许ACK集群访问);验证镜像标签是否正确。
2. 权限问题
- 问题1:Jenkins无Docker权限。
解决方案:确保Jenkins容器以root用户运行;挂载Docker套接字和Docker命令;在宿主机执行chmod 666 /var/run/docker.sock。 - 问题2:K8s Pod权限不足(如无法挂载存储)。
解决方案:检查PV/PVC的访问模式是否匹配;确认存储类是否支持当前集群;检查Pod的serviceAccount是否具备相关权限。
3. 配置问题
- 问题1:应用无法读取ConfigMap/Secret。
解决方案:检查ConfigMap/Secret的命名空间是否与Pod一致;验证挂载路径是否正确;确认配置文件格式是否符合应用要求。 - 问题2:镜像版本冲突(覆盖已有镜像)。
解决方案:使用语义化版本号(如v1.0.0、v1.0.1),避免使用latest标签;在流水线中通过参数化构建指定版本号。
4. 部署问题
- 问题1:Pod启动失败(CrashLoopBackOff)。
解决方案:查看Pod日志(kubectl logs -n dev <pod-name>);检查应用配置(如数据库连接);验证镜像是否包含必要的依赖。 - 问题2:滚动更新失败。
解决方案:调整Deployment的滚动更新策略(maxSurge、maxUnavailable);确保应用支持滚动更新(无状态);检查资源限制是否合理。
六、总结与优化建议
从传统私有服务器迁移到云原生环境,不仅是部署模式的转变,更是开发思维的升级。通过ACK实现服务的高可用、可扩展部署,通过Jenkins实现自动化流水线,大幅减少人工操作,提升部署效率和稳定性。
后续优化方向
- 监控告警:集成Prometheus+Grafana监控Pod、Service、Ingress的运行状态;配置告警规则(如Pod异常重启、CPU使用率过高)。
- 日志收集:集成ELK Stack(Elasticsearch+Logstash+Kibana)收集Pod日志,便于问题排查。
- CI/CD优化:引入GitOps理念(如ArgoCD),实现基于Git的声明式部署;添加镜像扫描(如Trivy),确保镜像安全。
- 多环境隔离:通过命名空间、网络策略、资源配额实现开发、测试、生产环境的严格隔离,避免相互影响。
云原生技术看似复杂,但只要理清核心概念、遵循标准流程、规避常见陷阱,业务开发也能快速上手。希望本文能为更多“跨界”开发者提供参考,顺利完成传统项目的云原生迁移,享受自动化部署带来的效率提升。
除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接
文章评论