This commit is contained in:
周伟
2022-05-11 18:46:27 +08:00
commit 387f48277a
8634 changed files with 2579564 additions and 0 deletions

View File

@@ -0,0 +1,694 @@
<!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>00 为什么我们要学习 Kubernetes 技术.md</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 class="current-tab" href="/专栏/Kubernetes 实践入门指南/00 为什么我们要学习 Kubernetes 技术.md">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>00 为什么我们要学习 Kubernetes 技术</h1>
<p>Kubernetes 是谷歌开源的分布式容器编排和资源管理系统。因为它的英文术语字数太长社区专门给它定义了一个缩写单词K8s。从 2014 年发布至今,已经成为 GitHub 社区中最炙手可热的开源项目。因为以 K8s 为核心的云原生技术已经成为业界企业数字化转型的事实标准技术栈。国内企业纷纷效仿并开始计划落地 K8s 集群作为企业应用发布的默认基础设施,但是具体怎么落实这项云原生技术其实并没有特别好实施的工具,大部分情况下我们必须结合现有企业的实际情况来落地企业应用。当然,这个说起来容易,真正开始落地的时候,技术人员就会发现遇到一点问题能在网上查到的都是一些碎片化的知识,很难系统的解决实际应用发布和部署问题。所以,笔者借着这个场景机会,秉着布道云原生技术的信心带着大家来一起探讨 <strong>K8s 落地的各项技术细节和实际的决策思路</strong>,让 K8s 的用户可以从容自如的应对落地容器集群编排技术。</p>
<p>在学习 K8s 技术之前,我想给大家梳理下当前社区在学习 K8s 过程中遇到的几个问题:</p>
<p><strong>选择多:</strong> K8s 系统是一套专注容器应用管理的集群系统它的组件一般按功能分别部署在主控节点master node和计算节点(agent node)。对于主控节点,主要包含有 etcd 集群controller manager 组件scheduler 组件api-server 组件。对于计算节点,主要包含 kubelet 组件和 kubelet-proxy 组件。初学者会发现其实 <strong>K8s 的组件并不是特别多,为什么给人的印象就是特别难安装呢?</strong> 这里需要特别强调的是,即使到了 2020 年,我们基础软硬件设施并不能保证装完就是最优的配置,仍然需要系统工程师解决一些兼容性问题。所以当你把这些 K8s 系统组件安装到物理机、虚拟机中,并不能保证就是最优的部署配置。因为这个原因,当你作为用户在做一份新的集群部署的方案的时候,需要做很多选择题才能调优到最优解。</p>
<p>另外,企业业务系统的发布,并不止依赖于 K8s它还需要包括网络、存储等。我们知道容器模型是基于单机设计的当初设计的时候并没有考虑大规模的容器在跨主机的情况下通信问题。Pod 和 Pod 之间的网络只定义了接口标准,具体实现还要依赖第三方的网络解决方案。一直发展到今天,你仍然需要面对选择,选择适合的网络方案和网络存储。</p>
<p>这里特别强调的是,目前容器网络并没有完美方案出现,它需要结合你的现有环境和基础硬件的情况来做选择。但是,当前很多书籍资料只是介绍当前最流行的开源解决方案,至于这个方案是否能在你的系统里面跑的更好是不承担责任的。这个给系统运维人员带来的痛苦是非常巨大的。一直到现在,我遇到很多维护 K8s 系统的开发运维还是对这种选择题很头疼。是的,开源社区的方案是多头驱动并带有竞争关系的,我们不能拍脑袋去选择一个容器网络之后就不在关心它的发展的。今天的最优网络方案可能过半年就不是最优的了。同理这种问题在应对选择容器存储解决方案过程中也是一样的道理。</p>
<p><strong>排错难:</strong> 当前 K8s 社区提供了各种各样的 K8s 运维工具,有 ansible 的dind 容器化的,有 mac-desktop 桌面版本的,还有其他云原生的部署工具。每种工具都不是简单的几行代码就能熟悉,用户需要投入很大的精力来学习和试用。因为各种底层系统的多样性,你会遇到各种各样的问题,比如容器引擎 Docker 版本低,时间同步组件 ntp 没有安装,容器网络不兼容底层网络等。任何一个点出了问题,你都需要排错。加上企业的系统环境本来就很复杂,很多场景下都是没有互联网可以查资料的,对排错来说即使所有的日志都收集起来做分析也很难轻易的排错。</p>
<p>你可能会觉得这是公司的基础设施没有建设好,可以考虑专家看看。用户倒是想解决这个问题,但是不管是商业方案还是开源方案都只是片面的考虑到 K8s 核心组件的排错而真正企业关心的应用容器集群主机网络存储监控日志持续集成发布等方面的排错实践就只能靠自己摸索你很难系统的学习到。还有K8s 集群的版本是每个季度有一个大版本的更新。对于企业用户来说<strong>怎么才能在保证业务没有影响的情况下平滑更新 K8s 组件呢?</strong> 头疼的问题就是这么出来的。一旦发生不可知问题,如何排错和高效的解决问题呢。这就是本系列专栏和大家探讨的问题。</p>
<p><strong>场景多:</strong> 在早期的应用编排场景,主要是为了削峰填谷,高效利用浪费的主机资源。一个不公开的企业运维秘密就是生产中主机资源平均利用率不超过 20%。这不是因为运维傻,这是因为如果遇到峰值,主机系统需要能平滑应对,需要给业务容量留有余地。因为容器的引入让原来主机仅可以部署 3-4 个进程的系统,现在可以充分利用容器进程隔离的技术在主机上部署 20-40 个进程系统,并且各自还不受影响。这就是容器应用的最大好处。</p>
<p>社区里面有好学习的技术架构师也曾经说过在介绍什么是容器时就拿租房过程中单身公寓和打隔断的群租房来对比形容特别形象。随着应用场景的多样性在应对突发流量的时候K8s 编排系统就是作为一种弹性伸缩的工具,快速提高进程的数量来承载流量。在解决微服务应用编排上,除了传统的微服务部署需求之外,还有混合部署需要的 Service Mesh 技术也对 K8s 提出了流量编排的新要求。另外还有 Serverless 场景下的 FaaS 框架 Openfaas 也对 K8s 带来了新的机会和应用难点。还有很多有状态中间件服务,如数据库集群等也都在大量的迁入到 K8s 集群中,并获得了很好的实践反馈。</p>
<p>现在 K8s 已经成为企业数字化转型中应用发布管理的标准基础设施方案,面对这么多场景需求,我们用一套容器编排集群能把所有需求都解决吗?显然是不可以的。实际情况下很多应用集群为了管理和优化上的考虑,都是多套集群系统。多套集群在多个区域部署,给运维带来了不少麻烦。这也是多场景下多集群治理带来的烦恼。</p>
<p>以上这些问题仅仅是笔者这些年来遇到的客户在和我交流过程中积累下的重点问题。实际过程中,我们还会遇到很多不是 K8s 的问题,但是我们为了落地容器编排集群不得不做的折中选择。这些都是我们在学习过程中需要注意和总结的经验。</p>
<h3>专栏特色</h3>
<ul>
<li>从实践场景出发,按照使用者的角度来梳理技术细节</li>
<li>通过完整的知识体系为 K8s 用户提供了使用依据</li>
<li>各章节切入点考虑普适性,让更多人能有理有据的选择适合自己的问题解决方案</li>
</ul>
<h3>你将获得什么</h3>
<p>通过本专栏的学习,你将<strong>全方位的理解 K8s 组件的原理技术</strong>,并结合云原生开源思想,学习到分布式系统的组合过程。为了解决日常场景中可能的问题,你也可以分章节获得<strong>独家的实践理解和解决思路过程</strong>,让你可以推演并学习到一些架构思维模型。并且笔者也给大家精选了 K8s 组件的详细讲解,可以让好奇的使用者,不断可以知道这些组件的原理,还能知道它们内部的实现,让你可以更准确的把握这些组件,相信你也有机会参与 K8s 的开发并写出更多的组件代替它们。</p>
<h3>专栏结构</h3>
<p>我在编写此系列文章的过程中,尽量避免复制网络上大量的参考资料,通过自身搭建的系统帮助大家从零搭建起生产落地的经验。因为涉及的内容太分散,容易让读者失焦。所以我重点把生产落地中遇到的几个点给大家重点讲讲。</p>
<p>第一部分,我就想讲讲 K8s 组件的优化和安装步骤过程中需要注意的问题。这块,有很多书都讲的很细了。但是我看到的书籍中都是讲过程,不讲为什么是这样设计的。这个在当年我学习 K8s 的过程中就非常的痛苦。这次我想帮大家扫清这块的学习障碍。</p>
<p>第二部分,我们需要讲配置。一套复杂的系统本身最重要的就是熟悉各种组件的配置信息。配置错了,大家怎么调试也不会有好的结果,所以这块的各个选项的理解也是很重要的。虽然这块很枯燥但是还必须掌握。我尽量通过 kubectl 来描述这块的内容。</p>
<p>第三部分,是对网络流量的架构分享,我想这块的内容也是大量 用户特别关心的地方。因为每一种网络方案还要涉及配置,压测,选择,还是有很多需要注意的地方。</p>
<p>第四部分,就是业务应用的存储选择了。网络存储方案发展至今,最出名的就是 Ceph 了。但是如果你不合时宜的直接上马 Ceph 方案,马上就会掉入运维 Ceph 的坑里面。其实容器存储的方案有很多种,你要结合你的需求,选择合适的方案。当然在企业内部,大家都需要有那种统一的方案,什么需求都可以支持的容器存储,我觉得这个就是对业务理解的偏差。更友好的选择应该是专项专用。这样肯定会带来各种各样的方案,这个也不是不能解决,使用 K8s 来编排管理各种存储也是业务发展的必然。</p>
<p>第五部分,我们会重点关注 K8s 集群的安全配置工作。安装 K8s 算是一个中型大小的任务,对 K8s 的安全配置和调优更是业务重心。这方面的开源工具层出不穷,我们需要学习和掌握核心方法论,梳理出自己需要的工具链,来应对容器安全落地实践。</p>
<p>最后,我希望大家能尽快入坑 K8s 的学习过程中,让我们一起探讨和落地安全可靠的 K8s 容器应用编排集群。</p>
<h3>专栏作者</h3>
<p>肖德时企业级容器技术的布道师Docker/Kubernetes/Openshift 等多项国际顶级开源项目社区开发者。前容器云创业公司数人云 CTO。目前对容器技术、Kubernetes 等分布式系统等领域进行技术研究和布道。</p>
<h3>适宜人群</h3>
<p>所有运维开发工程师、系统工程师、DevOps 工程师和想掌握容器和技术的IT工程师都可以来看一看。因为云原生技术已经在中国企业广泛应用我们需要学习并更深入的学习这项技术为了更准确的理解这项技术本系列最适合各应用阶段的企业员工和技术爱好者。</p>
</div>
</div>
<div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">下一页</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":"7099725b2c923d60","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>

View File

