概述
需要解决的问题:东西流量的访问
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 wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d21h <none
Service定义
service的通用yaml资源配置文件如下
apiVersion: v1
kind: Service
metadata: # 元数据
name: string #服务名称
namespace: string # 如果不填写的话,默认为 default
labels: # 自定义标签属性列表
- name: string
annotations: # 自定义注解属性列表
- name: string
spec: # 详细描述
selector: [] # 标签选择器
type: string # 可选有: ClusterIP(默认)/NodePort/LoadBalancer(外接负载均衡器时选择这个)
clusterIP: string # 不指定的话系统自动分配 IP,当 type=LoadBalancer 时必须指定
sessionAffinity: string # 是否支持 Session,默认值为空,可选值为 ClientIP,ClientIP 表示将同一个客户端的访问请求都转发到同一个后端 Pod
ports: # 需要暴露的端口列表
- name: string # 端口名称
protocols: string # 端口协议,支持 TCP 和 UDP,默认值为 TCP
port: int # 服务端口号,也就是service自己的端口
targetPort: int # 需要后端Pod的端口号
nodePort: int # 指定映射到Node节点的端口号
status: # 当 spec.type=LoadBalancer 时,设置外部负载均衡器的地址,用于公有云环境
loadBalancer:
ingress: # 外部负载均衡器
ip: string # 外部负载均衡器的 IP 地址
hostname: string # 外部负载均衡器的主机名
eg:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 8080
targetPort: 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: v1
kind: Service
metadata:
name: mysql-service
spec:
ports:
- port: 33306
targetPort: 3306
selector:
name: mysql-pod
targetPort
targetPort是pod上的端口,从port/nodePort
上来的数据,经过kube-proxy
流入到后端pod的targetPort上,最后进入容器。
与制作容器时暴露的端口一致(使用DockerFile中的EXPOSE),例如官方的nginx(参考DockerFile)暴露80端口。 对应的service.yaml如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort // 配置NodePort,外部流量可访问k8s中的服务
ports:
- port: 30080 // 服务访问端口
targetPort: 80 // pod控制器中定义的端口
nodePort: 30001 // NodePort
selector:
name: nginx-pod
总的来说,port和nodePort都是service的端口,前者暴露给k8s集群内部服务访问,后者暴露给k8s集群外部流量访问。从这两个端口到来的数据都需要经过反向代理kube-proxy,流入后端pod的targetPort上,最后到达pod内容器的containerPort
Hello world
创建nginx.yaml文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-service
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort # 指定服务类型为 NodePort
ports:
- port: 8080 # 指定集群内部 service 的端口
targetPort: 80 #指定 Pod 的端口
nodePort: 30000 # 指定外部连接的端口
selector:
app: nginx # 标签选择器,选择带有 app=nginx 的 Pod
执行
$ kubectl apply -f nginx-service.yaml
deployment.apps/nginx-service created
service/nginx-service created
查看
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-service-54f669bb6c-7vzmq 1/1 Running 0 59s
查看severice
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 284d
nginx-service NodePort 10.103.155.57 <none> 8080:30000/TCP 2m26s
查看详细信息
$ kubectl describe svc nginx-service
Name: nginx-service
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"nginx-service","namespace":"default"},"spec":{"ports":[{"nodePort...
Selector: app=nginx
Type: NodePort
IP: 10.103.155.57
LoadBalancer Ingress: localhost
Port: <unset> 8080/TCP
TargetPort: 80/TCP
NodePort: <unset> 30000/TCP
Endpoints: 10.1.0.139:80,10.1.0.140:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <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/v1
kind: Deployment
metadata:
name: httpbin-service
spec:
selector:
matchLabels:
app: httpbin
replicas: 1
template:
metadata:
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kennethreitz/httpbin
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpbin-service
spec:
type: NodePort # 指定服务类型为 NodePort
ports:
- port: 8000 # 指定集群内部 service 的端口
targetPort: 80 #指定 Pod 的端口
nodePort: 30001 # 指定外部连接的端口
selector:
app: httpbin # 标签选择器,选择带有 app=nginx 的 Pod
查看启动的pod
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
httpbin-service-6754cf494b-2gprb 1/1 Running 0 5m11s
nginx-service-54f669bb6c-7vzmq 1/1 Running 0 21h
查看pod的环境变量
$ kubectl delete svc pioneering-raccoon-mysql
service "pioneering-raccoon-mysql" deleted
baxiangdeMacBook:service baxiang$ kubectl exec httpbin-service-6754cf494b-2gprb env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=httpbin-service-6754cf494b-2gprb
KUBERNETES_PORT_443_TCP_PORT=443
PIONEERING_RACCOON_MYSQL_PORT_3306_TCP_ADDR=10.98.92.199
HTTPBIN_SERVICE_PORT=tcp://10.106.158.114:8000
HTTPBIN_SERVICE_PORT_8000_TCP=tcp://10.106.158.114:8000
HTTPBIN_SERVICE_PORT_8000_TCP_PROTO=tcp
HTTPBIN_SERVICE_PORT_8000_TCP_PORT=8000
HTTPBIN_SERVICE_PORT_8000_TCP_ADDR=10.106.158.114
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
PIONEERING_RACCOON_MYSQL_PORT_3306_TCP_PORT=3306
PIONEERING_RACCOON_MYSQL_PORT_3306_TCP=tcp://10.98.92.199:3306
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
NGINX_SERVICE_SERVICE_HOST=10.103.155.57
HTTPBIN_SERVICE_SERVICE_PORT=8000
KUBERNETES_SERVICE_PORT_HTTPS=443
NGINX_SERVICE_PORT=tcp://10.103.155.57:8080
HTTPBIN_SERVICE_SERVICE_HOST=10.106.158.114
KUBERNETES_SERVICE_HOST=10.96.0.1
NGINX_SERVICE_PORT_8080_TCP_PORT=8080
NGINX_SERVICE_PORT_8080_TCP_ADDR=10.103.155.57
PIONEERING_RACCOON_MYSQL_SERVICE_PORT=3306
PIONEERING_RACCOON_MYSQL_PORT=tcp://10.98.92.199:3306
PIONEERING_RACCOON_MYSQL_PORT_3306_TCP_PROTO=tcp
NGINX_SERVICE_PORT_8080_TCP_PROTO=tcp
PIONEERING_RACCOON_MYSQL_SERVICE_PORT_MYSQL=3306
NGINX_SERVICE_SERVICE_PORT=8080
NGINX_SERVICE_PORT_8080_TCP=tcp://10.103.155.57:8080
PIONEERING_RACCOON_MYSQL_SERVICE_HOST=10.98.92.199
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT=443
HOME=/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"
}