概述
需要解决的问题:东西流量的访问
1 pod的访问地址存在动态变化,需要实现服务发现与注册(Eureka 或者nacos)
2 pod的访问策略(负载均衡)
Pod和Service关系
由于 Kubernetes 集群中每个 Pod(容器组)都有一个唯一的 IP 地址(即使是同一个 Node 上的不同 Pod),我们需要一种机制,为前端系统屏蔽后端系统的 Pod(容器组)在销毁、创建过程中所带来的 IP 地址的变化。
Service(服务) 提供了这样的一个抽象层,它选择具备某些特征的 Pod(容器组)并为它们定义一个访问方式。Service(服务)使 Pod(容器组)之间的相互依赖解耦(原本从一个 Pod 中访问另外一个 Pod,需要知道对方的 IP 地址)。
一个 Service(服务)和 Pod(容器组)建立关联的方式通过 Label和Selector(标签选择器)。
查看service
$ kubectl get service -o wideNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTORkubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d21h <none
Service定义
service的通用yaml资源配置文件如下
apiVersion: v1kind: Servicemetadata: # 元数据name: string #服务名称namespace: string # 如果不填写的话,默认为 defaultlabels: # 自定义标签属性列表- name: stringannotations: # 自定义注解属性列表- name: stringspec: # 详细描述selector: [] # 标签选择器type: string # 可选有: ClusterIP(默认)/NodePort/LoadBalancer(外接负载均衡器时选择这个)clusterIP: string # 不指定的话系统自动分配 IP,当 type=LoadBalancer 时必须指定sessionAffinity: string # 是否支持 Session,默认值为空,可选值为 ClientIP,ClientIP 表示将同一个客户端的访问请求都转发到同一个后端 Podports: # 需要暴露的端口列表- name: string # 端口名称protocols: string # 端口协议,支持 TCP 和 UDP,默认值为 TCPport: int # 服务端口号,也就是service自己的端口targetPort: int # 需要后端Pod的端口号nodePort: int # 指定映射到Node节点的端口号status: # 当 spec.type=LoadBalancer 时,设置外部负载均衡器的地址,用于公有云环境loadBalancer:ingress: # 外部负载均衡器ip: string # 外部负载均衡器的 IP 地址hostname: string # 外部负载均衡器的主机名
eg:
kind: ServiceapiVersion: v1metadata:name: my-servicespec:selector:app: MyAppports:- protocol: TCPport: 8080targetPort: 80
上述配置将创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 80,并且具有标签 “app=MyApp” 的 Pod 上。这个 Service 将被指派一个 IP 地址(通常称为 “Cluster IP”)。
Type类型
1.ClusterIP
ClusterIP是 kubernetes 的默认方式,只能集群内部访问,分为普通 Service 和 Headless Service。
- 普通Service:创建的 Service 会分配一个集群内部可访问的固定虚拟 IP,这是最常使用的方式。
- Headless Service:创建的 Service 不会分配固定的虚拟 IP,同样也不会通过 kube-proxy 做反向代理和负载均衡,主要通过 DNS 提供稳定的网络 ID 进行访问,通常用于 StatefulSet 中。
2.NodePort
NodePort:拥有并且使用 ClusterIP,并且将 Service 的 port 端口映射到集群中每个 Node 节点的相同端口 port,这样在集群外部访问服务可以直接使用 nodeIP:nodePort 进行访问。
3.LoadBalancer
LoadBalancer:在 NodePort 的基础上(也就是拥有 ClusterIP 和 nodePort),还会向所处的公有云申请负载均衡器 LB(负载均衡器的后端直接映射到各 Node 节点的 nodePort 上),这样就实现了通过外部的负载均衡器访问服务。
4.ExternalName
ExternalName:这是 Service 的一种特例形式。主要用于解决运行在集群外部的服务问题,这种方式下会返回外部服务的别名来为集群内部提供服务。上述提到的 3 种模式主要依赖于 kube-proxy,而这种模式依赖于 kube-dns 的层级。
端口Port
nodePort
nodePort提供了集群外部客户端访问service的一种方式,:nodePort提供了集群外部客户端访问service的端口,即nodeIP:nodePort提供了外部流量访问k8s集群中service的入口。nodePort 的范围是 30000-32767,设置端口时不能够与已经在使用的有冲突。
比如外部用户要访问k8s集群中的一个Web应用,那么我们可以配置对应service的type=NodePort,nodePort=30001。其他用户就可以通过浏览器http://node:30001访问到该web服务。
而数据库等服务可能不需要被外界访问,只需被内部服务访问即可,那么我们就不必设置service的NodePort。
port
port是暴露在cluster ip上的端口,:port提供了集群内部客户端访问service的入口,即clusterIP:port。
mysql容器暴露了3306端口(参考DockerFile),集群内其他容器通过33306端口访问mysql服务,但是外部流量不能访问mysql服务,因为mysql服务没有配置NodePort。对应的service.yaml如下:
apiVersion: v1kind: Servicemetadata:name: mysql-servicespec:ports:- port: 33306targetPort: 3306selector:name: mysql-pod
targetPort
targetPort是pod上的端口,从port/nodePort上来的数据,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。
与制作容器时暴露的端口一致(使用DockerFile中的EXPOSE),例如官方的nginx(参考DockerFile)暴露80端口。 对应的service.yaml如下:
apiVersion: v1kind: Servicemetadata:name: nginx-servicespec:type: NodePort // 配置NodePort,外部流量可访问k8s中的服务ports:- port: 30080 // 服务访问端口targetPort: 80 // pod控制器中定义的端口nodePort: 30001 // NodePortselector:name: nginx-pod
总的来说,port和nodePort都是service的端口,前者暴露给k8s集群内部服务访问,后者暴露给k8s集群外部流量访问。从这两个端口到来的数据都需要经过反向代理kube-proxy,流入后端pod的targetPort上,最后到达pod内容器的containerPort
Hello world
创建nginx.yaml文件
apiVersion: apps/v1kind: Deploymentmetadata:name: nginx-servicespec:selector:matchLabels:app: nginxreplicas: 1template:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.14ports:- containerPort: 80---apiVersion: v1kind: Servicemetadata:name: nginx-servicespec:type: NodePort # 指定服务类型为 NodePortports:- port: 8080 # 指定集群内部 service 的端口targetPort: 80 #指定 Pod 的端口nodePort: 30000 # 指定外部连接的端口selector:app: nginx # 标签选择器,选择带有 app=nginx 的 Pod
执行
$ kubectl apply -f nginx-service.yamldeployment.apps/nginx-service createdservice/nginx-service created
查看
$ kubectl get podsNAME READY STATUS RESTARTS AGEnginx-service-54f669bb6c-7vzmq 1/1 Running 0 59s
查看severice
$ kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.96.0.1 <none> 443/TCP 284dnginx-service NodePort 10.103.155.57 <none> 8080:30000/TCP 2m26s
查看详细信息
$ kubectl describe svc nginx-serviceName: nginx-serviceNamespace: defaultLabels: <none>Annotations: kubectl.kubernetes.io/last-applied-configuration:{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"nginx-service","namespace":"default"},"spec":{"ports":[{"nodePort...Selector: app=nginxType: NodePortIP: 10.103.155.57LoadBalancer Ingress: localhostPort: <unset> 8080/TCPTargetPort: 80/TCPNodePort: <unset> 30000/TCPEndpoints: 10.1.0.139:80,10.1.0.140:80Session Affinity: NoneExternal Traffic Policy: ClusterEvents: <none>
外部访问
端口是30000
$ curl http://127.0.0.1:30000
服务发现
Service 服务发现目前有两种类型:环境变量和 DNS
ENV/环境变量
kubelet 会为每个 Pod(容器)添加一组环境变量,其中就包括当前系统中已经存在的 Service 的 IP 地址和端口号。
环境变量的格式如下所示:
{SVCNAME}_SERVICE_HOST=host{SVCNAME}_SERVICE_PORT=port
环境变量名都必须为大写,如果其中有连字符的会被转换为下划线。
注意的问题是:当某个特定的 Service 晚于 Pod 的创建,那么先创建的 Pod 就不会注册该 Service 的环境变量。
创建一个新的服务httpbin-service.yaml
apiVersion: apps/v1kind: Deploymentmetadata:name: httpbin-servicespec:selector:matchLabels:app: httpbinreplicas: 1template:metadata:labels:app: httpbinspec:containers:- name: httpbinimage: kennethreitz/httpbinports:- containerPort: 80---apiVersion: v1kind: Servicemetadata:name: httpbin-servicespec:type: NodePort # 指定服务类型为 NodePortports:- port: 8000 # 指定集群内部 service 的端口targetPort: 80 #指定 Pod 的端口nodePort: 30001 # 指定外部连接的端口selector:app: httpbin # 标签选择器,选择带有 app=nginx 的 Pod
查看启动的pod
$ kubectl get podsNAME READY STATUS RESTARTS AGEhttpbin-service-6754cf494b-2gprb 1/1 Running 0 5m11snginx-service-54f669bb6c-7vzmq 1/1 Running 0 21h
查看pod的环境变量
$ kubectl delete svc pioneering-raccoon-mysqlservice "pioneering-raccoon-mysql" deletedbaxiangdeMacBook:service baxiang$ kubectl exec httpbin-service-6754cf494b-2gprb envPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=httpbin-service-6754cf494b-2gprbKUBERNETES_PORT_443_TCP_PORT=443PIONEERING_RACCOON_MYSQL_PORT_3306_TCP_ADDR=10.98.92.199HTTPBIN_SERVICE_PORT=tcp://10.106.158.114:8000HTTPBIN_SERVICE_PORT_8000_TCP=tcp://10.106.158.114:8000HTTPBIN_SERVICE_PORT_8000_TCP_PROTO=tcpHTTPBIN_SERVICE_PORT_8000_TCP_PORT=8000HTTPBIN_SERVICE_PORT_8000_TCP_ADDR=10.106.158.114KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443PIONEERING_RACCOON_MYSQL_PORT_3306_TCP_PORT=3306PIONEERING_RACCOON_MYSQL_PORT_3306_TCP=tcp://10.98.92.199:3306KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1NGINX_SERVICE_SERVICE_HOST=10.103.155.57HTTPBIN_SERVICE_SERVICE_PORT=8000KUBERNETES_SERVICE_PORT_HTTPS=443NGINX_SERVICE_PORT=tcp://10.103.155.57:8080HTTPBIN_SERVICE_SERVICE_HOST=10.106.158.114KUBERNETES_SERVICE_HOST=10.96.0.1NGINX_SERVICE_PORT_8080_TCP_PORT=8080NGINX_SERVICE_PORT_8080_TCP_ADDR=10.103.155.57PIONEERING_RACCOON_MYSQL_SERVICE_PORT=3306PIONEERING_RACCOON_MYSQL_PORT=tcp://10.98.92.199:3306PIONEERING_RACCOON_MYSQL_PORT_3306_TCP_PROTO=tcpNGINX_SERVICE_PORT_8080_TCP_PROTO=tcpPIONEERING_RACCOON_MYSQL_SERVICE_PORT_MYSQL=3306NGINX_SERVICE_SERVICE_PORT=8080NGINX_SERVICE_PORT_8080_TCP=tcp://10.103.155.57:8080PIONEERING_RACCOON_MYSQL_SERVICE_HOST=10.98.92.199KUBERNETES_PORT=tcp://10.96.0.1:443KUBERNETES_PORT_443_TCP_PROTO=tcpKUBERNETES_SERVICE_PORT=443HOME=/root
DNS
通过在集群中部署 CoreDNS 服务(在老版本的 kubernetes 集群中使用的是 KubeDNS)来实现集群内部 Pod 通过 DNS 方式进行通讯
在 nginx Pod 中通过 DNS 解析的方式来访问名为 httpbin 的服务
$ kubectl exec -it nginx-service-54f669bb6c-7vzmq bash# apt-get update# apt-get install curl# curl httpbin-service:8000/ip{"origin": "10.1.0.139"}
