在K3d中部署SpringBoot
在前面的文章中,我们学习了如何使用k3d在本地快速创建一个Kubernetes集群环境。作为一个轻量级的Kubernetes发行版,k3s为我们提供了简单高效的方式来学习和体验Kubernetes。
这次,我们将在k3d集群中部署一个基于SpringBoot的微服务应用,并通过Ingress暴露服务进行访问。让我们开始吧!
准备工作
如果你还没有搭建k3d环境,可以参考我的这篇文章 - 在K3d中部署Rancher
搭建k3d环境
sudo k3d cluster create hello-k3d \
--api-port 6550 \
-p "9080:80@loadbalancer" -p "9443:443@loadbalancer" \
--servers-memory 4g \
--agents-memory 4g \
--servers 1 \
--agents 2
上面的命令会创建一个包含1个server节点和2个agent节点的集群。同时,它将主机的9080端口映射到集群的80端口(HTTP),将9443端口映射到集群的443端口(HTTPS)。
接下来,我们需要将kubeconfig文件复制到当前用户的目录下,这样普通用户就可以执行kubectl命令了:
sudo cat /root/.kube/config > ~/.kube/config
首先,我们需要从Github上克隆微服务应用的代码仓库:
git clone https://github.com/imzhoukunqiang/springboot-k3s.git
cd springboot-k3s
然后切换到1.2.0版本的分支:
git checkout 1.2.0
创建Namespace
我们将在springboot这个namespace下部署所有组件:
kubectl apply -f yamls/namespace.yaml
部署微服务
接下来,使用kubectl命令一次性部署food、supermarket和digital三个微服务:
kubectl apply -f yamls/food.yaml
kubectl apply -f yamls/supermarket.yaml
kubectl apply -f yamls/digital.yaml
这些YAML文件中定义了Deployment、Service和Ingress等Kubernetes对象。每个微服务有两个副本,通过Service暴露80端口,并且通过Ingress将supermarket-svc映射到supermarket-127-0-0-1.nip.io这个域名上。
部署完成后,我们可以通过下面的命令查看pods的运行状态:
kubectl get pods -n springboot
访问应用
最后,打开浏览器访问 http://supermarket-127-0-0-1.nip.io:9080,即可看到supermarket微服务的Web页面了。
通过上面的步骤,我们成功地将一个完整的SpringBoot微服务应用部署到了k3d集群中,并使用Ingress暴露了服务。在本地环境中,我们就可以方便地开发、测试和调试基于Kubernetes的应用程序了。
应用程序逻辑
这个SpringBoot应用由三个微服务组成:
Supermarket
Supermarket是一个聚合服务,它从digital和food两个服务中获取产品数据,并在”/shopping”路径下渲染成一个产品列表页面:
@RequestMapping("/shopping")
public ModelAndView shopping() {
ModelAndView modelAndView = new ModelAndView("shopping");
Map<String, List<Product>> productMap = loadProductMap();
modelAndView.addObject("productMap", productMap);
return modelAndView;
}
private Map<String, List<Product>> loadProductMap() {
LinkedHashMap<String, List<Product>> map = new LinkedHashMap<>();
map.put("手机数码", digitalRemote.getProductList());
map.put("零食饮料", foodRemote.getProductList());
return map;
}
Food
Food服务提供了一个”/products”接口,返回一个零食饮料类的产品列表:
@RequestMapping("/products")
public List<Product> products() {
ArrayList<Product> list = new ArrayList<>();
list.add(new Product("百事可乐", 3, RNG.nextInt(5, 500)));
list.add(new Product("风干牛肉", 49.9, RNG.nextInt(3, 300)));
list.add(new Product("去骨鸡爪", 38.9, RNG.nextInt(1, 300)));
list.add(new Product("乐事薯片", 7.8, RNG.nextInt(1, 300)));
list.add(new Product("恰恰瓜子", 12.5, RNG.nextInt(1, 300)));
list.add(new Product("雀巢咖啡", 32.9, RNG.nextInt(1, 300)));
return list;
}
Digital
Digital服务提供了一个”/products”接口,返回一个手机数码类的产品列表:
@RequestMapping("/products")
public List<Product> products() {
ArrayList<Product> list = new ArrayList<>();
list.add(new Product("iPhone 15", 5999, RNG.nextInt(5, 500)));
list.add(new Product("Xiaomi 14", 3799, RNG.nextInt(3, 300)));
list.add(new Product("Huawei Mate 60 Pro", 7999, RNG.nextInt(1, 300)));
return list;
}
微服务是如何治理的?
food
、digital
和supermarket
三个微服务我们分别部署了2个副本。- 我们还定义了 Service 资源对象,用于为每个微服务创建一个分布式的、集群内部可访问的入口地址。比如
food-svc
、supermarket-svc
和digital-svc
。 - Kubernetes 集群为我们提供了一个扁平的、全局可访问的服务地址空间。也就是说,集群内的任何一个 Pod,都可以通过其他服务的名称直接访问它们。
我们在 supermarket 服务中,定义了下面的环境变量。
env: # 环境变量
- name: server.port
value: '80'
- name: remote.foodUrl
value: 'http://food-svc'
- name: remote.digitalUrl
value: 'http://digital-svc'
其中:
server.port
定义了supermarket服务自身监听的端口号为80。remote.foodUrl
和remote.digitalUrl
则指定了food服务和digital服务的访问地址。
这里的food-svc
和digital-svc
是两个服务在Kubernetes集群中的服务名称,通过DNS服务发现机制,它们会被自动解析为相应服务的ClusterIP地址。
我们再看下food-svc是怎么定义和工作的
apiVersion: v1
kind: Service
metadata:
name: food-svc
namespace: springboot
spec:
selector:
app: food
ports:
- name: tcp-80
port: 80
protocol: TCP
targetPort: 80
sessionAffinity: None
type: ClusterIP
当我们在集群中创建了这个Service后,它会自动通过标签选择器发现所有具有app=food
标签的Pods,并为它们提供负载均衡和反向代理功能。
任何发送到food-svc
这个cluster IP的请求,都会被自动分发到关联Pod的80端口上。比如超市服务supermarket就可以通过访问http://food-svc
来向food服务发起远程调用。
由于type设置为ClusterIP,所以这个Service只有集群内部可访问,集群外是无法直接访问的。如果需要暴露给外部客户端,我们需要额外创建一个Ingress或NodePort等资源对象。
That’s all, bye!