【kubernetes系列】Kubernetes之service

news2024/9/28 11:16:00

本章节将分享一下关于kubernetes中service的相关知识。

一、service

概述

Service也是Kubernetes里的最核心的资源对象之一,正是因为对此概念的支持,Kubernetes在某种角度下可以被看成是一种微服务平台。由于Kubernetes中的pod并不稳定,比如由ReplicaSet、Deployment、DaemonSet等副本控制器创建的pod,其副本数量、pod名称、pod所运行的节点、pod的IP地址等,会随着集群规模、节点状态、用户缩放等因素动态变化。Service是一组逻辑pod的抽象,为一组pod提供统一入口,用户只需与service打交道,service提供DNS解析名称,负责追踪pod动态变化并更新转发表,通过负载均衡算法最终将流量转发到后端的pod。下图显示了Pod、RC与Service的逻辑关系。
在这里插入图片描述
从图中我们看到,Kubernetes的Service定义了一个服务的访问入口地址,前端的应用(fronted Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是通过Label Selector来实现“无缝对接”的。而RC的作用实际上是保证Service的服务能力和服务质量始终处于预期的标准。

运行在每个Node上的kube-proxy进程相当于就是一个智能的软件负载均衡器,它负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话机制。但Kubernetes发明了一种很巧妙又影响深远的设计:Service不是共用一个负载均衡的IP地址,而是每个Service分配了全局唯一的虚拟IP地址,这个虚拟IP地址被称为Cluster IP。这样一来,每个服务就变成了具备唯一IP地址的“通信节点”,服务调用就变成了最基础的TCP网络通信问题。

并且每个节点上的kube-proxy 这个组件始终监视着apiserver中有关service的变动信息,获取任何一个与service资源相关的变动状态,通过watch监视,一旦有service资源相关的变动和创建,kube-proxy都要转换为当前节点上的能够实现资源调度规则(例如:iptables、ipvs)。我们知道,Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址与之前旧Pod的不同,相应的变化信息会立即反映到apiserver上,而kube-proxy一定可以watch到etcd中的信息变化,而将它立即转为ipvs或者iptables中的规则,这一切都是动态和实时的,删除一个pod也是同样的原理。而Service一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内。它的Cluster IP不会发生改变。于是,服务发现这个棘手的问题在Kubernetes的架构里也得到轻松解决:只要用Service的Name与Service的Cluster IP地址做一个DNS域名映射即可完美解决问题。

kube-proxy的工作模式

kube-proxy有以下三种工作模式:
userspace 代理模式,早期使用,效率低,不作比较。
iptables 代理模式
ipvs 代理模式

ipvs vs. iptables

我们知道kube-proxy支持 iptables 和 ipvs 两种模式, 在kubernetes v1.8 中引入了 ipvs 模式,在 v1.9 中处于 beta 阶段,在 v1.11 中已经正式可用了。iptables 模式在 v1.1 中就添加支持了,从 v1.2 版本开始 iptables 就是 kube-proxy 默认的操作模式,ipvs 和 iptables 都是基于netfilter的,那么 ipvs 模式和 iptables 模式之间有哪些差异呢?

  • ipvs 为大型集群提供了更好的可扩展性和性能
  • pvs 支持比 iptables 更复杂的负载均衡算法(最小负载、最少连接、加权等等)
  • ipvs 支持服务器健康检查和连接重试等功能

ipvs 依赖 iptables

ipvs 会使用 iptables 进行包过滤、SNAT、masquared(伪装)。具体来说,ipvs 将使用ipset来存储需要DROP或masquared的流量的源或目标地址,以确保 iptables 规则的数量是恒定的,这样我们就不需要关心我们有多少服务了

service示例

下面我们动手创建一个Service,来帮助对它的理解。首先我们创建一个名为nginx-svc.yml 的定义文件,内容如下:

[root@k8s-m1 k8s-total]# cat nginx-svc.yml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  ports:
  - port: 80
  selector:
    tier: frontend

##上述内容定义了一个名为“nginx-service”的Service,它的服务端口为80,拥有“tier-frontend”这个Label的所有Pod实例都属于它,运行下面的命令进行创建:

[root@k8s-m1 k8s-total]# kubectl apply    -f nginx-svc.yml 
service/nginx-service created

创建pod

[root@k8s-m1 k8s-total]# cat nginx-deployment.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      tier: frontend
  replicas: 1
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
        - name: nginx-gateway
          image: nginx
          resources:
            requests:
              cpu: 100m
              memory: 100Mi
          ports:
            - containerPort: 80
[root@k8s-m1 k8s-total]# kubectl apply    -f nginx-deployment.yml 
deployment.apps/my-nginx created

查看相应的信息

[root@k8s-m1 k8s-total]# kubectl get svc nginx-service 
NAME            TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
nginx-service   ClusterIP   10.96.98.35   <none>        80/TCP    3m6s

[root@k8s-m1 k8s-total]# kubectl describe   svc nginx-service 
Name:              nginx-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          tier=frontend
Type:              ClusterIP
IP:                10.96.98.35
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.42.186:80
Session Affinity:  None
Events:            <none>

[root@k8s-m1 k8s-total]# kubectl get pod -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP              NODE     NOMINATED NODE   READINESS GATES
my-nginx-7ff446c4f4-6ltqm   1/1     Running   0          3m18s   10.244.42.186   k8s-m1   <none>           <none>

[root@k8s-m1 k8s-total]# kubectl get ep
NAME            ENDPOINTS                                                  AGE
nginx-service   10.244.42.186:80                                           6m36s

##通过上面可以看到nginx-service的clusterIP为10.96.98.35,包含的pod的ip为10.244.42.186

注意:
在spec.ports的定义中,targetPort属性用来确定提供该服务的容器所暴露 (EXPOSE–镜像中定义的)的端口号,即具体业务进程在容器内的targetPort上提供TCP/IP接入;而port属性则定义了Service的虚拟端口。前面我们定义nginx服务时,没有指定targetPort,则默认targetPort与port相同。(即serviceport是可以随意定义的,可以不用和targetPort一样,但是不一样的时候,就需要将targetPort指定出来)

工作过程如下 (Endpoint=Pod IP+ContainerPort):

  • 为实例分配集群虚拟IP。如果在声明时明确指定集群虚拟IP,则分配指定IP,如未指定则自动配。
  • 根据实例名称、分配的集群虚拟IP、端口号 创建DNS条目。
  • 根据标签选择器聚合符合条件的节点,并创建相应endpoint,endpoint包含所有符合条件pod的ip地址与端口号。
  • kube-proxy运行在集群中每一个节点上,并持续监控集群中service、endpoint变更,根据监控结果设置转发规则,将一个集群虚拟IP、端口与一个或者多个pod的IP、端口映射起来。
  • 当在集群内部通过服务名称访问创建的service时,首先由内部DNS将服务名称转换成集群虚拟IP与端口号,kube-proxy根据转发规则对service的流量计算负载均衡、转发到位于后端的pod。

在 Service 创建的请求中,可以通过设置 spec.clusterIP 字段来指定自己的集群 IP 地址。比如,希望替换一个已经已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。用户选择的 IP 地址必须合法,并且这个 IP 地址在 service-cluster-ip-range CIDR 范围内,这对 API Server 来说是通过一个标识来指定的。如果 IP 地址不合法,API Server 会返回 HTTP 状态码 422,表示值不合法。

无标签选择器service

当需要引入集群外部的服务到集群中使用时,因为集群中没有相关的pod实例,因此这种情况下就不需要标签选择器。有标签选择器时系统自动查询pod并创建相应的endpoint,无标签选择器时需要用户手动创建endpoint。

如引入外部Mysql的服务到集群内部使用:

[root@k8s-m1 k8s-total]# cat endpoint.yml 
kind: Endpoints
apiVersion: v1
metadata:
  name: mysql-production
subsets:
  - addresses:
      - ip: 192.168.2.142
    ports:
      - port: 3306
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-production
spec:
  ports:
    - port: 3306

#创建
[root@k8s-m1 k8s-total]# kubectl apply  -f endpoint.yml 
endpoints/mysql-production created
service/mysql-production created

#查看
[root@k8s-m1 k8s-total]# kubectl get svc mysql-production 
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
mysql-production   ClusterIP   10.99.22.164   <none>        3306/TCP   56s

[root@k8s-m1 k8s-total]# kubectl get ep mysql-production 
NAME               ENDPOINTS            AGE
mysql-production   192.168.2.142:3306   58s

[root@k8s-m1 k8s-total]# kubectl describe  svc mysql-production 
Name:              mysql-production
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP:                10.99.22.164
Port:              <unset>  3306/TCP
TargetPort:        3306/TCP
Endpoints:         192.168.2.142:3306
Session Affinity:  None
Events:            <none>

通过上面的配置,集群中就可以通过mysql-production这个服务名调用外部mysql。如果mysql地址发生变化,更新相应的endpoint即可。

注意:Endpoint IP 地址不能是 loopback(127.0.0.0/8)、 link-local(169.254.0.0/16)、或者 link-local 多播(224.0.0.0/24)。
注意:除需要手动创建endpoint外,无标签选择器与有标签选择器的servcie工作过程完全相同。请求将被路由到用户定义的 Endpoint(该示例中为 192.168.2.142:3306)

Service多端口问题

很多服务都存在多个端口的问题,通常一个端口提供业务服务,另外一个端口提供管理服务,比如Mycat、Codis等常见中间件。Kubernetes Service支持多个Endpoint,在存在多个Endpoint的情况下,要求每个Endpoint定义一个名字区分。下面是Tomcat多端口的Service定义样例:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  ports:
  - port: 8080
   name: service-port
  - port: 8005
   name: shutdown-port
  selector:
    tier: frontend

多端口为什么需要給每个端口命名呢?这就涉及Kubernetes的服务发现机制了,我们接下来进行讲解。

Kubernetes的服务发现机制

kubernetes 提供了 service 的概念可以通过 VIP 访问 pod 提供的服务,但是在使用的时候还有一个问题:怎么知道某个应用的 VIP?比如我们有两个应用,一个 app,一个 是 db,每个应用使用RC(控制器)进行管理,并通过 service 暴露出端口提供服务。app 需要连接到 db 应用,我们只知道 db 应用的名称,但是并不知道它的 VIP 地址。

环境变量方式

最早时Kubernetes采用了Linux环境变量的方式解决这个问题,即每个Service生成一些对应的Linux环境变量(ENV),并在每个Pod的容器在启动时,自动注入这些环境变量。
不同服务的环境变量用名称区分,例如:
{SVCNAME}_SERVICE_HOST and {SVCNAME}_SERVICE_PORT
如果服务有多个端口则端口的环境变量名称为 {SVCNAME}SERVICE{PORTNAME}_PORT。
以下是tomcat-service产生的环境变量条目,进入Tomcat的容器,使用env命令可以看到类似下面的变量:
TOMCAT_SERVICE_SERVICE_HOST= 10.96.98.35
TOMCAT_SERVICE_SERVICE_PORT_SERVICE_PORT=8080
TOMCAT_SERVICE_SERVICE_PORT_SHUTDOWN_PORT=8005
TOMCAT_SERVICE_SERVICE_PORT=8080
TOMCAT_SERVICE_PORT=tcp://10.244.56.3:8080
TOMCAT_SERVICE_PORT_8080_TCP_ADDR=10.244.56.3
TOMCAT_SERVICE_PORT_8080_TCP=tcp://10.244.56.3:8080
TOMCAT_SERVICE_PORT_8080_TCP_PROTO=tcp
TOMCAT_SERVICE_PORT_8080_TCP_PORT=8080
TOMCAT_SERVICE_PORT_8005_TCP=tcp://10.244.56.3:8005
TOMCAT_SERVICE_PORT_8005_TCP_ADDR=10.244.56.3
TOMCAT_SERVICE_PORT_8005_TCP_PROTO=tcp
TOMCAT_SERVICE_PORT_8005_TCP_PORT=8005

DNS方式:

一个可选(尽管强烈推荐)集群插件 是 DNS 服务器。 DNS 服务器监视着创建新 Service 的 Kubernetes API,从而为每一个 Service 创建一组 DNS 记录。 如果整个集群的 DNS 一直被启用,那么所有的 Pod 应该能够自动对 Service 进行名称解析。

例如,有一个名称为 “nginx-service” 的 Service,它在 Kubernetes 集群中名为 “default” 的 Namespace 中,为 “nginx-service.default” 创建了一条 DNS 记录。 在名称为 “default” 的 Namespace 中的 Pod 应该能够简单地通过名称查询找到 “nginx-service”。 在另一个 Namespace 中的 Pod 必须限定名称为 “nginx-service.default”。 这些名称查询的结果是 Cluster IP。

Kubernetes 也支持对端口名称的 DNS SRV(Service)记录。 如果名称为 “nginx-service.default” 的 Service 有一个名为 “http” 的 TCP 端口,可以对 “_http._tcp.nginx-service.default” 执行 DNS SRV 查询,得到 “http” 的端口号。

Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。 更多信息可以查看DNS Pod 和 Service。

Headless Service(无头服务):

在定义service时,如果.spec.clusterIP被指定为固定值则为服务分配指定的IP,如果.spec.clusterIP字段没有出现在配置中,则自动分配集群虚拟IP。但如果.spect.clusterIP的值被指定为”None”,此时创建的服务就被称为无头服务,其行为与普通服务有很大区别。首先不为服务分配集群虚拟IP,自然也就不能在DNS插件中添加服务相关条目。运行在各节点上的kube-proxy不为其添加转发规则,自然也就无法利用kube-proxy的转发、负载均衡功能。

虽然不向DNS插件添加服务相关条目,但可能添加其它条目,DNS 如何实现自动配置,依赖于 Service 是否定义了 selector。

配置 Selector:
此种情况下,系统仍然根据标签选择器创建endpoint,并根据endpoint向DNS插件中添加条目。比如命名空间为”default”,服务名称为”zk”,endpoing指向的pod名称为zk-1、zk-2,则向DNS插件中添加的条目类似于”zk-1.zk.default”,此时DNS中的条目直接指向pod。在StatefulSet类型资源中,使用无头服务为其中的pod提供名称解析服务,之所以可行,其实是因为StatefulSet能保证其管理的pod有序,名称地址等特征保持不变。

示例如下:

[root@k8s-m1 k8s-total]# cat nginx-svc.yml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-service-headless
spec:
  clusterIP: None
  ports:
  - port: 80
  selector:
    tier: frontend

---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  ports:
  - port: 80
  selector:
    tier: frontend
    
[root@k8s-m1 k8s-total]# kubectl apply  -f nginx-svc.yml 
service/nginx-service-headless created
service/nginx-service created

不配置 Selector:
对没有定义 selector 的 Headless Service,Endpoint 控制器不会创建 Endpoints 记录。 然而 DNS 系统会查找并配置,无论是:

ExternalName 类型 Service 的 CNAME 记录
记录:与 Service 共享一个名称的任何 Endpoints,以及所有其它类型

[root@k8s-m1 k8s-total]# kubectl get svc
NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes               ClusterIP   10.96.0.1        <none>        443/TCP    410d
nginx-service            ClusterIP   10.111.157.115   <none>        80/TCP     35s
nginx-service-headless   ClusterIP   None             <none>        80/TCP     35s

有ClusterIP Service 的DNS解析如下:

[root@k8s-m1 k8s-total]#  dig -t A nginx-service.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.10 <<>> -t A nginx-service.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8420
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx-service.default.svc.cluster.local. IN A

;; ANSWER SECTION:
nginx-service.default.svc.cluster.local. 30 IN A 10.111.157.115

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Tue Jun 27 20:46:56 CST 2023
;; MSG SIZE  rcvd: 123

Headless Service的DNS解析如下:

[root@k8s-m1 k8s-total]#  dig -t A nginx-service-headless.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.10 <<>> -t A nginx-service-headless.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21180
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;nginx-service-headless.default.svc.cluster.local. IN A

;; ANSWER SECTION:
nginx-service-headless.default.svc.cluster.local. 30 IN A 10.244.42.186

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Tue Jun 27 20:47:09 CST 2023
;; MSG SIZE  rcvd: 141

[root@k8s-m1 k8s-total]# kubectl get po -o wide
NAME                        READY   STATUS    RESTARTS   AGE    IP              NODE     NOMINATED NODE   READINESS GATES
my-nginx-7ff446c4f4-6ltqm   1/1     Running   0          5h1m   10.244.42.186   k8s-m1   <none>           <none>

可以发现是ClusterIP Service 的DNS解析在ClusterIP上,而Headless Service 的DNS解析在相应的Pod上,如果有多个Pod,会同时显示出来。

外部系统访问Service的问题

本文以上示例都以默认服务类型为前提,实际上kubernetes暴露服务IP的类型有四种,
service.spec.type允许指定一个需要的类型,默认是 ClusterIP 类型。Type 的取值以及行为如下:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
  • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 :,可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。(事实上可以通过metalLB 进行模拟LoadBlancer)
  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

首先,Node IP是Kubernetes集群中每个节点的物理网卡的IP地址,这是一个真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通信,不管它们中是否有部分节点不属于这个Kubernetes集群。这也表明了Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务时,必须要通过Node IP进行通信。

其次,Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,前面我们说过,Kubernetes里一个Pod里的容器访问另外一个Pod里的容器,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量则是通过Node IP所在的物理网卡流出的。

最后,我们说说Service的Cluster IP,它其实是一个虚拟的IP,原因有以下几点。
Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)。
Cluster IP无法被Ping,因为没有一个“实体网络对象”来响应。(实际使用中使用Flannel网络模式的Cluster IP无法被Ping,而使用calico网络模式的可以被Ping)
Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCP/IP通信的基础,并且它们属于Kubernetes集群这样一个封闭的空间,集群之外的节点如果要访问这个通信端口,则需要做一些额外的工作。
在Kubernetes集群之内,Node IP网、Pod IP网与Clsuter IP之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊的路由规则,与我们所熟知的IP路由有很大的不同。
根据上面的分析和总结,我们基本明白了:Service的Cluster IP属于Kubernetes集群内部的地址,无法在集群外部直接使用这个地址。那么矛盾来了:实际上我们开发的业务系统中肯定多少由一部分服务是要提供給Kubernetes集群外部的应用或者用户来使用的,典型的例子就是Web端的服务模块,比如上面的tomcat-service,那么用户怎么访问它?

采用NodePort是解决上述问题的最直接、最常用的做法。具体做法如下,以nginx-service为例,我们在Service的定义里做如下修改,改变了service的类型并固定了nodeport的端口:

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30080
  selector:
    tier: frontend

其中,nodePort:30080这个属性表明我们手动指定nginx-service的NodePort为30080,否则Kubernetes会自动分配一个可用的端口。接下来,我们在浏览器里访问http://nodeIP:30080,就可以看到nginx的欢迎界面了,如图所示。在这里插入图片描述

通过NodePort访问Service
NodePort的实现方式是在Kubernetes集群里的每个Node上为需要外部访问的Service开启一个对应的TCP监听端口,外部系统只要用任意一个Node的IP地址+具体的NodePort端口号即可访问此服务,在任意Node上运行netstat命令,我们就可以看到有NodePort端口被监听:

[root@k8s-m1 k8s-total]# netstat -anp|grep 30080
tcp        0      0 0.0.0.0:30080           0.0.0.0:*               LISTEN      30105/kube-proxy

但NodePort还没有完全解决外部访问Service的所有问题,比如负载均衡问题,假如我们的集群中有10个Node,则此时最好有一个负载均衡器,外部的请求只需要访问此负载均衡器的IP地址,由负载均衡负责转发流量到后面某个Node的NodePort上。实际使用中,

externalIPs

注意事项:对于使用了externalIPs的Service,当开启IPVS后,externalIP也会作为VIP被ipvs接管,因此如果在externalIp指定的Kubernetes集群中Node节点的IP,需将externalIp替换成预先规划好的VIP(在同一网段找一个未被使用的IP),否则会出现VIP和Node节点IP冲突的问题。使用命令行将VIP绑定到物理网卡上eth0(ens***)网口,而不是绑定到kube-ipvs0网口

[root@k8s-m1 k8s-total]# ip addr add 192.168.2.250/24 brd 192.168.2.255 dev ens32
[root@k8s-m1 k8s-total]# cat externalip-svc.yml
kind: Service
apiVersion: v1
metadata:
  name: nginx-externalip
spec:
  selector:
    tier: frontend
  ports:
  - name: http
    port: 80
  externalIPs:
    - 192.168.2.250

[root@k8s-m1 k8s-total]# kubectl apply  -f externalip-svc.yml
service/nginx-externalip created

#查看
[root@k8s-m1 k8s-total]# kubectl get svc
NAME                     TYPE        CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
kubernetes               ClusterIP   10.96.0.1        <none>          443/TCP        410d
nginx-externalip         ClusterIP   10.97.49.32      192.168.2.250   80/TCP         5s
nginx-service            NodePort    10.111.157.115   <none>          80:30080/TCP   32m
nginx-service-headless   ClusterIP   None             <none>          80/TCP         32m

更多关于kubernetes的知识分享,请前往博客主页。编写过程中,难免出现差错,敬请指出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/695569.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从0到1精通自动化测试,pytest自动化测试框架,pytest分布式执行(pytest-xdist)(十六)

一、前言 平常我们手工测试用例非常多时&#xff0c;比如有1千条用例&#xff0c;假设每个用例执行需要1分钟 如果一个测试人员执行需要1000分钟才能执行完&#xff0c;当项目非常紧急的时候&#xff0c;我们会用测试人力成本换取时间成本&#xff0c;这个时候多找个小伙伴把…

【技术操作】如何通过抓包来分析EasyCVR不回复上级平台的invite请求?

EasyCVR平台基于云边端协同架构&#xff0c;可支持多协议、多类型的海量设备接入与分发&#xff0c;平台既具备传统安防视频监控的能力&#xff0c;也具备接入AI智能分析的能力&#xff0c;在线下均有大量应用。 有用户反馈&#xff0c;在级联时&#xff0c;使用EasyCVR作为下级…

【2023年全新保姆级教程】解决Ubuntu文件系统磁盘空间不足low disk space on filesystem root

** 解决Ubuntu22.04文件系统磁盘空间不足low disk space on filesystem root ** 大家好&#xff0c;我是洲洲&#xff0c;欢迎关注&#xff0c;一个爱听周杰伦的程序员。关注公众号【程序员洲洲】即可获得10G学习资料、面试笔记、大厂独家学习体系路线等…还可以加入技术交流…

