理解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點至少有一處不能正常功能。此時,進行有針對性的排查便可。
感謝您的閱讀,歡迎關注個人微信公衆號: