理解OpenShift(1):网络之 Router 和 Routehtml
理解OpenShift(2):网络之 DNS(域名服务)前端
理解OpenShift(5):从 Docker Volume 到 OpenShift Persistent Volumeweb
** 本文基于 OpenShift 3.11,Kubernetes 1.11 进行测试 ***redis
顾名思义,Router 是路由器,Route 是路由器中配置的路由。OpenShift 中的这两个概念是为了解决从集群外部(就是从除了集群节点之外的其它地方)访问服务的需求。不晓得为何OpenShift 要将Kubernetes 中的 Ingress 改成 Router,我却是以为 Ingress 名字更贴切。sql
从外部经过 router 和从内部经过 servide 访问 pod 中的应用两个过程的简单的示意图以下:后端
上图中,某个应用的三个pod 分别位于 node1,node2 和 node3 上。OpenShift 中有三层IP地址概念:服务器
所以,要从集群外部访问 pod 中的应用,无非两种方式:微信
使用 ansible 采用默认配置部署 OpenShift 集群时,在集群 Infra 节点上,会以 Host networking 方式运行一个 HAProxy 的 pod,它会在全部网卡的 80 和 443 端口上进行监听。
[root@infra-node3 cloud-user]# netstat -lntp | grep haproxy tcp 0 0 127.0.0.1:10443 0.0.0.0:* LISTEN 583/haproxy tcp 0 0 127.0.0.1:10444 0.0.0.0:* LISTEN 583/haproxy tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 583/haproxy tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 583/haproxy
其中,172.0.0.1 上的 10443 和 10444 是HAproxy 本身使用的。下文会有解释。
所以,在每一个 infra 节点上,只能有一个 HAProxy pod,由于这些端口只能被占用一次。若是调度器找不到知足要求的节点,则router 服务的调度就会失败:
0/7 nodes are available: 2 node(s) didn't have free ports for the requested pod ports, 5 node(s) didn't match node selector
OpenShift HAProxy Router 支持两种部署方式:
OpenShift 提供了 oc adm router 命令来建立 router 服务。
建立router:
[root@master1 cloud-user]# oc adm router router2 --replicas=1 --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。
在 Router 服务的每一个 pod 之中,openshift-router 进程启动了一个 haproy 进程:
UID PID PPID C STIME TTY TIME CMD 1000000+ 1 0 0 Nov21 ? 00:14:27 /usr/bin/openshift-router 1000000+ 16011 1 0 12:42 ? 00:00:00 /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 16004
查看 haproxy 使用的配置文件(只是部分):
global maxconn 20000 daemon ca-base /etc/ssl crt-base /etc/ssl 。。。。 defaults maxconn 20000 # Add x-forwarded-for header. # server openshift_backend 127.0.0.1:8080 errorfile 503 /var/lib/haproxy/conf/error-page-503.http 。。。 timeout http-request 10s timeout http-keep-alive 300s # Long timeout for WebSocket connections. timeout tunnel 1h frontend public bind :80 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 4343), 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 :443 tcp-request inspect-delay 5s tcp-request content accept if { req_ssl_hello_type 1 } # 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 4343 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-1-84nrt:jenkins:10.128.2.13:8080 10.128.2.13:8080 cookie 8669a19afc9f0fed6824feb9fb1cf4ac weight 256 。。。
为了简单期间,上面只是配置文件的部份内容,它主要包括三种类型:
所以,OpenShift 的路由器功能须要能对这三部分进行管理和控制。
关于负载均衡器和 HAProxy 的详细介绍,能够参考 Neutron 理解 (7): Neutron 是如何实现负载均衡器虚拟化的 这篇文章。
要指定或修改 HAProxy 的全局配置,OpenShift 有提供两种方式:
(1)第一种是使用 oc adm router 命令在建立 router 时候指定各类参数,好比 --max-connections 用于设置最大链接数。好比:
oc adm router --max-connections=200000 --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=444 ROUTER_SERVICE_HTTP_PORT=81 STATS_PORT=1937
router3 会从新部署,新部署的HAProxy 的 https 监听端口是 444,http 监听端口是 80,统计端口是 1937.
(1)经过OpenShift Console 或者 oc 命令建立一条 route,它将 sit 项目的 jenkins 服务暴露到域名 sitjenkins.com.cn:
在界面上建立 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: 100 (100%) Endpoints: 10.128.2.15:8080, 10.131.0.10:8080
这里,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-1-bqhfj:jenkins:10.128.2.15:8080 10.128.2.15:8080 weight 256 check inter 5000ms server pod:jenkins-1-h2fff:jenkins:10.131.0.10:8080 10.131.0.10:8080 weight 256 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(:[0-9]+)?(/.*)?$ 1
(4)文件 /var/lib/haproxy/conf/os_tcp_be.map 中多了一条记录
sh-4.2$ cat /var/lib/haproxy/conf/os_tcp_be.map ^sitjenkins\.com\.cn(:[0-9]+)?(/.*)?$ 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 1 } # 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 4343 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 采用基于域名的负载均衡路由方式,示例以下,具体说明请参加官方文档。
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:10444 weight 1 send-prox
而这个后端是由本机的 127.0.0.1:10444 提供服务,所以又转到了前端 fe_sni:
frontend fe_sni # terminate ssl on edge bind 127.0.0.1:10444 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(:[0-9]+)?(/.*)?$ 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-1-bqhfj:jenkins:10.128.2.15:8080 10.128.2.15:8080 cookie 71c6bd03732fa7da2f1b497b1e4c7993 weight 256 check inter 5000ms server pod:jenkins-1-h2fff:jenkins:10.131.0.10:8080 10.131.0.10:8080 cookie fa8d7fb72a46958a7add1406e6d26cc8 weight 256 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-1-bqhfj:jenkins:10.128.2.15:8080 10.128.2.15:8080 cookie ... weight 256 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-1-h2fff:jenkins:10.131.0.10:8080 10.131.0.10:8080 cookie ... weight 256 ssl verifyhost jenkins.sit.svc verify required ca-file /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt check inter 5000ms
这里能够看出来从新使用密钥对链接进行加密,可是不知道为什么 mode 依然是 http,而不是 https。
route 配置主要有如下几个比较重要的:
(1)SSL 终结方式。共三种:
设置:
(2)负载均衡策略。也有三种:
举例:
该功能经常使用于一些开发测试流程,好比作A/B 测试。
在下面的配置中,有一个应用三个版本的部署,前端一个 route,各服务使用不一样的权重。
下面是 HAProxy 配置文件中的 backend 配置,采用 roundrobin 负载均衡模式:
OpenShift router 服务支持两种高可用模式。
这种模式只部署一个 router 服务,它支持集群的全部对外暴露的服务。要实现HA,须要设置副本数(replicas)大于1,使得会在超过一台服务器上建立pod,而后再经过DNS轮询或者四层负载均衡。
由于 router/pod 中的 HAProxy 要实现本地配置文件,所以实际上它们是有状态容器。OpenShift 采用 etcd 做为配置的统一存储,openshift-router 进程应该是采起某种机制(被通知或定时拉取)从 etcd 中获取 router 和 route 的配置,而后再修改本地的配置文件,再重启 HAPorxy 进程来应用新修改了的配置文件。 要深刻了解这里面的工做原理,能够去看源代码。
由于master 上的服务也须要有LB(8443端口),router 服务也须要LB(80和443端口)。所以,要么采用两个LB:
(图片来源)
要么采用一个LB 来支持 master 上的服务和 router 服务:
(图片来源)
这种模式下,管理员须要建立和部署多个 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)的分片功能相似,该功能更多地是为了解决性能问题,而没法彻底解决高可用问题。
从上面的分析能够看出,要使得 router 和 route 都正常工做,至少要确保如下几个环节都是没问题的:
若是您看到以下的错误页面,则说明上面的第3到7点至少有一处不能正常功能。此时,进行有针对性的排查便可。
感谢您的阅读,欢迎关注个人微信公众号: