Kubernetes 网络教程


在上一篇的博客中我们详细介绍了 Pod 的启动与部署,而服务部署之后的核心即在于通讯交互,今天就让我们一起来学习如何在 Kubernetes 实现 Pod 服务之间的流量管理与数据通讯。

一、Service

1. 服务模式

我们知道通过 Deployment 可以启动多个副本,那对于这多个 Pod 实例 Kubectl 又如何实现流量管理呢?

答案就是 Service,通过 Service 以及探针 Kubectl 便可实现高精度的流量管理,以更通俗的话来讲,Service 就是 Kubectl 集群内部的 Nginx 管理服务。

针对不同的服务方式,Service 提供了下述三类资源类型,其中 ClusterIPNodePort 后面会通过具体示例进行介绍,而 LoadBalancer 则为利用各大云服务厂商的负载均衡器对外提供服务,这里便不过多阐述。

类型 描述
ClusterIP 默认,通过集群方式暴露服务。
NodePort 使用 K8S 集群节点对外暴露服务。
LoadBalancer 使用云提供商的负载均衡器向外部暴露服务。

2. ClusterIP

ClusterIP 即传统的集群服务模式,在集群内通过分配虚拟 IP 管理流量,注意这个虚拟 IP 只能被 Pod / Node 内部访问而无法对外提供服务。

让我们来看由具体的场景作为切入点,设想在 Kubernetes 集群中部署了两个系统,而各自系统都实现了多副本实例,那么两个系统间存在交互那如何通讯使流量能够均匀分步到每个节点便成了关键。

在这类场景中,可以理解 ClusterIP 为集群内部的负载均衡器,其模糊了系统内部运行细节而将其作为一个整体与集群内其它系统实现服务通讯。

同样的,让我们以来看具体的配置示例,以 ClusterIP 为例新建 test-svc-cluster.yaml 文件如下:

apiVersion: v1
kind: Service
metadata:
  name: test-svc-cluster
spec:
  type: ClusterIP
  selector:
    app: test-label
  ports:
    - port: 9090
      targetPort: 9090

其中 kind: Service 指定服务类型为 service,与之前 Deployment 类似这里通过 spec.selector.app 同样用于配置标签,标识哪些 Pod 被当前 Service 所管理。

targetPort 属性用于将 Pod 实例内的端口 port 进行映射暴露。

配置 描述
metadata.name 指定 Service 名称。
spec.type 指定 Service 类型,可选:ClusterIP,NodePort,LoadBalancer。
spec.selector.app 指定 Service 所管理的标签组。
spec.ports.port 指定 Pod 内的服务端口。
spec.ports.targetPort 指定 Service 暴露的服务端口。

启动 Service 之后可以看到 Kubernetes 为其分配了虚拟 IP = 10.104.110.236

# 启动 service
[root@localhost app]# kubectl apply -f test-svc.yaml 
service/test-svc created

[root@localhost app]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    9d
test-svc     ClusterIP   10.104.110.236   <none>        9090/TCP   12s

同时通过 get endpoints 命令查看可以看到其成功通过 label 关联之前创建的多个 Pod 节点,在 ENDPOINTS 所列的服务信息也恰好与所创建的 3Pod 实例 IP 相匹配。

[root@localhost app]# kubectl get endpoints
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME         ENDPOINTS                                            AGE
kubernetes   192.168.49.2:8443                                    9d
test-svc     10.244.0.23:9090,10.244.0.24:9090,10.244.0.25:9090   24s

[root@localhost app]# kubectl get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
test-deployment-6ccd7f9d44-2chzq   1/1     Running   0          47h   10.244.0.24   minikube   <none>           <none>
test-deployment-6ccd7f9d44-d7sr7   1/1     Running   0          47h   10.244.0.25   minikube   <none>           <none>
test-deployment-6ccd7f9d44-r8xb7   1/1     Running   0          47h   10.244.0.23   minikube   <none>           <none>

让我们进入 Minikube VM 进行验证,多次请求同一个分配的虚拟 IP 可以看到返回了不同节点的 Hostname,说明 Service 成功实现负载均衡将流量覆盖到集群中的所有节点。

[root@localhost app]# minikube ssh
docker@minikube:~$ curl http://10.104.110.236:9090/api/hello
test-deployment-6ccd7f9d44-2chzq say Hello!

docker@minikube:~$ curl http://10.104.110.236:9090/api/hello
test-deployment-6ccd7f9d44-d7sr7 say Hello!

docker@minikube:~$ curl http://10.104.110.236:9090/api/hello
test-deployment-6ccd7f9d44-r8xb7 say Hello!

3. NodePort

正如上述所说的 ClusterIP 所分配的虚拟 IP 并不提供对外服务能力,而服务的对外访问诉求正是通过 NodePort 解决。

首先让我们来看一下 Node 节点的定义,在之前的示例中基于 replicas 多副本实现了高性能与高可用,但若 Kubernetes 服务自身出现宕机,其内部服务不论再多实例都不起作用。

因此,实际生产环境中 Kubernetes 同样是以多节点组成的集群的形式存在,而这每个节点称之为 Node

NodePort 即为 Kubernetes 集群中的每个节点 (Node) 上开一个端口以对外提供访问,K8S 会默认分配端口:30000–32767,访问任意 Node IP + NodePort 都能访问服务。

老规矩还是来看具体的示例,新建 test-svc-np.yaml 配置如下:

apiVersion: v1
kind: Service
metadata:
  name: test-svc-np
spec:
  type: ClusterIP
  selector:
    app: test-label
  ports:
    - port: 9090
      nodePort: 30000

NodePort 的绝大部分配置与上述 ClusterIP 并无差异,其中 ports.port 指定内网服务端口,而 ports.nodePort 则用于指定对外暴露的端口,需注意这个值可选范围在 30000–32767,若未指定则会随机分配。

如上述配置中即将内网 9090 服务暴露至 30000 端口,此时便可通过 NodeIP + NodePort 的方式访问服务。

可以看到此时通过 Node IP 便可访问服务,为临时的服务测试访问提供了通道。

# 启动服务
[root@localhost app]# kubectl apply -f test-svc-np.yaml
service/test-svc-np configured

[root@localhost app]# kubectl get svc
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
kubernetes    ClusterIP   10.96.0.1        <none>        443/TCP          12d
test-svc-np   NodePort    10.97.183.23     <none>        9090:30000/TCP   2m2s

# 获取 IP
[root@localhost app]# minikube service test-svc-np --url
http://192.168.49.2:30000

# 访问服务
[root@localhost app]# curl http://192.168.49.2:30000/api/hello
test-deployment-6ccd7f9d44-d7sr7 say Hello!

由于 Minikube 与真实宿主机仍然存在一层网络隔离,所以上述的请求都是基于 Minikube VM 服务环境下,若在服务测试时需要将端口临时暴露给宿主机,则可通过 kubectl port-forward 命令。

如下便将集群内部的 9090 端口临时映射暴露至宿主机 9091 端口,但需注意 port-forward 只是调试隧道并不保证负载均衡能力,建议仅可作用临时调试使用。

kubectl port-forward svc/test-svc-np 9091:9090 --address 0.0.0.0

4. LoadBalancer

LoadBalancer 是基于云厂商的向外部暴露服务,生产环境中更多的采用此类方式。

在刚刚的 NodePort 示例中,外部访问需求明确知道节点 IP,流量入口永远被固定到指定节点无法实现负载均衡,同时端口也被限制于 30000-32767 之间,对于 Web 服务而言则不符合 80/443 的使用习惯。

LoadBalancer 在启动服务后可获取其 EXTERNAL-IP 实现资源访问,请求将会均匀负载至集群的所有的 Node 上,屏蔽了集群内部网络细节并实现真正意义的负载均衡。

[root@localhost app]# kubectl get svc
NAME          TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
test-svc-lb   LoadBalancer   10.106.96.175    <pending>     9090:32322/TCP   8s

由于 Minikube 本地集群的原因这里 EXTERNAL-IP 无法正常访问,可通过 tunal 方式实现,详情参考官网:loadbalancer-access

简而言之,LoadBalancer 才是生产所推荐的服务暴露方式,用于实现对外入口与流量调度器。

二、Ingress

1. 网关服务

在上述的 Service 中通过 ClusterIP 实现了集群内部的流量管理,那又如何实现与外部的通讯交互?同时,集群内多个服务拥有多个 Service 时又该如何高效的进行管理也成了一大问题。

而这一切的问题都指向同一个答案,那就是:Ingress

需要注意的是,Ingress 本身并不提供与集群外部的直接访问功能,而是提供了集群内与外部的路由网关服务。其通过协议配置的机制来解析 URI、主机名称、路径等 Web 等,是统一的 HTTP/HTTPS 流量入口与路由规则层。

2. 配置规则

同样来看具体配置示例,新建 test-ingress.yaml 文件如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
    - http:
        paths:
          - path: /api/hello
            pathType: Prefix
            backend:
              service:
                name: test-svc-cluster
                port:
                  number: 9090

Ingress 核心配置项说明如下,若需更详细内容可参阅官网文档:Ingress

参数 作用
metadata.name 指定 Ingress 名称。
metadata.annotations 指定 Ingress 配置指令。
http.paths.path 指定网关路由路径。
http.paths.pathType 路径配置类型,可选:Exact,Prefix,ImplementationSpecific。
http.paths.backend.service 路由所匹配的后端 service 服务。

其中 annotations 用于 Ingress 控制器配置行为,参考所选择的不同 Ingress 控制器查询其相应文档了解具体配置,如 minikube 默认使用 Nginx-Ingress

pathType 相对较好理解即路径匹配规则,如最常用的 ExactPrefix 分别代表严格匹配和前缀匹配。

backend 配置项则用于指定当路由规则匹配时的流量转发,如上示例将在匹配 /api/hello 时将流量转发至 test-svc-clusterservice 服务。

3. 服务示例

那么就让我们运行上述的配置示例来查看其效果,注意在 minikube 集群下要使用 Ingress 需要先启动 Ingress-Controller 服务。

minikube addons enable ingress

通过 apply -f 启动后可以看到服务正常并监听 80 端口。

[root@localhost ~]# kubectl apply -f test-ingress.yaml
# ingress.extensions/test-ingress created

[root@localhost ~]# kubectl get ingress
NAME           CLASS   HOSTS   ADDRESS   PORTS   AGE
test-ingress   nginx   *                 80      10s

正如刚刚提到 Ingress 并不提供服务访问能力而是作为网关路由,所以要访问需要借助 NodePort 实现。

刚才提到了 minikube 默认使用 Nginx-Ingress,其在对应命名下空间下提供了默认 NodePort 服务 ingress-nginx-controller

通过 --url 便可获取其对应的访问地址,其中第一个是 32666 对应 80 端口即 HTTP 服务,第二个相对应的 HTTPS 服务。

[root@localhost ~]# kubectl get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.106.147.226   <none>        80:32666/TCP,443:30206/TCP   1h
ingress-nginx-controller-admission   ClusterIP   10.106.174.196   <none>        443/TCP                      1h

[root@localhost ~]# minikube service ingress-nginx-controller -n ingress-nginx --url
http://192.168.49.2:32666
http://192.168.49.2:30206

获取服务访问信息之后,便可通过此 IP 请求服务,可以看到成功实现负载均衡请求。

[root@localhost ~]# curl http://192.168.49.2:32666/api/hello
test-deployment-75ff6944d7-kfjcd say Hello!

[root@localhost ~]# curl http://192.168.49.2:32666/api/hello
test-deployment-75ff6944d7-zllwl say Hello!

[root@localhost ~]# curl http://192.168.49.2:32666/api/hello
test-deployment-75ff6944d7-vqx95 say Hello!

4. 优势差异

看到这你或许有个疑问,说到底这不是还是通过 NodePort 访问服务吗?那跟直接 Service 又有什么区别?

下面让我们以具体的场景进行介绍 Ingress 其核心竞争力到底在哪?

假设当前集群存在 3 个不同的系统集群 Service-1Service-2Service-3,通过 NodePort 方式对外提供服务,即存在 9091:300019092:300029093:30003,示意图如下:

通过图示可以看到服务器需要对外暴露三个端口,与之相对应防火墙我们也许配置三套规则规则。而在企业环境中服务数量往往及其庞大,那么其管理成本也将陡升。

那么 Ingress 其优势显而易见,将上述的场景通过 Ingress 改造后图示如下,集群只需对外暴露一个端口即可,提高安全性的同时维护成本也大大降低。

即便抛开这一切不谈,Ingress 所提供的 SSL/TLS 与域名服务解析等等能力也是 Service 所不具备。


文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录