mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
685 lines
22 KiB
HTML
685 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>15 从 0 开始创作云原生应用(殷达).md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/01 第一堂“云原生”课.md.html">01 第一堂“云原生”课.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/02 容器基本概念.md.html">02 容器基本概念.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/03 Kubernetes 核心概念.md.html">03 Kubernetes 核心概念.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/04 理解 Pod 和容器设计模式.md.html">04 理解 Pod 和容器设计模式.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/05 应用编排与管理:核心原理.md.html">05 应用编排与管理:核心原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/06 应用编排与管理.md.html">06 应用编排与管理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/07 应用编排与管理:Job & DaemonSet.md.html">07 应用编排与管理:Job & DaemonSet.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/08 应用配置管理.md.html">08 应用配置管理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/09 应用存储和持久化数据卷:核心知识.md.html">09 应用存储和持久化数据卷:核心知识.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/10 应用存储和持久化数据卷:存储快照与拓扑调度(至天).md.html">10 应用存储和持久化数据卷:存储快照与拓扑调度(至天).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/11 可观测性:你的应用健康吗?(莫源).md.html">11 可观测性:你的应用健康吗?(莫源).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/12 可观测性-监控与日志(莫源).md.html">12 可观测性-监控与日志(莫源).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/13 Kubernetes 网络概念及策略控制(叶磊).md.html">13 Kubernetes 网络概念及策略控制(叶磊).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/14 Kubernetes Service(溪恒).md.html">14 Kubernetes Service(溪恒).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/CNCF X 阿里巴巴云原生技术公开课/15 从 0 开始创作云原生应用(殷达).md.html">15 从 0 开始创作云原生应用(殷达).md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/16 深入解析 Linux 容器(华敏).md.html">16 深入解析 Linux 容器(华敏).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>15 从 0 开始创作云原生应用(殷达)</h1>
|
||
|
||
<h2>一、 云原生应用是什么?</h2>
|
||
|
||
<p>首先我们来思考一个问题:云原生应用是什么?</p>
|
||
|
||
<p><img src="assets/FlGLRC6CFQW7Xqx5WQmgFXFkPxmh" alt="avatar" /></p>
|
||
|
||
<p>在生活中我们会和各种各样的应用打交道,有时候会在移动端上使用淘宝购物、使用高德导航,在 PC 端使用 word 编辑文稿、使用 Photoshop 处理相片······这些在各类平台上的应用程序对用户而言,大多数都只需要用户点击安装就可以使用了。那么对于云上的应用,或者说在我们今天的云上、在 Kubernetes 上的应用,是什么样子的呢?</p>
|
||
|
||
<p>想象一下,如果我们要把一个应用程序部署到云上都需要做什么呢?</p>
|
||
|
||
<p><img src="assets/FgbvI682-snP7lTv8Efj6TBvtRS2" alt="avatar" /></p>
|
||
|
||
<p>首先我们要准备好它所需的环境,打包成一个 docker 镜像,把这个镜像放到 deployment 中。部署服务、应用所需要的账户、权限、匿名空间、秘钥信息,还有可持久化存储,这些 Kubernetes 的资源,简而言之就是要把一堆 yaml 配置文件布置在 Kubernetes 上。</p>
|
||
|
||
<p><img src="assets/FmTz-frFm2fS1jOGoyssX0z-hdI1" alt="avatar" /></p>
|
||
|
||
<p>虽然应用的开发者可以把这些镜像存放在公共的仓库中,然后把部署所需要的 yaml 的资源文件提供给用户,当然用户仍然需要自己去寻找这些资源文件在哪里,并把它们一一部署起来。倘若用户希望修改开发者提供的默认资源,比如说想使用更多的副本数,或者是修改服务端口,那他还需要自己去查:在这些资源文件中,哪些地方需要相应的修改。同时版本更替和维护,也会给开发者和用户造成很大的麻烦,所以可以看到最原始的这种 Kubernetes 的应用形态并不是非常的便利。</p>
|
||
|
||
<h2>二、Helm 与 Helm Chart</h2>
|
||
|
||
<h3>Helm 是什么?</h3>
|
||
|
||
<p><img src="assets/Fh-OqTnu1PrHRpx5ger_CWtRyIXt" alt="avatar" /></p>
|
||
|
||
<p>我们今天的主角:Helm,就在这样的环境下应用而生。开发者安装 Helm Chart 的格式,将应用所需要的资源文件都包装起来,通过模板化的方法,将一些可变的字段,比如说我们之前提到的要暴露哪一个端口、使用多少副本数量,把这些信息都暴露给用户,最后将封装好的应用包,也就是我们所说的 Helm Chart 集中存放在统一的仓库里面供用户浏览下载。</p>
|
||
|
||
<p>那么对于用户而言,使用 Helm 一条简单的命令就可以完成应用的安装、卸载和升级,我们可以在安装完成之后使用 kubectl 来查看一下应用安装后的 pod 的运行状态。需要注意的是,我们证明使用的是 Helm v3 的一个命令,与目前相对较为成熟的 Helm v2 是有一定的区别的。我们推荐大家在进行学习尝鲜的过程中使用这个最新的 v3 版本。</p>
|
||
|
||
<h3>如何去创作一个 Helm 应用</h3>
|
||
|
||
<p><img src="assets/Fr3tuEfSFfA8Y3CtOI7agIY1zX43" alt="avatar" /></p>
|
||
|
||
<p>站在开发者的角度上,我们应该如何去创作一个 Helm 应用呢?首先我们需要一个准备部署的镜像,这个镜像可以是一个 JAVA 程序、一个 Python 脚本,甚至是一个空的 Linux 镜像,跑几条命令。</p>
|
||
|
||
<h4>编写 Golang 程序</h4>
|
||
|
||
<p><img src="assets/FiO1KiSU0QikZMSmUE_W5reBmdqC" alt="avatar" /></p>
|
||
|
||
<p>如上图所示,这里我们是用 Golang 编写一个非常简单的 Hello World 的 http 服务,并且使用 docker 进行一个打包。Golang 的程序大致是长这个样子的,包括从环境变量中读取 pod、username 两个参数,在指定的端口上提取 http 服务,并返回相应的响应信息。</p>
|
||
|
||
<h4>构建 Docker 镜像</h4>
|
||
|
||
<p><img src="assets/FlHU3ldIkseL6MfY_cFb6BLlK9A2" alt="avatar" /></p>
|
||
|
||
<p>打包用的 Dockerfile 是长这个样子的。在这里面,我们首先对 Golang 代码进行编译,然后将编译后的程序放到 03:53 的一个镜像中来缩小镜像的体积。我们看到上文所说的两个环境变量只有 port 在这里面进行一个设置,username 将会在后续作为应用的一个参数报告给用户,所以在这里面我们先不做设置,在 docker 构建好镜像之后,我们把这个镜像上传到仓库中,比如说我们可以上传到 Docker Helm,或者是阿里云镜像仓库中。</p>
|
||
|
||
<h4>创建空白应用</h4>
|
||
|
||
<p><img src="assets/FoLmRbN6XO1vVBOKvArpgJNh7trC" alt="avatar" /></p>
|
||
|
||
<p>准备工作都做完之后,我们可以开始今天的重头戏,也就是构建这个 Helm Chart 了。首先我们先运行 helm create 的命令,创建一个空白的应用,那么在 create 命令运行完之后,可以看到在这个 Charts 的文件夹下出现了一系列文件和文件夹。</p>
|
||
|
||
<ul>
|
||
|
||
<li>Charts.yaml 的文件包含了 Helm Chart 的一些基本信息;</li>
|
||
|
||
<li>templates 文件夹内则是存放了这个应用所需要的各种 Kubernetes 的资源;</li>
|
||
|
||
<li>values.yaml 则是提供了一个默认的参数配置。</li>
|
||
|
||
</ul>
|
||
|
||
<h4>Chart 配置</h4>
|
||
|
||
<p><img src="assets/FtR1WQmfbfH6aTzD7oJ5A5v_GC0a" alt="avatar" /></p>
|
||
|
||
<p>接下来一个一个看:</p>
|
||
|
||
<p>在根目录下这个 Charts.yaml 文件内声明了当前 Chart 的名称和版本和一些基本信息,那么这些信息会在 chart 被放入仓库之后,供用户浏览和检索,比如我们在这里面可以把 chart 的 description 改成 My first hello world helm chart。</p>
|
||
|
||
<p>在 Charts.yaml 里面,有两个和版本相关的字段,其中 version 指的是我们当前这个 chart 应用包的版本,而另外一个 appVersion 则指的是我们内部所使用的,比如说在这里面就是我们内部所使用的 Golang 这个程序,我们给它打一个 tag 这个版本。</p>
|
||
|
||
<h4>template 文件夹</h4>
|
||
|
||
<p><img src="assets/FtiTc_vfrsua9Bp_DAugf1XmlFhp" alt="avatar" /></p>
|
||
|
||
<p>在 templates 这个文件夹下,则是存放各种部署应用所需要的 yaml 文件,比如说我们看到的 deployment 和 service。</p>
|
||
|
||
<p>我们当前的应用实际上只需要一个 deployment,但是有的应用可能包含多个组件,此时就需要在这个 templates 文件夹下放 deploymentA、deploymentB 等多个 yaml 文件。有时候我们还需要去配置一些 service account secret volume 的内容,也是在这里面去添加相应的内容。</p>
|
||
|
||
<p>在 templates 文件夹下,这个配置文件可以只是单纯的 Kubernetes.yaml 配置文件,也可以是配置文件的模板,比如说在这看到 deployment.yaml 文件里面,有很多以 {{ }} 包裹起来的变量,这些以 values 或者是 chart 开头的这些变量,都是从根目录下的 chart.yaml 以及 values.yaml 中获取出来的。</p>
|
||
|
||
<p><img src="assets/FoN7_vx878Ljhs5xukVQM3C8_Ear" alt="avatar" /></p>
|
||
|
||
<p>如上图所示,看到 replicaCount 实际上就是我们所要部署的副本数量,而 repository 则是指定了镜像的位置。我们之前在 docker 镜像构建中并没有设置 username 的环境变量,这里也是通过类似的方式暴露在了 values.yaml 里面。</p>
|
||
|
||
<p>Helm 在安装应用的时候,实际上会先去渲染 templates 这个文件夹下的模板文件,将所需要的变量都填入进去,然后再使用渲染后的 kubernetes.yaml 文件进行一个部署,而我们在创建这个 Helm Chart 的过程中,实际上并不需要考虑太多如何去渲染,因为 Helm 已经在客户端安装应用的时候帮我们把这些事情都完成了。</p>
|
||
|
||
<h4>校验与打包</h4>
|
||
|
||
<p><img src="assets/FjyvOU9o7XaU46Mh5vhJYt_KoZ7v" alt="avatar" /></p>
|
||
|
||
<p>在我们准备好这些应用后,就可以使用 helm lint 命令,来粗略检查一下我们制作的这个 chart 有没有语法上的错误,如果没有问题的话,就可以使用 Helm package 命令,对我们的 chart 文件包进行一个打包。打包之后,我们就可以得到一个 tar 的应用包了,这个就是我们所要发布的一个应用。</p>
|
||
|
||
<h4>安装测试</h4>
|
||
|
||
<p><img src="assets/Ftca-nUBwx_Fy1wvKF_eif_R9G23" alt="avatar" /></p>
|
||
|
||
<p>我们可以使用 Helm install 这个命令来尝试安装一下刚刚做好的应用包,然后使用 kubectl 来查看一下 pod 的运行状态,同样可以通过 port-forward 命令来把这个 pod 的端口映射到本地的端口上,这样就可以通过本地的这个 localhost 来访问到刚刚部署好的这个应用了。</p>
|
||
|
||
<h4>参数覆盖</h4>
|
||
|
||
<p><img src="assets/Fj0CeyV8ku5wg16mzZwIW--vvqtv" alt="avatar" /></p>
|
||
|
||
<p>有的同学可能会有疑惑:虽然我们应用开发者把这些可配置的信息都暴露在了 values.yanl 里面,用户使用应用的时候,如果想要修改该怎么办呢?这个答案其实也很简单,用户只需要在 install 的时候使用这个 set 参数设置,把想要设置的参数覆盖掉就行了。</p>
|
||
|
||
<p>同样,如果用户编写自己的 my-values.yaml 文件,也可以把这个文件在 install 的时候设置起来,这样的话,文件中的参数会覆盖掉原有的一些参数。如果用户不想重新去 install 一个新的 app,而是想要升级原来的 app,他也只需要用这个 helm upgrade 的命令把这个 Helm install 这个命令替换掉就可以了。</p>
|
||
|
||
<h4>修改 NOTES.txt</h4>
|
||
|
||
<p><img src="assets/FpUjnQVVQV8mzneN7bBeCLsMSRNH" alt="avatar" /></p>
|
||
|
||
<p>细心的同学可能会注意到,之前在执行 Helm install 的命令后,这个提示信息其实是有一些问题的,我们看一下之前所写的 deployment.yaml 这个文件,里面可以看到,两个 label 其实是有一定出入的,这个提示信息其实就是在 templates 的 notes 文件下,所以我们只需要到这个文件里面去把这里面的相应信息修改一下就可以了。</p>
|
||
|
||
<h4>升级版本</h4>
|
||
|
||
<p><img src="assets/FsmUV512mtq1-LByVUuUdOEXKwyq" alt="avatar" /></p>
|
||
|
||
<p>接下来我们回到 chart.yaml 的文件中,更新一下这个 version 字段,重新做一个打包,这样我们就可以对原来部署好的应用做这样一个版本升级了。</p>
|
||
|
||
<h4>应用上传</h4>
|
||
|
||
<p><img src="assets/FgEMgu2zmJm06MshWXVMz7mwAWaV" alt="avatar" /></p>
|
||
|
||
<p>制作完成的这个应用应该如何和其他人做分享呢?</p>
|
||
|
||
<p>Helm 官方是通过了 CHARTMUSEUM 这样一个工具,用这个工具,大家可以来构建自己的 chart 仓库,但是自己维护一个 chart 成本会比较高,另外对于使用户而言,如果它使用每一个应用的开发者都有自己的一个仓库的话,那这个用户他就需要去把这些所有的仓库都加入到自己的检索列表里面,这个非常麻烦,非常不利于应用的传播和分享。</p>
|
||
|
||
<h2>三、开放云原生应用中心</h2>
|
||
|
||
<h3>应用来源</h3>
|
||
|
||
<p><img src="assets/FnazvWuaUJi6rkcIhqUNi9VS-ND4" alt="avatar" /></p>
|
||
|
||
<p>我们团队最近推出了一个开放云原生应用中心:**Cloud Native App Hub。**在这里面,我们同步了各种非常流行的应用,同时还提供了一个开发者上传自己应用的一个渠道。</p>
|
||
|
||
<h3>提交应用</h3>
|
||
|
||
<p>在开放云原生应用中心,应用主要是来自两个渠道:</p>
|
||
|
||
<ul>
|
||
|
||
<li>一方面我们会定期从一些国外知名的 Helm 仓库同步 chart 资源,同时将其内部使用的一些 docker 镜像也一并做这样的替换。</li>
|
||
|
||
<li>另一方面,我们和 Helm 官方库一样,在 GitHub 上,也接受开发者通过 push request 的形式提交自己的应用。</li>
|
||
|
||
</ul>
|
||
|
||
<p><img src="assets/Fgg7QwtE8dAcRZkVKjLnYf9IOPmD" alt="avatar" /></p>
|
||
|
||
<p>感兴趣的同学可以移步我们的云原生应用中心位于 GitHub 上的 chart 仓库,仿照刚才所讲的 chart 制作流程创作自己的 chart,然后提交 push request。</p>
|
||
|
||
<h2>结束语</h2>
|
||
|
||
<p>最后欢迎大家使用 Helm 和阿里云来进行云原生应用的开发。如果有问题或者希望深入交流讨论的同学,可以扫码加入我们的 Kubernetes 钉钉技术大群,和大牛们一起探索技术。今天的介绍就到这里,谢谢大家。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/14 Kubernetes Service(溪恒).md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/CNCF X 阿里巴巴云原生技术公开课/16 深入解析 Linux 容器(华敏).md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996ea40d633d60","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>
|
||
|