learn.lianglianglee.com/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md.html
2022-09-06 22:30:37 +08:00

369 lines
22 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>09 南北向流量组件 IPVS 的落地实践.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 class="current-tab" 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 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>09 南北向流量组件 IPVS 的落地实践</h1>
<p>我们知道 Kubernetes 工作节点的流量管理都是由 kube-proxy 来管理的。kube-proxy 利用了 iptables 的网络流量转换能力,在整个集群的数据层面创建了一层集群虚拟网络,也就是大家在 Service 对象中会看到的术语 ClusterIP即集群网络 IP。既然 iptables 已经很完美的支持流量的负载均衡,并能实现南北向流量的反向代理功能,为什么我们还要让用户使用另外一个系统组件 IPVS 来代替它呢?</p>
<p>主要原因还是 iptables 能承载的 Service 对象规模有限,超过 1000 个以上就开始出现性能瓶颈了。目前 Kubernetes 默认推荐代理就是 IPVS 模式,这个推荐方案迫使我们需要开始了解 IPVS 的机制,熟悉它的应用范围和对比 iptables 的优缺点,让我们能有更多的精力放在应用开发上。</p>
<h3>一次大规模的 Service 性能评测引入的 IPVS</h3>
<p>iptables 一直是 Kubernetes 集群依赖的系统组件,它同时也是 Liinux 的内核模块,一般实践过程中我们都不会感知到它的性能问题。社区中有华为的开发者在 KuberCon 2018 中引入了一个问题:</p>
<blockquote>
<p>在超大规模如 10000 个 Service 的场景下kube-proxy 的南北向流量转发性能还能保持高效吗?</p>
</blockquote>
<p>通过测试数据发现,答案是否定的。在 Pod 实例规模达到上万个实例的时候iptables 就开始对系统性能产生影响了。我们需要知道哪些原因导致 iptables 不能稳定工作。</p>
<p>首先IPVS 模式 和 iptables 模式同样基于 Netfilter在生成负载均衡规则的时候IPVS 是基于哈希表转发流量iptables 则采用遍历一条一条规则来转发,因为 iptables 匹配规则需要从上到下一条一条规则的匹配,肯定对 CPU 消耗增大并且转发效率随着规则规模的扩大而降低。反观 IPVS 的哈希查表方案,在生成 Service 负载规则后,查表范围有限,所以转发性能上直接秒杀了 iptables 模式。</p>
<p>其次这里我们要清楚的是iptables 毕竟是为防火墙模型配置的工具,和专业的负载均衡组件 IPVS 的实现目标不同,这里并不能说 IPVS 就比 iptables 优秀,因为 IPVS 模式启用之后,仅仅只是取代南北向流量的转发,东西向流量的 NAT 转换仍然需要 iptables 来支撑。为了让大家对它们性能对比的影响有一个比较充分的理解,可以看下图:</p>
<p><img src="assets/d1b69cc0-dad0-11ea-a4e8-99c9f3b124a4.jpg" alt="png" /></p>
<p>从图中可以看到,当 Service 对象实例超过 1000 的时候iptables 和 IPVS 对 CPU 的影响才会产生差异,规模越大影响也越明显。很明显这是因为它们的转发规则的查询方式不同导致了性能的差异。</p>
<p>除了规则匹配的检索优势IPVS 对比 iptables 还提供了一些更灵活的负载均衡算法特性如:</p>
<ul>
<li>rr轮询调度</li>
<li>lc最小连接数</li>
<li>dh目标地址散列</li>
<li>sh源地址散列</li>
<li>sed最短期望延迟</li>
<li>nq最少队列</li>
</ul>
<p>而在 iptables 中我们默认只有轮询调度特性。</p>
<p>此刻让我们来重温一下 IPVSIP Virtual Server的定义它是构建在 Linux Netfilter 模块之上的实现南北向流量负载均衡的 Linux 内核模块。IPVS 支持 4 层地址请求的转发,把 Service 层的唯一虚拟 IP 绑定到容器副本 IP 组上,实现流量负载。所以它天然符合 Kubernetes 对 Service 的定义实现。</p>
<h3>IPVS 使用简介</h3>
<p>安装 ipvsadm这是 LVS 的管理工具:</p>
<pre><code class="language-bash">sudo apt-get install -y ipvsadm
</code></pre>
<p>创建虚拟服务:</p>
<pre><code class="language-bash">sudo ipvsadm -A -t 100.100.100.100:80 -s rr
</code></pre>
<p>使用容器创建 2 个实例:</p>
<pre><code class="language-bash">$ docker run -d -p 8000:8000 --name first -t jwilder/whoami
cd977829ae0c76236a1506c497d5ce1628f1f701f8ed074916b21fc286f3d0d1
$ docker run -d -p 8001:8000 --name second -t jwilder/whoami
5886b1ed7bd4095cb02b32d1642866095e6f4ce1750276bd9fc07e91e2fbc668
</code></pre>
<p>查出容器地址:</p>
<pre><code class="language-bash">$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' first
172.17.0.2
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' second
172.17.0.3
$ curl 172.17.0.2:8000
I'm cd977829ae0c
</code></pre>
<p>配置 IP 组绑定到虚拟服务 IP 上:</p>
<pre><code>$ sudo ipvsadm -a -t 100.100.100.100:80 -r 172.17.0.2:8000 -m
$ sudo ipvsadm -a -t 100.100.100.100:80 -r 172.17.0.3:8000 -m
$ ipvsadm -l
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-&gt; RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 100.100.100.100:http rr
-&gt; 172.17.0.2:8000 Masq 1 0 0
-&gt; 172.17.0.3:8000 Masq 1 0 0
</code></pre>
<p>到此IPVS 的负载配置步骤就给大家演示完了kube-proxy 也是如此操作 IPVS 的。</p>
<h3>kube-proxy 的 IPVS 模式配置参数详解</h3>
<ul>
<li><strong>--proxy-mode 参数</strong>:当你配置为 <code>--proxy-mode=ipvs</code>,立即激活 IPVS NAT 转发模式,实现 POD 端口的流量负载。</li>
<li><strong>--ipvs-scheduler 参数</strong>:修改负载均衡策略,默认为 rr——轮询调度。</li>
<li><strong>--cleanup-ipvs 参数</strong>:启动 IPVS 前清除历史遗留的规则。</li>
<li><strong>--ipvs-sync-period 和 --ipvs-min-sync-period 参数</strong>:配置定期同步规则的周期,例如 5s必须大于 0。</li>
<li><strong>--ipvs-exclude-cidrs 参数</strong>:过滤自建的 IPVS 规则,让你可以保留之前的负载均衡流量。</li>
</ul>
<h3>IPVS 模式下的例外情况</h3>
<p>IPVS 毕竟是为了负载均衡流量使用的还有一些场景下它是爱莫能助的。比如包过滤、端口回流、SNAT 等需求下还是要依靠 iptables 来完成。另外,还有 4 种情况下 IPVS 模式会回退到 iptables 模式。</p>
<ul>
<li>kube-proxy 启动并开启 <code>--masquerade-all=true</code> 参数</li>
<li>kube-proxy 启动时定义了集群网络</li>
<li>支持 Loadbalancer 类型的 Service</li>
<li>支持 NodePort 类型的 Service</li>
</ul>
<p>当然,为了优化 iptables 的过多规则生成kube-proxy 还引入 ipset 工具来减少 iptables 规则。以下表格就是 IPVS 模式下维护的 ipset 规则:</p>
<table>
<thead>
<tr>
<th align="left"><strong>set name</strong></th>
<th align="left"><strong>members</strong></th>
<th align="left"><strong>usage</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">KUBE-CLUSTER-IP</td>
<td align="left">All Service IP + port</td>
<td align="left">masquerade for cases that <code>masquerade-all=true</code> or <code>clusterCIDR</code> specified</td>
</tr>
<tr>
<td align="left">KUBE-LOOP-BACK</td>
<td align="left">All Service IP + port + IP</td>
<td align="left">masquerade for resolving hairpin issue</td>
</tr>
<tr>
<td align="left">KUBE-EXTERNAL-IP</td>
<td align="left">Service External IP + port</td>
<td align="left">masquerade for packets to external IPs</td>
</tr>
<tr>
<td align="left">KUBE-LOAD-BALANCER</td>
<td align="left">Load Balancer ingress IP + port</td>
<td align="left">masquerade for packets to Load Balancer type service</td>
</tr>
<tr>
<td align="left">KUBE-LOAD-BALANCER-LOCAL</td>
<td align="left">Load Balancer ingress IP + port with <code>externalTrafficPolicy=local</code></td>
<td align="left">accept packets to Load Balancer with <code>externalTrafficPolicy=local</code></td>
</tr>
<tr>
<td align="left">KUBE-LOAD-BALANCER-FW</td>
<td align="left">Load Balancer ingress IP + port with <code>loadBalancerSourceRanges</code></td>
<td align="left">Drop packets for Load Balancer type Service with <code>loadBalancerSourceRanges</code> specified</td>
</tr>
<tr>
<td align="left">KUBE-LOAD-BALANCER-SOURCE-CIDR</td>
<td align="left">Load Balancer ingress IP + port + source CIDR</td>
<td align="left">accept packets for Load Balancer type Service with <code>loadBalancerSourceRanges</code> specified</td>
</tr>
<tr>
<td align="left">KUBE-NODE-PORT-TCP</td>
<td align="left">NodePort type Service TCP port</td>
<td align="left">masquerade for packets to NodePort(TCP)</td>
</tr>
<tr>
<td align="left">KUBE-NODE-PORT-LOCAL-TCP</td>
<td align="left">NodePort type Service TCP port with <code>externalTrafficPolicy=local</code></td>
<td align="left">accept packets to NodePort Service with <code>externalTrafficPolicy=local</code></td>
</tr>
<tr>
<td align="left">KUBE-NODE-PORT-UDP</td>
<td align="left">NodePort type Service UDP port</td>
<td align="left">masquerade for packets to NodePort(UDP)</td>
</tr>
<tr>
<td align="left">KUBE-NODE-PORT-LOCAL-UDP</td>
<td align="left">NodePort type service UDP port with <code>externalTrafficPolicy=local</code></td>
<td align="left">accept packets to NodePort Service with <code>externalTrafficPolicy=local</code></td>
</tr>
</tbody>
</table>
<h3>使用 IPVS 的注意事项</h3>
<p>Kubernetes 使用 IPVS 模式可以覆盖大部分场景下的流量负载,但是对于长连接 TCP 请求的水平扩展分流是无能为力的。这是因为 IPVS 并没有能力对 keepalive_requests 做一些限制。一旦你遇到这样的场景,临时解决办法是把连接方式从长连接变为短连接。如设定请求值(比如 1000之后服务端会在 HTTP 的 Header 头标记 <code>Connection:close</code>,通知客户端处理完当前的请求后关闭连接,新的请求需要重新建立 TCP 连接,所以这个过程中不会出现请求失败,同时又达到了将长连接按需转换为短连接的目的。当然长期的解决之道,你需要在集群前置部署一组 Nginx 或 HAProxy 集群,有效帮助你限制长连接请求的阈值,从而轻松实现流量的弹性扩容。</p>
<h3>实践总结</h3>
<p>IPVS 模式的引入是社区进行高性能集群测试而引入的优化方案,通过内核已经存在的 IPVS 模块替换掉 iptables 的负载均衡实现,可以说是一次非常成功的最佳实践。因为 IPVS 内置在 Kernel 中,其实 Kernel 的版本对 IPVS 还是有很大影响的,在使用中一定需要注意。当笔者在揣摩着 IPVS 和 iptables 配合使用的纠结中,其实 Linux 社区已经在推进一个新技术 eBPF柏克莱封包过滤器技术准备一举取代 iptables 和 IPVS。如果你没有听说过这个技术你一定看过这个 Cilium 这个容器网络解决方案,它就是基于 eBPF 实现的:</p>
<p><img src="assets/8bb729b0-d661-11ea-8e3d-27a3708e9ea4.jpg" alt="png" /></p>
<p>通过 eBPF 技术,目前已经在高版本的 Kernel 之上实现了流量转发和容器网络互连,期待有一天可以完美替换 iptables 和 IPVS为我们提供功能更强、性能更好的流量管理组件。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.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":"7099726d5e383d60","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>