持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情
svc四种类型的应用
1. clusterIp 类型的应用
1) 工作原理
我们要实现一个上面的调用, 其实就是nginx调用webapp, 使用svc调度webapp.
为了实现图上的功能,主要需要以下几个组件的协同工作:
需要kubectl组件, apiserver组件, etcd组件, kube-proxy组件, ipvs组件. 各个组件协调配合完成工作, 具体如下:
- apiserver:kubectl执行命令后, 用户通过 kubectl 命令向 apiserver 发送创建 service 的命令,apiserver 接收到请求后将数据存储到 etcd 中
- kube-proxy:kubernetes 的每个节点中都有一个叫做 kube-porxy 的进程,kube-porxy会监听apiserver, apiserver这个进程负责感知service,pod 的变化,并将变化的信息写入本地的 ipvs 规则中
- ipvs:基于内核的钩子函数机制实现负载
2) 案例操作
第一步: 准备deployment资源清单
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
namespace: chapter09
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
template:
metadata:
labels:
app: myapp
release: stabel
env: test
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
-
资源清单的名字是 myapp-deployment
-
创建3个副本
-
匹配的pod的标签是myapp, stabel
-
创建pod的名称是myapp,
- 标签是myapp,stabel,test.
- 使用的镜像是wangyangliunx/myapp:v2版本
- 当前web容器使用http访问,端口是80
第二步: 准备service资源清单
apiVersion: v1
kind: Service
metadata:
name: myapp-clusterip
namespace: chapter09
spec:
type: ClusterIP
selector:
app: myapp
env: test
ports:
- name: http
port: 80
targetPort: 80
- 创建svc, 名字是myapp-clusterip
- svc的类型是ClusterIP
- 匹配pod的选择器是myapp, stabel
- service访问的端口是80, 映射的pod的端口是80
第三步:创建deployment,svc
kubectl apply -f myapp-deployment
kubectl apply -f myapp-clusterip
创建完成以后, 我们来看看整体的资源分布
我的集群一共有三台,一台master,两台node。我们创建的pod资源,可以看到一个分配到node1,两个分配到node2。
deployment,rs,svc都是和pod建立的一种关系, 并保存到etcd中,svc分配的地址和本地是可以互通的。
第四步:通过80端口访问web服务
curl http://10.97.243.212:80/hostname.html
我们发现每次请求的的主机是不一样的
第五步: 原理分析
ipvsadm -ln
我在输入这个命令的时候, 直接报错了。
这是因为当前k8s没有启动ipvs。
k8s启用ipvs
k8s启用ipvs
第一步:开启内核参数
cat >> /etc/sysctl.conf << EOF net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF sysctl -p第二步:加载内置的ipvs模块
lsmod|grep ip_vs modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 第三步: 安装ipvsadm
yum install ipvsadm ipset -y第四步: 设置k8s启用ipvs
kubectl edit configmap kube-proxy -n kube-system 修改配置 mode: "ipvs" iptables.masqueradeAll: true
第五步:删除k8s启动的pod
kubectl delete pod -all -n kube-systempod会重新创建。
第六步:检验ipvs启动是否生效
查看kube-proxy-***的日志
kubectl logs kube-proxy-xxx -n kube-system
找到其中一个kube-proxy, 看到using ipvs Proxier. 表示启用ipvs成功.
第七步: 查看ipvs维护的ip列表
ipvsadm -ln
我们可以找到一个指定svc, 根据svc查看ipvs匹配的ip列表.
按照要求安装好以后,再次输入命令, 可以看到成功返回了数据。在ipvs中维护了svc调度的pod的地址列表
分析一下这个地址: 10.97.243.212 是svc的ip地址。
而下面的地址是具体的pod的地址
也就是说,当我们访问10.97.243.212:80的时候, 会转发到pod的三个地址上。
第六步: 使用域名访问svc
刚才我们是使用的ip访问的svc, 可以正确的调度到pod. 其实我们还可以通过域名访问svc. 为什么呢?
kubectl get pod -n kube-system -o wide
系统自动为我们创建了dns集群插件--coredns, 而且有两个, 一主一备, 组成高可用. 我们可以想到, 其背后也一定有svc调度器. 我们先记下他们的ip:
- 10.244.0.13:53
- 10.244.0.12:53
ipvsadm -ln
我们看到了两个svc调度器, 都调用了这两个pod, 一个是TCP, 一个是UDP, 他们的端口都是53。 我们来看看调度dns的svc
从上图可以看出svc配置了三种调用方式。一个是TCP 53端口,一个是UDP 53端口,一个是TCP 9153端口。
所以,我们要是想访问dns的这两个pod就是用svc的地址就可以了, 10.96.0.10:53。有了dns插件, 他就会自动为svc创建一个域名, 这个域名的格式是:svc名称.svc命名空间.svc固定值.域名. 域名有了, 我们就可以访问了. 但是我们在本机访问这个域名肯定是访问不到的. 因为我们当前linux服务器在配置网络的时候设置了dns是10.211.55.0和10.211.55.1. 不是使用dnscore进行解析,所以解析不了. 如果想在物理机上测试效果的话, 我们可以安装一个bind的工具包,这个工具包可以设置本机的dns解析规则. 比如,我想访问www.zhangsan.com, 让其使用当前的dnscore进行解析. 下面就来看看怎么设置.
- 安装工具包
yum -y install bind-utils
- 使用dig工具来指定使用哪个dns解析域名 在工具包里有一个dig工具
dig -t A myapp-clusterip.default.svc.cluster.local. @10.96.0.10
这个命令的含义是: 使用@10.96.0.10这个地址的dns来解析myapp-clusterip.default.svc.cluster.local.这个域名, 下面来看看具体每个参数的含义.
-
dig: 这是一个dns解析的工具
-
t: 表示tcp协议
-
A: 代表解析A记录 相当于一个类型
-
myapp-clusterip.default.svc.cluster.local. : 域名 最后的点不能省略, 代表根域
- myapp-clusterip: svc名称
- default: svc命名空间
- svc: svc固定值
- cluster.local: 集群定义的域名. 如何查询集群定义的域名, 可以通过下面的命令查看
-
@10.96.0.10 : 找哪个地址做解析
这样就可以指定myapp-clusterip.default.svc.cluster.local.域名使用哪个dns服务器进行解析了. 如果不知道集群的名字, 可以通过命令查看
kubectl get configmap kubeadm-config -n kube-system -o yaml
我们来看看dig的解析结果:
我们红框圈起来的部分Answer section就是解析的结果。他解析到这个域名对应的svc的ip是10.110.38.6. 我们来查询一下svc:
正确的解析出来了。
但这样有个问题呀, 每次访问域名还要指定dns的ip, 这不太靠谱. 但是, 我们知道在k8s集群内部所有的pod默认都会把dns指向至dns插件的地址, 也就是说, 在pod内部访问域名myapp-clusterip.default.svc.cluster.local.的时候, 会默认使用ip为10.96.0.10的dns. 我们来测试一下:
- 进入到一个pod
kubectl exec -it pod名 -- /bin/sh
- 下载myapp-clusterip下的index.html文件
wget http://myapp-clusterip.default.svc.cluster.local./index.html
或者
nslookup myapp-clusterip.default.svc.cluster.local.
能够下载下来, 说明pod节点的dns确实是dnscore.
3)svc实现金丝雀部署
使用svc实现金丝雀部署. 我就简单附一个图来说明, 不代码实现了.
线上有两个共存的版本. svc通过标签控制了两个版本. 而这两个版本是相互独立的. 当一个请求过来的时候, 他可能请求到v1版本上, 也可能请求到v2版本上. 这就实现了金丝雀部署.
4)设置Headless Service
有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 Cluster IP ( spec.clusterIP ) 的值为 “ None ” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由
apiVersion: v1
kind: Service
metadata:
name: myapp-headless
namespace: default
spec:
selector:
app: myapp
clusterIP: "None"
ports:
- port: 80
targetPort: 80
2. NodePort 类型的应用
NodePort方式, 会把当前调度器的网址绑定到当前的物理网卡之上。这样我们可以根据物理网卡对应的端口负载调度实现内部的访问. 从外部实现内部访问. 下面来进行案例演示
1) 案例演示
第一步: 准备资源文件
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport-svc
namespace: chapter09
spec:
type: NodePort
selector:
app: myapp
release: stabel
ports:
- name: http
port: 80
targetPort: 80
- 定义一个svc, 名字叫 myapp-nodeport-svc, 类型是 nodeport
- svc匹配的标签是 app=myapp , release=stabel
- 匹配的端口是80:80, 含义是svc的80端口映射到pod的80端口 注意,这里的80:80端口依然是内部端口 如果我们想要定义外部访问端口,可以通过explain看一下
kubectl explain svc.spec.ports
我们可以在这里定义外部访问的端口的. 建议30000以上. 如果没有定义就是默认值.
这里我们看到给myapp-nodeport-svc分配了一个ip地址, 同时还分配了一个端口号。端口号是31226. 为什么是31226呢?因为我们没有在配置文件中设置端口, 所以随机生成了一个.映射对应容器的80端口. 我们来看看这个分配的关系
ipvsadm -ln
[root@master chapter09]# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.17.0.1:32059 rr
-> 10.244.1.135:80 Masq 1 0 0
-> 10.244.1.136:80 Masq 1 0 0
-> 10.244.1.143:80 Masq 1 0 0
TCP 192.168.122.1:32059 rr
-> 10.244.1.135:80 Masq 1 0 0
-> 10.244.1.136:80 Masq 1 0 0
-> 10.244.1.143:80 Masq 1 0 0
TCP 10.211.55.200:32059 rr
-> 10.244.1.135:80 Masq 1 0 0
-> 10.244.1.136:80 Masq 1 0 0
-> 10.244.1.143:80 Masq 1 0 1
TCP 10.244.0.0:32059 rr
-> 10.244.1.135:80 Masq 1 0 0
-> 10.244.1.136:80 Masq 1 0 0
-> 10.244.1.143:80 Masq 1 0 0
TCP 10.244.0.1:32059 rr
-> 10.244.1.135:80 Masq 1 0 0
-> 10.244.1.136:80 Masq 1 0 0
-> 10.244.1.143:80 Masq 1 0 0
查看一下本机的IP地址
[root@master chapter09]# ifconfig
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.1 netmask 255.255.255.0 broadcast 10.244.0.255
inet6 fe80::8e8:5aff:fe7d:ae37 prefixlen 64 scopeid 0x20<link>
ether 0a:e8:5a:7d:ae:37 txqueuelen 1000 (Ethernet)
RX packets 228802 bytes 15381066 (14.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 139443 bytes 13258739 (12.6 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:49:b9:dc:ca txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.211.55.200 netmask 255.255.255.0 broadcast 10.211.55.255
inet6 fe80::21c:42ff:fe10:8ac4 prefixlen 64 scopeid 0x20<link>
inet6 fdb2:2c26:f4e4:0:21c:42ff:fe10:8ac4 prefixlen 64 scopeid 0x0<global>
ether 00:1c:42:10:8a:c4 txqueuelen 1000 (Ethernet)
RX packets 268655 bytes 37173944 (35.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 255660 bytes 156535793 (149.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.0 netmask 255.255.255.255 broadcast 0.0.0.0
inet6 fe80::54cc:aeff:fead:8c77 prefixlen 64 scopeid 0x20<link>
ether 56:cc:ae:ad:8c:77 txqueuelen 0 (Ethernet)
RX packets 59 bytes 6489 (6.3 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 83 bytes 5483 (5.3 KiB)
TX errors 0 dropped 33 overruns 0 carrier 0 collisions 0
virbr0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.122.1 netmask 255.255.255.0 broadcast 192.168.122.255
ether 52:54:00:c4:88:4b txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
我们发现,正好和ipvs对应上。给所有的虚拟ip,物理ip都分配的访问入口。
这里面一共有5个31226的端口,为什么有这么多呢?来看看他们的含义:
- 10.211.55.200 :这时ens33 的物理网卡
- 10.244.0.0:这时flannel0的
- 172.17.0.1:这个是docker0的
- 192.168.122.1:这个是回环接口的
这就是说当前可用的所有网卡都做了32059的绑定, 但是,来看看给svc分配的ip。
[root@master chapter09]# kubectl get svc -n chapter09 | grep nodeport
myapp-nodeport-svc NodePort 10.109.218.33 <none> 80:32059/TCP 20m
查看给svc绑定的ip地址的端口是80
[root@master chapter09]# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.109.218.33:80 rr
-> 10.244.1.135:80 Masq 1 0 0
-> 10.244.1.136:80 Masq 1 0 0
-> 10.244.1.143:80 Masq 1 0 0
我们也可以直接在yaml文件中设置nodePort的端口
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport-svc
namespace: default
spec:
type: NodePort
selector:
app: myapp
release: stabel
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30006
需要注意的是: 我们的降级操作兼容不是很好. 所谓的降级操作就是从nodePort类型降级为clusterIp. 从clusterIp升级为nodePort是非常稳定的过程.
如何升降级呢? 通过edit命令修改svc的类型
kubectl edit svc名称
将ClusterIp修改为NodePort,注意大小写.
如上图, 修改以后, 我们看到原来的ClusterIp直接变成了NodePort类型. 从端口可以看出.
Service 通过 Cluster 节点的静态端口对外提供服务。
Cluster 外部可以通过 <NodeIP>:<NodePort> 访问 Service。
curl 10.211.55.200:31443
2)遇到的问题
问题描述
上面所有的都配置好了以后, 在宿主机通过: 访问 Service的时候,访问不通。比如我的master节点的ip是10.211.55.200 , 我在宿主机发请求。这时报请求拒绝。
curl 10.211.55.200:31227/index.html
curl: (7) Failed connect to 10.211.55.200:80; Connection refused
我在master本机请求, 本机请求也是拒绝。
curl 10.211.55.200:31227/index.html
curl: (7) Failed connect to 10.211.55.200:80; Connection refused
然后再两个node节点请求, 也是请求被拒绝
以下就是排查nodePort在外网请求不同的过程。
排查过程
第一步: 判断pod启动是否正常
排查方法: 通过:<Pod内部端口号> 请求看pod创建是否正常
curl 10.244.1.59:80/index.html
如上图, 可以看出, 我的pod是没有问题的
第二步: 判断svc关联pod内部端口是否可以正确请求
排查方法:通过:<svc内部端口号> 请求看svc关联pod是否正常
curl 10.109.215.237:80/index.html
如上图,也木有问题。如果这里有问题,检查svc匹配的标签和pod是否匹配上。是否匹配上也可以通过一下命令查看
kubectl describe svc myapp-nodeport-svc
如上图Endpoints表示匹配的pod的ip:port。如果没有匹配上pod,这里是none。
第三步:在虚拟机通过< NodeIP >:< NodePort >访问
比如:当前节点node1,请求我本机10.211.55.201:31227
curl 10.211.55.201:31227
Hello MyApp | Version: v2 | <a href="hostname.html">Pod Name</a>
如果还没好,看第四步
第四步:检查ipvs启动情况,具体参考链接如下:
3. LoadBalance 类型的应用
loadBalancer 和 nodePort 其实是同一种方式。区别在于 loadBalancer 比 nodePort 多了一步,就是可以调用 cloud provider 去创建 LB 来向节点导流
4. ExternalName 类型的应用
ExternalName类型的Service用于引入集群外部的服务,它通过externalName 属性指定外部一个服务的地址,然后在集群内部访问此service就可以访问到外部的服务了。
这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容( 例如:hub.hongfu.com )。ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务
kind: Service
apiVersion: v1
metadata:
name: myapp-externalname-svc
namespace: default
spec:
type: ExternalName
externalName: www.baidu.com
当查询主机 myapp-externalname-svc.defalut.svc.cluster.local ( SVC_NAME.NAMESPACE.svc.cluster.local )时,集群的 DNS 服务将返回一个值 www.baidu.com 的 CNAME 记录。访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发
我们还是通过dig工具在master节点指定解析的dns
dig -t A myapp-externalname-svc.default.svc.cluster.local @10.96.0.10
看解析的结果ANSWER SECTION,当我们输入myapp-externalname-svc.default.svc.cluster.local. 的时候 解析到了CNAME是www.baidu.com, 然后www.baidu.com 又经过一层dns解析到了 www.a.shifen.com, www.a.shifen.com 在定位到两个ip地址, 110.242.68.4或者110.242.68.3。 第一个是我们集群设置的转发,后三个是baidu自己设置的dns转发