k8s部署SpringCloud微服务集群
一、前言
本文简要介绍 kubernetes(后文简称 k8s )的基本原理,随后重点介绍了基于 k8s 部署 SpringBoot 微服务应用集群的方法。
为了更好的演示 K8s 部署应用集群的过程,我们开发了一个 SpringCloud 微服务项目 springCloudK8sMall ,springCloudK8sMall 和部署方案都是采用最简化的处理,目的是为了帮助大家快速入门 k8s。
二、Kubernetes 简介
1. 什么是 k8s?
从官方的定义来说,Kubernetes就是云原生微服务应用的编排器。但是编排器这个感念似乎并不好理解,所以个人认为下面这个介绍更能帮助理解 k8s 的本质。
k8s 是云的操作系统,使用户不必关心应用是在谁的云或服务器上运行。就像Linux和Windows意味着用户不必关心应用是运行在戴尔还是IBM的服务器上一样。
k8s 帮我们解决服务器,存储,网络的调度(编排)问题,使我们能够专注于应用的部署。就类似于Linux或者windows帮我们解决的CPU,内存,IO的调度,使我们可以专注于应用的开发和使用。
2. 为什么我们需要 K8s 呢?
1)公司可以不必关心应用是在谁的云或服务器上运行,避免被某个云厂商或服务器厂商绑架;
2)简化部署流程,快速扩缩容,快速恢复环境:
在日常开发运维中,你是否遇到过以下场景“
- 项目一开始需要搭建开发环境,你登录每一台服务器安装JDK、filebeat、安装nginx、安装nacos集群...,花了两三天时间才把集群搭建好。
- 项目开发好了,进入SIT、UAT环节,又需要重复两遍遍上述环节,又花了几天;项目顺利发布上线,搭建PROD环境,这次服务器更多,又花了更多时间部署环境;
- 项目运行了一段时间,安全排查出filebeat或者nginx版本存在安全漏洞,然后又登录各个环境的各台服务器一个个升级中间件;
- 有时候在PROD改了某个中间件的配置,但是不确定是否同步到了其他环境,随着时间的推移,各个环境开始不一致,为项目稳定运行埋下了未知风险。
从上述过程可以看出了,每一个环境的搭建,我们都需要执行相同的过程,同时,很难方便的对比各个环境的差异。
k8s一键创建/销毁环境:如果使用 k8s 部署,则只需要为每个环境配置一组 yaml 文件,执行一行简单命令,即可在几分钟之内完成环境搭建或销毁。同时只需要对比不同环境的yaml文件,即可知道他们之间的差异。接下来,我们就通过 演示项目 springCloudK8sMall 看看如何通过k8s实现这种能力。
# 一键创建或者销毁环境
kubectl apply/delete -f ./deploy/
#每个环境配置一组部署配置文件
|--[dev/test/prod]/deploy/
|--mall-frontend-deployment.yaml
|--mall-gateway-deployment.yaml
|--nacos-statefulset.yaml
|--xxx.yaml
三、Kubernetes原理简述
1. k8s架构
Kubernetes 集群由控制平面和一个或多个工作节点组成。以下是主要组件的简要概述:
- 控制平面组件: 管理集群的整体状态(不重要,部署应用不需要和这些组件打交道,了解即可):
- kube-apiserver: 公开 Kubernetes HTTP API 的核心组件服务器;
- etcd: 具备一致性和高可用性的键值存储,用于所有 API 服务器的数据存储;
- kube-scheduler: 查找尚未绑定到节点的 Pod,并将每个 Pod 分配给合适的节点;
- kube-controller-manager: 运行控制器来实现 Kubernetes API 行为。
- cloud-controller-manager: 与底层云驱动集成
- Node 组件:在每个节点上运行,维护运行的 Pod 并提供 Kubernetes 运行时环境:
- kubelet: 确保 Pod 及其容器正常运行;
- kube-proxy: 维护节点上的网络规则以实现 Service 的功能;
- 容器运行时(Container runtime);
我们所有的应用和中间件都是运行在 node 组件的容器运行时中,通过kube-proxy向集群内外提供网络访问。
2. k8s的一些基本概念
k8s引入了许多概念,但是本文只简单介绍容器、Pod、Deployment、Service等四个核心概念,因为通过这四个内容,你就可以把你的微服务集群运行起来了。
1)容器
容器和虚拟机相比,它实际上是一种资源隔离的进程,运行在容器中的应用比独占一个虚拟机消耗的资源更少,启动速度更快。所谓的资源隔离主要只指进程隔离、文件隔离、网络隔离:
- 进程隔离:最基本的隔离就是进程之间看不到彼此,这是由Linux的Namespace机制实现的;
- 文件隔离:第二种隔离就是隔离系统真实的文件系统。Docker利用Linux的mount机制,给每个隔离进程挂载了一个虚拟的文件系统,使得一个隔离进程只能访问这个虚拟的文件系统,无法看到系统真实的文件系统;
- 网络隔离:第三种隔离就是网络协议栈的隔离;
一个容器进程本质上是一个运行在沙盒中的隔离进程,由Linux系统本身负责隔离,Docker只是提供了一系列工具,帮助我们设置好隔离环境后,启动这个进程。
2)Pod
上面我们已经提过了,所有的应用和中间件都是运行在容器运行时中,那么我们如何管理容器运行时呢?
其实就是通过Pod,Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。Pod 是一个或者一组容器的封装器;这些容器共享存储、网络、以及怎样运行这些容器的规约。可以把 Pod 简单理解为传统虚拟化部署中的虚拟机,只不过类似容器和虚拟机的差别,Pod占用的资源更小而已。
3)Deployment
一个 Deployment 为 Pod 提供声明式的更新能力,也就是服务更新和扩缩容的能力。
所谓的声明式,就是说用户可以通过 Deployment 声明 Pod 的期望状态,Deployment 会自动帮你把 Pod 保持在期望状态。而期望状态,一般是指你需要这个 Pod 有几个副本,如何更新,如何定义 Pod 的健康状态的信息。
4)Service
Kubernetes 网络模型由几个部分构成:
- 集群中的每个 Pod 都会获得自己的、独一无二的集群范围 IP 地址。
- Pod 网络(也称为集群网络)处理 Pod 之间的通信。它确保所有 Pod 可以与所有其他 Pod 进行通信, 无论它们是在同一个节点还是在不同的节点上。
- Service API 允许你为由一个或多个后端 Pod 实现的服务提供一个稳定(长效)的 IP 地址或主机名, 其中组成服务的各个 Pod 可以随时变化。
k8s网络模型有两个重要的特点:
- 对外界不可见;
- Pod之间可以互访,但却是不稳定的。集群会为每个Pod生成IP,但是Pod没有重启的概念,所谓服务重启就是销毁旧 Pod,创建新 Pod。在某些场景下,我们需要服务保持稳定的网络,比如注册中心,各个微服务需要配置固定的注册中心地址。
Service就是为了解决上述两个问题:
- 对集群外暴露访问入口;
- 对集群内提供稳定的服务访问地址;
四、Kubernetes安装
注:如果你已经有了k8s运行环境,可以跳过这部分
minikube是一个本地Kubernetes,它提供和k8s一样的交互命令,安装更加简单,占用的资源也更少,所以在学习环境,我们通过WMware虚拟机安装minikube进行学习。
环境及版本说明
- VMware:
- VMware® Workstation 16 Pro 16.2.4 build-20089737
- 操作系统:
- minikube:
- minikube v1.34.0
- Kubernetes v1.31.0
1. 安装docker
卸载docker
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
安装docker
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache fast
yum -y install docker-ce
参考文档:通过aliyun镜像安装docker
配置dockers容器镜像(xxxxx.mirror.aliyuncs.com地址自行到阿里云容器镜像服务——》镜像加速器获取)
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://xxxxx.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
2.安装kubectl
使用阿里云镜像安装kubectl,你可以按照以下步骤操作:
1) 添加阿里云的Kubernetes仓库。
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
2) 安装kubectl
yum install -y kubectl
#验证安装是否成功。
kubectl version --client
3.安装minikube
1) 获取官方最新版minikube
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
install minikube-linux-amd64 /usr/local/bin/minikube
2) 设置默认驱动
minikube config set driver docker
3) 启动minikube 启动一个k8s节点
minikube delete ; minikube start --force --base-image='registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.44'
#--force是以root身份启动的docker的必须选项
#--base-image为指定minikube start 采用的基础镜像,上面docker pull拉取了什么镜像,这里就改成什么镜像(不指定可能会报错)
4) 验证minikube
[root@minikube ~]# minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
5) 检查minikuke相关的资源是否正常启动(全部都是Running状态表示正常)
[root@minikube ~]# kubectl get pod,svc,deployment,rc -n kube-system
NAME READY STATUS RESTARTS AGE
pod/coredns-6f6b679f8f-6gdhm 1/1 Running 0 7m5s
pod/etcd-minikube 1/1 Running 0 7m11s
pod/kube-apiserver-minikube 1/1 Running 0 7m11s
pod/kube-controller-manager-minikube 1/1 Running 0 7m11s
pod/kube-proxy-f4vnw 1/1 Running 0 7m5s
pod/kube-scheduler-minikube 1/1 Running 0 7m11s
pod/storage-provisioner 1/1 Running 1 (6m47s ago) 7m9s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 7m10s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/coredns 1/1 1 1 7m10s
五、Kubernetes部署SpringCloud微服务应用集群
1. 演示项目 springCloudK8sMall 应用介绍
如下图所示,springCloudK8sMall 是前后端分离的 B/S 应用。前端是一个基于 Vue 3 和 Element Plus 的商城演示项目,展示了一个微服务架构的商城系统,包括用户管理、商品管理、订单管理等功能。后端是一个基于Spring Cloud的微服务商城应用,以nacos为注册中心,包含用户微服务、商品微服务、订单微服务、所有后端微服务通过网关微服务向外暴露接口。
2. 镜像管理
1) 打包镜像
我们知道,k8s是管理容器应用的编排器,所以第一步就是要打包镜像,即应用容器化。
前提说明:从2024年7月起阿里云镜像加速器调整了 规则 ,只能在阿里云服务器使用阿里云镜像加速器。我使用的方式是购买了一台基础版的云服务器,用于打包镜像,打包后推动到免费的 阿里云容器镜像中心 ,再从本地虚拟机拉取镜像,运行容器。
下面,以前端工程为例,在工程根目录编写如下 Dockerfile 文件。
# Dockerfile
# 使用 Node.js 17 作为基础镜像
FROM node:17 as build-stage
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 package-lock.json (如果存在)
COPY package*.json ./
# 安装项目依赖
RUN npm install
# 复制项目文件和文件夹到工作目录
COPY . .
# 构建应用
RUN npm run build
# 生产阶段
FROM nginx:stable-alpine as production-stage
# 从构建阶段复制构建好的文件到 nginx
COPY --from=build-stage /app/dist /usr/share/nginx/html
# 暴露 80 端口
EXPOSE 80
# 启动 nginx
CMD ["nginx", "-g", "daemon off;"]
执行 docker build 命令完成镜像打包
docker build -t mall-frontend .
执行 docker images 命令可以查看镜像信息
[root@aliyun mall]# docker images | grep mall-frontend
mall-frontend latest de465df980c9 2 weeks ago 29.7MB
现在我们可以直接在本地运行 Docker 容器,验证镜像是否可以正常执行
# 运行容器
docker run -d -p 18080:80 mall-frontend
# 查看容器运行状态
[root@aliyun mall]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
66340f2a68f7 mall-frontend "/docker-entrypoint.…" 5 seconds ago Up 3 seconds 0.0.0.0:18080->80/tcp, :::18080->80/tcp awesome_swartz
# 测试访问
curl http://localhost:18080
2) 使用阿里云镜像中心管理镜像
1、免费申请试用阿里云镜像服务
登录 阿里云镜像服务 申请免费试用,进入 命名空间 创建一个自己的命名空间就可以开始使用了。
2、在打包镜像的服务器和运行k8s的服务器中登录镜像服务
# 登录阿里云Docker Registry,用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。
docker login --username=<username> registry.cn-hangzhou.aliyuncs.com
3、在打包镜像的服务器中将镜像推送到镜像中心
# 标记镜像(注意将推送地址更换为你自己的命名空间(namespace))
docker tag mall-frontend:latest registry.cn-hangzhou.aliyuncs.com/namsapce01/mall-frontend:latest
# 推送镜像
docker push registry.cn-hangzhou.aliyuncs.com/namsapce01/mall-frontend:latest
4、在运行k8s的服务器中拉取镜像
# 拉取镜像
docker pull registry.cn-hangzhou.aliyuncs.com/namsapce01/mall-frontend:latest
5、从docker加载镜像到minikube
上述拉取镜像的动作只是将镜像拉取到了本地docker中,由于我们是使用minikube运行k8s集群,因此还需要将镜像加载到minikube中
# 加载镜像到minikube
[root@minikube ~]# minikube image load registry.cn-hangzhou.aliyuncs.com/namsapce01/qsk-book:1.1
[root@minikube ~]# minikube image ls
registry.cn-hangzhou.aliyuncs.com/namsapce01/qsk-book:1.1
3. 小结
我们以前端工程为例介绍了镜像打包和镜像管理的全过程,后端工程镜像打包和管理唯一的差异点就是Dockerfile文件不同,这里不再赘述,大家可以自行查看对于的Dockerfile文件。
2. k8s部署前端
1. 定义 Pod
如前所述,Pod 是K8s中应用执行的最小单位,Pod 的作用即是封装容器。
因此,要在k8s中部署应用,第一步就是要定义应用的Pod。如下所示,Pod 的定义文件由两部分组成,spec 之前定义了诸如 API 版本、资源类型、Pod 名称等基本信息,spec 定义了容器的信息。
# mall-frontend-pod.yaml
apiVersion: v1 # 定义K8s API的版本
kind: Pod # 定义创建资源的类型
metadata:
name: mall-frontend-pod # POD的名称
spec: # 规格说明,定义容器信息
containers:
- name: mall-frontend-pod
image: registry.cn-hangzhou.aliyuncs.com/namsapce01/mall-frontend:latest # 镜像 这里的namespace01需要替换为自己的命名空间
imagePullPolicy: IfNotPresent # 镜像拉取策略:Always、Never、IfNotPresent
ports:
- containerPort: 80
定义好 Pod 对应的 yaml 文件之后,只需要执行 kubectl apply 命令,即可在 k8s 集群中运行对应的 Pod。Pod 的状态为 Running 即说明已经成功运行 Pod,不过此时的 Pod 只能在K8s集群内访问,并不能在集群外访问。
# 运行Pod
[root@minikube deploy]# kubectl apply -f mall-frontend-pod.yaml
pod/mall-frontend-pod created
查看POD状态
[root@minikube deploy]# kubectl get pods
NAME READY STATUS RESTARTS AGE
mall-frontend-pod 1/1 Running 0 10s
2.使用 Deployment 管理一组 Pod
在上一节我们了解了如何定义并运行一个 Pod,但在实际运维中,我们并不会这样直接运行一个 Pod。因为 Pod 实际上是一个临时资源,你不应该期待单个 Pod 既可靠又耐用。
在实际应用中,k8s 是通过 Deployment 管理一组 Pod,通过 Deployment 定义一组 Pod 的期望状态,K8s 会自动帮忙实现并保持在这个期望状态。
所谓 Pod 的期望状态,即一个Pod 运行几个副本,如何更新等状态。
下面这个文件定义了前端项目的 Deployment 文件,从文件中可以看到两个关键信息:
- replicas: 定义 Pod 的副本数,即有2个 mall-frontend 的 Pod 运行;
- selector: 选择器,这是一个k8s中很关键的概念,这里指定这个 Deployment 需要管理的 Pod,这里即表示管理的是 app=mall-frontend 的 Pod。
# mall-frontend-deployment.yaml
apiVersion: apps/v1 # 定义使用的 Kubernetes API 版本
kind: Deployment # 定义要创建的资源类型
metadata: # 元数据
name: mall-frontend # Deployment 的名称
labels:
app: mall-frontend # 为 Deployment 添加标签
spec: # Deployment规格说明
replicas: 2 # 定义期望运行的副本数
selector: # 定义如何选择要管理的 Pod
matchLabels:
app: mall-frontend
template: # 定义 Pod 的模板
metadata: # Pod 的元数据
labels: # 为 Pod 添加标签
app: mall-frontend
spec: # Pod 的规格
containers: # 定义容器
- name: mall-frontend
image: registry.cn-hangzhou.aliyuncs.com/namsapce01/mall-frontend:latest # 镜像
imagePullPolicy: IfNotPresent # 镜像拉取策略:Always、Never、IfNotPresent
ports: # 定义容器端口
- containerPort: 80
同样的,我们执行 kubectl apply 即运行对应的 Deployment
# 创建Deployment
[root@minikube deploy]# kubectl apply -f mall-frontend-deployment.yaml
deployment.apps/mall-frontend created
# 查看Dployment
[root@minikube deploy]# kubectl get deploy -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
mall-frontend 2/2 2 2 13m mall-frontend registry.cn-hangzhou.aliyuncs.com/namsapce01/mall-frontend:latest app=mall-frontend
从Deployment的状态可以看出,DEADY为2/2,即表示期望的POD数量为2,当前成功运行的数量也为2。这时继续查看 Pod 的状态可以发现,Deployment 自动生成了两个 Pod。
[root@minikube deploy]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mall-frontend-6bb5768dbc-25jh8 1/1 Running 0 22m
mall-frontend-6bb5768dbc-vmqvj 1/1 Running 0 22m
为什么k8s特别强调了期望数量和实际数量呢?这就是k8s提供的强大的声明式的更新能力,我们只需要定义我们期望的状态,k8s会自动创建并保持在这个状态。
现在我们来看一下如果其中的一个 Pod 被删除或者意外宕机会发生什么?
# 删除POD mall-frontend-6bb5768dbc-25jh8
[root@minikube deploy]# kubectl delete pod mall-frontend-6bb5768dbc-25jh8
pod "mall-frontend-6bb5768dbc-25jh8" deleted
# 查看POD状态
[root@minikube deploy]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mall-frontend-6bb5768dbc-vmqvj 1/1 Running 0 23m
mall-frontend-6bb5768dbc-xf5jd 1/1 Running 0 4s
从上面的过程可以看到,k8s 提供的 Deployment 自动新建了一个 Pod,仍然将 Pod 的数量保持在我们期望的数量2。
到这里,前端服务就已经部署好了,但是目前这个服务只能在集群内访问,集群外还无法访问到这个服务。
3.使用Service暴露服务的入口
我们在前文中已经提过,Service主要解决两个问题:
- 对集群外暴露访问入口;
- 对集群内提供稳定的服务访问地址;
这里我们先来演示第一个能力,即对集群外暴露访问入口。
从mall-frontend-service.yaml我们看到两组关键信息:
- selector: 定义这个service对于的POD
- port: 定义如何映射端口
- targetPort: 对于POD中容器的端口
- port: Service 暴露的端口,我们可以在集群内通过Service IP + 这个端口访问POD的服务
- nodePort: 提供一个集群外端口,通过集群IP访问
# mall-frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mall-frontend-service # Service 的名称
spec: # Service 规格说明
selector: # 定义如何选择要暴露的 Pod
app: mall-frontend # 选择标签为 app: mall-frontend 的 Pod
ports:
# 定义单个端口映射
- protocol: TCP # 使用 TCP 协议
port: 80 # Service 暴露的端口
targetPort: 80 # Pod 中容器的端口
nodePort: 30080 # 提供一个集群外访问的端口,只能在30000-32767范围内
type: NodePort # 定义 Service 类型,通过每个节点上的 IP 和静态端口(NodePort)公开 Service。 为了让 Service 可通过节点端口访问,Kubernetes 会为 Service 配置集群 IP 地址, 相当于你请求了 type: ClusterIP 的 Service。
执行kubectl apply 命令创建Service
[root@minikube deploy]# kubectl apply -f mall-frontend-service.yaml
service/mall-frontend-service created
查看Service状态
[root@minikube deploy]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mall-frontend-service NodePort 10.102.36.9 <none> 80:30080/TCP 77m
现在我们就可以通过集群IP+service端口访问前端服务了
# 查看集群IP
[root@minikube deploy]# minikube ip
192.168.49.2
# 在集群外访问前端服务
[root@minikube deploy]# curl 192.168.49.2:30080
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>mall</title><script defer="defer" src="/js/chunk-vendors.8382f0a2.js"></script><script defer="defer" src="/js/app.0cafe880.js"></script><link href="/css/chunk-vendors.5c8b628d.css" rel="stylesheet"><link href="/css/app.db76374a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but mall doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
到这里,前端服务就全部部署完成了,但是由于我们的k8s是运行VMware虚拟机中的,如果我们要在外部的windows机器上访问服务,还需要建立一个代理服务:
# 在运行VMware的windows机器上执行以下命令
ssh -L {本地访问的端口}:{minikube的ip}:{service暴露的端口} {虚拟机(如CentOS、Debian、Ubuntu等)的用户名}@{虚拟机的ip}
# 如执行,则可以在windows机器上通过浏览器访问127.0.0.1:9000访问对应的服务
ssh -L 9000:192.168.49.2:30080 root@192.168.8.128
2. k8s部署后端微服务集群
后端微服务集群的部署和前端差异不大,主要的区别就是 nacos 注册中心不应该使用 Deployment 管理负载,而应该使用 StatefulSet 管理负载。
Deployment 和 StatefulSet 都是用于管理应用负载的,主要差别是 Deployment 主要用于管理无状态服务,StatefulSet 用于管理有状态服务。
有状态无状态:
- 无状态:不会对本地环境产生任何依赖。
- 有状态:会对本地环境(存储、网络等)产生依赖,会存储数据到本地内存、磁盘等,如redis,db;
由于我们使用的是 Nacos 本地 DB 缓存数据,所以应该使用 StatefulSet 部署。