[kubernetes in Action实践]五、与pod通信——服务
k8s的服务使用expose暴露rc来创建服务对象使用yaml文件创建服务使用expose暴露rc来创建服务对象kubectl expose rc kubia --type=LoadBalancer --name kubia-http使用yaml文件创建服务
服务的简单使用
使用expose暴露rc来创建服务对象
kubectl expose rc kubia --type=LoadBalancer --name kubia-http
使用yaml文件创建服务
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
利用service的集群IP访问pod
kubectl exec kubia-gs8lk -- curl -s http://10.111.222.53
会话亲和性
spec:
# 会话亲和性,使都访问到一个pod
sessionAffinity: ClientIP
如果加上这个配置,就能使来自同 一个 client IP 的所有请求转发至同一个pod。
注:Kubernetes 仅仅支持两种形式的会话亲和性服务: None 和 ClientIP。
定义多端口的服务
你pod监听2个端口的时候就可以为服务指定多个端口。
在创建一个有多个瑞口的服务的时候,必须给每个瑞口指定名字。
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
selector:
app: kubia
使用命名端口
如果pod端口的定义如下所示
kind: Pod
spec:
containers:
- name: kubia
ports:
- name: http
containerPort: 8080
- name: https
containerPort: 8443
那么就可以在服务spec中按名称引用这些端口
kind: Service
apiVersion: v1
metadata:
name: kubia
spec:
ports:
- port: 80
name: http
targetPort: http
# 将端口80映射到容器中被称为http的端口
- port: 443
name: https
targetPort: https
# 将端口80映射到容器中被称为https的端口
使用命名端口最大的好处是即使更换端口号也无须更改服务spec
服务发现
发现服务IP和端口的方式
通过环境变量发现服务
pod开始运行时, Kubemetes会初始化一系列的环境变量指向现在存在的服务。因此如果创建的服务早于pod,pod上的进程就可以根据环境变量获得服务的ip和端口。
- 查看pod的环境变量
kubectl exec kubia-2zvxk env
KUBIA_SERVICE_HOST=10.102.1.194
KUBIA_SERVICE_PORT=80
ps: 如果没有以上变量,则可能服务创建晚于pod,可以先删除所有pod,使得rc重新创建pod。(kubectl delete po --all注意这是删除所有的,你可以只删除具有某某标签的)
例子:
当前端 pod 需要后端数据库服务 pod 时,可以通过名为 backend-database的服务将后端pod暴露出来,然后前端pod通过环境变量 BACKEND_DATABASE_SERVICE_HOST和BACKEND_DATABASE_SERVICE _PORT 去获得IP地址和端口信息。
注意:服务名称的横杠被转换为下划线,环境变量名称前的服务名都是大写的
通过DNS发现服务
在pod内部可通过FQDN访问服务
kubectl exec -it kubia-jc5v4 bash
curl http://kubia.default.svc.cluster.local
curl http://kubia.default
curl http://kubia
cat /etc/resolv.conf
详细看:https://www.cnblogs.com/boshen-hzb/p/7495344.html
连接集群外部的服务
endpoint介绍
endpoint介于服务与pod之间,服务通过选择器Selector
创建endpoint资源列表,它存储着pod的ip和端口信息。当连接到服务时,服务代理选择其中的ip和端口之一,传入连接重定向到该位置监听的服务器。
手动设置endpoint
服务的endpoint与服务解耦后,可以手动配置和更新它们。
创建没有选择器的服务
service
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
ports:
- port: 80
endpoint
apiVersion: v1
kind: Endpoints
metadata:
name: external-service # 与服务名相同
subsets:
- addresses:
- ip: 11.11.11.11
- ip: 22.22.22.22
# 服务将连接重定向到endpoint的IP地址
ports:
- port: 80
# endport的目标端口
这样创建后,服务就能当作有选择器的服务正常使用。
如果稍后决定将外部服务迁移到Kubemetes中运行的pod, 可以为服务添加选择器,从而对Endpoint进行自动管理。反过来也是一样的——将选择器从服务中移除,Kubenetes 将停止更新Endpoints。这意味着服务的IP地址可以保持不变,同时服
务的实际实现却发生了改变。
为外部服务创建别名
除了手动配置服务的Endpoint来代替公开外部服务方法,有一种更简单的方法,就是通过其完全限定域名(FQDN)访问外部服务
创建ExtemalName类型的服务
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
type: ExternalName # ExternalName类型
externalName: someapi.somecompany.com # 实际服务的完全限定域名
ports:
- port: 80
服务创建完成后,pod可以通过external-service.default.svc.cluster.local域名(甚至是external-service)连接到外部服务,而不是使用服务的实际FQDN。
在以后如果将其指向不同的服务,只需简单地修改externalName属性,或者将类型重新变回Cluster IP并为服务创建Endpoint
(无论是手动创建,还是对服务上指定标签选择器使其自动创建)
ExternalName服务仅在DNS级别实施,为服务创建了简单的CNAME
DNS记录。 因此,连接到服务的客户端将直接连接到外部服务,完全绕过服务代理。出于这个原因, 这些类型的服务甚至不会获得集群 IP。
将服务暴露给外部客户端
方案:
- 将服务类型设置成NodePort
每个集群节点都会打开一个端口,Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 Service。 - 将服务的类型设置成LoadBalance,NodePort的一种扩展
是服务可以通过专用的负载均衡器访问,负载均衡器将流量重定向到跨所有节点的节点端口,客户端通过负载均衡器的ip连接到服务 - 创建一个Ingeress资源(完全不同的机制),通过一个IP地址公开多个服务,运行了网络协议第7层(HTTP)。
使用 NodePort 类型的服务
通过创建NodePort服务, 可以让Kubenetes在其所有节点上保留一个端口(所有节点上都使用相同的端口号),并将传入的连接转发给作为服务部分的pod。
这与常规服务类似(它们的实际类型是ClusterIP), 但是不仅可以通过服务的内部集群IP访问NodePod服务,还可以通过任何节点的IP和预留节点端口访问NodePort服务。
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort
ports:
- port: 80 # 集群ip的端口号
targetPort: 8080 # 背后pod的目标端口号
nodePort: 30123 # 通过集群节点的30123端口号可以访问该服务,如果不设置,就会随机取
selector:
app: kubia
创建好NodePort类型服务后,查看svc,发现集群IP是10.106.117.182,EXTERNAL-IP是none,端口是80:30123
这意味着,当前可访问容器的方式有
1.任意节点IP:30123(外部)
2.集群IP:80(内部)
更改防火墙规则, 让外部客户端访问我们的 NodePort 服务
在通过节点端口访间服务之前, 需要配置谷歌云平台的防火墙, 以允许外部连接到该端口上的节点
我个人是本机测试,没有使用谷歌云平台,这里贴上命令看看
gcloud compute firewall-rules create kubia-svc-rule --allow=tcp:30123
如何查看节点ip
1.kubectl describe node kind-worker3
直接在返回的信息中找
2.kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'
通过jsonpath过滤出ip信息
要了解有关kubectl使用JSONPath的更多信息, 请参阅http://kubernetes.io/docs/user-guide/jsonpath上的文档。
通过负载均衡器将服务暴露出来
在云提供商上运行的Kubernetes集群通常支持从云基础架构自动提供负载平衡器。 所有需要做的就是设置服务的类型为Load Badancer而不是NodePort。 负载均衡器拥有自己独一无二的可公开访问的 IP 地址, 并将所有连接重定向到服务。可以通过负载均衡器的 IP 地址访问服务。如果Kubemetes在不支持Load Badancer服务的环境中运行,则不会调配负载平衡器,但该服务仍将表现得像一个NodePort服务。这是因为Load Badancer服务是NodePort服务的扩展,可以保证把请求传播到健康的节点。
apiVersion: v1
kind: Service
metadata:
name: kubia-loadbalancer
spec:
type: LoadBalancer
ports:
- port: 80 # 集群ip的端口号
targetPort: 8080 # 背后pod的目标端口号
selector:
app: kubia
实际使用:
我本机肯定不支持,但依然可以当作nodeport使用:
了解外部连接的特性
- 了解并防止不必要的网络跳数
当外部客户端通过节点端口连接到服务时,随机选择的pod并不一定在接收连接的同一节点上运行(即可能请求的节点1:32143,可能随机访问到的是节点2里的pod)。可能需要额外的网络跳转才能到达pod, 但这种行为并不符合期望。
可以通过将服务配置为仅将外部通信重定向到接收连接的节点上运行的pod来阻止此额外跳数。这是通过在服务的spec部分中设置externalTrafficPolicy: Local
字段来完成的。
如此,服务代理将选择节点本地运行的pod,如果没有本地pod,则连接将挂起,因此,需要确保负载均衡器将连接转发给至少具有一个pod的节点(如果是LoadBalancer)
还有一个缺点就是,因为负载均衡器使节点的连接均匀分布,部署pod多的节点的pod接受的连接少。如图:
- 服务记录真实的客户端IP
通常,当集群内的客户端连接到服务时,支持服务的pod可以获取客户端的IP地址 。但是,当通过节点端口接收到连接时,由于对数据包执行了源网络地址转换(SNAT), 因此数据包的源IP将发生更改。后端的pod无法看到实际的客户端IP,这对于某些需要了解客户端IP的应用程序来说可能是个问题。例如,对于Web服务器,这意味着访问日志无法显示浏览器的IP。
上面的externalTrafficPolicy: Local
会保留源地址IP。因为在接收连
接的节点和托管目标pod的节点之间没有额外的跳跃,它不执行SNAT。
官网解释:Kubernetes 创建一个外部负载均衡器
通过Ingress暴露服务
Ingress: 进入或进入的行为;进入的权利、手段、地点、入口
为什么需要Ingress
- 每个LoadBalancer服务需要自己的负载均衡器,以及独有的公有IP地址,而Ingress只需要一个公网IP就能为许多服务提供访问。
当客户端向Ingress发送HTTP请求时,Ingress会根据请求的主机名和路径决定请求转发的服务
- Ingress 在网络栈 (HTTP) 的应用层操作,并且可以提供一些服务(第4层运输层)不能实现的功能,诸如基于cookie的会话亲和性 (session affinity) 等功能。
Ingress控制器是必不可少的
只有Ingress控制器在集群中运行,Ingress 资源才能正常工作。不同的 Kubernetes 环境使用不同的控制器实现,但有些并不提供默认控制器。
插入一下
由于我是使用kind建的本地集群,kind要使用ingress需要另行配置
https://kind.sigs.k8s.io/docs/user/ingress/,我选择的是文档中的Ingress-Nginx,执行这些配置时镜像一直拉不下来,我使用了全局代理+局域网共享代理+增强模式才成功,也许全局代理+局域网共享代理也行,每个人的代理工具不一样这里我就没验证了。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: kubia.example.com # Ingress将域名映射到你的服务
http:
paths:
- path: /
backend:
serviceName: kubia-nodeport # 将所有的请求发送到kubia-nodeport服务的80端口,kubia-nodeport会代理转发到pod的8080
servicePort: 80
如图,ingress已经建好,ADDRESS就是ingress的ip地址。
在云提供商的环境上运行时,地址可能需要段时间才能显示,因为Ingress 控制器在幕后调配负载均衡器。
一旦知道 IP 地址,通过配置DNS服务器将 kubia.example.com解析为IP地址,这里我在/etc/hosts里配置了 127.0.0.1 kubia.example.com
,现在我们发送 HTTP 请求。
Ingress工作原理
1.客户端首先对kubia.example.com执行DNS查找
2.DNS服务器(或本地操作系统)返回了Ingress控制器的IP
3.客户端向Ingress控制器发送 HTTP 请求,并在host头指定kubia.example.com。
4.控制器从该头部确定客户端尝试访问哪个服务,通过与该服务关联的Endpoint对象查看pod IP,并将客户端的请求转发给其中一个pod。(而不是服务去转发)
通过相同的Ingress暴露多个服务
Ingress的配置中,rules和paths都是数组,因此它们可以包含多个条目 。一个Ingress可以将多个主机和路径映射到多个服务。
将不同的服务映射到相同主机的不同路径
apiVersion: extensions/v1beta1
# Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
# apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: kubia.example.com # Ingress将域名映射到你的服务
http:
paths:
- path: /kubia
backend:
serviceName: kubia # 对kubia.example.com/kubia的请求会转发至kubia服务
servicePort: 80
- path: /foo
backend:
serviceName: bar # 对kubia.example.com/foo的请求会转发至bar服务
servicePort: 80
在这种情况下 ,根据请求 URL 中的路径,请求将发送到两个不同的服务。因此,客户端可以通过 IP 地址(Ingress 控制器的 IP 地址〉访问两种不同的服务。
不同的服务映射到不同的主机上
同样,可以使用 Ingress 根据 HTTP 请求中的主机而不是仅靠路径映射到不同的服务。
apiVersion: extensions/v1beta1
# Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
# apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kubia
spec:
rules:
- host: kubia.example.com # Ingress将域名映射到你的服务
http:
paths:
- path: /
backend:
serviceName: kubia # 对kubia.example.com的请求会转发至kubia服务
servicePort: 80
- host: foo.example.com # Ingress将域名映射到你的服务
http:
paths:
- path: /
backend:
serviceName: bar # 对foo.example.com的请求会转发至bar服务
servicePort: 80
DNS 需要将 foo.example.com和kubia.example.com 域名都指向 Ingress 控制器的 IP 地址。
配置 Ingress 处理 TLS 传输(HTTPS)
客户端创建到 Ingress 控制器的 TLS 连接时,控制器将终止 TLS 连接。客户端和控制器之间的通信是加密的,而控制器和后端 pod 之间的通信则不是 。运行在pod上的应用程序不需要支持 TLS。例如,如果 pod 运行web服务器,则它只能接收HTTP 通信,并让 Ingress 控制器负责处理与TLS相关的所有内容。要使控制器能够这样做,需要将证书和私钥附加到 Ingress。这两个必需资源存储在称为Secret的Kubernetes 资源 中,然后在Ingress manifest 中引用。
# 创建私钥和证书
openssl genrsa -out tls.key 2048
openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj /CN=kubia.example.com
# 创建secret
kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
tls: # 配置TLS
- hosts:
- kubia.example.com # 将接收来自kubia.example.com主机的TLS连接
secretName: tls-secret # 从它获得之前创立的私钥和证书
rules:
- host: kubia.example.com # Ingress将域名映射到你的服务
http:
paths:
- path: /
backend:
serviceName: kubia-nodeport # 将所有的请求发送到kubia-nodeport服务的80端口
servicePort: 80
更新ingress资源
kubectl apply -f kubia-ingress-tls.yaml
访问:curl -k -v https://kubia.example.com/
pod的就绪
一些pod需要时间来加载配置和数据,之后再接收请求,这种情况下就需要不把请求转发到正在启动的pod中,直到它完全准备就绪。
就绪探针
就绪探测器会定期调用,并确定特定的 pod 是否接收客户端请求。当容器的准备就绪探测返回成功时,表示容器己准备好接收请求。考虑到应用程序的具体情况,这种确切的准备就绪的判定(通过url返回)一般是应用程序开发人员责任。
类型
- Exec探针,执行进程的地方。容器的状态由进程的退出状态代码确定
- HTTP GET探针,向容器发送 HTTP GET 请求,通过响应的HTTP 状态代码,判断容器是否准备好。
- TCP socket 探针,它打开一个TCP 连接到容器的指定端口。如果连接己建立,则认为容器己准备就绪。
和存活探针的对比
启动容器时,可以为 Kubernetes 配置一个等待时间,经过等待时间后才可以执行第1次准备就绪检查。之后,它会周期性地调用探针,并根据就绪探针的结果采取行动。如果某个 pod 报告它尚未准备就绪,则会从该服务中删除该 pod 。如果再次准备就绪,则重新添加 pod。
与存活探针不同,如果容器未通过准备检,则不会被终止或重新启动。存活探针通过杀死异常的容器并用新的正常容器替代它们来保持 pod 正常工作,而就绪探针确保只有准备好处理请求的pod才可以接收。
添加就绪探针
kubectl edit rc kubia
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia
spec:
replicas: 3
selector:
app: kubia
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: arrowarcher/kubia
readinessProbe: # pod的每个容器都会有一个就绪探针
exec:
command:
- ls
- /var/ready
ports:
- containerPort: 8080
# 使用dns
dnsPolicy: "ClusterFirst"
就绪探针将定期在容器内执行ls /var/ready命令。如果文件存在, 则ls 命令返回退出码 0, 否则返回非零的退出码。如果文件存在,则就绪探针将成功,否则,它会失败。
# 更新rc:
kubectl apply -f c_kubia_rc-readinessprobe.yaml
# 删除pod,让其重建
kubectl delete pods -l app=kubia
kubectl get po
观察发现所有pod都变成了ready=Flase
创建/var/ready,使用touch,如果文件不存在,会创建该文件
kubectl exec kubia-ctfqz -- touch /var/ready
ps:就绪探针默认10秒检查一次
实际应用中,应用程序是否可以并且希望接收客户端请求,决定了就绪探测应该返回成功和失败。
如果想要从服务中手动添加或者删除pod,请将enabled作为标签添加到pod和 服务的选择器中,要移除pod时删除其标签,不要手动更改探针。
总结:
- 如果没有就绪探针,pod将立即成为服务端点,若pod还没准备好接收请求,而服务已经转发到该pod,客户端会收到“连接被拒绝”的错误,应该始终定义一个就绪探针
- 定义就绪探针时,不要纳入停止pod的逻辑
使用headless服务来发现独立的pod
已经看到如何使用服务来提供稳定的 IP 地址, 从而允许客户端连接到支持服务的每个 pod (或其他端点)。 到服务的每个连接都被转发到一个随机选择的 pod 上。但是如果客户端需要链接到所有的 pod 呢?如果后端的 pod 都需要连接到所有其他pod 呢?
Kubernetes 允许客户通过 DNS 查找发现 pod IP。 通常, 当执行服务的 DNS 查找时, DNS 服务器会返回单个 IP一服务的集群 IP。 但是, 如果告诉Kubernetes, 不需要为服务提供集群 IP (通过在服务 spec 中将 clusterIP 字段设置为 None 来完成此操作), 则 DNS 服务器将返回 podIP 而不是单个服务 IP。
DNS 服务器不会返回单个 DNS A记录, 而是会为该服务返回多个A记录, 每个记录指向当时支持该服务的单个pod的 IP。 客户端因此可以做一个简单的 DNS A 记录查找并获取属于该服务一部分的所有 pod的 IP。 客户端可以使用该信息连接到其中的一个、 多个或全部。
创建headless服务
将服务 spec中的clusterIP字段设置为None 会使服务成为headless 服务,因为Kubemetes 不会为其分配集群IP。
apiVersion: v1
kind: Service
metadata:
name: kubia-headless
spec:
clusterIP: None
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
通过DNS发现pod
准备好pod后可以执行DNS查找以查看是否获得了实际的podIP。kubia容器镜像不包含nslookup或dig二进制文件,我们使用kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/vl --command -- sleep infinity
创建一个新的包含nslookup和dig的pod。
执行
kubectl exec dnsutils nslookup kubia-headless
dns查找
DNS服务器为FQDN(kubia-headless.default.svc.cluster.local)返回了个不同的IP,查看pod ip对照发现是对的
看看非headless服务返回的:
DNS服务器返回的是集群IP
尽管 headless 服务看起来可能与常规服务不同, 但在客户的视角上它们并无不同。即使使用 headless 服务,客户也可以通过连接到服务的 DNS 名称来连接到 pod上, 就像使用常规服务一样。但是对于 headless 服务, 由于 DNS 返回了 pod 的 IP,客户端直接连接到该 pod, 而不是通过服务代理。
注意:headless 服务仍然提供跨 pod 的负载平衡, 但是通过 DNS 轮询机制不是通过服务代理。
发现所有pod——包括未就绪的pod
当希望即使pod没有准备就绪也能发现匹配服务标签选择器的pod时,可以使用DNS查找机制查找那些未准备好的pod。将以下注解添加到服务中:
kind: Service
metadata:
annotations:
service.alpha.kubernetes.io/tolerate-unready-end
points: "true"
排除服务故障
如果无法通过服务访问pod
- 首先,确保集群内连接到服务的集群IP,而不是从外部
- 不要使用ping 服务ip判断服务是否可以访问(服务的集群IP是虚拟IP,无法ping通)
- 确保就绪探针是成功的
- 确保某个容器是服务一部分(kubectl get endpoints检查)
- 如果尝试通过FQDN或其中一部分访问服务但不起作用,查看是否可以使用其集群IP访问。
- 检查是否连接到服务的公开端口,而不是目标端口
- 尝试直接连接到pod IP以确认pod正在接收正确端口的连接
- 如果甚至无法通过pod IP访问应用,确保应用不是仅仅绑定到本地主机。
更多推荐
所有评论(0)