QGIS实战操作-解决TIFF影像黑边问题

QGIS实战操作-解决TIFF影像黑边问题 问题描述 "TIFF图像黑边问题"就是当我们处理或使用TIFF格式的遥感或者地理信息数据时&#xff0c;经常会在图像边缘看到一些不该出现的黑色区域&#xff0c;这些黑边可能会影响我们对图像的观看体验&#xff0c;甚至可能对图像的…

Unity 自定义代码模板

文章目录 1. Unity 中自带的代码模板创建自己的代码模板3. 代码模板的修改4. 自定义标签 1. Unity 中自带的代码模板 在 Unity 的安装目录下找到 Unity\Editor\Data\Resources\ScriptTemplates 文件夹&#xff0c;结构如下&#xff1a; 对应上图可得出&#xff1a; 文件名后…

CSS知识点汇总(一)

介绍一下标准的 CSS 的盒子模型&#xff1f; 两种盒子模型&#xff1a;IE盒模型&#xff08;border-box&#xff09;、W3C标准盒模型&#xff08;content-box&#xff09; 盒模型&#xff1a; 分为内容(content)、填充(padding)、边界(margin)、 边框(border)四个部分&#x…

IntelliJ IDEA maven 引用本地 jar 文件

一、背景说明 由于某些特定原因&#xff0c;不能在远程maven仓库中下载所需要版本的jar文件&#xff0c;需要在maven中引用本地jar文件。 二、解决方案 1、创建 libs 目录 为了方便jar包管理&#xff0c;可以在工程目录下&#xff0c;创建一个与src目录平级的libs目录。如下…

OpenCV之Window开发环境配置

下载opencv开发包 win配置: 双击SDK包解压 解压中 解压成功 配置环境变量 验证环境 创建控制台应用并配置工程属性表添加opencv环境 创建属性表 配置属性 复制属性表并修改名Project1X64_Debug与Project1X64_Release 导入属性表 测试opencv功能 示例代码 #include <ope…

rsync安装使用

目录 一、磁盘性能测试安装fio磁盘性能测试工具测试内容为顺序读、随机读、顺序写、随机写、随机读写结果分析 二、CPU性能测试安装sysbench性能测试工具结果分析 三、内存性能测试结果分析 四、网络性能测试测试iperf3工具测试结果分析1.测试TCP吞吐量2 . 测试UDP丢包和延迟 此…

Kubernetes-Ingress、Ingress Controller、Ingress Class

概念 1.Ingress 是对K8S集群中服务的外部访问进行管理的 API 对象。Ingress 公开从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源上定义的规则控制。 2.Ingress Controller 通常负责通过负载均衡器来实现 Ingress。 3.必须拥有一个 Ingress Controller…

【已解决】如何下载使用公司内部Maven私有仓库中的依赖

文章目录 1、前言2、解决办法3、总结 1、前言 ​今天刚来公司实习&#xff0c;面对leader抛来的项目和文档&#xff0c;对于第一次实习的我&#xff0c;显然有点手足无措&#xff0c;于是就按着平时写代码的习惯。1、拉代码放本地&#xff1b;2、开始导入项目依赖&#xff1b;…

