Kubernetes-Service

0x00 概念介绍

service描述:k8s中的service实际上是微服务框架中的微服务,Service定义了一个服务的访问入口,可以通过该入口访问其背后的集群实例(Pods)


Q:什么是Service服务?

A:Service是K8s通过Labels(标签)选择的方式来匹配一组Pod,然后提供对外访问的一种机制。一组Pod可以对应到多个svc的,每一个service都可以理解为一个微服务

Q:service如何与后端的Pod关联?

A:Service与其后端的Pod多个副本集群通过Label Selector进行关联(等式或者集合进行过滤),而构建这些Pod的RS,Deployment,StatefulSet等控制器是保证Pod数量满足预定义数量

Q:微服务的好处?

A:复杂的系统微服务服务化,由多个提供不同业务服务而彼此独立的微服务组成,服务之间通过TCP/IP协议进行通信,便拥有了强大的分布式,水平弹性扩展能力

Q:什么是EndPoint?

A:端点 Pod IP + Container Port,默认情况下每个Pod将提供一个独立端点供SVC或者其他的Pod进行访问。kubectl get ep -o wide; 获取全局EndPoint

Q:采用Label关联Controller创建的Pod后如何进行EndPoint访问?

A:传统做法是前端部署一个负载均衡工具(如 Nginx)并为该组开放一个对外端口例如8080,并将这些Pod的EndPoint转发到nginx上,之后客户端将可以通过Nginx负载均衡(LB)对外的IP和Port来访问服务,而对于Client的请求最终会被分发到哪个Pod是由负载均衡算法决定的;kubectl get pod -o wide --show-lables;

Q:K8s如何实现负载均衡?

A:在k8s架构中我们提到过kube-proxy组件它实际上实现了一个软件负载均衡器的作用,负责把Service的请求转发到某一个具体后端的Pod实例上,并且在内部实现了会话保持的机制。

Q:为什么要使用Label匹配Pod?

A:Pod的EndPoint可能会随着Pod的销毁或者重建而发生改变,k8s为每个Service都分配了一个Cluster IP的全局虚拟IP,一旦Service被创建,其生命周期内该IP会一直保持不变,所以只要Service Name与Service的Cluster IP做一个DNS域名映射就可以轻松的解决服务发现的问题

Service只有一个RB轮询算法,他能够提供负载均衡的能力,但是有以下限制:

A,提供4层负载均衡能力[只能基于IP地址和端口进行转发]

B,提供7层功能[不能通过主机名以及域名的方案去进行负载均衡],但是我们可能需要更多的匹配规则来转发请求,这点上4层负载均衡是不支持的

0x01 服务发现

描述:谈到微服务就必须考虑到服务发现,前面说了Service与Cluster IP关联原理,但随之而来的是K8S如何做到四层/七层服务发现的呢?


///四层服务发现///
描述:四层服务发现主要由两种方式,环境变量/DNS

环境变量方式
描述:在早期采用了Linux环境变量的方式,即每个Service生成一些对应的Linux环境变量,并在Pod启动时自动注入这些变量
例子 kubectl exec -it deploy-im env
输出: KUBERNETES_SERVICE_PORT=443

DNS方式
描述:鉴于环境变量的方式的局限性以及SVC的Cluster IP的可读性差等问题引入了DNS方式进行服务发现,利用Service Name做DNS域名,应用程序或者集群中的其他服务可以通过域名+Port形式直接访问服务。
例子:cat > services-dns-test.yaml <<'END'
apiVersion: v1
kind: Pod
metadata:
    name: test
    namespace: default
spec:
    containers:
    - name: test
      image: test:latest
      imagePullPolicy: "IfNotPresent"
      command: ["sleep", "3000"]
    restartPolicy: Never
END
操作:
1,kubectl create -f services-dns-test.yaml
    结果:created
2,kubectl get pod -o wide | grep "dns"
    结果:test Running
3,kubectl expose deployment deploy-blog-svc && kubectl get svc
    结果:deploy-blog-svc
4,kubectl exec -it busybox-dns-test -- sh -c "nslookup -type=a deploy-blog-svc"


///七层服务发现///
描述:在实际的应用场景中,一定是由某些服务暴露给用户或者集群外部访问的。例如网站服务,而四层服务发现仅用于k8s集群内部的访问。而七层服务发现我们需要通过前端代理进行实现,例如采用Nginx-Ingress或者Traefik-Ingress进行实现

Q:k8s内部Pod间相互访问的原理

A:Pod IP是由Docker Daemon根据docker0网桥IP地址分配的或者是网络插件Flannel实现的,即Pod间的通信是通过Pod IP所在虚拟二层网络通信的,而真实的TCP/IP流量是通过Node的网卡流出.

A:Service的Cluster IP与Pod IP类似于集群的内部地址属于虚拟IP,无法直接被其他k8s集群所访问;

A:列举外部访问内部服务的方式:1,NodePort,采用Kube-proxy组件,每个节点将为其Service开放一个30000-32000的外部端口;2,LoadBalance,支持使用外部负载均衡的云提供服务商,比如GCE或者AWS。LB是异步创建其信息将会通过Service的statusLoadBalance字段发布出去;3,ExternalName,通过返回CNAME和其值,可以将服务映射到ExternalName字段内,例如:foo.example.com


///代理实现原理///
描述:访问k8s集群中创建的内部Pod端口流程示意图,其中Pod中的容器端口需要加入到EndPoints端点控制器里面

关于上图
作用解析
apiServer:监听服务和端点通过kube-proxy去监控,以及通过监控kube-proxy去实现服务端点信息的发现
kube-proxy:通过选择标签去监控对应的Pod并写入到iptable规则里面
client:访问服务时通过iptables中的规则被定向到pod的地址信息
iptables:规则是通过kube-proxy去写入


k8s代理模式的分类
描述:在k8s集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为service实现了一种VIP(虚拟IP)的形式[可以在集群内部直接访问],而不是ExternalName[返回集群外部的地址信息]的形式
1,在k8s 1.0,代理完全由userspace实现
2,在1.1,新增了iptables代理,但并不是默认的运行模式
3,在1.2,默认是iptables
4,在1.14,默认使用ipvs代理,但是缺省还是iptables

 Userspace 代理模式

描述:客户端首先访问iptables,然后通过iptables访问到kube-proxy之后访问到具体的pod上,同时kube-apiserver也会监控kube-proxy服务更新以及端点的维护,但是由于每次访问都需要kube-proxy进行一次代理,这将会导致kube-proxy压力非常大

 Iptables 代理模式

描述:iptables代理模式对userspace模式进行了改变,外部访问直接通过Iptables而不需要kube-proxy去调度访问;此时kube-apiserver依然通过监控kube-proxy去实现iptables的端口的更新维护。显而易见的,优点是访问速度大大增加以及kube-proxy稳定性会提高,并且承受的压力会减少很多

 IPVS 代理模式

描述:IPVS模式,实际是将iptables代理模式中的iptables变更为ipvs,即把原本是通过iptables进行服务定向转发变成了通过IPVS模块去实现负载均衡以及流量导向,其他的方面与Iptables代理模式相同,所有的访问也是不经过kube-proxy;该模式kube-proxy会监视k8s Service对象和endpoints,调用netlink接口以相应的创建ipvs规则并定期与k8s service对象和endpoints对象同步ipvs规则,以确保IPVS状态保持一致。当访问服务时流量将被重定向到其中的一个后端Pod

优点:ipvs是基于netlink的hook功能,在内核空间中使用哈希表作为底层数据结构,使得ipvs可以更快的重定向流量,并且在同步代理规则时有更好的性能,此外ipvs为负载均衡算法提供了更多的选项

如果操作系统没有提前预装ipvs模块以及其依赖需求不满足时,k8s将会默认使用iptables的代理模式

IPVS负载均衡算法:

rr: 轮询调度
lc: 最小连接数
dh: 目标哈希
sh: 源哈希
sed: 最短期望延迟
nq: 不排队调度

 负载均衡

在k8s 1.0版本,service是4层(TCP/UDP over IP)即只能通过主机和端口进行负载概念
在k8s 1.1版本,新增了IngressAPI,用来表示七层服务。可以进行七层的负载均衡,

Q: 为何不使用round-robin DNS?

A: k8s不采用DNS负载均衡算法,最大的原因就是DNS会在很多客户端进行缓存,很多服务访问DNS进行域名解析的时候,解析完成之后得到地址以后,很多服务都不会对DNS的缓存进行清除,也就是说只要缓存存在服务下次访问的还是这个地址,因此也就达不到负载均衡的要求,因此DNS一般仅仅作为负载均衡的一种辅助手段

Tips:此处的四层与七层不是OSI模型中的概念,而是负载均衡的概念,你可以简单通过以下两个例子进行了解

1,四层负载均衡原理:在接收到客户端请求后,通过修改数据包得到 目的/源地址信息与端口号(ip+端口号)将流量转发到应用服务器
2,七层负载均衡原理:可以对同一个Web服务器进行负载,它除了根据IP加端口进行负载外,还可根据http协议中的URL/浏览器类别/语言来决定是否要进行负载均衡

0x02 服务发现类型

服务发现分类/
k8s集群中Service服务发现方式有以下四种类型ServiceType

1,ClusterIP:默认类型,自动分配一个仅集群内部可以访问的虚拟IP(常常由flannel/calico网络插件进行管理)[service创建一个仅集群内部可访问的Ip,集群内部其他的Pod可以通过该服务访问到其监控下的Pod]

2,NodePort:在ClusterIP基础上为service在每台机器上绑定一个端口,这样就可以通过NodePort来访问该服务[在service以及各个node上开启端口,外部的应用程序或者客户端访问node的端口将会转发到service的端口,而service将会依据负载均衡随机将请求转发到某一个pod的端口上][为了防止某一个节点down掉,建议将集群中所有的node节点+端口都设置进入负载均衡中]

3,LoadBalancer:在NodePort的基础上,借助cloud provider创建一个外部负载均衡器(在云主机构建的k8s基础上),并将请求转发到Nodeport[在nodePort基础之上,即各个节点前加入了负载均衡器,实现了真正的高可用,一般云供应商提供的k8s集群就是这种,即本身自带负载均衡器]

4,ExternalName:把集群外部的服务引入到集群内部并且在集群内部直接使用。没有任何类型的代理被创建,这只有k8s 1.7或者更高版本的kube-dns才支持[当我们的集群服务需要访问k8s之外的集群时,可以选择这种类型,然后把外部服务的IP以及端口写入到k8s服务中,k8s代理将会帮助我们访问到外部的集群服务]

ClusterIP - Service

描述:它主要在每个Node节点使用iptables[代理模式不同地层的方案也是不一致的],将发向clusterIP对应端口的数据转发到kube-proxy中。kube-proxy自己内部实现有负载均衡的方法,并可以查询到这个service下对应的pod的地址和端口,进而把数据转发给对应的pod的地址和端口

Tips:采用IPVS模块替代了iptables,其实还是采用了iptables中类似于netfilter的hook功能进行实现的

由以下几个组件进行协同工作,实现ClusterIP的服务发现:
1,ApiServer: 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中
2,kube-proxy:每个节点中都有一个叫做kube-proxy的进程,这个进程负责感知service和pod的变化,并将变化信息写入到iptables规则中
3,iptables使用NAT等技术将virtualIP的流量转发到endpoint


示例
ClusterIP 资源清单示例(此处使用的是IPVS负载均衡技术和Flannel网络插件)
cat > ClusterIP-demo.yaml <<'EOF'
# Namespace: 后面的Service演示的资源清单都将放入在该名称空间下
kind: Namespace
apiVersion: v1
metadata:
    name: service-test
    labels:
        keys: service-test
---
# Deployment 资源控制器
apiVersion: apps/v1
kind: Deployment
metadata:
    name: clusterip-deploy
    namespace: service-test
spec:
    replicas: 3
    selector: 
        matchLabels:
            app: nginx-clusterip
            release: stable
    template:
        metadata:
            labels:
                app: nginx-clusterip
                release: stable
                env: test
        spec:
            containers:
            - name: nginx
              image: harbor.weiyigeek.top/test/nginx:v2.0
              imagePullPolicy: IfNotPresent
              ports:
              - name: http
                containerPort: 80
---
# Service 服务发现
apiVersion: v1
kind: Service
metadata:
    name: clusterip-deploy
    namespace: service-test
spec:
    type: ClusterIP
    selector:
        app: nginx-clusterip
        release: stable
    ports:
    - name: http
      port: 80
      targetPort: 80
EOF

操作流程

# 1,资源清单的部署
kubectl create -f ClusterIP-demo.yaml
# 2,查看资源控制管理器管理的Pod
kubectl get ns | grep "service-test" # 查看命名空间
kubectl get deploy -n service-test -o wide --show-labels # deployment 控制器
kubectl get rs -n service-test -o wide --show-labels # 受到deployment控制器管理,但是由ReplicaSet资源控制器创建的Pod
kubectl get pod -n service-test -o wide --show-labels # pod相关信息
# 3,查看Service资源控制器
kubectl get svc -n service-test -o wide

Headless - Service

描述:Headless Service即无头服务,他也是一种特殊的Cluster IP,当某个应用不需要,不想要负载均衡(暂时不需要访问)以及单独的ServiceIp时

简单的说:为了更好的转发性能,我们希望可以自己控制负载均衡策略来替代k8s默认的负载策略,或者一个应用期望知道同组服务的其他实例

特点:通过无头服务的方式去解决hostname和portname的变化问题,也就是通过它去进行绑定

配置:通过指定ClusterIP(spec.ClusterIP)的值为None来创建Headless Service。这类Service并不会被分配ClusterIp,且kube-proxy不会处理它们,平台也不会为他们进行负载均衡和路由


//示例/
Headless资源清单示例
cat > headless-demo.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
    name: myapp-headless
    namespace: service-test
spec:
    clusterIP: "None"
    selector:
        app: nginx-clusterip
        release: stable
    ports:
    - port: 80
      targetPort: 80
EOF
操作流程
# 1,创建svc
kubectl apply -f headless-demo.yaml
# 2,查看
kubectl get svc -n service-test
kubectl get pod -n service-test -o wide
# 3,在SVC创建成功之后,他将写入到coreDNS中去,并且会有一个主机名被写到coreDNS中,写入格式:svc名称 + 命名空间的名称 + 当前集群的域名
kubectl get pod -n kube-system -o wide | grep "coredns"
# 4,在无头服务中,虽然他没有ip了,但是可以通过访问域名的方式依然可以访问服务下的pod,需要将第三步写入到coreDNS的ip设置进入到/etc/resolv.conf
cat /etc/resolv.conf
dig -t A myapp-headless.service-test.svc.cluster.local @x.x.x.x
# 5,验证
curl http://myapp-headless.service-test.svc.cluster.local/host.html
# 6,查看是否经过负载均衡
sudo ipvsadm -Ln

NodePort - Service

描述:nodePort的原理是在node上开放一个端口,当客户端访问该svc则将向该端口的流量导入到kube-proxy,然后由kube-proxy进一步的根据svc绑定的Pod标签,将请求转发给对应的Pod容器

/示例
NodePort资源清单
cat > nodeport-demo.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
    name: nodeport-demo
    namespace: service-test
spec:
    type: NodePort
    selector:
        app: nginx-clusterip
        release: stable
    ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 32306
      protocol: TCP
EOF

操作流程
# 1,创建Service
kubectl apply -f nodeport-demo.yaml
# 2, 查看创建的NodePort,其保留的端口80:32306/TCP
kubectl get svc -n service-test -o wide
# 3,验证两种方式访问
80端口
32306端口
# 4,查看负载
sudo ipvsadm -Ln

LoadBalancer - Service

 loadBalancer和nodePort其实是同一种方式,只是前者运行在云厂商服务器中;两者的区别在于LB就是可以调用cloud provider去创建LB来向节点导流,但是会增加额外的预算

ExternalName - Service

描述:该类型的Service通过返回CNAME和它的值,可以将服务映射到externalName字段的内容。他是Service的特例,没有selector也没有定义任何的端口和EndPoints,相反的对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务,所以其类似软链或者快捷方式;其目标是为了将外部流量引入到集群内部;ExternalName接受Ipv4地址字符串,但作为包含数字的DNS名称,而不是IP地址,类似于Ipv4地址的外部名称不能由CoreDNS或者Ingress-Nginx解析,因为外部名称旨在指定规范的DNS名称。要对IP地址进行硬编码,请考虑使用headless Services

