【k8s in Action 笔记】 第五章 服务service 一 ( dns、ENDPOINTS)
我们基于第四章中代码清单4.4 kubia-rc.yaml创建的3个pod实例k8s in Action 笔记 第四章 控制器ReplicationController代码清单5.1 kubia-svc.yamlapiVersion: v1kind: Servicemetadata:name: kubiaspec:ports:- port: 80target...
建议阅读本篇文章之前先阅读之前的一篇service介绍的文章, 《k8s 核心概念 2 service》,本文基本上是对该篇文章进行细化和扩展。
关联文章:
第五章 服务service 一 ( dns、ENDPOINTS) service 作用
第五章 服务service 二
【k8s】Service种类、类型(ClusterIP、NodePort、LoadBalancer、ExternalName) service的几种类型
K8S中的IP地址(Node IP、Pod IP、Cluster IP、External IP) 和service有关联的一些ip概念
前言
pod 通常需要对来自集群内部其他 pod ,以及来自集群外部的客户端的 HTTP 请求作出响应,所以需要一种寻找其他 pod 的方法来使用其他 pod 提供的服务。
在 Kubernetes 中通过服务 (service) 解决以下问题:
- pod 是短暂的: pod 随时启动和关闭
- Kubernetes 在 pod 启动前会给已经调度到节点上的 pod 分配 IP 地址:客户端不能提前知道 pod 的 IP 地址
- 水平伸缩意味着多个 pod 可能提供相同的服务:每个 pod 都有自己的 IP 地址
5.1 介绍服务
Kubernetes 服务是一种为一组功能相同的 pod 提供但以不变的接入点的资源。当服务存在时,它的 IP 地址和端口不会改变。与服务建立的连接会被路由到提供该服务的任意一个 pod 上。
5.1.1 创建服务
服务使用标签选择器来指定属于同一组的 pod 。
5.1.1.1 通过 kubectl expose 创建服务
创建服务的最简单的方法就是通过 kubectl expose ,在 第2章曾使用该方法来暴露创建的rc
5.1.1.2 通过 YAML 描述文件来创建服务
先创建2个nodesjs的pod,kubia-manual.yaml和kubia-manual2.yaml
kubia-manual.yaml:
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual
labels:
app: kubia
spec:
nodeSelector:
install_node: "true"
containers:
- image: docker.artnj.test.com.cn/cci/kubia:v3
name: kubia
ports:
- containerPort: 8080
protocol: TCP
kubia-manual2.yaml:
apiVersion: v1
kind: Pod
metadata:
name: kubia-manual2
labels:
app: kubia
spec:
nodeSelector:
install_node: "true"
containers:
- image: docker.artnj.test.com.cn/cci/kubia:v3
name: kubia
ports:
- containerPort: 8080
protocol: TCP
代码清单5.1 kubia-svc.yaml
必须指定label标签,否则service不会管理该pod
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
# Service 的名称
name: kubia
spec:
# 该服务可用的端口
ports:
# 第一个可用端口的名字
- name: http
# 可用端口为 80
port: 80
# 服务将连接转发到容器的 8080 端口
targetPort: 8080
# 第二个可用端口的名字
- name: https
# 可用端口为 443
port: 443
# 服务将连接转发到容器的 8443 端口
targetPort: 8443
# 具有 app=kubia 标签的 pod 都属于该服务
selector:
app: kubia
注意: selector标签,只管理带 app: kubia的pod
可以看到基本svc
的配置非常简单,只定义了两个端口
和一个选择器
,我们在选择器中注明了app: kubia
,意思就是让这个svc去将所有携带app: kubia标签
的 pod 纳入自己的管理范围。
执行创建命令,并查看已创建的svc
kubectl create -f kubia-svc.yaml
$ kubectl create -f kubia-svc.yaml
service/kubia created
$ kubectl get svc 'kubectl get services: 查看当前所有服务,svc是简写'
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 17d
kubia ClusterIP 10.254.25.196 <none> 80/TCP 3h26m
我们发现新的服务已经被分配了一个内部集群IP 10.254.25.196,type为ClusterIP
,这个是服务的四种类型之一,默认项
,其他项分别为NodePort
、LoadBalancer
、ExternalName
。因为只是集群的IP地址,只能在集群内部可以被访问。也就是说,其他 pod 就可以通过这个 ip 访问到其后面的 pod。
5.1.1.3 从集群内部测试服务
可以通过以下三种方式向服务发送请求:
- 创建一个 pod ,它将请求发送到服务的集群 IP 并记录响应。可以通过 kubectl logs 查看 pod 日志检查服务的响应
- 使用 ssh 远程登录到其中一个 Kubernetes 节点上,然后使用 curl 命令
- 通过 kubectl exec 命令在一个已经存在的 pod 中执行 curl 命令
都是集群内部的通信方式
5.1.1.4 在运行的容器中远程执行命令
kubectl exec kubia-manual -- curl -s http://10.254.25.196
[root]$ kubectl exec kubia-manual -- curl -s http://10.254.25.196
This is v3 running in pod kubia-manual '从日志来看是 kubia-manual回的消息'
[root]$ kubectl exec kubia-manual -- curl -s http://10.254.25.196
This is v3 running in pod kubia-manual2 '从日志来看是 kubia-manual2回的消息'
--
代表 kubectl 命令项的结束,在--
之后的内容是指在 pod 内部需要执行的命令。如果需要执行的命令没有以 - 开始的参数,那么--
不是必须的。这里面的 -s是为了隐藏额外信息
访问多次相同的请求,发现会由不同的pod处理,说明servicec自动帮我做了负载均衡。
上面的命令是模拟在容器kubia-manual 中尝试访问service,service会自动分配请求至其背后的pod,可能创建的3个pod中的任意一个给响应。
如果多次执行同样的命令,每次调用执行应该在不同的pod上。因为服务代理通常将每个连接随机指向
选中的后端Pod中的一个,即使连接来自于同一个客户端。
可以看到,svc
同时也实现了负载均衡
,合理的将请求平摊到了每一个 pod
上。
5.1.1.5 配置服务上的会话亲和性
如果希望特定客户端产生的所有请求每次都指向同一个 pod ,可以设置服务的 spec.sessionAffinity 属性为 ClientIP ,而不是默认值 None 。
...
spec:
sessionAffinity: ClientIP
...
这种方式会使服务代理将来自同一个客户端 IP 的所有请求转发至同一个 pod 。 Kubernetes 仅支持两种形式的会话亲和性服务: None 和 ClientIP 。
5.1.1.6 同一个服务暴露多个端口
创建的服务可以暴露一个端口,也可以暴露多个端口,比如你的pod监听2个端口,比如HTTP监听8080端口,HTTPS监听8443端口,可以使用一个服务,从端口80和443转发至8080和8443,无需创建2个服务,这样通过一个集群 IP ,使用一个服务就可以将多个端口全部暴露出来。
注意:在创建一个有多个端口的服务的时候,必须给每个端口指定名字。
...
kind: Pod
spec:
containers:
- name: kubia
ports:
# 应用监听端口 8080 ,并命名为 http
- name: http
containerPort: 8080
# 应用监听端口 8443 ,并命名为 https
- name: https
containerPort: 8443
注意:标签选择器应用于整个服务,不能对每个端口做单独的配置。如果不同的 pod 有不同的端口映射关系,需要创建两个服务。
5.1.1.7 使用命名的端口
我们就可以将在服务中引用命名的端口:
...
kind: Service
spec:
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: https
采用命名端口的方式可以使得更换 pod 端口时无须更改服务的 spec ,并且不同的 pod 可以使用不同的端口。
5.1.2 服务发现
服务发现在微服务架构里,服务之间经常进行通信,服务发现就是解决不同服务之间通信的问题。比如一个nginx的pod,要访问一个mysql服务,就需要知道mysql服务的ip和port,获取ip和port的过程就是服务发现。
通过创建服务,现在可以通过一个单一稳定的IP地址访问到Pod。在服务整个生命周期内这个地址保持不变。在服务后面的pod可能删除重建,pod地址肯改变,数量也可能改变,但是始终可以通过服务的单一不变的IP地址访问到这些pod。
但客户端如何知道服务的IP和端口?是否需要先创建服务,然后手动查找其IP并将IP传递给客户端pod的配置选项?当然不是。k8s还未客户端提供了发现服务ip和port的方式。
kubernetes 支持两种服务发现模式
5.1.2.1 环境变量
Pod创建的时候,服务的ip和port会以环境变量的形式注入到pod里,比如kubia服务,会把下面一系列环境变量注入到pod里,通过这些环境变量访问kubia服务。
1)pod创建在kubia服务之前
在创建我们自己的服务kubia之前,已经存在KUBERNETES服务
了,因此此时只有KUBERNETES服务相关的环境变量。
$ kubectl exec kubia-5rskv env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-5rskv
KUBERNETES_SERVICE_HOST=fd00::1 '只能看到自带的KUBERNETES服务'
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://[fd00::1]:443
KUBERNETES_PORT_443_TCP=tcp://[fd00::1]:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=fd00::1
NODE_VERSION=12.2.0
YARN_VERSION=1.15.2
HOME=/root
环境变量中,服务名作为前缀时,所有字母变为变为大写,且服务名称中的 - 将被转换为 _
这种情况下需要先删除pod,重新创建pod
。因为k8s 只会在创建时间晚于服务的 pod 中注入服务域名
。
执行kubectl delete po -l app=kubia
重建 pod
2)pod创建在kubia服务之后
我们删除kubia-5rskv Pod实例,rc会自动重新拉起一个Pod
$ kubectl delete pod kubia-5rskv
pod "kubia-5rskv" deleted
我们查看现在的3个pod,发现kubia-5rskv已经被删除,新拉起了一个zkrzf
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
kubia-694qf 1/1 Running 0 3d5h
kubia-j29mx 1/1 Running 0 4d2h
kubia-zkrzf 0/1 ContainerCreating 0 40s
我们再看下环境变量
$ kubectl exec kubia-zkrzf env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-zkrzf
KUBIA_PORT_80_TCP=tcp://[fd00::388a]:80
KUBIA_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_HOST=fd00::1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://[fd00::1]:443
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_PORT=tcp://[fd00::388a]:80
KUBERNETES_PORT_443_TCP=tcp://[fd00::1]:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=fd00::1
KUBIA_SERVICE_HOST=fd00::388a '多了KUBIA服务'
KUBIA_SERVICE_PORT=80
KUBIA_PORT_80_TCP_PORT=80
KUBIA_PORT_80_TCP_ADDR=fd00::388a
KUBERNETES_SERVICE_PORT_HTTPS=443
NODE_VERSION=12.2.0
YARN_VERSION=1.15.2
HOME=/root
此时,有2个服务相关的参数,分别是KUBERNETES服务
和KUBIA服务
。
5.1.2.2 通过 DNS 发现服务
K8s集群会内置一个dns服务器,service创建成功后,会在dns服务器里新增一些记录,想要访问某个服务,通过dns服务器解析出对应的ip和port,从而实现服务访问。
上面的描述可能有点难以理解,我们换个方式解释。你可能已经发现了,在上文的测试中,我们使用了curl http://x.x.x.x
的方式访问的svc
,这样的话如果svc
重建导致ip
地址改变
了,那我们岂不是访问不到了?k8s 也想到了这一点,所以提供了通过FQDN
(全限定域名)访问服务的方式,你可以在任意一个 pod 上直接使用服务的名称来访问服务:
FQDN
类似dns,通过域名方式来访问,不知过FQDN
的域名规则有点不一样
FQDN域名语法:
服务名.命名空间.集群后缀
下面,我们通过服务访问:
$ kubectl exec kubia-manual -- curl -s http://kubia:80
This is v3 running in pod kubia-manual
$ kubectl exec kubia-manual -- curl -s http://kubia:80
This is v3 running in pod kubia-manual2
注意:客户端仍然必须知道服务的端口号。如果服务使用标准端口号(例如HTTP的80端口或PostgreSQL的5432端口),这样是没有问题的。如果不是标准端口,客户端可以从环境变量获取端口号。
可以将命名空间和集群后缀省略,一下3种方式相同:
kubectl exec kubia-manual -- curl -s http://kubia:80
kubectl exec kubia-manual -- curl -s http://kubia.default:80
kubectl exec kubia-manual -- curl -s http://kubia.default.svc.cluster.local:80
原理是 进入容器后,查看/etc/resolv.conf:
nameserver 194.246.9.6
search default.svc.cluster.local svc.cluster.local cluster.local service.openpalette
options ndots:5
5.2 连接集群外的服务
5.2.2 介绍服务ENDPOINTS
服务不是和pod直接相连的。service和pod是通过endpoint来进行相连的。
[root]$ kubectl describe svc kubia
Name: kubia
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=kubia
Type: ClusterIP
IP: 10.254.25.196
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 172.33.1.55:8080,172.33.1.56:8080 'Endpoints'
Session Affinity: None
Events: <none>
Endpoint资源就是暴露一个服务的IP地址和端口的列表,kubectl get endpoints kubia
查看 kubia 的 endpoints 基本信息:
[root]$ kubectl get endpoints kubia
NAME ENDPOINTS AGE
kubia 172.33.1.55:8080,172.33.1.56:8080 5h46m
服务中在 spec.selector 定义了 pod 选择器,但是在重定向传入连接时不会直接使用它。选择器用于构建 IP 和端口列表,然后存储在 Endpoints 资源中
。当客户端连接到服务时,服务代理选择这些 IP 和端口对中的一个,并将传入连接重定向到该位置监听的服务器。
也就是说,通过创建了kubia 服务,在我们创建pod时,才会自动创建Endpoints。反之如果没有服务,则不会自动创建Endpoints。
5.2.2 手动配置服务Endpoints
通过创建了kubia 服务,在我们创建pod时,才会自动创建Endpoints。反之如果没有服务,则不会自动创建Endpoints。
下面我们就来演示手动配置场景。
创建没有选择器的服务:
由于没有选择器,k8s不知道该服务包含哪些pod,因此,也无法自动创建Endpoints,因为Endpoints也需要指定的pod才行。
使用以下描述文件 external-service.yaml 可以创建一个不指定 pod 选择器的服务:
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
# Service 的名称
name: external-service
spec:
# 该服务可用的端口
ports:
# 第一个可用端口的名字
- name: http
# 可用端口为 80
port: 80
targetPort: http
# 第二个可用端口的名字
- name: https
# 可用端口为 443
port: 443
targetPort: https
为没有选择器的服务配置 Endpoints:
Endpoints是一种独立的资源,并不是服务的一个属性。实际上二者是协作关系,因此二者也有一些约束,例如Endpoints 对象需要与服务具有相同的名称。
使用以下描述文件 external-service-endpoints.yaml 可以创建一个 Endpoints 资源:
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Endpoints
kind: Endpoints
metadata:
# Endpoints 的名称,与对应的 Service 名称一致
name: external-service
# 该 Endpoints 的子集
subsets:
# 第一个子集的地址信息
- addresses:
# 地址包含以下 ip 列表
- ip: 11.11.11.11
- ip: 22.22.22.22
# 第一个子集的端口信息
ports:
# 每个 ip 可用的端口列表
# 【注意】这个名字必须和服务端端口的名字对应
- name: http
port: 80
- name: https
port: 443
Endpoints 对象需要与服务具有相同的名称,并包含该服务将要重定向的目标 IP 地址和端口列表。
当服务和 Endpoints 都创建后,服务就会自动使用对应当 Endpoints ,并能够像具有 pod 选择器那样当服务正常使用。
5.2.3 为外部服务创建别名
除了手动配置服务的 Endpoints 来代替公开外部服务的方法,还可以通过其完全限定域名 (FQDN) 来访问外部服务。
5.2.3.1 创建 ExternalName 类型的服务
通过以下描述文件 external-service-externalname.yaml 可以创建一个 ExternalName 类型的服务,这个服务会将请求转发到 spec.externalName 指定的实际服务的完全限定域名。
# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
# Service 的名称
name: external-service
spec:
# Service 的类型为 ExternalName
type: ExternalName
# 这个服务将所有请求都转发到 someapi.somecompany.com
externalName: leetcode-cn.com
# 该服务可用的端口
ports:
# 第一个可用端口的名字
- name: http
# 可用端口为 80
port: 80
# 使用 ExternalName 时, targetPort 将被忽略
# 第一个可用端口的名字
- name: https
# 可用端口为 443
port: 443
# 使用 ExternalName 时, targetPort 将被忽略
服务创建完成后, pod 可以通过 external-service(.default.svc.cluster.local)
域名(括号内的可不加)连接到外部服务,而不用使用外部服务的实际 FQDN 。这样允许修改服务的定义,并且在以后可以修改 externalName 指向到不同的服务,或者将类型变为 ClusterIP 并为服务创建 Endpoints 。
ExternalName 服务仅在 DNS 级别实施——为服务创建了简单 CNAME DNS记录。因此,连接到服务的客户端将直接连接到外部服务,完全绕过服务代理,所以这类型的服务不会获得集群 IP 。
注意: CNAME 记录指向完全限定的域名而不是 IP 地址。
因为svc是通过我们事先定义好的标签选择器来查找 pod 的,所以 pod 的 ip 地址变动对于svc毫无影响,其实在svc和pod之间还包含了一个资源叫做endpoint,endpoint(简称ep)是一组地址及其端口的合集,如下图,只要一个svc有标签选择器的话,他就会自动创建一个同名的ep来标记出自己的要管理的 pod。
我们可以通过如下命令来查看我们刚才创建的kubia服务的ep:
$ kubectl describe svc kubia
Name: kubia
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=kubia
Type: ClusterIP
IP: fd00::a0f5
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 172.30.4.58:8080,172.30.4.59:8080,172.30.4.60:8080
Session Affinity: None
Events: <none>
然后就可以在Endpoints
列中找到他包含的地址及端口号,这三个用,分隔的地址正是三个pod
的地址。你可以使用kubectl get pod -o wide
来查看 pod 的地址。你可以执行kubectl delete po -l app=kubia
命令来重建所有的kubia pod,然后再来查看ep,会发现其ENDPOINTS也会自动更换
成这三个 pod 的值。
我们回顾下代码清单5.1 kubia-svc.yaml中的脚本,通过spec.selector来指定标签为kubia的Pod。
其中被selector
选中的 Pod
,就称为 Service
的Endpoints
,可以使用kubectl get ep
命令查看。
$ kubectl get ep
NAME ENDPOINTS AGE
kubia 172.30.4.58:8080,172.30.4.59:8080,172.30.4.60:8080 4d1h
我们可以看到kubia的后端端点172.30.4.58:8080,172.30.4.59:8080,172.30.4.60:8080
,也就是httpd应用Pod的IP地址。
需要注意的是,只有处于Running
状态且readlinessProbe
检查通过的Pod,才会出现在Service的Endpoints
列表里,同样当某个Pod出现问题时,Service也会把这个Pod从Endpoints中摘掉
。
服务的原理 kube-proxy
在Kubernetes集群中,每个Node会运行一个kube-proxy进程, 负责为Service实现一种 VIP(虚拟 IP,就是clusterIP)的代理形式。
Service其实是由kube-proxy组件和Iptables(代理的具体模式之一)来实现的。
现在的Kubernetes中默认是使用的iptables
这种模式来代理
这种模式,kube-proxy
会监视Kubernetes master对 Service 对象
和 Endpoints (端点)对象
的添加和移除
。 对每个 Service
,它会添加上 iptables 规则
,从而捕获到达该 Service
的 clusterIP
(虚拟 IP)和端口的请求,进而将请求重定向到 Service
的一组 backend
(后端) 中的某一个个上面。 对于每个 Endpoints
对象,它也会安装iptables
规则,这个规则会选择一个 backend Pod
。
默认的策略是,随机
选择一个 backend。 我们也可以实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity
的值设置为 “ClientIP
” (默认值为 “None”)。
另外需要了解的是如果最开始选择的 Pod 没有响应,iptables 代理能够自动地重试另一个 Pod,所以它需要依赖 readiness probes(准备就绪探测器)。
参考
Kubernetes 实战 —— 05. 服务:让客户端发现 pod 并与之通信(上)
Kubernetes 实战 —— 05. 服务:让客户端发现 pod 并与之通信(下)
Kubernetes In Action 学习笔记 服务发现
Kubernetes in Action中文版.pdf 观后笔记 一
更多推荐
所有评论(0)