本文共 17024 字,大约阅读时间需要 56 分钟。
在谈kubernetes的service之前,小编向带大家复习一下负载均衡的概念,那么何为负载均衡呢,小编举一个简单的例子,能力相同的两个人同时进入一家公司,一个人天天加班到深夜,另一个人则天天优哉游哉到点下班,但两个人的工资一样,此时那个埋头苦干的人就会抱怨了,凭什么我天天累死累活,这不公平!相对于机器而言也是一样的,一台机器一直干活,另一台机器一直空闲,总有一台干活的机器会罢工的!那么如何解决这个问题呢?小编这里举一个web集群的例子:
客户端访问一个虚拟的IP,通过这个虚拟的IP将请求发送给nginx的负载(主),此时主负载将接受的请求抛给后端的web服务做真正的处理,这里的负载可以是轮询也可以是根据集群的资源,指定发送到某台web服务器上。那有些读者就想问了,如何设置这个虚拟的IP,又怎么知道负载什么时候会宕机,负载又如何发现后端的web服务器的,如何获取集群资源,精确计算出哪一台web服务比较空闲等等,这一些列的问题,不要着急,小编接下来根据kubernetes的service将这其中的原理一一道来。希望一篇文章就能写完,此时绝不说废话了,拜托拜托。service是kubernetes最核心的概念,通过创建service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载发送到后端的各个容器应用上。
apiVersion: v1 kind: Servicemetadata: #元数据 name: string #service的名称 namespace: string #service所属的命名空间 labels: #service的标签 - name: string annotations: #service的注解 - name: string spec: selector: [] #label选择器,将选择具有指定label标签的pod作为管理范围 type: string #Service的类型 [clusterIP|NodePort|LoadBalancer] clusterIP: string #虚拟服务IP地址 sessionAffinity: string #是否支持session [ClientIP|None] 表示将同一个客户端的访问请求都转发到同一个后端 ports: #service需要暴露的端口 - name: string #端口名称,区分不同的应用的端口 protocol: string #使用的协议 prot: int #service监听的端口 targetPort: int #发送到后端的应用的端口 nodePort: int #当spec.type=NodePort时,指定映射到物理机的端口 status: #当spec.type=LoadBalancer时,设置外部负载均衡器的地址 loadBalancer: ingress: ip: string #外部负载的IP hostname: string #外部负载均衡的主机名
这小编以三个案例介绍:
#webapp-rc.yaml
apiVersion: v1kind: ReplicationControllermetadata: name: webappspec: replicas: 2 template: metadata: name: webapp labels: app: webapp spec: containers: - name: webapp image: docker.io/tomcat imagePullPolicy: IfNotPresent ports: - containerPort: 8080
[root@zy yaml_file]# kubectl create -f webapp-rc.yaml #创建
[root@zy yaml_file]# kubectl get pods -l app=webapp -o yaml|grep podIP #查看pod的IP
[root@zy yaml_file]# curl 172.17.0.5:8080 #访问#创建service管理pod[root@zy yaml_file]# kubectl expose rc webapp
或者 yaml文件
#webapp-svc.yamlapiVersion: v1kind: Servicemetadata: name: webappspec: ports: - port: 8081 targetPort: 8080 selector: app: webapp
[root@zy yaml_file]# kubectl get svc #查看创建的service的IP
[root@zy yaml_file]# curl 10.254.90.131:8081 #通过serviceIP访问pod中的应用② 多端口service有时候一个容器应用也可能提供多个端口的服务,那么在service的定义中也可以相应地设置为将多个端口对应到多个应用中,有点类似于Apache的虚拟主机中的基于端口配置。
apiVersion: v1kind: Servicemetadata: name: webappspec: ports: - port: 8081 targetPort: 8080 name: web - port: 8005 targetPort: 8005 name: management selector: app: webapp
③ 外部服务service
在某些环境中,应用系统需要将一个外部数据库作为后端服务进行连接,或将另一个集群或者namespace中的服务作为服务的后端,这时可以通过创建一个无label selector的service来实现:apiVersion: v1kind: Servicemetadata: name: my-servicespec: ports: - protocol: TCP port: 33060 targetPort: 3306
因为定义一个没有selector的service,系统不会自动创建endpoint,需要手动定义,创建一个与service同名的endpoint,用于指向实际后端访问地址。
#endpointkind: EndPointsapiVersion: v1metadata: name: my-service #与service相同subsets:- addresses: - IP: xxx.xxx.xxx.xxx ports: - port: 3306
此时:
这个service绑定的EndPoint就会连接外部的172.0.1.16:3306的服务,内部访问这个service时,路由就会转发到cluster B 的相应的服务中。 通过上面的三个案例是不是对service有了初步的了解呢,小编在这里给大家总结一下,service的好处: 由于pod被RC或者deploy管理,pod重启之后,pod的IP地址是改变的,如果使用podIP去访问后端的应用,每次都要查IP,但是有了service,service的虚拟IP是固定的,我们只要访问service的IP,至于service如何发现后端的重启的Pod,我们不需要关系 Service的负载作用,如果一个service管理多个pod,而这多个pod提供的是相同的服务,那么service自身也实现了负载: RoundRobin(轮询):将请求发送到后端的各个pod上 SessionAffinity:基于客户端IP地址进行会话,如果SessionAffinity=ClientIP时,同一个客户端发送请求,会被转发到后端相同的pod中。在某些场景中,我们希望自己控制负载均衡的策略,不使用service提供的默认的负载,或者应用程序希望知道属于同组服务的其他实例。Kubernetes提供了Headless Service来实现这个功能,即不为service设置clusterIP,仅通过label selector将后端的pod列表返回给调用的客户端:
apiVersion: v1kind: Servicemetadata: labels: name: cassandra name: cassandraspec: ports:- port: 9042 ClusterIP: None selector: name: cassandra
这样service就不在具有一个特定的clusterIP,对其进行访问将获得包含label“name: cassandra”的全部pod列表,然后客户端程序自行决定如何处理这个pod列表。
对于去中心化类的应用集群,headless Service将非常有用。接下来我们通过搭建一个Cassandra集群来看看headless Service巧妙的使用,自动实现应用集群的创建。 通过对headless Service的使用,实现了Cassandra各节点之间的相互查找和集群的自动搭建。开始搭建:#单个pod Cassandra节点: Cassandra-pod.yamlapiVersion: v1kind: Podmetadata: labels: name: cassandra name: cassandraspec: containers: - args: - /run.sh resources: limits: cpu: "0.5" image: docker.io/shenshouer/cassandra:v5 imagePullPolicy: IfNotPresent name: cassandra ports: - name: cql containerPort: 9042 - name: thrift containerPort: 9160 volumeMounts: - name: data mountPath: /cassandra_data env: - name: MAX_HEAP_SIZE value: 512M - name: HEAP_NEWSIZE value: 100M - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumes: - name: data
#cassandra-svc.yaml
apiVersion: v1kind: Servicemetadata: labels: name: cassandra name: cassandraspec: ports: - port: 9042 selector: name: cassandra
#cassandra-rc.yaml
apiVersion: v1kind: ReplicationControllermetadata: labels: name: cassandra name: cassandraspec: replicas: 1 selector: name: cassandra template: metadata: labels: name: cassandra spec: containers: - command: - /run.sh resources: limits: cpu: 0.5 env: - name: MAX_HEAP_SIZE value: 512M - name: HEAP_NEWSIZE value: 100M - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace image: docker.io/shenshouer/cassandra:v5 imagePullPolicy: IfNotPresent name: cassandra ports: - containerPort: 9042 name: cql - containerPort: 9160 name: thrift volumeMounts: - mountPath: /cassandra_data name: data volumes: - name: data emptyDir: {}
#依次执行一下命令[root@zy yaml_file]# kubectl create -f cassandra-pod.yaml[root@zy yaml_file]# kubectl create -f cassandra-svc.yaml[root@zy yaml_file]# kubectl create -f cassandra-rc.yaml
#此时我们查看pod
[root@zy yaml_file]# kubectl get rc #查看RC
[root@zy yaml_file]# kubectl scale rc cassandra --replicas=2 #副本扩容到2个
[root@zy yaml_file]# kubectl exec -ti cassandra -- nodetool status #查看Cassandra Pod中运行nodetool看见以上的页面,表示扩容的pod已经成功加入到Cassandra集群中。原理解释:因为service是headless Service,他会返回selector中的所有的pod,一开始集群中只有一个pod,所以返回一个,让集群中的pod变成多个时,他会将所有的pod都返回,那么Cassandra是如何处理这新的pod呢?Cassandra镜像它还给Cassandra添加一个定制的SeedProvider,在Cassandra中, SeedProvider设置一个gossip协议用来发现其它Cassandra节点。KubernetesSeedProvider使用内置的Kubernetes发现服务找到KubernetesAPI服务器,然后利用Kubernetes API发现新的节点。就这样headless Service将selector选择中的所有的endpoint,都发送给Cassandra集群,Cassandra集群根据SeedProvider,利用内置的Kubernetes发现服务找到KubernetesAPI服务器,然后利用Kubernetes API发现新的节点,并加入到集群。参考文档:
由于pod和service是kubernetes集群范围内的虚拟的概念,所有集群外部的客户端无法访通过Pod的IP或者service的IP去访问到它们。为了让外部的客户端可以访问这些服务,kubernetes提供了将Pod或者service的端口号映射到物理机上,以使得客户端访问宿主机的端口从而访问到其容器中的服务。
案例1(①通过设置容器基本的hostPort,将容器应用的端口映射到物理机上)#pod-hostport.yamlapiVersion: v1kind: Podmetadata: name: webapp labels: app: webappspec: containers: - name: webapp image: docker.io/tomcat imagePullPolicy: IfNotPresent ports: - containerPort: 8080 hostPort: 8070
#创建之后,访问宿主机的8070端口[root@zy yaml_file]# curl 192.168.130.130:8070访问浏览器:案例2:(使用spec.hostNetWork参数定义) 通过设置pod级别的hostNetWork=true,该Pod中所有容器的端口号都将被直接映射到物理机上,但是需要注意的是,在容器的ports定义中,如果不指定hostPort,则默认为containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值。
apiVersion: v1kind: Podmetadata: name: webapp labels: app: webappspec: hostNetwork: true containers: - name: webapp image: docker.io/tomcat imagePullPolicy: IfNotPresent ports: - containerPort: 8080
案例3(将service的端口号映射到物理机中)
#webapp-nodePort-svc.yamlapiVersion: v1kind: Servicemetadata: name: webappspec: type: NodePort ports: - port: 8080 targetPort: 8080 nodePort: 30000 selector: app: webapp
该service将监听8080端口,并发送到后端selector选择的endpoint的8080端口,对宿主机暴露的端口为30000
[root@zy yaml_file]# curl 192.168.130.130:30000 #访问案例4通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址,LoadBalancer在NodePort基础上,K8S可以请求底层云平台创建一个负载均衡器,将每个Node作为后端,进行服务分发。该模式需要底层云平台(例如GCE)支持。小编这里以一个yaml为例:
apiVersion: v1kind: Servicemetadata: name: my-servicespec: selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376 nodePort: 30061 clusterIP: 10.0.171.12 loadBalancerIP: 78.11.42.19 type: loadBalancerstatus: loadBalancer: ingress: - ip: 146.147.12.155 #这个是云服务商提供的负载IP
这里大家要注意了,这一节非常的重要,希望大家一定细细阅读,以后在分析kubernetes核心时这一节是重中之重的基础。
Kubernetes的服务发现一共有两种方式,1.通过环境变量的方式,2.通过集群范围内的DNS来完成服务名到clusterIP的解析。这里小编带大家了解一下如何搭建DNS服务。Kubernetes提供的虚拟DNS服务名为ksydns,由4个组件组成:
etcd:DNS存储 kube2sky:将kubernetes master中的service注册到etcd skyDNS:提供DNS域名解析服务 healthz:提供skydns服务的健康检查功能① 编写配置文件
② 修改每台node上的kubelet启动参数③ 创建相应的资源对象④ 测试这里小编就不一一介绍了,给出搭建的地址:Kube2sky容器因供应用通过kubernetes master的API获取集群所有的service信息,并持续监控新service的生成,然后写入etcd中:
通过命令查看etcd中存储的service信息:[root@zy ~]#kubectl exec kube-dns-v2-dc1s -c etcd --namespace=kube-system etcdctl ls /skydns/local/cluster/
此时看见目录结果如下:
/skydns/local/cluster/default/skydns/local/cluster/kube-system/skydns/local/cluster/svc可以看见在skydns下是我们配置的cluster.local(域名后缀),之后是命名空间,svc下也通过命名空间生成子目录。然后查看具体的内容:[root@zy ~]# kubectl exec kube-dns-v2-dc1s -c etcd --namespace=kube-system etcdctl get /skydns/local/cluster/default/服务名 #这样就能看见相应的clusterIP和域名映射
然后根据kubectl启动参数的设置(--cluster_dns),kubelet 会在每一个新创建的pod中设置DNS域名解析,配置文件为:/etc/resolv.conf文件,会在其中加入一条,nameserver和search配置:
nameserver DNS解析服务IPsearch default.svc.cluster.local svc.cluster.local cluster.local localdomain
有了这些配置,应用程序就能够想访问网站域名一样,通过服务的名称访问到服务。总的来说就是,kube2sky 通过kubernetes master的API 将新增的service持续存储到etcd中,每一个新增的pod都会有一个/etc/resolv.conf文件,我们在通过服务名称访问服务时,通过skyDNS 查询etcd,获取服务的IP,然后通过服务的IP就能直接访问到服务后端的endpoint中的容器中的应用。
从kubernetes1.6开始,用户可以在kubernetes集群内部配置私有的DNS区域和外部的上游域名服务,在kubernetes的pod定义中支持两个DNS策略,Default和ClusterFirst,dnsPolicy默认是ClusterFirst,如果是Default,域名解析配置则完全从Pod的节点的/etc/resolv.conf中继承下来。
如果dnsPolicy设置的是ClusterFirst,则DNS查询会被发送到kube-dns(skydns)服务。kube-dns服务负责以集群域名为后缀(例cluster.local)进行服务的域名解析。 那么自定义的DNS和上游DNS又是啥呢? 由上图所示,当dnsPolicy设置为ClusterFirst时,DNS首先会被发送到kube-dns的缓存层,从这里检查域名的后缀,如果是cluster.local,则被发送到kube-dns服务,如果是自定义的*.out.of.kubernetes,则被发送到自定义解析器,如果两者均不符合,则被发送到上游的DNS中进行解析。域名解析顺序:kube-dns ------ > 自定义DNS -------- > 上游DNS自定义DNS方式: 从kubernetes1.6开始,集群管理者可以使用configMap指定自定义的存根域和上游的DNS Server。① 安装dnsmasq作为自定义的DNS服务#安装[root@zy ~]# yum install -y dnsmasq#生成一个自定义的DNS记录文件 /tmp/hosts[root@zy ~]# echo "192.168.130.131 server.out-of.kubernetes" > /tmp/hosts#启动DNS服务[root@zy ~]# dnsmasq -q -d -h -q -R -H /tmp/hosts#参数解释:-d:以debug模式启动,在前台运行,便于观察日志-q:输出查询记录-h:不使用/etc/hosts-R:不使用/etc/resolve.conf-H:使用自定义的文件作为DNS记录② 创建自定义DNS的configMap#dns-configmap.yaml
apiVersion: v1kind: ConfigMapmetadata: name: kube-dns namespace: kube-systemdata: stubDomains: | {"out-of.kubernetes" : ["192.168.130.130"] } #自定义DNS服务器地址 upstreamNameservers: | #上游DNS地址 ["8.8.8.8","8.8.4.4"]注意:stubDomains:表示存根域名,自定义DNS就在这里配置,Key是DNS后缀,value是一组DNS服务IPupstreamNameservers:表示上游DNS配置,如果指定了,那么从节点/etc/resovl.conf就会被覆盖。最多指定3个IP。
[root@zy ~]# kubectl create -f dns-configmap.yaml
③ 测试
apiVersion: v1kind: Podmetadata: name: testerspec: dnsPolicy: ClusterFirst containers: - name: busybox image: docker.io/busybox imagePullPolicy: IfNotPresent command: ["sleep"] args: ["3600"]
创建一个pod,然后进入其中
[root@zy yaml_file]# kubectl exec -it tester – sh/ # ping server.out-of.kubernetes
此时就会在dnsmasq的输出日志中,看见,server.out-of.kubernetes被转发到自定义DNS服务:192.168.130.130,然后通过DNS服务器,根据/tmp/hosts域名和IP的映射找到192.168.130.131。
Ingerss产生的原因:我们知道pod被deployment/RC管理的时候,pod重启之后其IP可能会改变,为了能准确的访问到pod中的服务,kubernetes使用service,而service会在宿主机上提供一个端口用于客户端访问,但是如果service很多的话其维护成本就会很高。如果可以借助于nginx的类似于虚拟主机的方式,通过不同的URL能够访问到后端的不同service,那么就少了service对宿主机的大量的端口映射,如何能做到这样呢?kubernetes1.1开始,新增了一个Ingress的资源对象,用于解决上述问题。
Ingerss介绍:Ingress 包含两大组件:Ingress Controller 和 Ingress。原理图:Ingress Controller作用:由于我们是使用nginx的方式实现,那么每一次有新的service或者新的Ingress规则时,就要修改一次nginx.conf文件,这样实在太麻烦,所以Ingress Controller 就是专门解决这个问题,通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化,然后读取他,按照他自己模板生成一段 Nginx 配置,再写到 Nginx Pod 里,最后 reload 一下。Ingress Controller-service:为了让Ingress Controller可以对外提供服务,这里需要一个service,而service监听的端口就是80(http)|443(https)上图所示,该service映射到物理机的端口是30080和30443。通过Ingress访问后端应用服务的过程(以web服务为例):首先定义一个deployment维持三个web服务的副本,然后给其web服务定义一个名为myapp的service,此时编写Ingress的规则通过host:myapp.zzy.com,并绑定了名为myapp的service,即当客户端访问myapp.zzy.com:30080时,会被转发到Ingress Controller-service的80端口上,根据Ingress Controller更新的Ingress规则,会将请求转发到myapp:80,然后就能直接访问后端的pod中的容器,最后容器将请求响应会客户端。注意:Ingress Controller将基于Ingress规则将客户端请求直接转发到service对应的后端的endpoint。apiVersion: extensions/v1beta1kind: Ingressmetadata: name: mywebsite-ingressspec: rules: - host: mywebsite.com http: paths: - path: /demo backend: serviceName: webapp servicePort: 8080
• rules:用于定义当前Ingress资源的转发规则列表;由rules定义规则,或没有匹配到规则时,所有的流量会转发到由backend定义的默认后端。
• backend:默认的后端用于服务那些没有匹配到任何规则的请求;定义Ingress资源时,必须要定义backend或rules两者之一,该字段用于让负载均衡器指定一个全局默认的后端。backend对象的定义由2个必要的字段组成:serviceName和servicePort,分别用于指定流量转发的后端目标Service资源名称和端口。• host:包含 于 使用 的 TLS 证书 之内 的 主机 名称 字符串 列表部署步骤:
部署方法小编已经在下面给出:自己动手也是成长的一部分嘛!
小编也从网上了找了许多的ingress 部署博客,好像基本上都是wget一些yaml文件,但是给出的地址好像都不能访问了,所以还是老老实实的看文档搭建吧,加油加油!① 转发到单个后端服务上
apiVersion: extensions/v1beta1kind: Ingressmetadata: name: test-ingressspec: backend: serviceName: myweb servicePort: 8080
上面的配置对Ingress Controller的访问请求都将被转发到“myweb:8080”这个服务上。
② 同一域名下,不同的URL路径被转发到不同的服务上apiVersion: extensions/v1beta1kind: Ingressmetadata: name: test-ingressspec: rules: - host: mywebsite.com http: paths: - path: /web backend: serviceName: web-service servicePort: 80 - path: /api backend: serviceName: api-service servicePort: 8081
以上配置当访问:
mywebsite.com/web:80 ------转发到------ web-service:80mywebsite.com/api:80 ------转发到------ api-service:8081③ 不同域名被转发到不同的服务apiVersion: extensions/v1beta1kind: Ingressmetadata: name: testspec: rules: - host: foo.bar.com http: paths: - backend: serviceName: service1 servicePort: 80 - host:bar.foo.com http: paths: - backend: serviceName: service2 servicePort: 80
访问foo.bar.com-- service1:80 访问bar.foo.com -- service2:80
④ 不使用域名的转发规则apiVersion: extensions/v1beta1kind: Ingressmetadata: name: test-ingressspec: rules: - http: paths: - path: /demo backend: serviceName: webapp servicePort: 8080
这种配置,通过任意一条运行的ingress-controller 的node都能访问到后端的服务。
注意:这种方式默认是不能使用https访问的,如果想使用https访问,需要在编写Ingress规则的时候,加入一个annotation ”ingress.kubernetes.io/ssl-redirect=false” 来关闭强制启用HTTPS的设置。由于小编之前用的是单机版的kubernetes集群,之前遇到过一些问题导致pod无法启动,然后就修改了apiservice的配置:
就将这个两个配置删除了。然后创建Cassandra 的pod的时候就报错:然后查看:[root@zy yaml_file]# kubectl get serviceaccount解决:在/etc/kubernetes/apiserver 中的--admission_control加入ServiceAccount:然后在/etc/kubernetes/controller-manager配置:--service_account_private_key_file=/var/run/kubernetes/apiserver.key之后重启这两个服务:再执行命令[root@zy yaml_file]# kubectl get serviceaccount小编本来以为这样问题就解决了,结果查询Cassandra 的pod的日志发现:小编初步推断可能是集群没有搭建DNS:因为在查看SeedProvider 源码时:后期会慢慢排查,将问题解决!
在service暴露端口时,发现外部访问时,之后 可以使用IP访问,其他主机或者浏览器访问不到Kubernetes是1.5.2版本,这里小编设置了service的type为NodePort,并且nodePort设置了一个值,最终pod和service都启动正常,使用clusterIP和本机IP都可以访问,但是外部集群无法访问。
原因:使用service+NodePort时,其中的网络是上图所示,客户端访问时,需要通过kube-proxy这个服务,但是在1.2以上这个kube-proxy服务在启动时需要添加参数:把KUBE_PROXY_ARGS=”“改为KUBE_PROXY_ARGS=”–proxy-mode=userspace”解决:
修改master的/etc/kubernetes/proxy将其中的启动命令的参数添加:KUBE_PROXY_ARGS="--proxy-mode=userspace"重启kube-proxy服务:[root@zy yaml_file]# systemctl restart kube-proxy
然后在启动相应的pod和service,之后浏览器访问:
OK:或者:在默认的iptables mode下,修改(vim /etc/sysctl.conf)文件,加入:net.ipv4.ip_forward=1重启主机,然后在通过service访问即可。到这里可能大家还是不太明白为什么service不能外部访问,需要修改kube-proxy的配置,这里小编就给大家介绍一下kube-proxy与service的关系:
kube-proxy其实就是管理service的访问入口,包括集群内Pod到Service的访问和集群外访问service。kube-proxy管理sevice的Endpoints,该service对外暴露一个Virtual IP,也成为Cluster IP, 集群内通过访问这个Cluster IP:Port就能访问到集群内对应的serivce下的Pod。service是通过Selector选择的一组Pods的服务抽象,其实就是一个微服务,提供了服务的LB和反向代理的能力,而kube-proxy的主要作用就是负责service的实现。而kube-proxy内部原理:kube-proxy当前实现了两种proxyMode:userspace和iptables。其中userspace mode是v1.0及之前版本的默认模式,从v1.1版本中开始增加了iptables mode,在v1.2版本中正式替代userspace模式成为默认模式。小编的集群是1.5.2的默认的是iptables,所以需要做一些配置,因此我们改为了userspace,就可以让外部通过service暴露的端口映射到宿主机上来访问服务啦。
Userspace:userspace是在用户空间,通过kube-proxy来实现service的代理服务。Iptables:它完全利用Linux内核iptables来实现service的代理和LB转载于:https://blog.51cto.com/14048416/2401291