【k8s系列十二】k8s 之 Service的四种类型的应用

148 阅读13分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情

svc四种类型的应用

1. clusterIp 类型的应用

1) 工作原理

我们要实现一个上面的调用, 其实就是nginx调用webapp, 使用svc调度webapp.

为了实现图上的功能,主要需要以下几个组件的协同工作:

image

需要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

image

创建完成以后, 我们来看看整体的资源分布

image

我的集群一共有三台,一台master,两台node。我们创建的pod资源,可以看到一个分配到node1,两个分配到node2。

deployment,rs,svc都是和pod建立的一种关系, 并保存到etcd中,svc分配的地址和本地是可以互通的。

第四步:通过80端口访问web服务

curl http://10.97.243.212:80/hostname.html

我们发现每次请求的的主机是不一样的 image

第五步: 原理分析

ipvsadm -ln

我在输入这个命令的时候, 直接报错了。

image

这是因为当前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

image

image

第五步:删除k8s启动的pod

kubectl delete pod -all -n kube-system

pod会重新创建。

image

第六步:检验ipvs启动是否生效

查看kube-proxy-***的日志

kubectl logs kube-proxy-xxx -n kube-system

image

找到其中一个kube-proxy, 看到using ipvs Proxier. 表示启用ipvs成功.

第七步: 查看ipvs维护的ip列表

ipvsadm -ln

image 我们可以找到一个指定svc, 根据svc查看ipvs匹配的ip列表.

按照要求安装好以后,再次输入命令, 可以看到成功返回了数据。在ipvs中维护了svc调度的pod的地址列表

image

分析一下这个地址: 10.97.243.212 是svc的ip地址。

而下面的地址是具体的pod的地址

image

也就是说,当我们访问10.97.243.212:80的时候, 会转发到pod的三个地址上。

第六步: 使用域名访问svc

刚才我们是使用的ip访问的svc, 可以正确的调度到pod. 其实我们还可以通过域名访问svc. 为什么呢?

kubectl get pod -n kube-system -o wide

image

系统自动为我们创建了dns集群插件--coredns, 而且有两个, 一主一备, 组成高可用. 我们可以想到, 其背后也一定有svc调度器. 我们先记下他们的ip:

  • 10.244.0.13:53
  • 10.244.0.12:53
ipvsadm -ln

image

image

我们看到了两个svc调度器, 都调用了这两个pod, 一个是TCP, 一个是UDP, 他们的端口都是53。 我们来看看调度dns的svc

image

从上图可以看出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进行解析. 下面就来看看怎么设置.

  1. 安装工具包
yum -y install bind-utils
  1. 使用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

image

我们来看看dig的解析结果:

image

我们红框圈起来的部分Answer section就是解析的结果。他解析到这个域名对应的svc的ip是10.110.38.6. 我们来查询一下svc:

image

正确的解析出来了。

但这样有个问题呀, 每次访问域名还要指定dns的ip, 这不太靠谱. 但是, 我们知道在k8s集群内部所有的pod默认都会把dns指向至dns插件的地址, 也就是说, 在pod内部访问域名myapp-clusterip.default.svc.cluster.local.的时候, 会默认使用ip为10.96.0.10的dns. 我们来测试一下:

  1. 进入到一个pod
kubectl exec -it pod名 -- /bin/sh
  1. 下载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实现金丝雀部署. 我就简单附一个图来说明, 不代码实现了.

image

线上有两个共存的版本. 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

image

我们可以在这里定义外部访问的端口的. 建议30000以上. 如果没有定义就是默认值.

image

这里我们看到给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名称

image 将ClusterIp修改为NodePort,注意大小写.

image 如上图, 修改以后, 我们看到原来的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

image

如上图, 可以看出, 我的pod是没有问题的

第二步: 判断svc关联pod内部端口是否可以正确请求

排查方法:通过:<svc内部端口号> 请求看svc关联pod是否正常

curl 10.109.215.237:80/index.html

image

如上图,也木有问题。如果这里有问题,检查svc匹配的标签和pod是否匹配上。是否匹配上也可以通过一下命令查看

kubectl describe svc myapp-nodeport-svc

image

如上图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

image

看解析的结果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转发