iptables在kubernetes proxy中的作用
当service有了port和nodePort之后,就可以对内/外提供服务。那么其具体是通过什么原理来实现的呢?奥妙就在kube-proxy在本地node上创建的iptables规则。
Kubernetes为每个service分配一个clusterIP(虚拟ip)。不同的service用不同的ip,所以端口也不会冲突。Kubernetes的虚拟ip是通过iptables机制实现的。每个service定义的端口,kube-proxy都会监听一个随机端口对应,然后通过iptables nat规则做转发。比如Kubernetes上有个dns服务,clusterIP:10.254.0.10,端口:53。应用对10.254.0.10:53的请求会被转发到该node的kube-proxy监听的随机端口上,然后再转发给对应的pod。如果该服务的pod不在当前node上,会先在kube-proxy之间进行转发。该转发完全通过iptables实现。
Kube-Proxy 通过配置 DNAT 规则(从容器出来的访问,从本地主机出来的访问两方面),将到这个服务地址的访问映射到本地的kube-proxy端口(随机端口)。然后 Kube-Proxy 会监听在本地的对应端口,将到这个端口的访问给代理到远端真实的 pod 地址上去。
kubernetes 日志分析
如果 kubernetes 的启动参数中有 --logtostderr=true
表示使用 systemd 接管 kubernetes 的输出,可以用 journalctl 查看,如下,
1
2
3
4
5
6
7
8
9
10
[root@Centos-L410 project]# journalctl -u kube-controller-manager | tail
3 月 20 11 :50 :01 Centos-L410 kube-controller-manager[9359 ]: W0320 11 :50 :01.599523 9359 reflector.go:224 ] /usr/lib/golang/src/runtime/asm_amd64.s:2232 : watch of *api.PersistentVolume ended with : 502 : (unhandled http status [OK] with body [nil ]) [0 ]
3 月 20 11 :50 :01 Centos-L410 kube-controller-manager[9359 ]: E0320 11 :50 :01.912642 9359 reflector.go:206 ] /usr/lib/golang/src/runtime/asm_amd64.s:2232 : Failed to watch *api.Pod: Internal error occurred: too old resource version: 7704 (8379 )
3 月 20 11 :50 :01 Centos-L410 kube-controller-manager[9359 ]: E0320 11 :50 :01.914766 9359 reflector.go:206 ] /usr/lib/golang/src/runtime/asm_amd64.s:2232 : Failed to watch *api.Pod: Internal error occurred: too old resource version: 7704 (8379 )
3 月 20 11 :50 :01 Centos-L410 kube-controller-manager[9359 ]: E0320 11 :50 :01.915664 9359 reflector.go:206 ] /usr/lib/golang/src/runtime/asm_amd64.s:2232 : Failed to watch *api.Pod: Internal error occurred: too old resource version: 7704 (8379 )
3 月 20 11 :50 :01 Centos-L410 kube-controller-manager[9359 ]: W0320 11 :50 :01.967267 9359 reflector.go:224 ] /usr/lib/golang/src/runtime/asm_amd64.s:2232 : watch of *api.Namespace ended with : 502 : (unhandled http status [OK] with body [nil ]) [0 ]
3 月 20 12 :59 :06 Centos-L410 kube-controller-manager[9359 ]: I0320 12 :59 :06.597924 9359 replication_controller.go:409 ] Replication Controller has been deleted default /redis-master
3 月 20 12 :59 :08 Centos-L410 kube-controller-manager[9359 ]: I0320 12 :59 :08.708102 9359 replication_controller.go:409 ] Replication Controller has been deleted default /redis-slave
3 月 20 12 :59 :10 Centos-L410 kube-controller-manager[9359 ]: I0320 12 :59 :10.770278 9359 replication_controller.go:409 ] Replication Controller has been deleted default /frontend
3 月 20 12 :59 :41 Centos-L410 kube-controller-manager[9359 ]: I0320 12 :59 :41.859582 9359 event .go:216 ] Event (api.ObjectReference): reason: 'SuccessfulCreate' Created pod: redis-master-xuv93
从 log 中可以发现,刚刚创建了一个 redis-master-xuv93 的 Pod,这个 Pod 中运行了一个 redis。通过 docker ps
可以查看到对应的容器,
1
2
3
4
[root@Centos-L410 project]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
56 f58c6fb142 kubeguide/redis-master:1.0 "redis-server /etc/re" About an hour ago Up About an hour k8s_master.b7750720_redis-master-xuv93_default_8e614b60-ee58-11e5 -a10f-c80aa9c034dc_bb2168d8
d377dcefdbc5 registry.access.redhat.com/rhel7/pod-infrastructure:latest "/pod" About an hour ago Up About an hour k8s_POD.4 f810ae8_redis-master-xuv93_default_8e614b60-ee58-11e5 -a10f-c80aa9c034dc_270230ab
可以发现,当前有2个容器在运行,56f58c6fb142 就是 redis-master-xuv93 这个 Pod 对应的容器;另一个是 kubernetes 的控制节点,是 Pod 网络访问代理。可以用 docker inspect
查看这两个节点的 IP ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@Centos -L410 arnes]# docker inspect 56 f58c6fb142
......
"Env" : [
"KUBERNETES_SERVICE_HOST=10.254.0.1" ,
"KUBERNETES_SERVICE_PORT=443" ,
"KUBERNETES_PORT=tcp://10.254.0.1:443" ,
"REDIS_MASTER_SERVICE_HOST=10.254.225.16" ,
"REDIS_MASTER_PORT_6379_TCP_PROTO=tcp" ,
"KUBERNETES_PORT_443_TCP_PROTO=tcp" ,
"KUBERNETES_PORT_443_TCP_PORT=443" ,
"REDIS_MASTER_SERVICE_PORT=6379" ,
"REDIS_MASTER_PORT=tcp://10.254.225.16:6379" ,
"REDIS_MASTER_PORT_6379_TCP=tcp://10.254.225.16:6379" ,
"REDIS_MASTER_PORT_6379_TCP_PORT=6379" ,
"REDIS_MASTER_PORT_6379_TCP_ADDR=10.254.225.16" ,
"KUBERNETES_SERVICE_PORT_HTTPS=443" ,
"KUBERNETES_PORT_443_TCP=tcp://10.254.0.1:443" ,
"KUBERNETES_PORT_443_TCP_ADDR=10.254.0.1" ,
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ,
"HOME=/root"
],
......
从容器的信息上来看,redis 容器 56f58c6fb142 本身并没有 IP 地址,但是,通过容器内部互联,监听了 10.254.225.16:6379
这个 虚拟IP+端口,因此,直接访问 10.254.225.16:6379 即可,如下,
1
2
3
4
5
6
7
8
[root@Centos-L410 project]
Trying 10.254 .225 .16 ...
Connected to 10.254 .225 .16 .
Escape character is '^]' .
^]
telnet> q
Connection closed.
另外,由于 d377dcefdbc5 是 redis 的访问代理,因此,访问这个地址的6379端口,也是可以通的,如下,
1
2
3
4
5
6
7
8
[root@Centos-L410 project]
Trying 172.17 .1 .22 ...
Connected to 172.17 .1 .22 .
Escape character is '^]' .
^]
telnet> q
Connection closed.
可以验证,这个 redis 服务的确是在监听 10.254.225.16:6379
,只不过这个是 kubernetes service 的虚拟 IP,
1
2
3
4
[root@Centos-L410 project]# kubectl get services
NAME CLUSTER_IP EXTERNAL_IP PORT(S) SELECTOR AGE
kubernetes 10.254.0.1 <none> 443 /TCP <none> 20 d
redis-master 10.254.225.16 <none> 6379 /TCP name=redis-master 1 h
kubernetes 的客户端负载均衡方式
通常情况下,kubernetes 的负载均衡方式是如下进行,
由 kube-proxy 进程负责将请求转发到每个工作 Pod 上,好处是对客户端透明,坏处是多了一次转发,性能上有所损耗。另一种办法是采用客户端负载均衡方式,如下,
客户端先去向 apiserver 询问可以服务的工作 Pod 的地址,然后直接访问该地址,这样的好处是少了一次转发,坏处是客户端需要做一些逻辑判断。
如果使用客户端负载均衡的方式,那么就可以用类似上述的方法来获取服务真正的监听端口。如果需要直接访问该 IP 的话,需要在访问端增加路由规则,如下,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@Ubuntu-Asus 192.168 .2 .201 15 :12 :53 ~
root@Ubuntu-Asus 192.168 .2 .201 15 :13 :54 ~
default via 192.168 .2 .1 dev wlan0 proto static
172.17 .0 .0 /16 dev docker0 proto kernel scope link src 172.17 .0 .1
172.17 .1 .0 /24 via 192.168 .2 .202 dev wlan0
192.168 .2 .0 /24 dev wlan0 proto kernel scope link src 192.168 .2 .201 metric 9
root@Ubuntu-Asus 192.168 .2 .201 15 :13 :56 ~
Trying 172.17 .1 .22 ...
Connected to 172.17 .1 .22 .
Escape character is '^]' .
^]q
telnet> q
Connection closed.
也可以使用 redis client 来访问,
1
2
3
4
5
6
7
# arnes@Ubuntu-Asus [192.168.2.201] in ~/download/redis-3.0.5/src [15:16:49]
$ ./redis-cli -h 172.17.1.22 -p 6379
172.17.1.22:6379> scan 0 count 100
1) "0"
2) 1) "messages"
172.17.1.22:6379> get messages
"Hello World!"
这样,外部系统就可以直接访问 Pod。