go channel 详解

一、概述 在Go语言中&#xff0c;channel是一种特殊的类型&#xff0c;用于在并发编程中实现不同的goroutine之间的通信和同步。本文将深入探讨golang的channel是如何工作的&#xff0c;并介绍如何使用channel来提高程序的性能和可靠性。 二、什么是Channel&#xff1f; 在G…

【Opencv】----- 图片无缝融合

上次写了一篇关于GAN缺陷生成的图片的&#xff0c;发现大家的对这个根本不感兴趣&#xff0c;可能是用于在座的各位觉得样本有的是&#xff0c;我为什么还苦哈哈的去生成啊&#xff0c;说的也不是没有道理&#xff0c;可能只是我搞了更加苦哈哈的工业深度学习吧&#xff0c;所以…

软件测试技能,JMeter压力测试教程,监听器之每秒事务数与响应时间(十四)

前言 压测的时候&#xff0c;我们会经常关注2个重要的指标 TPS 和 RT TPS&#xff1a;每秒处理的事务数(Transactions per Second)&#xff0c;jmeter的Throughput为吞吐量&#xff08;请求数/秒&#xff09; RT&#xff1a;响应时间(Reponse Time&#xff09;&#xff0c;从…

西亚文明的时空概念与历史上的文明形态

“西亚”这个地理概念主要指的是今日土耳其、伊朗、伊拉克、叙利亚、黎巴嫩、约旦、以色列和沙特等阿拉伯半岛国家所在的地区。若以地理区域来划分&#xff0c;与西亚文明比较密切的区域主要有&#xff1a;小亚细亚&#xff08;今土耳其&#xff09;、两河流域&#xff08;今伊…

n.moduleList 和Sequential的解析与对比

n.moduleList 和Sequential的解析与对比 1.nn.ModuleList2.nn.Sequential3、nn.Sequential与nn.ModuleList的区别4、使用场景 1.nn.ModuleList 2.nn.Sequential 3、nn.Sequential与nn.ModuleList的区别 4、使用场景

汇编输入int 16h

int 16h 读入缓冲区ASCII码 检测点17.1 int 16h会设置if1 assume cs:code code segment start:mov ah,0int 16hmov ah,1cmp al,rje redcmp al,gje greencmp al,bje bluejmp sret red:shl ah,1 green:shl ah,1 blue:mov bx,0b800hmov es,bxmov si,1mov cx,2000 s: mov byte ptr…

mysql 基础架构与组件

基础架构 下图是 MySQL 的一个简要架构图&#xff0c;从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。 先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图。 连接器&#xff1a; 身份认证和权限相关(登录 MySQL 的时候)。查询缓存: 执行查…

Docker安装mysql8-超详细、每步都有截图

1.下载mysql8镜像&#xff08;版本根据自己选择&#xff1a;这里是8.0.20&#xff09; docker pull mysql:8.0.202.启动镜像 docker run -p 3307:3306 --name mysql8 -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0.20这里特殊的地方只是说明一下、对每个参数有个了解。没其他实…

webassembly添加调试

本文主要参考&#xff1a;webassembly下C调试 一、安装扩展程序 如果有条件的&#xff1a;DevTools下载 使用第三方Chrome扩展插件搜索下载网站地址 &#xff0c;搜索DWARF下载。 二、使用扩展程序 解压安装包后&#xff0c;直接将crx文件拖入到web浏览器上&#xff0c;如果…