微服务Microservices
历史与背景
从2016 年和 2017 开始,微服务技术迅猛普及,Spring Cloud
为代表的传统侵入式开发框架,占据着微服务市场的主流地位,在golang
领域,微服务框架也蓬勃发展,产生了众多成熟的微服务框架,包括go-zero, go-kratos,go-micro,go-kit
描述
微服务(
Microservices
)是一种架构风格一个大型复杂软件应用由一个或多个微服务组成
系统中的各个微服务可被独立部署,各个微服务之间是松耦合的
每个微服务仅关注于完成一件任务并很好地完成该任务
在所有情况下,每个任务代表着一个小的业务能力
组件与组成概念
- 服务注册:服务提供方将自己调用地址注册到服务注册中心(采用
consul, etcd
等),让服务调用方能够方便地找到自己 - 服务发现:服务调用方从服务注册中心找到自己需要调用的服务的地址
- 负载均衡:服务提供方一般以多实例的形式提供服务,负载均衡功能能够让服务调用方连接到合适的服务节点
- 服务网关:服务网关是服务调用的唯一入口,可以在这个组件是实现用户鉴权、动态路由、灰度发布、A/B 测试、负载限流等功能
- 配置中心:将本地化的配置信息(
properties, xml, yaml
等)注册到配置中心,实现程序包在开发、测试、生产环境的无差别性,方便程序包的迁移 API
管理:以方便的形式编写及更新API
文档,并以方便的形式供调用者查看和测试- 集成框架:微服务组件都以职责单一的程序包对外提供服务,集成框架以配置的形式将所有微服务组件(特别是管理端组件)集成到统一的界面框架下,让用户能够在统一的界面中使用系统
- 分布式事务:对于重要的业务,需要通过分布式事务技术(
TCC
、高可用消息服务、最大努力通知)保证数据的一致性 - 调用链:记录完成一个业务逻辑时调用到的微服务,并将这种串行或并行的调用关系展示出来
- 支撑平台:系统微服务化后,系统变得更加碎片化,系统的部署、运维、监控等都比单体架构更加复杂,那么,就需要将大部分的工作自动化
优点
- 降低系统复杂度:每个服务都比较简单,只关注于一个业务功能
- 松耦合:微服务架构方式是松耦合的,每个微服务可由不同团队独立开发,互不影响
- 跨语言:只要符合服务
API
契约,开发人员可以自由选择开发技术,这就意味着开发人员可以采用新技术编写或重构服务,由于服务相对较小,所以这并不会对整体应用造成太大影响 - 独立部署:微服务架构可以使每个微服务独立部署。开发人员无需协调对服务升级或更改的部署。这些更改可以在测试通过后立即部署。所以微服务架构也使得
CI/CD
成为可能
缺点
- 缺乏统一的标准:业务逻辑应该按照什么规则划分为微服务,这本身就是一个经验工程,微服务的目标是充分分解应用程序,以促进敏捷开发和持续集成部署
- 微服务的分布式特点带来的复杂性:开发人员需要基于
RPC
或者消息实现微服务之间的调用和通信,而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手 - 分区的数据库体系和分布式事务:更新多个业务实体的业务交易相当普遍,不同服务可能拥有不同的数据库,
CAP
原理的约束,使得我们不得不放弃传统的强一致性,而转而追求最终一致性,这个对开发人员来说是一个挑战 - 测试挑战:传统的单体
WEB
应用只需测试单一的REST API
即可,而对微服务进行测试,需要启动它依赖的所有其他服务,这种复杂性不可低估 - 跨多个服务的更改:比如在传统单体应用中,若有
A、B、C
三个服务需要更改,A
依赖B
,B
依赖C
,我们只需更改相应的模块,然后一次性部署即可,但是在微服务架构中,我们需要仔细规划和协调每个服务的变更部署。我们需要先更新C
,然后更新B
,最后更新A
- 部署复杂:微服务由不同的大量服务构成,每种服务可能拥有自己的配置、应用实例数量以及基础服务地址,这里就需要不同的配置、部署、扩展和监控组件,此外,我们还需要服务发现机制,以便服务可以发现与其通信的其他服务的地址,因此,成功部署微服务应用需要开发人员有更好地部署策略和高度自动化的水平
服务网格Service Mesh
背景
2017 年底,非侵入式的 Service Mesh
技术从萌芽到走向了成熟
服务网格Service Mesh
是处理服务间通信的基础设施层。它负责构成现代云原生应用程序的复杂服务拓扑来可靠地交付请求
在实践中,Service Mesh
通常以轻量级网络代理阵列的形式实现,这些代理与应用程序代码部署在一起,对应用程序来说无需感知代理的存在
Istio
是第二代Service Mesh
的明星项目,由Google、IBM、Lyft
联合发布
描述
是微服务之间位于应用层下面的一层,负责微服务之间的注册发现,网络调用,限流,熔断等
特点
- 应用程序间通讯的中间层
- 轻量级网络代理
- 应用程序无感知
- 解耦应用程序的重试/超时、监控、追踪和服务发现
特性
- 流量管理:控制服务间的流量和
API
调用流,使调用更可靠,增强不同环境下的网络鲁棒性 - 可观测性:了解服务之间的依赖关系和它们之间的性质和流量,提供快速识别定位问题的能力
- 策略实施:通过配置
mesh
而不是以改变代码的方式来控制服务之间的访问策略 - 服务识别和安全:提供在
mesh
里的服务可识别性和安全性保护
Istio
架构
分为控制平面和数据平面
数据平面:由一组智能代理
Envoy
以sidecar
模式部署,协调和控制所有服务之间的网络通信- 通常是按照无状态目标设计的,但实际上为了提高流量转发性能,需要缓存一些数据,因此无状态也是有争议的
- 直接处理入站和出站数据包,转发、路由、健康检查、负载均衡、认证、鉴权、产生监控数据等
- 对应用来说透明,即可以做到无感知部署
控制平面:负责管理和配置代理路由流量,以及在运行时执行的政策
- 不直接解析数据包
- 与控制平面中的代理通信,下发策略和配置
- 负责网络行为的可视化
- 通常提供
API
或者命令行工具可用于配置版本化管理,便于持续集成和部署
Envoy
sidecar
模式: 将应用程序的功能划分为单独的进程运行在同一个最小调度单元中(例如k8s
中的 Pod
)
Istio
使用 Envoy
代理的扩展版本,该代理是以 C++
开发的高性能代理,用于调解服务网格中所有服务的所有入站和出站流量
Envoy
代理被部署为服务的 sidecar
(即架构中Service
旁边的Proxy
),在逻辑上为服务增加了 Envoy
的许多内置特性,例如
- 动态服务发现
- 负载均衡
TLS
终端HTTP/2
与gRPC
代理- 熔断器
- 健康检查
- 基于百分比流量分割的分阶段发布
- 故障注入
- 丰富的指标
以 sidecar
模式部署,这允许 Istio
将大量关于流量行为的信号作为属性提取出来,并发送给监控系统以提供有关整个服务网格的行为信息,Sidecar
代理模型还允许你将 Istio
功能添加到现有部署中,无需重新构建或重写代码
Pilot
Pilot
为 Envoy sidecar
提供服务发现、用于智能路由的流量管理功能(例如,A/B
测试、金丝雀发布等)以及弹性功能(超时、重试、熔断器等),将控制流量行为的高级路由规则转换为特定于环境的配置
Citadel
通过内置的身份和证书管理,可以支持强大的服务间以及最终用户的身份验证,可以使用 Citadel
来升级服务网格中的未加密流量,使用 Citadel
,operator
可以执行基于服务身份的策略,而不是相对不稳定的 3 层或 4 层网络标识
Galley
配置验证、提取、处理和分发组件,负责将其余的 Istio
组件与从底层平台(例如 Kubernetes
)获取用户配置的细节隔离开来
Istio Service Mesh
的实践
首先,需要有一套k8s
系统,可以采用rke2
或者k3s
搭建
下载安装Istio
(此处需要考虑国内网络访问问题)
$ curl -L https://istio.io/downloadIstio | sh -
$ curl -L https://istio.io/downloadIstio | sh -
转到 Istio
包目录,例如,如果包是 istio-1.19.3
$ cd istio-1.19.3
$ cd istio-1.19.3
安装目录包含
samples/
目录下的示例应用程序bin/
目录下的istioctl
客户端二进制文件
将 istioctl
客户端添加到路径(Linux
或 macOS
)
$ export PATH=$PWD/bin:$PATH
$ export PATH=$PWD/bin:$PATH
对于本次安装,我们采用 demo
配置组合。 选择它是因为它包含了一组专为测试准备的功能集合,另外还有用于生产或性能测试的配置组合。
$ istioctl install --set profile=demo -y
$ istioctl install --set profile=demo -y
给命名空间添加标签,指示 Istio
在部署应用的时候,自动注入 Envoy
边车代理
$ kubectl label namespace default istio-injection=enabled
namespace/default labeled
$ kubectl label namespace default istio-injection=enabled
namespace/default labeled
运行istio
自带的服务,快速验证集群配置与网络是否正常,如果后面一条命令输出<title>
标签,则表示集群正常
$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
$ kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- curl -sS productpage:9080/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
$ kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- curl -sS productpage:9080/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
使用gin
开发微服务
环境说明
实验设备IP
: 172.16.67.147
实验系统: Ubuntu22.04 amd64
k8s
可以默认使用containerd
作为容器运行时,这个和使用docker
不冲突,docker
也是使用containerd
作为容器运行时的
k8s
仍然可以直接拉取docker.io
的镜像
使用本地Dockerfile
构建镜像,推送到dockerhub
(或者企业自己搭建的harbor
仓库)之后,可以直接被k8s
拉取并使用
gin
代码
此处使用gin
开发一个web
服务,并构建镜像,并部署到k8s
创建一个文件夹gin-demo
,并创建一个gin-demo/main.go
文件写入如下,该服务会监听8080
端口,并返回打印当前时间和本机hostname
的返回
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"os/exec"
)
func main() {
r := gin.New()
r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
hostname, _ := exec.Command("hostname").CombinedOutput()
date, _ := exec.Command("date").CombinedOutput()
c.JSON(200, gin.H{
"message": fmt.Sprintf("%s pong at %s", string(date), string(hostname)),
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"os/exec"
)
func main() {
r := gin.New()
r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
hostname, _ := exec.Command("hostname").CombinedOutput()
date, _ := exec.Command("date").CombinedOutput()
c.JSON(200, gin.H{
"message": fmt.Sprintf("%s pong at %s", string(date), string(hostname)),
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
更新mod
依赖
$ cd gin-demo
$ go mod init
$ go mod tidy
$ cd gin-demo
$ go mod init
$ go mod tidy
阶段式构建Dockerfile
FROM golang:alpine as builder
WORKDIR /work
ADD go.mod .
ADD go.sum .
RUN go env -w GOPROXY=https://goproxy.cn,direct && go mod download
ADD main.go .
RUN go build -o gin-demo .
FROM alpine
WORKDIR /work
COPY --from=builder /work/gin-demo .
RUN chmod +x gin-demo
ENTRYPOINT ["./gin-demo"]
FROM golang:alpine as builder
WORKDIR /work
ADD go.mod .
ADD go.sum .
RUN go env -w GOPROXY=https://goproxy.cn,direct && go mod download
ADD main.go .
RUN go build -o gin-demo .
FROM alpine
WORKDIR /work
COPY --from=builder /work/gin-demo .
RUN chmod +x gin-demo
ENTRYPOINT ["./gin-demo"]
开始构建镜像gin-demo
下面采用的dockerhub
账号是2976560783
,所以构建的镜像也需要包含该信息
$ docker build -t 2976560783/gin-demo .
$ docker build -t 2976560783/gin-demo .
登录dockerhub
并推送该镜像
$ docker login
$ docker push 2976560783/gin-demo
$ docker login
$ docker push 2976560783/gin-demo
gin web
服务
构建k8s
的Deployment
资源yaml
配置文件gin-demo.yaml
需要注意修改下面image:
参数的值,配置3实例
apiVersion: apps/v1
kind: Deployment
metadata:
name: gin-demo
labels:
app: gin-demo
spec:
replicas: 3
selector:
matchLabels:
app: gin-demo
template:
metadata:
labels:
app: gin-demo
spec:
containers:
- name: gin-demo
image: 2976560783/gin-demo:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
protocol: TCP
apiVersion: apps/v1
kind: Deployment
metadata:
name: gin-demo
labels:
app: gin-demo
spec:
replicas: 3
selector:
matchLabels:
app: gin-demo
template:
metadata:
labels:
app: gin-demo
spec:
containers:
- name: gin-demo
image: 2976560783/gin-demo:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
protocol: TCP
应用该配置文件
$ kubectl apply -f gin-demo.yaml
$ kubectl apply -f gin-demo.yaml
查看部署状态
查看到READY
栏下面是2/2
,这个就是因为我们安装了istio
的原因,istio
会在每个服务pod
中安装一个Proxy
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
gin-demo-d4659f67c-4vgjc 2/2 Running 2 (5m4s ago) 15h 10.42.0.36 demo-server-node <none> <none>
gin-demo-d4659f67c-7zcn6 2/2 Running 2 (5m4s ago) 15h 10.42.0.40 demo-server-node <none> <none>
gin-demo-d4659f67c-j8xvn 2/2 Running 2 (5m4s ago) 15h 10.42.0.41 demo-server-node <none> <none>
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
gin-demo-d4659f67c-4vgjc 2/2 Running 2 (5m4s ago) 15h 10.42.0.36 demo-server-node <none> <none>
gin-demo-d4659f67c-7zcn6 2/2 Running 2 (5m4s ago) 15h 10.42.0.40 demo-server-node <none> <none>
gin-demo-d4659f67c-j8xvn 2/2 Running 2 (5m4s ago) 15h 10.42.0.41 demo-server-node <none> <none>
可以看到部署的pod
会产生几个IP
,是由虚拟网卡产生的设备内部的NAT IP
地址
测试k8s
设备机器访问服务(从其他设备是无法远程访问到10.42.0.24
的)
$ curl http://10.42.0.36:8080/ping
{"message":"Fri Oct 20 01:24:56 UTC 2023\n pong at gin-demo-d4659f67c-4vgjc\n"}
$ curl http://10.42.0.36:8080/ping
{"message":"Fri Oct 20 01:24:56 UTC 2023\n pong at gin-demo-d4659f67c-4vgjc\n"}
但是我们目前的服务是不具备负载均衡的,同时我们的Pod
也可能随时会被k8s
关闭重启
Service
负载均衡
创建一个gin-demo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: gin-demo
labels:
app: gin-demo
spec:
ports:
- port: 8080
name: http
protocol: TCP
targetPort: 8080
selector:
app: gin-demo
apiVersion: v1
kind: Service
metadata:
name: gin-demo
labels:
app: gin-demo
spec:
ports:
- port: 8080
name: http
protocol: TCP
targetPort: 8080
selector:
app: gin-demo
应用kubectl apply -f gin-demo-service.yaml
该Service
会依据spec.selector:
参数定义的匹配规则,把流量定向到Deployment
资源的metadata.labels
参数相同的资源上
$ kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gin-demo ClusterIP 10.43.45.69 <none> 8080/TCP 15h
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 22h
$ kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gin-demo ClusterIP 10.43.45.69 <none> 8080/TCP 15h
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 22h
现在可以多次执行如下命令,会发现获取的返回值会随机来源于某个Pod gin-demo
(ip
的值与gin demo service
的值保持一致)
$ curl http://10.43.45.69:8080/ping
$ curl http://10.43.45.69:8080/ping
现在进入到任意一个节点的pod
内部执行命令
$ kubectl exec -it gin-demo-d4659f67c-4vgjc -- sh
$ kubectl exec -it gin-demo-d4659f67c-4vgjc -- sh
访问域名gin-demo
,输出如下,运行多次,发现域名被映射为服务gin-demo
,访问多次可以看到负载均衡的效果
$ wget http://gin-demo:8080/ping -O - |cat
Connecting to gin-demo:8080 (10.43.50.108:8080)
writing to stdout
- 100% |**************************************************************************************************************************************| 80 0:00:00 ETA
written to stdout
{"message":"Wed Oct 18 09:46:42 UTC 2023\n pong at gin-demo-69d5b4d649-j8xvn\n"}
$ wget http://gin-demo:8080/ping -O - |cat
Connecting to gin-demo:8080 (10.43.50.108:8080)
writing to stdout
- 100% |**************************************************************************************************************************************| 80 0:00:00 ETA
written to stdout
{"message":"Wed Oct 18 09:46:42 UTC 2023\n pong at gin-demo-69d5b4d649-j8xvn\n"}
现在确定可以从设备内部网络访问service
可以实现动态的负载均衡
Ingress
外部设备访问
目前service
和pod
都不能被外部网络设备访问,所以就需要配置一个ingress
整体架构
创建一个gin-demo-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gateway
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- http:
paths:
- path: /api/v1(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: gin-demo
port:
name: http
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gateway
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- http:
paths:
- path: /api/v1(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: gin-demo
port:
name: http
应用ingress
$ kubectl apply -f gin-demo-ingress.yaml
$ kubectl apply -f gin-demo-ingress.yaml
查看ingress
的状态
$ kubectl get ingresses
NAME CLASS HOSTS ADDRESS PORTS AGE
gateway <none> * 172.16.67.147 80 15h
$ kubectl get ingresses
NAME CLASS HOSTS ADDRESS PORTS AGE
gateway <none> * 172.16.67.147 80 15h
$ kubectl describe ingresses
Name: gateway
Labels: <none>
Namespace: default
Address: 172.16.67.147
Ingress Class: <none>
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/api/v1(/|$)(.*) gin-demo:http (10.42.0.36:8080,10.42.0.40:8080,10.42.0.41:8080)
Annotations: kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$2
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 15h (x2 over 15h) nginx-ingress-controller Scheduled for sync
Normal Sync 19m nginx-ingress-controller Scheduled for sync
$ kubectl describe ingresses
Name: gateway
Labels: <none>
Namespace: default
Address: 172.16.67.147
Ingress Class: <none>
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/api/v1(/|$)(.*) gin-demo:http (10.42.0.36:8080,10.42.0.40:8080,10.42.0.41:8080)
Annotations: kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$2
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 15h (x2 over 15h) nginx-ingress-controller Scheduled for sync
Normal Sync 19m nginx-ingress-controller Scheduled for sync
表明在访问http
路径有前缀/api/v1
的时候,请求会被转发到gin-demo
的service
,之后再被负载转发到deployment
接下去从另外一台设备进行访问,多次访问的结果pong at
后面的数据是会变化的
$ curl http://172.16.67.147/api/v1/ping
{"message":"Fri Oct 20 01:42:22 UTC 2023\n pong at gin-demo-d4659f67c-4vgjc\n"}
$ curl http://172.16.67.147/api/v1/ping
{"message":"Fri Oct 20 01:42:22 UTC 2023\n pong at gin-demo-d4659f67c-4vgjc\n"}
Service NodeType
类型外部访问
有些时候我们不希望使用ingress
,比如想换一个外部网络访问端口
创建一个gin-demo-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: gin-demo-node-port
labels:
app: gin-demo
spec:
type: NodePort
ports:
- port: 8080
name: http
protocol: TCP
targetPort: 8080
nodePort: 31001
selector:
app: gin-demo
apiVersion: v1
kind: Service
metadata:
name: gin-demo-node-port
labels:
app: gin-demo
spec:
type: NodePort
ports:
- port: 8080
name: http
protocol: TCP
targetPort: 8080
nodePort: 31001
selector:
app: gin-demo
上述配置文件指定type: NodePort
,spec.ports[0].nodePort
其中nodePort
可选值是30000-32767
,上面的配置参数表示在宿主机上开放一个31001
端口,可以被远程设备访问
应用该配置文件
$ kubectl apply -f gin-demo-nodeport.yaml
$ kubectl apply -f gin-demo-nodeport.yaml
查看service
状态
$ kubectl describe svc gin
Name: gin-demo
Namespace: default
Labels: app=gin-demo
Annotations: <none>
Selector: app=gin-demo
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.43.86.114
IPs: 10.43.86.114
Port: http 8080/TCP
TargetPort: 8080/TCP
Endpoints: 10.42.0.36:8080,10.42.0.40:8080,10.42.0.41:8080
Session Affinity: None
Events: <none>
Name: gin-demo-node-port
Namespace: default
Labels: app=gin-demo
Annotations: <none>
Selector: app=gin-demo
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.43.4.171
IPs: 10.43.4.171
Port: http 8080/TCP
TargetPort: 8080/TCP
NodePort: http 31001/TCP
Endpoints: 10.42.0.36:8080,10.42.0.40:8080,10.42.0.41:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
$ kubectl describe svc gin
Name: gin-demo
Namespace: default
Labels: app=gin-demo
Annotations: <none>
Selector: app=gin-demo
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.43.86.114
IPs: 10.43.86.114
Port: http 8080/TCP
TargetPort: 8080/TCP
Endpoints: 10.42.0.36:8080,10.42.0.40:8080,10.42.0.41:8080
Session Affinity: None
Events: <none>
Name: gin-demo-node-port
Namespace: default
Labels: app=gin-demo
Annotations: <none>
Selector: app=gin-demo
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.43.4.171
IPs: 10.43.4.171
Port: http 8080/TCP
TargetPort: 8080/TCP
NodePort: http 31001/TCP
Endpoints: 10.42.0.36:8080,10.42.0.40:8080,10.42.0.41:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
可以看到,两个不同的service
的Endpoints
是一样的,gin-demo-node-port
还可以远程访问
在另外一台设备访问
$ curl http://172.16.67.147:31001/ping
{"message":"Fri Oct 20 03:22:53 UTC 2023\n pong at gin-demo-d4659f67c-4vgjc\n"}
$ curl http://172.16.67.147:31001/ping
{"message":"Fri Oct 20 03:22:53 UTC 2023\n pong at gin-demo-d4659f67c-4vgjc\n"}