这节讲一下 k8s 网络相关的资源类型 service 和 endpoint,关于这一块,这篇博客讲的不错:https://www.cnblogs.com/moonlight-lin/p/14553119.html
在前面创建的 POD 会发现一个问题,POD的 IP 地址不是固定不变的,对外提供服务时很不方面,使用 Service 资源可以解决这个问题。
Service
先创建一个deployment 资源配置文件,其中 tomcat 容器添加开放的端口声明,别名为 app008-8080 :
[root@ylserver10686071 ~]# cat deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app008
namespace: prod
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: tomcat
image: tomcat:8.0
ports:
- name: app008-8080
containerPort: 8080
protocol: TCP
创建deployment资源,查看创建的POD IP 地址:
[root@ylserver10686071 ~]# kubectl apply -f deployment.yml
deployment.apps/app008 created
[root@ylserver10686071 ~]# kubectl get pods -n prod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app008-c7b79f6c-l4xdx 1/1 Running 0 2m11s 10.233.67.33 ylserver10686072 <none> <none>
[root@ylserver10686071 ~]#
创建Service 资源配置文件:
[root@ylserver10686071 ~]# cat service.yml kind: Service apiVersion: v1 metadata: name: app008 namespace: prod spec: type: ClusterIP ports: - name: app008-svc protocol: TCP port: 38080 targetPort: 8080 selector: app: web
-
- type: ClusterIP service类型,不填的话默认为 ClusterIP,其他类型有 NodePort、LoadBalancer、ExternalName、Headless Services
- port: 38080 servcice 暴露的端口
- targetPort: 8080 与 POD 关联的端口,不填的话默认和 port字段相同,也可以填 POD 端口的别名,这里为 app008-8080
- selector 标签选择器,这里关联了定义 标签为 app: web 的POD
创建 Service资源,查看 Service 时可以简写为 svc:
[root@ylserver10686071 ~]# kubectl apply -f service.yml
service/app008 created
[root@ylserver10686071 ~]# kubectl get svc -n prod -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
app008 ClusterIP 10.233.63.3 <none> 38080/TCP 6s app=web
[root@ylserver10686071 ~]#
请求 svc 暴露的端口检验是否访问正常:
[root@ylserver10686071 ~]# curl -I http://10.233.63.3:38080 HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 20 Jul 2021 09:11:56 GMT [root@ylserver10686071 ~]#
如果网络插件使用的是ipvs,会生成一个虚拟网口和ClusterIP 对应:
[root@ylserver10686071 ~]# ip a|grep -i14 10.233.63.3 6: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default link/ether ca:3d:52:63:42:78 brd ff:ff:ff:ff:ff:ff inet 10.233.35.162/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.233.20.148/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.233.25.192/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.233.54.175/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.233.0.1/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.233.0.3/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.233.63.3/32 scope global kube-ipvs0 valid_lft forever preferred_lft forever
使用ipvsadm 可以看到 ipvs 转发规则:
[root@ylserver10686071 ~]# ipvsadm -Ln|grep -4 10.233.63.3 TCP 10.233.35.162:8000 rr -> 10.233.72.33:8000 Masq 1 0 0 TCP 10.233.54.175:443 rr -> 10.233.75.35:8443 Masq 1 2 0 TCP 10.233.63.3:38080 rr -> 10.233.67.33:8080 Masq 1 0 0 TCP 10.233.75.0:38443 rr -> 10.233.67.18:8443 Masq 1 0 0 TCP 127.0.0.1:34654 rr [root@ylserver10686071 ~]# kubectl get pods -n prod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES app008-c7b79f6c-l4xdx 1/1 Running 0 42m 10.233.67.33 ylserver10686072 <none> <none> [root@ylserver10686071 ~]#
ClusterIP只能在 k8s 集群内部才可以访问到,如果是集群外部的话,可以使用 NodePort 类型映射到宿主机端口上,修改下配置文件,nodePort 为宿主机暴露端口:
[root@ylserver10686071 ~]# cat service.yml kind: Service apiVersion: v1 metadata: name: app008 namespace: prod spec: type: NodePort ports: - name: app008-svc protocol: TCP nodePort: 38090 port: 38080 targetPort: 8080 selector: app: web
更新 service资源配置文件,查看资源信息:
[root@ylserver10686071 ~]# kubectl apply -f service.yml service/app008 configured [root@ylserver10686071 ~]# kubectl get svc -n prod -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR app008 NodePort 10.233.63.3 <none> 38080:38090/TCP 3h1m app=web [root@ylserver10686071 ~]#
当Service 为NodePort 类型时,集群内所有的宿主机都会监听nodePort端口,并转发到对应的POD,验证一下:
[root@ylserver10686071 ~]# ss -anputl|grep 38090 tcp LISTEN 0 128 *:38090 *:* users:(("kube-proxy",pid=11474,fd=16)) [root@ylserver10686072 ~]# ss -anputl|grep 38090 tcp LISTEN 0 128 *:38090 *:* users:(("kube-proxy",pid=11321,fd=16)) [root@ylserver10686073 ~]# ss -anputl|grep 38090 tcp LISTEN 0 128 *:38090 *:* users:(("kube-proxy",pid=11315,fd=16))
其中NodePort 转发到 POD 主要也是使用ipvs 实现的,如果网络插件为flannel,则由 iptables 实现,这里就不做展开。
Endpoints
当创建service资源配置了selector时,endpoints 控制器会自动创建 endpoints 资源对象,该资源对象记录了 svc 和 pod 的一一对应关系,存储在数据库etcd中,查看创建的endpoints详细信息:
[root@ylserver10686071 ~]# kubectl get endpoints -n prod -o wide
NAME ENDPOINTS AGE
app008 10.233.67.33:8080 5m40s
[root@ylserver10686071 ~]# kubectl describe endpoints app008 -n prod
Name: app008
Namespace: prod
Labels: <none>
Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2021-07-20T12:56:33Z
Subsets:
Addresses: 10.233.67.33
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
app008-svc 8080 TCP
Events: <none>
[root@ylserver10686071 ~]#
service还有一个用途,可以关联集群外的主机的端口,假如 集群外 有一台 mysql 主机,IP地址为 10.68.60.57,端口为 3306 ,编写 service 和 endpoints 资源配置文件,此处的service 不再使用 selector 做关联 :
[root@ylserver10686071 ~]# cat svc-endpoints.yml apiVersion: v1 kind: Service metadata: name: mysql-svc namespace: prod spec: ports: - protocol: TCP port: 3306 targetPort: 3306 --- apiVersion: v1 kind: Endpoints metadata: name: mysql-svc namespace: prod subsets: - addresses: - ip: 10.68.60.57 ports: - port: 3306
创建 svc 和 endpoints 资源,并查看相关信息:
[root@ylserver10686071 ~]# kubectl apply -f svc-endpoints.yml service/mysql-svc created endpoints/mysql-svc created [root@ylserver10686071 ~]# kubectl get svc -n prod NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE mysql-svc ClusterIP 10.233.56.68 <none> 3306/TCP 7s [root@ylserver10686071 ~]# kubectl get endpoints -n prod NAME ENDPOINTS AGE mysql-svc 10.68.60.57:3306 14s [root@ylserver10686071 ~]#
使用ClusterIP 测试 mysql 是否能够连接成功:
[root@ylserver10686071 ~]# mysql -uroot -p -h10.233.56.68 Enter password: Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 1071787 Server version: 5.7.31-log MySQL Community Server (GPL) Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MySQL [(none)]>
到这里就会有一个疑问,既然用的是ClusterIP ,为何不直接用 mysql 主机的IP 地址呢,其实对于 k8s 集群内的 POD 可以直接使用k8s提供的域名请求集群外的mysql主机,针对的是环境中没有DNS服务器的情况,有DNS服务器还是建议直接用域名做关联。
创建一个 镜像为 mysql:5.7 的 deployment 资源配置文件,使用command可以覆盖原来images的启动命令,平常可用来调试:
apiVersion: apps/v1 kind: Deployment metadata: name: mysql-client namespace: prod spec: replicas: 1 selector: matchLabels: mysql: client template: metadata: labels: mysql: client spec: containers: - name: mysql-client image: mysql:5.7 command: ["tail","-f","/dev/null"] - name: busybox image: busybox:latest command: ["tail"] args: ["-f","/dev/null"] - name: tool image: slongstreet/bind-utils:latest command: ["tail","-f","/dev/null"]
创建deployment资源后,进入容器使用 svc 的名称 mysql-svc 连接 mysql主机:
[root@ylserver10686071 ~]# kubectl exec -it mysql-client-858f464d57-64vbk -n prod -- bash
root@mysql-client-6c566f95cd-7h4x2:/# mysql -uroot -p -hmysql-svc
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1071942
Server version: 5.7.31-log MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
这里会有一个疑惑,为何可以通过 svc的名称直接连接到mysql主机呢,查看下POD的 resolv.conf 就知道:
[root@ylserver10686071 ~]# kubectl exec -it mysql-client-858f464d57-64vbk -c mysql-client -n prod -- cat /etc/resolv.conf
nameserver 169.254.25.10
search prod.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
[root@ylserver10686071 ~]#
配置文件resolv.conf 中 options ndots:5 参数表示查询的域名 . 的个数少于5的时候,会根据 search 中配置的列表依次在对应的域名中先进行搜索,如果没有返回,则最后再直接查询域名本身,验证一下:
[root@ylserver10686071 ~]# kubectl exec -it mysql-client-858f464d57-64vbk -c tool -n prod -- sh / # host -v www.baidu.com Trying "www.baidu.com.prod.svc.cluster.local" Trying "www.baidu.com.svc.cluster.local" Trying "www.baidu.com.cluster.local" Trying "www.baidu.com" ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57120 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.baidu.com. IN A ;; ANSWER SECTION: www.baidu.com. 28 IN CNAME www.a.shifen.com. www.a.shifen.com. 28 IN A 163.177.151.110 www.a.shifen.com. 28 IN A 163.177.151.109
同理,验证一下 svc的名称是如何解析的:
/ # host -v mysql-svc Trying "mysql-svc.prod.svc.cluster.local" ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43733 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mysql-svc.prod.svc.cluster.local. IN A ;; ANSWER SECTION: mysql-svc.prod.svc.cluster.local. 5 IN A 10.233.56.68 / # nslookup mysql-svc.prod.svc.cluster.local Server: 169.254.25.10 Address: 169.254.25.10:53 Name: mysql-svc.prod.svc.cluster.local Address: 10.233.56.68
实验结果显示域名 mysql-svc 的完整域名为 mysql-svc.prod.svc.cluster.local ,解析的IP 地址为 ClusterIP 地址,域名格式是 [servicename].[namespace].svc.cluster.local ,同个namespace 域名可以直接简写为 servicename,不同namespace 则需要写完整的域名,想想这是为什么。
实验验证一下,创建一个在 kube-public namespace 下的 deployment资源配置文件:
[root@ylserver10686071 ~]# cat busybox.yml apiVersion: apps/v1 kind: Deployment metadata: name: busybox namespace: kube-public spec: replicas: 1 selector: matchLabels: k8s-app: busybox template: metadata: labels: k8s-app: busybox spec: containers: - name: busybox image: busybox:latest command: - tail - -f - /dev/null
创建deployment资源,并验证:
[root@ylserver10686071 ~]# kubectl apply -f busybox.yml deployment.apps/busybox created [root@ylserver10686071 ~]# kubectl exec -it busybox-bbf7c9c98-fw97g -n kube-public -- sh / # ping -c4 mysql-svc ping: bad address 'mysql-svc' / # ping -c4 mysql-svc.prod.svc.cluster.local PING mysql-svc.prod.svc.cluster.local (10.233.56.68): 56 data bytes 64 bytes from 10.233.56.68: seq=0 ttl=64 time=0.296 ms 64 bytes from 10.233.56.68: seq=1 ttl=64 time=0.229 ms 64 bytes from 10.233.56.68: seq=2 ttl=64 time=0.340 ms 64 bytes from 10.233.56.68: seq=3 ttl=64 time=0.263 ms --- mysql-svc.prod.svc.cluster.local ping statistics --- 4 packets transmitted, 4 packets received, 0% packet loss round-trip min/avg/max = 0.229/0.282/0.340 ms / # cat /etc/resolv.conf nameserver 169.254.25.10 search kube-public.svc.cluster.local svc.cluster.local cluster.local options ndots:5 / #
ExternalName
上面的实验中,跨namespace 访问 svc 需要 填写完整的域名,如果想跟在同个namespace下 使用 简写,借助 externalname 也可以实现该功能。
编写externalname 类型的 service 资源配置文件:
[root@ylserver10686071 ~]# cat service-extname.yml
kind: Service
apiVersion: v1
metadata:
name: mysql-svc
namespace: kube-public
spec:
type: ExternalName
externalName: mysql-svc.prod.svc.cluster.local
创建资源后,进POD里面验证一下:
[root@ylserver10686071 ~]# kubectl apply -f service-extname.yml service/mysql-svc created [root@ylserver10686071 ~]# kubectl exec -it busybox-bbf7c9c98-fw97g -n kube-public -- sh / # ping -c4 mysql-svc PING mysql-svc (10.233.56.68): 56 data bytes 64 bytes from 10.233.56.68: seq=0 ttl=64 time=0.212 ms 64 bytes from 10.233.56.68: seq=1 ttl=64 time=0.320 ms 64 bytes from 10.233.56.68: seq=2 ttl=64 time=0.273 ms 64 bytes from 10.233.56.68: seq=3 ttl=64 time=0.208 ms --- mysql-svc ping statistics --- 4 packets transmitted, 4 packets received, 0% packet loss round-trip min/avg/max = 0.208/0.253/0.320 ms / # ping -c4 mysql-svc.prod.svc.cluster.local PING mysql-svc.prod.svc.cluster.local (10.233.56.68): 56 data bytes 64 bytes from 10.233.56.68: seq=0 ttl=64 time=0.205 ms 64 bytes from 10.233.56.68: seq=1 ttl=64 time=0.398 ms 64 bytes from 10.233.56.68: seq=2 ttl=64 time=0.278 ms 64 bytes from 10.233.56.68: seq=3 ttl=64 time=0.214 ms --- mysql-svc.prod.svc.cluster.local ping statistics --- 4 packets transmitted, 4 packets received, 0% packet loss round-trip min/avg/max = 0.205/0.273/0.398 ms / #
总结一下:
- Service资源有四种类型:ClusterIP、NodePort、LoadBalancer、ExternalName、Headless Services;
- Service资源类型为ClusterIP、NodePort 是,Endpoints 控制器会创建 endpoints,记录对应的POD 地址 或者 自定义的子网地址;
- ExternalName 类型的 Service 可以软连接到其他 namespace 的 svc ,可以想在同个namespace一样使用其他namespace的svc地址。