kubernetes集群上运行的pod,在集群内访问是很容易的,最简单的,可以通过pod的ip来访问,也可以通过对应的svc来访问。但在集群外,由于基于flannel的kubernetes集群的pod ip是内部地址,因此从集群外是访问不到的。

为了解决这个问题,kubernetes提供了如下几个方法。

hostNetwork: true

hostNetwork为true时,容器将使用宿主机node的网络,因此,只要知道容器在哪个node上运行,从集群外以 node-ip + port 的方式就可以访问容器的服务。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  hostNetwork: true
  containers:
    - name: nginx
      image: nginx

pod启动后,如下,可以看到pod的ip地址与node optiplex-2的地址是一致的,以node optiplex-2的ip地址来请求pod 80端口的服务,访问的是pod nginx的http服务。

$ kubectl get pods -o wide nginx
NAME    READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          13m   192.168.0.161   optiplex-2   <none>           <none>
$ 
$ kubectl get nodes -o wide optiplex-2
NAME         STATUS   ROLES    AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
optiplex-2   Ready    <none>   160d   v1.16.4   192.168.0.161   <none>        Ubuntu 18.04.3 LTS   4.15.0-63-generic   docker://18.9.7
$
$ curl http://192.168.0.161
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

hostNetwork的优点是直接使用宿主机的网络,只要宿主机能访问,Pod就可以访问;但缺点也是很明显的:

  • 易用性:Pod漂移到其他node上,访问时需要更换ip地址。workaroud的做法是将Pod绑定在某几个node上,并在这几个node上运行keepalived以漂移vip,从而客户端可以使用vip+port的方式来访问。
  • 易用性:Pod间可能出现端口冲突,造成Pod无法调度成功。
  • 安全性:Pod可以直接观察到宿主机的网络。

hostPort

hostPort的效果与hostNetwork类似,都是可以通过Pod所在node的ip地址+ Pod Port来访问Pod的服务。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
          hostPort: 80

pod启动后,如下,可以看到,pod的ip地址是flannel的内部ip,与宿主机node的ip不同;与hostNetwork一样,也可以通过node ip + pod port访问。

$ kubectl get pods -o wide nginx
NAME    READY   STATUS    RESTARTS   AGE     IP             NODE       NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          3m50s   10.244.0.156   ubuntu-1   <none>           <none>
$ 
$ kubectl get nodes ubuntu-1 -o wide
NAME       STATUS   ROLES    AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
ubuntu-1   Ready    master   435d   v1.16.4   192.168.0.154   <none>        Ubuntu 18.04.2 LTS   4.15.0-49-generic   docker://18.9.2
$ 
$ curl http://192.168.0.154
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

hostPort与hostNetwork的原理不同,如下,hostPort实际是一系列的iptables规则所做的fullnat。

-A CNI-DN-1652b489c7cf2eeec2243 -s 10.244.0.156/32 -p tcp -m tcp --dport 80 -j CNI-HOSTPORT-SETMARK
-A CNI-DN-1652b489c7cf2eeec2243 -s 127.0.0.1/32 -p tcp -m tcp --dport 80 -j CNI-HOSTPORT-SETMARK
-A CNI-DN-1652b489c7cf2eeec2243 -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.244.0.156:80
-A CNI-HOSTPORT-DNAT -p tcp -m comment --comment "dnat name: \"cbr0\" id: \"3881dd51f0971e5ccdd03f89a48e2386c5e7d8987014a12a31ad40b1df685158\"" -m multiport --dports 80 -j CNI-DN-1652b489c7cf2eeec2243
-A CNI-HOSTPORT-MASQ -m mark --mark 0x2000/0x2000 -j MASQUERADE
-A CNI-HOSTPORT-SETMARK -m comment --comment "CNI portfwd masquerade mark" -j MARK --set-xmark 0x2000/0x2000

hostPort的优缺点与hostNetwork类似,因为它们都是使用了宿主机的网络资源。hostPort相对hostNetwork的一个优点是,hostPort不需要提供宿主机的网络信息,但其性能不如hostNetwork,因为需要经过iptables的转发才能到达Pod。

NodePort

与hostPort、hostNetwork只是Pod的配置不同,NodePort是一种service,其使用的是宿主机node上的端口号,从集群外以 任意node的ip + nodePort 来访问Pod的服务。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
  name: nginx
spec:
  type: NodePort
  ports:
    - name: nginx
      port: 80
      nodePort: 30018
  selector:
    name: nginx

svc配置中的nodePort,即为访问服务时,宿主机的端口号。可以在配置文件中指定(当然不能与其他nodePort类型的svc冲突),也可以不配置,由k8s来分配。

创建上述Pod和service后,如下,查看pod和svc的相关信息,我们可以通过宿主机的ip地址+noePort来访问pod的服务。

$ kubectl get pods -o wide nginx
NAME    READY   STATUS    RESTARTS   AGE     IP             NODE         NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          2m35s   10.244.4.133   optiplex-2   <none>           <none>
$
$ kubectl get svc -o wide nginx
NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE    SELECTOR
nginx   NodePort   10.103.106.64   <none>        80:30018/TCP   190d   name=nginx,run=nginx
$
$ kubectl get nodes optiplex-1 -o wide
NAME         STATUS   ROLES    AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
optiplex-1   Ready    <none>   169d   v1.16.4   192.168.0.240   <none>        Ubuntu 18.04.3 LTS   4.15.0-73-generic   docker://18.9.7
$ 
$ curl http://192.168.0.240:30018
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

nodePort类型的svc是kube-proxy通过iptables来实现的,其iptables规则在所有node上均有相同的一份,相关原理请参考谈谈kubernets的service组件的Virtual IP

LoadBalancer

LoadBalance也是一种service。LoadBalancer通常需要外部设备的支持,例如在AWS、Azure等公有云上的负载均衡设备。

在我的环境上,LoadBalancer是通过metalLB来实现的,具体请参考MetalLB:穷人的LoadBalancer

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
  name: nginx
spec:
  type: LoadBalancer
  ports:
    - name: nginx
      port: 80
  selector:
    name: nginx

与nodePort唯一不同的就是service的type。创建后,如下,查看pod及svc的状态,可以看到service nginx比nodePort类型时,多了EXTERNAL-IP的信息,其中 192.168.9.0 地址即为metalLB为LoadBalancer 类型svc所分配的外部IP地址;用户可以通过该地址从集群外访问nginx服务。

$ kubectl get pods nginx -o wide
NAME    READY   STATUS    RESTARTS   AGE     IP             NODE       NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          3m18s   10.244.0.158   ubuntu-1   <none>           <none>
$
$ kubectl get svc nginx -o wide
NAME    TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE   SELECTOR
nginx   LoadBalancer   10.102.66.83   192.168.9.0   80:31009/TCP   15s   name=nginx
$
$ curl http://192.168.9.0
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Ingress

nodePort和LoadBalancer类型的service聚焦于四层,而Ingrss则聚焦于七层。

在kubernetes的设计里,Ingress仅仅是一个概念,kubernetes并没有直接提供其实现;集群管理员需要部署ingress controller;Ingress controller可以基于nginx、treafik等等来实现,详细情况请参考kubernetes笔记: ingress

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
spec:
  rules:
    - host: nginx.example.com
      http:
        paths:
          - backend:
              serviceName: nginx
              servicePort: 80

如上,Ingrss nginx 绑定了service nginx,其域名为 nginx.example.com。

以基于nginx的ingress controller为例,创建上述ingress后,Ingress controller会监听service nginx,获取其endpoints,生成并更新ingress controller的配置。

当集群外请求到达ingress controller时,其会将http请求的host为nginx.example.com的流量,转发给endpoints,从而完成七层负载均衡,集群外的客户端也就可以请求Pod的服务了。

$ curl -v http://nginx.example.com/ping

当然了,为了能够接受集群外的流量,ingress controller本身还需要使用hostPort或者hostNetwork的方式来部署。

Pod IP全局可达

当kubernetes的网络方案选择calico或者contiv时,还可以配置Pod IP全局可达,从而直接在集群外访问。

原理是宿主机与上联交换机建立BGP邻居;当Pod running时,宿主机BGP会向上联交换机发布路由,上联交换机再完成与汇聚交换机的BGP交换,从而将流量导入到Pod。

总结

以上各种方式均可以实现集群外访问Pod服务,可以根据实际需求、环境来选择。

Ref: