mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
649 lines
23 KiB
HTML
649 lines
23 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>03 宏观认识:整体架构.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/01 开篇: Kubernetes 是什么以及为什么需要它.md.html">01 开篇: Kubernetes 是什么以及为什么需要它.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/02 初步认识:Kubernetes 基础概念.md.html">02 初步认识:Kubernetes 基础概念.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a class="current-tab" href="/专栏/Kubernetes 从上手到实践/03 宏观认识:整体架构.md.html">03 宏观认识:整体架构.md.html</a>
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/04 搭建 Kubernetes 集群 - 本地快速搭建.md.html">04 搭建 Kubernetes 集群 - 本地快速搭建.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/05 动手实践:搭建一个 Kubernetes 集群 - 生产可用.md.html">05 动手实践:搭建一个 Kubernetes 集群 - 生产可用.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/06 集群管理:初识 kubectl.md.html">06 集群管理:初识 kubectl.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/07 集群管理:以 Redis 为例-部署及访问.md.html">07 集群管理:以 Redis 为例-部署及访问.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/08 安全重点 认证和授权.md.html">08 安全重点 认证和授权.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/09 应用发布:部署实际项目.md.html">09 应用发布:部署实际项目.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/10 应用管理:初识 Helm.md.html">10 应用管理:初识 Helm.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/11 部署实践:以 Helm 部署项目.md.html">11 部署实践:以 Helm 部署项目.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/12 庖丁解牛:kube-apiserver.md.html">12 庖丁解牛:kube-apiserver.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/13 庖丁解牛:etcd.md.html">13 庖丁解牛:etcd.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/14 庖丁解牛:controller-manager.md.html">14 庖丁解牛:controller-manager.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/15 庖丁解牛:kube-scheduler.md.html">15 庖丁解牛:kube-scheduler.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/16 庖丁解牛:kubelet.md.html">16 庖丁解牛:kubelet.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/17 庖丁解牛:kube-proxy.md.html">17 庖丁解牛:kube-proxy.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/18 庖丁解牛:Container Runtime (Docker).md.html">18 庖丁解牛:Container Runtime (Docker).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/19 Troubleshoot.md.html">19 Troubleshoot.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/20 扩展增强:Dashboard.md.html">20 扩展增强:Dashboard.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/21 扩展增强:CoreDNS.md.html">21 扩展增强:CoreDNS.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/22 服务增强:Ingress.md.html">22 服务增强:Ingress.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/23 监控实践:对 K8S 集群进行监控.md.html">23 监控实践:对 K8S 集群进行监控.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/24 总结.md.html">24 总结.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 宏观认识:整体架构</h1>
|
||
|
||
<p>工欲善其事,必先利其器。本节我们来从宏观上认识下 K8S 的整体架构,以便于后续在此基础上进行探索和实践。</p>
|
||
|
||
<h2>C/S 架构</h2>
|
||
|
||
<p>从更高层来看,K8S 整体上遵循 C/S 架构,从这个角度来看,可用下面的图来表示其结构:</p>
|
||
|
||
<pre><code> +-------------+
|
||
|
||
| |
|
||
|
||
| | +---------------+
|
||
|
||
| | +-----> | Node 1 |
|
||
|
||
| Kubernetes | | +---------------+
|
||
|
||
+-----------------+ | Server | |
|
||
|
||
| CLI | | | | +---------------+
|
||
|
||
| (Kubectl) |----------->| ( Master ) |<------+-----> | Node 2 |
|
||
|
||
| | | | | +---------------+
|
||
|
||
+-----------------+ | | |
|
||
|
||
| | | +---------------+
|
||
|
||
| | +-----> | Node 3 |
|
||
|
||
| | +---------------+
|
||
|
||
+-------------+
|
||
|
||
</code></pre>
|
||
|
||
<p>左侧是一个官方提供的名为 <code>kubectl</code> 的 CLI (Command Line Interface)工具,用于使用 K8S 开放的 API 来管理集群和操作对象等。</p>
|
||
|
||
<p>右侧则是 K8S 集群的后端服务及开放出的 API 等。根据上一节的内容,我们知道 Node 是用于工作的机器,而 Master 是一种角色(Role),表示在这个 Node 上包含着管理集群的一些必要组件。具体组件的详细介绍参考第 11 小节对各组件的详细剖析。</p>
|
||
|
||
<p>当然在这里,只画出了一个 Master,在生产环境中,为了保障集群的高可用,我们通常会部署多个 Master 。</p>
|
||
|
||
<h2>Master</h2>
|
||
|
||
<p>下面我们来逐层分解, 首先是 Master ,这里我们只介绍其管理集群的相关组件。Master 是整个 K8S 集群的“大脑”,与大脑类似,它有几个重要的功能:</p>
|
||
|
||
<ul>
|
||
|
||
<li>接收:外部的请求和集群内部的通知反馈</li>
|
||
|
||
<li>发布:对集群整体的调度和管理</li>
|
||
|
||
<li>存储:存储</li>
|
||
|
||
</ul>
|
||
|
||
<p>这些功能,也通过一些组件来共同完成,通常情况下,我们将其称为 control plane 。如下图所示:</p>
|
||
|
||
<pre><code>+----------------------------------------------------------+
|
||
|
||
| Master |
|
||
|
||
| +-------------------------+ |
|
||
|
||
| +------->| API Server |<--------+ |
|
||
|
||
| | | | | |
|
||
|
||
| v +-------------------------+ v |
|
||
|
||
| +----------------+ ^ +--------------------+ |
|
||
|
||
| | | | | | |
|
||
|
||
| | Scheduler | | | Controller Manager | |
|
||
|
||
| | | | | | |
|
||
|
||
| +----------------+ v +--------------------+ |
|
||
|
||
| +------------------------------------------------------+ |
|
||
|
||
| | | |
|
||
|
||
| | Cluster state store | |
|
||
|
||
| | | |
|
||
|
||
| +------------------------------------------------------+ |
|
||
|
||
+----------------------------------------------------------+
|
||
|
||
</code></pre>
|
||
|
||
<p>它主要包含以下几个重要的组成部分。</p>
|
||
|
||
<h3>Cluster state store</h3>
|
||
|
||
<p>存储集群所有需持久化的状态,并且提供 watch 的功能支持,可以快速的通知各组件的变更等操作。</p>
|
||
|
||
<p>因为目前 Kubernetes 的存储层选择是 etcd ,所以一般情况下,大家都直接以 etcd 来代表集群状态存储服务。即:将所有状态存储到 etcd 实例中。</p>
|
||
|
||
<p>刚才我们说 Master 相当于是 K8S 集群的大脑,更细化来看,etcd 则是大脑中的核心,为什么这么说?可以参考后面详细剖析的章节,本章我们先从更高的层次来看集群的整体架构。</p>
|
||
|
||
<p>你可能会问, etcd 是必须的吗?就目前而言,etcd 是必须的,这主要是 Kubernetes 的内部实现。</p>
|
||
|
||
<p>而早在 2014 年左右,社区就一直在提议将存储层抽象出来,后端的实际存储作为一种插件化的存在。<a href="https://github.com/kubernetes/kubernetes/issues/1957">呼声</a>比较大的是另一种提供 k/v 存储功能的 <a href="https://www.consul.io/">Consul</a> 。</p>
|
||
|
||
<p>不过得益于 etcd 的开发团队较为活跃,而且根据 K8S 社区的反馈做了相当大的改进,并且当时 K8S 团队主要的关注点也不在此,所以直到现在 etcd 仍不是一个可选项。</p>
|
||
|
||
<p>如果现在去看下 Kubernetes 的源代码,你会发现存储层的代码还比较简洁清晰,后续如果有精力也许将此处插件化也不是不可能。</p>
|
||
|
||
<h3>API Server</h3>
|
||
|
||
<p>这是整个集群的入口,类似于人体的感官,接收外部的信号和请求,并将一些信息写入到 etcd 中。</p>
|
||
|
||
<p>实际处理逻辑比三次握手简单的多:</p>
|
||
|
||
<ul>
|
||
|
||
<li>请求 API Server :“嗨,我有些东西要放到 etcd 里面”</li>
|
||
|
||
<li>API Server 收到请求:“你是谁?我为啥要听你的”</li>
|
||
|
||
<li>从请求中,拿出自己的身份凭证(一般是证书):“是我啊,你的master,给我把这些东西放进去”</li>
|
||
|
||
<li>这时候就要看是些什么内容了,如果这些内容 API Server 能理解,那就放入 etcd 中 “好的 master 我放进去了”;如果不能理解,“抱歉 master 我理解不了”</li>
|
||
|
||
</ul>
|
||
|
||
<p>可以看到,它提供了认证相关的功能,用于判断是否有权限进行操作。当然 API Server 支持多种认证方法,不过一般情况下,我们都使用 x509 证书进行认证。</p>
|
||
|
||
<p>API Server 的目标是成为一个极简的 server,只提供 REST 操作,更新 etcd ,并充当着集群的网关。至于其他的业务逻辑之类的,通过插件或者在其他组件中完成。关于这部分的详细实现,可以参考后面的 API Server 剖析相关章节。</p>
|
||
|
||
<h3>Controller Manager</h3>
|
||
|
||
<p>Controller Manager 大概是 K8S 集群中最繁忙的部分,它在后台运行着许多不同的控制器进程,用来调节集群的状态。</p>
|
||
|
||
<p>当集群的配置发生变更,控制器就会朝着预期的状态开始工作。</p>
|
||
|
||
<h3>Scheduler</h3>
|
||
|
||
<p>顾名思义,Scheduler 是集群的调度器,它会持续的关注集群中未被调度的 Pod ,并根据各种条件,比如资源的可用性,节点的亲和性或者其他的一些限制条件,通过绑定的 API 将 Pod 调度/绑定到 Node 上。</p>
|
||
|
||
<p>在这个过程中,调度程序一般只考虑调度开始时, Node 的状态,而不考虑在调度过程中 Node 的状态变化 (比如节点亲和性等,截至到目前 v1.11.2 也暂未加入相关功能的稳定特性)</p>
|
||
|
||
<h2>Node</h2>
|
||
|
||
<p>Node 的概念我们在上节已经提过了,这里不再过多赘述,简单点理解为加入集群中的机器即可。</p>
|
||
|
||
<p>那 Node 是如何加入集群接受调度,并运行服务的呢?这都要归功于运行在 Node 上的几个核心组件。我们先来看下整体结构:</p>
|
||
|
||
<pre><code>+--------------------------------------------------------+
|
||
|
||
| +---------------------+ +---------------------+ |
|
||
|
||
| | kubelet | | kube-proxy | |
|
||
|
||
| | | | | |
|
||
|
||
| +---------------------+ +---------------------+ |
|
||
|
||
| +----------------------------------------------------+ |
|
||
|
||
| | Container Runtime (Docker) | |
|
||
|
||
| | +---------------------+ +---------------------+ | |
|
||
|
||
| | |Pod | |Pod | | |
|
||
|
||
| | | +-----+ +-----+ | |+-----++-----++-----+| | |
|
||
|
||
| | | |C1 | |C2 | | ||C1 ||C2 ||C3 || | |
|
||
|
||
| | | | | | | | || || || || | |
|
||
|
||
| | | +-----+ +-----+ | |+-----++-----++-----+| | |
|
||
|
||
| | +---------------------+ +---------------------+ | |
|
||
|
||
| +----------------------------------------------------+ |
|
||
|
||
+--------------------------------------------------------+
|
||
|
||
</code></pre>
|
||
|
||
<h3>Kubelet</h3>
|
||
|
||
<p>Kubelet 实现了集群中最重要的关于 Node 和 Pod 的控制功能,如果没有 Kubelet 的存在,那 Kubernetes 很可能就只是一个纯粹的通过 API Server CRUD 的应用程序。</p>
|
||
|
||
<p>K8S 原生的执行模式是操作应用程序的容器,而不像传统模式那样,直接操作某个包或者是操作某个进程。基于这种模式,可以让应用程序之间相互隔离,互不影响。此外,由于是操作容器,所以应用程序可以说和主机也是相互隔离的,毕竟它不依赖于主机,在任何的容器运行时(比如 Docker)上都可以部署和运行。</p>
|
||
|
||
<p>我们在上节介绍过 Pod,Pod 可以是一组容器(也可以包含存储卷),K8S 将 Pod 作为可调度的基本单位, 分离开了构建时和部署时的关注点:</p>
|
||
|
||
<ul>
|
||
|
||
<li>构建时,重点关注某个容器是否能正确构建,如何快速构建</li>
|
||
|
||
<li>部署时,关心某个应用程序的服务是否可用,是否符合预期,依赖的相关资源是否都能访问到</li>
|
||
|
||
</ul>
|
||
|
||
<p>这种隔离的模式,可以很方便的将应用程序与底层的基础设施解耦,极大的提高集群扩/缩容,迁移的灵活性。</p>
|
||
|
||
<p>在前面,我们提到了 Master 节点的 <code>Scheduler</code> 组件,它会调度未绑定的 Pod 到符合条件的 Node 上,而至于最终该 Pod 是否能运行于 Node 上,则是由 <code>Kubelet</code> 来裁定的。关于 Kubelet 的具体原理,后面有详细剖析的章节。</p>
|
||
|
||
<h3>Container runtime</h3>
|
||
|
||
<p>容器运行时最主要的功能是下载镜像和运行容器,我们最常见的实现可能是 <a href="https://www.docker.com/">Docker</a> , 目前还有其他的一些实现,比如 <a href="https://github.com/rkt/rkt">rkt</a>, <a href="https://github.com/kubernetes-sigs/cri-o">cri-o</a>。</p>
|
||
|
||
<p>K8S 提供了一套通用的容器运行时接口 CRI (Container Runtime Interface), 凡是符合这套标准的容器运行时实现,均可在 K8S 上使用。</p>
|
||
|
||
<h3>Kube Proxy</h3>
|
||
|
||
<p>我们都知道,想要访问某个服务,那要么通过域名,要么通过 IP。而每个 Pod 在创建后都会有一个虚拟 IP,K8S 中有一个抽象的概念,叫做 <code>Service</code> ,<code>kube-proxy</code> 便是提供一种代理的服务,让你可以通过 <code>Service</code> 访问到 Pod。</p>
|
||
|
||
<p>实际的工作原理是在每个 Node 上启动一个 <code>kube-proxy</code> 的进程,通过编排 <code>iptables</code> 规则来达到此效果。深入的解析,在后面有对应的章节。</p>
|
||
|
||
<h2>总结</h2>
|
||
|
||
<p>本节中,我们了解到了 K8S 的整体遵循 C/S 架构,集群的 Master 包含着几个重要的组成部分,比如 <code>API Server</code>, <code>Controller Manager</code> 等。</p>
|
||
|
||
<p>而 Node 上,则运行着三个必要的组件 <code>kubelet</code>, <code>container runtime</code> (一般是 Docker), <code>kube-proxy</code> 。</p>
|
||
|
||
<p>通过所有组件的分工协作,最终实现了 K8S 对容器的编排和调度。</p>
|
||
|
||
<p>完成了这节的学习,那我们就开始着手搭建一个属于我们自己的集群吧。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/02 初步认识:Kubernetes 基础概念.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Kubernetes 从上手到实践/04 搭建 Kubernetes 集群 - 本地快速搭建.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":"70997230b8393d60","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>
|
||
|