/示例//
ExternalName资源清单示例

cat > externalname-demo.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
    name: externalname-demo
    namespace: service-test
spec:
    type: ExternalName
    externalName: baidu.com
EOF

Tips:查找主机 externalname-demo.service-test.svc.cluster.local时通过DNS的CNAME记录,转发到baidu.com

操作流程

# 1,创建
kubectl apply -f externalname-demo.yaml
# 2, 查看
kubectl get svc -n service-test -o wide
# 3, 查询主机DNS解析集群的DNS服务器将返回一个baidu.com的CNAME值,访问这个服务的工作方式和其他的相同,不同的是重定向发生在DNS层,不实在代理层
dig -t A externalname-demo.service-test.svc.cluster.local @x.x.x.x

ExternalIPS - Service

externalIPS//

## 资源清单

tee svc-ExternalIP.yaml <<'EOF'
kind: Service
apiVersion: v1
metadata:
    name: extip
    namespace: test
spec:
    externalIPs:
    - "192.168.21.3" # 外部网卡地址,不可以设置为k8s master地址
    ports:
    - name: http
      port: 8081
      protocol: TCP
      targetPort: 80
    selector:
      app: nginx-demo
EOF


## 操作流程
# 1,快速部署由deployment管理的nginx应用
kubectl create deployment -n test --image=nginx:latest --replica 1 nginx-demo
# 2,部署svc-ExternalIP,创建和查看
kubectl apply -f svc-ExternalIP.yaml
kubectl describe svc/extip -n test
kubectl get svc -n test blog
# 3,验证
curl extip.test.svc:8081
# 4,检测监听
netstat -tlnp | grep "192.168.21.3:8081"

0x03 Proxy - 代理转发

使用port-forward访问集群中的应用程序

描述:在对k8s内部服务进行Debug时,使用kubectl port-forward 访问k8s集群中的Server进行调试

//step 1
# 创建Redis的Deployment和Service
# Deployment
cat > redis-master-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
    name: redis-master-deployment
    labels:
        app: redis
spec:
    selector:
        matchLabels:
            app: redis
            role: master
            tier: backend
    replicas: 1
    template:
        metadata:
            labels:
                app: redis
                role: master
                tier: backend
        spec:
            containers:
            - name: master
              image: redis
              resouces:
                 requests:
                    cpu: 100m
                    memory: 100Mi
              ports:
              - containerPort: 6379
EOF
# Service
cat > redis-master-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
    name: redis-master-service
    labels:
        app: redis
        role: master
        tier: backend
spec:
    ports:
    - port: 6379
      targetPort: 6379
    selector:
        app: redis
        role: master
        tier: backend
EOF


//step 2
# 创建 Deployment,Service
kubectl apply -f redis-master-deployment.yaml
kubectl apply -f redis-master-service.yaml


//step 3
# 查看部署情况
kubectl get pods
kubectl get deployment
kubectl get rs
kubectl get svc -o wide | grep redis
kubectl get pods redis-master-deployment-xxx-xxx --template='{{(index (index .spec.containers 0).ports 0).containerPort}}{{"\n"}}'


//step 4///
# 使用kubectl port-forward 命令转发本地端口到Pod的端口,用户可以使用资源名称来进行端口转发
# 1,端口转发
kubectl port-forward redis-master-deployment-xxxx-xxx 7000:6379
kubectl port-forward pods/redis-master-deployment-xxxx-xxx 7000:6379
kubectl port-forward deployment/redis-master-deployment 7000:6379
kubectl port-forward svc/redis-master-service 7000:6379
kubectl port-forward rs/redis-master-deployment-xxxx 7000:6379

# 2,kubectl port-forward --address 127.0.0.1,10.10.107.191 redis-master-deployment-xxxx-xxx 7000:6379 输出
# Forwarding from 127.0.0.1:7000 -> 6379
# Handing connection for 7000
# 3,连接
redis-cli -h 10.10.107.191 -p 7000


//step 5///
# 本机7000端口的连接被转发到集群中的redis所在的pod的6379端口。利用该命令可以方便开发或者运维人员进行Debug开发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值