kubernetes 服务
这里的服务指的 kubernetes 的 service。
为什么需要服务
指定 IP 来访问 pod 应用,在 k8s 中并不合适,因为:
- pod 随时会启动或者关闭,且地址是动态的。虽然可以固定 pod 的 IP,但不建议这么做。
- 多副本的 pod,意味着多个 pod 提供相同的服务,通过 pod ip 访问要自己解决负载均衡的问题。
服务的特点
kubernetes service 是一种资源:为一组功能相同的 pod 提供接入的资源。
服务在它的生命周期内,它的 IP 地址和端口不会改变。 客户端通过服务的 IP 地址和端口的连接默认会 均衡分发 到该服务的任意一个 pod 上。客户端不需要知道每个 pod 的 IP 地址。
创建服务
服务通过标签选择器来决定把请求分发到哪些 pod 。
创建一个三副本的 deployment 用于测试
先创建一个三副本的 deployment,名称是 kubia。
-
- 8080 端口用于指示 k8s :应用监听于 8080 端口。
- 默认会打上标签
app=kubia
。 - 命令可加
--dry-run=client -o yaml
来仅查看生成的 yaml。
创建服务
这是最小化的服务配置了。
检查服务
# kubectl get svc kubia
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia ClusterIP 172.20.19.158 <none> 80/TCP 22m
172.20.19.158:80
。
查看 pod 列表。
# kubectl get pod -l app=kubia
NAME READY STATUS RESTARTS AGE
kubia-679b67d5fc-gltdv 1/1 Running 0 72m
kubia-679b67d5fc-n55kn 1/1 Running 0 72m
kubia-679b67d5fc-xjp5n 1/1 Running 0 72m
# curl 172.20.19.158
You have hit kubia-679b67d5fc-xjp5n
# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv
# curl 172.20.19.158
You have hit kubia-679b67d5fc-n55kn
# curl 172.20.19.158
You have hit kubia-679b67d5fc-xjp5n
服务会话亲和性
这里指的是 kubernetes service 的会话亲和性。
在上面的检查服务中,可以发现请求在 3 个 pod 中轮询,如果希望同一客户端的请求都分发到同一个 pod,就可以设置服务的会话亲和性。
会话亲和性默认为 None
,可以设置的属性为:ClientIP
。k8s 的服务工作在传输层,所以它只能通过 ClientIP 来区别客户端。
设置会话的亲和性
验证。可以发现 3 次请求都分发给相同的 pod。
# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv
# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv
# curl 172.20.19.158
You have hit kubia-679b67d5fc-gltdv
同一服务暴露多端口
在创建一个有多个端口的服务时,必须给每个端口指定名字。
- http 是上面第 8 行指定的端口名称。
- https 是上面第 10 行指定的端口名称。
服务使用命名端口后,在 pod 的服务端口号变更的情况下,不需要变更服务定义里的配置。
服务发现
kubernetes 为客户端提供了发现服务的 IP 和端口的方式。
1,通过环境变量发现
服务早于 pod 创建,pod 里才能通过环境变量的方式发现服务的 IP 和端口。
可以通过在 pod.spec 上将 enableServiceLinks 标志设置为 false 来禁用变量自动注入。
在 pod 里运行 env
命令发现服务的 IP 和端口。
# kubectl exec kubia-64cdbdbff-5klvc -- env | grep SERVICE
KUBIA_SERVICE_HOST=172.20.19.158
KUBIA_SERVICE_PORT=80
环境变量中的服务名称中的横杠被转换成下画线,且全部都是大写。
2,通过 DNS 发现
在部署了 coredns 的情况下,k8s 会修改每个容器的 /etc/resolv.conf
,让容器使用内部的 DNS 来进行域名解析。因此,在知道服务名称的情况下,能查询出服务的 FQDN(Fully Qualified Domain Name)并通过它访问服务。
pod 是否使用内部 DNS 服务器是根据 pod 中 spec 的 dnsPolicy
属性来决定。
进入一个容器 ping 服务名称。
# kubectl exec -it kubia-64cdbdbff-5klvc -- bash
root@kubia-64cdbdbff-5klvc:/# ping -c1 kubia #(1)!
PING kubia.default.svc.cluster.local (172.20.19.158): 56 data bytes
64 bytes from 172.20.19.158: icmp_seq=0 ttl=64 time=0.156 ms
kubia
是前面创建的服务名称。
返回的 kubia.default.svc.cluster.local
中:
kubia
是服务名称。default
是命名空间(namespace)。svc.cluster.local
是集群域后缀。
端口号还是需要从环境变量中获取。
通过域名访问服务
# kubectl exec -it kubia-64cdbdbff-5klvc -- bash
root@kubia-64cdbdbff-5klvc:/# curl http://kubia.default.svc.cluster.local
You have hit kubia-64cdbdbff-dkmct
root@kubia-64cdbdbff-5klvc:/# curl http://kubia.default
You have hit kubia-64cdbdbff-5klvc
root@kubia-64cdbdbff-5klvc:/# curl http://kubia #(1)!
You have hit kubia-64cdbdbff-pv49x
- 如果是同一个 namespace 下,可直接用服务名。
endpoint
endpoint
是介于 service 和 pod 之间的一种资源,是暴露一个服务的 IP 地址和端口的列表。
查看 endpoint 。
# kubectl describe svc kubia
Name: kubia
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=kubia #(1)!
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 172.20.19.158
IPs: 172.20.19.158
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 172.21.116.134:8080,172.21.67.195:8080,172.21.71.195:8080 #(2)!
Session Affinity: None
Events: <none>
- 这个标签选择器用于构建 endpoint 列表。
- endpoint 的 IP 址址和端口列表。
手动创建 endpoint
-
创建没有选择器的服务,没有选择器意味着不会自动创建 endpoint,需要手动管理 endpoint。
- 这里的服务名称要和创建的 endpoint 名称要一致。
-
为没有选择器的服务创建 endpoint。
apiVersion: v1 kind: Endpoints metadata: name: svc1 #(1)! subsets: - addresses: - ip: 183.2.172.185 - ip: 121.14.77.201 ports: - port: 80
- 这里的名称要和前面创建的服务名称要一致。
如果将外部服务迁移到 k8s 集群中后,可以为服务添加选择器来让 k8s 自动管理 endpoint。
连接集群外部服务
让在集群里的 pod 像连接到内部服务一样的方式连接到外部服务。
- 通过手动管理 endpoint 的方式来实现,endpoint 里指定外部的 IP 地址和端口,如上面的例子。
- 通过 DNS 域名来实现。
创建 ExternalName
类型的服务。此类型不会创建 endpoint。
apiVersion: v1
kind: Service
metadata:
name: external-svc
spec:
type: ExternalName
externalName: www.baidu.com
ports:
- port: 80
创建服务后,pod 可以通过 external-svc.default.svc.cluster.local
来访问外部服务。
如果要指向另外的服务,只需要修改 externalName
属性即可,pod 侧访问的 DNS 地址不用变更。
连接到此类型服务的客户端将直接连接到外部服务,完全绕过服务代理。
对外暴露服务
1,NodePort
如果客户端只指定某一个节点,那么该节点发生故障时,客户端将无法访问服务,即单点故障。
集群中的每个节点都打开一个端口(所有节点都使用相同的端口号)。端口的范围在 api-server
的 service-node-port-range
参数里指定。
该端口不会在节点上有监听。
创建 NodePort 类型服务
查看服务
# kubectl get svc kubia-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-nodeport NodePort 172.20.188.1 <none> 80:30123/TCP 25s
$ curl 172.20.188.1:80 #(1)!
You have hit kubia-64cdbdbff-pv49x
$ curl 10.95.22.1:30123 #(2)!
You have hit kubia-64cdbdbff-dkmct
$ curl 10.95.22.2:30123
You have hit kubia-64cdbdbff-5klvc
- 通过集群内的 cluster-ip 访问。
- 通过本机节点的 IP 地址访问。
2,LoadBalance
LoadBalance 是 NodePort 类型的一种扩展。
创建 LoadBanlancer 类型服务
查看服务
# kubectl get svc kubia-lb
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-lb LoadBalancer 10.196.69.86 10.96.130.224 18880:30654/TCP 15s
系统会自动给服务分配一个 NodePort 端口,如果不想分配请加上配置 spec.allocateLoadBalancerNodePorts: false
。
访问服务
$ curl 10.196.69.86:18880 #(1) 通过集群内的 cluster-ip 访问加发布的端口访问。
You have hit kubia-865dd996f4-9lczw
$ curl 10.96.130.224:18880 #(2) 通过 LB 地址加发布的端口访问。
You have hit kubia-865dd996f4-9lczw
$ curl 10.96.0.1:30654 #(3) 通过节点的 IP 地址加 NodePort 端口访问。
You have hit kubia-865dd996f4-9lczw
- 通过集群内的 cluster-ip 访问加发布的端口访问。
- 通过 LB 地址加发布的端口访问。
- 通过节点的 IP 地址加 NodePort 端口访问。
浏览器使用 keep-alive
连接,连接有效期内的请求都会发送到同一个 pod。
externalTrafficPolicy: Local
特点(默认是 Cluster
):
- 连接进入节点后只转给该节点的 pod,不会跨节点转发。
- 该节点上的应用能拿到请求的源 IP。
- 可能导致跨节点的 pod 负载不均衡:A 节点有 1 个 pod 承载 50% 请求,B 节点有 2个 pod 每个 pod 承载 25% 请求。
3,Ingress
Ingress 运行在 HTTP 层(网络协议第 7 层)。
更新中。。。
就绪探针
就绪探针会定期调用,就绪探测返回成功时,表示容器已准备好接收请求。
可以配置一个等待时间(initialDelaySeconds
),经过等待时间后才执行第一次就绪检查。
如果就绪检查失败,并不会终止或者重新 pod,只是把 pod 从服务的 endpoint 中移除。
可以通过删除 pod 或者更改 pod 标签来决定 pod 是否在服务中。
当删除 pod 时,k8s 会自动把 pod 从服务中移除。
就绪探针的三种类型:
- Exec 探针,在容器里执行命令,如果返回状态码 0 说明已就绪。
- HTTP GET 探针,向容器发 HTTP GET 请求,如果返回 2xx 或 3xx 说明已就绪。
- TCP socket 探针,与容器建立 TCP 连接,如果连接能建立则表示容器已就绪。
添加就绪探针例子,在spec.template.spec.containers
里添加。
因为没有 /var/ready
文件,所以容器都没就绪( READY 为 0/1 )。
# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
kubia-8797b788-6th9s 0/1 Running 0 27s
kubia-8797b788-gprrg 0/1 Running 0 26s
kubia-8797b788-w82cz 0/1 Running 0 26s
# kubectl exec kubia-8797b788-6th9s -- touch /var/ready
# kubectl get pods
NAME READY STATUS RESTARTS AGE
kubia-8797b788-6th9s 1/1 Running 0 10m
kubia-8797b788-gprrg 0/1 Running 0 10m
kubia-8797b788-w82cz 0/1 Running 0 10m
kubectl describe
查看容器的就绪探测的参数信息。
Readiness: exec [ls /var/ready] delay=0s timeout=1s period=10s #success=1 #failure=3
定时器参数:
readinessProbe:
exec:
command:
- ls
- /var/ready
initialDelaySeconds: 0 # 容器启动立即探测
timeoutSeconds: 1 # 表示容器必须在 1s 内返回结果,否则视为探测失败
periodSeconds: 10 # 探测周期,每 30s 探测一次
successThreshold: 1 # 连续探测 1 次成功表示成功
failureThreshold: 3 # 连续探测 3 次失败表示失败
headless 服务
也叫无头服务,有以下特点:
- 不分配
ClusterIP
。 - 请求不过负载均衡器(kube-proxy 不会为其创建转发规则),直接转发给后端 pod 。
- 对该服务名的 DNS 解析,返回的是全部后端 pod IP 。
StatefulSet 使用 Headless Service 解决 Pod 间互相访问的问题。 下面是一个与有状态服务一起使用的例子。
创建 headless 类型服务
clusterIP
设置成None
,表示 headless 服务。- headless service 的名称,要和上面的第 11 行匹配。
有状态副本集创建后,pod 的信息如下:
# kubectl get pods -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 49m 172.21.95.243 k8s-master3 <none> <none>
web-1 1/1 Running 0 49m 172.21.103.242 k8s-master1 <none> <none>
web-2 1/1 Running 0 49m 172.21.6.11 k8s-master2 <none> <none>
有状态副本集 pod 的命名:有状态副本集的名称-序号
。
无状态服务的信息:
# kubectl get svc kubia-headless
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-headless ClusterIP None #(1) <none> 80/TCP 113s
CLUSTER-IP
为None
表示是无头服务。
起个有 dns 工具的 pod 来查询域名,查询无头服务时,返回全部的 pod IP。
# kubectl run -it --image=tutum/dnsutils --image-pull-policy=IfNotPresent dnsutils --restart=Never --rm --overrides='{ "apiVersion": "v1", "spec": { "nodeName": "k8s-master3" } }' -- /bin/bash
If you don't see a command prompt, try pressing enter.
root@dnsutils:/#
root@dnsutils:/# nslookup kubia-headless
Server: 172.20.0.10
Address: 172.20.0.10#53
Name: kubia-headless.default.svc.cluster.local
Address: 172.21.6.11
Name: kubia-headless.default.svc.cluster.local
Address: 172.21.95.243
Name: kubia-headless.default.svc.cluster.local
Address: 172.21.103.242
还为每个 pod 生成了对应的服务,格式为:<pod-name>.<svc-name>.<namespace>.svc.cluster.local
。
root@dnsutils:/# nslookup web-0.kubia-headless
Server: 172.20.0.10
Address: 172.20.0.10#53
Name: web-0.kubia-headless.default.svc.cluster.local
Address: 172.21.95.243
root@dnsutils:/# nslookup web-1.kubia-headless
Server: 172.20.0.10
Address: 172.20.0.10#53
Name: web-1.kubia-headless.default.svc.cluster.local
Address: 172.21.103.242
root@dnsutils:/# nslookup web-2.kubia-headless
Server: 172.20.0.10
Address: 172.20.0.10#53
Name: web-2.kubia-headless.default.svc.cluster.local
Address: 172.21.6.11
请求 headless 服务,会按 DNS 解析回来的第一条进行访问。
root@dnsutils:/# curl kubia-headless:8080 #(1)!
You have hit web-1
root@dnsutils:/# curl kubia-headless:8080
You have hit web-0
root@dnsutils:/# curl kubia-headless:8080
You have hit web-2
- 目标端口使用 pod 实际的监听端口。