@@ -0,0 +1,966 @@
<!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>01 重新认识 Kubernetes 的核心组件.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>01 重新认识 Kubernetes 的核心组件</h1>
<p>本篇我们开始介绍 Kubernetes 的核心组件,为了方便大家提前在脑中建立起完整的 Kubernetes 架构印象,笔者整理出核心组件的介绍如下:</p>
<ul>
<li>kube-apiserver提供了 Kubernetes 各类资源对象Pod、RC、Service 等)的增删改查及 watch 等 HTTP REST 接口,是整个系统的管理入口。</li>
<li>kube-controller-manager作为集群内部的管理控制中心负责集群内的 Node、Pod 副本、服务端点Endpoint、命名空间Namespace、服务账号ServiceAccount、资源定额ResourceQuota等对象管理。</li>
<li>kube-scheduler集群调度器提供了策略丰富弹性拓扑能力。调度实现专注在业务可用性、性能和容量等能力上。</li>
<li>kube-proxy提供南北向流量负载和服务发现的反向代理。</li>
<li>kubelet是工作节点的管理 Pod 的控制程序,专门来调度启动 Pod 容器组。</li>
<li>etcd是集群数据集中存储的分布式键值服务用来存储 Kubernetes 集群中所有数据和状态的数据库。</li>
<li>cni-plugins容器网络工作组维护的标准网络驱动如 fannel、ptp、host-local、portmap、tuning、vlan、sample、dhcp、ipvlan、macvlan、loopback、bridge 等网络插件供业务需求使用。这层 Overlay 网络只能包含一层,无法多层网络的互联互通。</li>
<li>runc运行单个容器的容器运行时进程遵循 OCI开放容器标准</li>
<li>cri-o容器运行时管理进程类似 Docker 管理工具 containerd国内业界普遍使用 containerd。</li>
</ul>
<p>我们可以用如下一张架构设计图更能深刻理解和快速掌握 Kubernetes 的核心组件的布局:</p>
<p><img src="assets/95b9e530-d605-11ea-855e-35d62ffd230b.jpg" alt="1-1-k8s-arch" /></p>
<p>通过以上的介绍,核心组件的基本知识就这么多。从最近几年落地 Kubernetes 云原生技术的用户反馈来看,大家仍然觉得这套系统太复杂,不太好管理,并且随时担心系统给业务带来致命性的影响。</p>
<p>那么 Kubernetes 的组件是为分布式系统设计的为什么大家还是担心它会影响业务系统的稳定性呢从笔者接触到的用户来讲业界并没有统一的可以直接参考的解决方案。大家在落地过程中只能去摸石头过河一点一点总结经验并在迭代中不断地改进实施方案。因为业务规模的不同Kubernetes 实施的架构也完全不同,你很难让基础设施的一致性在全部的商业企业 IT 环境中保持一致性。业务传播的都是最佳实践,在 A 用户这里可以行的通,不代表在 B 用户可以实施下去。</p>
<p>当然,除了客观的限制因素之外,我们应用 Kubernetes 的初衷是尽量的保持企业的 IT 基础设施的一致性,并随着企业业务需求的增长而弹性扩展。毕竟 Kubernetes 是谷歌基于内部 Borg 应用管理系统成功经验的基础之上开源的容器编排系统,它的发展积累了整个业界的经验精华,所以目前企业在做数字转型的阶段,都在无脑的切换到这套新的环境中,生怕技术落后影响了业务的发展。</p>
<p>本篇的目的是让大家从企业的角度更深刻的理解 Kubernetes 的组件,并能用好他们,所以笔者准备从一下几个角度来分析:</p>
<ul>
<li>主控节点组件的使用策略</li>
<li>工作节点组件的使用策略</li>
<li>工作节点附加组件的使用策略</li>
</ul>
<h3>主控节点组件的使用策略</h3>
<p>从刚接手维护 Kubernetes 集群的新用户角度考虑,一般第一步要做的就是遵循安装文档把集群搭建起来。世面上把集群安装的工具分为两类,</p>
<p>一类是学习用集群安装工具:</p>
<ul>
<li>Docker Desktop for Kubernetes</li>
<li>KindKubernetes IN Docker</li>
<li>Minikube</li>
<li>MicroK8s</li>
</ul>
<p>另一类是生产级别的安装工具:</p>
<ul>
<li>kubeadm</li>
<li>kops</li>
<li>kubespray</li>
<li>Rancher Kubernetes EngineRKE</li>
<li>K3s</li>
</ul>
<p>其中 kubeadm 和 RKE 采用了容器组件的形式来安装 Kubernetes 集群。虽然采用系统容器来运行集群环境对主机系统的侵入性降低了,但运维的维护成本会线性增加。维护过容器应用的用户都会知道,容器技术主要是对运行进程的隔离,它并不是为系统进程设计的隔离工具,容器的生命周期都很短,随时可以失败。当容器进程出错的时候,隔离环境很难还原故障的环境。常用的办法就是通过重启容器来忽略故障,期望能快速排除故障。</p>
<p>但是往往这种潜在的小问题就是让你很烦恼并对长时间无法重现感到烦恼。那么对于系统进程Linux 是有对应的系统维护工具 Systemd 来维护的。它的生态足够完善,并能在多种 Linux 环境中保持行为一致。当出现问题的时候,运维可以直接登录主机快速排查系统日志来定位排错。根据这样的经验积累,笔者推荐生产环境还是采用原生进程的方式来维护 Kubernetes 的组件,让运维可以集中精力在集群架构上多做冗余优化。</p>
<p>接下来我们重新理解 etcd 集群的架构。根据 Kubernetes 官方文档的参考资料介绍,通常按照 etcd 集群的拓扑模型可以分为两类生产级别的 Kubernetes 集群。</p>
<p>栈式 etcd 集群拓扑:</p>
<p><img src="assets/9c01f2f0-d607-11ea-8ff5-1f2baade933b.jpg" alt="kubeadm-ha-topology-stacked-etcd" /></p>
<p>独立式 etcd 集群拓扑:</p>
<p><img src="assets/639579f0-d611-11ea-8e3d-27a3708e9ea4.jpg" alt="kubeadm-ha-topology-external-etcd" /></p>
<p>参考上面的架构图,我们可以看到 etcd 集群的部署方式影响着 Kubernetes 集群的规模。在落地实践中因为采购的机器都是高性能大内存的刀片服务器,业务部门的期望是能充分的把这些资源利用上,并不期望用这些机器来跑集群控制管理组件。</p>
<p>当遇到这种情况,很多部署方案会采用第一种方案,是把主机节点、工作节点和 etcd 集群都放在一起复用资源。从高可用架构来讲,高度的应用密度集合并不能给用户带来无限制的好处。试想当出现节点宕机后这种架构的隐患是业务应用会受到极大的影响。所以通常的高可用架构经验是工作节点一定要和主控节点分开部署。在虚拟化混合环境下,主控节点可以使用小型的虚拟机来部署最为合适。当你的基础设施完全采用物理机的时候,直接使用物理机来部署主控节点是很浪费的,建议在现有物理机集群中先使用虚拟化软件虚拟化一批中小型虚拟机来提供管理节点的管理资源。开源的管理系统有 OpenStack商业的方案是 VMware vSphere按需求丰俭由人即可。</p>
<p>除了以上标准的部署解决方案,社区还提供了单机模式部署的 K3s 集群模式。把核心组件直接绑定为一个单体二进制文件,这样的好处就是这个系统进程只有一个,非常容易管理和恢复集群。在纯物理机环境,使用这种单点集群架构来部署应用,我们可以通过冗余部署多套集群的方式来支持应用的高可用和容灾。下图就是 K3s 的具体架构图:</p>
<p><img src="assets/814b8660-d611-11ea-8a86-ed86f9ad27de.jpg" alt="k3s-arch" /></p>
<p>K3s 本来是为嵌入式环境提供的精简 Kubernetes 集群但是这个不妨碍我们在生产实践中灵活运用。K3s 提供的是原生 Kubernetes 的所有稳定版本 API 接口,在 x86 集群下可以发挥同样的编排容器业务的能力。</p>
<h3>工作节点组件的使用策略</h3>
<p>在工作节点上默认安装的组件是 kubelet 和 kube-proxy。在实际部署的过程中kubelet 是有很多配置项需要调优的,这些参数会根据业务需求来调整,并不具备完全一样的配置方案。让我们再次认识一下 kubelet 组件,对于 kubelet它是用来启动 Pod 的控制管理进程。虽然 kubelet 总体启动容器的工作流程,但是具体的操作它是依赖主机层面的容器引擎来管理的。对于依赖的容器引擎,我们可以选择的组件有 containerd、ori-o 等。Kubernetes 默认配置的组件是 cri-o。但是业界实际落地部署最多的还是 containerd因为它的部署量巨大很多潜在的问题都会被第一时间解决。containerd 是从 docker 引擎抽离出来的容器管理工具,用户具备长期的使用经验,这些经验对于运维和管理容器会带来很多潜在的使用信心。</p>
<p><img src="assets/9da05520-d611-11ea-8ff5-1f2baade933b.jpg" alt="containerd-plugin" /></p>
<p>对于容器实例的维护,我们常用的命令行工具是 Docker在切换到 containerd 之后,命令行工具就切换为 ctr 和 crictl。很多时候用户无法搞清楚这两个工具的用处并和 Docker 混为一谈。</p>
<p>Docker 可以理解为单机上运行容器的最全面的开发管理工具这个不用多介绍大家都了解。ctr 是 containerd 的客户端级别的命令行工具主要的能力是管理运行中的容器。crictl 这个工具是管理 CRI 运行时环境的,在上图中是操作 cri-containerd 的组件。它的功能主要聚焦在 Pod 层面的镜像加载和运行。</p>
<p>还请大家注意下 Docker、ctr、crictl 三者细节实现上的差别。举个例子Docker 和 ctr 确实都是管理主机层面的镜像和容器的,但是他们都有自己独立的管理目录,所以你即使是同样的加载镜像的操作,在主机存储的文件位置也是不同的,他们的镜像层无法复用。而 crictl 是操作 Pod 的,它并不是直接操作镜像的进程,一般把命令发送给对应的镜像管理程序,比如 containerd 进程。</p>
<p>另外一个组件是 kube-proxy它是面向 Service 对象概念的南北向反向代理服务。通过对接 Endpoint 对象,可以按照均衡策略来负载流量。另外为了实现集群全局的服务发现机制,每一个服务都会定义全局唯一的名字,也就是 Service 的名字。这个名字可以通过附加的组件 coredns 来实现集群内的名字解析也就是服务发现。对于流量的负载Kubernetes 是通过 iptables 或 IPVSIP Virtual Server来实现。</p>
<p>在正常的集群规模下Service 并不会超过 500 个,但是华为容器技术团队做了一个极限压测,发现了 iptables 在实现反向代理的时候出现的性能瓶颈。试验验证了,当 Service 增加到足够大的时候Service 规则增加对于 iptables 是 O(n) 的复杂度,而切换到 IPVS 却是 O(1)。压测结果如下:</p>
<p><img src="assets/0885a380-d613-11ea-8ff5-1f2baade933b.jpg" alt="ipvs-iptables-perf" /></p>
<p>目前 Kubernetes 默认反向代理激活模块为 IPVS 模式iptables 和 IPVS 都是基于 Linux 的子模块 netfilter他们的相同点就是做反向代理但是还是有以下 3 点区别需要知道:</p>
<ul>
<li>IPVS 提供大规模集群扩展性和高性能</li>
<li>IPVS 提供更丰富的负载均衡算法(最小负载、最小链接数、基于局部性调度、加权轮叫等等)</li>
<li>IPVS 支持服务器健康检查和网络重试机制</li>
</ul>
<p>当 IPVS 在处理流量的 packet filtering、SNAT 和 masquerade 需求时,仍然需要使用 iptables 的扩展包工具 ipset 配置固定数量的转换规则,不会像 iptables 模式下随着服务和 Pod 的增加而线性写入规则导致系统的计算 CPU 负载加大,影响集群的处理能力。</p>
<p>以下表单是 IPVS 模式需要使用的 ipset 规则:</p>
<table>
<thead>
<tr>
<th align="left">set name</th>
<th align="left">members</th>
<th align="left">usage</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">KUBE-CLUSTER-IP</td>
<td align="left">All service IP + port</td>
<td align="left">Mark-Masq 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 solving hairpin purpose</td>
</tr>
<tr>
<td align="left">KUBE-EXTERNAL-IP</td>
<td align="left">service external IP + port</td>
<td align="left">masquerade for packages 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 packages to load balancer type service</td>
</tr>
<tr>
<td align="left">KUBE-LOAD-BALANCER-LOCAL</td>
<td align="left">LB ingress IP + port with <code>externalTrafficPolicy=local</code></td>
<td align="left">accept packages 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">package filter for load balancer 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">package filter for load balancer 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 packages 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 packages to nodeport service with <code>externalTrafficPolicy=local</code></td>
</tr>
</tbody>
</table>
<p>另外IPVS 模式会在以下场景下降级使用 iptables 模式:</p>
<ul>
<li>kube-proxy 启动的同时启用 <code>--masquerade-all=true</code></li>
<li>kube-proxy 启动时指定 Pod 层网段</li>
<li>Load Balancer 类型的 Service</li>
<li>NodePort 类型的 Service</li>
<li>配置了 externalIP 的 Service</li>
</ul>
<h3>工作节点附加组件的使用策略</h3>
<p>提到附加的组件,一般常识是这些组件可有可无,锦上添花的能力而已。但是在 Kubernetes 集群中这些附加组件是不得不安装的不然整个集群就是一套鸡肋的展览品。Kubernetes 官方把这些附加组件分为以下五类:</p>
<ul>
<li>网络和网络策略</li>
<li>服务发现</li>
<li>可视化管理</li>
<li>基础设施</li>
<li>遗留组件</li>
</ul>
<p>大家看到标题,基本上就能理解这些组件的用处。我这里还是从实用的角度和大家一起重新认识一下这些组件,为之后的使用提供经验参考。</p>
<p><strong>1. 网络和网络策略</strong></p>
<p>对于网络,我们主要指容器网络。注意在 Kubernetes 集群里面,是有两层虚拟网络的。一说虚拟网络,就会有丢包率,这个是以往虚拟化环境不可想象的问题。为了提高或者说规避这方面的棘手问题,我们会放弃所有官方的方案,采用传统的网络方案来支持。当然传统的网络方案大都不是为 Kubernetes 网络设计的,需要做很多自定义适配工作来完善体验。在不理想的传统方案之外,容器网络方案中最流行的有 Calico、Cilium、Flannel、Contiv 等等。采用这些方案之后随着业务流量的增加一定会带来网络丢包的情况。网络丢包带来的问题是业务处理能力的降低为了恢复业务实例的处理能力我们常规的操作是水平扩展容器实例数。注意正是因为实例数的增加反而会提高业务处理能力让运维人员忽略容器网络带来的性能损耗。另外Kubernetes 在业务实践中还参考了主流网络管理的需求设计,引入了 Network Policies。这些策略定义了 Pod 之间的连通关系,方便对业务容器组的安全网络隔离。当然笔者在实践中发现,这些策略完全依赖容器网络的实现能力,依赖性强,只能作为试验品体验,但是在实际业务中,还没有看到实际的能力优势。</p>
<p><strong>2. 服务发现</strong></p>
<p>目前提供的能力就是给 Pod 提供 DNS 服务,并引入了域名的定义规则。官方认可的只有 CoreDNS。注意 ,这个服务发现只能在集群内部使用。不推荐直接暴露给外部服务,集群对外暴露的服务仍然是 IP 和端口。外部 DNS 可以灵活的指定这个固定 IP 来让业务在全局服务发现。</p>
<p><strong>3. 可视化管理</strong></p>
<p>官方提供了 Dashboard这是官方提供的标准管理集群的 web 界面,很多开发集成测试环境,使用它就可以满足业务管理的需求。这个可选安装。</p>
<p><strong>4. 基础设施</strong></p>
<p>官方提供了 KubeVirt是可以让 Kubernetes 运行虚拟机的附加组件,默认运行在裸机群集上。从目前的实践经验来看,这种能力还属于试验性的能力,一般很少人使用。</p>
<p><strong>5. 遗留组件</strong></p>
<p>对于很多老版本的 Kubernetes有很多历史遗留的组件可以选用所以官方把这些可选的组件都保留了下来帮助用户在迁移集群版本的过程中可以继续发挥老集群的能力。一般很少人使用。</p>
<p>通过三个纬度的介绍,我相信大家对 Kubernetes 的核心组件有了更深入的理解。在生产实践中,为了标准化运维模型,我们对 Kubernetes 的组件可以按照业务需求定义一个基线模型,有选择的使用这些组件,相信一定可以规避很多兼容性的问题。在笔者遇到的大部分的 Kubernetes 集群故障案例中,大部分就是对组件的错用或者误用,让问题变的更复杂,更难以复现。</p>
<p>当然,云端的 Kubernetes 可以彻底解决基线的问题,我相信未来用户会越来越容易的使用到靠谱的 Kubernetes 集群环境。只是你一定要记住,我们只是把运维 Kubernetes 的难题交给专业的云服务开发者而已。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/00 为什么我们要学习 Kubernetes 技术.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">下一页</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":"7099725d296a3d60","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>

View File

@@ -0,0 +1,732 @@
<!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>03 DevOps 场景下落地 K8s 的困难分析.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>03 DevOps 场景下落地 K8s 的困难分析</h1>
<p>Kubernetes 是用于自动部署,扩展和管理容器化应用程序的开源系统,一般被 DevOps 团队用来解决在 CI/CD也就是持续集成、持续发布场景下遇到的工具链没法统一构建过程没法标准化等痛点。DevOps 团队在落地 Kubernetes 的过程中发现,在安装、发布、网络、存储、业务滚动升级等多个环节都会遇到一些不可预期的问题,并且官方的参考资料并没有确定性的方案来解决。很多 DevOps 因为需要快速迭代,都不得不采用现有的经验临时解决遇到的问题,因为场景限制,各家的问题又各有各的诉求,让很多经验无法真正的传承和共享。本文旨在直面当前的 DevOps 痛点,从源头梳理出核心问题点,并结合业界最佳的实践整理出一些可行的方法论,让 DevOps 团队在日后落地可以做到从容应对,再也不用被 Kubernetes 落地难而困扰了。</p>
<h3>Kubernetes 知识体系的碎片化问题</h3>
<p>很多 DevOps 团队在落地 Kubernetes 系统时会时常借助互联网上分享的业界经验作为参考,并期望自己少点趟坑。但是当真落地到具体问题的时候,因为环境的不一致,场景需求的不一致等诸多因素,很难在现有的方案中找到特别合适的方案。</p>
<p>另外还是更加糟糕的情况是,网上大量的资料都是过期的资料,给团队的知识体系建设带来了很多障碍。虽然团队可以借助外部专家的指导、专业书籍的学习等多种方法,循序渐进地解决知识的盲点。我们应该避免 Kubernetes 爆炸式的知识轰炸,通过建立知识图谱有效地找到适合自己团队的学习路径,让 Kubernetes 能支撑起你的业务发展。以下就是笔者为你提供的一份知识图谱的参考图例:</p>
<p><img src="assets/5586f7d0-d639-11ea-aefa-4fa1d18dcf14.jpg" alt="img" /></p>
<p>有了图谱,你就有了一张知识导航图,帮助你在需要的时候全局了解团队的 Kubernetes 能力。</p>
<h3>容器网络的选择问题</h3>
<p>容器网络的选择难题一直是 DevOps 团队的痛点。Kubernetes 集群设计了 2 层网络,第一层是服务层网络,第二层是 Pod 网络。Pod 网络可以简单地理解为东西向容器网络,和常见的 Docker 容器网络是一致的设计。服务层网络是 Kubernetes 对外暴露服务的网络简单地可以理解为南北向容器网络。Kubernetes 官方部署的常见网络是 Flannel它是最简化的 Overlay 网络,因为性能不高只能在开发测试环境中使用。为了解决网络问题,社区提供了如 Calico、Contiv、Cilium、Kube-OVN 等诸多优秀的网络插件,让用户在选择时产生困惑。</p>
<p>首先企业在引入 Kubernetes 网络时,仅仅把它作为一套系统网络加入企业网络。企业网络一般设计为大二层网络,对于每一套系统的网络规划都是固定的。这样的规划显然无法满足 Kubernetes 网络的发展。为了很好地理解和解决这样的难题,我们可以先把大部分用户的诉求整理如下:</p>
<ul>
<li>第一 ,由于容器实例的绝对数量剧增,如果按照实例规划 IP 数量,显然不合理。</li>
<li>第二、我们需要像虚拟机实例一样,给每一个容器实例配置固定的 IP 地址。</li>
<li>第三、容器网络性能不应该有损耗,最少应该和物理网络持平。</li>
</ul>
<p>在这样的需求下网络性能是比较关键的指标。查阅网上推荐的实践可以看到一些结论Calico 的虚拟网络性能是接近物理网络的,它配置简化并且还支持 NetworkPolicy它是最通用的方案。在物理网络中可以采用 MacVlan 来获得原生网络的性能,并且能打通和系统外部网络的通信问题。</p>
<p>这样的信息虽然是网上分享的最佳实践,我们仍然会不太放心,还是需要在本地网络环境中通过测试来验证的。 这种网络验证是必须的,只是在选择网络选型时,我们大可不必把每一项网络方案都去测试一遍。我们可以遵循一些合理的分类来明确方向。</p>
<p>其实,我们应该回到 Kubernetes 的设计初衷,它是一套数据中心级别的容器集群系统,我们可以给它独立的网络分层。按照这个方向选择,它的网络应该采用虚拟网络,这样我们就会看到可以选择的方案是 Flannel、Calico、Cilium。</p>
<p>还有另外一个方向,就是希望 Kubernetes 网络是你整个企业网络的延展,这样的设计目标之下,等于你给 Kubernetes 网络落地原有的网络。Kubernetes 上的对象系统全部压缩到只使用 Pod 网络这一层。笔者人为在很多传统遗留的系统中,如果想落地 Kubernetes 方案就会遇到这样的问题。在这样的场景下,应该采用 Contiv、Kube-Ovn 直接对接原生网络。这样的设计会平滑的让很多遗留系统平滑的迁入到云原生的网络之中。这是非常好的实践。</p>
<h3>存储方案的引入问题</h3>
<p>容器 Pod 是可以挂载盘的,对于外挂盘有本地存储、网络存储,对象存储这样的三类。在早期 Kubernetes 发展阶段,容器存储驱动是百花齐放,很多驱动的 Bug 导致容器运行出现各种各样的问题。现在 Kubernetes 社区终于制定了 CSI 容器存储标准方案,并且已经达到生产可用阶段。所以,我们在选择存储驱动的时候一定需要选择 CSI 的存储驱动来调用存储。</p>
<p>本地存储原来都是直接挂载目录,现在采用 CSI 方式之后,本地的资源也期望采用 PV、PVC 的方式来申请,不要在直接 Mount 目录了。</p>
<p>对于网络存储,一般我们特别指定 NFS。NFS 比较特别的地方是它是共享挂载方式,也就是一个目录可以被多台主机挂载。但是 PV、PVC 的挂载方式仍然期望目标存储是唯一的,这个时候,一定要注意规划好 NFS 的目录结构。还有需要注意的是存储的大小在 NFS 中是没法限制的,完全有底层 NFS 来规划。为了有效管理存储空间大小DevOps 团队可以手动创建 PV 并限制好空间大小,然后让用户采用 PVC 来挂载这个手动创建的 PV。</p>
<p>对于对象存储,因为抽象能力的增强,可以实现 PV 的动态创建和 PVC 的自动挂载,并且申请的大小可以动态满足。这个是最理想的存储方案,但是因为后端存储系统如 Ceph 类需要专人维护,才能保证系统的问题性。在投入资源时需要多加考虑。</p>
<h3>容器引擎的选择问题</h3>
<p>很多人好奇这有什么好选择的,直接安装 Docker 不就完事了。因为容器技术的发展,目前 Kubernetes 官方引擎已经默认安装为 Cri-O 开源引擎。原来Kubernetes 社区给容器引擎定义了一套标准,所以容器引擎开始出现多元化。为了更清楚地理解容器引擎的位置,我们可以通过一张图来详细理解容器引擎的位置:</p>
<p><img src="assets/92bc5f00-d639-11ea-8a86-ed86f9ad27de.jpg" alt="img" /></p>
<p>显而易见Cotnainerd 已经取代 Docker 的位置,由于 Containerd 源自 Docker 源码,它的可靠性是经过多年的历练的,目前是最可靠的容器引擎。</p>
<p>除此之外,当业务发展需要实现多租户时,对于主机环境不在信任,这个时候的容器引擎需要更进一步的隔离。目前可以选择的方案有 KataContainer、firecracker、gVisor。这种技术一般被称为富容器技术通过采用裁剪虚拟化组件来彻底隔离容器环境是真正轻量级虚拟机。打个比方你就会明白。因为服务器的规格越来越高级CPU 一般都达到 32 核,内存高达 256G 的规格都很常见,在这么大容量的主机上,如果只跑一个用户群体的容器,显然会浪费。原来我们实现资源划分都是采用虚拟机隔离一层之后在分配给业务,业务 DevOps 团队在规划 Kubernetes 集群资源。当我们把虚拟化这一层例如 Openstack 和 Kubernetes 合并之后,势必需要把虚拟化技术真正引入到 Kubernetes 中,所以,这就是富容器的意义所在。当然这块的实践配置仍然还无法做到傻瓜方式,仍然需要专业的开发人员进行调优,所以我们还需要谨慎试用。</p>
<h3>集群规模的规划问题</h3>
<p>对于企业来说,一套系统应该只希望部署一套,减少管理运维成本。但是毕竟 Kubernetes 是一套开源系统,在很多场景下它并没有办法解决跨网的管理,我们不得不为了业务划分部署多套集群。多套集群等于就是多套基础设施,让很多 DevOps 团队开始感到一些运维压力。这里我们可以对比一下:</p>
<p><img src="assets/a9a8bfb0-d639-11ea-939d-ff5108240fa1.jpg" alt="img" /></p>
<p>这里我认为应该参考谷歌内部集群运维的经验,按照数据中心的规模,每个数据中心只规划一套集群。那么国内企业比较头疼的现状是内部网络采用防火墙隔离成若干隔离区域,每个网络区域直接通过白名单方式开放有限的端口,甚至生产网络只允许通过跳板机执行运维操作。如果是一套 Kubernetes 集群部署到这样的复杂网络中,势必需要规划梳理很多参数和规则,比分开部署集群需要投入更多的精力。</p>
<p>为了有效地解决这个难题,我们可以通过迭代的办法,先期采用多套集群的模式,然后通过主机标签和 Namespace 空间方式,不断把多个集群的主机归并到单一集群中。当然,谷歌的安全级别只有一种,不管是开发测试还是集成生产都是一个安全策略,这非常适合一套集群的规划。但是很多企业的安全级别是分开管理的,服务质量 SLA 也是不一样的。在这样的情况下,我们可以把集群分为开发测试集群和生产集群,也是很合理的规划。毕竟,安全是企业的生命线,然后才是集群运营成本的规划。</p>
<h3>安全审计的引入问题</h3>
<p>Kubernetes 系统是一套复杂的系统,它的安全问题也是企业非常重视的环节。首先,对于集群调用的认证和授权,原生有一套 RBAC角色的权限访问控制模型。这种 RBAC 在角色权限不是很多的情况下,它是可以支撑的。但是对于更细力度的控制就无法轻易满足了。比如:允许用户访问用户的 Namespace但是不允许访问 kube-system 系统级的命名空间这样细粒度的虚拟。社区提供了 Open Policy Agent 工具就是来解决这个问题的。</p>
<p><img src="assets/c744db80-d639-11ea-8e3d-27a3708e9ea4.jpg" alt="img" /></p>
<p>简单地说RBAC 是白名单做法,用户规则多的情况下,策略变更需要涉及多个角色的定义更新,维护成本高。采用 OPA 是黑名单的做法,只需要一个规则就可以搞定变更。</p>
<p>另外,企业 Kubernetes 的安全情况需要借助一些工具来定期审查。比较出名的工具是 CIS Kubernetes Benchmark你可以参考应用。</p>
<h3>业务保障团队的建设问题</h3>
<p>很多 DevOps 团队在接手 Kubernetes 之后,明显发现这套系统的运维难度是之前其它系统的数倍。对于业务稳定性的要求给 DevOps 团队带来很多不确定的压力。很明显的原因是Kubernetes 对人员的能力要求提高了。</p>
<p>参考谷歌的 SRE 团队的建设历程我发现这是国内企业比较缺失的一个岗位。SRE 在国内传统企业并不多见,它类似资深运维架构师,但处理问题的视角以业务为中心来保障企业的正常运营。随着阿里系在引入业务保障体系之后的成功,国内领先的大厂已经渐渐接受了这种新的角色,并且还在不断升华这个岗位的能力范围。</p>
<p>对于传统企业来说,现有 DevOps 人员如何有效的升级知识结构,并能转变思路以业务保障为中心全局的来思考问题成为新的课题。从资源上来讲,很多企业的技术能力是由合作伙伴的整合来完成的,并不是一定需要传统企业打破原有企业的岗位规划,完全采用互联网的做法也是有很多风险在里面。因为传统企业的第一要素是安全,然后才是可靠性。因为传统企业的数据可靠性早就比互联网企业要成熟很多,通过大量的冗余系统足够保证数据的完整性。在这样的情况之下,原厂的 DevOps 团队应该充分理解 Kubernetes 的能力缺陷,多借助合作伙伴的技术合作共赢的方式,让 Kubernetes 系统的落地更加稳健。</p>
<h3>集群安装的问题</h3>
<p>大家别小看 Kubernetes 的安装工具的问题,目前业界有很多种 Kubernetes 集群安装部署方式,这让企业的 DevOps 团队感到困惑。</p>
<p>首先Kubernetes 的核心组件发布的都是二进制版本,也就是在主机层面可以使用 systemd 的方式来安装部署。有一些部署版本采用静态 Pod 的方式来部署都是比较特别的部署方式,不建议传统企业采用。还有一些发行版本完全采用容器的部署方式来数据,虽然早期使用过程中感觉很方便,但是在日后运维中,因为容器的隔离导致组件的状态无法第一时间获取,可能会给业务故障的排查带来一定的障碍。所以笔者推荐的方式还是采用二进制组件的方式来部署最佳。当前 Kubernetes 官方主推的是 kubeadm 来安装集群,但是 kubeadm 竟然也是采用镜像来部署核心组件,虽然在便捷性上给用户节省了很多事情,但是也给未来的故障运维带来很多坑,请小心使用。</p>
<p>其次,因为企业对集群有高可用的需求,所以 Master 节点一般配置为 3 台。其中,最重要的组件就是 etcd 键值集群的维护。其中让企业很容易混淆的地方是Master 节点只要有 3 台以上,我们的系统就是高可用的,其实不然。如果 3 台 Master 节点放在一个网络区域,当这个网络区域出现抖动的时候,服务仍然还是会出问题。解决办法就是把 Master 放在 3 个不同的网络区域才能实现容错和高可用。</p>
<p>另外,还有很大一部分企业的情况是服务器的规模有限,比如 5~6 台左右,也想使用 Kubernetes 集群。如果划出 3 台作为 Master 节点不跑业务会觉得很浪费。这个时候,我认为应该采用更轻量级的 Kubernetes 集群系统来支持。这里我推荐 K3s 集群系统,它巧妙的把所有核心组件都编译到一个二进制程序里,只需要 40M 大小就可以部署。虽然这个集群是单机版本的集群,但是 Kubernetes 集群的 Node 节点上承载的业务并不会因为 Master 节点宕机就不可以访问。我们只需要在业务上做到多节点部署,就可以完美切合这样的单节点集群。企业可以根据需要灵活采用这种方案。</p>
<p>综上所述,我认为企业落地 Kubernetes 是有一定的技术挑战的DevOps 需要迎面接受挑战并结合落地情况选择一些合理的方案。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">下一页</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":"709972611b0c3d60","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>

View File

@@ -0,0 +1,828 @@
<!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>04 微服务应用场景下落地 K8s 的困难分析.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>04 微服务应用场景下落地 K8s 的困难分析</h1>
<p>近些年企业应用开发架构发生了细微的变化根据康威定律由于企业组织架构的变化导致微服务应用体系开始在企业应用开发过程中流行起来。微服务是最近几年企业数字化转型过程中在技术团队技术选型中比较常见的架构升级方案之一。在这个背景下DevOps 团队为了应对企业架构的变化,迫切需要使用一套统一的基础设施来维护微服务应用的整个生命周期,这就给我们带来了新的挑战——如何应对微服务应用场景,平稳快速的落地 Kubernetes 集群系统。</p>
<h3>基于 Kubernetes 下的微服务注册中心的部署问题</h3>
<p>经典的微服务体系都是以注册中心为核心,通过 CS 模式让客户端注册到注册中心服务端,其它微服务组件才能互相发现和调用。当我们引入 Kubernetes 之后,因为 Kubernetes 提供了基于 DNS 的名字服务发现,并且提供 Pod 级别的网格,直接打破了原有物理网络的单层结构,让传统的微服务应用和 Kubernetes 集群中的微服务应用无法直接互联互通。为了解决这个问题,很多技术团队会采用如下两种方式来打破解决这种困境。</p>
<h4><strong>创建大二层网络,让 Pod 和物理网络互联互通</strong></h4>
<p>这个思路主要的目的是不要改变现有网络结构,让 Kubernetes 的网络适应经典网络。每一个 Pod 分配一个可控的网络段 IP。常用的方法有 macvlan、Calico BGP、Contiv 等。这样的做法直接打破了 Kubernetes 的应用架构哲学,让 Kubernetes 成为了一个运行 Pod 的资源池,而上面的更多高级特性 ServiceIngress、DNS 都无法配合使用。随着 Kubernetes 版本迭代,这种阉割功能的 Kubernetes 架构就越来越食之无味弃之可惜了。</p>
<h4><strong>注册中心部署到 Kubernetes 集群中,外网服务直接使用 IP 注册</strong></h4>
<p>这种思路是当前最流行的方式,也是兼顾历史遗留系统的可以走通的网络部署结构。采用 StatefulSet 和 Headless Service我们可以轻松地搭建 AP 类型的注册中心集群。当 Client 端连接 Server 端时,如果在 Kubernetes 内部可以采用域名的方式。例如:</p>
<pre><code class="language-yaml">eureka:
client:
serviceUrl:
defaultZone: http://eureka-0.eureka.default.svc.cluster.local:8761/eureka,http://eureka-1.eureka.default.svc.cluster.local:8761/eureka,http://eureka-2.eureka.default.svc.cluster.local:8761/eureka
</code></pre>
<p>对于集群外部的微服务,可以直接采用 IP 直连 Servicer 端的 NodeIP例如</p>
<pre><code class="language-yaml">eureka:
client:
serviceUrl:
defaultZone: http://&lt;node-ip&gt;:30030/eureka
</code></pre>
<p>大家通过上面两种方案肯定都会得出一个结论,如果考虑和传统网络耦合在一起,怎么部署都不是很理想。我们如何解决这样的困局呢?</p>
<p>我们回顾 Kubernetes 设计之初就会发现,它是为数据中心设计的应用基础设施,并没有设计能兼容传统网络的架构,所以才导致我们部署起来感觉怎么操作都不对劲。但是企业内部的业务逻辑复杂,技术团队一般都是小心谨慎地把业务系统慢慢迁移到新的云原生的集群中,所以我们势必又会遇到这样的混合架构的场景。这个时候我们可以借鉴业界实践过的单元化设计,按照网关为边界,划分应用单元,把完整的一套微服务上架到 Kubernetes 中。这样Kubernetes 集群和外部的服务之间的调用可以采用 RPC/HTTP API 的方式进行异构调用,从而规避打破 Kubernetes 云原生体系。</p>
<h3>微服务核心能力的优化设计问题</h3>
<p>经典微服务架构的内部,服务和服务之间的函数调用,我们通常见过的有 Spring Feign/Dubbo RPC/gRPC ProtoBuf 等。为了能知道服务的所在位置,我们必须有一个注册中心才能获得对方的 IP 调用关系。然后你在结合 Kubernetes 集群的 CoreDNS 的实现,你会自然想到一个问题,如果所有服务组件都在一个 Namespace 之下它们直接的关系直接可以在配置文件里面写入名字CoreDNS 是帮助我们实现在集群之下的服务发现的。也就是说,当你把微服务部署到 Kubernetes 之后,像 Eureka 这样的服务基本就是鸡肋了。</p>
<p>很多有经验的架构师会说,道理是这样的,但是例如 Spring Cloud 的微服务体系就是以 Eureka 为中心的,可能不用它不行。这个问题我觉得是历史遗留问题,以 Spring Boot 框架做为基础,我们完全可以基于 Kubernetes 的服务发现能力构建微服务体系。</p>
<p>另外,因为 Kubernetes 的 Pod 设计包含了 SideCar 模型,所以我们可以把通用的微服务关心的限流、熔断、安全 mTLS、灰度发布等特性都放在一个独立的 Sidecar proxy 容器中,代理所有这些通用的容器治理需求。这样就可以极大的解放开发人员的心智模型,专心写业务代码就可以了。大家已经看出来,这不就是服务网格吗?是的,确实融入到服务网格的设计模式中了,但是当前的服务网格参考 Istio 并没有在业界大量落地使用,我们仍需要利用现有的微服务框架自建这样的体系。</p>
<p>另外,微服务体系的业务观测能力,通过 Kubernetes 的生态图,我们可以采用 ELK 收集业务日志,通过 Prometheus 监控加上 Grafana 构建可视化业务模型,更进一步完善微服务的能力设计体系。</p>
<h3>微服务应用的部署策略痛点</h3>
<p>很多微服务应用在部署 Kubernetes 集群时,多采用 Deployment 对象。其实当前 Kubernetes 还提供了 StatefulSet 对象。这个 Workload 对象一般开发者望文生义,以为就是有状态的,需要挂盘才用这个。其实 StatefulSet 还提供了强劲的滚动更新的策略,因为 StatefulSet 对每一个 Pod 都提供了唯一有编号的名字,所以更新的时候可以按照业务需要一个一个地更新容器 Pod。这个其实对业务系统来说特别重要我甚至认为微服务的容器服务都应该用上 StatefulSet 而不是 Deployment。Deployment 其实更适合 Node.js、Nginx 这样的无状态需求的应用场景。</p>
<p><img src="assets/2eb32890-d65c-11ea-81fd-87b070d29677.jpg" alt="img" /></p>
<p>另外,微服务部署在 Kubernetes并不是说你的微服务就是高可用了。你仍然需要提供亲和性/反亲和性策略让 Kubernetes 调度应用到不同的主机上,让业务容器能合理地分布,不至于当出现宕机时直接导致“血崩”现象,并直接影响你的业务系统。示例:</p>
<pre><code class="language-yaml">affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- weight: 100
labelSelector:
matchExpressions:
- key: k8s-app
operator: In
values:
- kube-dns
topologyKey: kubernetes.io/hostname
</code></pre>
<p>微服务应用在更新过程中,肯定需要更新 Endpoint 和转发规则,这个一直是 Kubernetes 集群的性能瓶颈点,我们可以采用 readinessProbe 和 preStop 让业务更平滑地升级,示例如下:</p>
<pre><code class="language-yaml">apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
component: nginx
template:
metadata:
labels:
component: nginx
spec:
containers:
- name: nginx
image: &quot;nginx&quot;
ports:
- name: http
hostPort: 80
containerPort: 80
protocol: TCP
readinessProbe:
httpGet:
path: /healthz
port: 80
httpHeaders:
- name: X-Custom-Header
value: Awesome
initialDelaySeconds: 15
timeoutSeconds: 1
lifecycle:
preStop:
exec:
command: [&quot;/bin/bash&quot;, &quot;-c&quot;, &quot;sleep 30&quot;]
</code></pre>
<p>最后,运维团队因为要处理的微服务容器会越来越多,可以给应用容器加上 PodDisruptionBudget 限制,保证在运维过程中不会影响到现有业务,示例如下:</p>
<pre><code class="language-yaml">apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: zookeeper
</code></pre>
<h3>总结</h3>
<p>在 Kubernetes 中有一个 Service 对象,翻译成中文就是服务对象,很容易和微服务中的服务对上关系,其实它们之间没有任何关系。我们不应该把微服务架构直接生搬硬套地落地到容器集群中,更科学的办法就是熟悉并统筹微服务体系和 Kubernetes 体系中各自的特性,在设计自己的微服务架构时,能详细思考 CI/CD 过程中、发布过程中到底我们需要那些业务需求,然后在分析应该用哪种方案更合适自己的团队,只有这样才能彻底拜托历史包袱,让 Kubernetes 技术能在合适的位置发挥应有的作用。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">下一页</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":"709972631fe33d60","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>

View File

@@ -0,0 +1,790 @@
<!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>05 解决 K8s 落地难题的方法论提炼.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>05 解决 K8s 落地难题的方法论提炼</h1>
<p>做过技术落地的读者应该有所体会,任何技术经过一段时间的积累都会形成一套约定成熟的方法论,包括 Kubernetes 也不例外。在这些落地实践中比较突出的问题有构建集群的问题、CI/CD 如何构建的问题、资源租户管理的问题,还有安全问题最为突出。本文为了让使用 Kubernetes 的后来者能少走弯路,通过总结前人经验的方式给大家做一次深度提炼。</p>
<h3>构建弹性集群策略</h3>
<p>Kubernetes 集群架构是为单数据中心设计的容器管理集群系统。在企业落地的过程中,因为场景、业务、需求的变化,我们已经演化出不同的集群部署方案,大概分类为统一共享集群、独立环境多区集群、应用环境多区集群、专用小型集群:</p>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">成本</th>
<th align="left">管理</th>
<th align="left">弹性</th>
<th align="left">安全</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">统一共享集群</td>
<td align="left"><img src="assets/8cfbd0f0-d65c-11ea-9203-1bcff1a80007.jpg" alt="green" /></td>
<td align="left"><img src="assets/8cfbd0f0-d65c-11ea-9203-1bcff1a80007.jpg" alt="green" /></td>
<td align="left"><img src="assets/e37fa4b0-d65c-11ea-b558-cd3c105f83ae.jpg" alt="red" /></td>
<td align="left"><img src="assets/e37fa4b0-d65c-11ea-b558-cd3c105f83ae.jpg" alt="red" /></td>
</tr>
<tr>
<td align="left">独立环境多区集群</td>
<td align="left"><img src="assets/b1b81cf0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="yellow" /></td>
<td align="left"><img src="assets/b1b81cf0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="yellow" /></td>
<td align="left"><img src="assets/c8edbba0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="brown" /></td>
<td align="left"><img src="assets/c8edbba0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="brown" /></td>
</tr>
<tr>
<td align="left">应用环境多区集群</td>
<td align="left"><img src="assets/c8edbba0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="brown" /></td>
<td align="left"><img src="assets/c8edbba0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="brown" /></td>
<td align="left"><img src="assets/b1b81cf0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="yellow" /></td>
<td align="left"><img src="assets/b1b81cf0-d65c-11ea-a0a4-91ded31f57b1.jpg" alt="yellow" /></td>
</tr>
<tr>
<td align="left">专用小型集群</td>
<td align="left"><img src="assets/e37fa4b0-d65c-11ea-b558-cd3c105f83ae.jpg" alt="red" /></td>
<td align="left"><img src="assets/e37fa4b0-d65c-11ea-b558-cd3c105f83ae.jpg" alt="red" /></td>
<td align="left"><img src="assets/8cfbd0f0-d65c-11ea-9203-1bcff1a80007.jpg" alt="green" /></td>
<td align="left"><img src="assets/8cfbd0f0-d65c-11ea-9203-1bcff1a80007.jpg" alt="green" /></td>
</tr>
</tbody>
</table>
<p>通过以上的对比分析,显然当前最佳的方式是,以环境为中心或以应用为中心部署多集群模式会获得最佳的收益。</p>
<h3>构建弹性 CI/CD 流程的策略</h3>
<p>构建 CI/CD 流程的工具很多, 但是我们无论使用何种工具,我们都会困 惑如何引入 Kubernetes 系统。通过实践得知,目前业界主要在采用 GitOps 工作流与 Kubernetes 配合使用可以获得很多的收益。这里我们可以参考业界知名的 CI/CD 工具 JenkinsX 架构图作为参考:</p>
<p><img src="assets/0307b200-d65d-11ea-8ff5-1f2baade933b.jpg" alt="5-1-jenkinsx_arch" /></p>
<p>GitOps 配合 Jenkins 的 Pipeline 流水线,可以创建业务场景中需要的流水线,可以让业务应用根据需要在各种环境中切换并持续迭代。这种策略的好处在于充分利用 Git 的版本工作流控制了代码的集成质量,并且依靠流水线的特性又让持续的迭代能力可以得到充分体现。</p>
<h3>构建弹性多租户资源管理策略</h3>
<p>Kubernetes 内部的账号系统有 User、Group、ServiceAccount当我们通过 RBAC 授权获得资源权限之后,其实这 3 个资源的权限能力是一样的。因为使用场景的不同,针对人的权限,我们一般会提供 User、Group 对象。当面对 Pod 之间,或者是外部系统服务对 Kubernetes API 的调用时,一般会采用 ServiceAccount。在原生 Kubernetes 环境下,我们可以通过 Namespace 把账号和资源进行绑定,以实现基于 API 级别的多租户。但是原生的多租户配置过于繁琐,一般我们会采用一些辅助的开源多租户工具来帮助我们,例如 Kiosk 多租户扩展套件:</p>
<p><img src="assets/24de6a40-d65d-11ea-a0a4-91ded31f57b1.jpg" alt="5-1-kiosk-workflow" /></p>
<p>通过 Kiosk 的设计流程图,我们可以清晰地定义每一个用户的权限,并配置合理的资源环境。让原来繁琐的配置过程简化成默认的租户模板,让多租户的配置过程变得更标准。</p>
<h3>构建弹性安全策略</h3>
<p>基于 Kubernetes 容器集群的安全考量,它的攻击面很多。所以我们要想做一份完备的安全策略,依然需要借助在系统层面的安全经验作为参考。根据业界知名的 MITRE ATT&amp;CK 全球安全知识库的安全框架设计,我们有如下方面需要考量:</p>
<p><img src="assets/479410d0-d65d-11ea-8e3d-27a3708e9ea4.jpg" alt="5-1-security-attach-interface" /></p>
<h4><strong>Initial Access准入攻击面</strong></h4>
<p>我们需要考虑的面主要是认证授权的审计工作。比如在云端的 Kubernetes当云端的认证凭证泄露就会导致容器集群暴露在外。比如 Kubeconfig 文件,它是集群管理员的管理授权文件,一旦被攻击者获得授权,整个集群就会暴露在攻击者的眼前。另外基础镜像的潜在 Bug 问题、应用程序的漏洞等问题,稍有不慎,也会对集群带来安全隐患。还有内置的开源面板 Kubernetes Dashboard 也不应该暴露在外网,需要保证其面板的端口安全。</p>
<h4><strong>Execution执行攻击面</strong></h4>
<p>本攻击面需要防范的地方是防止攻击者能直接在容器内部执行程序的能力。比如 Kubernetes 的 <code>kubectl exec</code> 命令就可以进入容器内部执行命令。另外,攻击者如果包含有运行容器的权限,就可以使用合法的 Service Account 账号访问 API Server然后尝试攻击。还有如果容器内置了 SSH 服务,也能通过网络钓鱼的方式让攻击者获取容器的远程访问权限。</p>
<h4><strong>Persistence后门攻击面</strong></h4>
<p>这个攻击面主要利用集群特性来部署后门来获得持续控制集群资源的目的。比如提供含有后门程序的容器就可以在每一台主机上部署一个实例隐藏后门程序。另外Kubernetes 集群默认支持 hostPath 挂载特性方便攻击者挂载可读写目录并在主机留下后门程序,方便下次通过 Cronjob 技术挂载此目录并执行后门程序。</p>
<h4><strong>Privilege escalation权限提权攻击面</strong></h4>
<p>这里主要是因为容器默认具备系统特权执行的能力,当容器启动 Privileged 参数是可以直接访问主机 Kernel 提供的系统能力的让攻击者可以执行系统后门攻击。另外Kubernetes 内置了 cluster-admin 超级管理员权限,当攻击者具有 cluster-binding 的权力,他就可以赋予普通用户 cluster-admin 的角色并直接取得集群管理员的角色权力。</p>
<h4><strong>Defense evasion防御性攻击面</strong></h4>
<p>这个技术主要是攻击者通过清空日志或者事件来隐藏自己的攻击行踪的技术。比如:攻击者通过删除容器系统日志来隐藏后门程序容器的破坏行为。另外攻击者可以通过 <code>kubectl delete</code> 方式重置容器实例,变相清空事件日志来达到隐藏攻击行为。</p>
<h4><strong>Credential access凭证访问攻击面</strong></h4>
<p>这个攻击技术主要是攻击者了解 Kubernetes 的特性,专门扫描获取密钥凭证的技术。比如通过扫描 secrets 获得潜在的攻击密钥。另外容器应用程序一般通过环境变量赋值密钥位置,攻击者也可以通过遍历环境变量获得敏感凭证数据。</p>
<h4><strong>Discovery扫描攻击技术面</strong></h4>
<p>当攻击者熟悉 Kubernetes 集群的特性之后,可以通过扫描 API Server 的接口、Kubelet API 接口、Pod 端口获得必要的攻击漏洞。另外攻击者可以在集群中运行容器,然后渗透进入 Dashboard 开源面板容器,用此面板容器的身份去 API Server 收集集群的信息。</p>
<h4><strong>Lateral movement侧面攻击面</strong></h4>
<p>攻击者通过第三方系统的漏洞获得攻击 Kubernetes 集群的能力。比如当攻击者拥有 Dashboard 的管理权限,就可以通过内部容器的 exec 能力在容器内部执行木马漏洞程序。因为集群内部的 Pod 网络是互联互通的,所以攻击者也可以任意访问任何感兴趣的 Pod 容器。</p>
<h4><strong>Impact破坏攻击面</strong></h4>
<p>攻击者通过破坏、滥用和扰乱正常执行行为来达到破坏环境的目的。例如删除 Deployment 配置、存储和计算资源等破坏容器运行。另外就是在容器内运行挖矿程序等非法滥用计算资源。还有 API Server 的拒绝服务攻击让集群不可用。</p>
<p>为此,我们的安全策略是给用户提供最小的授权来运行容器。很多用户通过建立专用的管理面板来阻隔用户对 Kubernetes 的接触,这是比较常见的做法。但是,目前云端很多 Kubernetes 服务仍然会让用户接触到主机层面的入口,让安全问题暴露在潜在攻击者的面前。一般通过 VPC 的方式限制只有内部人员可以访问集群,但是内部的安全审计仍然是一个长期需要维护的过程,需要专业的安全人员制定完善的防范策略来降低攻击风险。</p>
<h3>总结</h3>
<p>通过以上常见问题的分析和实践,我们发现 Kubernetes 的优点和缺点都非常鲜明,让我们爱恨交织。因为 Kubernetes 的系统复杂,让很多操作步骤都变得复杂起来。所以,在结合前人经验的同时,很多开发团队提供了开源的增强组件来加固 Kubernetes 集群的方方面面的能力。我建议大家可以多积累并使用这些增强组件来加固自己的集群,让我们一起站在巨人的肩膀上用好 Kubernetes 集群。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">下一页</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":"709972654cf13d60","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>

View File

@@ -0,0 +1,940 @@
<!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>08 K8s 集群安装工具 kubeadm 的落地实践.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>08 K8s 集群安装工具 kubeadm 的落地实践</h1>
<p>kubeadm 是 Kubernetes 项目官方维护的支持一键部署安装 Kubernetes 集群的命令行工具。使用过它的读者肯定对它仅仅两步操作就能轻松组建集群的方式印象深刻:<code>kubeadm init</code> 以及 <code>kubeadm join</code> 这两个命令可以快速创建 Kubernetes 集群。当然这种便捷的操作并不能在生产环境中直接使用,我们要考虑组件的高可用布局,并且还需要考虑可持续的维护性。这些更实际的业务需求迫切需要我们重新梳理一下 kubeadm 在业界的使用情况,通过借鉴参考前人的成功经验可以帮助我们正确的使用好 kubeadm。</p>
<p>首先,经典的 Kubernetes 高可用集群的架构图在社区官方文档中定义如下:</p>
<p><img src="assets/c5131cb0-d660-11ea-8a75-23aebf65f18c.jpg" alt="7-1-kubeadm-k8s-ha-arch" /></p>
<p>从上图架构中可知Kubernetes 集群的控制面使用 3 台节点把控制组件堆叠起来,形成冗余的高可用系统。其中 etcd 系统作为集群状态数据存储的中心,采用 Raft 一致性算法保证了业务数据读写的一致性。细心的读者肯定会发现,控制面节点中 apiserver 是和当前主机 etcd 组件进行交互的,这种堆叠方式相当于把流量进行了分流,在集群规模固定的情况下可以有效的保证组件的读写性能。</p>
<p>因为 etcd 键值集群存储着整个集群的状态数据,是非常关键的系统组件。官方还提供了外置型 etcd 集群的高可用部署架构:</p>
<p><img src="assets/4a5db0c0-d660-11ea-aefa-4fa1d18dcf14.jpg" alt="7-2-ha-kube-etcd-external-arch" /></p>
<p>kubeadm 同时支持以上两种技术架构的高可用部署,两种架构对比起来,最明显的区别在于外置型 etcd 集群模式需要的 etcd 数据面机器节点数量不需要和控制面机器节点数量一致,可以按照集群规模提供 3 个或者 5 个 etcd 节点来保证业务高可用能力。社区的开发兴趣小组 k8s-sig-cluster-lifecycle 还发布了 etcdadm 开源工具来自动化部署外置 etcd 集群。</p>
<h3>安装前的基准检查工作</h3>
<p>集群主机首要需要检查的就是硬件信息的唯一性,防止集群信息的冲突。确保每个节点上 MAC 地址和 product_uuid 的唯一性。检查办法如下:</p>
<ul>
<li>您可以使用命令 <code>ip link</code><code>ifconfig -a</code> 来获取网络接口的 MAC 地址</li>
<li>可以使用 <code>sudo cat /sys/class/dmi/id/product_uuid</code> 命令对 product_uuid 校验</li>
</ul>
<p>检查硬件信息的唯一性,主要是为了应对虚拟机模板创建后产生的虚拟机环境重复导致,通过检查就可以规避。</p>
<p>另外,我们还需要确保默认的网卡是可以联网的,毕竟 Kubernetes 组件通过默认路由进行组网。</p>
<p>还有一个问题是在主流 Linux 系统中 nftables 当前可以作为内核 iptables 子系统的替代品。导致 iptables 工具充当了一层兼容层nftables 后端目前还无法和 kubeadm 兼容nftables 会导致生成重复防火墙规则并破坏 kube-proxy 的工作。目前主流系统如 CentOS 可以通过如下配置解决:</p>
<pre><code class="language-bash">update-alternatives --set iptables /usr/sbin/iptables-legacy
</code></pre>
<h3>检查端口</h3>
<h4><strong>控制平面节点</strong></h4>
<table>
<thead>
<tr>
<th align="left">协议</th>
<th align="left">方向</th>
<th align="left">端口范围</th>
<th align="left">作用</th>
<th align="left">使用者</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">TCP</td>
<td align="left">入站</td>
<td align="left">6443*</td>
<td align="left">Kubernetes API 服务器</td>
<td align="left">所有组件</td>
</tr>
<tr>
<td align="left">TCP</td>
<td align="left">入站</td>
<td align="left">2379-2380</td>
<td align="left">etcd server client API</td>
<td align="left">kube-apiserver, etcd</td>
</tr>
<tr>
<td align="left">TCP</td>
<td align="left">入站</td>
<td align="left">10250</td>
<td align="left">Kubelet API</td>
<td align="left">kubelet 自身、控制平面组件</td>
</tr>
<tr>
<td align="left">TCP</td>
<td align="left">入站</td>
<td align="left">10251</td>
<td align="left">kube-scheduler</td>
<td align="left">kube-scheduler 自身</td>
</tr>
<tr>
<td align="left">TCP</td>
<td align="left">入站</td>
<td align="left">10252</td>
<td align="left">kube-controller-manager</td>
<td align="left">kube-controller-manager 自身</td>
</tr>
</tbody>
</table>
<p><code>\*</code> 标记的任意端口号都可以被覆盖,所以您需要保证所定制的端口是开放的。</p>
<h4><strong>工作节点</strong></h4>
<table>
<thead>
<tr>
<th align="left">协议</th>
<th align="left">方向</th>
<th align="left">端口范围</th>
<th align="left">作用</th>
<th align="left">使用者</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">TCP</td>
<td align="left">入站</td>
<td align="left">10250</td>
<td align="left">Kubelet API</td>
<td align="left">kubelet 自身、控制平面组件</td>
</tr>
<tr>
<td align="left">TCP</td>
<td align="left">入站</td>
<td align="left">30000-32767</td>
<td align="left">NodePort 服务**</td>
<td align="left">所有组件</td>
</tr>
</tbody>
</table>
<p><code>**</code> NodePort 服务的默认端口范围。</p>
<p>注意在企业部署集群的时候,大部分情况会初始化一个小规模集群来开局,所以外部单独配置 etcd 集群的情况属于特例。把 etcd 集群堆叠部署在控制面节点上是小规模集群的首选方案。</p>
<p>另外Pod 容器网络插件会启用一些自定义端口,需要参阅他们各自的文档对端口要求进行规划。</p>
<h3>安装容器运行时引擎</h3>
<p>kubenet 并不能直接启动容器,所以集群节点机器需要统一部署容器运行时引擎。从 v1.6.0 版本起Kubernetes 开始默认允许使用 CRI容器运行时接口。从 v1.14.0 版本起kubeadm 将通过观察已知的 UNIX 域套接字来自动检测 Linux 节点上的容器运行时。下表中是可检测到的正在运行的 runtime 和 socket 路径。</p>
<table>
<thead>
<tr>
<th align="left"><strong>运行时</strong></th>
<th align="left"><strong>域套接字</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Docker</td>
<td align="left">/var/run/docker.sock</td>
</tr>
<tr>
<td align="left">containerd</td>
<td align="left">/run/containerd/containerd.sock</td>
</tr>
<tr>
<td align="left">CRI-O</td>
<td align="left">/var/run/crio/crio.sock</td>
</tr>
</tbody>
</table>
<p>如果同时检测到 Docker 和 containerd则优先选择 Docker。目前业界已经在剥离 Docker逐渐往 containerd 引擎靠拢,所以注意集群环境容器运行时引擎的升级。</p>
<h3>安装 kubeadm、kubelet 和 kubectl</h3>
<p>采用 kubeadm 安装集群时,它并不能管理 kubectl 工具的版本,需要集群管理员注意版本号的一致性,避免版本兼容性问题。 如果不这样做,则存在发生版本偏差的风险,可能会导致一些预料之外的错误和问题。</p>
<p>官方提供的组件安装方式是通过操作系统安装包系统如 yum 来管理,但是在国内实际的网络环境中,我们仍然面临下载失败的情况,为了统一的安装体验,建议提前下载相应的系统安装包,如无法获取可以直接采用二进制文件来部署。</p>
<p>通过运行命令 <code>setenforce 0</code><code>sed ...</code> 将 SELinux 设置为 permissive 模式可以有效的将其禁用。 这是允许容器访问主机文件系统所必须的,例如正常使用 Pod 网络。 您必须这么做,直到 kubelet 做出升级支持 SELinux 为止。</p>
<p>一些 RHEL/CentOS 7 的用户曾经遇到过问题:由于 iptables 被绕过而导致流量无法正确路由的问题。您应该确保在 <code>sysctl</code> 配置中的 <code>net.bridge.bridge-nf-call-iptables</code> 被设置为 1。</p>
<pre><code class="language-bash">cat &lt;&lt;EOF &gt; /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
</code></pre>
<p>确保在此步骤之前已加载了 <code>br_netfilter</code> 模块。这可以通过运行 <code>lsmod | grep br_netfilter</code> 来完成。要检查是否加载它,请调用 <code>modprobe br_netfilter</code></p>
<p>另外kubelet 在控制平面节点上还需要注意对 cgroup 驱动程序支持情况,默认支持 cgroupfs另外还有 systemd 驱动可以选择。因为主流操作系统对 systemd 支持,建议采用 containerd 引擎的用户通过配置切换到 systemd 驱动。</p>
<h3>使用 kubeadm 安装高可用集群</h3>
<h4><strong>为 kube-apiserver 创建负载均衡</strong></h4>
<p>因为工作节点和控制面节点之间是通过 kube-apiserver 来同步集群状态的,工作节点需要通过一个反向代理来把流量负载均衡到控制面集群中。一般的安装案例中,采用额外的 HAProxy 加 keeplived 来做请求流量的负载均衡。因为最新的 Linux 内核已经支持 IPVS 组件,可以实现内核态的流量代理,业界实践已经有通过动态维护 IPVS 规则来实现负载访问 apiserver。具体配置如图</p>
<p><img src="assets/aa990160-d660-11ea-8a86-ed86f9ad27de.jpg" alt="7-3-kubeadm-lvs-lb" /></p>
<h3>实践总结</h3>
<p>Kubernetes 推出了很多安装解决方案因为环境的差异化让各种安装工具百花齐放让用户选择起来很是困惑。kubeadm 算是一个在多种选型中比较突出的一个方案。因为采用了容器化部署方式,其运维难度要比二进制方式要大很多,在安装过程中还是会碰到版本不一致等问题,目前社区也在优化巩固这方面的功能稳定性,可以预见在不久之后,基于 kubeadm 的方式应该会成为主流的安装解决方案。</p>
<p>参考文章:</p>
<ul>
<li><a href="https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/">Options for Highly Available topology</a></li>
<li><a href="https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/">Creating Highly Available clusters with kubeadm</a></li>
<li><a href="https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/install-kubeadm/">安装 kubeadm</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">下一页</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":"7099726b4a303d60","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>

View File

@@ -0,0 +1,944 @@
<!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</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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="ipvs-iptables-compare" /></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="8-2-cilium-diagram" /></p>
<p>通过 eBPF 技术,目前已经在高版本的 Kernel 之上实现了流量转发和容器网络互连,期待有一天可以完美替换 iptables 和 IPVS为我们提供功能更强、性能更好的流量管理组件。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">下一页</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>

View File

@@ -0,0 +1,816 @@
<!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>11 服务发现 DNS 的落地实践.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>11 服务发现 DNS 的落地实践</h1>
<p>DNS 服务是 Kubernetes 内置的服务发现组件,它方便容器服务可以通过发布的唯一 App 名字找到对方的端口服务,再也不需要维护服务对应的 IP 关系。这个对传统企业内部的运维习惯也是有一些变革的。一般传统企业内部都会维护一套 CMDB 系统,专门来维护服务器和 IP 地址的对应关系,方便规划管理好应用服务集群。当落地 K8s 集群之后,因为应用容器的 IP 生命周期短暂,通过 App 名字来识别服务其实对运维和开发都会更方便。所以本篇就是结合实际的需求场景给大家详细介绍 DNS 的使用实践。</p>
<h3>CoreDNS 介绍</h3>
<p>Kubernetes 早期的 DNS 组件叫 KubeDNS。CNCF 社区后来引入了更加成熟的开源项目 CoreDNS 替换了 KubeDNS。所以我们现在提到 KubeDNS其实默认指代的是 CoreDNS 项目。在 Kubernetes 中部署 CoreDNS 作为集群内的 DNS 服务有很多种方式,例如可以使用官方 Helm Chart 库中的 Helm Chart 部署,具体可查看 <a href="https://github.com/helm/charts/tree/master/stable/coredns">CoreDNS Helm Chart</a></p>
<pre><code>$ helm install --name coredns --namespace=kube-system stable/coredns
</code></pre>
<p>查看 coredns 的 Pod确认所有 Pod 都处于 Running 状态:</p>
<pre><code>kubectl get pods -n kube-system -l k8s-app=kube-dns
NAME READY STATUS RESTARTS AGE
coredns-699477c54d-9fsl2 1/1 Running 0 5m
coredns-699477c54d-d6tb2 1/1 Running 0 5m
coredns-699477c54d-qh54v 1/1 Running 0 5m
coredns-699477c54d-vvqj9 1/1 Running 0 5m
coredns-699477c54d-xcv8h 1/1 Running 0 6m
</code></pre>
<p>测试一下 DNS 功能是否好用:</p>
<pre><code>kubectl run curl --image=radial/busyboxplus:curl -i --tty
nslookup kubernetes.default
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: kubernetes
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local
</code></pre>
<h3>服务发现规则</h3>
<p>DNS 支持的服务发现是支持 Service 和 Pod 的。它的规则如下。</p>
<h4><strong>Services</strong></h4>
<p>A 记录:</p>
<ul>
<li>Serviceheadless Service 除外)将被分配一个 DNS A 记录,格式为 my-svc.my-namespace.svc.cluster.local。该 DNS 记录解析到 Service 的 ClusterIP。</li>
<li>Headless Service没有 ClusterIP也将被分配一个 DNS A 记录,格式为 my-svc.my-namespace.svc.cluster.local。该 DNS 记录解析到 Service 所选中的一组 Pod 的 IP 地址的集合。调用者应该使用该 IP 地址集合或者按照轮询round-robin的方式从集合中选择一个 IP 地址使用。</li>
</ul>
<p>SRV 记录Service含 headless Service的命名端口有 name 的端口)将被分配一个 SRV 记录,其格式为 _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local。</p>
<ul>
<li>对于一个普通 Service非 headless Service该 SRV 记录解析到其端口号和域名 my-svc.my-namespace.svc.cluster.local。</li>
<li>对于一个 Headless Service该 SRV 记录解析到多个结果:每一个结果都对应该 Service 的一个后端 Pod包含其端口号和 Pod 的域名 auto-generated-pod-name.my-svc.my-namespace.svc.cluster.local。</li>
</ul>
<h4><strong>Pods</strong></h4>
<p>Kubernetes 在创建 Pod 时,将 Pod 定义中的 metadata.name 的值作为 Pod 实例的 hostname。</p>
<ul>
<li>Pod 定义中有一个可选字段 spec.hostname 可用来直接指定 Pod 的 hostname。例如某 Pod 的 spec.hostname 字段被设置为 my-host则该 Pod 创建后 hostname 将被设为 my-host。</li>
<li>Pod 定义中还有一个可选字段 spec.subdomain 可用来指定 Pod 的 subdomain。例如名称空间 my-namespace 中,某 Pod 的 hostname 为 foo并且 subdomain 为 bar则该 Pod 的完整域名FQDN为 foo.bar.my-namespace.svc.cluster.local。</li>
</ul>
<p>备注A 记录不是根据 Pod name 创建的,而是根据 hostname 创建的。如果一个 Pod 没有 hostname 只有 subdomain则 Kubernetes 将只为其 headless Service 创建一个 A 记录 default-subdomain.my-namespace.svc.cluster-domain.example该记录指向 Pod 的 IP 地址。</p>
<h3>DNS 优化</h3>
<p>社区根据压测数据,对 CoreDNS 需要的内存提供了一个计算公式:</p>
<pre><code>MB required (default settings) = (Pods + Services) / 1000 + 54
</code></pre>
<p>注解:</p>
<ul>
<li>30 MB 留给缓存,默认缓存大小为 1 万条记录。</li>
<li>5 MB 留给应用查询操作使用,默认压测单例 CoreDNS 支持大约 30K QPS。</li>
</ul>
<p><img src="assets/aaddf7c0-e123-11ea-9254-2dbb61d9b3dd.jpg" alt="kubedns-perf" /></p>
<h3>集成外部 DNS 服务</h3>
<p>我们在使用 Kubernetes 的场景中,企业经常已经默认有了自己的 DNS 服务,在部署容器集群的时候,肯定期望和外置的 DNS 服务做一些集成,方便企业内部的使用。</p>
<p>默认 DNS 查询策略是 ClusterFirst也就是查询应用名字首先是让集群内部的 CoreDNS 提供名字服务。而我们需要解决的是让指定的别名访问外部的服务,这个时候就需要做如下配置:</p>
<pre><code>apiVersion: v1
kind: ConfigMap
metadata:
name: kube-dns
namespace: kube-system
data:
stubDomains: |
{&quot;consul.local&quot;: [&quot;10.150.0.1&quot;]}
upstreamNameservers: |
[&quot;8.8.8.8&quot;, &quot;8.8.4.4&quot;]
</code></pre>
<p>上面这个例子很好的解释了外置 Consul 服务,也可以很好地集成到 Kubernetes 服务中。如果是正式的域名,直接转向查询 8.8.8.8 上游 DNS 服务器了。</p>
<h3>总结</h3>
<p>CoreDNS 是 Kubernetes 集群中最核心,也是最容易理解的一个组件,它的功能单一,很容易上手。但是名字解析的规则还是需要大家熟悉,避免一些不必要的认知错误。</p>
<p>参考:</p>
<ul>
<li><a href="https://github.com/coredns/deployment/blob/master/kubernetes/Scaling_CoreDNS.md">https://github.com/coredns/deployment/blob/master/kubernetes/Scaling_CoreDNS.md.html</a></li>
<li><a href="https://kuboard.cn/learning/k8s-intermediate/service/dns.html">https://kuboard.cn/learning/k8s-intermediate/service/dns.html</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">下一页</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":"709972711ec53d60","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>

View File

@@ -0,0 +1,694 @@
<!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>12 练习篇K8s 集群配置测验.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>12 练习篇K8s 集群配置测验</h1>
<p>第二部分的内容围绕 Kubernetes 核心组件的安装配置一一给大家拆解了一遍当前集群组件最主流的配置就是这些containerd、kubeadm、IPVS、Calico、kube-dns。读者通过官方文档就可以独立配置一套集群只是笔者发现因为集群配置的过度复杂能获得的环境也是千差万别很难得到统一的认知。本篇测验的目的就是带着大家一起校验一遍我们学习到的经验一起搭建一套集群的全过程以此来校验我们掌握的集群知识。</p>
<h3>环境</h3>
<p>从第一天接触容器技术之后,我们想解决的问题就是环境依赖问题。因为 Docker 是让环境包裹这应用一起制作成镜像分发的。等我们配置 Kubernetes 集群的时候,我们操作的最小单元是 Pod你可以理解为是一个容器组这个容器组并不是简单的把一组容器放一起就完事了。它的设计巧妙之处在于以 pause 为核心的基础容器把相关应用的所有环境依赖都掌握在自己的运行时里面。其它相关业务容器只是加入到这个运行时里面,这些业务容器出现问题并不会破坏环境。这是 Kubernetes 构建业务集群的核心设计,非常巧妙的解决了应用服务的可用性问题。</p>
<p>现在我们要选择操作系统的版本了。你会发现并没有任何官方文档说过,哪一个版本是指定的。其实官方并没有这样的约定。因为容器的目的就是解决环境的依赖,但是这么多年的演进,说的更清楚一点,我们仍然有一个核心依赖就是 Kernel 依赖搞不定。Kernel 的特性会决定容器的特性,我们一般在选择上会参考 Docker 的版本来定,主流的有 18.09、19.03 等。你发现没有,你并不能保证在特定的环境下这些 Docker 版本没有问题,这就是我们在配置生产环境中出现问题自己埋下的坑。</p>
<p>如果你是企业内部使用,最好的办法是建立基准线,明确版本号,在大量实践的基础上投入人力来维护这个版本的稳定性。因为容器技术发展很快,现在 Kubernetes 已经和 Docker 越来越规避,都在使用 containerd 来支持底层容器运行时的管理,作为用户我们是无法回避这个。这里又突显一个问题,因为组件的变革,我到底应该选择哪个版本呢,它们稳定吗?因为 Kubernetes 是开源社区推动的软件,我们一定要遵循开源的方式来使用这些软件才能得到正确的经验。</p>
<p>我总结出来的经验如下,方便大家参考:</p>
<p><strong>1. x86-64 仍然是当前对容器最好的系统架构体系</strong>,目前主流的系统聚集在 redhat/centos 7.x 系列Ubuntu 16.04 系列。对于内核红帽系主要在 3.10 以上Ubuntu 能到 4.4 以上。有些用户会通过开源 kernel 仓库把红帽系的 Kernel 升级到 4.4也比较常见。升级内核的代价就是引入很多未知的模块让系统变得不稳定。ARM 系统架构会对整个 Kubernetes 组件的文件格式产生兼容性要求,在选择适配的时候,一定要注意有没有准备好 Kubernetes 相应的组件。总结下来,主流的操作系统主要是红帽的 7.x 系列和 Ubuntu LTS 系列 16.04。升级大版本操作系统对 Kubernetes 来说,需要做很多适配工作,目前开源社区是不太可能帮用户做的。一定注意。</p>
<p><strong>2. Kubernetes 的版本更新很快</strong>,整个社区会维护 3 个主线版本,如现在主要为 1.16.x、1.17.x、1.18.x。这个 x 版本号差不多 2 周就一个迭代,主要是修复 Bug。很多团队在使用上总结了一些技巧比如取奇数版本或者偶数版本作为自己的主力版本这个做法的目的就是规避最新版本带来的不稳定性。并不是说奇数版本好或者是偶数版本稳定这是纯属瞎猜。作为开源软件它的质量是社区在维护落实到用户这里就是大家都是小白鼠需要在自己的环境试验验证组件的可靠性。总结下来主流的环境还是选择比最新版本低 1 个或者 2 个子版本作为周期来当做自己的软件来维护。维护开源软件不是免费的,它是通过大家的努力才能保证组件的使用可靠性的。</p>
<p>\3. 除了 Kubernetes 主线版本的选择我们应该延迟 1 到 2 个版本之外,<strong>对于其它附属组件如 Calico、kube-dns、Containerd 等,应该需要选择最新版本</strong>。主要原因在于它们是一线运行的组件,被调用的次数是更多的,发现问题的机会更突出。越早发现问题越快得到修复。这又是开源里面的原则,就是越早发现、越早修复,组件越稳定。很多用户在组件选择上,会比较保守,导致很多修复过的 Bug 还存在于你的集群中,让不确定性得到蔓延。总结下来,跑容器的一线组件应该使用最新版本,越早发现,你的程序越稳固。言下之意,当开源小白鼠,咱们也要有对策,通过自动化测试的环境,把这些组件多测测。</p>
<p>\4. 很多以为 Kubernetes 安装上之后就完事大吉,环境的事情就不用操心了。诚然,通过容器确实可以解决一部分运维的问题。但是应用架构的可靠性并不能依靠 Kubernetes 。为什么在有了 容器之后,在 DevOps 领域开始引入了 SRE 的概念,就是说业务保障一直是业务核心能力,不能依赖 Kubernetes。<strong>用了 Kubernetes 之后,你更要关注架构的稳定性。</strong></p>
<h4><strong>kubeadm 的配置测验</strong></h4>
<p>kubeadm 推出的初衷是为了用更平滑的方式来安装、升级 Kubernetes。在早期我是排斥的因为二进制的安装方式好像更简洁排错也更方便。但是随着安装经验的丰富我发现二进制的安装还是无法标准化配置起来手工操作的地方很多无法满足一键安装的目的。</p>
<p>kubeadm 是唯一被官方认可的安装项目,可以说明社区对它云原生的安装配置方式的认可。这里引出的问题就是 kubeadm 是不是安装的结果都是一样呢?</p>
<p>不是的。它考虑的单机模式,高可用模式,组件混合镜像模式,组件分组镜像模式,这些让用户在安装的时候会遇到很多选择,也会产生出一些不可知的问题。因为 kubeadm 的版本的不同,它的安装过程和细节都会微调,我们应该尽量使用最新版本的 kubeadm 来安装,这样就可以得到很多一键部署的好处。很多原来手工需要做的事情,如自签名证书的签发都是 kubeadm 自动帮你做了,另外如安装之前的环境调优的参数也会自动帮你生成。这些操作都是之前需要手工考虑并自己手工执行的。</p>
<p>目前笔者的经验就是:以官方文档为基准,选择最新版本的 kubeadm 为最佳组件,然后做 Kubernetes 的安装规划工作,目前还没有碰到什么难题。</p>
<h4>Calico、IPVS、kube-dns 组件的配置测验</h4>
<p>很多用户谈到容器网络就色变,因为之前容器网络的方案太多,让用户根本选择不清楚。每家都说自己的网络方案好,但是其实每家的解决方案都不是最完美的方案。</p>
<p>目前,容器网络方案并没有官方推荐的方案。从 Kubernetes 官方文档中介绍到,网络这个范畴是不包括在集群组件中的。这是社区的选择,无可厚非。但是我们安装集群,如果没有配置网络,这个 Kubernetes 是无法承载业务容器的。为了这个实际问题,我们要选择一个合适的方案。</p>
<p>为什么说 Calico 是当前最理想的方案,主要原因是它的配置简单,在 100 台物理机规模下通过 IPIP 模式创建的容器网络,性能已经接近主机网卡模式,损耗很小。这个是以前版本的 Calico 无法解决的,现在最新版本经过测试发现性能提升不少。</p>
<p>对于 kube-dns它对容器网络还是有依赖只有你有了容器网络之后kube-dns 才能正常工作。目前主流的是 CoreDns在 100 台物理机下同样性能很好,目前运行上并没有遇到什么大问题。</p>
<p>IPVS 是 Kernel 的内核模块,主要代替 kube-proxy 的南北向流量。但是因为它的功能还是局限在 proxy 之上,对于 kube-proxy 的东西向流量的支持还是不行的,需要靠 iptables 来转换。华为通过压测发现之前用 iptables 来解决南北向流量的性能瓶颈,这才提出的 IPVS 的方案。随着现在 eBPF 可编程数据包的出现,让 IPVS 的方案开始进入到一个过度阶段。因为 eBPF 技术不仅仅支持南北向,也能支持东西向,可以完美替换到 iptables 的工作能力。毕竟 iptables 是为防火墙设计的,复杂并动态变更的规则会对系统带来影响,从而导致对业务的影响,这些都是用户不愿意看到的。</p>
<p>总结下来,这些组件的配置目前来看都有默认配置,基本上配置一次就不用更改,大家只要能验证能否正常工作就可以,没有必要花费太大精力在这些组件的配置上。</p>
<h3>总结</h3>
<p>所谓 Kubernetes 的配置测验,依靠的是大家动手去安装,单靠例子的示范很难让你获得一手的经验。但是测验的目的是让你能明白在安装过程中遇到的问题和解决方案。我们要感受到开源软件的不同之处,它利用社区的力量来维护版本的稳定性。大家安装的时候肯定会遇到这样那样的问题,除了自己验证排错之外,你可以理直气壮的在社区的问题列表栏上写下自己的问题,方便大家互通有无。很多国内的用户并没有理解这种交互带来的价值,因为开源运动是一种社交活动,它依赖用户的互动,如果大家都不去反馈,这个软件只会变的越来越差。</p>
<p>大家要理解 kubeadm 的意义,就是未来可能更多的配置要被默认值替代,大家只需要一键执行就可以获得一套性能可观的 Kubernetes 环境。这个需要时间,我们可以期待。</p>
<p>参考:</p>
<ul>
<li><a href="https://kubernetes.io/blog/2017/01/stronger-foundation-for-creating-and-managing-kubernetes-clusters/">https://kubernetes.io/blog/2017/01/stronger-foundation-for-creating-and-managing-kubernetes-clusters/</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">下一页</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":"70997272eb673d60","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>

View File

@@ -0,0 +1,916 @@
<!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>14 应用网关 OpenResty 对接 K8s 实践.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>14 应用网关 OpenResty 对接 K8s 实践</h1>
<p>当前云原生应用网关有很多选择例如Nginx/OpenResty、Traefik、Envoy 等,从部署流行度来看 OpenResty 毋容置疑是最流行的反向代理网关。本篇探讨的就是 Kubernetes 为了统一对外的入口网关而引入的 Ingress 对象是如何利用 OpenResty 来优化入口网关的能力的。</p>
<h3>为什么需要 OpenResty</h3>
<p>原生 Kubernetes Service 提供对外暴露服务的能力,通过唯一的 ClusterIP 接入 Pod 业务负载容器组对外提供服务名(附注:服务发现使用,采用内部 kube-dns 解析服务名称)并提供流量的软负载均衡。缺点是 Service 的 ClusterIP 地址只能在集群内部被访问,如果需要对集群外部用户提供此 Service 的访问能力Kubernetes 需要通过另外两种方式来实现此类需求,一种是 <strong>NodePort</strong>,另一种是 <strong>LoadBalancer</strong></p>
<p><img src="assets/ea127350-f337-11ea-a9e7-47fb41a2df40.jpg" alt="nodeport" /></p>
<p>当容器应用采用 NodePort 方式来暴露 Service 并让外部用户访问时会有如下困扰:</p>
<ul>
<li>外部访问服务时需要带 NodePort</li>
<li>每次部署服务后NodePort 端口会改变</li>
</ul>
<p><img src="assets/fb9c8070-f337-11ea-a837-2d1765fb9067.jpg" alt="loadbalancer" /></p>
<p>当容器应用采用 LoadBalancer 方式时,主要应用场景还是对接云厂商提供负载均衡上,当然云厂商都提供对应的负载均衡插件方便 Kubernetes 一键集成。</p>
<p>对于大部分场景下,我们仍然需要采用私有的入口应用网关来对外提供服务暴露。这个时候通过暴露七层 Web 端口把外部流量挡在外面访问。同时对于用户来讲屏蔽了 NodePort 的存在,频繁部署应用的时候用户是不需要关心 NodePort 端口占用的。</p>
<p>在早期 Kubernetes 引入的 ingress controller 的方案是采用的 Nginx 作为引擎的,它在使用中有一些比较突出的问题:</p>
<h4><strong>reload 问题</strong></h4>
<p>Kubernetes 原生 Ingress 在设计上,将 YAML 配置文件交由 Ingress Controller 处理,转换为 nginx.conf再触发 reload nginx.conf 使配置生效。日常运维免不了偶尔动一动 Ingress YAML 配置,每一次配置生效,都会触发一次 reload这是不能接受的尤其在入口流量采用⻓连接时更容易导致事故。</p>
<h4><strong>扩展能力薄弱</strong></h4>
<p>虽然 Ingress 设计之初是为了解决入口网关,但业务对于入口网关的需求一点都不比内部网关少。业务级灰度控制、熔断、流量控制、鉴权、流量管控等需求在 Ingress 上实现的呼声更高。然而原生 Ingress 提供的扩展是捉襟见肘。</p>
<p><img src="assets/1f3b0060-f338-11ea-a9de-eb9ce9ef4f62.jpg" alt="ingress" /></p>
<p>为了解决以上 Nginx 固有的问题,显然基于 Nginx + Lua 的扩展方案 OpenResty 是不二的替换方案。社区方面已经完成的从 Nginx 到 OpenResty 的 Ingress 核心组件替换。(附注:<a href="https://github.com/kubernetes/ingress-nginx/pull/4220">https://github.com/kubernetes/ingress-nginx/pull/4220</a></p>
<h3>重新认识 NGINX Ingress Controller</h3>
<p><img src="assets/2bdec7c0-f338-11ea-949f-999a932fc96d.jpg" alt="nginx-ingress-arch" /></p>
<p>通常情况下Kubernetes 控制器利用同步循环模式来检查控制器中的所需状态是否被更新或需要更改。为此,我们需要使用集群中的不同对象建立一个模型,特别是 Ingresses、Services、Endpoints、Secrets 和 Configmaps 来生成一个反映集群状态的当前配置文件。</p>
<p>为了从集群中获取这个对象,我们使用 Kubernetes Informers尤其是 <strong>FilteredSharedInformer</strong>。这个 Informer 允许在添加、修改或删除新对象时,使用回调对单个变化做出反应。不幸的是,我们无法知道某个特定的变化是否会影响最终的配置文件。因此在每一次变更时,我们都要根据集群的状态从头开始重建一个新的模型,并与当前模型进行比较。如果新模型与当前模型相等,那么我们就避免生成一个新的 Nginx 配置并触发重载。否则,我们检查是否仅是关于 Endpoints 的差异。如果是这样,我们就使用 HTTP POST 请求将新的 Endpoints 列表发送到 Nginx 内部运行的 Lua 处理程序,并再次避免生成新的 Nginx 配置和触发重载。如果运行的模型和新模型之间的区别不仅仅是 Endpoints我们会根据新模型创建一个新的 Nginx 配置,替换当前模型并触发重载。</p>
<p>为了避免进程重载,我们仍然需要清楚如下情况会导致重载:</p>
<ul>
<li>创建新的入口资源</li>
<li>在现有的 Ingress 中增加了 TLS 部分</li>
<li>Ingress 注解的变化,影响的不仅仅是上游配置。例如,负载平衡注解不需要重新加载。</li>
<li>从 Ingress 中添加/删除一个路径。</li>
<li>对象 Ingress、Service、Secret 被删除的时候</li>
<li>从 Ingress 中缺失的一些引用对象是可用的,比如 Service 或 Secret</li>
<li>密钥配置更新</li>
</ul>
<p>另外,因为有 Lua 加持,我们还需要了解如何添加 lua 插件到 Nginx Ingress Controller 中。例如用例子来作为范例理解下如何添加并激活插件:</p>
<p>参考 <a href="https://github.com/ElvinEfendi/ingress-nginx-openidc">https://github.com/ElvinEfendi/ingress-nginx-openidc</a>,加入 Openidc Lua 插件。</p>
<ul>
<li>添加 Lua 插件到 rootfs/etc/nginx/lua/plugins/openidc/main.lua</li>
<li>构建自己的 Ingress 镜像:<code>docker build -t ingress-nginx-openidc rootfs/</code></li>
</ul>
<p>Dockerfile 范例:</p>
<pre><code class="language-dockerfile">FROM quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1
USER root
RUN luarocks install lua-resty-openidc
USER www-data
COPY etc/nginx/template/nginx.tmpl /etc/nginx/template
COPY etc/nginx/lua/plugins/openidc /etc/nginx/lua/plugins/openidc
</code></pre>
<ul>
<li>更新 Nginx 配置激活 Lua 插件 /etc/nginx/template/nginx.tmpl加入 <code>plugins.init({ &quot;openidc&quot; })</code></li>
<li>把自定义 Ingress 镜像部署到集群中,就可以提供相应的插件能力。</li>
</ul>
<h3>零宕机生产部署</h3>
<p>官方 Nginx Ingress Controller 通过容器部署在机器上,当需要升级配置时 Ingress Pod 仍然需要重启更新,此时网络流量会出现秒断。在生产环境中,这种情况是不允许出现的。我们需要 Nginx Ingress Controller 需要一直保持运行并接入流量。</p>
<p>当终止 Pod 进程时Kubernetes 会发出 SIGTERM 信号给容器主进程,默认等待 30 秒之后会发送 SIGKILL 立即终止容器进程。Kubernetes 启动容器进程能自主支持 SIGTERM 信号并能优雅关闭,但是并不是每一个容器进程都能处理,比如 Nginx 就无法直接支持。</p>
<p>Nginx 支持的信号会有所不同:</p>
<pre><code> Nginx Signals
+-----------+--------------------+
| signal | response |
+-----------+--------------------+
| TERM, INT | fast shutdown |
| QUIT | graceful shutdown |
+-----------+--------------------+
</code></pre>
<p>所以如果我们不做任何前置的信号处理,当 Kubernetes 发出 GIGTERM 后Nginx 将立即终止进程,如果此时恰好 Nginx 正在处理流量,用户会出现短暂 HTTP 503 错误。为了优雅关闭 Nginx 进程,所以我们需要想办法提前给 Nginx 进程一个 SIGQUIT 信号。解决办法就是利用 Pod 对象的 preStop Hook 来提前执行 SIGQUIT 发送操作。以下脚本就可以发送 SIGQUIT</p>
<pre><code>/usr/local/openresty/nginx/sbin/nginx -c /etc/nginx/nginx.conf -s quit
while pgrep -x nginx; do
sleep 1
done
</code></pre>
<p>我们可以将上面的脚本放到一个单行命令中,并将其添加到 Pod 规范的生命周期部分,如下所示:</p>
<pre><code>lifecycle:
preStop:
exec:
command: [&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;sleep 5; /usr/local/openresty/nginx/sbin/nginx -c /etc/nginx/nginx.conf -s quit; while pgrep -x nginx; do sleep 1; done&quot;]
</code></pre>
<p>请注意,在实际脚本之前有一个 sleep 5 命令。这将等待任何与 Kubernetes 相关的处理条件通过后再启动优雅的关闭。在测试过程中如果不执行这个睡眠Nginx 还是会中断连接。</p>
<p>另外,因为默认优雅关闭进程的等待时长是 5 秒,如果需要更长的时间可以加入如下配置:</p>
<pre><code>spec:
terminationGracePeriodSeconds: 600
</code></pre>
<p>到此优雅关闭的问题就完美解决了。但是如此配置我们还是无法保证业务流量可以无中断升级。为了最终的业务部署连续性,一般我们会冗余部署 2 套 Ingress 来彻底解决重启业务带来的影响,操作步骤如下:</p>
<pre><code>helm upgrade --install nginx-ingress stable/nginx-ingress --namespace ingress -f nginx/values.yaml
helm upgrade --install nginx-ingress-temp stable/nginx-ingress --namespace ingress-temp -f nginx/values.yaml
</code></pre>
<p>通过更改 DNS 把流量引入到 ingress-temp然后观察流量引入的情况</p>
<pre><code>kubectl logs -lcomponent=controller -ningress -f
kubectl logs -lcomponent=controller -ningress-temp -f
</code></pre>
<p>更新老 Ingress Controller在 Nginx values.yaml 中添加以下配置:</p>
<pre><code>controller:
lifecycle:
preStop:
exec:
command: [&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;sleep 5; /usr/local/openresty/nginx/sbin/nginx -c /etc/nginx/nginx.conf -s quit; while pgrep -x nginx; do sleep 1; done&quot;]
terminationGracePeriodSeconds: 600
</code></pre>
<p>发布新的 Nginx Ingress Controller</p>
<pre><code>helm upgrade --install nginx-ingress stable/nginx-ingress --namespace ingress --version 1.6.16 -f nginx/values.yaml
</code></pre>
<p>更新 DNS 恢复流量到老 Ingress 中。清理临时 Ingress Controller</p>
<pre><code>helm delete --purge nginx-ingress-temp --namespace ingress-temp
kubectl delete namespace ingress-temp
</code></pre>
<h3>为 Kubernetes ingress-nginx Controller 创建自定义 Annotation</h3>
<p>云原生的 Ingress Controller 和 Nginx 的配置方式不一样的地方就是大量使用 Annotation 标签来定义一些重用的配置选项。我们需要多了解它的实现原理并能灵活运用,这样对我们的业务运营会很帮助。</p>
<p>添加一个自定义 Annotation 的过程如下:</p>
<ul>
<li>git clone 官方 Ingress repo在 internal/ingress/annotations 目录下创建自定义 annotation 目录。并添加 main.go写入 Annotation 业务逻辑。</li>
<li>在 internal/ingress/annotations/annotations.go 文件中加入此新增注解变量。</li>
<li>在 types.go 声明注解的结构体对象,之后在 controller.go 中,你必须确保服务对象被注解中的值所填充。这个文件包含了处理一个 ingress 对象的逻辑,并将其转换为一个可以加载到 nginx 配置中的对象。</li>
<li>nginx.tmpl 中加入注解结构体的展开变量,以方便生成最终的 nginx 配置模板。</li>
</ul>
<p>完整的例子可以参考这里:</p>
<blockquote>
<p><a href="https://github.com/diazjf/ingress/commit/2e6ac94dd79e80e1b2d788115647f44a526afcfd">https://github.com/diazjf/ingress/commit/2e6ac94dd79e80e1b2d788115647f44a526afcfd</a></p>
</blockquote>
<h3>经验总结</h3>
<p>Ingress 对象是 Kubernetes 引入流量的标准对象,在企业内部需要注意,尽量采用 Ingress 对象隔离流量分组。因为 Ingress 天然和 APIServer 集成监测,可以动态对外暴露服务能力。我们已经没有必要在做二次开发,定制开发自己的网关接入方案。直接采用 Ingress 网关都可以满足需求不能满足需求的地方OpenResty 提供的 Lua 插件机制可以很好地补充。除了官方提供的 Nginx ingress controller其实国内也有开源厂商提供了内置更多插件的 OpenResty 网关Apache APISIX Ingress<a href="https://github.com/api7/ingress-controller">https://github.com/api7/ingress-controller</a>),通过上面的介绍,你可以快速应用起来。</p>
<p>参考资料:</p>
<ul>
<li><a href="https://medium.com/better-programming/creating-a-custom-annotation-for-the-kubernetes-ingress-nginx-controller-444e9d486192">https://medium.com/better-programming/creating-a-custom-annotation-for-the-kubernetes-ingress-nginx-controller-444e9d486192</a></li>
<li><a href="https://medium.com/codecademy-engineering/kubernetes-nginx-and-zero-downtime-in-production-2c910c6a5ed8">https://medium.com/codecademy-engineering/kubernetes-nginx-and-zero-downtime-in-production-2c910c6a5ed8</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">下一页</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":"70997276fcd33d60","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>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,852 @@
<!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>16 Cilium 容器网络的落地实践.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>16 Cilium 容器网络的落地实践</h1>
<p>随着越来越多的企业采用 Kubernetes围绕多云、安全、可见性和可扩展性等新要求可编程数据平面的需求用例范围越来越广。此外服务网格和无服务器等新技术对 Kubernetes 底层提出了更多的定制化要求。这些新需求都有一些共同点:它们需要一个更可编程的数据平面,能够在不牺牲性能的情况下执行 Kubernetes 感知的网络数据操作。</p>
<p>Cilium 项目通过引入扩展的伯克利数据包过滤器eBPF技术在 Linux 内核内向网络栈暴露了可编程的钩子。使得网格数据包不需要在用户和内核空间之间来回切换就可以通过上下文快速进行数据交换操作。这是一种新型的网络范式,它也是 Cilium 容器网络项目的核心思想。</p>
<p><img src="assets/e071dc20-0c99-11eb-8b8d-fb5aa176e4a4.jpg" alt="14-1-cilium-cni" /></p>
<h3>为什么需要落地 Cilium 容器网络?</h3>
<p>Kubernetes 的容器网络方案发展至今,一直是百家争鸣,各有特色。之前因为 CNI 网络方案不成熟,大家用起来都是战战兢兢,时刻提防容器网络给业务带来不可接受的效果,随即就把容器网络替换成主机网络。随着时间的磨砺,当前主流的容器网络方案如 Calico 等已经经历成百上千次生产环境的应用考验,大部分场景下都可以达到用户可以接受的网络性能指标。因为成功经验开始增多,用户也开始大规模启用容器网络的上线了。随着业务流量的引入越来越大,用户对 Kubernetes 网络的认知也趋于一致。大致分为两大类,一类是 Cluster IP是一层反向代理的虚拟网络一类是 Pod IP是容器间交互数据的网络数据平面。对于反向代理虚拟网络的技术实现早期 kube-proxy 是采用 iptables后来引入 IPVS 也解决了大规模容器集群的网络编排的性能问题。这样的实现结构你从顶端俯瞰会明显感知到 Kubernetes 网络数据平台非常零散,并没有实现一套体系的网络策略编排和隔离。显然,这样的技术结构也无法引入数据可视化能力。这也是 Istio 服务网格引入后,通过增加 envoy sidecar 来实现网络流量可视化带来了机会。但是这种附加的边界网关毕竟又对流量增加了一层反向代理让网络性能更慢了。Cilium 原生通过 eBPF 编排网络数据,让可视化更简单。</p>
<p>Cilium 还有一个强项就是通过 eBPF 是可以自定义隔离策略的,这样就可以在非信任的主机环境编排更多的容器网络隔离成多租户环境,让用户不在担心数据的泄露,可以更专注在数据业务的连通性上。因为 eBPF 的可编程性,我们还能依据业务需求,增加各种定制化插件,让数据平台可以更加灵活安全。</p>
<h3>Cilium CNI 实现</h3>
<p>Cilium Agent、Cilium CLI Client 和 CNI Plugin 运行在集群中的每一个节点上以守护进程的形式部署。Cilium CNI 插件执行所有与网络管道有关的任务如创建链接设备veth 对),为容器分配 IP配置 IP 地址路由表sysctl 参数等。Cilium Agent 编译 BPF 程序,并使内核在网络栈的关键点上运行这些程序。</p>
<p>Cilium 提供两种联网模式:</p>
<ul>
<li>叠加网络Overlay模式Cilium 默认的网络模式。集群中的所有节点为一个网状的隧道,使用基于 udp 的封装协议。VXLAN默认或 Geneve 通用网络虚拟化封装。在这种模式下Cilium 可以自动形成一个覆盖网络,无需用户使用 kube-controller-manager 中的 <code>--allocate-node-cidrs</code> 选项进行任何配置。</li>
<li>直接/本地路由模式在这个配置中Cilium 会把所有不针对另一个本地端点的数据包交给 linux 内核的路由子系统。这个设置需要一个额外的路由守护程序,如 Bird、Quagga、BGPD、Zebra 等,通过节点的 IP 向所有其他节点公布非本地节点分配的前缀。与 VxLAN 叠加相比BGP 方案具有更好的性能,更重要的是,它使容器 IP 可路由化,无需任何额外的网状配置。</li>
</ul>
<p>Cilium 在主机网络空间上创建了三个虚拟接口cilium<em>host、cilium</em>net 和 cilium<em>vxlan。Cilium Agent 在启动时创建一个名为“cilium</em>host -&gt; cilium<em>net”的 veth 对,并将 CIDR 的第一个 IP 地址设置为 cilium</em>host然后作为 CIDR 的网关。CNI 插件会生成 BPF 规则,编译后注入内核,以解决 veth 对之间的连通问题。数据链路参考如下:</p>
<p><img src="assets/30eb18f0-0c9b-11eb-bf86-c1f111c841d1.jpg" alt="14-2-cilium-cni-kube-impl" /></p>
<h3>落地安装实践</h3>
<p>因为 Cilium 对内核要求很高,本来我以为需要采用 Ubuntu 才可以安装,后来查阅文档发现,只要是 CentOS 7.x 之后就可以支持。安装步骤如下。</p>
<ol>
<li>先挂载 bpf 模块:</li>
</ol>
<pre><code>sudo mount bpffs -t bpf /sys/fs/bpf
</code></pre>
<ol start="2">
<li>修改 /etc/fstab 文件,在最后面加入如下配置:</li>
</ol>
<pre><code>bpffs /sys/fs/bpf bpf defaults 0 0
</code></pre>
<ol start="3">
<li>安装一套干净的 Kubernetes 系统,这里我采用 K3s 快速搭建:</li>
</ol>
<pre><code>export MASTER_IP=$(ip a |grep global | grep -v '10.0.2.15' | awk '{print $2}' | cut -f1 -d '/')
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC=&quot;--flannel-backend=none --no-flannel --node-ip=${MASTER_IP} --node-external-ip=${MASTER_IP} --bind-address=${MASTER_IP} --no-deploy servicelb --no-deploy traefik&quot; sh -
</code></pre>
<ol start="4">
<li>在这之后,当你运行以下命令,你应该看到 local-pathmetrics-server 和 coredns pods 处于 Pending 状态。这是正常的。</li>
</ol>
<pre><code>[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2755484853674342510a4a49400a53424a57">[email&#160;protected]</a> ~]# kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-7944c66d8d-xn96v 0/1 Pending 0 3m2s
local-path-provisioner-6d59f47c7-77bfz 0/1 Pending 0 3m2s
metrics-server-7566d596c8-8bhrq 0/1 Pending 0 3m2s
</code></pre>
<ol start="5">
<li>安装 helm3</li>
</ol>
<pre><code>sudo wget — no-check-certificate https://get.helm.sh/helm-v3.2.4-linux-amd64.tar.gz
sudo tar -zxvf helm-v3.2.4-linux-amd64.tar.gz
sudo mv linux-amd64/helm /usr/local/bin/helm
</code></pre>
<ol start="6">
<li>安装 Cilium 套件:</li>
</ol>
<pre><code># sudo helm repo add cilium https://helm.cilium.io/
# sudo helm install cilium cilium/cilium --set global.device=eth0 --set global.tag=&quot;v1.8.1&quot; \
--set global.containerRuntime.integration=&quot;containerd&quot; \
--set global.containerRuntime.socketPath=&quot;/var/run/k3s/containerd/containerd.sock&quot; \
--set global.kubeProxyReplacement=&quot;strict&quot; \
--set global.hubble.enabled=&quot;true&quot; \
--set global.hubble.listenAddress=&quot;:4244&quot; \
--set global.hubble.metrics.enabled=&quot;{dns,drop,tcp,flow,port-distribution,icmp,http}&quot; \
--set global.hubble.relay.enabled=&quot;true&quot; \
--set global.hubble.ui.enabled=&quot;true&quot; \
--kubeconfig /etc/rancher/k3s/k3s.yaml --namespace kube-system
NAME: cilium
LAST DEPLOYED: Fri Oct 9 19:53:59 2020
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
You have successfully installed Cilium with Hubble Relay and Hubble UI.
Your release version is 1.8.4.
For any further help, visit https://docs.cilium.io/en/v1.8/gettinghelp
</code></pre>
<ol start="7">
<li>去掉 IPAM 特性:</li>
</ol>
<pre><code>kubectl edit configmap cilium-config -n kube-system
### 把以下配置去掉
ipam: cluster-pool
k8s-require-ipv4-pod-cidr: &quot;true&quot;
k8s-require-ipv6-pod-cidr: &quot;false&quot;
</code></pre>
<p>稍等片刻,就可以看到 Cilium 相关的容器都启动起来了。Cilium 容器网络配置成功。</p>
<ol start="8">
<li>修改 Hubble UI 控制台服务为 NodePort 模式:</li>
</ol>
<pre><code>sudo kubectl edit svc hubble-ui -n kube-system
sudo kubectl get svc hubble-ui -n kube-system
</code></pre>
<p>然后就可以查看可视化数据平台,如下图:</p>
<p><img src="assets/649f0760-0c9b-11eb-b059-25b81b32ffa7.jpg" alt="14-3-hubble-ui" /></p>
<h3>经验总结</h3>
<p>Cilium 网络方案从实际体验来看,已经可以满足常规容器网络需求。它的可视化控制台 Hubble 是对数据平面可视化的最原生实现,比 Istio 的方案显然要技高一筹。数据可视化这块让笔者有点意外,没有想到 eBPF 的编程能力可以这么强,为之后更多的插件功能带来更多期待。因为 cilium 技术太新按照实践经验笔者推荐大家在开发测试环境可以大胆使用起来生产环境还要再等等我相信在经过半年的磨砺Cilium 应该会成为 Kubernetes 社区使用最多的容器网络方案。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099727add883d60","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>

View File

@@ -0,0 +1,888 @@
<!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>17 应用流量的优雅无损切换实践.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>17 应用流量的优雅无损切换实践</h1>
<p>Kubernetes 的部署基本上都是默认滚动式的,并且保证零宕机,但是它是有一个前置条件的。正是这个前置条件让零宕机部署表现为一个恼人的问题。为了实现 Kubernetes 真正的零宕机部署,不中断或不丢失任何一个运行中的请求,我们需要深入应用部署的运行细节并找到根源进行深入的根源分析。本篇的实践内容继承之前的知识体系,将更深入的总结零宕机部署方法。</p>
<h3>刨根问底</h3>
<h4><strong>滚动更新</strong></h4>
<p>我们首先来谈谈滚动更新的问题。根据默认情况Kubernetes 部署会以滚动更新策略推动 Pod 容器版本更新。该策略的思想就是在执行更新的过程中,至少要保证部分老实例在此时是启动并运行的,这样就可以防止应用程序出现服务停止的情况了。在这个策略的执行过程中,新版的 Pod 启动成功并已经可以引流时才会关闭旧 Pod。</p>
<p>Kubernetes 在更新过程中如何兼顾多个副本的具体运行方式提供了策略参数。根据我们配置的工作负载和可用的计算资源,滚动更新策略可以细调超额运行的 PodsmaxSurge和多少不可用的 Pods maxUnavailable。例如给定一个部署对象要求包含三个复制体我们是应该立即创建三个新的 Pod并等待所有的 Pod 启动,并终止除一个 Pod 之外的所有旧 Pod还是逐一进行更新下面的代码显示了一个名为 Demo 应用的 Deployment 对象,该应用采用默认的 RollingUpdate 升级策略,在更新过程中最多只能有一个超额运行的 PodsmaxSurge并且没有不可用的 Pods。</p>
<pre><code>kind: Deployment
apiVersion: apps/v1
metadata:
name: demo
spec:
replicas: 3
template:
# with image docker.example.com/demo:1
# ...
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
</code></pre>
<p><img src="assets/31b06780-0d19-11eb-86c5-33053ff1297d.jpg" alt="15-1-rolling-udpate.gif" /></p>
<p>此部署对象将一次创建一个带有新版本的 Pod等待 Pod 启动并准备好后触发其中一个旧 Pod 的终止,并继续进行下一个新 Pod直到所有的副本都被更新。下面显示了 <code>kubectl get pods</code> 的输出和新旧 Pods 随时间的变化。</p>
<pre><code>$ kubectl get pods
NAME READY STATUS RESTARTS AGE
demo-5444dd6d45-hbvql 1/1 Running 0 3m
demo-5444dd6d45-31f9a 1/1 Running 0 3m
demo-5444dd6d45-fa1bc 1/1 Running 0 3m
...
demo-5444dd6d45-hbvql 1/1 Running 0 3m
demo-5444dd6d45-31f9a 1/1 Running 0 3m
demo-5444dd6d45-fa1bc 1/1 Running 0 3m
demo-8dca50f432-bd431 0/1 ContainerCreating 0 12s
...
demo-5444dd6d45-hbvql 1/1 Running 0 4m
demo-5444dd6d45-31f9a 1/1 Running 0 4m
demo-5444dd6d45-fa1bc 0/1 Terminating 0 4m
demo-8dca50f432-bd431 1/1 Running 0 1m
...
demo-5444dd6d45-hbvql 1/1 Running 0 5m
demo-5444dd6d45-31f9a 1/1 Running 0 5m
demo-8dca50f432-bd431 1/1 Running 0 1m
demo-8dca50f432-ce9f1 0/1 ContainerCreating 0 10s
...
...
demo-8dca50f432-bd431 1/1 Running 0 2m
demo-8dca50f432-ce9f1 1/1 Running 0 1m
demo-8dca50f432-491fa 1/1 Running 0 30s
</code></pre>
<h4><strong>应用可用性的理想和现实之间的差距</strong></h4>
<p>通过上面的案例看执行效果可知,从旧版本到新版本的滚动更新看起来确实是平滑更新的。然而不希望发生的事情还是发生了,从旧版本到新版本的切换并不总是完美平滑的,也就是说应用程序可能会丢失一些客户端的请求。这是不可以接受的情况。</p>
<p>为了真正测试当一个实例被退出服务时,请求是否会丢失。我们不得不对我们的服务进行压力测试并收集结果。我们感兴趣的主要一点是我们的传入的 HTTP 请求是否被正确处理,包括 HTTP 连接是否保持活着。</p>
<p>这里可以使用简单的 Fortio 负载测试工具,用一连续的请求访问 Demo 的 HTTP 端点。例子种配置包括 50 个并发连接 /goroutine每秒请求比率为 500测试超时 60 秒。</p>
<pre><code>fortio load -a -c 50 -qps 500 -t 60s &quot;&lt;http://example.com/demo&gt;&quot;
</code></pre>
<p>我们在进行滚动更新部署时同时运行这个测试,如下图报告所示,会有一些连接失败的请求:</p>
<pre><code>Fortio 1.1.0 running at 500 queries per second, 4-&gt;4 procs, for 20s
Starting at 500 qps with 50 thread(s) [gomax 4] for 20s : 200 calls each (total 10000)
08:49:55 W http_client.go:673&gt; Parsed non ok code 502 (HTTP/1.1 502)
[...]
Code 200 : 9933 (99.3 %)
Code 502 : 67 (0.7 %)
Response Header Sizes : count 10000 avg 158.469 +/- 13.03 min 0 max 160 sum 1584692
Response Body/Total Sizes : count 10000 avg 169.786 +/- 12.1 min 161 max 314 sum 1697861
[...]
</code></pre>
<p>输出结果表明,并非所有的请求都被成功处理。</p>
<h4><strong>了解问题根源</strong></h4>
<p>现在需要搞清楚的问题是Kubernetes 在滚动更新时将流量重新路由,从一个旧的 Pod 实例版本到新的 Pod 实例版本,到底发生了什么。让我们来看看 Kubernetes 是如何管理工作负载连接的。</p>
<p>假设我们的客户端是直接从集群内部连接到 Demo 服务,通常会使用通过 Cluster DNS 解析的服务虚拟 IP最后到 Pod 实例。这是通过 kube-proxy 来实现的kube-proxy 运行在每个 Kubernetes 节点上并动态更新 iptables让请求路由到 Pod 的 IP 地址。Kubernetes 会更新 Pods 状态中的 endpoints 对象,因此 demo 服务只包含准备处理流量的 Pods。</p>
<p>还有一个情况,客户端流量是从 ingress 方式连接到 Pods 实例,它的连接方式不一样。滚动更新时应用请求会有不同的请求宕机行为。如 Nginx Ingress 是直接把 Pod IP 地址的 endpoints 对象观察起来,有变化时将重载 Nginx 实例,导致流量中断。</p>
<p>当然我们应该需要知道的是Kubernetes 的目标时在滚动更新过程中尽量减少服务中断。一旦一个新的 Pod 还活着并且准备提供服务时Kubernetes 就会将一个旧的 Pod 从 Service 中移除,具体操作是将 Pod 的状态更新为 Terminating将其从 endpoints 对象中移除,并发送一个 SIGTERM 。SIGTERM 会导致容器以一种优雅的方式(需要应用程序能正确处理)关闭,并且不接受任何新的连接。在 Pod 被驱逐出 endpoints 对象后负载均衡器将把流量路由到剩余的新的对象上。注意此时Pod 在负载均衡器注意到变化并更新其配置的时候,移出 endpoints 对象记录和重新刷新负载均衡配置是异步发生的,因此不能保证正确的执行顺序还可能会导致一些请求被路由到终止的 Pod这就是在部署过程中造成应用可用性差的真实原因。</p>
<h3>实现零故障部署</h3>
<p>现在我们的目标就是如何增强我们的应用程序能力,让它以真正的零宕机更新版本。</p>
<p>首先,实现这个目标的前提条件是我们的容器要能正确处理终止信号,即进程会在 SIGTERM 上优雅地关闭。如何实现可以网上查阅应用优雅关闭的最佳实践,这里不在赘述。</p>
<p>下一步是加入就绪探针,检查我们的应用是否已经准备好处理流量。理想情况下,探针已经检查了需要预热的功能的状态,比如缓存或数据库初始化。</p>
<p>为了解决 Pod terminations 目前没有阻塞和等待直到负载均衡器被重新配置的问题,包含一个 preStop 生命周期钩子。这个钩子会在容器终止之前被调用。生命周期钩子是同步的,因此必须在向容器发送最终终止信号之前完成。</p>
<p>在下面的例子中,在 SIGTERM 信号终止应用进程之前使用 preStop 钩子来等待 120 秒,并且同时 Kubernetes 将从 endpoints 对象中移除 Pod。这样可以确保在生命周期钩子等待期间负载均衡器可以正确的刷新配置。</p>
<p>为了实现这个行为,在 demo 应用部署中定义一个 preStop 钩子如下:</p>
<pre><code>kind: Deployment
apiVersion: apps/v1beta1
metadata:
name: demo
spec:
replicas: 3
template:
spec:
containers:
- name: zero-downtime
image: docker.example.com/demo:1
livenessProbe:
# ...
readinessProbe:
# ...
lifecycle:
preStop:
exec:
command: [&quot;/bin/bash&quot;, &quot;-c&quot;, &quot;sleep 120&quot;]
strategy:
# ...
</code></pre>
<p>使用负载测试工具重新测试,发现失败的请求数为零,终于实现无损流量的更新。</p>
<pre><code>Fortio 1.1.0 running at 500 queries per second, 4-&gt;4 procs, for 20s
Starting at 500 qps with 50 thread(s) [gomax 4] for 20s : 200 calls each (total 10000)
[...]
Code 200 : 10000 (100.0 %)
Response Header Sizes : count 10000 avg 159.530 +/- 0.706 min 154 max 160 sum 1595305
Response Body/Total Sizes : count 10000 avg 168.852 +/- 2.52 min 161 max 171 sum 1688525
[...]
</code></pre>
<h3>实践总结</h3>
<p>应用的滚动更新是流量平滑切换的原子操作基础。只有让 Kubernetes 能正确处理滚动更新,才有可能实现应用流量的无损更新。在此基础之上,通过部署多套 Ingress 资源来引入流量是可以解决平滑流量的切换的。另外,因为 Helm 支持部署一套应用的多个版本,通过版本的选择也是可以快速切换流量的。这样的技巧都是基于最底层的 Pod 能保证不中断请求才行。</p>
<h3>参考资料</h3>
<ul>
<li><a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/">https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">下一页</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":"7099727cb9a43d60","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>

View File

@@ -0,0 +1,936 @@
<!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>22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>22 存储对象 PV、PVC、Storage Classes 的管理落地实践</h1>
<p>谈到 Kubernetes 存储对象的管理,大多数读者使用最多的就是 Local、NFS 存储类型。因为基于本地卷的挂载使用很少出现问题,并不会出现有什么困难的场景需要用心学习整理。但是从我这里出发想带领读者一起,往更深层的对象实现细节和云原生的存储运维角度出发,看看我们能怎么管理这些资源才是落地的实践。</p>
<h3>了解 PV、PVC、StorageClass</h3>
<p>StorageClass 是描述存储类的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。 Kubernetes 本身并不清楚各种类代表的是什么。这个类的概念在其他存储系统中有时被称为“配置文件”。</p>
<p>每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段,这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。</p>
<p>StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。参考范例如下:</p>
<pre><code class="language-yaml">apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
- debug
volumeBindingMode: Immediate
</code></pre>
<p>持久卷PersistentVolumePV是集群中的一块存储可以由管理员事先供应或者使用存储类StorageClass来动态供应。 持久卷是全局集群资源,就像 Node 也是全局集群资源一样,没有 Namespace 隔离的概念。PV 持久卷和普通的 Volumes 一样,也是使用卷插件来实现的,只是它们拥有自己独立的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。</p>
<p>持久卷申领PersistentVolumeClaimPVC表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会消耗 PV 资源。Pod 可以请求特定数量的资源CPU 和内存);同样 PVC 申领也可以请求特定 PV 的大小和访问模式。</p>
<p>尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源,常见的情况是针对不同的用户需要提供具有不同属性(如性能)的 PersistentVolume 卷。 集群管理员需要能够提供不同性质的 PersistentVolume并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户。 为了满足这类需求就有了存储类StorageClass资源。</p>
<p>所以总结下来,对于存储资源,我们默认指代的就是铁三角 API 对象StorageClass、PersistentVolume、PersistentVolumeClaim。</p>
<h3>了解 CSI</h3>
<p>从 Kubernetes v1.13 开始 CSI 进入稳定可用阶段所以用户有必要了解这个容器存储接口。CSI 卷类型是一种外部引用驱动的 CSI 卷插件,用于 Pod 与在同一节点上运行的外部 CSI 卷驱动程序交互。部署 CSI 兼容卷驱动后,用户可以使用 CSI 作为卷类型来挂载驱动提供的存储。</p>
<p><img src="assets/c1e6bc40-2ce1-11eb-90f6-fbd19bda6e6e" alt="19-1-csi-arch" /></p>
<p>一直以来,存储插件的测试、维护等事宜都由 Kubernetes 社区来完成,即使有贡献者提供协作也不容易合并到主分支发布。另外,存储插件需要随 Kubernetes 一同发布,如果存储插件存在问题有可能会影响 Kubernetes 其他组件的正常运行。</p>
<p>鉴于此Kubernetes 和 CNCF 决定把容器存储进行抽象通过标准接口的形式把存储部分移到容器编排系统外部去。CSI 的设计目的是定义一个行业标准,该标准将使存储供应商能够自己实现,维护和部署他们的存储插件。这些存储插件会以 Sidecar Container 形式运行在 Kubernetes 上并为容器平台提供稳定的存储服务。</p>
<p>如上 CSI 设计图:<strong>浅绿色</strong>表示从 Kubernetes 社区中抽离出来且可复用的组件,负责连接 CSI 插件(右侧)以及和 Kubernetes 集群交互:</p>
<ul>
<li>Driver-registrar使用 Kubelet 注册 CSI 驱动程序的 sidecar 容器,并将 NodeId (通过 GetNodeID 调用检索到 CSI endpoint添加到 Kubernetes Node API 对象的 annotation 里面。</li>
<li>External-provisioner监听 Kubernetes PersistentVolumeClaim 对象的 sidecar 容器,并触发对 CSI 端点的 CreateVolume 和 DeleteVolume 操作;</li>
<li>External-attacher可监听 Kubernetes VolumeAttachment 对象并触发 ControllerPublish 和 ControllerUnPublish 操作的 sidecar 容器,负责 attache/detache 卷到 Node 节点上。</li>
</ul>
<p>右侧<strong>浅灰色</strong>表示第三方实现的存储插件驱动,分别有三个服务:</p>
<ul>
<li>CSI identify标志插件服务并维持插件健康状态</li>
<li>CSI Controller创建/删除、attaching/detaching、快照等</li>
<li>CSI Nodeattach/mount、umount/detach</li>
</ul>
<p>通过对比 Kubernetes 的内置 Volume Plugin以及外置 Provisioner 和 CSI 三种方式,在对接比较常见的存储时,可以使用不需要改动的内置方案,因为开箱即用,但是缺点也非常明显,只支持有限的存储类型,可拓展性较差甚至有版本限制,另外官方宣布以后新特性将不再添加到其中。相比之下,使用 CSI 则可以实现和 Kubernetes 的核心组件解耦,并能支持更多的存储类型和高级特性,因而也是推荐使用的一种供应方式。由于对编排系统而言是非侵入式插件部署,因而更受存储服务商的青睐。</p>
<h3>采用 Volume Snapshots 备份</h3>
<p>与 API 资源 PersistentVolume 和 PersistentVolumeClaim 用于为用户和管理员提供卷的方式类似VolumeSnapshotContent 和 VolumeSnapshot API 资源被提供用于为用户和管理员创建卷快照。</p>
<p>VolumeSnapshotContent 是指从群集中由管理员配置的卷中获取的快照。它是集群中的资源,就像 PersistentVolume 是集群资源一样。</p>
<p>VolumeSnapshot 是用户对卷的快照请求。它类似于 PersistentVolumeClaim。</p>
<p>VolumeSnapshotClass 允许您指定属于 VolumeSnapshot 的不同属性。这些属性可能在存储系统上同一个卷的快照中会有所不同,因此不能使用 PersistentVolumeClaim 的同一个 StorageClass 来表达。</p>
<p>卷快照为 Kubernetes 用户提供了一种标准化的备份恢复方法,可以在特定时间点复制卷的内容,而无需创建一个全新的卷。例如,数据库管理员可以通过该功能在执行编辑或删除修改之前备份数据库。</p>
<p>用户在使用该功能时需要注意以下几点。</p>
<ul>
<li>API 对象 VolumeSnapshot、VolumeSnapshotContent 和 VolumeSnapshotClass 是 CRD不是核心 API 的一部分。 VolumeSnapshot 支持仅适用于 CSI 驱动程序。</li>
<li>作为 VolumeSnapshot 测试版部署过程的一部分Kubernetes 团队提供了一个部署到控制平面的快照控制器,以及一个名为 csi-snapshotter 的帮助容器,与 CSI 驱动程序一起部署。快照控制器监视 VolumeSnapshot 和 VolumeSnapshotContent 对象,并负责动态供应中 VolumeSnapshotContent 对象的创建和删除。帮助容器 csi-snapshotter 监视 VolumeSnapshotContent 对象,并触发针对 CSI 端点的 CreateSnapshot 和 DeleteSnapshot 操作。</li>
<li>CSI 驱动程序可能已经实现或没有实现卷快照功能。已提供卷快照支持的 CSI 驱动程序可能会使用 csi-snapshotter。详情请参见 CSI 驱动程序文档。</li>
<li>CRD 和 快照控制器的安装是 Kubernetes 发行版的责任。</li>
</ul>
<h4><strong>VolumeSnapshot 和 VolumeSnapshotContent 的生命周期</strong></h4>
<p>VolumeSnapshotContents 是集群中的资源。VolumeSnapshot 是对这些资源的请求。VolumeSnapshotContents 和 VolumeSnapshot 之间的交互遵循这个生命周期。</p>
<p><strong>1. 供应卷快照</strong></p>
<p>有两种方式可以配置快照:预配置或动态配置。</p>
<p><strong>2. 预备</strong></p>
<p>群集管理员会创建一些 VolumeSnapshotContents。它们携带了存储系 统上真实卷照的详细信息,可供集群用户使用。它们存在于 Kubernetes API 中,可供消费。</p>
<p><strong>3. 动态</strong></p>
<p>您可以请求从 PersistentVolumeClaim 动态获取快照而不是使用预先存在的快照。VolumeSnapshotClass 指定了存储提供商的特定参数,以便在获取快照时使用。</p>
<p><strong>4. 绑定</strong></p>
<p>快照控制器处理 VolumeSnapshot 对象与适当的 VolumeSnapshotContent 对象的绑定,在预供应和动态供应的情况下都是如此。绑定是一个一对一的映射。</p>
<p>在预供应绑定的情况下VolumeSnapshot 将保持未绑定状态,直到请求的 VolumeSnapshotContent 对象被创建。</p>
<p><strong>5. 作为快照源保护的持久性卷索赔</strong></p>
<p>这个保护的目的是为了确保在使用中的 PersistentVolumeClaim API 对象在快照时不会被从系统中移除(因为这可能导致数据丢失)。</p>
<p>当一个 PersistentVolumeClaim 的快照被取走时,该 PersistentVolumeClaim 是在使用中的。如果您删除了一个正在使用的 PersistentVolumeClaim API 对象作为快照源PersistentVolumeClaim 对象不会被立即删除。相反PersistentVolumeClaim 对象的删除会被推迟到快照准备好或中止之后。</p>
<p><strong>6. 删除</strong></p>
<p>删除是通过删除 VolumeSnapshot 对象来触发的,将遵循 DeletionPolicy。如果 DeletionPolicy 是 Delete那么底层存储快照将和 VolumeSnapshotContent 对象一起被删除。如果 DeletionPolicy 是 Retain那么底层快照和 VolumeSnapshotContent 都会保留。</p>
<h4><strong>VolumeSnapshots</strong></h4>
<p>每个 VolumeSnapshot 包含一个规格和一个状态:</p>
<pre><code class="language-yaml">apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
name: new-snapshot-test
spec:
volumeSnapshotClassName: csi-hostpath-snapclass
source:
persistentVolumeClaimName: pvc-test
</code></pre>
<p>persistentVolumeClaimName 是快照的 PersistentVolumeClaim 数据源的名称。动态供应快照时需要该字段。</p>
<p>卷快照可以通过使用属性 volumeSnapshotClassName 指定 VolumeSnapshotClass 的名称来请求特定的类。如果没有设置任何内容,则使用默认的类(如果可用)。</p>
<p>对于预设的快照,您需要指定一个 volumeSnapshotContentName 作为快照的源如下例所示。对于预置快照volumeSnapshotContentName 源字段是必需的。</p>
<pre><code class="language-yaml">apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
name: test-snapshot
spec:
source:
volumeSnapshotContentName: test-content
</code></pre>
<h4><strong>Volume Snapshot Contents</strong></h4>
<p>每个 VolumeSnapshotContent 包含一个规格和状态。在动态供应中快照通用控制器会创建 VolumeSnapshotContent 对象。下面是一个例子:</p>
<pre><code class="language-yaml">apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshotContent
metadata:
name: snapcontent-72d9a349-aacd-42d2-a240-d775650d2455
spec:
deletionPolicy: Delete
driver: hostpath.csi.k8s.io
source:
volumeHandle: ee0cfb94-f8d4-11e9-b2d8-0242ac110002
volumeSnapshotClassName: csi-hostpath-snapclass
volumeSnapshotRef:
name: new-snapshot-test
namespace: default
uid: 72d9a349-aacd-42d2-a240-d775650d2455
</code></pre>
<p>volumeHandle 是在存储后端创建的卷的唯一标识符,由 CSI 驱动程序在卷创建期间返回。动态供应快照时需要该字段。它指定了快照的卷源。</p>
<p>对于预配置的快照,群集管理员负责创建 VolumeSnapshotContent 对象,具体如下:</p>
<pre><code class="language-yaml">apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshotContent
metadata:
name: new-snapshot-content-test
spec:
deletionPolicy: Delete
driver: hostpath.csi.k8s.io
source:
snapshotHandle: 7bdd0de3-aaeb-11e8-9aae-0242ac110002
volumeSnapshotRef:
name: new-snapshot-test
namespace: default
</code></pre>
<p>snapshotHandle 是在存储后端创建的卷快照的唯一标识符。该字段对预置快照是必需的。它指定了该 VolumeSnapshotContent 所代表的存储系统上的 CSI 快照 ID。</p>
<h3>从快照中恢复卷</h3>
<p>您可以通过使用 PersistentVolumeClaim 对象中的 dataSource 字段来提供一个新的卷,并从快照中恢复数据。下面是一个例子:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restore-pvc
spec:
storageClassName: csi-hostpath-sc
dataSource:
name: new-snapshot-test
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
</code></pre>
<h3>总结</h3>
<p>随着有状态应用的广泛采用,有状态的存储对象资源开始被广泛使用,这样 Kubernetes 生态中开始包含了更多对存储资源的管理对象。其中最重要的就是备份和恢复对象。当然目前最新的 API 对象中包含的还是测试阶段的 VolumeSnapshot 的概念,无法直接生产可用。另外注意的是 CSI 卷驱动的外置插件架构设计,目前是生产可用,请不要在使用内置驱动来挂载卷了。根据存储驱动落地的情况,大量的 NFS 存储的管理仍然是当前最重要的部分,大家只需要掌握创建、备份、恢复操作就已经算掌握了 90% 的存储对象使用技能。对于分布式存储 Ceph我们可以在开发测试环节大量使用等待 Ceph 驱动的成熟时机就可以大量采用。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">下一页</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":"70997286a96b3d60","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>

View File

@@ -0,0 +1,688 @@
<!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>24 练习篇K8s 集群配置测验.md</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">00 为什么我们要学习 Kubernetes 技术.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/01 重新认识 Kubernetes 的核心组件.md">01 重新认识 Kubernetes 的核心组件.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/02 深入理解 Kubernets 的编排对象.md">02 深入理解 Kubernets 的编排对象.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/03 DevOps 场景下落地 K8s 的困难分析.md">03 DevOps 场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/04 微服务应用场景下落地 K8s 的困难分析.md">04 微服务应用场景下落地 K8s 的困难分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/05 解决 K8s 落地难题的方法论提炼.md">05 解决 K8s 落地难题的方法论提炼.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/06 练习篇K8s 核心实践知识掌握.md">06 练习篇K8s 核心实践知识掌握.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/07 容器引擎 containerd 落地实践.md">07 容器引擎 containerd 落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/08 K8s 集群安装工具 kubeadm 的落地实践.md">08 K8s 集群安装工具 kubeadm 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/09 南北向流量组件 IPVS 的落地实践.md">09 南北向流量组件 IPVS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/10 东西向流量组件 Calico 的落地实践.md">10 东西向流量组件 Calico 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/11 服务发现 DNS 的落地实践.md">11 服务发现 DNS 的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/12 练习篇K8s 集群配置测验.md">12 练习篇K8s 集群配置测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/13 理解对方暴露服务的对象 Ingress 和 Service.md">13 理解对方暴露服务的对象 Ingress 和 Service.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/14 应用网关 OpenResty 对接 K8s 实践.md">14 应用网关 OpenResty 对接 K8s 实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/15 Service 层引流技术实践.md">15 Service 层引流技术实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/16 Cilium 容器网络的落地实践.md">16 Cilium 容器网络的落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/17 应用流量的优雅无损切换实践.md">17 应用流量的优雅无损切换实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/18 练习篇:应用流量无损切换技术测验.md">18 练习篇:应用流量无损切换技术测验.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/19 使用 Rook 构建生产可用存储环境实践.md">19 使用 Rook 构建生产可用存储环境实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/20 有状态应用的默认特性落地分析.md">20 有状态应用的默认特性落地分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md">21 案例:分布式 MySQL 集群工具 Vitess 实践分析.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md">22 存储对象 PV、PVC、Storage Classes 的管理落地实践.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">23 K8s 集群中存储对象灾备的落地实践.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 实践入门指南/24 练习篇K8s 集群配置测验.md">24 练习篇K8s 集群配置测验.md.html</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>24 练习篇K8s 集群配置测验</h1>
<p>第二部分的内容围绕 Kubernetes 核心组件的安装配置一一给大家拆解了一遍当前集群组件最主流的配置就是这些Containerd、kubeadm、IPVS、Calico、kube-dns。读者通过官方文档就可以独立配置一套集群只是笔者发现因为集群配置的过度复杂能获得的环境也是千差万别很难得到统一的认知。本篇测验的目的就是带着大家一起校验一遍我们学习到的经验一起搭建一套集群的全过程以此来校验我们掌握的集群知识。</p>
<h3>环境</h3>
<p>从第一天接触容器技术之后,我们想解决的问题就是环境依赖问题。因为 Docker 是让环境包裹这应用一起制作成镜像分发的。等我们配置 Kubernetes 集群的时候,我们操作的最小单元是 Pod你可以理解为是一个容器组这个容器组并不是简单的把一组容器放一起就完事了。它的设计巧妙之处在于以 pause 为核心的基础容器把相关应用的所有环境依赖都掌握在自己的运行时里面。其它相关业务容器只是加入到这个运行时里面,这些业务容器出现问题并不会破坏环境。这是 Kubernetes 构建业务集群的核心设计,非常巧妙地解决了应用服务的可用性问题。</p>
<p>现在我们要选择操作系统的版本了。你会发现并没有任何官方文档说过,哪一个版本是指定的。其实官方并没有这样的约定。因为容器的目的就是解决环境的依赖,但是这么多年的演进,说得更清楚一点,我们仍然有一个核心依赖就是 Kernel 依赖搞不定。Kernel 的特性会决定容器的特性,我们一般在选择上会参考 Docker 的版本来定,主流的有 18.09、19.03 等。</p>
<p>你发现没有,你并不能保证在特定的环境下这些 Docker 版本没有问题,这就是我们在配置生产环境中出现问题自己埋下的坑。如果你是企业内部使用,最好的办法是建立基准线,明确版本号,在大量实践的基础上投入人力来维护这个版本的稳定性。因为容器技术发展很快,现在 Kubernetes 已经和 Docker 越来越规避,都在使用 containerd 来支持底层容器运行时的管理,作为用户我们是无法回避这个。</p>
<p>这里又突显一个问题,因为组件的变革,我到底应该选择哪个版本呢,它们稳定吗?因为 Kubernetes 是开源社区推动的软件,我们一定要遵循开源的方式来使用这些软件才能得到正确的经验。我总结出来的经验如下,方便大家参考:</p>
<p>x86-64 仍然是当前对容器最好的系统架构体系,目前主流的系统聚集在 RedHat/CentOS 7.x 系列、Ubuntu 16.04 系列。对于内核红帽系主要在 3.10 以上Ubuntu 能到 4.4 以上。有些用户会通过开源 Kernel 仓库把红帽系的 Kernel 升级到 4.4也比较常见。升级内核的代价就是引入很多未知的模块让系统变得不稳定。ARM 系统架构会对整个 Kubernetes 组件的文件格式产生兼容性要求,在选择适配的时候,一定要注意有没有准备好 Kubernetes 相应的组件。总结下来,主流的操作系统主要是红帽的 7.x 系列和 Ubuntu LTS 系列 16.04。升级大版本操作系统对 Kubernetes 来说,需要做很多适配工作,目前开源社区是不太可能帮用户做的。一定注意。</p>
<p>Kubernetes 的版本更新很快,整个社区会维护 3 个主线版本,如现在主要为 1.16.x、1.17.x、1.18.x。这个 x 版本号差不多 2 周就一个迭代,主要是修复 Bug。很多团队在使用上总结了一些技巧比如取奇数版本或者偶数版本作为自己的主力版本这个做法的目的就是规避最新版本带来的不稳定性。并不是说奇数版本好或者是偶数版本稳定这是纯属瞎猜。作为开源软件它的质量是社区在维护落实到用户这里就是大家都是小白鼠需要在自己的环境试验验证组件的可靠性。总结下来主流的环境还是选择比最新版本低 1 个或者 2 个子版本作为周期来当做自己的软件来维护。维护开源软件不是免费的,它是通过大家的努力才能保证组件的使用可靠性的。</p>
<p>除了 Kubernetes 主线版本的选择我们应该延迟 1 到 2 个版本之外,对于其它附属组件如 Calico、kube-dns、Containerd 等,应该需要选择最新版本。主要原因在于它们是一线运行的组件,被调用的次数是更多的,发现问题的机会更突出。越早发现问题越快得到修复。这又是开源里面的原则,就是越早发现、越早修复,组件越稳定。很多用户在组件选择上,会比较保守,导致很多修复过的 Bug 还存在于你的集群中,让不确定性得到蔓延。总结下来,跑容器的一线组件应该使用最新版本,越早发现,你的程序越稳固。言下之意,当开源小白鼠,咱们也要有对策,通过自动化测试的环境,把这些组件多测测。</p>
<p>很多以为 Kubernetes 安装上之后就完事大吉,环境的事情就不用操心了。诚然,通过容器确实可以解决一部分运维的问题。但是应用架构的可靠性并不能依靠 Kubernetes。为什么在有了 容器之后,在 DevOps 领域开始引入了 SRE 的概念,就是说业务保障一直是业务核心能力,不能依赖 Kubernetes。用了 Kubernetes 之后,你更要关注架构的稳定性。</p>
<h4>kubeadm 的配置测验</h4>
<p>kubeadm 推出的初衷是为了用更平滑的方式来安装、升级 Kubernetes。在早期我是排斥的因为二进制的安装方式好像更简洁排错也更方便。但是随着安装经验的丰富我发现二进制的安装还是无法标准化配置起来手工操作的地方很多无法满足一键安装的目的。kubeadm 是唯一被官方认可的安装项目,可以说明社区对它云原生的安装配置方式的认可。</p>
<p>这里引出的问题就是kubeadm 是不是安装的结果都是一样呢?不是的。它考虑的单机模式、高可用模式、组件混合镜像模式、组件分组镜像模式,这些让用户在安装的时候会遇到很多选择,也会产生出一些不可知的问题。</p>
<p>因为 kubeadm 的版本的不同,它的安装过程和细节都会微调,我们应该尽量使用最新版本的 kubeadm 来安装,这样就可以得到很多一键部署的好处。很多原来手工需要做的事情,如自签名证书的签发都是 kubeadm 自动帮你做了,另外如安装之前的环境调优的参数也会自动帮你生成。这些操作都是之前需要手工考虑并自己手工执行的。目前笔者的经验就是:以官方文档为基准,选择最新版本的 kubeadm 为最佳组件,然后做 Kubernetes 的安装规划工作,目前还没有碰到什么难题。</p>
<h4>Calico、IPVS、kube-dns 组件的配置测验</h4>
<p>很多用户谈到容器网络就色变,因为之前容器网络的方案太多,让用户根本选择不清楚。每家都说自己的网络方案好,但是其实每家的解决方案都不是最完美的方案。目前,容器网络方案并没有官方推荐的方案。从 Kubernetes 官方文档中介绍到,网络这个范畴是不包括在集群组件中的。这是社区的选择,无可厚非。但是我们安装集群,如果没有配置网络,这个 Kubernetes 是无法承载业务容器的。</p>
<p>为了这个实际问题,我们要选择一个合适的方案。</p>
<p>为什么说 Calico 是当前最理想的方案,主要原因是它的配置简单,在 100 台物理机规模下通过 IPIP 模式创建的容器网络,性能已经接近主机网卡模式,损耗很小。这个是以前版本的 Calico 无法解决的,现在最新版本经过测试发现性能提升不少。对于 kube-dns它对容器网络还是有依赖只有你有了容器网络之后kube-dns 才能正常工作。目前主流的是 CoreDns在 100 台物理机下同样性能很好,目前运行上并没有遇到什么大问题。</p>
<p>IPVS 是 Kernel 的内核模块,主要代替 kube-proxy 的南北向流量。但是因为它的功能还是局限在 proxy 之上,对于 kube-proxy 的东西向流量的支持还是不行的,需要靠 iptables 来转换。华为通过压测发现之前用 iptables 来解决南北向流量的性能瓶颈,这才提出的 IPVS 的方案。随着现在 eBPF 可编程数据包的出现,让 IPVS 的方案开始进入到一个过度阶段。因为 eBPF 技术不仅仅支持南北向,也能支持东西向,可以完美替换到 iptables 的工作能力。毕竟 iptables 是为防火墙设计的,复杂并动态变更的规则会对系统带来影响,从而导致对业务的影响,这些都是用户不愿意看到的。</p>
<p>总结下来,这些组件的配置目前来看都有默认配置,基本上配置一次就不用更改,大家只要能验证能否正常工作就可以,没有必要花费太大精力在这些组件的配置上。</p>
<h3>总结</h3>
<p>所谓 Kubernetes 的配置测验,依靠的是大家动手去安装,单靠例子的示范很难让你获得一手的经验。但是测验的目的是让你能明白在安装过程中遇到的问题和解决方案。我们要感受到开源软件的不同之处,它利用社区的力量来维护版本的稳定性。</p>
<p>大家安装的时候肯定会遇到这样那样的问题,除了自己验证排错之外,你可以理直气壮的在社区的问题列表栏上写下自己的问题,方便大家互通有无。很多国内的用户并没有理解这种交互带来的价值,因为开源运动是一种社交活动,它依赖用户的互动,如果大家都不去反馈,这个软件只会变的越来越差。</p>
<p>大家要理解 kubeadm 的意义,就是未来可能更多的配置要被默认值替代,大家只需要一键执行就可以获得一套性能可观的 Kubernetes 环境。这个需要时间,我们可以期待。</p>
<p>参考:</p>
<ul>
<li><a href="https://kubernetes.io/blog/2017/01/stronger-foundation-for-creating-and-managing-kubernetes-clusters/">https://kubernetes.io/blog/2017/01/stronger-foundation-for-creating-and-managing-kubernetes-clusters/</a></li>
</ul>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 实践入门指南/23 K8s 集群中存储对象灾备的落地实践.md">上一页</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":"7099728aab283d60","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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB