learn.lianglianglee.com/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md.html
2022-09-06 22:30:37 +08:00

422 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>15 Service 层引流技术实践.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Kubernetes 实践入门指南/00 为什么我们要学习 Kubernetes 技术.md.html">00 为什么我们要学习 Kubernetes 技术</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md.html">01 重新认识 Kubernetes 的核心组件</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md.html">02 深入理解 Kubernets 的编排对象</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md.html">03 DevOps 场景下落地 K8s 的困难分析</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md.html">04 微服务应用场景下落地 K8s 的困难分析</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md.html">05 解决 K8s 落地难题的方法论提炼</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md.html">06 练习篇K8s 核心实践知识掌握</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md.html">07 容器引擎 containerd 落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md.html">08 K8s 集群安装工具 kubeadm 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md.html">09 南北向流量组件 IPVS 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md.html">10 东西向流量组件 Calico 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md.html">11 服务发现 DNS 的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md.html">12 练习篇K8s 集群配置测验</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md.html">13 理解对方暴露服务的对象 Ingress 和 Service</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md.html">14 应用网关 OpenResty 对接 K8s 实践</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md.html">15 Service 层引流技术实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md.html">16 Cilium 容器网络的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md.html">17 应用流量的优雅无损切换实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md.html">18 练习篇:应用流量无损切换技术测验</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md.html">19 使用 Rook 构建生产可用存储环境实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md.html">20 有状态应用的默认特性落地分析</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html">21 案例:分布式 MySQL 集群工具 Vitess 实践分析</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html">22 存储对象 PV、PVC、Storage Classes 的管理落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md.html">23 K8s 集群中存储对象灾备的落地实践</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md.html">24 练习篇K8s 集群配置测验</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>15 Service 层引流技术实践</h1>
<p>Kubernetes 引入的 Service 层给集群带来了两样特性:第一是 ClusterIP通过集群 DNS 分配的服务别名服务可以获得一个稳定的服务名字例如foo.bar.svc.cluster.local。第二是反向代理通过 iptables/IPVS/eBPF 等各种网络数据转换技术把流量负载到上游的 Pod 容器组中。到这里,其实 Service 层的基本技术已经给大家介绍了,但是从实践的角度再次分析,发现其中还有很多最新的进展需要给大家讲解以下,并从中我们能总结出技术发展过程中如何优化的策略总结。</p>
<h3>Ingress 的误解?</h3>
<p>在社区文档中介绍的 Ingress 资源,我们知道它是应对 HTTP(S) Web 流量引入到集群的场景创建的资源对象。一般介绍中我们会说它不支持 L4 层的引流。如果想支持其它网络协议,最好用 Service 的另外两种形式 <code>ServiceType=NodePort</code> 或者 <code>ServiceType=LoadBalancer</code> 模式来支持。</p>
<p>首先Ingress 资源对象能不能支持 L4 层,并不是完全由这个资源对象能把控,真正承载引流能力的是独立部署的 Ingress-Nginx 实例,也就是 Nginx 才能决定。我们知道 Nginx 本身就是支持 L4 层的。所以Ingress 通过变相增加参数的方式可以提供支持:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: ConfigMap
metadata:
name: tcp-services
namespace: default
data:
27017: &quot;default/tcp-svc:27017&quot;
---
apiVersion: flux.weave.works/v1beta1
kind: HelmRelease
metadata:
name: nginx-ingress
namespace: default
spec:
releaseName: nginx-ingress
chart:
repository: https://kubernetes-charts.storage.googleapis.com
name: nginx-ingress
version: 1.6.10
values:
tcp:
27017: &quot;default/tcp-svc:27017&quot;
</code></pre>
<p>同理UDP 协议也可以支持,在此不再赘述。从实践角度来讲,这种依赖 Nginx 的在其它 Ingress 控制器中会有不同的支持,你需要详细查阅开发文档来确认。</p>
<p>另外Ingress-Nginx 在介绍中都是直接通过 Service 来把流量负载均衡到 Pod 容器组,连很多架构图都是这么画的:</p>
<p><img src="assets/54191580-f89f-11ea-983a-f5723f270458.jpg" alt="img" /></p>
<p>在工程代码实现上默认 Ingress-Nginx 是不经过 Service也不经过 kube-proxy 访问 Pod而且通过 Service 找到对应的 Endpoint然后直接把请求分发到对应的 Pod IP 和端口。这样做的目的是规避了 Service 这层的 DNAT 转换,缺点就是没有了反向代理能力,接入流量服务也不是高可用的 SLA。为了能把 Service 这层反向代理能力重新启用,可以加入注解参数:</p>
<pre><code>nginx.ingress.kubernetes.io/service-upstream: &quot;true&quot;
</code></pre>
<p>通过这个注解特性,就可以让 Nginx 直接使用上游的 Service ClusterIP 作为上游入口。这种特性的增强有很多,如果你有兴趣,可以多到<a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/">注解特性的清单</a>这里查阅。另外,如果你是 Golang 程序员,还能扩展新注解参数并不断的给 Nginx 加入新特性,请参考<a href="https://medium.com/better-programming/creating-a-custom-annotation-for-the-kubernetes-ingress-nginx-controller-444e9d486192">案例入门</a>,这里不再赘述。</p>
<h3>Service 的误解?</h3>
<p>在社区文档中描述 Pod 发布到集群中,明确说是无法直接对外提供服务的。所以才引入了 Service 资源对象来暴露服务。但是随着实践案例的增加Pod 也是可以直接绑定主机端口的,请看如下案例:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: nginx
image: nginx
ports:
- containerPort: 8080
</code></pre>
<p>对于以 hostNetwork 方式运行的 Pod 需要显式设置其 DNS 策略 ClusterFirstWithHostNet只有这样 Pod 才会走集群 DNS 查询服务。这个范例也提醒了我们 Service IP 并不是唯一引流的方案,一定要结合实际场景来应用 Kubernetes 的特性。</p>
<p>接下来我们在来刨析下 Service IP它是由 iptables 创建的虚拟 IP 地址。根据 iptables 定义规则,统称这类 IP 为 DNAT 模式:</p>
<p><img src="assets/68015df0-f89f-11ea-9926-2b08df6c26e2.jpg" alt="png" /></p>
<p>通过 kube-proxy 生成的 iptables 规则(最新版本采用 IPVS 模块生成代理规则,这里不在赘述,原理类似),每当一个数据包的目的地是服务 IP 时,它就会被 DNAT 化DNAT = 目的地网络地址转换),也就是说目的地 IP 从服务 IP 变成了 iptables 随机选择的一个端点 Pod IP。这样可以保证负载流量可以在后端 Pod 中均匀分布。当 DNAT 发生时,这些信息会被存储在 conntrack 即 Linux 连接跟踪表中(存储 5 元组数据记录集协议、srcIP、srcPort、dstIP、dstPort。当数据回复回来的时候就可以取消 DNAT也就是把源 IP 从 Pod IP 改成 Service IP。这样一来客户端就不需要知道后面的数据包流是如何处理的。</p>
<p><img src="assets/7ca72a00-f89f-11ea-bdae-77091ca4470c.jpg" alt="png" /></p>
<p>对于从 Pod 发起并出站的数据流量,也是需要 NAT 转换的。一般来说,节点可以同时拥有私有虚拟 IP 和公有 IP。在节点与外部 IP 的正常通信中,对于出站数据包,源 IP 由节点的私有虚拟 IP 变为其公有 IP对于入站数据包的回复则反过来。但是当连接到外部 IP 是由 Pod 发起时,源 IP 是 Pod 的 IPkube-proxy 会多加一些 iptables 规则,做 SNATSource Network Address Translation也就是 IP MASQUERADE。SNAT 规则告诉内核出站数据包需要使用节点的外网 IP 来代替源 Pod 的 IP。系统还需要保留一个 conntrack 条目来解除 SNAT 的回复。</p>
<p>注意这里的性能问题由于集群容器规模的增加conntrack 会暴增,后由华为容器团队引入的 IPVS 方案也是在做大规模容器负载的压测中发现这个瓶颈,并提出引入 IPVS 来解决这个问题。在笔者实际应用中发现iptables 方案在小规模集群场景下性能和 IPVS 持平,所以 IPVS 方案从本质上来讲还只是一个临时方案,它只解决了入站流量数据包的 DNAT 转换SNAT 转换还是需要 iptables 来维护。去 iptables 化相信在不久的将来会被 eBPF 技术取代,并最终实现最优的流量引流设计方案。</p>
<h3>Ingress 的高级策略</h3>
<p>Ingress 引入后,它的用途在被不断扩大,这里我总结下可能的应用案例,期望在需要的时候可以快速应用。</p>
<p><strong>Ingress 规则汇聚能力案例</strong></p>
<pre><code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: &lt;ingress-name&gt;
spec:
rules:
- host: &lt;yourchoice1&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
http:
paths:
- path: /
backend:
serviceName: &lt;service1-name&gt;
servicePort: &lt;service1-port&gt;
- host: &lt;yourchoice2&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
http:
paths:
- path: /
backend:
serviceName: &lt;service2-name&gt;
servicePort: &lt;service2-port&gt;
</code></pre>
<p><strong>请求地址分流案例</strong></p>
<pre><code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: &lt;ingress-name&gt;
spec:
rules:
- host: &lt;yourchoice&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
http:
paths:
- path: /foo
backend:
serviceName: &lt;service1-name&gt;
servicePort: &lt;service1-port&gt;
- path: /bar
backend:
serviceName: &lt;service2-name&gt;
servicePort: &lt;service2-port&gt;
</code></pre>
<p><strong>SSL 直通案例</strong></p>
<pre><code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: &lt;ingress-name&gt;
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: &quot;true&quot;
spec:
tls:
- hosts:
- &lt;yourchoice&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
rules:
- host: &lt;yourchoice&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
http:
paths:
- path: /
backend:
serviceName: &lt;service-name&gt;
servicePort: &lt;service-port&gt;
</code></pre>
<p><strong>SSL 终结案例</strong></p>
<pre><code>apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
name: mytlssecret
data:
tls.crt: &lt;base64 encoded cert&gt;
tls.key: &lt;base64 encoded key&gt;
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: &lt;ingress-name&gt;
spec:
tls:
- hosts:
- &lt;yourchoice&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
secretName: mytlssecret
rules:
- host: &lt;yourchoice&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
http:
paths:
- path: /
backend:
serviceName: &lt;service-name&gt;
servicePort: &lt;service-port&gt;
</code></pre>
<p><strong>CORS 跨站请求案例</strong></p>
<p>要在 Ingress 规则中启用跨源资源共享CORS请添加注释</p>
<pre><code>ingress.kubernetes.io/enable-cors: &quot;true&quot;
</code></pre>
<p><strong>重写路由地址案例</strong></p>
<pre><code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: &lt;ingress-name&gt;
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: &lt;yourchoice&gt;.&lt;cluster-id&gt;.k8s.gigantic.io
http:
paths:
- path: /foo
backend:
serviceName: &lt;service-name&gt;
servicePort: &lt;service1port&gt;
</code></pre>
<p><strong>请求流量限制案例</strong></p>
<pre><code>nginx.ingress.kubernetes.io/limit-connections一个 IP 地址允许的并发连接数
nginx.ingress.kubernetes.io/limit-rps每秒从给定 IP 接受的连接数
</code></pre>
<p>在一个 Ingress 规则中同时指定这两个注解limit-rps 优先。</p>
<p><strong>后端支持接入 SSL 案例</strong></p>
<p>默认情况下Nginx 使用 HTTP 来到达服务。在 Ingress 规则中添加注解:</p>
<pre><code>nginx.ingress.kubernetes.io/secure-backends: &quot;true&quot;
</code></pre>
<p>在 Ingress 规则中把协议改为 HTTPS。</p>
<p><strong>白名单案例</strong></p>
<p>你可以通过:</p>
<pre><code>nginx.ingress.kubernetes.io/whitelist-source-range
</code></pre>
<p>注解来指定允许的客户端 IP 源范围,该值是一个逗号分隔的 CIDRs 列表,例如 10.0.0.0/24,172.10.0.1。</p>
<p><strong>Session Affinity 和 Cookie affinity</strong></p>
<p>注解:</p>
<pre><code>nginx.ingress.kubernetes.io/affinity
</code></pre>
<p>在 Ingress 的所有上行中启用并设置亲和力类型。这样一来,请求将始终被指向同一个上游服务节点。</p>
<p>如果你使用 cookie 类型,你也可以用注解:</p>
<pre><code>nginx.ingress.kubernetes.io/session-cookie-name
</code></pre>
<p>来指定用于路由请求的 cookie 的名称。默认情况下是创建一个名为 route 的 cookie。</p>
<pre><code>annotation nginx.ingress.kubernetes.io/session-cookie-hash
</code></pre>
<p>定义了将使用哪种算法对使用的上游进行哈希。默认值是 MD5可能的值是 MD5、SHA1 和 Index。Index 选项不进行哈希,而是使用内存中的索引,它更快,开销更短。</p>
<p>注意此 Index 和上游服务节点列表的匹配规则是不一致的。因此在服务重新加载时,如果上游服务节点 Pod 发生了变化,索引值不能保证与之前的服务节点 Pod 一致。请谨慎使用,只有在需要的情况下才使用此 index 算法。</p>
<h3>总结</h3>
<p>Service 层接入引流技术,主要的能力就是需要把流量准确无误的引入到服务 Pod 中当我们需要弹性、高可用时只能通过增加一层服务反向代理的冗余设计才能保证流量引入的可靠性。这种冗余设计有三种选择Ingress、NodePort、LoadBalancer目前看三种选择都无法能适应所有业务场景鉴于对 Ingress 已经在 1.19 最新版本中升级到 GA从引入流量的规范性角度首选还是 Ingress因为它的 ingress-controller 能力通过注解可以无限放大笔者预测在未来的发展趋势下Service 能力会被 Ingress 接管,大家再也不用操心 NodePort 了。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997278f9493d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>