理解OpenShift(1):网络之 Router 和 Route

时间:2023-03-08 16:25:03

理解OpenShift(1):网络之 Router 和 Route

理解OpenShift(2):网络之 DNS(域名服务)

理解OpenShift(3):网络之 SDN

理解OpenShift(4):用户及权限管理

理解OpenShift(5):从 Docker Volume 到 OpenShift Persistent Volume

** 本文基于 OpenShift 3.11,Kubernetes 1.11 进行测试 ***

1. OpenShift 为什么需要 Router 和 Route?

顾名思义,Router 是路由器,Route 是路由器中配置的路由。OpenShift 中的这两个概念是为了解决从集群外部(就是从除了集群节点以外的其它地方)访问服务的需求。不晓得为什么OpenShift 要将Kubernetes 中的 Ingress 改为 Router,我倒是觉得 Ingress 名字更贴切。

从外部通过 router 和从内部通过 servide 访问 pod 中的应用两个过程的简单的示意图如下:

理解OpenShift(1):网络之 Router 和 Route

上图中,某个应用的三个pod 分别位于 node1,node2 和 node3 上。OpenShift 中有三层IP地址概念:

  • pod 自己的 IP 地址,可以类比为 OpenStack 中虚拟机的固定IP。它只有在集群内才有意义。
  • service 的 IP 地址。Service 通常有 ClusterIP,这也是一种集群内部的IP 地址。
  • 应用的外部 IP 地址,可以类比为OpenStack 中的浮动IP,或者IDC IP(和浮动IP 之间是NAT 映射关系)。

因此,要从集群外部访问 pod 中的应用,无非两种方式:

  • 一种是利用一个代理(proxy),把外部 IP 地址转化为后端的 Pod IP 地址。这就是 OpenShift router/route 的思路。OpenShift 中的 router 服务,是一个运行在特定节点(通常是基础架构节点)上的集群基础服务,由集群管理员负责创建和管理。它可以有多个副本(pod)。router 中可有多个 route,每个 route 能通过外部HTTP 请求的域名找出其后端的 pod 列表,并进行网络包的转发。也就是将pod 中的应用暴露到外网域名,使得用户可以外面通过域名访问到应用。这实际上是一种七层负载均衡器。OpenShift 默认采用 HAProxy 来实现,当然也支持其它实现,比如 F5.
  • 另一种是将服务直接暴露到集群外。这种方式具体会在『服务 Service』那一篇文章中详细解释。

2. OpenShift 如何利用 HAProxy 实现 router 和 route?

2.1 Router 部署

使用 ansible 采用默认配置部署 OpenShift 集群时,在集群 Infra 节点上,会以 Host networking 方式运行一个 HAProxy 的 pod,它会在所有网卡的 80 和 443 端口上进行监听。

[root@infra-node3 cloud-user]# netstat -lntp | grep haproxy
tcp 127.0.0.1: 0.0.0.0:* LISTEN /haproxy
tcp 127.0.0.1: 0.0.0.0:* LISTEN /haproxy
tcp 0.0.0.0: 0.0.0.0:* LISTEN /haproxy
tcp 0.0.0.0: 0.0.0.0:* LISTEN /haproxy

其中,172.0.0.1 上的 10443 和 10444 是HAproxy 自己使用的。下文会有解释。

因此,在每个 infra 节点上,只能有一个 HAProxy pod,因为这些端口只能被占用一次。如果调度器找不到满足要求的节点,则router 服务的调度就会失败:

/ nodes are available:  node(s) didn't have free ports for the requested pod ports, 5 node(s) didn't match node selector

OpenShift HAProxy Router 支持两种部署方式:

  • 一种是常见的单Router 服务部署,它有一个或多个实例(pod),分布在多个节点上,负责整个集群上部署的服务的对外访问。
  • 另一种是分片(sharding)部署。此时,会有多个 Router 服务,每个Router 服务负责指定的若干project,两者之间采用标签(label)进行映射。这是为了解决单个 Router 的性能不够问题而提出的解决方案。

OpenShift 提供了 oc adm router 命令来创建 router 服务。

创建router:

[root@master1 cloud-user]# oc adm router router2 --replicas= --service-account=router
info: password for stats user admin has been set to J3YyPjlbqf
--> Creating router router2 ...
warning: serviceaccounts "router" already exists
clusterrolebinding.authorization.openshift.io "router-router2-role" created
deploymentconfig.apps.openshift.io "router2" created
service "router2" created
--> Success

详细的部署方法请参见官方文档 https://docs.openshift.com/container-platform/3.11/install_config/router/default_haproxy_router.html。

2.2 Router pod 中的 HAProxy 进程

在 Router 服务的每个 pod 之中,openshift-router 进程启动了一个 haproy 进程:

UID        PID  PPID  C STIME TTY          TIME CMD
+ Nov21 ? :: /usr/bin/openshift-router
+ : ? :: /usr/sbin/haproxy -f /var/lib/haproxy/conf/haproxy.config -p /var/lib/haproxy/run/haproxy.pid -x /var/lib/haproxy/run/haproxy.sock -sf

查看 haproxy 使用的配置文件(只是部分):

global
maxconn
daemon
ca-base /etc/ssl
crt-base /etc/ssl
。。。。 defaults
maxconn # Add x-forwarded-for header. # server openshift_backend 127.0.0.1:
errorfile /var/lib/haproxy/conf/error-page-.http 。。。
timeout http-request 10s
timeout http-keep-alive 300s # Long timeout for WebSocket connections.
timeout tunnel 1h frontend public bind :
mode http
tcp-request inspect-delay 5s
tcp-request content accept if HTTP
monitor-uri /_______internal_router_healthz # Strip off Proxy headers to prevent HTTpoxy (https://httpoxy.org/)
http-request del-header Proxy # DNS labels are case insensitive (RFC ), we need to convert the hostname into lowercase
# before matching, or any requests containing uppercase characters will never match.
http-request set-header Host %[req.hdr(Host),lower] # check if we need to redirect/force using https.
acl secure_redirect base,map_reg(/var/lib/haproxy/conf/os_route_http_redirect.map) -m found
redirect scheme https if secure_redirect use_backend %[base,map_reg(/var/lib/haproxy/conf/os_http_be.map)] default_backend openshift_default # public ssl accepts all connections and isn't checking certificates yet certificates to use will be
# determined by the next backend in the chain which may be an app backend (passthrough termination) or a backend
# that terminates encryption in this router (edge)
frontend public_ssl bind :
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type } # if the connection is SNI and the route is a passthrough don't use the termination backend, just use the tcp backend
# for the SNI case, we also need to compare it in case-insensitive mode (by converting it to lowercase) as RFC says
acl sni req.ssl_sni -m found
acl sni_passthrough req.ssl_sni,lower,map_reg(/var/lib/haproxy/conf/os_sni_passthrough.map) -m found
use_backend %[req.ssl_sni,lower,map_reg(/var/lib/haproxy/conf/os_tcp_be.map)] if sni sni_passthrough # if the route is SNI and NOT passthrough enter the termination flow
use_backend be_sni if sni # non SNI requests should enter a default termination backend rather than the custom cert SNI backend since it
# will not be able to match a cert to an SNI host
default_backend be_no_sni 。。。
backend be_edge_http:demoprojectone:jenkins
mode http
option redispatch
option forwardfor
balance leastconn
timeout server 4m timeout check 5000ms
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-Port %[dst_port]
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Proto-Version h2 if { ssl_fc_alpn -i h2 }
http-request add-header Forwarded for=%[src];host=%[req.hdr(host)];proto=%[req.hdr(X-Forwarded-Proto)];proto-version=%[req.hdr(X-Forwarded-Proto-Version)]
cookie 4376ea64d7d0abf11209cfe5f7cca1e7 insert indirect nocache httponly secure
server pod:jenkins--84nrt:jenkins:10.128.2.13: 10.128.2.13: cookie 8669a19afc9f0fed6824feb9fb1cf4ac weight 。。。

为了简单期间,上面只是配置文件的部分内容,它主要包括三种类型:

  • 全局配置,比如最大连接数 maxconn,超时时间 timeout 等;以及front部分,即前端配置,HAProxy 默认会在 443 和 80 两个端口上分别监听外部 https 和 http 请求。
  • backend,即每个服务的后端配置,里面有很多关键内容,比如后端协议(mode)、负载均衡方法(balance)、后端列表(server,这里是pod,包括其IP 地址和端口)、证书等。

因此,OpenShift 的路由器功能需要能对这三部分进行管理和控制。

关于负载均衡器和 HAProxy 的详细介绍,可以参考 Neutron 理解 (7): Neutron 是如何实现负载均衡器虚拟化的 这篇文章。

2.3 全局配置管理

要指定或修改 HAProxy 的全局配置,OpenShift 有提供两种方式:

(1)第一种是使用 oc adm router 命令在创建 router 时候指定各种参数,比如 --max-connections 用于设置最大连接数。比如:

oc adm router --max-connections= --ports='81:80,444:443' router3

创建出来的HAProxy 的 maxconn 将是 20000,router3 这个服务对外暴露出来的端口是 81 和 444,但是 HAProxy pod 的端口依然是 80 和 443.

(2)通过设置 dc/<dc router名> 的环境变量来设置 router 的全局配置。

在官方文档 https://docs.openshift.com/container-platform/3.4/architecture/core_concepts/routes.html#haproxy-template-router 中有完整的环境变量列表。比如运行以下命令后,

 oc set env dc/router3 ROUTER_SERVICE_HTTPS_PORT= ROUTER_SERVICE_HTTP_PORT= STATS_PORT=

router3 会重新部署,新部署的HAProxy 的 https 监听端口是 444,http 监听端口是 80,统计端口是 1937.

2.4 OpenShift passthrough 类型的 route 与 HAProxy backend

(1)通过OpenShift Console 或者 oc 命令创建一条 route,它将 sit 项目的 jenkins 服务暴露到域名 sitjenkins.com.cn:

在界面上创建 route:

理解OpenShift(1):网络之 Router 和 Route

理解OpenShift(1):网络之 Router 和 Route

结果:

Name:                   sitjenkins.com.cn
Namespace: sit
Labels: app=jenkins-ephemeral
template=jenkins-ephemeral-template
Annotations: <none>
Requested Host: sitjenkins.com.cn
Path: <none>
TLS Termination: passthrough
Endpoint Port: web Service: jenkins
Weight: (%)
Endpoints: 10.128.2.15:, 10.131.0.10: 

这里,service name 起了一个中介作用,把 route 和服务的端点(也就是pod)连接了起来。

(2)router 服务的两个 pod 中的 HAProxy 进程的配置文件中多了一个backend:

# Secure backend, pass through
backend be_tcp:sit:sitjenkins.com.cn
balance source hash-type consistent
timeout check 5000ms}
server pod:jenkins--bqhfj:jenkins:10.128.2.15: 10.128.2.15: weight check inter 5000ms
server pod:jenkins--h2fff:jenkins:10.131.0.10: 10.131.0.10: weight check inter 5000ms

其中,这些后端 server 其实就是 pod,它们是 openshift 通过步骤(1)中的 service name 找到的。balance 是负载均衡策略,后文会解释。

(3)文件 /var/lib/haproxy/conf/os_sni_passthrough.map 中多了一条记录

sh-4.2$ cat /var/lib/haproxy/conf/os_sni_passthrough.map
^sitjenkins\.com\.cn(:[-]+)?(/.*)?$

(4)文件 /var/lib/haproxy/conf/os_tcp_be.map 中多了一条记录

sh-4.2$ cat /var/lib/haproxy/conf/os_tcp_be.map
^sitjenkins\.com\.cn(:[-]+)?(/.*)?$ be_tcp:sit:sitjenkins.com.cn

(5)HAProxy 根据上面的 map 文件为该条 route 选择第(2)步中增加的 backend的逻辑如下

frontend public_ssl  #解释:前端协议 https,

  bind :443  ##前端端口 443
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type } # if the connection is SNI and the route is a passthrough don't use the termination backend, just use the tcp backend
# for the SNI case, we also need to compare it in case-insensitive mode (by converting it to lowercase) as RFC says
acl sni req.ssl_sni -m found ##检查 https request 支持 sni
acl sni_passthrough req.ssl_sni,lower,map_reg(/var/lib/haproxy/conf/os_sni_passthrough.map) -m found ##检查通过 sni 传来的 hostname 在 os_sni_patthrough.map 文件中
use_backend %[req.ssl_sni,lower,map_reg(/var/lib/haproxy/conf/os_tcp_be.map)] if sni sni_passthrough ##从 oc_tcp_be.map 中根据 sni hostname 获取 backend name # if the route is SNI and NOT passthrough enter the termination flow
use_backend be_sni if sni # non SNI requests should enter a default termination backend rather than the custom cert SNI backend since it
# will not be able to match a cert to an SNI host
default_backend be_no_sni

(6)HAPorxy 进程会重启,从而应用修改了的配置文件。

理解(5)中的脚本需要的一些背景知识:

从上面的蓝色注释中,我们能看到 HAProxy 进程通过 https 请求中通过 SNI 传入的域名 sitjenkins.com.cn ,在 os_tcp_be.map 文件中获取到了 backend 名称 be_tcp:sit:sitjenkins.com.cn,这样就和(2)步骤中的 backend 对应上了。

OpenShift 的 router 使用的 HAProxy 采用基于域名的负载均衡路由方式,示例如下,具体说明请参加官方文档。

理解OpenShift(1):网络之 Router 和 Route

2.5 OpenShift edge 和 re-encrypt 类型的 route 与 HAProxy

HAProxy 前端:前端依然是在 443 端口监听外部 HTTPS 请求

frontend public_ssl
bind :443
.....
# if the route is SNI and NOT passthrough enter the termination flow
use_backend be_sni if sni

但是,当 TLS 终止类型不是 passthrough (edge 或者 re-encrypt)时,会使用backend be_sni。

backend be_sni
server fe_sni 127.0.0.1: weight send-prox

而这个后端是由本机的 127.0.0.1:10444 提供服务,因此又转到了前端 fe_sni:

frontend fe_sni
# terminate ssl on edge
bind 127.0.0.1: ssl no-sslv3 crt /var/lib/haproxy/router/certs/default.pem crt-list /var/lib/haproxy/conf/cert_config.map accept-proxy
mode http
。。。。。。 # map to backend
# Search from most specific to general path (host case).
# Note: If no match, haproxy uses the default_backend, no other
# use_backend directives below this will be processed.
use_backend %[base,map_reg(/var/lib/haproxy/conf/os_edge_reencrypt_be.map)] default_backend openshift_default

map 映射文件:

sh-4.2$ cat /var/lib/haproxy/conf/os_edge_reencrypt_be.map
^edgejenkins\.com\.cn(:[-]+)?(/.*)?$ be_edge_http:sit:jenkins-edge

Edge 类型 route 的 HAProxy 后端:

backend be_edge_http:sit:jenkins-edge
mode http
option redispatch
option forwardfor
balance leastconn timeout check 5000ms
.....
server pod:jenkins--bqhfj:jenkins:10.128.2.15: 10.128.2.15: cookie 71c6bd03732fa7da2f1b497b1e4c7993 weight check inter 5000ms
server pod:jenkins--h2fff:jenkins:10.131.0.10: 10.131.0.10: cookie fa8d7fb72a46958a7add1406e6d26cc8 weight check inter 5000ms

Re-encrypt 类型 route 的 HAProxy 后端:

# Plain http backend or backend with TLS terminated at the edge or a
# secure backend with re-encryption.
backend be_secure:sit:reencryptjenkins.com.cn
mode http
。。。。

http-request set-header X-Forwarded-Host %[req.hdr(host)]
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
    http-request set-header X-Forwarded-Proto https if { ssl_fc }
    http-request set-header X-Forwarded-Proto-Version h2 if { ssl_fc_alpn -i h2 }

  server pod:jenkins--bqhfj:jenkins:10.128.2.15: 10.128.2.15: cookie ... weight  ssl verifyhost jenkins.sit.svc verify required ca-file /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt check inter 5000ms #与后端的链路采用 ssl 加密,并且要检查hostname
server pod:jenkins--h2fff:jenkins:10.131.0.10: 10.131.0.10: cookie ... weight ssl verifyhost jenkins.sit.svc verify required ca-file /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt check inter 5000ms

这里可以看出来重新使用密钥对连接进行加密,但是不知道为何 mode 依然是 http,而不是 https。

2.6 设置和修改 route 配置

route 配置主要有以下几个比较重要的:

(1)SSL 终结方式。共三种:

  • edge:TLS 在 router 上被终结,然后非SSL网络包被转发给后端 pod。因此需要在 router 上安装 TLS 证书。不安装的话,会使用 router 的默认证书。
  • passthrough:加密网络包直接被发给 pod,router 上不做TLS 终结,因为不需要在 router 上配置证书或密钥。
  • Re-encryption:是 edge 的一种变种。首先 router 上会使用一个证书做 TSL 终结,然后使用另外的证书再进行加密,然后发给后端 pod。因此,整个网络路径都是加密的。

设置:

(2)负载均衡策略。也有三种:

  • roundrobin:根据权重轮流使用所有后端。
  • leastconn:选择最少连接的后端接收请求。
  • source:将源IP进行哈希,确保来自同一个源IP的请求发给同一个后端。
设置:
  • 要修改整个 router 的负载均衡策略,可使用 ROUTER_TCP_BALANCE_SCHEME 环境变量,为该 router 的所有 passthrough 类型的 route设置负载均衡策略,使用 ROUTER_LOAD_BALANCE_ALGORITHM 为其它类型的 route 设置策略。
  • 可以使用 haproxy.router.openshift.io/balance 为某个 route 设置负载均衡策略。

举例:

  • 设置整个 router 的环境变量:oc set env dc/router ROUTER_TCP_BALANCE_SCHEME=roundrobin
改完以后,该 router 实例会重新部署,所有 passthrough 的 route 都是 roundrobin 类型的了。默认为 source 类型。
  • 修改某个 route 的负载均衡的策略:oc edit route aaaa.svc.cluster.local
aaarticlea/png;base64,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" alt="" />
修改完成后,HAProxy 中对应该 route 的 backend 中的 balance 值会被修改为 leastconn。

2.7 一个 route 将流量分给多个后端服务

该功能常用于一些开发测试流程,比如做A/B 测试。

在下面的配置中,有一个应用三个版本的部署,前端一个 route,各服务使用不同的权重。

理解OpenShift(1):网络之 Router 和 Route

下面是 HAProxy 配置文件中的 backend 配置,采用 roundrobin 负载均衡模式:

理解OpenShift(1):网络之 Router 和 Route

3. OpenShift router 服务如何实现高可用?

OpenShift router 服务支持两种高可用模式。

3.1 单 router 服务多副本,并利用和DNS/LB 实现高可用

这种模式只部署一个 router 服务,它支持集群的所有对外暴露的服务。要实现HA,需要设置副本数(replicas)大于1,使得会在超过一台服务器上创建pod,然后再通过DNS轮询或者四层负载均衡。

理解OpenShift(1):网络之 Router 和 Route

因为 router/pod 中的 HAProxy 要实现本地配置文件,因此实际上它们是有状态容器。OpenShift 采用 etcd 作为配置的统一存储,openshift-router 进程应该是采取某种机制(被通知或定时拉取)从 etcd 中获取 router 和 route 的配置,然后再修改本地的配置文件,再重启 HAPorxy 进程来应用新修改了的配置文件。 要深入了解这里面的工作原理,可以去看源代码。

因为master 上的服务也需要有LB(8443端口),router 服务也需要LB(80和443端口)。因此,要么采用两个LB:

理解OpenShift(1):网络之 Router 和 Route

图片来源

要么采用一个LB 来支持 master 上的服务和 router 服务:

理解OpenShift(1):网络之 Router 和 Route

图片来源

3.2 多 router 服务通过分片(sharding)实现高可用

这种模式下,管理员需要创建和部署多个 router 服务,每个router 服务支持一个或几个 project/namespace。router 和 project/namespace 之间的映射使用标签(label)来实现。具体的配置请参考官网 https://docs.openshift.com/container-platform/3.11/install_config/router/default_haproxy_router.html。实际上,和一些产品(比如mysql,memedcache)的分片功能类似,该功能更多地是为了解决性能问题,而无法完全解决高可用问题。

理解OpenShift(1):网络之 Router 和 Route

4. 常见问题如何排查?

从上面的分析可以看出,要使得 router 和 route 都正常工作,至少要确保以下几个环节都是没问题的:

  1. 客户端使用 route 中配置的域名和端口来访问服务。
  2. DNS 能将域名解析到目标 router 所在的服务器(在使用分片配置时比较复杂,尤其需要注意)。
  3. 如有采用另外的四层负载均衡器的话,它得配置正确、工作正常。
  4. HAProxy 能通过域名匹配到正确的backend。
  5. router 和 route 的配置被正确地反映到了 HAProxy 的配置文件中了。
  6. HAProxy 进程重启了,从而读取了新修改的配置文件。
  7. 后端 pod 列表正确,并且至少有一个 pod 正常工作。

如果您看到如下的错误页面,则说明上面的第3到7点至少有一处不能正常功能。此时,进行有针对性的排查即可。

理解OpenShift(1):网络之 Router 和 Route

感谢您的阅读,欢迎关注我的微信公众号:

理解OpenShift(1):网络之 Router 和 Route