拉巴力的纸皮箱


  • 首页

  • 标签

  • 归档

  • 关于

  • 搜索

Kubernetes学习笔记

发表于 2021-07-13

整体架构





  • 图来源:k8s-整体概述和架构

关键名词

  • CNCF(Cloud Native Computing Foundation,云原生计算基金会)

关键总结

  1. 自动化
  2. Docker是其目前支持的底层容器技术之一
  3. 服务弹性扩容机制应对突发流量
  4. Kubernetes视为云计算时代的操作系统
  5. k8s解决进程(服务)部署、容器编排的问题,service mesh解决进程(服务)通信的问题

核心组件

Kubernetes API Server

  • Kubernetes API Server的核心功能是提供Kubernetes各类资源对象(如Pod、RC、Service等)的增、删、改、查及Watch等HTTP Rest接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽,是整个系统的数据总线和数据中心。除此之外,它还有以下一些功能特性。
    1. 是集群管理的API入口。
    2. 是资源配额控制的入口。
    3. 提供了完备的集群安全机制。
  • 可以通过命令行工具kubectl来与Kubernetes API Server交互,它们之间的接口是RESTful API;另一种方式是通过编程方式调用Kubernetes API Server。
  • Kubernetes API Server本身也是一个Service,它的名称就是kubernetes,并且它的Cluster IP地址是Cluster IP地址池里的第1个地址。
  • 由于API Server是Kubernetes集群数据的唯一访问入口,因此安全性与高性能就成为API Server设计和实现的两大核心目标。通过采用HTTPS安全传输通道与CA签名数字证书强制双向认证的方式,API Server的安全性得以保障。此外,为了更细粒度地控制用户或应用对Kubernetes资源对象的访问权限,Kubernetes启用了RBAC访问控制策略。
  • API层:主要以REST方式提供各种API接口,除了有Kubernetes资源对象的CRUD和Watch等主要API,还有健康检查、UI、日志、性能指标等运维监控相关的API。
  • etcd数据库:用于持久化存储Kubernetes资源对象的KV数据库。
  • 借助etcd提供的Watch API接口,API Server可以监听(Watch)在etcd上发生的数据操作事件,比如Pod创建事件、更新事件、删除事件等,在这些事件发生后,etcd会及时通知API Server。
  • 为了让Kubernetes中的其他组件在不访问底层etcd数据库的情况下,也能及时获取资源对象的变化事件,API Server模仿etcd的Watch API接口提供了自己的Watch接口,这样一来,这些组件就能近乎实时地获取它们感兴趣的任意资源对象的相关事件通知了。
  • Kubernetes API Server最主要的REST接口是资源对象的增、删、改、查接口,除此之外,它还提供了一类很特殊的REST接口——Kubernetes Proxy API接口,这类接口的作用是代理REST请求,即Kubernetes API Server把收到的REST请求转发到某个Node上的kubelet守护进程的REST端口,由该kubelet进程负责响应。
  • Kubernetes Proxy API里关于Node的相关接口、关于Pod的相关接口
  • 在Kubernetes集群之外访问某个Pod容器的服务(HTTP服务)时,可以用Proxy API实现,这种场景多用于管理目的,比如逐一排查Service的Pod副本,检查哪些Pod的服务存在异常。Proxy API也有Service的Proxy接口,其接口定义与Pod的接口定义基本一样:/api/v1/proxy/namespaces/{namespace}/services/{name}。

Controller Manager

  • Kubernetes集群中,每个Controller都是这样的一个“操作系统”,它们通过API Server提供的(List-Watch)接口实时监控集群中特定资源的状态变化,当发生各种故障导致某资源对象的状态发生变化时,Controller会尝试将其状态调整为期望的状态。
  • Controller Manager是Kubernetes中各种操作系统的管理者,是集群内部的管理控制中心,也是Kubernetes自动化功能的核心。

kubelet

  • kubelet进程在启动时通过API Server注册自身的节点信息,并定时向API Server汇报状态信息,API Server在接收到这些信息后,会将这些信息更新到etcd中。在etcd中存储的节点信息包括节点健康状况、节点资源、节点名称、节点地址信息、操作系统版本、Docker版本、kubelet版本等。
  • 在Kubernetes集群中,在每个Node(又称Minion)上都会启动一个kubelet服务进程。该进程用于处理Master下发到本节点的任务,管理Pod及Pod中的容器。每个kubelet进程都会在API Server上注册节点自身的信息,定期向Master汇报节点资源的使用情况,并通过cAdvisor监控容器和节点资源。

kube-proxy

  • 每个Node上的kube-proxy进程获取每个Service的Endpoints,实现了Service的负载均衡功能。
  • Service只是一个概念,而真正将Service的作用落实的是它背后的kube-proxy服务进程
  • 在Kubernetes集群的每个Node上都会运行一个kube-proxy服务进程,我们可以把这个进程看作Service的透明代理兼负载均衡器,其核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。
  • Service的Cluster IP与NodePort等概念是kube-proxy服务通过iptables的NAT转换实现的,kube-proxy在运行过程中动态创建与Service相关的iptables规则,这些规则实现了将访问服务(Cluster IP或NodePort)的请求负载分发到后端Pod的功能。由于iptables机制针对的是本地的kube-proxy端口,所以在每个Node上都要运行kube-proxy组件,这样一来,在Kubernetes集群内部,我们可以在任意Node上发起对Service的访问请求。综上所述,由于kube-proxy的作用,在Service的调用过程中客户端无须关心后端有几个Pod,中间过程的通信、负载均衡及故障恢复都是透明的。
  • kube-proxy进程转发Service流量方式
    1. userspace(用户空间代理)模式。(是一个真实的TCP/UDP代理,类似HA Proxy,负责从Service到Pod的访问流量的转发。当某个Pod以Cluster IP方式访问某个Service的时候,这个流量会被Pod所在本机的iptables转发到本机的kube-proxy进程,然后由kube-proxy建立起到后端Pod的TCP/UDP连接,随后将请求转发到某个后端Pod上,并在这个过程中实现负载均衡功能。)
    2. iptables模式。Kubernetes从1.2版本开始,将iptables作为kube-proxy的默认模式。iptables模式下的kube-proxy不再起到Proxy的作用,其核心功能:通过API Server的Watch接口实时跟踪Service与Endpoint的变更信息,并更新对应的iptables规则,Client的请求流量则通过iptables的NAT机制“直接路由”到目标Pod。根据Kubernetes的网络模型,一个Node上的Pod与其他Node上的Pod应该能够直接建立双向的TCP/IP通信通道,所以如果直接修改iptables规则,则也可以实现kube-proxy的功能,只不过后者更加高端,因为是全自动模式的。与第1代的userspace模式相比,iptables模式完全工作在内核态,不用再经过用户态的kube-proxy中转,因而性能更强。
    3. IPVS模式。iptables模式虽然实现起来简单,但存在无法避免的缺陷:在集群中的Service和Pod大量增加以后,iptables中的规则会急速膨胀,导致性能显著下降,在某些极端情况下甚至会出现规则丢失的情况,并且这种故障难以重现与排查,于是Kubernetes从1.8版本开始引入第3代的IPVS(IP Virtual Server)模式,如图5.16所示。IPVS在Kubernetes 1.11中升级为GA稳定版。
      iptables与IPVS虽然都是基于Netfilter实现的,但因为定位不同,二者有着本质的差别:iptables是为防火墙而设计的;IPVS则专门用于高性能负载均衡,并使用更高效的数据结构(Hash表),允许几乎无限的规模扩张,因此被kube-proxy采纳为第三代模式。在IPVS模式下,kube-proxy又做了重要的升级,即使用iptables的扩展ipset,而不是直接调用iptables来生成规则链。ipset则引入了带索引的数据结构,因此当规则很多时,也可以很高效地查找和匹配。

Scheduler

  • Kubernetes Scheduler在整个系统中承担了“承上启下”的重要功能,“承上”是指它负责接收Controller Manager创建的新Pod,为其安排一个落脚的“家”——目标Node;“启下”是指安置工作完成后,目标Node上的kubelet服务进程接管后继工作,负责Pod生命周期中的“下半生”。
  • 在整个调度过程中涉及三个对象,分别是待调度Pod列表、可用Node列表,以及调度算法和策略。

命令

kubectl

  1. 创建RC: kubectl create -f mysql-rc.yaml; kubectl get rc
  2. 创建Service:kubectl create -f mysql-svc.yaml; kubectl get svc
  3. 查看在集群中有多少个Node:kubectl get nodes
  4. 查看某个Node的详细信息:kubectl describe node
  5. 修改RC的副本数量,来实现Pod的动态缩放(Scaling):kubectl scale rc redis-slave --replicas=3
  6. 创建Deployment:kubectl create -f tomcat-deployment.yaml
  7. 查看Deployment的信息: kubectl get deployments
  8. 查看对应的Replica Set: kubectl get rs
  9. 查看创建的Pod:kubectl get pods
  10. 查看Deployment的更新过程:kubectl rollout status
  11. 检查Deployment部署的历史记录:kubectl rollout history

Kubernetes

  • Kubernetes里的3种IP,这3种IP分别如下
    • Node IP:Node的IP地址。
    • Pod IP:Pod的IP地址。
    • Cluster IP:Service的IP地址。
  • node IP是Kubernetes集群中每个节点的物理网卡的IP地址,是一个真实存在的物理网络。,所有属于这个网络的服务器都能通过这个网络直接通信,不管其中是否有部分节点不属于这个Kubernetes集群。这也表明在Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务时,都必须通过Node IP通信。(未打通Pod IP网络的情况下)
  • Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络。Kubernetes要求位于不同Node上的Pod都能够彼此直接通信,所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器时,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量是通过Node IP所在的物理网卡流出的。
  • Service的Cluster IP,它也是一种虚拟的IP,但更像一个“伪造”的IP网络,原因有以下几点。
    • Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)。
    • Cluster IP无法被Ping,因为没有一个“实体网络对象”来响应。
    • Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCP/IP通信的基础,并且它们属于Kubernetes集群这样一个封闭的空间,集群外的节点如果要访问这个通信端口,则需要做一些额外的工作。
    • 在Kubernetes集群内,Node IP网、Pod IP网与Cluster IP网之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊路由规则,与我们熟知的IP路由有很大的不同。
    • Service的Cluster IP属于Kubernetes集群内部的地址,无法在集群外部直接使用这个地址。

ConfigMap

  • 把所有的配置项都当作key-value字符串,当然value可以来自某个文本文件,比如配置项password=123456、user=root、host=192.168.8.4用于表示连接FTP服务器的配置参数。这些配置项可以作为Map表中的一个项,整个Map的数据可以被持久化存储在Kubernetes的Etcd数据库中,然后提供API以方便Kubernetes相关组件或客户应用CRUD操作这些数据,上述专门用来保存配置参数的Map就是Kubernetes ConfigMap资源对象。
  • Kubernetes提供了一种内建机制,将存储在etcd中的ConfigMap通过Volume映射的方式变成目标Pod内的配置文件,不管目标Pod被调度到哪台服务器上,都会完成自动映射。进一步地,如果ConfigMap中的key-value数据被修改,则映射到Pod中的“配置文件”也会随之自动更新。于是,Kubernetes ConfigMap就成了分布式系统中最为简单(使用方法简单,但背后实现比较复杂)且对应用无侵入的配置中心。
  • ConfigMap供容器使用的典型用法如下。
    1. 生成为容器内的环境变量。
    2. 设置容器启动命令的启动参数(需设置为环境变量)。
    3. 以Volume的形式挂载为容器内部的文件或目录。
  • 不使用YAML文件,直接通过kubectl create configmap也可以创建ConfigMap,可以使用参数–from-file或–from-literal指定内容,并且可以在一行命令中指定多个参数。
  • 容器应用对ConfigMap的使用有以下两种方法。
    1. 通过环境变量获取ConfigMap中的内容。
    2. 通过Volume挂载的方式将ConfigMap中的内容挂载为容器内部的文件或目录。
  • Kubernetes从1.6版本开始,引入了一个新的字段envFrom,实现了在Pod环境中将ConfigMap(也可用于Secret资源对象)中所有定义的key=value自动生成为环境变量。

Master

  • 负责整个集群的管理和控制
  • 在Master上运行着以下关键进程
    • Kubernetes API Server(kube-apiserver)
    • Kubernetes Controller Manager(kube-controller-manager)
    • Kubernetes Scheduler(kube-scheduler)

Node

  • Pod运行在一个被称为节点(Node)的环境中,这个节点既可以是物理机,也可以是私有云或者公有云中的一个虚拟机,通常在一个节点上运行几百个Pod。
  • Node除了Master,Kubernetes集群中的其他机器被称为Node。与Master一样,Node可以是一台物理主机,也可以是一台虚拟机。
  • 每个Node上都运行着以下关键进程
    • kubelet
    • kube-proxy
    • Docker Engine(docker)
  • Node可以在运行期间动态增加到Kubernetes集群中,在默认情况下kubelet会向Master注册自己,这也是Kubernetes推荐的Node管理方式。

Pod

  • 由一组容器组成
  • 每个Pod都有一个特殊的被称为“根容器”的Pause容器
  • Kubernetes为每个Pod都分配了唯一的IP地址,称之为Pod IP,一个Pod里的多个容器共享Pod IP地址。
  • Kubernetes要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通信,一个Pod里的容器与另外主机上的Pod容器能够直接通信。
  • Pod其实有两种类型:普通的Pod及静态Pod(Static Pod)。
  • Pod的IP加上这里的容器端口(containerPort),组成了一个新的概念——Endpoint,它代表此Pod里的一个服务进程的对外通信地址。(一个Pod也存在具有多个Endpoint的情况)
  • 在Kubernetes里,一个计算资源进行配额限定时需要设定以下两个参数。
    • Requests:该资源的最小申请量,系统必须满足要求。
    • Limits:该资源最大允许使用的量,不能被突破,当容器试图使用超过这个量的资源时,可能会被Kubernetes“杀掉”并重启。通常,我们会把Requests设置为一个较小的数值,符合容器平时的工作负载情况下的资源需求,而把Limit设置为峰值负载情况下资源占用的最大量。
  • Pod的管理对象,除了RC和Deployment,还包括ReplicaSet、DaemonSet、StatefulSet、Job等,分别用于不同的应用场景中。
  • Pod定义详解YAML格式的Pod定义文件的完整内容
  • 静态Pod是由kubelet进行管理的仅存在于特定Node上的Pod
    • 创建静态Pod有两种方式:配置文件方式和HTTP方式。
  • 在容器内获取Pod信息(Downward API)
    • Downward API有什么价值呢?
    • 在某些集群中,集群中的每个节点都需要将自身的标识(ID)及进程绑定的IP地址等信息事先写入配置文件中,进程在启动时会读取这些信息,然后将这些信息发布到某个类似服务注册中心的地方,以实现集群节点的自动发现功能。
  • Pod的重启策略(RestartPolicy)包括Always、OnFailure和Never,默认值为Always。
    • kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算,例如1、2、4、8倍等,最长延时5min,并且在成功重启后的10min后重置该时间。
    • Pod的重启策略与控制方式息息相关,当前可用于管理Pod的控制器包括ReplicationController、Job、DaemonSet及直接通过kubelet管理(静态Pod)。每种控制器对Pod的重启策略要求如下。◎ RC和DaemonSet:必须设置为Always,需要保证该容器持续运行。◎ Job:OnFailure或Never,确保容器执行完成后不再重启。◎ kubelet:在Pod失效时自动重启它,不论将RestartPolicy设置为什么值,也不会对Pod进行健康检查。
  • Pod健康检查和服务可用性检查
    • Kubernetes对Pod的健康状态可以通过两类探针来检查: LivenessProbe和ReadinessProbe,kubelet定期执行这两类探针来诊断容器的健康状况。
    • LivenessProbe探针:用于判断容器是否存活(Running状态);ReadinessProbe探针:用于判断容器服务是否可用(Ready状态)
    • LivenessProbe和ReadinessProbe均可配置以下三种实现方式。
      1. ExecAction:在容器内部执行一个命令,如果该命令的返回码为0,则表明容器健康。
      2. TCPSocketAction:通过容器的IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。
      3. HTTPGetAction:通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康。
    • Kubernetes的ReadinessProbe机制可能无法满足某些复杂应用对容器内服务可用状态的判断,所以Kubernetes从1.11版本开始,引入Pod Ready++特性对Readiness探测机制进行扩展,在1.14版本时达到GA稳定版,称其为Pod Readiness Gates。通过Pod Readiness Gates机制,用户可以将自定义的ReadinessProbe探测方式设置在Pod上,辅助Kubernetes设置Pod何时达到服务可用状态(Ready)。
  • init container(初始化容器)与应用容器在本质上是一样的,但它们是仅运行一次就结束的任务,并且必须在成功执行完成后,系统才能继续执行下一个容器

Pod的核心:pause容器

  • Kubernetes的Pod抽象基于Linux的namespace和cgroups,为容器提供了隔离的环境。从网络的角度看,同一个Pod中的不同容器犹如运行在同一个主机上,可以通过localhost进行通信。
  • Docker容器非常适合部署单个软件单元。但是当你想要一起运行多个软件时,尤其是在一个容器里管理多个进程时,这种模式会变得有点麻烦。Kubernetes非常不建议“富容器”这种方式,认为将这些应用程序部署在部分隔离并且部分共享资源的容器组中更为有用。为此,Kubernetes为这种使用场景提供了一个称为Pod的抽象。
  • 原则上,任何人都可以配置Docker来控制容器组之间的共享级别——只需创建一个父容器,并创建与父容器共享资源的新容器,然后管理这些容器的生命周期。在Kubernetes中,pause容器被当作Pod中所有容器的“父容器”,并为每个业务容器提供以下功能:·在Pod中,它作为共享Linux namespace(Network、UTS等)的基础;·启用PID namespace共享,它为每个Pod提供1号进程,并收集Pod内的僵尸进程。
  • 这个pause容器运行一个非常简单的进程,它不执行任何功能,一启动就永远把自己阻塞住了(见pause()系统调用)。正如你看到的,它当然不会只知道“睡觉”。它执行另一个重要的功能——即它扮演PID 1的角色,并在子进程成为“孤儿进程”的时候,通过调用wait()收割这些僵尸子进程。这样就不用担心我们的Pod的PID namespace里会堆满僵尸进程了。这也是为什么Kubernetes不随便找个容器(例如Nginx)作为父容器,让用户容器加入的原因。

Pod的调度

  • 在Kubernetes平台上,我们很少会直接创建一个Pod,在大多数情况下会通过RC、Deployment、DaemonSet、Job等控制器完成对一组Pod副本的创建、调度及全生命周期的自动控制任务。
  • RC也出现了新的继任者——Deployment,用于更加自动地完成Pod副本的部署、版本更新、回滚等功能。RC的继任者其实并不是Deployment,而是ReplicaSet,因为ReplicaSet进一步增强了RC标签选择器的灵活性。
  • 与RC不同,ReplicaSet被设计成能控制多个不同标签的Pod副本。一种常见的应用场景是,应用MyApp目前发布了v1与v2两个版本,用户希望MyApp的Pod副本数保持为3个,可以同时包含v1和v2版本的Pod,就可以用ReplicaSet来实现这种控制
  • Kubernetes的滚动升级就是巧妙运用ReplicaSet的这个特性来实现的,同时,Deployment也是通过ReplicaSet来实现Pod副本自动控制功能的。我们不应该直接使用底层的ReplicaSet来控制Pod副本,而应该使用管理ReplicaSet的Deployment对象来控制副本,这是来自官方的建议。
  • 在真实的生产环境中的确也存在一种需求:希望某种Pod的副本全部在指定的一个或者一些节点上运行,比如希望将MySQL数据库调度到一个具有SSD磁盘的目标节点上,此时Pod模板中的NodeSelector属性就开始发挥作用了。
  • 如果NodeSelector选择的Label不存在或者不符合条件,比如这些目标节点此时宕机或者资源不足,该怎么办?如果要选择多种合适的目标节点,比如SSD磁盘的节点或者超高速硬盘的节点,该怎么办?Kubernates引入了NodeAffinity(节点亲和性设置)来解决该需求。
  • 与单独的Pod实例不同,由RC、ReplicaSet、Deployment、DaemonSet等控制器创建的Pod副本实例都是归属于这些控制器的,这就产生了一个问题:控制器被删除后,归属于控制器的Pod副本该何去何从?在Kubernates 1.9之前,在RC等对象被删除后,它们所创建的Pod副本都不会被删除;在Kubernates 1.9以后,这些Pod副本会被一并删除。如果不希望这样做,则可以通过kubectl命令的–cascade=false参数来取消这一默认特性。
  • Deployment或RC:全自动调度:Deployment或RC的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维持用户指定的副本数量。
  • 除了使用系统自动调度算法完成一组Pod的部署,Kubernetes也提供了多种丰富的调度策略,用户只需在Pod的定义中使用NodeSelector、NodeAffinity、PodAffinity、Pod驱逐等更加细粒度的调度策略设置,就能完成对Pod的精准调度。
  • 亲和性调度功能包括节点亲和性(NodeAffinity)和Pod亲和性(PodAffinity)两个维度的设置。NodeAffinity意为Node亲和性的调度策略,是用于替换NodeSelector的全新调度策略。亲和性的操作符也包括In、NotIn、Exists、DoesNotExist、Gt、Lt。

Pod的扩缩容

  • Kubernetes对Pod的扩缩容操作提供了手动和自动两种模式,手动模式通过执行kubectl scale命令或通过RESTful API对一个Deployment/RC进行Pod副本数量的设置,即可一键完成。自动模式则需要用户根据某个性能指标或者自定义业务指标,并指定Pod副本数量的范围,系统将自动在这个范围内根据性能指标的变化进行调整。
  • 自动扩缩容机制:HPA控制器基于Master的kube-controller-manager服务启动参数–horizontal-pod-autoscaler-sync-period定义的探测周期(默认值为15s),周期性地监测目标Pod的资源性能指标,并与HPA资源对象中的扩缩容条件进行对比,在满足条件时对Pod副本数量进行调整。
  • Kubernetes从1.11版本开始,弃用基于Heapster组件完成Pod的CPU使用率采集的机制,全面转向基于Metrics Server完成数据采集。从1.10版本开始,Kubernetes引入了对外部系统指标的支持。

Label

  • 一个Label是一个key=value的键值对,其中key与value由用户自己指定。Label可以被附加到各种资源对象上,例如Node、Pod、Service、RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上。Label通常在资源对象定义时确定,也可以在对象创建后动态添加或者删除。
  • 过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象
  • 有两种Label Selector表达式:基于等式的(Equality-based)和基于集合的(Set-based)
  • 使用Label可以给对象创建多组标签,Label和Label Selector共同构成了Kubernetes系统中核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性。

Annotation

  • Annotation(注解)与Label类似,也使用key/value键值对的形式进行定义。不同的是Label具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector。Annotation则是用户任意定义的附加信息,以便于外部工具查找。在很多时候,Kubernetes的模块自身会通过Annotation标记资源对象的一些特殊信息。通常来说,用Annotation来记录的信息如下。◎ build信息、release信息、Docker镜像信息等,例如时间戳、release id号、PR号、镜像Hash值、Docker Registry地址等。◎ 日志库、监控库、分析库等资源库的地址信息。◎ 程序调试工具信息,例如工具名称、版本号等。◎ 团队的联系信息,例如电话号码、负责人名称、网址等。

Service

  • Service是分布式集群架构的核心,一个Service对象拥有如下关键特征。
  • 拥有一个虚拟IP(Cluster IP、Service IP或VIP)和端口号。
  • 通过Label关联Pod和Service。
  • 并不是每个Pod和它里面运行的容器都能被映射到一个Service上,只有提供服务(无论是对内还是对外)的那组Pod才会被映射为一个服务。
  • 通常,Cluster IP是在Service创建后由Kubernetes系统自动分配的,其他Pod无法预先知道某个Service的Cluster IP地址,因此需要一个服务发现机制来找到这个服务。
  • Kubernetes的Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是通过Label Selector来实现无缝对接的。RC的作用实际上是保证Service的服务能力和服务质量始终符合预期标准。
  • 运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。
  • Service没有共用一个负载均衡器的IP地址,每个Service都被分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP。这样一来,每个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题。
  • Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址与之前旧Pod的不同。而Service一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内,它的Cluster IP不会发生改变。
  • Kubernetes提供了Headless Service,即不为Service设置ClusterIP(入口IP地址),仅通过Label Selector将后端的Pod列表返回给调用的客户端。
  • Service只是一个概念,而真正将Service的作用落实的是它背后的kube-proxy服务进程

服务发现

  • 每个Kubernetes中的Service都有唯一的Cluster IP及唯一的名称。
  • 如何通过Service的名称找到对应的Cluster IP。Kubernetes通过Add-On增值包引入了DNS系统,把服务名作为DNS域名,这样程序就可以直接使用服务名来建立通信连接了。目前,Kubernetes上的大部分应用都已经采用了DNS这种新兴的服务发现机制。
  • Service具有稳定的IP地址(区别于容器不固定的IP地址)和端口,并会在一组匹配的后端Pod之间提供负载均衡,匹配的条件就是Service的Label Selector与Pod的Labels相匹配。
  • Kubernetes的Service代表的是Kubernetes后端服务的入口,它主要包含服务的访问IP(虚IP)和端口,因此工作在L4。既然Service只存储服务入口信息,那如何关联后端Pod呢?前文已经提到Service通过Label Selector选择与之匹配的Pod。那么被Service选中的Pod,当它们运行且能对外提供服务后,Kubernetes的Endpoints Controller会生成一个新的Endpoints对象,记录Pod的IP和端口,这就解决了前文提到的后端实例健康检查问题。
  • Kubernetes会从集群的可用服务IP池中为每个新创建的服务分配一个稳定的集群内访问IP地址,称为Cluster IP。Kubernetes还会通过添加DNS条目为Cluster IP分配主机名。Cluster IP和主机名在集群内是独一无二的,并且在服务的整个生命周期内不会更改。只有将服务从集群中删除,Kubernetes才会释放Cluster IP和主机名。用户可以使用服务的Cluster IP或主机名访问正常运行的Pod。
  • Kubernetes使用Kube-proxy组件管理各服务与之后端Pod的连接,该组件在每个节点上运行。
  • Kube-proxy是一个基于出站流量的负载平衡控制器,它监控Kubernetes API Service并持续将服务IP(包括Cluster IP等)映射到运行状况良好的Pod,落实到主机上就是iptables/IPVS等路由规则。访问服务的IP会被这些路由规则直接DNAT到Pod IP,然后走底层容器网络送到对应的Pod。
  • 服务分配的Cluster IP是一个虚拟IP,刚接触Kubernetes Service的人经常犯的错误是试图ping这个IP,然后发现它没有任何响应。实际上,这个虚拟IP只有和它的port一起使用才有作用,直接访问该IP或者想访问该IP的其他端口都是徒劳。
  • Kubernetes Service能够支持TCP、UDP和SCTP三种协议,默认是TCP协议。
  • 当Service的后端Pod准备就绪后,Kubernetes会生成一个新的Endpoints对象,而且这个Endpoints对象和Service同名。
  • Service的三个port先来看一个最简单的Service定义:Service的几个port的概念很容易混淆,它们分别是port、targetPort和NodePort。port表示Service暴露的服务端口,也是客户端访问用的端口,例如Cluster IP:port是提供给集群内部客户访问Service的入口。需要注意的是,port不仅是Cluster IP上暴露的端口,还可以是external IP和Load Balancer IP。Service的port并不监听在节点IP上,即无法通过节点IP:port的方式访问Service。NodePort是Kubernetes提供给集群外部访问Service入口的一种方式(另一种方式是Load Balancer),所以可以通过Node IP:nodePort的方式提供集群外访问Service的入口。需要注意的是,我们这里说的集群外指的是Pod网段外,例如Kubernetes节点或因特网。targetPort很好理解,它是应用程序实际监听Pod内流量的端口,从port和NodePort上到来的数据,最终经过Kube-proxy流入后端Pod的targetPort进入容器。在配置服务时,可以选择定义port和targetPort的值重新映射其监听端口,这也被称为Service的端口重映射。Kube-proxy通过在节点上iptables规则管理此端口的重新映射过程。
  • Kubernetes Service有几种类型:Cluster IP、Load Balancer和NodePort。
  • 其中,Cluster IP是默认类型,自动分配集群内部可以访问的虚IP——Cluster IP。
  • Cluster IP的主要作用是方便集群内Pod到Pod之间的调用。
  • Cluster IP主要在每个node节点使用iptables,将发向Cluster IP对应端口的数据转发到后端Pod中
  • NodePort的实现机制是Kube-proxy会创建一个iptables规则,所有访问本地NodePort的网络包都会被直接转发至后端Port。
  • NodePort的问题集中体现在性能和可对宿主机端口占用方面。一旦服务多起来,NodePort在每个节点上开启的端口会变得非常庞大且难以维护
  • 最早的时候,Kubernetes采用了Docker曾经使用过的方法——环境变量。Kubelet创建每个Pod时,会把系统当前所有服务的IP和端口信息都通过环境变量的方式注入容器。这样Pod中的应用可以通过读取环境变量获取所需服务的地址信息。
  • 但这种方式的缺点也很明显:·容易环境变量洪泛,Docker启动参数过长会影响性能,甚至直接导致容器启动失败;·Pod想要访问的任何Service必须在Pod自己被创建之前创建,否则这些环境变量就不会被注入。更理想的方案是,应用能够直接使用服务的名字,不需要关心它实际的IP地址,中间的转换能够自动完成。名字和IP之间的转换即DNS,DNS的方式并没有以上两个限制。在Kubernetes中使用域名服务,即假设Service(my-svc)在namespace(my-ns)中,暴露名为http的TCP端口,那么在Kubernetes的DNS服务器中会生成两种记录,分别是A记录:域名(my-svc.my-ns)到Cluster IP的映射和SRV记录。
  • 所谓的无头(headless)Service即没有selector的Service。Servcie抽象了该如何访问Kubernetes Pod,也能够抽象其他类型的backend

Kubernetes Service的工作原理

  • 主要涉及的Kubernetes组件有Controller Manager(包括Service Controller和Endpoints Controller)和Kube-proxy
  • IPVS是LVS的负载均衡模块,亦基于netfilter,但比iptables性能更高,具备更好的可扩展性。
  • IPVS支持三种负载均衡模式:Direct Routing(简称DR)、Tunneling(也称ipip模式)和NAT(也称Masq模式)。
  • Kube-proxy实现的是分布式负载均衡,而非集中式负载均衡。何谓分布式负载均衡器呢?就是每个节点都充当一个负载均衡器,每个节点上都会被配置一模一样的转发规则。上文提到,受制于iptables的实现,iptables模式的转发策略底层实现其实就是随机法,即将请求随机地分配到各个后端Pod(可能在不同节点上)。由概率统计理论得知,随着客户端调用服务端次数的增加,其实际效果越来越接近评价分配,也就是轮询(rr)的结果。缺点也比较明显,就是没有考虑机器的性能问题。根据木桶原理,Service的性能瓶颈会受性能最差的节点影响。那么,支持多种Load Balancer算法的IPVS模式呢?例如,lc(最小连接数)策略能否奏效?受制于Kube-proxy的分布式负载均衡架构,恐怕很难。同一个后端Pod可能有不同的Kube-proxy把请求转发给它,因此任何一个Kube-proxy都无法准确估计其后端Pod的连接数,故最小连接数这种转发策略无法派上用场。不过,可以尝试IPVS模式的sed(最短时延)转发策略。

Ingress

  • Ingress Controller将Ingress入口地址和后端Pod地址的映射关系(规则)实时刷新到Load Balancer的配置文件中,再让负载均衡器重载(reload)该规则,便可实现服务的负载均衡和自动发现。
  • Kubernetes为什么要发明Ingress这个概念呢?笔者认为,其中一个重要的原因便是服务动态发现和负载均衡。在微服务的开发模式下,外部网络要通过域名、UR路径、负载均衡等转发到后端私有网络中,微服务之所以称为微,是因为它是动态变化的,它会经常被增加、删除或更新。传统的反向代理对服务动态变化的支持不是很方便,也就是服务变更后,不是很容易马上改变配置和热加载。Nginx Ingress Controller的出现就是为了解决这个问题,它可以时刻监听服务注册或服务编排API,随时感知后端服务变化,自动重新更改配置并重新热加载,期间服务不会暂停或停止,这对用户来说是无感知的。
  • 因为微服务架构及Kubernetes等编排工具最近几年才开始逐渐流行,所以一开始的反向代理服务器(例如Nginx和HA Proxy)并未提供对微服务的支持,才会出现Nginx Ingress Controller这种中间层做Kubernetes和负载均衡器(例如Nginx)之间的适配器(adapter)。Nginx Ingress Controller的存在就是为了与Kubernetes交互,同时刷新Nginx配置,还能重载Nginx。而号称云原生边界路由的Traefik设计得更彻底,首先它是个反向代理,其次原生提供了对Kubernetes的支持,也就是说,Traefik本身就能跟Kubernetes打交道,感知Kubernetes集群服务的更新。Traefik是原生支持Kubernetes Ingress的,因此用户在使用Traefik时无须再开发一套Nginx Ingress Controller,受到了广大运维人员的好评。相比Nginx和HA Proxy这类老古董,Traefik设计思想比较先进,有点“Envoy+Istio”降维打击Nginx的意思。

RC(Replication Controller)

  • 为需要扩容的Service关联的Pod创建一个RC(定义Pod副本数量,Label等)
  • RC是Kubernetes系统中的核心概念之一,简单来说,它其实定义了一个期望的场景,即声明某种Pod的副本数量在任意时刻都符合某个预期值。
  • RC的定义包括如下几个部分。
    • Pod期待的副本数量。
    • 用于筛选目标Pod的Label Selector。
    • 当Pod的副本数量小于预期数量时,用于创建新Pod的Pod模板(template)。
  • 定义了一个RC并将其提交到Kubernetes集群中后,Master上的Controller Manager组件就得到通知,定期巡检系统中当前存活的目标Pod,并确保目标Pod实例的数量刚好等于此RC的期望值。
  • 最佳的系统升级方式是旧版本的Pod每停止一个,就同时创建一个新版本的Pod,在整个升级过程中此消彼长,而运行中的Pod数量始终是10个,几分钟以后,当所有的Pod都已经是新版本时,系统升级完成。通过RC机制,Kubernetes很容易就实现了这种高级实用的特性,被称为“滚动升级”(Rolling Update)
  • 总结一下Replication Controller的职责,如下所述。
    1. 确保在当前集群中有且仅有N个Pod实例,N是在RC中定义的Pod副本数量。
    2. 通过调整RC的spec.replicas属性值来实现系统扩容或者缩容。
    3. 通过改变RC中的Pod模板(主要是镜像版本)来实现系统的滚动升级。

Replica Set

  • Replication Controller由于与Kubernetes代码中的模块Replication Controller同名,同时“Replication Controller”无法准确表达它的本意,所以在Kubernetes 1.2中,升级为另外一个新概念——Replica Set,官方解释其为“下一代的RC”。Replica Set与RC当前的唯一区别是,Replica Sets支持基于集合的Label selector(Set-based selector),而RC只支持基于等式的Label Selector(equality-based selector),这使得Replica Set的功能更强。

  • kubectl命令行工具适用于RC的绝大部分命令同样适用于Replica Set。此外,我们当前很少单独使用Replica Set,它主要被Deployment这个更高层的资源对象所使用,从而形成一整套Pod创建、删除、更新的编排机制。我们在使用Deployment时,无须关心它是如何创建和维护Replica Set的,这一切都是自动发生的。

  • Replica Set与Deployment这两个重要的资源对象逐步替代了之前RC的作用,是Kubernetes 1.3里Pod自动扩容(伸缩)这个告警功能实现的基础

  • 总结一下RC(Replica Set)的一些特性与作用。

    • 在大多数情况下,我们通过定义一个RC实现Pod的创建及副本数量的自动控制。
    • 在RC里包括完整的Pod定义模板。
    • RC通过Label Selector机制实现对Pod副本的自动控制。
    • 通过改变RC里的Pod副本数量,可以实现Pod的扩容或缩容。
    • 通过改变RC里Pod模板中的镜像版本,可以实现Pod的滚动升级。

Deployment

  • Deployment是Kubernetes在1.2版本中引入的新概念,用于更好地解决Pod的编排问题。为此,Deployment在内部使用了Replica Set来实现目的,无论从Deployment的作用与目的、YAML定义,还是从它的具体命令行操作来看,我们都可以把它看作RC的一次升级,两者的相似度超过90%。Deployment相对于RC的一个最大升级是我们可以随时知道当前Pod“部署”的进度。
  • Deployment的典型使用场景有以下几个。
    • 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建。
    • 检查Deployment的状态来看部署动作是否完成(Pod副本数量是否达到预期的值)。
    • 更新Deployment以创建新的Pod(比如镜像升级)。
    • 如果当前Deployment不稳定,则回滚到一个早先的Deployment版本。
    • 暂停Deployment以便于一次性修改多个PodTemplateSpec的配置项,之后再恢复Deployment,进行新的发布。
    • 扩展Deployment以应对高负载。
    • 查看Deployment的状态,以此作为发布是否成功的指标。
    • 清理不再需要的旧版本ReplicaSets。除了API声明与Kind类型等有所区别,Deployment的定义与Replica Set的定义很类似。
  • Pod的升级和回滚:如果Pod是通过Deployment创建的,则用户可以在运行时修改Deployment的Pod定义(spec.template)或镜像名称,并应用到Deployment对象上,系统即可完成Deployment的自动更新操作。如果在更新过程中发生了错误,则还可以通过回滚操作恢复Pod的版本。
    • 通过kubectl set image命令为Deployment设置新的镜像名称
    • 使用kubectl rollout status命令查看Deployment的更新过程
    • 用kubectl rollout history命令检查这个Deployment部署的历史记录
    • 撤销本次发布并回滚到上一个部署版本: kubectl rollout undo deployment/nginx-deployment
    • 使用–to-revision参数指定回滚到的部署版本号:kubectl rollout undo deployment/nginx-deployment –to-revision=2
    • 运行kubectl rolling-update命令完成Pod的滚动升级
    • 如果在更新过程中发现配置有误,则用户可以中断更新操作,并通过执行kubectl rolling- update –rollback完成Pod版本的回滚

RC、Deployment、ReplicaSet

StatefulSet

  • StatefulSet从本质上来说,可以看作Deployment/RC的一个特殊变种,它有如下特性。
    • StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。假设StatefulSet的名称为kafka,那么第1个Pod叫kafka-0,第2个叫kafka-1,以此类推。
    • StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态。
    • StatefulSet里的Pod采用稳定的持久化存储卷,通过PV或PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全)。
  • StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与Headless Service配合使用,即在每个StatefulSet定义中都要声明它属于哪个Headless Service。Headless Service与普通Service的关键区别在于,它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。
  • StatefulSet的更新策略Kubernetes从1.6版本开始,针对StatefulSet的更新策略逐渐向Deployment和DaemonSet的更新策略看齐,也将实现RollingUpdate、Paritioned和OnDelete这几种策略,以保证StatefulSet中各Pod有序地、逐个地更新,并且能够保留更新历史,也能回滚到某个历史版本。
  • 使用StatefulSet搭建MongoDB集群
    • 在创建StatefulSet之前,需要确保在Kubernetes集群中管理员已经创建好共享存储,并能够与StorageClass对接,以实现动态存储供应的模式。
    • 创建StatefulSet为了完成MongoDB集群的搭建,需要创建如下三个资源对象。
      • 一个StorageClass,用于StatefulSet自动为各个应用Pod申请PVC。
      • 一个Headless Service,用于维护MongoDB集群的状态。
      • 一个StatefulSet。
    • MongoDB集群的扩容:假设在系统运行过程中,3个mongo实例不足以满足业务的要求,这时就需要对mongo集群进行扩容。仅需要通过对StatefulSet进行scale操作,就能实现在mongo集群中自动添加新的mongo节点。使用kubectl scale命令将StatefulSet设置为4个实例: # kubectl scale –replicas=4 statefulset mongo,同时,系统也为mongo-3分配了一个新的PVC用于保存数据
    • 自动故障恢复(MongoDB集群的高可用):Kubernetes使用StatefulSet来搭建有状态的应用集群(MongoDB、MySQL等),同部署无状态的应用一样简便。Kubernetes能够保证StatefulSet中各应用实例在创建和运行的过程中,都具有固定的身份标识和独立的后端存储;还支持在运行时对集群规模进行扩容、保障集群的高可用等非常重要的功能。

HPA

  • Horizontal Pod Autoscaling(Pod横向自动扩容,HPA)。HPA与之前的RC、Deployment一样,也属于一种Kubernetes资源对象。。
  • HPA有以下两种方式作为Pod负载的度量指标。
    • CPUUtilizationPercentage。
    • 应用程序自定义的度量指标,比如服务在每秒内的相应请求数(TPS或QPS)。

Job

  • Job所控制的Pod副本是短暂运行的,可以将其视为一组Docker容器,其中的每个Docker容器都仅仅运行一次。当Job控制的所有Pod副本都运行结束时,对应的Job也就结束了。Job在实现方式上与RC等副本控制器不同,Job生成的Pod副本是不能自动重启的,对应Pod副本的RestartPoliy都被设置为Never。因此,当对应的Pod副本都执行完成时,相应的Job也就完成了控制使命,即Job生成的Pod在Kubernetes中是短暂存在的。Kubernetes在1.5版本之后又提供了类似crontab的定时任务——CronJob,解决了某些批处理任务需要定时反复执行的问题。
  • Job所控制的Pod副本的工作模式能够多实例并行计算,以TensorFlow框架为例,可以将一个机器学习的计算任务分布到10台机器上,在每台机器上都运行一个worker执行计算任务,这很适合通过Job生成10个Pod副本同时启动运算。

DaemonSet

  • 在每个Node上调度并且仅仅创建一个Pod副本。这种调度通常用于系统监控相关的Pod,比如主机上的日志采集、主机性能采集等进程需要被部署到集群中的每个节点,并且只能部署一个副本,这就是DaemonSet这种特殊Pod副本控制器所解决的问题。
  • 这种用法适合有这种需求的应用。◎ 在每个Node上都运行一个GlusterFS存储或者Ceph存储的Daemon进程。◎ 在每个Node上都运行一个日志采集程序,例如Fluentd或者Logstach。◎ 在每个Node上都运行一个性能监控程序,采集该Node的运行性能数据,例如Prometheus Node Exporter、collectd、New Relic agent或者Ganglia gmond等。
  • 目前DaemonSet的升级策略包括两种:OnDelete和RollingUpdate。

Volume

  • Volume(存储卷)是Pod中能够被多个容器访问的共享目录。Kubernetes的Volume概念、用途和目的与Docker的Volume比较类似,但两者不能等价。首先,Kubernetes中的Volume被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下;其次,Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器终止或者重启时,Volume中的数据也不会丢失。最后,Kubernetes支持多种类型的Volume,例如GlusterFS、Ceph等先进的分布式文件系统。
  • Volume的使用也比较简单,在大多数情况下,我们先在Pod上声明一个Volume,然后在容器里引用该Volume并挂载(Mount)到容器里的某个目录上。
  • Kubernetes的Volume还扩展出了一种非常有实用价值的功能,即容器配置文件集中化定义与管理,这是通过ConfigMap这种新的资源对象来实现的。
  • Volume是被定义在Pod上的,属于计算资源的一部分,而实际上,网络存储是相对独立于计算资源而存在的一种实体资源。
  • 同一个Pod中的多个容器能够共享Pod级别的存储卷Volume。
  • PV可以被理解成Kubernetes集群中的某个网络存储对应的一块存储,它与Volume类似,但有以下区别。
    • PV只能是网络存储,不属于任何Node,但可以在每个Node上访问。
    • PV并不是被定义在Pod上的,而是独立于Pod之外定义的。
    • PV目前支持的类型包括:gcePersistentDis。

PV、PVC、StorageClass

  • k8s之PV、PVC、StorageClass详解
  • PV是对底层网络共享存储的抽象,将共享存储定义为一种“资源”,比如Node也是容器应用可以消费的资源。PV由管理员创建和配置,与共享存储的具体实现直接相关。
  • PVC则是用户对存储资源的一个“申请”,就像Pod消费Node资源一样,PVC能够消费PV资源。PVC可以申请特定的存储空间和访问模式。(PVC 的全称是:PersistentVolumeClaim(持久化卷声明),PVC 是用户存储的一种声明)
  • StorageClass,用于标记存储资源的特性和性能,管理员可以将存储资源定义为某种类别,正如存储设备对于自身的配置描述(Profile)。根据StorageClass的描述可以直观的得知各种存储资源的特性,就可以根据应用对存储资源的需求去申请存储资源了。存储卷可以按需创建。
  • 数据的容灾由具体安装部署的服务自行实现,k8s只负责资源的分配。

Namespace

  • Namespace在很多情况下用于实现多租户的资源隔离。Namespace通过将集群内部的资源对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。
  • Kubernetes集群在启动后会创建一个名为default的Namespace,通过kubectl可以查看: kubectl get namespaces
  • 如果不特别指明Namespace,则用户创建的Pod、RC、Service都将被系统创建到这个默认的名为default的Namespace中。
  • 一旦创建了Namespace,我们在创建资源对象时就可以指定这个资源对象属于哪个Namespace。
  • 当给每个租户创建一个Namespace来实现多租户的资源隔离时,还能结合Kubernetes的资源配额管理,限定不同租户能占用的资源,例如CPU使用量、内存使用量等

CRI

  • CRI(容器运行时接口)详解
  • 归根结底,Kubernetes Node(kubelet)的主要功能就是启动和停止容器的组件,我们称之为容器运行时(Container Runtime),其中最知名的就是Docker了。为了更具扩展性,Kubernetes从1.5版本开始就加入了容器运行时插件API,即Container Runtime Interface,简称CRI。
  • kubelet使用gRPC框架通过UNIX Socket与容器运行时(或CRI代理)进行通信
  • Protocol Buffers API包含两个gRPC服务:ImageService和RuntimeService。
  • kubelet的职责在于通过RPC管理容器的生命周期,实现容器生命周期的钩子,存活和健康监测,以及执行Pod的重启策略等。
  • 要尝试新的Kubelet-CRI-Docker集成,只需为kubelet启动参数加上–enable-cri=true开关来启动CRI。这个选项从Kubernetes 1.6开始已经作为kubelet的默认选项了

CRI介绍

  • 从零开始入门 K8s:理解容器运行时接口 CRI
  • 在 CRI 出现之前(也就是 Kubernetes v1.5 之前),Docker 作为第一个容器运行时,Kubelet 通过内嵌的 dockershim 操作 Docker API 来操作容器,进而达到一个面向终态的效果。在这之后,又出现了一种新的容器运行时 - rkt,它也想要成为 Kubernetes 支持的一个容器运行时,当时它也合到了 Kubelet 的代码之中。这两个容器运行时的加入使得 Kubernetes 的代码越来越复杂、难以维护。之后 hyber.sh 加入社区,也想成为第三个容器运行时。
  • 此时就有人站出来说,我们能不能对容器运行时的操作抽象出一个接口,将 Kubelet 代码与具体的容器运行时的实现代码解耦开,只要实现了这样一套接口,就能接入到 Kubernetes 的体系中,这就是我们后来见到的 Container Runtime Interface (CRI)。

监控

  • 所有以非API Server方式创建的Pod都叫作Static Pod
  • 在新的Kubernetes监控体系中,Metrics Server用于提供Core Metrics(核心指标),包括Node和Pod的CPU和内存使用数据。其他Custom Metrics(自定义指标)则由第三方组件(如Prometheus)采集和存储。

网络部分

  • Docker时代,需要将容器的端口映射到宿主机端口,以便在外部访问。
    • k8s: type=NodePort和nodePort=30001的两个属性表明此Service开启了NodePort方式的外网访问模式。
  • Kubernetes集群的网络配置在多个Node组成的Kubernetes集群内,跨主机的容器间网络互通是Kubernetes集群能够正常工作的前提条件。
  • Kubernetes本身并不会对跨主机的容器网络进行设置,这需要额外的工具来实现。除了谷歌公有云GCE平台提供的网络设置,一些开源的工具包括Flannel、Open vSwitch、Weave、Calico等都能够实现跨主机的容器间网络互通。随着CNI网络模型的逐渐成熟,Kubernetes将优先使用CNI网络插件打通跨主机的容器网络。
  • Kubernetes网络模型设计的一个基础原则是:每个Pod都拥有一个独立的IP地址,并假定所有Pod都在一个可以直接连通的、扁平的网络空间中。所以不管它们是否运行在同一个Node(宿主机)中,都要求它们可以直接通过对方的IP进行访问。设计这个原则的原因是,用户不需要额外考虑如何建立Pod之间的连接,也不需要考虑如何将容器端口映射到主机端口等问题。
  • Kubernetes的网络依赖于Docker,Docker的网络又离不开Linux操作系统内核特性的支持

IP-per-Pod模型

  • 将IP地址和端口在Pod内部和外部都保持一致,也就不需要使用NAT来进行地址转换了
  • IP-per-Pod模式和Docker原生的通过动态端口映射方式实现的多节点访问模式有什么区别呢?主要区别是后者的动态端口映射会引入端口管理的复杂性,而且访问者看到的IP地址和端口与服务提供者实际绑定的不同(因为NAT的缘故,它们都被映射成新的地址或端口了),这也会引起应用配置的复杂化。
  • 同时,标准的DNS等名字解析服务也不适用了,甚至服务注册和发现机制都将迎来挑战,因为在端口映射情况下,服务自身很难知道自己对外暴露的真实的服务IP和端口,外部应用也无法通过服务所在容器的私有IP地址和端口来访问服务。总的来说,IP-per-Pod模型是一个简单的兼容性较好的模型。从该模型的网络的端口分配、域名解析、服务发现、负载均衡、应用配置和迁移等角度来看,Pod都能够被看作一台独立的虚拟机或物理机。
  • 按照这个网络抽象原则,Kubernetes对网络有什么前提和要求呢?Kubernetes对集群网络有如下要求。
    1. 所有容器都可以在不用NAT的方式下同别的容器通信。
    2. 所有节点都可以在不用NAT的方式下同所有容器通信,反之亦然。
    3. 容器的地址和别人看到的地址是同一个地址。

Docker网络基础

  • 网络命名空间(Network Namespace)、Veth设备对、网桥、Iptables和路由。
  • 标准的Docker支持以下4类网络模式
    1. host模式:使用–net=host指定。
    2. container模式:使用–net=container:NAME_or_ID指定。(同一个Pod内的容器)
    3. none模式:使用–net=none指定。
    4. bridge模式:使用–net=bridge指定,为默认设置。
  • Docker的网络局限从Docker对Linux网络协议栈的操作可以看到,Docker一开始没有考虑到多主机互联的网络解决方案。
  • 之后,Docker开启了一个宏伟的虚拟化网络解决方案——Libnetwork,这个概念图没有了IP,也没有了路由,已经颠覆了我们的网络常识,对于不怎么懂网络的大多数人来说,它的确很有诱惑力,未来是否会对虚拟化网络的模型产生深远冲击,我们还不得而知,但它仅仅是Docker官方当前的一次“尝试”。
  • Docker容器端口映射原理都是在本地的iptable的nat表中添加相应的规则,将访问本机IP地址:hostport的网包进行一次DNAT,转换成容器IP:containerport。
  • 怎么从容器内访问外网呢?一般情况下需要两个因素:ip_forward和SNAT/MASQUERADE。
    在默认情况下,容器可以访问外部网络的连接,因为容器的默认网络接口为docker0网桥上的接口,即主机上的本地接口。其原理是通过Linux系统的转发功能实现的(把主机当交换机)。至于SNAT/MASQUERADE,Docker会自动在iptables的POSTROUTING链上创建形如下面的规则:即从容器网段出来访问外网的包,都要做一次MASQUERADE,即出去的包都用主机的IP地址替换源地址。

Network Namespace

  • network namespace的增删改查功能已经集成到Linux的ip工具的netns子命令中

  • 为了支持网络协议栈的多个实例,Linux在网络栈中引入了网络命名空间,这些独立的协议栈被隔离到不同的命名空间中。处于不同命名空间中的网络栈是完全隔离的,彼此之间无法通信,就好像两个“平行宇宙”。通过对网络资源的隔离,就能在一个宿主机上虚拟多个不同的网络环境。Docker正是利用了网络的命名空间特性,实现了不同容器之间的网络隔离。

  • 在Linux的网络命名空间中可以有自己独立的路由表及独立的iptables设置来提供包转发、NAT及IP包过滤等功能。为了隔离出独立的协议栈,需要纳入命名空间的元素有进程、套接字、网络设备等。进程创建的套接字必须属于某个命名空间,套接字的操作也必须在命名空间中进行。同样,网络设备也必须属于某个命名空间。因为网络设备属于公共资源,所以可以通过修改属性实现在命名空间之间移动。当然,是否允许移动与设备的特征有关。

  • Linux的网络协议栈是十分复杂的,为了支持独立的协议栈,相关的这些全局变量都必须被修改为协议栈私有。最好的办法就是让这些全局变量成为一个Net Namespace变量的成员,然后为协议栈的函数调用加入一个Namespace参数。这就是Linux实现网络命名空间的核心。同时,为了保证对已经开发的应用程序及内核代码的兼容性,内核代码隐式地使用了命名空间中的变量。程序如果没有对命名空间有特殊需求,就不需要编写额外的代码,网络命名空间对应用程序而言是透明的。

tun/tap

  • 它是一组通用的虚拟驱动程序包,里面包含了两个设备,分别是用于网络数据包处理的虚拟网卡驱动,以及用于内核空间与用户空间交互的字符设备(Character Devices,这里具体指/dev/net/tun)驱动。

  • tun和tap是两个相对独立的虚拟网络设备,其中tap模拟了以太网设备,操作二层数据包(以太帧),tun则模拟了网络层设备,操作三层数据包(IP报文)。

  • 使用tun/tap设备的目的是实现把来自协议栈的数据包先交由某个打开了/dev/net/tun字符设备的用户进程处理后,再把数据包重新发回到链路中。你可以通俗地将它理解为这块虚拟化网卡驱动一端连接着网络协议栈,另一端连接着用户态程序,而普通的网卡驱动则是一端连接则网络协议栈,另一端连接着物理网卡。只要协议栈中的数据包能被用户态程序截获并加工处理,程序员就有足够的舞台空间去玩出各种花样,譬如数据压缩、流量加密、透明代理等功能都能够以此为基础来实现,比如典型的VPN应用程序。

  • 使用tun/tap设备传输数据需要经过两次协议栈,不可避免地会有一定的性能损耗,如果条件允许,容器对容器的直接通信并不会把tun/tap作为首选方案,一般是基于稍后介绍的veth来实现的。但是tun/tap没有veth那样要求设备成对出现、数据要原样传输的限制,数据包到用户态程序后,程序员就有完全掌控的权力,要进行哪些修改,要发送到什么地方,都可以编写代码去实现,因此tun/tap方案比起veth方案有更广泛的适用范围。

  • tun/tap设备到底是什么?从Linux文件系统的角度看,它是用户可以用文件句柄操作的字符设备;从网络虚拟化角度看,它是虚拟网卡,一端连着网络协议栈,另一端连着用户态程序。

  • tun表示虚拟的是点对点设备,tap表示虚拟的是以太网设备

  • tun/tap设备可以将TCP/IP协议栈处理好的网络包发送给任何一个使用tun/tap驱动的进程,由进程重新处理后发到物理链路中。tun/tap设备就像是埋在用户程序空间的一个钩子,我们可以很方便地将对网络包的处理程序挂在这个钩子上,OpenVPN、Vtun、flannel都是基于它实现隧道包封装的。

  • 从网络协议栈的角度看,tun/tap设备这类虚拟网卡与物理网卡并无区别。只是对tun/tap设备而言,它与物理网卡的不同表现在它的数据源不是物理链路,而是来自用户态!这也是tun/tap设备的最大价值所在

  • flannel的UDP模式的技术要点就是tun/tap设备。

  • tun/tap设备其实就是利用Linux的设备文件实现内核态和用户态的数据交互,而访问设备文件则会调用设备驱动相应的例程,要知道设备驱动也是内核态和用户态的一个接口。

  • 普通的物理网卡通过网线收发数据包,而tun设备通过一个设备文件(/dev/tunX)收发数据包。所有对这个文件的写操作会通过tun设备转换成一个数据包传送给内核网络协议栈。当内核发送一个包给tun设备时,用户态的进程通过读取这个文件可以拿到包的内容。当然,用户态的程序也可以通过写这个文件向tun设备发送数据。tap设备与tun设备的工作原理完全相同,区别在于:·tun设备的/dev/tunX文件收发的是IP包,因此只能工作在L3,无法与物理网卡做桥接,但可以通过三层交换(例如ip_forward)与物理网卡连通;·tap设备的/dev/tapX文件收发的是链路层数据包,可以与物理网卡做桥接。

  • tun/tap设备的用处是将协议栈中的部分数据包转发给用户空间的应用程序,给用户空间的程序一个处理数据包的机会。常见的tun/tap设备使用场景有数据压缩、加密等,最常见的是VPN,包括tunnel及应用层的IPSec等。我们将使用tun设备搭建一个基于UDP的VPN

veth pair(Veth设备对)

  • veth全称是(Virtual Ethernet)虚拟以太网,其原理类似linux管道,在一个veth设备写入网络包,其对端的veth设备可以读取到对应的网络包。
  • veth实际上不是一个设备,而是一对设备,因而也常被称作veth pair。要使用veth,必须在两个独立的网络名称空间中进行才有意义,因为veth pair是一端连着协议栈,另一端彼此相连的,在veth设备的其中一端输入数据,这些数据就会从设备的另外一端原样不变地流出。
  • 引入Veth设备对是为了在不同的网络命名空间之间通信,利用它可以直接将两个网络命名空间连接起来。
  • 创建Veth设备对: ip link add veth0 type veth peer name veth1
  • veth pair的缺点
    • veth pair相当于网线连接两个网口,打个比喻,我们平时使用电脑插路由器网线,在你电脑的网口和路由器的lan口就是veth pair。
    • 其不足也在这里,只能连接两个network namespace,如果要多个network namespace进行通信,会非常复杂,你会建立一系列的veth pair,整个关系网是点对点的,也就是任意两个network namespace都需要veth pair来通信。
    • 这个问题的解决办法需要依赖linux网桥(bridge),利用网桥来将多个veth设备连接起来。
  • 由于两个容器之间采用veth通信不需要反复多次经过网络协议栈,这让veth比起tap/tun具有更好的性能,也让veth pair的实现变的十分简单。

bridge(网桥)

  • 网桥是一个二层的虚拟网络设备,把若干个网络接口“连接”起来,以使得网络接口之间的报文能够互相转发。网桥能够解析收发的报文,读取目标MAC地址的信息,和自己记录的MAC表结合,来决策报文的转发目标网络接口。为了实现这些功能,网桥会学习源MAC地址(二层网桥转发的依据就是MAC地址)。在转发报文时,网桥只需要向特定的网口进行转发,来避免不必要的网络交互。如果它遇到一个自己从未学习到的地址,就无法知道这个报文应该向哪个网络接口转发,就将报文广播给所有的网络接口(报文来源的网络接口除外)。在实际的网络中,网络拓扑不可能永久不变。设备如果被移动到另一个端口上,却没有发送任何数据,网桥设备就无法感知到这个变化,网桥还是向原来的端口转发数据包,在这种情况下数据就会丢失。所以网桥还要对学习到的MAC地址表加上超时时间(默认为5min)。如果网桥收到了对应端口MAC地址回发的包,则重置超时时间,否则过了超时时间后,就认为设备已经不在那个端口上了,它就会重新广播发送。

  • 在Linux的内部网络栈里实现的网桥设备,作用和上面的描述相同。过去Linux主机一般都只有一个网卡,现在多网卡的机器越来越多,而且有很多虚拟的设备存在,所以Linux的网桥提供了在这些设备之间互相转发数据的二层设备。Linux内核支持网口的桥接(目前只支持以太网接口)。但是与单纯的交换机不同,交换机只是一个二层设备,对于接收到的报文,要么转发,要么丢弃。运行着Linux内核的机器本身就是一台主机,有可能是网络报文的目的地,其收到的报文除了转发和丢弃,还可能被送到网络协议栈的上层(网络层),从而被自己(这台主机本身的协议栈)消化,所以我们既可以把网桥看作一个二层设备,也可以把它看作一个三层设备。

  • 容器先创建veth设备对(veth0、veth1),再把veth1连到同一个bridge。

  • 配置例子:

    网桥br0:分配IP地址192.168.31.1;
    容器:三个网络名称空间(容器),分别编号为1、2、3,均使用veth pair接入网桥,且有如下配置:
    在容器一端的网卡名为veth0,在网桥一端网卡名为veth1、veth2、veth3;
    三个容器中的veth0网卡分配IP地址:192.168.1.10、192.168.1.11、192.168.1.12;
    三个容器中的veth0网卡设置网关为网桥,即192.168.31.1;
    网桥中的veth1、veth2、veth3无IP地址;
    物理网卡eth0:分配的IP地址14.123.254.86;
    外部网络:外部网络中有一台服务器,地址为122.246.6.183
    
  • 混杂模式(Promiscuous mode),简称Promisc mode,俗称“监听模式”

    • 混杂模式是指一个网卡会把它接收的所有网络流量都交给CPU,而不是只把它想转交的部分交给CPU。
    • 在IEEE 802定的网络规范中,每个网络帧都有一个目的MAC地址。在非混杂模式下,网卡只会接收目的MAC地址是它自己的单播帧,以及多播及广播帧;在混杂模式下,网卡会接收经过它的所有帧
    • 将网络设备加入Linux bridge后,会自动进入混杂模式。

iptables

  • 在Linux网络协议栈中有一组回调函数挂接点,通过这些挂接点挂接的钩子函数可以在Linux网络栈处理数据包的过程中对数据包进行一些操作,例如过滤、修改、丢弃等。整个挂接点技术叫作Netfilter和iptables。
  • Netfilter负责在内核中执行各种挂接的规则,运行在内核模式中;而iptables是在用户模式下运行的进程,负责协助和维护内核中Netfilter的各种规则表。二者互相配合来实现整个Linux网络协议栈中灵活的数据包处理机制。

路由

  • 路由功能由IP层维护的一张路由表来实现。当主机收到数据报文时,它用此表来决策接下来应该做什么操作。当从网络侧接收到数据报文时,IP层首先会检查报文的IP地址是否与主机自身的地址相同。如果数据报文中的IP地址是主机自身的地址,那么报文将被发送到传输层相应的协议中。如果报文中的IP地址不是主机自身的地址,并且主机配置了路由功能,那么报文将被转发,否则,报文将被丢弃。
  • Linux的路由表至少包括两个表(当启用策略路由时,还会有其他表):一个是LOCAL,另一个是MAIN。在LOCAL表中会包含所有的本地设备地址。LOCAL路由表是在配置网络设备地址时自动创建的。LOCAL表用于供Linux协议栈识别本地地址,以及进行本地各个不同网络接口之间的数据转发。可以通过下面的命令查看LOCAL表的内容: ip route show table local type local
  • MAIN表用于各类网络IP地址的转发。它的建立既可以使用静态配置生成,也可以使用动态路由发现协议生成。动态路由发现协议一般使用组播功能来通过发送路由发现数据,动态地交换和获取网络的路由信息,并更新到路由表中。路由表的查看我们可以使用ip route list命令查看当前的路由表: ip route list
  • Netstat -rn 是另一个查看路由表的工具:
    在它显示的信息中,如果标志是U,则说明是可达路由;如果标志是G,则说明这个网络接口连接的是网关,否则说明这个接口直连主机。

Linux隧道:ipip

  • tun设备也叫作点对点设备,之所以叫这个名字,是因为tun经常被用来做隧道通信(tunnel)。
  • Linux原生支持下列5种L3隧道:
    • ipip:即IPv4 in IPv4,在IPv4报文的基础上封装一个IPv4报文;
    • GRE:即通用路由封装(Generic Routing Encapsulation),定义了在任意一种网络层协议上封装其他任意一种网络层协议的机制,适用于IPv4和IPv6;
    • sit:和ipip类似,不同的是sit用IPv4报文封装IPv6报文,即IPv6 over IPv4;
    • ISATAP:即站内自动隧道寻址协议(Intra-Site Automatic Tunnel Addressing Protocol),与sit类似,也用于IPv6的隧道封装;
    • VTI:即虚拟隧道接口(Virtual Tunnel Interface),是思科提出的一种IPSec隧道技术。下面我们以ipip为例,介绍Linux隧道通信的基本原理。:
  • Linux L3隧道底层实现原理都基于tun设备。我们熟知的各种VPN软件,其底层实现都离不开这5种隧道协议。其他隧道实现方式与ipip隧道的大同小异。

VXLAN

  • 目前比较常见的封装报文的技术有VxLAN和隧道(gre、ipip等),其中vxlan是通过udp协议进行封装的,隧道方式是通过ip层封装的。
  • VXLAN(Virtual eXtensible LAN,虚拟可扩展的局域网),是一种虚拟化隧道通信技术。它是一种overlay(覆盖网络)技术,通过三层的网络搭建虚拟的二层网络。
  • 简单来讲,VXLAN是在底层物理网络(underlay)之上使用隧道技术,依托UDP层构建的overlay的逻辑网络,使逻辑网络与物理网络解耦,实现灵活的组网需求。
  • 它不仅能适配虚拟机环境,还能用于容器环境。由此可见,VXLAN这类隧道网络的一个特点是对原有的网络架构影响小,不需要对原网络做任何改动,就可在原网络的基础上架设一层新的网络。
  • 不同于其他隧道协议,VXLAN是一个一对多的网络,并不仅是一对一的隧道协议。一个VXLAN设备能通过像网桥一样的学习方式学习到其他对端的IP地址,也可以直接配置静态转发表。
  • VLAN技术的缺陷是VLAN Header预留的长度只有12 bit,故最多只能支持2的12次方(4096)子网的划分,无法满足云计算场景下主机数量日益增长的需求。VXLAN能突破VLAN的最多4096个子网的数量限制,以满足大规模云计算数据中心的需求。
  • VXLAN的报文就是MAC in UDP,即在三层网络的基础上构建一个虚拟的二层网络。为什么这么说呢?VXLAN的封包格式显示原来的二层以太网帧(包含MAC头部、IP头部和传输层头部的报文),被放在VXLAN包头里进行封装,再套到标准的UDP头部(UDP头部、IP头部和MAC头部),用来在底层网络上传输报文。
  • 总的来说,VXLAN报文的转发过程就是:原始报文经过VTEP,被Linux内核添加上VXLAN包头及外层的UDP头部,再发送出去,对端VTEP接收到VXLAN报文后拆除外层UDP头部,并根据VXLAN头部的VNI把原始报文发送到目的服务器。
  • 传统的局域网是怎么构建的(非虚拟lan),要构成一个局域网,需要一台路由器设备作为网关和其他接入该路由器的设备,这时候lan是这样定义的:接在同一台路由器上(物理)且具有相同网络号(逻辑)的设备在同一个局域网下。那么vxlan可以使得局域网的构建可以打破物理上的限制,构建逻辑上的lan——评论

Macvlan

  • 通常,我们在自定义Docker与外部网络通信的网络时会用到NAT,还有Linux bridge、Open vSwitch、Macvlan几种选择,相比之下,Macvlan拥有更好的性能。
  • 在Macvlan出现之前,我们可以通过网卡别名(例如eth0:1)的方式为一块以太网卡添加多个IP地址,却不能为其添加多个MAC地址。原因是以太网卡是以MAC地址为唯一识别的,而网卡别名并没有改变这些网卡的MAC地址。Macvlan接口可以看作是物理以太网接口的虚拟子接口。Macvlan允许用户在主机的一个网络接口上配置多个虚拟的网络接口,每个Macvlan接口都有自己的区别于父接口的MAC地址,并且可以像普通网络接口一样分配IP地址。因此,使用Macvlan技术带来的效果是一块物理网卡上可以绑定多个IP地址,每个IP地址都有自己的MAC地址。
  • 使用Macvlan的虚拟机或者容器网络与主机在同一个网段,即同一个广播域中。
  • Macvlan支持5种模式,分别是bridge、VEPA、Private、Passthru和Source模式。
  • 在Macvlan虚拟网络世界中,物理网卡(父接口)相当于一个交换机,对于进出其子Macvlan网卡的数据包,物理网卡只转发数据包而不处理数据包,于是也就造成了使用本机Macvlan网卡的IP无法和物理网卡的IP通信。总结,Macvlan只为虚拟机或容器提供访问外部物理网络的连接。
  • Macvlan是将虚拟机或容器通过二层连接到物理网络的一个不错的方案,但它也有一些局限性,例如:·因为每个虚拟网卡都要有自己的MAC地址,所以Macvlan需要大量的MAC地址,而Linux主机连接的交换机可能会限制一个物理端口的MAC地址数量上限,而且许多物理网卡的MAC地址数量也有限制,超过这个限制就会影响到系统的性能;·IEEE 802.11标准(即无线网络)不喜欢同一个客户端上有多个MAC地址,这意味着你的Macvlan子接口没法在无线网卡上通信。我们可以通过复杂的办法突破以上这些限制,但还有一种更简单的办法。那就是使用IPvlan。

IPvlan

  • Macvlan和IPvlan虚拟网络模型提供的功能看起来差不多,那么,什么时候需要用到IPvlan呢?要回答这个问题,先来看看Macvlan先天存在的不足:·无法支持大量的MAC地址;·无法工作在无线网络环境中。
  • 与Macvlan类似,IPvlan也是从一个主机接口虚拟出多个虚拟网络接口。区别在于IPvlan所有的虚拟接口都有相同的MAC地址,而IP地址却各不相同。因为所有的IPvlan虚拟接口共享MAC地址,所以特别需要注意DHCP使用的场景。DHCP分配IP地址的时候一般会用MAC地址作为机器的标识。因此,在使用Macvlan的情况下,客户端动态获取IP的时候需要配置唯一的Client ID,并且DHCP服务器也要使用该字段作为机器标识,而不是使用MAC地址。
  • 外部网络默认情况下是不知道IPvlan虚拟出来的网络的,如果不在外部路由器上配置好对应的路由规则,那么IPvlan的网络是不能被外部直接访问的。
  • 我们将IPvlan称为Macvlan的“救护员”是因为IPvlan除了能够完美解决以上问题,还允许用户基于IPvlan搭建比较复杂的网络拓扑,不再基于Macvlan的简单的二层网络,而是能够与BGP(Boader Gateway Protocol,边界网关协议)等协议扩展我们的网络边界。

Docker的四大网络模式

  • 从网络的角度看容器,就是network namespace+容器的组网方案。利用network namespace,可以为Docker容器创建隔离的网络环境。容器具有完全独立的网络栈,与宿主机隔离。用户也可以让Docker容器共享主机或者其他容器的network namespace。
  • 容器的网络方案可以分为三大部分:
    1. 单机的容器间通信;
    2. 跨主机的容器间通信;
    3. 容器与主机间通信。
  • Docker有以下4种网络模式:
    1. bridge模式,通过–network=bridge指定;(Docker容器的默认组网模式)
      • 连接在docker0上的所有容器的默认网关均为docker0,即访问非本机容器网段要经过docker0网关转发,而同主机上的容器(同网段)之间通过广播通信。
      • bridge模式为Docker容器创建独立的网络栈,保证容器内的进程使用独立的网络环境,使容器和容器、容器和宿主机之间能实现网络隔离。
    2. host模式,通过–network=host指定;
      • 连接到host网络的容器共享Docker host的网络栈,容器的网络配置与host完全一样。host模式下容器将不会获得独立的network namespace,而是和宿主机共用一个network namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
      • host模式下的容器可以看到宿主机的所有网卡信息,甚至可以直接使用宿主机IP地址和主机名与外界通信,无须额外进行NAT,也无须通过Linux bridge进行转发或者进行数据包的封装。
      • 当然,host模式有利有弊,优点是没有性能损耗且配置方便,缺点也很明显,例如:·容器没有隔离、独立的网络栈:容器因与宿主机共用网络栈而争抢网络资源,并且容器崩溃也可能使主机崩溃,导致网络的隔离性不好;
      • 端口资源冲突:宿主机上已经使用的端口就不能再用了。
    3. container模式,通过–network=container:NAME_or_ID指定,即joiner容器;
      • 创建容器时使用–network=container:NAME_or_ID模式,在创建新的容器时指定容器的网络和一个已经存在的容器共享一个network namespace,但是并不为Docker容器进行任何网络配置,这个Docker容器没有网卡、IP、路由等信息,需要手动为Docker容器添加网卡、配置IP等。需要注意的是,container模式指定新创建的容器和已经存在的任意一个容器共享一个network namespace,但不能和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。
      • Kubernetes的Pod网络采用的就是Docker的container模式网络,我们将在后面的章节详细介绍。
    4. none模式,通过–network=none指定。
  • 连接在docker0上的所有容器的默认网关均为docker0,即访问非本机容器网段要经过docker0网关转发,而同主机上的容器(同网段)之间通过广播通信。
    • none模式下的容器只有lo回环网络,没有其他网卡
    • none模式网络可以在容器创建时通过–network=none指定。这种类型的网络没有办法联网,属于完全封闭的网络。唯一的用途是客户有充分的自由度做后续的配置。这种模式下的Docker容器拥有自己的network namespace,但是并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息,需要我们自己为Docker容器添加网卡、配置IP等。

容器组网的挑战

  • 容器网络的挑战:“以隔离的方式部署容器,在提供隔离自己容器内数据所需功能的同时,保持有效的连接性”,提炼两个关键词便是“隔离”和“连接”。

  • 容器网络最大的挑战在跨容器通信层面,具体有以下几点:

    1. 虚拟机拥有较强的隔离机制,虚拟网卡与硬件网卡在使用上没有什么区别。由于容器基于网络隔离能力较弱的network namespace,容器网络的设计首要考虑隔离性和安全性;
    2. 出于安全考虑,很多情况下容器会被部署在虚拟机内部,这种嵌套部署对应一个新的组网模型;
    3. 原生Docker容器不解决跨主机通信问题,而大规模的容器部署势必涉及不同主机上的网络通信;
    4. 容器相较虚拟机生命周期更短,重启更加频繁,重启后IP地址将发生变化,需要进行高效的网络地址管理,因此静态的IP地址分配或DHCP(耗费数秒才能生效)将不起作用;
    5. 在创建虚拟机的时候,IP地址的分配是一种静态方式。但是在容器上面,IP地址的分配通常是动态的。在创建容器时,通常我们不知道它的IP是什么,只有真正运行后才能知道;
    6. 容器相较虚拟机发生迁移的频率更高,且原生的Docker容器不支持热迁移,迁移后的容器面临IP地址漂移,而且经常出现不在同一网段的情况,对应的网络策略的刷新要及时跟上;
    7. 容器的部署数量远大于虚拟机,如果为每个容器分配一个主机网络的IP地址,则可能会导致不够分配;
    8. 大规模跨机部署带来多主机间的ARP洪泛,造成大量的资源浪费;
    9. 大部分的容器网络方案都会使用iptables和NAT,NAT的存在会造成通信两端看不到彼此的真实IP地址,并且iptable和NAT的结合使用限制了可扩展性和性能,这让使用容器的主要优势之一荡然无存;
    10. 大规模部署使用veth设备会影响网络性能,最严重时甚至会比裸机降低50%;
    11. 过多的MAC地址。当MAC地址超过限制时,主机中的物理网络接口卡虽然可以切换到混杂模式,但会导致性能下降,而且顶级机架(ToR)交换机可支持的MAC地址数量也有上限。
  • 针对以上挑战,有一些针对性的解决方案。例如,可以使用Macvlan和IPvlan替代veth,以降低处理veth网络的性能开销。当使用IPvlan驱动时,IPvlan工作在L3,而L3网络不会受到洪泛影响,因此只有主机的物理网卡MAC地址可见,容器MAC地址不会暴露在网络中,从而解决数据中心可见MAC地址过多给ToR交换机带来的影响。容器网络对NAT的依赖及NAT自身的局限性,而避免NAT的基本思想是将容器和虚拟机、主机同等对待。我们可以直接将容器连接到主机的网络接口来实现这一点,即容器与主机共享本地局域网(LAN)的资源,通过DHCP服务器或静态地址分配的方式给容器分配LAN的IP地址。这样一来,所有L4的容器网络端口完全暴露,虽然这种直接的暴露比管理映射端口更好,但保证安全状态需要网络策略的加持。直接连接到物理接口,即把容器的veth/Macvlan网卡与连通互联网的物理网卡桥接。不过,这种方法需要修改物理接口,即移除IP地址并将其分配到桥接接口上。

  • 消除NAT的第二种方法是把主机变成全面的路由器,甚至是使用BGP的路由器。主机会将前缀路由到主机中的容器,每个容器会使用全球唯一的IP地址。尽管在IPv4地址空间已经耗尽的今天,给每个容器提供一个完全可路由的IPv4地址有些不太实际,但IPv6将使这种主机作为路由器的容器组网技术变得可能,事实上很多公有云都已经支持IPv6。以上这些都只是针对某个具体问题的,而非整体的端到端的方案,例如IP地址管理、跨主机通信、消除NAT、安全策略等。Docker最初并没有提供必要的此类能力,在开始的很长一段时间内,只支持使用Linux bridge+iptables的单机网络部署方式,这种方式下容器的可见性只存在于主机内部,这严重地限制了容器集群的规模及可用性!

  • Docker目前拥有两种网络解决方案

    • 一种是原生方式,即Docker官方支持且无须配置的开箱即用。
    • 允许第三方网络管理工具以插件方式替代Docker中内置的网络功能,接口标准便是CNM。
    • 所以docker + calico + k8s 使用的是CNM还是CNI??TODO
  • 对相对成熟的第三方容器解决方案进行分类,则大致可以分为隧道方案和路由方案。

  • 当前主流的容器网络虚拟化技术有Linux bridge、Macvlan、IPvlan、Open vSwitch、flannel、Weave、Calico等。而容器网络最基础的一环是为容器分配IP地址,主流的方案有本地存储+固定网段的host-local,DHCP,分布式存储+IPAM的flannel和SDN的Weave等。任何一个主流的容器组网方案无非就是网络虚拟机+IP地址分配,即Linux bridge、Macvlan、IPvlan、Open vSwitch、flannel、Weave、Calico等虚拟化技术和host-local、DHCP、flannel、Weave任取两样的排列组合。

  • calico方案是使用哪种IP地址分配方案???k8s pod ip是k8s分配的,使用calico只需要规划好node ip的分配。

  • overlay网络

    • 隧道网络也称为overlay网络,有时也被直译为覆盖网络。
    • overlay网络最大的优点是适用于几乎所有网络基础架构,它唯一的要求是主机之间的IP连接。但overlay网络的问题是随着节点规模的增长,复杂度也会随之增加,而且用到了封包,因此出了网络问题定位起来比较麻烦。
    • 典型的基于overlay的网络插件有:
      • Weave:源自Weaveworks,包括Weave Net、Weave Scope和Weave Flux。Weave Net是一种用于构建和部署Docker容器的网络工具;
      • Open vSwitch(OVS):基于VXLAN和GRE协议,但是性能方面损失比较严重;
      • flannel:源自CoreOS,支持自研的UDP封包及Linux内核的VXLAN协议。
    • Weave和flannel用到的封包技术特点类似,不过使用的是VXLAN。另外,Weave的思路是共享IP而非绑定。在传输层先找到目的地址,然后把包发到对端,节点之间互相通过协议共享信息。使用UDP进行封包的时候性能损失在50%以上,使用VXLAN也会有20%~30%的损耗。
  • 路由方案

    • 路由方案回过头来想一下,我们为什么要封包?其实它是改包,主要解决的问题是同一个问题,即在容器网络里,主机间不知道对方的目的地址,没有办法把IP包投递到正确的地方。传统的三层网络是用路由来互相访问的,不需要封包。至于路由规则怎么维护?传统的网络解决方案是利用BGP部署一个分布式的路由集群。 传统BGP分布式路由集群方案通过路由来实现,比较典型的网络插件有:
    • Calico:源自Tigera,基于BGP的路由方案,支持很细致的ACL控制,对混合云亲和度比较高;
    • Macvlan:从逻辑和Kernel层来看,是隔离性和性能最优的方案,基于二层隔离,需要二层路由器支持,大多数云服务商不支持,因此混合云上比较难以实现;
    • Metaswitch:容器内部配一个路由指向自己宿主机的地址,这是一个纯三层的网络不存在封包,因此性能接近原生网络。路由方案的另一个优点是出了问题也很容易排查。路由方案往往需要用户了解底层网络基础结构,因此使用和运维门槛较高。
    • Calico是一个纯三层网络方案。不同主机上的每个容器内部都配一个路由,指向自己所在的IP地址;每台服务器变成路由器,配置自己的路由规则,通过网卡直接到达目标容器,整个过程没有封包。
    • 那么,路由交换是不是很难呢?用传统的BGP技术就可以实现。这个协议在大规模应用下是一个很好的场景,而且BGP有一个自治域的概念。在这个场景下会有一个问题,路由之间的信息交换实际上基于TCP,每两个之间都有一个TCP连接,规模大了维护这些连接的开销会非常高。
    • Calico的设计灵感源自通过将整个互联网的可扩展IP网络原则压缩到数据中心级别。Calico在每一个计算节点,利用Linux Kernel实现高效的vRouter来负责数据转发,而每个vRouter通过BGP把自己节点上的工作负载的路由信息向整个Calico网络传播。小规模部署可以直接互联,大规模下可通过指定的BGP Route Reflector完成。保证最终所有的容器之间的数据流量都是通过IP路由的方式完成互联的。

容器网络组网类型

  • 关于容器网络对overlay和underlay的分类,常见的非主机网络(host network)的容器组网类型有L2 overlay、L3 overlay、L2 underlay和L3 underlay。
  1. overlay网络

    • overlay网络是在传统网络上虚拟出一个虚拟网络,承载的底层网络不再需要做任何适配。在容器的世界里,物理网络只承载主机网络通信,虚拟网络只承载容器网络通信。overlay网络的任何协议都要求在发送方对报文进行包头封装,接收方剥离包头。
    1. L2 overlay
      • 传统的二层网络的范围有限,L2 overlay网络是构建在底层物理网络上的L2网络,相较于传统的L2网络,L2 overlay网络是个“大二层”的概念,其中“大”的含义是可以跨越多个数据中心(即容器可以跨L3 underlay进行L2通信),而“二层”指的是通信双方在同一个逻辑的网段内,例如172.17.1.2/16和172.17.2.3/16。VXLAN就是L2 overlay网络的典型实现,其通过在UDP包中封装原始L2报文,实现了容器的跨主机通信。L2 overlay网络容器可在任意宿主机间迁移而不改变其IP地址的特性,使得构建在大二层overlay网络上的容器在动态迁移时具有很高的灵活性。
    2. L3 overlay
      • L3 overlay组网类似L2 overlay,但会在节点上增加一个网关。每个节点上的容器都在同一个子网内,可以直接进行二层通信。跨节点的容器间通信只能走L3,都会经过网关转发,性能相比于L2 overlay较弱。牺牲的性能获得了更高的灵活性,跨节点通信的容器可以存在于不同的网段中,例如192.168.1.0/24和172.17.16.0/24。flannel的UDP模式采用的就是L3 overlay模型。L3 overlay网络容器在主机间迁移时可能需要改变其IP地址。
  2. underlay网络

    • underlay网络一般理解为底层网络,传统的网络组网就是underlay类型,区别于上文提到的overlay网络。
    1. L2 underlay
      • L2 underlay网络就是链路层(L2)互通的底层网络。IPvlan L2模式和Macvlan属于L2 underlay类型的网络。
    2. L3 underlay
      • 在L3 underlay组网中,可以选择IPvlan的L3模式,该模式下IPvlan有点像路由器的功能,它在各个虚拟网络和主机网络之间进行不同网络报文的路由转发工作。只要父接口相同,即使虚拟机/容器不在同一个网络,也可以互相ping通对方,因为IPvlan会在中间做报文的转发工作。IPvlan的L3模式,flannel的host-gw模式和Calico的BGP组网方式都是L3 underlay类型的网络。

Kubernetes网络

  • Kubernetes网络包括网络模型、CNI、Service、Ingress、DNS等。在Kubernetes的网络模型中,每台服务器上的容器有自己独立的IP段,各个服务器之间的容器可以根据目标容器的IP地址进行访问。

  • 为了实现这一目标,重点解决以下两点:

    • 各台服务器上的容器IP段不能重叠,所以需要有某种IP段分配机制,为各台服务器分配独立的IP段;
    • 从某个Pod发出的流量到达其所在服务器时,服务器网络层应当具备根据目标IP地址,将流量转发到该IP所属IP段对应的目标服务器的能力。总结起来,实现Kubernetes的容器网络重点需要关注两方面:IP地址分配和路由。
  • Kubernetes网络策略

    • 与Kubernetes Ingress API类似,Kubernetes只提供了Network Policy的API定义,不负责具体实现。
    • 通常,Policy Controller是由Kubernetes网络插件提供的。支持Network Policy的网络插件有Calico、Cilium、Weave Net、Kube-router、Romana等。需要注意的是,flannel不在这个名单中。

IP地址分配

  • Kubernetes使用各种IP范围为节点、Pod和服务分配IP地址。
  • 系统会从集群的VPC网络为每个节点分配一个IP地址。该节点IP用于提供从控制组件(如Kube-proxy和Kubelet)到Kubernetes Master的连接;
  • 系统会为每个Pod分配一个地址块内的IP地址。用户可以选择在创建集群时通过–pod-cidr指定此范围;
  • 系统会从集群的VPC网络为每项服务分配一个IP地址(称为ClusterIP)。
    大部分情况下,该VPC与节点IP地址不在同一个网段,而且用户可以选择在创建集群时自定义VPC网络。

Pod出站流量

  • Kubernetes处理Pod的出站流量的方式主要分为以下三种:
  • Pod到Pod
    • 在Kubernetes集群中,每个Pod都有自己的IP地址,运行在Pod内的应用都可以使用标准的端口号,不用重新映射到不同的随机端口号。所有的Pod之间都可以保持三层网络的连通性,比如可以相互ping对方,相互发送TCP/UDP/SCTP数据包。CNI就是用来实现这些网络功能的标准接口。
  • Pod到Service
    • Pod的生命周期很短暂,但客户需要的是可靠的服务,因此Kubernetes引入了新的资源对象Service,其实它就是Pod前面的4层负载均衡器。Service总共有4种类型,其中最常用的类型是ClusterIP,这种类型的Service会自动分配一个仅集群内部可以访问的虚拟IP。Kubernetes通过Kube-proxy组件实现这些功能,每台计算节点上都运行一个Kubeproxy进程,通过复杂的iptables/IPVS规则在Pod和Service之间进行各种过滤和NAT。
  • Pod到集群外
    • 从Pod内部到集群外部的流量,Kubernetes会通过SNAT来处理。SNAT做的工作就是将数据包的源从Pod内部的IP:Port替换为宿主机的IP:Port。当数据包返回时,再将目的地址从宿主机的IP:Port替换为Pod内部的IP:Port,然后发送给Pod。当然,中间的整个过程对Pod来说是完全透明的,它们对地址转换不会有任何感知。

Kubernetes网络架构综述

  • 谈到Kubernetes的网络模型,就不能不提它著名的“单Pod单IP”模型,即每个Pod都有一个独立的IP,Pod内所有容器共享network namespace(同一个网络协议栈和IP)。
  • “单Pod单IP”网络模型为我们勾勒了一个Kubernetes扁平网络的蓝图,在这个网络世界里:容器是一等公民,容器之间直接通信,不需要额外的NAT,因此不存在源地址被伪装的情况;Node与容器网络直连,同样不需要额外的NAT。扁平化网络的优点在于:没有NAT带来的性能损耗,而且可追溯源地址,为后面的网络策略做铺垫,降低网络排错的难度等。
  • 总体而言,集群内访问Pod,会经过Service;集群外访问Pod,经过的是Ingress。Service和Ingress是Kubernetes专门为服务发现而抽象出来的相关概念。
  • Kubernetes Ingress提供了负载平衡器的典型特性:HTTP路由、黏性会话、SSL终止、SSL直通、TCP和UDP负载平衡等
  • 与CRI之于Kubernetes的runtime类似,Kubernetes使用CNI作为Pod网络配置的标准接口。需要注意的是,CNI并不支持Docker网络,也就是说,docker0网桥会被大部分CNI插件“视而不见”。
  • 当然也有例外,Weave就是一个会处理docker0的CNI插件。

  • 图中描绘了当用户在Kubernetes里创建了一个Pod后,CRI和CNI协同创建Pod所属容器,并为它们初始化网络协议栈的全过程。具体过程如下:
    1. 当用户在Kubernetes的Master里创建了一个Pod后,Kubelet观察到新Pod的创建,于是首先调用CRI(后面的runtime实现,比如dockershim、containerd等)创建Pod内的若干个容器。
    2. 在这些容器里,第一个被创建的pause容器是比较特殊的,这是Kubernetes系统“赠送”的容器,也称pause容器。里面运行着一个功能十分简单的C程序,具体逻辑是一启动就把自己永远阻塞在那里。一个永远阻塞而且没有实际业务逻辑的pause容器到底有什么用呢?用处很大。我们知道容器的隔离功能利用的是Linux内核的namespace机制,而只要是一个进程,不管这个进程是否处于运行状态(挂起亦可),它都能“占”用着一个namespace。因此,每个Pod内的第一个系统容器pause的作用就是占用一个Linux的network namespace。
    3. Pod内其他用户容器通过加入这个network namespace的方式共享同一个network namespace。用户容器和pause容器之间的关系有点类似于寄居蟹和海螺。因此,Container runtime创建Pod内的用户容器时,调用的都是同一个命令:docker run–net=none。意思是只创建一个network namespace,不初始化网络协议栈。如果这个时候通过nsenter方式进入容器,会看到里面只有一个本地回环设备lo。
    4. 容器的eth0是怎么创建出来的呢?答案是CNI。CNI主要负责容器的网络设备初始化工作。Kubelet目前支持两个网络驱动,分别是Kubenet和CNI。Kubenet是一个历史产物,即将废弃,因此本节不过多介绍。CNI有多个实现,官方自带的插件就有p2p、bridge等,这些插件负责初始化pause容器的网络设备,也就是给pause容器内的eth0分配IP等,到时候,Pod内其他容器就使用这个IP与其他容器或节点进行通信。Kubernetes主机内容器的默认组网方案是bridge。flannel、Calico这些第三方插件解决容器之间的跨机通信问题,典型的跨机通信解决方案有bridge和overlay等。

Kubernetes主机内组网模型

  • Kubernetes经典的主机内组网模型是veth pair+bridge的方式。
  • 当Kubernetes调度Pod在某个节点上运行时,它会在该节点的Linux内核中为Pod创建network namespace,供Pod内所有运行的容器使用。从容器的角度看,Pod是具有一个网络接口的物理机器,Pod中的所有容器都会看到此网络接口。因此,每个容器通过localhost就能访问同一个Pod内的其他容器。
  • Kubernetes使用veth pair将容器与主机的网络协议栈连接起来,从而使数据包可以进出Pod。容器放在主机根network namespace中veth pair的一端连接到Linux网桥,可让同一节点上的各Pod之间相互通信。

  • 如果Kubernetes集群发生节点升级、修改Pod声明式配置、更新容器镜像或节点不可用,那么Kubernetes就会删除并重新创建Pod。在大部分情况下,Pod创建会导致容器IP发生变化。也有一些CNI插件提供Pod固定IP的解决方案,例如Weave、Calico等。

  • 使用新建的bridge网桥(CNI bridge)代替docker0网桥(docker0也可以继续保留,常规容器还是用docker0,而需要互通的容器可以借助于这个工具给docker容器新建虚拟网卡并绑定IP桥接到bridge)
  • bridge和主机eth0之间是也是利用veth pair这个技术。

Kubernetes跨节点组网模型

  • Kubernetes典型的跨机通信解决方案有bridge、overlay等。
  • bridge网络本身不解决容器的跨机通信问题,需要显式地书写主机路由表,映射目标容器网段和主机IP的关系,集群内如果有N个主机,需要N-1条路由表项。
  • 至于overlay网络,它是构建在物理网络之上的一个虚拟网络,其中VXLAN是主流的overlay标准。VXLAN就是用UDP包头封装二层帧,即所谓的MAC in UDP。和bridge网络类似,Pod同样接在Linux网桥上,目的地址落在本机Pod网段的网络包同样发给Linux网桥cni0。不同的是,目的Pod在其他节点上的路由表规则。
  • bridge和overlay是Kubernetes最早采用的跨机通信方案,但随着集成Weave和Calico等越来越多的CNI插件,Kubernetes也支持虚拟路由等方式。

Pod的hosts文件

  • 与宿主机一样,容器也有/etc/hosts文件,用来记录容器的hostname和IP地址的映射关系。通过向Pod的/etc/hosts文件中添加条目,可以在Pod级别覆盖对hostname的解析。
  • 一个Pod内如果有多个容器,修改任意一个容器的hostname都会影响其他容器,因为Pod共享UTS namespace。

打通CNI与Kubernetes:Kubernetes网络驱动

  • Kubernetes支持两种网络驱动,分别是Kubenet和CNI,其中:
    ·CNI plugins:遵守appc/CNI规范,允许自由接入多个符合CNI标准的网络插件;
    ·Kubenet plugins:基于cbr0的一个单机容器网络方案,同时使用CNI的bridge和host-local插件实现一些功能。
  • CNI是容器网络的标准化,试图通过JSON描述一个容器网络配置。CNI是Kubernetes与底层网络插件之间的一个抽象层,为Kubernetes屏蔽了底层网络实现的复杂度,同时解耦了Kubernetes的具体网络插件实现。
  • CNI主要有两类接口:分别是在创建容器时调用的配置网络接口:[插图]和删除容器时调用的清理网络接口
  • 不论是配置网络接口还是清理网络接口,都有两个入参,分别是网络配置和runtime配置。网络配置很好理解,runtime配置则主要是容器运行时传入的网络namespace信息。
  • 可以简单理解就是cni就是一个开发网络插件的规范,这个规范规定了这个插件要实现的子命令(add delete)。所以如果你自己开发了一个k8网络同时实现了针对自己网络的cni插件,就是一个可执行命令,那么你的网络方案就可以被kubernetes使用。——评论

Kubernetes网络策略

  • 网络策略就是基于Pod源IP(所以Kubernetes网络不能随随便便做SNAT)的访问控制列表,限制的是Pod之间的访问。通过定义网络策略,用户可以根据标签、IP范围和端口号的任意组合限制Pod的入站/出站流量。网络策略作为Pod网络隔离的一层抽象,用白名单实现了一个访问控制列表(ACL),从Label Selector、namespace selector、端口、CIDR这4个维度限制Pod的流量进出。
  • Egress表示出站流量,即Pod作为客户端访问外部服务,Pod地址作为源地址。策略可以定义目的地址和目的端口,可以根据ports和to定义规则。ports字段用来指定目的端口和协议。to(目的地址)分为IP地址段、Pod selector和Kubernetes namespace selector;
  • Ingress表示入站流量,Pod地址和服务作为服务端(目的地址),提供外部访问。与Egress类似,策略可以定义源地址和端口,可以根据ports和from定义规则。ports字段同样用来指定目的端口和协议。from(源地址)分为IP地址段、Pod selector和Kubernetes namespace selector

Kubernetes网络故障定位指南

  • Kubernetes网络利用Linux内核Netfilter模块设置低级别的集群IP负载均衡,除了iptables和IPVS(前面已经详细解析过,这里不再赘述),还需要用到两个关键的模块:IP转发(IP forward)和桥接。

  • IP转发是一种内核态设置,允许将一个接口的流量转发到另一个接口,该配置是Linux内核将流量从容器路由到外部所必需的。

  • 如果一个容器请求外部的服务,由于容器IP是不可路由的,则远程服务器不知道应该把响应发到哪里。但事实上,只要每个主机对容器到外部的连接做一次SNAT或是Masquerade就能实现。

  • 我们的Docker主机能够和数据中心的其他机器通信,它们有可路由的IP。当一个容器尝试访问一个外部服务时,运行容器的主机将网络包中的容器IP用它本身的IP替换,即Masquerade(SNAT的一种)。对于外部服务,看起来像是和主机建立了连接。当响应返回到主机的时候,它进行一个逆转换(把网络包中的主机IP替换成容器IP)。对于容器,这个操作完全是透明的,它不知道发生了这样的一个转换。Kubernetes NodePort的实现默认就开启了Kube-proxy的–masq-all=true选项。

  • SNAT导致Linux内核丢包的原因在于其conntrack的实现。SNAT代码在POSTROUTING链上被调用两次。

  • 解决方法需要在masquerade规则中设置flag NF_NAT_RANGE_PROTO_RANDOM_FULLY。

  • 通过使用打了补丁的flannel和Kube-proxy,能够显著降低conntrack表的插入错误,使整个集群中的丢包错误的数目从每几秒一次下降到每几个小时一次。需要注意的是,iptabels的–ramdom-fully选项只能缓解集群SNAT带来的这个问题,而并不能根治。因此,并不推荐在生产环境使用NodePort。

  • pod 访问集群外的pod和访问集群外的服务ip转化过程区别,是否NAT?TODO

    • pod 访问集群外的pod不需要NAT,访问集群外的服务取决于是否打通网络和如何配置。

Kubernetes DNS架构演进之路

  • Kubernetes DNS服务目前有两个实现,分别是Kube-dns和CoreDNS。
  • 无论是Kube-dns还是CoreDNS,基本原理都是利用watch Kubernetes的Service和Pod,生成DNS记录,然后通过重新配置Kubelet的DNS选项让新启动的Pod使用Kube-dns或CoreDNS提供的Kubernetes集群内域名解析服务。

Kubernetes网络插件生态

  • Docker自己的网络方案比较简单,就是每个宿主机上会跑一个非常纯粹的Linux bridge,这个bridge可以认为是一个二层的交换机,但它的能力有限,只能做一些简单的学习和转发。出网桥的流量会经过iptables,经过NAT,最后通过路由转发在宿主之间进行通信。当真正用Docker原生的网络模型部署一个比较复杂的业务时,会遇到诸如:容器重启之后IP就变了;每台宿主机会分配固定的网段,因此同一个容器迁到不同宿主机时,除了IP发生变化,网段也会变化,随之而来的网络策略都需要调整等问题。另外,NAT的存在会造成两端在通信时看到对方的地址是不真实的,而且NAT本身也有性能损耗。这些问题都对Docker自身网络方案的应用造成了障碍。
  • 一些最常见的术语包括:
    • 第2层网络:OSI(Open Systems Interconnections,开放系统互连)网络模型的“数据链路”层。第2层网络会处理网络上两个相邻节点之间的帧传递。第2层网络的一个典型示例是以太网。
    • 第3层网络:OSI网络模型的“网络”层。第3层网络的主要关注点,是在第2层连接之上的主机之间路由数据包。IPv4、IPv6和ICMP是第3层网络协议的示例。
    • VXLAN:即虚拟可扩展的LAN。首先,VXLAN用于通过在UDP数据包中封装第2层以太网帧帮助实现大型云部署。VXLAN虚拟化与VLAN类似,但提供更大的灵活性和功能(VLAN仅限于4096个网络ID)。VXLAN是一种overlay协议,可在现有网络之上运行。
    • overlay网络:是建立在现有网络之上的虚拟逻辑网络。overlay网络通常用于在现有网络之上提供有用的抽象,并分离和保护不同的逻辑网络。
    • 封装:是指在附加层中封装网络数据包以提供其他上下文和信息的过程。
    • 在overlay网络中,封装被用于从虚拟网络转换到底层地址空间,从而能路由到不同的位置(数据包可以被解封装,并继续到其目的地)。
    • 网状网络:是指每个节点连接到许多其他节点以协作路由,并实现更大连接的网络。
    • 网状网络(mesh network)允许通过多个路径进行路由,从而提供更可靠的网络。网状网格的缺点是每个附加节点都会增加大量开销。
    • BGP:代表“边界网关协议”,用于管理边缘路由器之间数据包的路由方式。BGP通过考虑可用路径、路由规则和特定网络策略等因素,将数据包从一个网络发送到另一个网络。BGP有时被用作容器网络的路由机制,但不会用在overlay网络中。

CNI标准的胜出:从此江湖没有CNM

  • CNI即容器网络接口(Container Network Interface)。Kubernetes采用CNI而非CNM(容器网络模型),这背后有很长的一段故事,核心的原因就是CNI对开发者的约束更少,更开放,不依赖于Docker工具,而CNM对Docker有非常强的依赖,无法作为通用的容器网络标准。在CNI标准中,网络插件是独立的可执行文件,被上层的容器管理平台调用。网络插件只有两件事情要做:把容器加入网络或把容器从网络中删除。调用插件的配置通过两种方式传递:环境变量和标准输入。

  • CNI很简单,只需要:

    • 1个配置文件,配置文件描述插件的版本、名称、描述等基本信息;
    • 1个可执行文件,可执行文件就是CNI插件本身会在容器需要建立网络和需要销毁容器时被调用;
    • 读取6个环境变量,获得需要执行的操作、目标网络Namespace、容器的网卡必要信息;
    • 接受1个命令行参数,同样用于获得需要执行的操作、目标网络Namespace、容器的网卡必要信息;
    • 实现2个操作(ADD/DEL)。
  • Kubernetes使用CNI网络插件的工作流程

    • Kubernetes调用CRI创建pause容器,生成对应的network namespace;
    • 调用网络driver(因为配置的是CNI,所以会调用CNI的相关代码);
    • CNI driver根据配置调用具体的CNI插件;
    • CNI插件给pause容器配置正确的网络,Pod中的其他容器都是用pause容器的网络栈。
  • CNI的初衷是创建一个框架,用于在配置或销毁容器时动态配置适当的网络配置和资源。CNI规范概括了用于配制网络的插件接口,这个接口可以让容器运行时与插件进行协调。CNI插件负责为每个容器分配IP地址,为容器接口配置和管理IP地址,以及多主机连接相关的功能。容器运行时(runtime)会调用网络插件,从而在容器启动时分配IP地址并配置网络,并在删除容器时再次调用它以清理这些资源。容器运行时决定了容器应该加入哪个网络及它需要调用哪个插件。然后,插件会将网络接口添加到容器网络命名空间中(例如,作为一个veth pair的一端)。接着,它会在主机上进行相关配置(例如,将veth的其他部分连接到网桥上)。最后,CNI会通过调用单独的IPAM(IP地址管理)插件分配IP地址并设置路由。在Kubernetes中,Kubelet可以在适当的时间调用它找到的插件,为通过Kubelet启动的Pod进行自动的网络配置。

  • CNI的最大价值在于提供了一致的容器网络操作界面,不论是什么网络插件都使用一致的API,提高了网络配置的自动化程度和一致性的体验。

  • Kubernetes是一个支持多容器的运行环境,而Docker只是其中一个容器而已。每一个运行环境都会配置网络环境,所以当人们问“Kubernetes会支持CNM吗?”时,他们真正的意思是“Kubernetes是否在Docker运行时下支持CNM?”。当然,我们希望同一个网络插件支持所有的运行环境,但这并不是一个绝对目标。

  • Kubernetes认为CNI更适合快速开发和迭代。早期的实验尝试证明,Kubernetes可以利用CNI插件替代几乎所有Kubelet中硬编码的网络逻辑。

  • 考虑到Kubernetes和Docker项目的独立性等种种原因,促使Kubernetes选择CNI作为网络模型。这将带来诸如:docker inspect命令显示不了Pod的IP地址,直接被Docker启动的容器可能无法和被Kubernetes启动的容器通信等问题。

  • 但Kubernetes必须做一个权衡:选择CNI,使Kubernetes网络配置更简单、灵活并且不需要额外的配置(例如,配置Docker使用Kubernetes或其他网络插件的网桥)。

SDN

  • 软件定义网络(Software Defined Network,SDN)

  • 有了虚拟化网络设备后,下一步就是要使用这些设备组成网络,容器分布在不同的物理主机上,每一台物理主机都有物理网络相互联通,然而这种网络的物理拓扑结构是相对固定的,很难跟上云原生时代的分布式系统的逻辑拓扑结构变动频率,譬如服务的扩缩、断路、限流,等等,都可能要求网络跟随做出相应的变化。正因如此,软件定义网络(Software Defined Network,SDN)的需求在云计算和分布式时代变得前所未有地迫切。

  • SDN的核心思路是在物理的网络之上再构造一层虚拟化的网络,将控制平面和数据平面分离开来,实现流量的灵活控制,为核心网络及应用的创新提供良好的平台。SDN里位于下层的物理网络被称为Underlay,它着重解决网络的连通性与可管理性,位于上层的逻辑网络被称为Overlay,它着重为应用提供与软件需求相符的传输服务和网络拓扑。

  • VLAN的全称是“虚拟局域网”(Virtual Local Area Network)

  • VXLAN,这是三层虚拟化网络(Network Virtualization over Layer 3,NVO3)的标准技术规范之一,是一种典型的Overlay网络。

  • 副本网卡:MACVLAN

Kubernetes的网络实现

  • Kubernetes网络的设计主要致力于解决以下问题。
    1. 容器到容器之间的直接通信。
    2. 抽象的Pod到Pod之间的通信。
    3. Pod到Service之间的通信。
    4. 集群外部与内部组件之间的通信。

容器到容器之间的直接通信

  • 同一个Pod内的容器(Pod内的容器是不会跨宿主机的)共享同一个网络命名空间,共享同一个Linux协议栈。所以对于网络的各类操作,就和它们在同一台机器上一样,它们甚至可以用localhost地址访问彼此的端口。

Pod到Pod之间的通信

  • 每一个Pod都有一个真实的全局IP地址,同一个Node内的不同Pod之间可以直接采用对方Pod的IP地址通信,而且不需要采用其他发现机制,例如DNS、Consul或者etcd。Pod容器既有可能在同一个Node上运行,也有可能在不同的Node上运行,所以通信也分为两类:同一个Node内Pod之间的通信和不同Node上Pod之间的通信。
  • 同一个Node内Pod之间的通信
    • 同一个Node内两个Pod之间的关系可以看出,Pod1和Pod2都是通过Veth连接到同一个docker0网桥上的,它们的IP地址IP1、IP2都是从docker0的网段上动态获取的,它们和网桥本身的IP3是同一个网段的。另外,在Pod1、Pod2的Linux协议栈上,默认路由都是docker0的地址,也就是说所有非本地地址的网络数据,都会被默认发送到docker0网桥上,由docker0网桥直接中转。综上所述,由于它们都关联在同一个docker0网桥上,地址段相同,所以它们之间是能直接通信的。
  • 不同Node上Pod之间的通信
    • Pod的地址是与docker0在同一个网段的,我们知道docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行,因此要想实现不同Node上Pod容器之间的通信,就必须想办法通过主机的这个IP地址进行寻址和通信。
    • 另一方面,这些动态分配且藏在docker0之后的所谓“私有”IP地址也是可以找到的。Kubernetes会记录所有正在运行的Pod的IP分配信息,并将这些信息保存在etcd中(作为Service的Endpoint)。这些私有IP信息对于Pod到Pod的通信也是十分重要的,因为我们的网络模型要求Pod到Pod使用私有IP进行通信。所以首先要知道这些IP是什么。
    • 之前提到,Kubernetes的网络对Pod的地址是平面的和直达的,所以这些Pod的IP规划也很重要,不能有冲突。只要没有冲突,我们就可以想办法在整个Kubernetes的集群中找到它。
    • 支持不同Node上Pod之间的通信,就要满足两个条件:
      1. 在整个Kubernetes集群中对Pod的IP分配进行规划,不能有冲突;
      2. 找到一种办法,将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以互相访问。
    • 根据条件1的要求,我们需要在部署Kubernetes时对docker0的IP地址进行规划,保证每个Node上的docker0地址都没有冲突。我们可以在规划后手工配置到每个Node上,或者做一个分配规则,由安装的程序自己去分配占用。例如,Kubernetes的网络增强开源软件Flannel就能够管理资源池的分配。
    • 根据条件2的要求,Pod中的数据在发出时,需要有一个机制能够知道对方Pod的IP地址挂在哪个具体的Node上。也就是说先要找到Node对应宿主机的IP地址,将数据发送到这个宿主机的网卡,然后在宿主机上将相应的数据转发到具体的docker0上。一旦数据到达宿主机Node,则那个Node内部的docker0便知道如何将数据发送到Pod。
    • 在实际的私有云环境中,除了需要部署Kubernetes和Docker,还需要额外的网络配置,甚至通过一些软件来实现Kubernetes对网络的要求。做到这些后,Pod和Pod之间才能无差别地进行透明通信。为了达到这个目的,开源界有不少应用增强了Kubernetes、Docker的网络,几个常用的组件及其组网原理。

Pod和Service网络实战

  • Kubernetes的网络模型要求每个Node上的容器都可以相互访问。默认的Docker网络模型提供了一个IP地址段是172.17.0.0/16的docker0网桥。每个容器都会在这个子网内获得IP地址,并且将docker0网桥的IP地址(172.17.42.1)作为其默认网关。需要注意的是,Docker宿主机外面的网络不需要知道任何关于这个172.17.0.0/16的信息或者知道如何连接到其内部,因为Docker的宿主机针对容器发出的数据,在物理网卡地址后面都做了IP伪装MASQUERADE(隐含NAT)。也就是说,在网络上看到的任何容器数据流都来源于那台Docker节点的物理IP地址。这里所说的网络都指连接这些主机的物理网络。这个模型便于使用,但是并不完美,需要依赖端口映射的机制。在Kubernetes的网络模型中,每台主机上的docker0网桥都是可以被路由到的。也就是说,在部署了一个Pod时,在同一个集群内,各主机都可以访问其他主机上的Pod IP,并不需要在主机上做端口映射。综上所述,我们可以在网络层将Kubernetes的节点看作一个路由器。如果将实验环境改画成一个网络图,那么它看起来如图7.12所示。
  • 每一个新部署的容器都将使用这个Node(docker0的网桥IP)作为它的默认网关。而这些Node(类似路由器)都有其他docker0的路由信息,这样它们就能够相互连通了。
  • 首先,一个Pod内的所有容器都需要共用同一个IP地址,这就意味着一定要使用网络的容器映射模式(container模式)。然而,为什么不能只启动第1个Pod中的容器,而将第2个Pod中的容器关联到第1个容器呢?我们认为Kubernetes是从两方面来考虑这个问题的:首先,如果在Pod内有多个容器的话,则可能很难连接这些容器;其次,后面的容器还要依赖第1个被关联的容器,如果第2个容器关联到第1个容器,且第1个容器死掉的话,第2个容器也将死掉。启动一个基础容器(pause容器),然后将Pod内的所有容器都连接到它上面会更容易一些。
  • Kubernetes的kube-proxy作为一个全功能的代理服务器管理了两个独立的TCP连接:一个是从容器到kube-proxy:另一个是从kube-proxy到负载均衡的目标Pod。总结:跨node的pod到pod请求,经过自身node的kube-proxy(因为要通过etcd定位目标pod属于到哪个node),不经过目标node的kube-proxy,因为连接是直接通过目标node的eth0的(是否NAT方式,看k8s具体使用的组网实现)。

容器网络模型

  • 随着容器技术在企业生产系统中的逐步落地,用户对容器云的网络特性要求也越来越高。跨主机容器间的网络互通已经成为基本要求,更高的要求包括容器固定IP地址、一个容器多个IP地址、多个子网隔离、ACL控制策略、与SDN集成等。目前主流的容器网络模型主要有Docker公司提出的Container Network Model(CNM)模型和CoreOS公司提出的Container Network Interface(CNI)模型。
  • CNM模型:CNM模型主要通过Network Sandbox、Endpoint和Network这3个组件进行实现
  • CNI 模型:CNI提供了一种应用容器的插件化网络解决方案,定义对容器网络进行操作和配置的规范,通过插件的形式对CNI接口进行实现。CNI是由rkt Networking Proposal发展而来的,试图提供一种普适的容器网络解决方案。CNI仅关注在创建容器时分配网络资源,和在销毁容器时删除网络资源,这使得CNI规范非常轻巧、易于实现,得到了广泛的支持。
    • 在CNI模型中只涉及两个概念:容器和网络。
      1. 容器(Container):是拥有独立Linux网络命名空间的环境,例如使用Docker或rkt创建的容器。关键之处是容器需要拥有自己的Linux网络命名空间,这是加入网络的必要条件。
      2. 网络(Network):表示可以互连的一组实体,这些实体拥有各自独立、唯一的IP地址,可以是容器、物理机或者其他网络设备(比如路由器)等。对容器网络的设置和操作都通过插件(Plugin)进行具体实现,CNI插件包括两种类型:CNI Plugin和IPAM(IP Address Management)Plugin。CNI Plugin负责为容器配置网络资源,IPAM Plugin负责对容器的IP地址进行分配和管理。IPAM Plugin作为CNI Plugin的一部分,与CNI Plugin一起工作。
  • CNI和CNM并非是完全不可调和的两个模型,二者是可以进行转化的。
  • CNM阵营支持CNM标准的网络插件有:
    • Docker Swarm overlay;Macvlan&IP network drivers;Calico;Contiv。
    • Docker Libnetwork的优势就是Docker原生与Docker容器生命周期结合紧密,缺点是与Docker耦合度过高。
  • CNI阵营支持CNI标准的网络插件:
    • Weave;Macvlan;flannel;Calico;Contiv;Mesos CNI。
    • CNI的优势是兼容其他容器技术(例如rkt)及上层编排系统(Kubernetes&Mesos),而且社区活跃势头迅猛,再加上Kubernetes主推,迅速成为容器网络的事实标准。

Kubernetes网络策略

  • Network Policy的主要功能是对Pod间的网络通信进行限制和准入控制,设置方式为将Pod的Label作为查询条件,设置允许访问或禁止访问的客户端Pod列。策略控制器由第三方网络组件提供,目前Calico、Cilium、Kube-router、Romana、Weave Net等开源项目均支持网络策略的实现。
  • Network Policy的工作原理如图7.19所示,policy controller需要实现一个API Listener,监听用户设置的NetworkPolicy定义,并将网络访问规则通过各Node的Agent进行实际设置(Agent则需要通过CNI网络插件实现)。
  • egress:定义目标Pod允许访问的“出站”白名单规则,目标Pod仅允许访问满足to条件的服务端IP范围和ports定义的端口号。
  • Namespace级别还可以设置一些默认的全局网络策略,以方便管理员对整个Namespace进行统一的网络策略设置。

开源的网络组件

  • Kubernetes的网络模型假定了所有Pod都在一个可以直接连通的扁平网络空间中。这在GCE里面是现成的网络模型,Kubernetes假定这个网络已经存在。而在私有云里搭建Kubernetes集群,就不能假定这种网络已经存在了。我们需要自己实现这个网络假设,将不同节点上的Docker容器之间的互相访问先打通,然后运行Kubernetes。目前已经有多个开源组件支持容器网络模型。有几个常见的网络组件及其安装配置方法,包括Flannel、Open vSwitch、直接路由和Calico等。

Flannel

  • Flannel之所以可以搭建Kubernetes依赖的底层网络,是因为它能实现以下两点。

    1. 它能协助Kubernetes,给每一个Node上的Docker容器都分配互相不冲突的IP地址。
    2. 它能在这些IP地址之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内。
  • Flannel之间的底层通信协议的可选技术包括UDP、VxLan、AWS VPC等多种方式。通过源flanneld封包、目标flanneld解包,最终docker0收到的就是原始的数据,对容器应用来说是透明的,感觉不到中间Flannel的存在。

  • 根据不同的封包方式,flannel提供了UDP和VXLAN两种传输方法。

  • 事实上,flannel的Gateway模式的性能甚至要比Calico好。然而,由于flannel只能修改各个主机的路由表,一旦主机之间隔了其他路由设备,比如三层路由器,这个包就会在路由设备上被丢掉。这样一来,Host-Gateway的模式就只能用于二层直接可达的网络,由于广播风暴的问题,这种网络通常是比较小规模的。

  • flannel的底层实现实质上是一种overlay网络(除了Host-Gateway模式),即把某一协议的数据包封装在另一种网络协议中进行路由转发。

  • 总的来说,flannel是大多数用户的不错选择。从管理角度来看,它提供了一个简单的网络模型,用户只需要一些基础知识,就可以设置适合大多数用例的环境。与其他方案相比,flannel相对容易安装和配置,许多常见的Kubernetes集群部署工具和许多Kubernetes发行版都可以默认安装flannel。

  • Open vSwitch是一个开源的虚拟交换机软件,有点儿像Linux中的bridge,但是功能要复杂得多。Open vSwitch的网桥可以直接建立多种通信通道(隧道)

  • 无论是OVS还是Flannel,通过覆盖网络提供的Pod到Pod通信都会引入一些额外的通信开销,如果是对网络依赖特别重的应用,则需要评估对业务的影响。

Calico

  • Calico是一个基于BGP的纯三层的网络方案,与OpenStack、Kubernetes、AWS、GCE等云平台都能够良好地集成。Calico在每个计算节点都利用Linux Kernel实现了一个高效的vRouter来负责数据转发。每个vRouter都通过BGP1协议把在本节点上运行的容器的路由信息向整个Calico网络广播,并自动设置到达其他节点的路由转发规则。Calico保证所有容器之间的数据流量都是通过IP路由的方式完成互联互通的。Calico节点组网时可以直接利用数据中心的网络结构(L2或者L3),不需要额外的NAT、隧道或者Overlay Network,没有额外的封包解包,能够节约CPU运算,提高网络效率,如图7.24所示。 Calico不使用额外的封包解包Calico在小规模集群中可以直接互联,在大规模集群中可以通过额外的BGP route reflector来完成。

  • 此外,Calico基于iptables还提供了丰富的网络策略,实现了Kubernetes的Network Policy策略,提供容器间网络可达性限制的功能。

  • Calico的主要组件如下。

    1. Felix:Calico Agent,运行在每个Node上,负责为容器设置网络资源(IP地址、路由规则、iptables规则等),保证跨主机容器网络互通。
    2. etcd:Calico使用的后端存储。
    3. BGP Client:负责把Felix在各Node上设置的路由信息通过BGP协议广播到Calico网络。
    4. Route Reflector:通过一个或者多个BGP Route Reflector来完成大规模集群的分级路由分发。
    5. CalicoCtl:Calico命令行管理工具。
  • IP Pool可以使用两种模式:BGP或IPIP。

  • Calico的设计比较新颖,flannel的host-gw模式之所以不能跨二层网络,是因为它只能修改主机的路由,Calico把改路由表的做法换成了标准的BGP路由协议。相当于在每个节点上模拟出一个额外的路由器,由于采用的是标准协议,Calico模拟路由器的路由表信息可以被传播到网络的其他路由设备中,这样就实现了在三层网络上的高速跨节点网络。

  • 现实中的网络并不总是支持BGP路由,因此Calico也设计了一种ipip模式,使用overlay的方式传输数据。ipip的包头非常小,而且是内置在内核中的,因此它的速度理论上要比VXLAN快,但是安全性更差。

  • 虽然flannel被公认为是最简单的选择,但Calico以其性能、灵活性闻名于Kubernetes生态系统。Calico的功能更全面,正如Calico的slogan提到的:为容器和虚拟机工作负载提供一个安全的网络连接。

  • Calico以其丰富的网络功能著称,不仅提供容器的网络解决方案,还可以用在虚拟机网络上。除了网络连接,网络策略是Calico最受追捧的功能之一。使用Calico的策略语言,可以实现对容器、虚拟机工作负载和裸机主机各节点之间网络通信进行细粒度和动态的安全规则控制。Calico基于iptables实现了Kubernetes的网络策略,通过在各个节点上应用ACL(访问控制列表)提供工作负载的多租户隔离、安全组及其他可达性限制等功能。此外,Calico还可以与服务网格Istio集成,以便在服务网格层和网络基础架构层中解释和实施集群内工作负载的网络策略。

  • Calico还支持容器漂移。因为Calico配置的三层网络使用BGP路由协议在主机之间路由数据包。BGP路由机制可以本地引导数据包,这意味着无须overlay网络中额外的封包解包操作。由于免去了额外包头(大部分情况下依赖宿主机IP地址)封装数据包,容器在不同主机之间迁移没有网络的限制。

  • Calico在每一个计算节点利用Linux内核的一些能力实现了一个高效的vRouter负责数据转发,而每个vRouter通过BGP把自己运行的工作负载的路由信息向整个Calico网络传播。

  • BGP Route Reflector(BIRD)简单的BGP可能成为较大规模部署的性能瓶颈,因为它要求每个BGP客户端连接到网状拓扑中的每一个其他BGP客户端。随着集群规模的增大,一些设备的路由表甚至会被撑满。

  • 因此,在较大规模的部署中,Calico建议使用BGP Route Reflector(路由器反射器)。互联网中通常使用BGP Route Reflector充当BGP客户端连接的中心点,从而避免与互联网中的每个BGP客户端进行通信。Calico使用BGP Route Reflector是为了减少给定一个BGP客户端与集群其他BGP客户端的连接。用户也可以同时部署多个BGP Route Reflector服务实现高可用。Route Reflector仅仅是协助管理BGP网络,并没有工作负载的数据包经过它们。

  • 然而在需要使用overlay网络的环境中,Calico也提供了IP-in-IP(简称ipip)的隧道技术

  • 为Pod分配固定IP很多传统的应用程序,在上容器云时都会有使用固定IP地址的需求。虽然这种要求并不符合Kubernetes对网络的基本假定,但Calico IPAM却支持为Pod分配固定IP。

  • 为什么Calico网络选择BGP

    • 我们都知道Calico使用了BGP,那么为什么Calico要选择BGP呢?为什么选择BGP而不是一个IGP协议(如OSPF或者IS-IS)?
    • 要弄清楚原因,我们需要先明确目前BGP和IGP是如何在一个大规模网络中工作的。任何网络,尤其是大型网络,都需要处理两个不同的路由问题:
      1. 发现网络中路由器之间的拓扑结构。
      2. 发现网络中正在工作的节点,以及可到达该网络的外部连接。
    • 网络中的端点(Endpoint)没有运行路由协议的能力,而路由器(Router)可以。同一个网络内的两个网络之间通过路由器连接。IGP需要执行大量复杂计算,才能让每台设备在同一时刻都能得到对所处网络拓扑的相同认知。这其实就限制了IGP所能运行的规模。在一个IGP的单一视图内应该只包含几十个(极端情况下可能是小几百个)路由器,以满足这些规模和性能的需求。虽然存在一些可以突破规模限制的技术[比如在OSPF中使用area(区域)或者在IS-IS中使用Level(层),两者都可以认为是将网络切割成若干个单一视图],但这些技术也带来了其他架构上的限制(超出本书范畴这里不再展开)。IGP也被限制在它们所能通告的最大Endpoints数量上,这个数量浮动范围比较大,但是上限也就在几千到1万多。当IGP中的路由数量超过5000或6000条时,许多大型网络的管理人员都会感到紧张。那么BGP呢?截至2015年1月,在公共互联网上宣告的路由超过526000条,一些网络中甚至拥有超过100万的节点。为了解决大规模网络中的路由可扩展性问题,BGP被开发出来。BGP可以在一个网络中扩容到几百台路由器的规模,而如果使用BGP Route Reflection这一数量更是可以到达数万台。如果需要的话,BGP可以宣告数百万条路由信息,并通过非常灵活的策略加以管理。因此,我们就能理解为什么Calico使用BGP宣告网络端点的路由了。一言以蔽之,就是提高路由规则的可扩展性以满足大规模组网的需求。在Calico设计的网络中,可能会出现几万台路由器,以及潜在的几百万条路由信息或者Endpoints,这种数量级是与IGP的设计不匹配的,但是BGP可以,特别是当我们使用BGP Route Reflection扩展路由器数量的时候。Calico的核心设计思想是:使用通用的互联网工具和技术实现大规模网络互连结构。产生这种设计思想的主要原因是互联网行业在运营真正大型网络上已经积累了几十年的经验,使用的工具和技术(例如BGP)也已经过长时间的磨炼,如果把这些工作全部丢进垃圾桶,重新造轮子就不够明智了。对于那些数据终端规模已经接近互联网规模的公有云环境,一个云上可用区就可以轻松承载数千到数万个服务器,并运行着几万甚至小几十万个虚拟机(在Calico中称为Endpoints)。如果这些虚拟机中再运行容器,则Endpoints的数量可能还会增加一个或两个数量级。因此,从规模角度,我们应该尽量复用互联网运营商的成功经验。总结Calico网络使用BGP的原因有:(1)BGP是一种简单的路由协议。(2)拥有当前行业的最佳实践。(3)唯一能够支撑Calico网络规模的协议。
  • Calico是一个比较有意思的虚拟网络解决方案,完全利用路由规则实现动态组网,通过BGP通告路由。由于Calico利用宿主机协议栈的三层确保容器之间跨主机的连通性,报文的流向完全通过路由规则控制,没有overlay,没有NAT,直接经过宿主机协议栈处理,因此我们称Calico是一个纯三层的组网方案,转发效率也较高。Calico的核心设计思想就是Router,它把每个操作系统的协议栈看成一个路由器,然后把所有的容器看成连在这个路由器上的网络终端,在路由器之间运行标准的BGP,并让节点自己学习这个网络拓扑该如何转发。然而,当网络端点数量足够大时,自我学习和发现拓扑的收敛过程非常耗费资源和时间。Calico的缺点是网络规模会受到BGP网络规模的限制。Calico路由的数目与容器数目相同,极易超过三层交换、路由器或节点的处理能力,从而限制了整个网络的扩张,因此Calico网络的瓶颈在于路由信息的容量。Calico会在每个节点上设置大量的iptables规则和路由规则,这将带来极大的运维和故障排障难度。Calico的原理决定了它不可能支持VPC,容器只能从Calico设置的网段中获取IP。Calico目前的实现没有流量控制的功能,会出现少数容器抢占节点多数带宽的情况。当然,我们可以结合CNI的bandwidth插件实现流量整形。Calico的应用场景主要在IDC内部,Calico官方推荐将其部署在大二层网络上,这样所有路由器之间是互通的。所谓大二层就是没有任何三层的网关,所有的机器、宿主机、物理机在二层是可达的。大二层主要的问题是弹性伸缩的问题。频繁开关机的时候,容器启停虽然不影响交换机,但容易产生广播风暴。事实上,在很多有经验的网络工程师眼里,大二层存在单一故障问题,也就是说,任何一个都会有一定的硬件风险让整个大二层瘫痪。因此,实际场景经常会把集群划分成多个网段,对外是三层网络的结构。如图5-18所示,从架构上看,Calico在每个节点上会运行两个主要的程序,一个是Felix,另一个是BIRD。Felix会监听etcd的事件并负责配置节点上容器的网络协议栈和主机上的iptables规则及路由表项。BIRD会从内核里获取IP的路由发生了变化的信息,并告知Route Reflector。Route Reflector是一个路由程序,它会通过标准BGP的路由协议扩散到其他宿主机上,让集群的其他容器都知道这个IP。 Calico网络拓扑参考资料:https://www.lijiaocn.com/%E9%A1%B9%E7%9B%AE/2017/04/11/calico-usage.html.

  • Calico 不足:

    • BGP 支持问题:需要网路设备支持 BGP 协议,否则需要追加 IPIP 隧道;
    • 规划 2 层直连:需要节点做良好的规划实现 2 层网络直接互联;
    • 大规模配置复杂:网络规划,手动部署 Route Reflector,增加 API 代理。

Weave

  • 支持数据加密的网络插件Weave是CNCF的官方子项目,是其第一个也是目前唯一一个容器网络插件项目。用一个词评价Weave,就是“功能齐全”,有网络通信、有安全策略、有域名服务、有加密通信还有监控。Weave的工作模式与flannel相似,它最早只提供了UDP(称为sleeve模式)的网络模式,后来又加上了fastpath方式(基于VXLAN和OVS),不过Weave消除了flannel中用来存储网络地址的额外组件etcd,自己集成了高可用的数据存储功能。
  • Weave控制面在实现上与Calico类似,数据面在1.2版本前使用userspace实现,即通过UDP封装实现L2 overlay。Weave在1.2版本后,结合了Linux内核的Open vSwitch datapata(odp)和VXLAN,在网络性能上有较大的提升。由于odp和VXLAN与内核相关模块结合较为紧密,因此在实际使用过程中可能会遇到一些和内核相关的问题

Cilium

  • 为微服务网络连接安全而生什么是Cilium?Cilium是“纤毛”的意思,它十分细小又无处不在。Cilium官方的定位是:API-aware Networking and Security plugin,helping Linux Secure Microservices.Cilium是一个具备API感知的网络和安全的开源软件,该软件用于透明地保护使用Linux容器管理平台(如Docker和Kubernetes)部署的应用程序服务之间的网络连接。
  • 为什么使用Cilium在回答为什么使用Cilium这个问题之前,我们先来探讨为什么从单机时代便广泛应用的iptables在微服务时代显得有些力不从心?
    1. iptables在微服务时代的限制作为通用操作系统的一个组件,iptables专注于为Linux管理员提供系统安全性管理的“瑞士军刀”,即基于静态环境的IP和端口配置网络转发、过滤等规则。然而,随着iptables在大规模、高度动态的微服务环境中投入使用,原始设计目标与现代基础设施需求之间的不匹配愈发明显。前文在对Kube-proxy转发模式讨论时就提到了基于iptables服务负载均衡的严重性能瓶颈。除了性能和规模,由于容器的创建和销毁非常频繁,基于IP做身份关联的故障排除和安全审计等功能很难实现。现代数据中心应用程序的开发已经转向面向服务的体系结构(SOA),即我们常说的微服务。基于微服务的应用程序被拆分为一个个独立的小型服务,这些服务使用HTTP、gRPC和Kafka等轻量级协议,通过API相互通信。但是,现有的Linux网络安全机制(例如iptables)仅在网络和传输层(即IP地址和端口)上运行,并且缺乏对微服务层的可见性(visibility)。微服务架构下的应用程序,尤其是通过容器部署的是高度动态变化的。在向高度动态的微服务架构的转变过程中,确实给微服务之间的连接安全性提出了挑战和机遇。具体表现为:传统的Linux网络安全方法(例如iptables)过滤IP地址和TCP/UDP端口,但容器高度不稳定的生命周期导致这些方法难以与应用程序并排扩展。因为负载均衡和访问控制列表要不断更新,系统可能要维护成千上万条规则,这给运维带来了较大负担。出于更精细的安全考虑,协议端口不能再用于区分应用流量,因为同一端口可能承载跨服务的各种消息。另一个挑战是提供准确的可见性,因为传统系统使用IP地址作为主要识别工具,而IP在微服务架构中的寿命可能才几秒。归根结底,通用的基于IP/端口的防火墙方式在微服务架构中的网络和安全面临一系列限制。因此我们不禁发问:如果在微服务时代从头开始设计内核Linux网络和安全方法会是什么样子?
    2. BPF:让Linux感知微服务幸运的是,我们拥有BPF(Berkeley Packet Filter)。一句话总结BPF就是:BPF是Linux内核中的一个高性能沙箱虚拟机,它将内核变成了可编程的。作为一种Linux内核的黑科技,BPF可以在不影响安全性或性能的情况下扩展Linux内核,提供了内核的数据包过滤机制。跟netfilter和tc等一样,BPF是一个框架,用于在内核中的各个挂钩点运行自定义逻辑,这其中就包括Linux网络协议栈中的多处挂载点。这也是那些基于BPF实现的profiling和tracing(例如tcpdump)工具的工作原理。BPF给用户提供两种SOCKET选项:SO_ATTACH_FILTER和SO_ATTACH_BPF,允许用户在sokcet上添加自定义的filter,只有满足该filter指定条件的数据包才会上发到用户空间。SO_ATTACH_FILTER插入的是cBPF(classic Berkeley Packet Filter,即经典BPF,我们说的BPF值就是cBPF)代码,SO_ATTACH_BPF插入的是eBPF(extended Berkeley Packet Filter,即扩展BPF)代码。从Linux 3.15开始,eBPF被引入内核,eBPF扩充了cBPF的功能,丰富了指令集但保留了对cBPF的兼容。例如,tcpdump还是用的cBPF,但cBPF字节码被加载到内核后会被内核自动转换为eBPF字节码。注:若不特殊说明,本书不区分cBPF和eBPF,统一用BPF指代。BPF的工作原理是在内核提供了一个虚拟机,用户态将过滤规则以虚拟机指令的形式传递到内核,由内核根据这些指令来过滤网络数据包。BPF逻辑被编写为简单的“BPF程序”,它们在运行前先要通过验证,以确保它们在任何情况下都不会导致运行它的内核崩溃。验证之后,这些程序被一个JIT(just in time)编译器编译成CPU相关的代码(例如X86)在内核态运行,这意味着它们以与编译到内核中的代码相同的速度运行。最重要的是,任何网络报文都没法绕过BPF在内核态的限制。要理解BPF的作用,首先要意识到Linux内核本质上是事件驱动的!写数据到磁盘,读写socket,请求定时器等,这些过程都是系统调用,都是事件驱动的。世界上最大的单体应用,有着1000万行代码量的Linux无时无刻不在处理各种事件。BPF给我们提供了在事件发生时运行指定的BPF程序的能力。例如,我们可以在以下事件发生时运行我们的BPF程序:·应用发起read/write/connect等系统调用;·TCP发生重传;·网络包达到网卡。BPF在过去几年中发展迅速,Netflix、Facebook和Google等在Linux上做了大量投资的公司,都在积极探索使用BPF作为内核的可扩展性机制,把Linux打造成一个可感知微服务的操作系统。为什么这么说呢?BPF能够使Linux内核感知到API层。当内核能够理解两个应用程序通信过程中调用了哪些API时,它便能够为API调用提供安全保障,并以此构建一个基于身份认证的机制。因此,不同于以前简单的IP+Port过滤网络包的方式,有了BPF加持的Linux内核可以理解什么是一个微服务,微服务的标签有哪些,这个微服务的安全性是怎么样的。
    3. Cilium:把BPF带到Kubernetes首先,Cilium是一个CNI插件,它提供网络连通性、服务负载均衡等功能,但主打的功能还是安全。例如,Cilium实现了Kubernetes的网络策略API,还提供了基于身份认证的微服务安全机制。从一开始,Cilium就是为大规模、高度动态的容器环境而设计的。Cilium在3/4层运行,除了提供传统的网络和安全服务,还有一些L7的负载均衡和流量过滤功能。区别于传统系统中的IP地址识别,Cilium原生地了解服务/容器/Pod标识,并解析HTTP、gRPC和Kafka等协议,提供比传统防火墙更简单、更强大的可见性和安全性。BPF的高效灵活是Cilium的基础。Cilium支持在各种集成点(例如,网络IO、应用程序套接字和跟踪点)中将BPF字节码动态插入Linux内核,为工作负载(包括进程和容器)提供透明的网络连接保护、负载均衡、安全和可观测性支持。最关键的是,BPF的使用使得Cilium能够以高度可扩展的方式实现以上功能,尤其能够应对大规模的微服务场景。Cilium基于BPF,但为用户隐藏了BPF的复杂性,提供了与通用编排框架(例如Kubernetes等)Mesos的集成。Cilium的工作原理如图5-26所示。[插图]图5-26 Cilium的工作原理BPF的强大功能可实现高效的内核数据转发,为常见的微服务用例提供巨大的性能优势,例如Cilium就可以作为Kubernetes服务负载均衡(iptables或IPVS)和Istio本地代理(Envoy)的可选项。
    4. Cilium功能一览具体来说,Cilium实现了以下功能。1)容器的网络连接Cilium的网络模型较简单,即一个三层网络空间为所有服务端点提供链接,并通过策略层实现安全控制。Cilium支持以下跨节点网络模型。·overlay:基于封装的虚拟网络产生所有主机。目前,已支持基于VXLAN和Geneve等封包协议;·直接路由:使用Linux主机内置或云提供商的路由表,通过底层网络路由应用程序容器的IP地址。从架构上看,Cilium将安全与网络寻址进行解耦,极大简化了网络模型,也提高了扩展性,降低了排错难度。2)基于策略的网络安全Cilium同时提供基于数据包和API的网络安全与认证,为传统部署和微服务架构提供安全的网络连接。Cilium的网络策略分为以下几大类。·基于身份:在每个包内,Cilium将负载和身份信息打包在一起(而不是依靠源IP地址),提供高可扩展安全性;·基于IP/CIDR:如果基于身份的方式不适用,那么可以采用基于IP/CIDR安全的方式控制安全访问。Cilium建议在安全策略中尽量采用抽象方式,避免写入具体IP地址。例如,使用Kubernetes的Service(通过label selector选择后端Pod)名;·基于API:HTTP/REST、gRPC和Kafka通过暴露IP和端口对外提供服务,其安全机制明显不足。基于API的网络策略允许使用更细粒度的安全限制,例如REST方法等。下文会详细展开介绍Cilium的安全机制,这里不再赘述。3)分布式可扩展负载均衡BPF提供高性能L3/L4的服务转发与负载均衡,转发策略有rr、wrr、源hash等。基于散列表实现的BPF提供O(1)时间复杂度的路由性能(这一点与IPVS很像),也就是说,Cilium可以替换Kube-proxy实现Kubernetes Service机制。这样所有Kubernetes集群IP服务会自动在BPF中得到高效地实现,而且性能不会随着服务数量的增加而下降。4)可视化与网络策略类似,Cilium也同时在网络包和API调用两个层面实现了可视化。所有可视化信息,不仅是IP地址和端口号,还包括丰富的工作流元数据,例如容器和Pod标签、服务名等。这样一来,Cilium就能提供基于标签、安全身份和事件类型的过滤和可视化。Cilium的可视化基于BPF高性能循环缓冲区(perf ring buffer),可以追踪每秒百万级的应用事件。除此之外,还利用BPF可编程性的高效通道允许数据可视化同时降低额外负担。最后,Cilium的所有可视化都对外提供API,可以嵌入现有系统中。5)监控Cilium周期性地监控集群连接状态,包括节点之间延迟、节点失效和底层网络问题等,并且可以将Cilium整合到Prometheus监控系统中。
  • 总的来说,Cilium使用BPF作为底层引擎,创建了一个精确优化的网络堆栈,用于在Kubernetes等平台上运行API驱动的微服务。下文将重点讨论使用Cilium带来的两个主要好处:·不只简单关注数据包、IP地址和端口,而是将服务标识和API协议(例如HTTP、gRPC和Kafka)视为平台中的一等公民;·针对在微服务环境中越来越常见的规模、动态和部署模式(例如服务网格代理)优化Linux网络转发、可见性和过滤。
  • 然而在同一主机上,将数据从一个Linux套接字复制到另一个Linux套接字可以做到非常高效。因此,一个直观的想法是如果服务和sidecar运行在同一台宿主机上,那么我们可以直接在两个socket之间复制数据,这将带来极大的性能提升(3~4倍)。这也是Cilium和BPF使Linux内核可感知微服务的一个例子。
  • 简而言之,如果采用Service Mesh这种架构,那么使用Cilium+Sockmap应该是减少CPU/内存使用和降低延迟的一种简单方法。
  • 在云原生带来的微服务浪潮下,尽管几乎所有关于如何设计和运行应用程序的内容都在变化,但像iptables(基于内核的netfilter)这样的内核功能仍然是Kubernetes、Mesos、Docker等现代微服务环境中网络路由、包过滤、安全性和记录网络数据的最常用工具。然而,在高度动态和复杂的微服务世界中,仅仅通过IP地址和端口的传统镜头来思考网络和安全性会导致实现效率非常低,只能实现过程可见性和过滤,并且通常非常复杂且难以排查。由于BPF是Linux内部强大的新内核可扩展性机制,使我们有机会在微服务时代重新思考Linux网络和安全堆栈并解决这些问题。Cilium通过将安全性与寻址分离,不仅可以在高度动态的环境中应用安全策略,还提供了除传统的L3/L4网络隔离外的应用层安全限制。Cilium将不再基于传统的“IP+Port”的方式做网络策略,而是基于“身份”,这将带来可见性和安全性,使用起来也更简单,而且功能上更加强大(支持对单个RPC调用的细粒度控制)。参考资料:http://Cilium.readthedocs.io/en/stable/bpf/.

CNI-Genie

  • 一个直观的想法是能不能在同一个容器集群中集成多个网络插件,博采众长?下面介绍一个由华为开源的多网络插件:CNI-Genie。CNI-Genie是一个集成引导插件,本身无具体功能,由引用的插件完成网络功能,支持flannel、Calico、Weave Net、Canal、Romana等CNI插件,还支持SR-IOV、DPDK等。值得一提的是,CNI-Genie本身也是一个符合CNI标准的容器网络插件。
  • CNI-Genie本质上就是Kubernetes和底层多个CNI插件之间的适配器(adapter)。
  • CNI-Genie使用户能够在同一个集群中运行多个CNI,并有助于为每个容器创建多个接口。事实上,多网络平面是CNI-Genie的一个重要功能。需要注意的是,使用CNI-Genie所集成的网络插件才是容器多网络平面和多网卡(IP地址)的真正提供者。

提供给Kubernetes集群外访问

  1. 采用NodePort是解决上述问题的最直接、有效的常见做法。(将Service的端口号映射到物理机)
    • NodePort的实现方式是在Kubernetes集群里的每个Node上都为需要外部访问的Service开启一个对应的TCP监听端口,外部系统只要用任意一个Node的IP地址+具体的NodePort端口号即可访问此服务
    • 在任意Node上运行netstat命令,就可以看到有NodePort端口被监听: netstat -tlp | grep 31002
    • NodePort没有完全解决外部访问Service的所有问题,比如负载均衡问题。
  2. 将容器应用的端口号映射到物理机。
    • 设置容器级别的hostPort,将容器应用的端口号映射到物理机上。
    • 通过设置Pod级别的hostNetwork=true,该Pod中所有容器的端口号都将被直接映射到物理机上。
  3. DNS服务搭建
    • 在集群内需要能够通过服务名对服务进行访问,这就需要一个集群范围内的DNS服务来完成从服务名到ClusterIP的解析。
  4. Ingress:HTTP 7层路由机制
    • 在Kubernetes中,Ingress Controller将以Pod的形式运行,监控API Server的/ingress接口后端的backend services,如果Service发生变化,则Ingress Controller应自动更新其转发规则。

容器 vs 虚拟机

  • 容器是进程级别的隔离技术,因此相比虚拟机有启动快、占用资源少、体积小等优点。
  • 容器与虚拟机对比传统的虚拟机需要模拟整台机器,包括硬件(因此,虚拟机方案需要硬件的支持,例如VT-X),每台虚拟机都需要有自己的操作系统。虚拟机一旦被开启,预分配给它的资源将全部被占用。每台虚拟机包括应用程序、必要的依赖库,以及一个完整的用户操作系统。容器和宿主机共享操作系统,而且可以实现资源的动态分配。容器包含应用程序和所依赖的软件包,并且不同容器之间共享内核,这与虚拟机相比大大节省了额外的资源占用。在宿主机操作系统中,不同容器在用户空间以隔离的方式运行着各自的进程。虚拟机和容器最大的区别在于没有Guest OS(客户虚拟机)这一层。
  • 虚拟机和容器都是在硬件和操作系统以上的,虚拟机有Hypervisor层,Hypervisor即虚拟化管理软件,是整个虚拟机的核心所在。它为虚拟机提供了虚拟的运行平台,管理虚拟机的操作系统运行。每台虚拟机都有自己的操作系统及系统库。容器没有Hypervisor这一层,也没有Hypervisor带来性能的损耗,每个容器和宿主机共享硬件资源及操作系统。
  • Docker容器有Docker Engine这一层。其实Docker Engine远远比Hypervisor轻量,它只负责对Linux内核namespace API的封装和调用,真正的内核虚拟化技术是由Linux提供的。容器的轻量带来的一个好处是资源占用远小于虚拟机。同样的硬件环境,可以运行容器的数量远大于虚拟机,这对提供系统资源利用率非常有用。每台虚拟机都有一个完整的Guest OS,Guest OS能为应用提供一个更加隔离和安全的环境,不会因为应用程序的漏洞给宿主机造成任何威胁。
  • 从虚拟化层面来看,传统虚拟化技术是对硬件资源的虚拟,容器技术则是对进程的虚拟,从而提供更轻量级的虚拟化,实现进程和资源的隔离。从架构来看,容器比虚拟机少了Hypervisor层和Guest OS层,使用Docker Engine进行资源分配调度并调用Linux内核namespace API进行隔离,所有应用共用主机操作系统。因此在体量上,Docker较虚拟机更轻量级,在性能上优于虚拟化,接近裸机性能。

NAT

  • SNAT: Source Network Address Translation,是修改网络包源ip地址的。
  • DNAT: Destination Network Address Translation,是修改网络包目的ip地址的。

Istio

Service Mesh

  • 在Kubernetes逐渐普及的时代,Service Mesh技术已完全取代了使用软件库实现网络运维的方式。严格来说,Service Mesh并不在Kubernetes的核心范围之内。但是,在Kubernetes的帮助下,应用上云后,还面临着服务治理的难题。现在,大多数云原生的应用都是微服务架构,微服务的注册。服务之间的相互调用关系,服务异常后的熔断、降级,调用链的跟踪、分析等一系列现实问题摆在各机构面前。Service Mesh就是解决这类微服务发现和治理问题的一个概念。
  • 在我看来,Service Mesh之于微服务架构就像TCP之于Web应用。
    Istio提供了真正可供操作、非侵入式的方案,相对于Spring Cloud、Dubbo这些SDK方式让人有种耳目一新的感觉。
  • 只有在服务数量和服务间调用的复杂度上升到一定程度后,Service Mesh才会真正派上用场。
  • 所谓sidecar模式,翻译过来就是边车模式,是一种分布式和微服务架构的设计模式,目的是实现了控制和逻辑的分离与解耦。
  • 软件设计中的sidecar模式通过给应用服务加装一个“边车”达到控制和逻辑分离的目的。该设计模式通过给应用程序加上一个“边车”的方式拓展应用程序现有的功能,例如日志记录、监控、流量控制、服务注册、服务发现、服务限流、服务熔断等在业务服务中不需要实现的控制面功能,可以交给“边车”,业务服务只需要专注于实现业务逻辑即可。
  • sidecar模式一般有两种实现方式:·通过SDK的形式,在开发时引入该软件包依赖,使其与业务服务集成起来。这种方法可以与应用密切集成,提高资源利用率并且提高应用性能,但也对代码有侵入,受到编程语言和软件开发人员水平的限制;·agent形式。服务所有的通信都是通过这个agent代理的,这个agent同服务一起部署,和服务一起有着相同的生命周期创建。这种方式对应用服务没有侵入性,不受编程语言和开发人员水平的限制,做到了控制与逻辑分开部署。但是会增加应用延迟,并且管理和部署的复杂度会增加。
  • Service Mesh作为sidecar运行时,对应用程序来说是透明的,所有应用程序间的流量都会通过sidecar,然后由sidecar转发给应用程序。换句话说,由于sidecar劫持了流量,所以对应用程序流量的控制都可以在sidecar中实现。
  • William Morgan在What’s a service mesh?And why do I need one文章中指出Service Mesh有以下几个特点:·应用程序间通信的中间层;·轻量级网络代理;·应用程序无感知;·解耦应用程序的重试/超时、监控、追踪和服务发现。Service Mesh将底层那些难以控制的网络通信统一管理,诸如流量管控、丢包重试、访问控制等。而上层的应用层协议只须关心业务逻辑。Service Mesh是一个用于处理服务间通信的基础设施层,它负责为构建复杂的云原生应用传递可靠的网络请求。
  • Kube-proxy实现了流量在Kubernetes Service的负载均衡,但是没法对流量做细粒度的控制,例如灰度发布和蓝绿发布(按照百分比划分流量到不同的应用版本)等。Kubernetes社区提供的蓝绿发布案例其实是针对Deployment的,但不支持Service。
  • Istio Service Mesh把Kubernetes看作服务注册机构,通过控制平面生成数据平面的配置,数据平面的透明代理以sidecar容器的方式部署在每个应用服务的Pod中。之所以说是透明代理,是因为应用程序容器完全无感知代理的存在。区别在于Kube-proxy拦截的是进出Kubernetes节点的流量,而Istio sidecar拦截的是进出该Pod的流量。

Istio

  • 什么是Istio?官方给出的定义是:An open platform to connect,secure,control and control services.即一个提供微服务连接的、安全的、流量控制的和可观察性的开放平台。Istio分为两个平面:数据平面和控制平面。数据平面由一组sidecar的代理(Envoy)组成。这些代理调解和控制微服务之间的所有网络通信,并且与控制平面的Mixer通信,接受调度策略。控制平面通过管理和配置Envoy管理流量。此外,控制平面配置Mixers来实施路由策略并收集检测到的监控数据
  • 在安装Istio核心组件之前,需要安装一个“服务注册器”,这个“服务注册器”既可以是Kubernetes,也可以是Nomad & Consul。下面笔者以Kubernetes为例,讲解如何在Kubernetes集群中安装Istio控制平面。
  • Istio提供多种安装路径,具体取决于你环境中的Kubernetes平台。但不论平台如何,基本流程都是相同的,即:(1)确认Istio对Pod和服务的要求。(2)安装Kubernetes。(3)在Kubernetes上安装Istio。

Istio sidecar透明注入

  • 网格中的每个Pod都必伴随着一个Istio的sidecar一同运行。下文中将会介绍两种把sidecar注入Pod的方法:使用istioctl客户端工具进行注入,或者使用Istio sidecar injector自动完成注入过程,并且深入sidecar内部解析其工作原理。
  • 需要注意的是,跟手工注入不同,自动注入过程是发生在Pod级别的,因此不会看到Deployment本身发生什么变化。但是可以使用kubectl describe观察单独的Pod,在其中能看到注入sidecar的相关信息。1. 验证sidecar注入部署sleep应用,检查是不是只产生了一个容器。

Istio CNI插件

  • Istio当前默认使用特权init容器istio-init将访问用户容器的流量转发到Envoy。istioinit的主要作用是运行脚本配置容器内的iptables规则。Istio CNI插件的主要设计目标是消除这个特权init container,使用Kubernetes CNI机制实现相同功能的替代方案,因此它是Kubernetes CNI的一个具体实现。

云原生架构

  • 根据云计算服务提供的内容,业界把云计算分成三层:基础架构即服务(IaaS)、平台即服务(PaaS)和软件即服务(SaaS)。根据云计算服务提供的来源和服务对象,云计算分为公有云和私有云。
  • 在虚拟化计算和云计算服务蓬勃发展的阶段,人们也意识到了虚拟化技术的弊端。虚拟化技术虚拟出来的是一个完整操作系统,它的底层包括宿主机操作系统和虚拟化层,势必导致虚拟机的性能低于物理机的性能。此外,完整的操作系统所占用的存储空间较大,而且启动一个虚拟机,等同于启动一个完整操作系统。但是往往在虚拟服务器中可能仅仅是为了运行某一个软件而已。为此,LXC(Linux Container)技术和Docker技术开始出现。它摒弃了启动完整系统的弊端,在现有操作系统上对任务进行隔离,并实现资源按需分配。它允许多个容器共享一个操作系统内核,容器内存储的仅仅是与某个应用紧密相关的资源,其空间占用往往只有几十到几百MB。单独容器化如同虚拟PC一样会面临高可用性不足、管理低级等问题。为此,业界推出了容器编排技术。

什么是云原生

  • 云原生(Cloud Native)概念是由Pivotal的Matt Stine在2013年首次提出的。这个概念得到了社区的不断完善,内容越来越丰富,目前已经包括了DevOps(Development和Operations的组合)、持续交付(Continuous Delivery,CD)、微服务(MicroServices)、敏捷基础设施(Agile Infrastructure)和十二要素(The Twelve-Factor App)等几大主题。这个概念不但包括根据业务能力对企业(高校)进行文化、组织架构的重组与建设,也包括方法论和原则,以及具体的操作工具。采用基于云原生的技术和管理方法,可以更好地从云中诞生业务,也可以把业务迁移到不同的云中,从而享受云的高效与持续服务的能力。
  • 2015年云原生计算基金会(CNCF)成立,对云原生定义进行了修改,认为云原生需要包含应用容器化、面向微服务架构以及支持容器编排调度等方面的内容。
  • 云三层模型与云原生架构的对比图所示,原先的IaaS层升级为敏捷基础设施,而PaaS和SaaS层则合并为微服务架构。敏捷基础设施和微服务都属于技术范畴的架构。在整个云原生架构中,也少不了自动化的持续交付和DevOps等管理措施。
  • 在传统的应用系统开发过程中,软件开发商喜欢聚焦在业务系统,专注于系统如何开发、如何闭源成一个独立的整体系统。但是随着开源软件的盛行,全球合作背景下的分工细化,再加之GitHub的影响力越来越大,一个软件开发商很难在短时间内处理所有问题。软件开发商应该充分利用第三方开源或不开源的组件,自己仅仅实现必要的代码,再借助敏捷基础架构进行灵活多变的必要集成,从而节省大量人力、物力和时间,以便更加聚焦业务开发,同时又能利用整体协作快速部署业务。云原生的意义就在于此,按照云原生的理念进行顶层架构设计、实施、部署,即可实现快速迭代,投入生产使用。云原生主要包括两部分内容:云原生基础架构和云原生应用。

云原生基础架构

  • Kubernetes也不能简单地称为云原生基础架构。Kubernetes的容器编排技术为云原生基础架构提供了必要的平台支撑功能。是否是云原生基础架构的关键在于是否使用自动化处理的方式。

云原生应用

  • 云原生应用程序的关键在于提供弹性、敏捷性、可操作性和可观察性。

  • 快速了解云原生架构

  • 概念随着新的技术发展而演化。

    • 第一阶段:容器化封装 +自动化管理 + 面向微服务
    • 第二阶段:DevOps、持续交付、微服务、容器
    • 第三阶段:DevOps、持续交付、容器、服务网格、微服务、声明式API对云原生的解构
  • 云原生应用:docker 应用打包、发布、运行,Kubernetes 服务部署和集群管理,Istio 构建服务治理能力。


TODO

  1. Netfilter、BPF、SNAT
  2. 虚拟网络相关基础知识(Veth、Linux Bridge、Open vSwitch等)
  3. VxLan、vlan、Open vSwitch、Macvlan、IPvlan等的分类、区别、关系
  4. NVIDIA和AMD两个厂商的GPU、NUMA、Huge Page
  5. 现在是用veth还是IPvlan?
  6. 学习各种网络插件,比对和总结。
  7. istio、Service Mesh

Reference

  • Kubernetes权威指南:从Docker到Kubernetes实践全接触(第4版)
  • Kubernetes网络权威指南:基础、原理与实践
  • 云原生架构进阶实战
  • docker网络之veth设备
  • Linux虚拟网络设备之bridge(桥)
  • 一文详细讲述—Linux网络虚拟化
  • K8S 网络详解 3 CNI 与 CNM 网络模型
  • Kubernetes利用CNI-bridge插件打通网络
  • 60道重要的Kubernetes面试题
  • 一文带你理解云原生 – 很全面的总结!!!
  • 一文深入理解 Kubernetes

IT中的生活哲学

发表于 2021-07-06

前言

  • 最近看完了《SRE:Google运维解密》之后,越来越觉得IT届的很多解决方案、流程设计、技术原理都来源于生活,或者说跟生活上的很多场景很类似。
  • 本文的标题或许不太正确,并没字面上的包含关系,两者有时是相互借鉴的。
  • 本文只是草稿,将持续记录个人认为的一些”生活哲学“。

生活哲学

混沌工程

  1. 在生活中总会出现一些小问题或事件,我们也会定时做一些演练,这样一旦发生大的突变事件时,我们才能更好的应对。

数据库落盘

  1. 为了保证数据不丢,在数据落盘时是现写日志,而不是直接修改数据行。这跟生活中点菜时记录客户的订单或者平常待办事件的备忘一个道理。既可以保证数据不丢,又可以提升处理时间。

《SRE:Google运维解密》-笔记

发表于 2021-07-05

序言

  • 可靠性就像安全性,越早关注越好。
  • 这就意味着一些小型创业公司,在应付日常面临的种种挑战时,也应该抽出一部分精力来面对可靠性这个话题。这与盖房子有些类似,如果一开始将整个地基打好并保持继续修缮,要比盖好房子之后再重新修改设计要容易得多
  • Margaret 曾经说过:“无论对一个软件系统运行原理掌握得多么彻底,也不能阻止人犯意外错误。”
  • 只有靠着对细节的不懈关注,做好充足的灾难预案和准备工作,时刻警惕着,不放过一切机会去避免灾难发生。这就是SRE 最重要的理念!

第1章 介绍

  • 不能将碰运气当成战略。

Google的解决之道:SRE

  • SRE就是让软件工程师来设计一个新型运维团队的结果。

  • 目前来看,UNIX 系统内部细节和1~3层网络知识是Google最看重的两类额外的技术能力。

    • (a)对重复性、手工性的操作有天然的排斥感。
    • (b)有足够的技术能力快速开发出软件系统以替代手工操作。
  • 从本质上来说,SRE 就是在用软件工程的思维和方法论完成以前由系统管理员团队手动完成的任务。这些SRE倾向于通过设计、构建自动化工具来取代人工操作。

  • Google的经验法则是,SRE团队必须将50%的精力花在真实的开发工作上。

  • 由于SRE模型中为了提高可靠性需要采取一些与常规做法违背的做法,所以需要强有力的管理层支持才能推行下去。例如:由于一个季度内的错误预算耗尽而停止发布新功能的决定,可能需要管理层的支持才能让产品研发部门重视起来。

  • 我们可以认为DevOps是SRE核心理念的普适版,可以用于更广范围内的组织结构、管理结构和人员安排。同时,SRE是DevOps模型在Google的具体实践,带有一些特别的扩展。

SRE方法论

  • 一般来说,SRE团队要承担以下几类职责:可用性改进,延迟优化,性能优化,效率优化,变更管理,监控,紧急事务处理以及容量规划与管理。

  • 事后总结应该包括以下内容:事故发生、发现、解决的全过程,事故的根本原因,预防或者优化的解决方案。

  • Google 的一项准则是“对事不对人”,事后总结的目标是尽早发现和堵住漏洞,而不是通过流程去绕过和掩盖它们。

  • 一般来说,任何软件系统都不应该一味地追求100% 可靠。因为对最终用户来说,99.999% 和 100% 的可用性是没有实质区别的

  • 如果100% 不是一个正确的可靠性目标,那么多少才是呢?这其实并不是一个技术问题,而是一个产品问题。

  • 如果一个服务的可靠性目标是99.99%,那么错误预算就是 0.01%。这意味着产品研发部门和SRE部门可以在这个范围内将这个预算用于新功能上线或者产品的创新等任何事情。错误预算可以用于什么范畴呢?研发团队需要用这个预算上线新功能,吸引新用户。理想情况下,我们应该使用错误预算来最大化新功能上线的速度,同时保障服务质量。这个基本模型建立起来之后,许多常见的战术策略,例如灰度发布、1% AB测试等就全说得通了。这些战术性手段都是为了更合理地使用整个服务的错误预算。通过引进“错误预算”的概念,我们解决了研发团队和SRE团队之间的组织架构冲突。SRE团队的目标不再是 “零事故运行”,SRE团队和产品研发团队目标一致,都是在保障业务服务可靠性需求的同时尽可能地加快功能上线速度。这个改动虽小,意义却很大。一次“生产事故”不再是一件坏事,而仅仅是创新流程中一个不可避免的环节,两个团队通过协作共同管理它。

  • 一个需要人工阅读邮件和分析警报来决定目前是否需要采取某种行动的系统从本质上就是错误的。监控系统不应该依赖人来分析警报信息,而是应该由系统自动分析,仅当需要用户执行某种操作时,才需要通知用户。

  • 一个监控系统应该只有三类输出。紧急警报(alert)意味着收到警报的用户需要立即执行某种操作,目标是解决某种已经发生的问题,或者是避免即将发生的问题。工单(ticket)意味着接受工单的用户应该执行某种操作,但是并非立即执行。系统并不能自动解决目前的情况,但是如果一个用户在几天内执行这项操作,系统不会受到任何影响。日志(logging)平时没有人需要关注日志信息,但是日志信息依然被收集起来以备调试和事后分析时使用。正确的做法是平时没人会去主动阅读日志,除非有特殊需要。

  • 任何需要人工操作的事情都只会延长恢复时间。一个可以自动恢复的系统即使有更多的故障发生,也要比事事都需要人工干预的系统可用性更高。当不可避免地需要人工介入时,我们也发现与“船到桥头自然直”的态度相比,通过事先预案并且将最佳方法记录在“运维手册(playbook)”上通常可以使MTTR 降低3倍以上。初期几个万能的工程师的确可以解决生产问题,但是长久看来一个手持“运维宝典”经过多次演习的on-call工程师才是正确之路。

  • 虽然不论多么完备的“运维手册”也无法替代人的创新思维,但是在巨大的时间压力和产品压力下,运维手册中记录的清晰调试步骤和分析方法对处理问题的人是不可或缺的。因此,Google SRE将大部分工作重心放在“运维手册”的维护上,同时通过“Wheel of Misfortune”等项目[2]不断培训团队成员。

  • 变更管理SRE的经验告诉我们,大概 70% 的生产事故由某种部署的变更而触发。变更管理的最佳实践是使用自动化来完成以下几个项目:● 采用渐进式发布机制。● 迅速而准确地检测到问题的发生。● 当出现问题时,安全迅速地回退改动。

  • 容量规划有几个步骤是必需的:● 必须有一个准确的自然增长需求预测模型,需求预测的时间应该超过资源获取的时间。● 规划中必须有准确的非自然增长的需求来源的统计。● 必须有周期性压力测试,以便准确地将系统原始资源信息与业务容量对应起来。

个人总结

  1. 尽早关注安全性;关注细节和充分的准备
  2. 自动化工具替代人工操作
  3. 事后总结的若干要点;事后总结“对事不对人”
  4. 使用错误预算来最大化新功能上线的速度;目标不再是 “零事故运行”
  5. 自动恢复的系统;事先预案和“运维手册;自动化减少人为事故
  6. 容量规划;周期性压力测试和调整

第3章 拥抱风险

  • 你可能认为Google会试图构建一个百分之百可靠的服务。事实证明,超过一定值后,再提高可靠性对于一项服务(和它的用户)来说,结果可能会更差而不是更好!极端的可靠性会带来成本的大幅提升:过分追求稳定性限制了新功能的开发速度和将产品交付给用户的速度,并且很大程度地增加了成本,这反过来又减少了一个团队可以提供的新功能的数量。

  • 此外,用户通常不会注意到一项服务在高可靠性和极端可靠性之间的差异,因为用户体验主要是受较不可靠的组件主导,例如手机移动网络或者他们正在使用的设备。简单地说,用户在一个有着99%可靠性的智能手机上是不能分辨出99.99%和99.999%的服务可靠性的区别的!基于这一点,SRE旨在寻求快速创新和高效的服务运营业务之间的风险的平衡,而不是简单地将服务在线时间最大化。这样一来,我们可以优化用户的整体幸福感,平衡系统的功能、服务和性能。

管理风险

  • 不可靠的系统会很快侵蚀用户的信心,所以我们想要减少系统出故障的几率。然而,经验表明,在构建系统的过程中,可靠性进一步提升的成本并不是线性增加的—可靠性的下一个改进可能比之前的改进成本增加100倍。

度量服务的风险

  • Google标准做法是通过一个客观的指标来体现一个待优化的系统属性。

  • 可用性=系统正常运行时间/(系统正常运行时间+停机时间)

  • 在Google内部,基于时间的可用性通常是毫无意义的。
    我们通过请求成功率来定义服务可用性。
    可用性=成功请求数/总的请求数

  • 使用请求成功率指标量化计划外停机时间使得这种指标更适合在不直接服务终端用户的系统中使用。

  • 通常,我们会为一项服务设定季度性的可用性目标,每周甚至每天对性能进行跟踪。我们通过寻找、跟踪和调整重要的、不可避免的偏差来使服务达到一个高层次的可用性目标。

服务的风险容忍度

  • 为了辨别服务的风险容忍度,SRE必须与产品负责人一起努力,将一组商业目标转化为明确的可以实现的工程目标。

  • 尽管当时YouTube已经有了一个很出色的产品,但它仍然在不断变化和快速发展着。因此,我们为YouTube设定了一个相比我们企业的产品更低的可用性目标,因为快速发展更加重要。

  • 对于一项给定的服务的故障预期是另一个需要重点考虑的因素。我们的业务对于服务的停机时间的容忍程度有多高?持续的低故障率或者偶尔发生的全网中断哪一个会更糟糕?这两种类型的故障可能会导致绝对数量上完全相同的错误被返回,但可能对于业务的影响相差很大。下面这个例子说明了一个提供私人信息的系统中自然发生的完全和部分服务中断的区别。假设有一个联系人管理应用程序,一种情况是导致用户头像显示失败的间歇性故障,另一种情况是将A用户的私人联系列表显示给B用户的故障。第一种情况显然是一个糟糕的用户体验,SRE会努力去快速地解决这个问题。然而,在第二种情况下,暴露私人数据的风险可能会破坏基本的用户信任。因此,在第二种情况下,在进行调试和事后的数据清理时,完全停止该服务更加恰当。

  • 对于Google提供的其他服务,有时候,我们可以接受计划内的常规的服务中断。几年前,Ads前端曾经就是这样的一种服务。它是广告商和网站创建者用来建立、配置、运行和监控他们的广告活动的服务。因为这项工作大部分发生在正常工作时间内,我们认为维修窗口中发生的偶然的、正常的、计划之中的故障是可以接受的,并且我们把这些故障看作计划内停机时间,而不是计划外停机时间。

使用错误预算的目的

  • 产品研发的绩效是如何很大程度通过产品研发速度体现的,这会激励员工尽可能快地创建新的代码。同时, SRE 的绩效表现取决于该服务的可靠性,这意味着SRE 会对高频率的更改提出抗议。两个团队之间的信息不对称进一步加剧了这种内在的紧张局势。产品开发者更了解编写和发布他们的代码所需的时间,而SRE则更关注服务可靠性程度(以及生产环境中的其他相关事项)。

  • 软件对故障的容忍度对意外事件的容忍程度有多高?做得太少,我们就只能设计出一个脆弱无用的产品。做得太多,我们的产品可能没有人会使用(但运行非常稳定)。

  • 测试新发布的代码的最好做法就是在一个典型工作负载的服务子集中进行测试,这种做法通常被称为金丝雀测试。

  • 一项决策越是基于数据做出的,常常就越好。

  • 错误预算提供了一个明确的、客观的指标来决定服务在一个单独的季度中能接受多少不可靠性。这个指标在SRE与产品研发部门的谈判中将政治因素排除。

  • 我们的实际做法如下:● 产品管理层定义一个SLO,确定一项服务在每个季度预计的正常运行时间。● 实际在线时间是通过一个中立的第三方来测算的:我们的监控系统。● 这两个数字的差值就是这个季度中剩余的不可靠性预算。● 只要测算出的正常在线时间高于SLO,也就是说,只要仍然有剩余的错误预算,就可以发布新的版本。

  • 错误预算的主要好处就是它能够激励产品研发和SRE一起找出创新和可靠性之间合理的平衡点。

  • 只要系统符合SLO,就可以继续发行新版本。如果频繁地违反SLO 导致错误预算被耗尽,那么发布就会暂停,同时需要在系统测试和开发环节投入更多资源使得系统更有弹性,以使性能得到提升。

  • 有比这种简单的开/关技术更巧妙和有效的方法:[2]例如,当SLO 违规导致错误预算接近耗尽时,将发布的速度减慢,或者回退到上一版本。

  • 例如,如果产品研发人员想要在测试上节约时间或者想要提高发布速度并且SRE 表示反对时,那么就可以通过错误预算指导决策。当预算剩余很多时,产品研发人员就可以承担更多的风险。如果预算接近耗尽,产品研发人员自身将会推动更多的测试或者放慢发布的速度,因为他们不想冒着用尽预算的风险和拖延他们的程序上线。实际上,产品开发团队这样就开始进行自我监管。他们知道预算还剩多少,并且可以控制自己的风险。(当然,这要求SRE在SLO达不到的时候有权停止程序的发布。)如果网络中断或者数据中心发生故障影响了SLO,怎么办?这样的事件也会给错误预算带来不良的影响,会使本季度剩余部分的发布将会减少。整个团队会支持这种发布频率的降低,因为每个人都有义务保障服务正常运行。

  • 利用错误预算可以同时找到制定得过高的可用性目标,显示出它们所导致的灵活性和创新速度方面的问题。如果团队无法发布新的功能,他们可以选择降低SLO(从而增加错误预算)来提高创新速度。

  • 管理服务的可靠性主要在于管理风险,而且管理风险的成本可能很高。

  • ● 100%可能永远都不是一个正确的可靠性目标:不仅是不可能实现的,而且它通常比一项服务的用户期望的可靠性大得多。我们要将服务风险和愿意承担的业务风险相匹配。● 错误预算在SRE和产品研发团队之间调整激励,同时强调共同责任。错误预算使得讨论发布速率更容易,同时可有效地减少任何关于事故的讨论。这样,多个团队可以毫无怨言地对生产环境风险度达成一致。


第4章 服务质量目标

  • 如果不详细了解服务中各种行为的重要程度,并且不去度量这些行为的正确性的话,就无法正确运维这个系统,更不要说可靠地运维了

  • 我们需要利用一些主观判断结合过去的经验以及对服务的理解来定义一些服务质量指标(SLI)、服务质量目标(SLO),以及服务质量协议(SLA)。

服务质量术语

  • SLO的选择和公布可以帮助设立用户对服务质量的预期。

  • 该策略可以应对那些没有根据的抱怨—“服务太慢了”。如果没有一个明确的SLO,用户经常会按照自己的理解设置一个服务性能的预期,即使这可能跟运维人员或者设计者所想的完全不同。这种问题可能会导致对某个服务的过度依赖—用户错误地认为这个服务会比实际情况更可靠

  • 由于真正的全球Chubby服务故障出现的频率太低,以至于其他服务负责人开始认为全球Chubby服务永远不会出故障,从而不停地将更多的服务依赖于此。
    Chubby全球服务的高可靠性实际上提供了一种安全假象,因为这些服务实际上在Chubby全球服务不可用的时候不能正常工作,不管这种情况是多么罕见。

  • 每个季度,如果真实故障没有将可用性指标降低到SLO之下,SRE会有意安排一次可控的故障,将服务停机。利用这种方法,我们可以很快找出那些对Chubby全球服务的不合理依赖,强迫服务的负责人尽早面对这类分布式系统的天生缺陷。

  • 不管某个服务是否具有SLA,定义SLI与SLO,并且用它们来管理服务质量都是很有价值的。

指标在实践中的应用

  • 只有理解用户对系统的真实需求才能真正决定哪些指标是否有用。指标过多会影响对那些真正重要的指标的关注,而选择指标过少则会导致某些重要的系统行为被忽略。一般来说,四五个具有代表性的指标对系统健康程度的评估和关注就足够了。

  • 常见的服务,根据它们的相关SLI通常会归类为以下几个大类。● 用户可见的服务系统,例如莎士比亚搜索服务的前端服务器通常关心可用性、延迟,以及吞吐量。换句话说:是否能正常处理请求?每个请求花费的时间是多少?多少请求可以被处理?● 存储系统通常强调:延迟、可用性和数据持久性。换句话说:读写数据需要多少时间?我们是否可以随时访问数据?数据是否一段时间内还能被读取?扩展讨论参见第26章。● 大数据系统,例如数据处理流水线系统,一般来说关心吞吐量和端到端延迟。换句话说:处理了多少数据?数据从输入到产出需要多少时间?(某些流水线任务还会关注某个单独处理阶段的延迟。)● 所有的系统都应该关注:正确性。是否返回了正确的回复,是否读取了正确的数据,或者进行了正确的数据分析操作。正确性是系统健康程度的一个重要指标,但是它更关注系统内部的数据,而不是系统本身,所以这通常不是SRE直接负责的。指标的收集利用某种监控系统,大部分指标数据都在服务器端被收集,例如Borgmon(具体参见第10章)或者Prometheus。或者利用某种日志分析系统,例如分析日志中HTTP 500回复所占的比例。然而,某些系统可以加入对客户端数据的收集,否则可能会错失一些不影响服务器端指标,但是对用户产生影响的问题。例如,只关注莎士比亚服务器搜索后端的延迟可能会错失由页面JavaScript脚本导致的用户可见的延迟问题。在这个例子中,度量页面在浏览器中可用的延迟是度量用户体验的一个更好的指标。

  • 平均请求延迟可能看起来很简单,但是却掩盖了一个重要的细节;很可能大部分请求都是很快的,但是长尾请求速度却很慢。

  • 大部分指标都应该以“分布”,而不是平均值来定义。

  • 响应时间的分布越分散,意味着普通用户受到长尾请求延迟的影响就越明显,这可能预示了负载过高情况下出现的排队问题。

  • 一般来说,SRE更倾向于分析一组数据的百分比分布,而非其算术平均值。长尾效应比算术平均值更有特点,使用百分比分布能够更清晰地进行分析。因为计算机系统的本身特质决定,数据是具有特定分布特点的 ——例如,请求延迟必须大于0,同时如果超时设置为1000ms,则不可能有成功请求超过这个时间。因此,我们不能假设算术平均值和中位数是相等的——它们甚至可能相差甚远!

  • 汇总间隔:每1分钟汇总一次● 汇总范围:集群中的全部任务● 度量频率:每10秒一次● 包含哪些请求:从黑盒监控任务发来的HTTP GET请求● 数据如何获取:通过监控系统获取服务器端信息得到● 数据访问延迟:从收到请求到最后一个字节被发出

目标在实践中的应用

  • 我们应该从思考(或者调研)用户最关心的方面入手,而非从现在能度量什么入手。

  • 不要仅以目前的状态为基础选择目标

  • 了解系统的各项指标和限制非常重要,但是仅仅按照当前系统的标准制定目标,而不从全局出发,可能会导致团队被迫长期运维一个过时的系统,没有时间去推动架构重构等任务。

  • 避免绝对值虽然要求系统可以在没有任何延迟增长的情况下无限扩张,或者“永远”可用是很诱人的,但是这样的要求是不切实际的。就算有一个系统能够做到这一点,它也需要花很长时间来设计和构建,同时运维也很复杂—最关键的是,这可能比用户可以接受的(甚至是很开心地接受的)标准要高太多。

  • 不要追求完美我们可以随着时间流逝了解系统行为之后优化SLO的定义。刚开始可以以一个松散的目标开始,逐渐收紧。这比一开始制定一个困难的目标,在出现问题时放松要好得多。

  • 如果服务一切正常,可能力量应该花在其他的优先级上,例如消除技术债务、增加新功能,或者引入其他产品等。

个人总结

  1. 没有根据的抱怨—“服务太慢了”;指标制定
  2. 大部分指标都应该以“分布”,而不是平均值来定义。
  3. 如果服务一切正常,可能力量应该花在其他的优先级上,例如消除技术债务、增加新功能,或者引入其他产品等。

第5章 减少琐事

  • 如果系统正常运转中需要人工干预,应该将此视为一种Bug。

  • “正常”的定义会随系统的进步而不断改变。要把更多的时间花费在长期项目研发上而非日常运维中。因为术语日常运维可能会被误解,我们在这里使用一个专门的词语——琐事(toil)。

琐事的定义

  • 琐事不仅仅代表“我不喜欢做的工作”。

  • 到底什么是琐事?琐事就是运维服务中手动性的,重复性的,可以被自动化的,战术性,没有持久价值的工作。而且,琐事与服务呈线性关系的增长。并不是每件琐事都有以上全部特性,但是,每件琐事都满足下列一个或多个属性:手动性

  • 例如手动运行脚本以便自动执行一些任务。运行一个脚本可能比手动执行脚本中的每一步要快,但具体运行脚本所花费的手动的时间(而非脚本所需要的运行时间)应该被认为是琐事。重复性的如果某件事是第一次做,甚至第二次做,都不应该算作琐事。琐事就是不停反复做的工作。如果你正在解决一个新出现的问题或者寻求一种新的解决办法,不算作琐事。

  • 可以被自动化的如果计算机可以和人类一样能够很好地完成某个任务,或者通过某种设计变更来彻底消除对某项任务的需求,这项任务就是琐事。如果主观判断是必需的,那么很大程度上这项任务不属于琐事。[8]

  • 战术性的琐事是突然出现的、应对式的工作,而非策略驱动和主动安排的。处理紧急警报是琐事。我们可能永远无法完全消除这种类型的工作,但我们必须继续努力减少它。没有持久价值如果在你完成某项任务之后,服务状态没有改变,这项任务就很可能是琐事。如果这项任务会给服务带来永久性的改进,它就不是琐事。一些繁重的工作—比如挖掘遗留代码和配置并且将它们清理出去也不是琐事。与服务同步线性增长如果在工作中所涉及的任务与服务的大小、流量或用户数量呈线性增长关系,那这项任务可能属于琐事。一个良好管理和设计的服务应该至少可以应对一个数量级的增长,而不需要某些一次性工作(例如增加资源)之外的额外工作。

为什么琐事越少越好

  • SRE的一个公开目标是保持每个SRE的工作时间中运维工作(即琐事)的比例低于50%。SRE至少花50%的时间在工程项目上,

  • 以减少未来的琐事或增加服务功能。增加服务功能包括提高可靠性、性能,或利用率,同时也会进一步消除琐事。

什么算作工程工作

  • 工程工作通常是有创新性和创造性的,着重通过设计来解决问题,解决方案越通用越好。

琐事繁多是不是一定不好

  • 琐事不会总是让每个人都不开心,特别是不太多的时候。已知的和重复性的工作有一种让人平静的功效。完成这些事可以带来一种满足感和快速胜利感。琐事可能是低风险低压力的活动,有些员工甚至喜欢做这种类型的工作。

  • 琐事的存在并不总是坏事,但是每个人都必须清楚,在SRE所扮演的角色中,一定数量的琐事是不可避免的,这其实是任何工程类工作都具有的特点。少量的琐事存在不是什么大问题。但是一旦琐事的数量变多,就会有害了。如果琐事特别繁重,那就应该非常担忧,大声抱怨。在许多琐事有害的原因中,有如下因素需要考虑:职业停滞如果花在工程项目上的时间太少,你的职业发展会变慢,甚至停滞。Google确实会奖励做那些脏活累活的人,但是仅仅是该工作是不可避免,并有巨大的正面影响的时候才会这样做。没有人可以通过不停地做脏活累活满足自己的职业发展。

  • 士气低落每个人对自己可以承担的琐事限度有所不同,但是一定有个限度。过多的琐事会导致过度劳累、厌倦和不满。另外,牺牲工程实践而做琐事会对SRE组织的整体发展造成损害。

  • 原因如下:造成误解。我们努力确保每个SRE以及每个与SRE一起工作的人都理解SRE是一个工程组织。如果个人或者团队过度参与琐事,会破坏这种角色,造成误解。进展缓慢琐事过多会导致团队生产力下降。如果SRE团队忙于为手工操作和导出数据救火,新功能的发布就会变慢。开创先例如果SRE过于愿意承担琐事,研发同事就更倾向于加入更多的琐事,有时候甚至将本来应该由研发团队承担的运维工作转给SRE来承担。其他团队也会开始指望SRE接受这样的工作,这显然是不好的。促进摩擦产生即使你个人对琐事没有怨言,你现在的或未来的队友可能会很不开心。如果团队中引入了太多的琐事,其实就是在鼓励团队里最好的工程师开始寻找其他地方提供的更有价值的工作。违反承诺那些为了项目工程工作而新入职的员工,以及转入SRE的老员工会有被欺骗的感觉,这非常不利于公司的士气。

小结

  • 如果我们都致力于每一周通过工程工作消除一点琐事,就可以持续性地整顿服务。我们就可以将更多的力量投入到扩大服务规模的工程工作上去,或者是进行下一代的服务的架构设计,又或者是建立一套跨SRE使用的工具链。让我们多创新,少干琐事吧!

个人总结

  • 琐事应该尽量自动化,把时间创造性的事情上!

为什么要监控

  • 紧急警报的处理会占用员工的宝贵时间。如果该员工正在工作时间段,该警报的处理会打断他原本的工作流程。如果该员工正在家,紧急警报的处理则会影响他的个人生活,甚至是把他从睡眠中叫醒。当紧急警报出现得太频繁时,员工会进入“狼来了”效应,怀疑警报的有效性甚至忽略该警报,有的时候在警告过多的时候甚至会忽略掉真实发生的故障。由于无效信息太多,分析和修复可能会变慢,故障时间也会相应延长。高效的警报系统应该提供足够的信息,并且误报率非常低。

对监控系统设置合理预期

  • 但是监控系统中最重要的一点就是整个“生产故障,人工处理紧急警报,简单定位和深入调试”过程必须要保持非常简单,必须能被团队中任何一个人所理解。

现象与原因

  • 监控系统应该解决两个问题:什么东西出故障了,以及为什么出故障。

  • “现象”和“原因”的区分是构建信噪比高的监控系统时最重要的概念

黑盒监控与白盒监控

  • 黑盒监控是面向现象的,代表了目前正在发生的—而非预测会发生的—问题,即“系统现在有故障”。白盒监控则大量依赖对系统内部信息的检测,如系统日志、抓取提供指标信息的HTTP节点等。白盒监控系统因此可以检测到即将发生的问题及那些重试所掩盖的问题等。

  • 这里应该注意,在一个多层系统中,某一个服务的现象是另外一个服务的原因。

  • 白盒监控有时是面向现象的,有时是面向原因的,这取决于白盒系统所提供的信息。

  • 黑盒监控可以保证系统只在某个问题目前正在发生,并且造成了某个现象时才会发出紧急警报。

4个黄金指标

  • 监控系统的4个黄金指标分别是延迟、流量、错误和饱和度(saturation)。

  • 延迟增加是饱和度的前导现象。99% 的请求延迟(在某一个小的时间范围内,例如一分钟)可以作为一个饱和度早期预警的指标。

  • 如果我们度量所有这4个黄金指标,同时在某个指标出现故障时发出警报(或者对于饱和度来说,快要发生故障时),能做到这些,服务的监控就基本差不多了。

关于长尾问题

  • 区分平均值的“慢”和长尾值的“慢”的一个最简单办法是将请求按延迟分组计数(可以用来制作直方图):延迟为0~10ms之间的请求数量有多少,30~100ms之间,100~300ms之间等。

度量指标时采用合适的精度

  • 如果我们的监控目标需要高精度数据,但是却不需要极低的延迟,可以通过一些内部采样机制外部汇总的方式降低成本。

  • 这种方式使我们可以观测到短暂的CPU热点,但是又不需要为此付出高额成本进行收集和保留高精度数据

简化,直到不能再简化

  • 那些不常用的数据收集、汇总,以及警报配置应该定时删除(某些SRE团队的标准是一个季度没有用到一次即将其删除)。

将上述理念整合起来

  • ● 每当收到紧急警报时,应该立即需要我进行某种操作。每天只能进入紧急状态几次,太多就会导致“狼来了”效应。● 每个紧急警报都应该是可以具体操作的。● 每个紧急警报的回复都应该需要某种智力分析过程。如果某个紧急警报只是需要一个固定的机械动作,那么它就不应该成为紧急警报。● 每个紧急警报都应该是关于某个新问题的,不应该彼此重叠。

小结

  • E-mail警报的价值通常极为有限,很容易变成噪声。我们应该倾向于构建一个良好的监控台页面,直接显示所有的非紧急的异常情况。

个人总结

  1. 避免太多“紧急警报”造成“狼来了”效应
  2. 监控系统应该解决两个问题:什么东西出故障了,以及为什么出故障。
  3. 监控系统的4个黄金指标分别是延迟、流量、错误和饱和度(saturation)。

自动化的价值

  • 任何一个人或者一群人执行数百次动作时,不可能保证每次都用同样的方式进行:没有几个人能像机器一样永远保持一致。这种不可避免的不一致性会导致错误、疏漏、数据质量的问题和可靠性问题。在这个范畴内—一致性地执行范围明确、步骤已知的程序—是自动化的首要价值。

自动化的应用案例

  • 广泛使用的工具有Puppet、Chef、cfengine,甚至 Perl都提供了自动化完成特定任务的方法,主要区别在于对帮助进行自动化的组件的抽象层次不同。

可靠性是最基本的功能

  • 民航[20]或工业应用中—经常会指出高效的自动化的缺点[21]:随着时间的推移,操作员与系统的有用的、直接接触会逐渐减少,因为自动化会覆盖越来越多的日常活动。不可避免的,当自动化系统出现问题时,操作员将无法成功地操作该系统。

小结

  • 团队应该在开发流程开始时就留出一定资源进行发布工程工作。尽早采用最佳实践和最佳流程可以降低成本,以免未来重新改动这些系统。

个人总结

  1. 自动化很重要,但也要避免自动化工具出问题时,人工无法处理

第9章 简单化

  • 可靠性只有靠对最大程度的简化不断追求而得到。

  • 软件系统本质上是动态的和不稳定的。[25]只有真空中的软件系统才是永远稳定的。如果我们不再修改代码,就不会引入新的Bug。如果底层硬件或类库永远不变,这些组件也就不会引入Bug。如果冻结当前用户群,我们将永远不必扩展系统。事实上,一个对SRE管理系统的方法不错的总结是:“我们的工作最终是在系统的灵活性和稳定性上维持平衡。”

乏味是一种美德

  • 必要复杂度是一个给定的情况所固有的复杂度,不能从该问题的定义中移除,而意外复杂度则是不固定的,可以通过工程上的努力来解决。
  • 例如,编写一个Web服务器需要处理快速提供Web页面的必要复杂度。但是,如果我们用Java编写该服务器,试图减少GC的影响就可能会引入意外复杂度。

我绝对不放弃我的代码

  • 那些由于功能开关没有启用而没有被执行的代码,就像一个定时炸弹一样等待爆炸,正如Knight Capital的痛苦经历
  • 审查代码以确保它确实符合商业目标,定期删除无用代码,并且在各级测试中增加代码膨胀检测。

“负代码行”作为一个指标

  • 我曾经做过的一些最令人满意的编码工作就是删除了数千行已经没用的代码。

最小 API

  • 法国诗人 Antoine de Saint Exupery 曾写道,“不是在不能添加更多的时候,而是没有什么可以去掉的时候,才能达到完美。”
    (参见文献[Sai39])这个原则同样适用于软件的设计和构建。API是这个规则应该遵循的一个清晰的例子。

  • 书写一个明确的、最小的API 是管理软件系统管理简单性必要的部分。我们向API消费者提供的方法和参数越少,这些API就越容易理解,我们就能用更多的精力去尽可能地完善这些方法。同时,一个反复出现的主题是:有意识地不解决某些问题可以让我们能够更专注核心问题,使得我们已有的解决方案更好。在软件工程上,少就是多!一个很小的,很简单的API通常也是一个对问题深刻理解的标志。

个人总结

  1. 我们的工作最终是在系统的灵活性和稳定性上维持平衡
  2. 必要复杂度是业务固有的,而意外复杂度则是不固定的,可以通过工程上的努力来解决
  3. “不是在不能添加更多的时候,而是没有什么可以去掉的时候,才能达到完美。”

第Ⅲ部分 具体实践

  • 正确的解决方案不一定是当场把问题一次性修复好,而可以靠降低系统准确度、关闭一些不重要的功能,或者将用户流量导向其他没有问题的任务实例等手段暂时缓解问题。解决方案的细节肯定是和每个服务和团队相关的。但是如何有效地应对紧急问题的方法论是每个团队都适用的。

第10章 基于时间序列数据进行有效报警

  • 一个大型系统不应该要求运维人员持续关注其中使用的无数个小组件,而是应该自动汇总所有的信息,自动抛弃其中的异常情况。监控系统应该主要从高级服务质量目标层面进行报警,但是也应该保持足够的粒度,可以追踪到某个具体组件。

Borgmon的起源

  • Prometheus 都是开源软件中与Borgmon 基于时间序列数据报警理念类似的系统。
  • Prometheus[3]与Borgmon十分类似,尤其是当你对比其中的规则计算语法时。变量收集和规则计算的理念在所有这些项目中都很类似。希望借助这些工具,读者可以自行试验、部署本章内描述的一些想法。

黑盒监控

  • 白盒监控只能看到已经接收到的请求,并不能看到由于DNS故障导致没有发送成功的请求,或者是由于软件服务器崩溃而没有返回的错误
  • 探针程序使用应用级别的自动请求探测目标是否成功返回。

配置文件的维护

  • Borgmon提供一种类似于宏的模板机制,允许用户创造出一些可重用的规则库,进一步减小了配置文件中的重复程度,降低配置文件出错的可能性。

个人总结

  1. Prometheus
  2. 黑盒监控(类似APM)
  3. 保证监控系统的维护成本与服务的部署规模呈非线性相关增长是非常关键的。

  • SRE团队和纯运维团队十分不一样的地方在于,SRE团队非常强调用工程化手段来应对运维问题。而这些运维问题,当达到一定规模时,也确实只有采用软件工程化手段才能解决。

  • 我们为SRE花在纯运维事务上的时间设立了50%的上限。SRE至少要花50%的时间进行工程项目研发,以便能够研发出更好的自动化和服务优化手段来更好地服务整个业务。

安全感

  • 现代理论研究指出,在面临挑战时,一个人会主动或非主动(潜意识)地选择下列两种处理方法之一(参见文献[Kah11]):● 依赖直觉,自动化、快速行动。● 理性、专注、有意识地进行认知类活动。

  • 当处理复杂系统问题时,第二种行事方式是更好的,可能会产生更好的处理结果,以及计划更周全的执行过程。为了确保on-call工程师可以保持在第二种处理方式范围内,我们必须要减轻on-call所带来的压力感。

  • 在应急事故处理过程中,凭直觉操作和快速反应(例如服务出现问题就先重启服务器)看起来都是很有用的方法,但是这些方法都有自己的缺点。直觉很可能是错误的,而且直觉一般都不是基于明确的数据支持的。因此,在处理问题的过程中,on-call工程师很有可能由于凭直觉去解释问题产生的原因而浪费宝贵的时间。快速反应主要是由习惯而产生的,习惯性的快速反应的动作后果一般都没有经过详细考虑,这可能会将灾难扩大。在应急事件处理过程中,最理想的方法论是这样的:在有足够数据支撑的时候按步骤解决问题,同时不停地审视和验证目前所有的假设。

  • 让on-call SRE知道他们可以寻求外部帮助,对减轻on-call压力也很有帮助。最重要的资源有:● 清晰的问题升级路线。● 清晰定义的应急事件处理步骤。● 无指责,对事不对人的文化氛围(参见文献[Loo10]和[All12])

  • 最后,在应急事件处理结束时,仔细评估哪些地方有问题,哪些地方做得好是非常关键的。而且应该采取措施避免再次发生同样的问题。SRE团队必须在大型应急事件发生之后书写事后报告,详细记录所有事件发生的时间线。这些事后报告都是对事不对人的,为日后系统性地分析问题产生的原因提供了宝贵数据。犯错误是不可避免的,软件系统应该提供足够的自动化工具和检查来减少人为犯错误的可能性(参见文献[Loo10])。

避免运维压力过大

  • 控制on-call工程师收到的针对同一起事故的报警总数也很重要。有的时候,一个异常情况可能会触发多条报警,所以合理地分组汇总报警信息是很重要的。

  • 奸诈的敌人—运维压力不够虽然给一个非常安静的系统on-call值班是很幸福的事情,但是当一个系统太稳定,或者SRE on-call的周期太长会发生什么呢?SRE团队运维压力不够也是一个不良现象。长时间不操作生产环境会导致自信心问题,包括自信心太强以及自信心不够。这些现象只有在下一次发生问题时,才会显现出来。为了避免这种问题,应该控制SRE团队的大小,保证每个工程师每个季度至少参与oncall一次,最好两次。这样可以保证团队成员有足够的生产环境操作经验。“命运之轮”(见第28章)也是一种有助提高技能和共享知识的团队活动。同时,Google每年举办一次持续数天的全公司灾难恢复演习(DiRT),针对理论性和实际性的灾难进行演练。

个人总结

  1. 理性,逐步验证解决
  2. 清晰定义的应急事件处理步骤和升级流程
  3. 定期演练避免一旦突发时生疏

第12章 有效的故障排查手段

  • 值得警惕的是,理解一个系统应该如何工作并不能使人成为专家。只能靠调查系统为何不能正常工作才行。

  • 新手们常常不能有效地进行故障排查,是因为这个过程理想情况下同时需要两个条件。

    • 1.对通用的故障排查过程的理解(不依靠任何特定系统)。2.对发生故障的系统的足够了解。

理论

  • 我们不断提出一个造成系统问题的假设,进而针对这些假设进行测试和排除。

实践

  • 有效的故障报告应该写清预期是什么,实际的结果是什么,以及如何重现

  • 合理判定一个问题的严重程度需要良好的工程师判断力,同时,也需要一定程度的冷静。

  • 在大型问题中,你的第一反应可能是立即开始故障排查过程,试图尽快找到问题根源。这是错误的!不要这样做。正确的做法应该是:尽最大可能让系统恢复服务。

  • 这可能需要一些应急措施,比如,将用户流量从问题集群导向其他还在正常工作的集群,或者将流量彻底抛弃以避免连锁过载问题,或者关闭系统的某些功能以降低负载。缓解系统问题应该是你的第一要务。在寻找问题根源的时候,不能使用系统的用户并没有得到任何帮助。当然,快速定位问题时仍应该及时保存问题现场,比如服务日志等,以便后续进行问题根源分析时使用。

  • 这种方法也同样适用于计算机系统:如果一个Bug有可能导致不可恢复的数据损坏,停止整个系统要比让系统继续运行更好。对新SRE来说,这个想法是反直觉,令人不安的。对以前曾经有过产品研发背景的人来说更有点难以接受

  • 在日志中支持多级记录是很重要的,尤其是可以在线动态调整日志级别。

最后一个修改

  • 计算机系统有惯性存在:我们发现,一个正常工作的计算机系统会维持工作,直到某种外力因素出现,例如一个配置文件的修改,用户流量的改变等。检查最近对系统的修改可能对查找问题根源很有帮助。

  • 某项测试可能产生有误导性的结果。例如:防火墙规则可能只允许某些特定IP访问,所以在你的工作机上ping 数据库可能会失败,而实际从应用服务器上ping数据库可能是成功的。

  • 将你的想法明确地记录下来,包括你执行了哪些测试,以及结果是什么。

  • 尤其是当你处理更加复杂的问题时,良好的文档可以让你记住曾经发生过什么,可避免重复执行。

神奇的负面结果

  • 我们在设计实验的时候应该将可能的负面结果考虑在内,因为一个可靠的、应用广泛的负面结果对其他人更有帮助。

  • 公布负面结果有助于提升整个行业的数据驱动风气。

  • 公布结果。如果你对一项测试的结果感兴趣,那么很有可能其他人也感兴趣。当你公布结果的时候,其他人不需要再重新设计和运行一套类似试验。

  • 更多的试验压根没有公布数据,因为很多人错误地认为负面结果意味着没有价值。

使故障排查更简单

  • 使故障排查更简单有很多方法可以简化和加速故障排查过程。可能最基本的是:● 增加可观察性。在实现之初就给每个组件增加白盒监控指标和结构化日志。● 利用成熟的、观察性好的组件接口设计系统。

个人总结

  1. 理解一个系统应该如何工作并不能使人成为专家。只能靠调查系统为何不能正常工作才行。
  2. 有效的故障报告应该写清预期是什么,实际的结果是什么,以及如何重现
  3. 先让系统恢复服务,再排查问题,尽量保存现场相关数据或日志等。
  4. 发生问题时,留意“最后一个修改”
  5. 排查问题后文档记录,避免以后重复执行
  6. 实验的负面结果也有价值

第13章 紧急事件响应

  • 东西早晚要坏的,这就是生活。
  • 将紧急事件的处理过程变成一个学习机会。

当系统出现问题时怎么办

  • 当系统出现问题时怎么办首先,别惊慌失措!这不是世界末日,你也并不是一个人在战斗!作为一个专业人士,你已经接受过如何正确处理这样的情况的训练。通常来说,我们处理的问题一般不涉及真实的物理危险,只是计算机系统出现了问题。最差的情况下,也只是半个互联网都停止运转了(Google网络规模已经是全球排名前三)。所以请深吸一口气,慢慢来。

测试导致的紧急事故

  • 这次测试带来的严重后果促使开发者对代码类库中的问题进行了一次彻底的修复,同时制定了一个周期性测试机制来保证这类严重问题不再重现。

  • 由于我们没有在测试环境中测试回滚机制,没有发现这些机制其实是无效的,导致了事故总时长被延长了。我们现在要求在大型测试中一定先测试回滚机制。

变更部署带来的紧急事故

  • 在这些带外通信系统之外,Google还有命令行工具和其他的访问方式确保我们能够在其他条件无法访问的时候进行更新和变更回滚。这些工具和访问方式在这次事故中起到了重大作用,但是工程师应该更加频繁地测试,以便更为熟悉它们。

  • 具有讽刺意味的是,我们本来计划在下个季度提高部署测试流程和自动化的优先级。这次事故的发生直接将他们的优先级提高了,同时强调不管风险看起来有多小,也要经过严格完整的部署测试。

流程导致的严重事故

  • 我们在机器管理自动化上投入了大量的时间和精力,以使我们能很轻松地在整个集群里运行、停止和重新配置大量任务。但是当意外来临时,自动化的效率有时可能是很可怕的。

所有的问题都有解决方案

  • 时间和经验一再证明,系统不但一定会出问题,而且会以没有人能够想到的方式出问题。Google学到的最关键的一课是,所有的问题都有对应的解决方案,虽然对一个面对着疯狂报警的工程师来说,它可能不是那么显而易见。如果你想不到解决办法,那么就在更大的范围内寻求帮助。找到更多团队成员,寻求更多的帮助,做你需要做的一切事情,但是要快。最高的优先级永远是将手头问题迅速解决。很多时候,触发这个事故的人对事故了解得最清楚,一定要充分利用这一点。

  • 非常重要的是,一旦紧急事件过去之后,别忘了留出一些时间书写事后报告。

向过去学习,而不是重复它

  • 向过去学习,而不是重复它为事故保留记录没有什么比过去的事故记录是更好的学习资料了。历史就是学习其他人曾经犯的错误。在记录中,请一定要诚实,一定要事无巨细。尤其重要的是,提出关键的问题。时刻寻找如何能在战术及战略上避免这项事故的发生。公布和维护事后报告,确保全公司的每个人都能从中学到你所学到的知识。在事故结束后,确保自己和其他人切实完成事故中总结的待办事项。这样能够避免未来再次发生以同样的因素触发的同样的事故。一旦开始仔细学习过去的事故,我们就能更好地避免未来的事故。

  • 鼓励主动测试面对失败,理论和实践是两个完全不同的领域。直到你的系统真的失败的那一刻,你并不真的了解它,以及依赖它的系统,或者它的用户会如何应对。不要预设任何假设,也不要依赖任何没有经过测试的假设。你是希望这个系统在星期六凌晨两点钟,公司大部分同事都还在参加黑森林中的团建时出现故障,还是希望和最可靠和最聪明的同事在一起仔细监控着它们上周详细评审过的测试时出现故障呢?

一次流程管理良好的事故

  • Josephine上线之后,发现Robin也自愿加入进来。Sabrina 提醒Robin和Josephine,他们应该优先处理任何Mary交给他们的工作,同时他们必须告知Mary他们进行的任何操作。Robin和Josephine 通过阅读实时事故状况文档,很快熟悉了目前的情况。到下午5点时,Sabrina开始寻找接下来负责处理事故的替代人,因为她和她的同事们快要到下班时间了。她更新了事故状况文档。在5点45分时,她召开了一个简短的电话会议,让所有人都清楚目前的情况。在6点时,他们与他们的姐妹团队(另外一个办公室同团队的人)进行了职责交接。Mary第二天早上回到公司时,发现她身处大西洋另一端的同事已经定位了具体问题,缓解了问题,同时将事故做了了结,已经开始写事后总结了。问题解决了!她冲了点咖啡,开始规划一些结构性改变,使得这类问题在未来不会再重现。

什么时候对外宣布事故

  • 先宣布事故发生,随后找到一个简单解决方案,然后宣布事故结束,要比在问题已经持续几个小时之后才想起流程管理更好。应当针对事故设立一个明确的宣布条件。Google团队依靠下面几个宽松的标准——如果下面任何一条满足条件,这次事故应该被及时宣布。● 是否需要引入第二个团队来帮助处理问题?● 这次事故是否正在影响最终用户?● 在集中分析一小时后,这个问题是否依然没有得到解决?

  • 如果平时不经常使用,事故流程管理的可靠性萎缩得很快。所以怎么使工程师不忘记他们的流程管理技能呢?难道一定要制造更多事故吗?幸运的是,事故流程管理框架常常也适用于其他的跨时区、或者跨团队的常规运维变更实施。如果我们经常使用流程管理框架处理生产变更请求,那么在事故来临时,就可以很好地利用流程管理框架管理它。如果你的组织经常进行灾难恢复演习(你应该这样做!参见文献[Kir12]),事故流程管理应该包含在其中。Google经常针对之前发生的灾难进行角色扮演式演习,比如演习另外一个地区的团队处理过的问题,以更好地熟悉事故流程管理体系。

小结 !!!

  • 我们发现,通过事前准备一个事故流程管理策略,并确保平稳实施,以及经常测试,我们能够降低事故的平均恢复时间(MTTR),同时减轻处理紧急事故的人的工作压力。任何对服务可靠性关注的组织团队都会从类似策略上获得帮助。事故流程管理最佳实践划分优先级:控制影响范围,恢复服务,同时为根源调查保存现场。事前准备:事先和所有事故处理参与者一起准备一套流程。信任:充分相信每个事故处理参与者,分配职责后让他们自主行动。反思:在事故处理过程中注意自己的情绪和精神状态。如果发现自己开始惊慌失措或者感到压力难以承受,应该寻求更多的帮助。考虑替代方案:周期性地重新审视目前的情况,重新评估目前的工作是否应该继续执行,还是需要执行其他更重要或者更紧急的事情。练习:平时不断地使用这项流程,直到习惯成自然。换位思考:上次你是事故总控负责人吗?下次可以换一个职责试试。鼓励每个团队成员熟悉流程中的其他角色。

个人总结

  1. 东西早晚要坏的,这就是生活。
  2. 将紧急事件的处理过程变成一个学习机会。
  3. 在大型测试中一定先测试回滚机制。
  4. 时间和经验一再证明,系统不但一定会出问题,而且会以没有人能够想到的方式出问题。所有的问题都有对应的解决方案。
  5. 向过去学习,而不是重复它(事后报告)
  6. 事前准备一个事故流程管理策略

第15章 事后总结:从失败中学习

  • 一篇事后总结是一次事故的书面记录,包括该事故造成的影响,为缓解该事故采取的措施,事故的根本原因,以及防止未来问题重现的后续任务。

Google的事后总结哲学

  • 书写事后总结的主要目的是为了保证该事故被记录下来,理清所有的根源性问题,同时最关键的是,确保实施有效的措施使得未来重现的几率和影响得到降低,甚至避免重现。

  • 书写事后总结不是一种惩罚措施,而是整个公司的一次学习机会。但是书写事后总结的过程确实需要消耗团队的一定时间和精力,所以我们在选择上很严格。每个团队都有一些内部灵活性,但是基本的事后总结条件为:● 用户可见的宕机时间或者服务质量降级程度达到一定标准。● 任何类型的数据丢失。● on-call 工程师需要人工介入的事故(包括回滚、切换用户流量等)。● 问题解决耗时超过一定限制。● 监控问题(预示着问题是由人工发现的,而非报警系统)。

  • 在SRE的文化中,最重要的就是事后总结“对事不对人”。一篇事后总结必须重点关注如何定位造成这次事件的根本问题,而不是指责某个人或某团队的错误或者不恰当的举动。一篇对事不对人的事后总结假设所有参与事件处理的人都是善意的,他们在自己当时拥有的信息下做了正确的举动。如果因为某些“错误的”举动就公开指责或者羞辱某个人或团队,那么人们就会自然地逃避事后总结。

  • 我们不能“修好”某个人,但是可以通过改善系统和流程从而更好地协助他在设计和维护大型复杂系统时,做出更多“正确”的判断。

  • 当一次事故发生时,我们不能把事后总结当成例行公事。我们的工程师将事后总结看作一个修复问题,一个使Google变得更可靠的机会。一篇“对事不对人”的事后总结不应该简单地指责或者抱怨某个团队,而应该确实提出服务如何能够获得进步。

  • 下面是两个例子。指责“我们需要重写整个复杂后端系统。在过去三个季度中,它每周都在出问题。我们对一点一点修复它的问题已经烦透了!真的,如果我再收到一个报警,那我就自己重写了。”对事不对人“通过重写整个后端系统可能可以避免这些烦人的报警信息继续发生,目前版本的维护手册非常冗长,学习成本很高。相信通过重写,可以减少报警信息,未来的oncall工程师会感谢我们的。”

  • 最佳实践:避免指责,提供建设性意见。

  • 对事不对人的事后总结有的时候比较难写,因为事后总结的格式清晰地表明了触发事故的原因。从事后总结中排除指责的因素可以使人们在向上级汇报问题的时候更有自信。同时我们也不应该因为某个团队和个人经常写事后总结而对他们产生怀疑。一个充满相互指责风气的环境很容易让人将事故和问题掩盖起来,从而对整个组织酿成更大的灾难。

协作和知识共享

  • 最佳实践:所有的事后总结都需要评审

  • 一旦所有的事故参与者都对文档和其中的代办事项表示了肯定,这篇事后总结会被添加到该团队或者整个组织的文档汇总中。

  • 透明化的共享机制保证了每个人都可以很容易地找到和学习以前的事故。

建立事后总结文化

  • 最佳实践:公开奖励做正确事的人

  • 最佳实践:收集关于事后总结有效性的反馈

个人总结

  • 最佳实践:避免指责,提供建设性意见。
  • 一篇“对事不对人”的事后总结不应该简单地指责或者抱怨某个团队,而应该确实提出服务如何能够获得进步。
  • 一个充满相互指责风气的环境很容易让人将事故和问题掩盖起来,从而对整个组织酿成更大的灾难。

第16章 跟踪故障

  • 提高可靠性的唯一可靠的方法论是建立一个基线(baseline),同时不断跟踪改变。

  • 通过找到基础设施中造成故障最多的一部分,可以更好地知道如果提高该部分的稳定性或性能会带来多大帮助。

  • 甚至有的时候,我们需要人为制造一些宕机时间,以免给内部用户造成某种假象(通常指某些服务设计的架构已经决定发生故障会耗时很久才能解决,但是经常由于运气因素而造成非常稳定的假象。某些团队选择定期制造人为宕机时间,以避免用户过于依赖该服务)。

个人总结

  1. 定时宕机,避免把依赖当作理所当然。

第17章 测试可靠性

  • 如果你还没有亲自试过某件东西,那么就假设它是坏的。

  • 测试的数量直接取决于所服务系统的可靠性要求。随着代码的测试覆盖度上升,每次改动的不确定性和降低系统可靠度的可能性都降低了。足够的代码测试覆盖度可以让我们对系统做出更多的改动,而不会使系统可靠度下降到可接受水平之下

  • 测试和平均修复时间的关系系统通过某项测试或者一系列测试并不一定能证明系统是稳定的,但是失败的测试通常证明了系统不可靠。

软件测试的类型

  • 软件测试基本分为两大类:传统测试和生产测试。传统测试在软件开发过程中很常见,主要用来在开发过程中离线评估软件的正确性。生产测试在生产Web服务器上进行,用来评估一个已经部署的软件系统工作是否正常。

  • 单元测试单元测试(unit test)是最小、最简单的软件测试形式。这些测试用来评估某一个独立的软件单元,比如一个类,或者一个函数的正确性。这些测试不考虑包含该软件单元的整体系统的正确性。单元测试同时也是一种规范,用来保证某个函数或者模块完全符合系统对其的行为要求。单元测试经常被用来引入测试驱动开发的概念。集成测试通过独立的单元测试的软件组件被组装成大的系统组件。工程师通过在这个组件中运行一个集成测试(integration test)来检验该组件的功能的正确性。依赖注入(dependency injection),利用类似Dagger[53]这样的工具,我们可以创建出复杂依赖的mock(测试中替代真实逻辑的伪组件),用以方便地测试某个系统组件。一个常见的例子是通过依赖注入来用轻便的mock替换一个有状态的数据库,同时保持一模一样的行为特征。

  • 系统测试系统测试(system test)是一个在未部署的系统上运行的大型测试。某个组件的所有模块(Module)都会被装载到系统中(例如通过集成测试的软件服务器)。接下来工程师可以端到端地测试系统功能。系统测试包括以下几种类型。冒烟测试(smoke test)工程师在冒烟测试中可检测非常简单但是非常重要的系统行为。这是最简单的一种系统测试形式。冒烟测试有时也被称为理性测试,如果该测试不通过,那么其他更昂贵的测试可以不用运行了。性能测试(performance test)一旦冒烟测试通过,系统基本的正确性已经得到了保障。下一步通常是通过某个系统测试的变形来保证整个系统的性能自始至终保持在可接受范围内。因为系统的响应时间和资源要求可能在开发过程中大量改变,该系统必须接受某些测试以确保它不会在没人知道的情况下逐渐变慢(在发布到最终用户之前)。例如,一个程序可能随着改变开始需要32GB内存,而以前只需要8GB。或者该程序的响应时间由10ms变成了50ms,随后变成了100ms。性能测试可以保证随着时间推移系统性能不会下降,或者资源要求不会升高。回归测试(regression test)另外一种系统测试可保证之前的Bug不会重现。回归测试可以被理解为曾经发生过的,导致系统故障或产生错误信息的Bug列表。通过将这些Bug记录为系统测试或者集成测试,重构代码的工程师可以保证他们不会偶然间将他们曾经辛苦调查和修复的Bug又带回来。很重要的是,每个测试都有成本,时间成本和计算资源成本。在一个极限上,单元测试非常便宜,通常可以在毫秒级和很少的资源上(例如一个笔记本电脑上)完成。而在另一个极限上,将一个完整的软件服务器设立起来,同时包括它所有的依赖系统(或者是mock),然后运行相关的测试可能会需要很长时间—几分钟到几小时—一般还需要专属的运算资源。时刻关注这些测试的成本,是软件开发效率提升的重要因素,同时也鼓励程序员更有效地利用我们的测试资源。生产测试生产测试和一个已经部署在生产环境中的业务系统直接交互,而不是运行在密闭的测试环境中。这些测试和黑盒监控在很多地方十分类似(参见第6章),有的时候也被称为黑盒测试。生产测试对运行一个可靠的生产环境来说是必要的。

小结

  • 测试是工程师提高可靠性投入回报比最高的一种手段。

个人总结

  • 测试分类查资料总结

基于意图的容量规划

  • 向大型团队推广内部软件工具需要以下几点:● 持续的和完整的推广方案。● 用户的拥护。● 资深工程师和管理层的赞助,因为他们看到了项目的实用潜力。

使用DNS进行负载均衡

  • DNS负载均衡。最简单的方案是在DNS回复中提供多个A记录或者AAAA记录,由客户端任意选择一个IP地址使用

负载均衡:虚拟IP

  • 虚拟IP地址(VIP)不是绑定在某一个特定的网络接口上的,它是由很多设备共享的。

识别异常任务:流速控制和跛脚鸭任务

  • 从一个客户端的视角来看,某个后端任务可能处于下列任一种状态中:健康后端任务初始化成功,正在处理请求。拒绝连接后端任务处于无响应状态。这可能是因为任务正在启动或者停止,或者是因为后端正处于一种异常状态(虽然很少有后端任务在非停止状态下停止监听端口)。

  • 跛脚鸭状态后端任务正在监听端口,并且可以服务请求,但是已经明确要求客户端停止发送请求。

  • 当某个请求进入跛脚鸭状态时,它会将这个状态广播给所有已经连接的客户端。但是那些没有建立连接的客户端呢?

  • 在Google的RPC框架实现中,不活跃的客户端(没有建立TCP连接的客户端)也会定期发送UDP健康检查包。这就使跛脚鸭状态可以相对较快地传递给所有的客户端—通常在一到两个RTT周期内—无论它们处于什么状态下。

  • 允许任务处于这种半正常的跛脚鸭状态的好处就是让无缝停止任务变得更容易,处于停止过程中的任务不会给正在处理的请求返回一个错误值。能够无影响地停止一个活跃的后端任务可以让处理代码推送、设备维护活动,和机器故障问题导致的任务重启变得对用户透明。这个停止过程通常按照以下步骤进行:1.任务编排系统发送一个SIGTERM信号给该任务。2.后端任务进入跛脚鸭状态,同时请求它的所有客户端发送请求给其他后端任务。这通过SIGTERM信号处理程序中调用RPC实现中的API完成。3.任何在后端进入跛脚鸭状态时正在进行的请求(或者在进入状态之后,但是其他客户端收到通知之前)仍会继续进行。4.随着请求回复被发送回客户端,该后端任务的活跃请求逐渐降低为0。5.在配置的时间过后,该后端程序要么自己干净地退出,要么任务编排系统主动杀掉它。该时间应该被设置为一个足够大的值,以便一般的请求可以有足够的时间完成。每个服务的该数值都不同,一般来说取决于客户端的复杂程度,10s到150s是一个不错的选择。这个策略使客户端可以在后端程序进行耗时较长的初始化过程中(这时后端程序还不能服务请求)就建立连接。如果后端程序等到服务可以接受请求的时候才建立链接,就增加了一些不必要的延迟。一旦后端程序可以提供服务了,它就会主动通知所有客户端。

利用划分子集限制连接池大小

  • 利用划分子集限制连接池大小在健康管理之外,负载均衡另外要考虑的一个因素就是子集划分:限制某个客户端任务需要连接的后端任务数量。我们的RPC系统中的每个客户端都会针对后端程序维持一个长连接发送请求。这些连接通常在客户端启动的时候就建立完成,并且保持活跃状态,不停地有请求通过它们,直到客户端终止。另外一个方案是针对每个请求建立和销毁后端连接,这样会带来极大的资源成本和造成延迟问题。在极端情况下,如果某个连接闲置时间非常长,我们的RPC实现可以自动将该连接转为“不活跃”状态,转为UDP模式连接,而非TCP模式。

QPS陷阱

  • Google在多年的经验积累中得出:按照QPS来规划服务容量,或者是按照某种静态属性(认为其能指代处理所消耗的资源:例如某个请求所需要读取的键值数量)一般是错误的选择。就算这个指标在某一个时间段内看起来工作还算良好,早晚也会发生变化。有些变动是逐渐发生的,有些则是非常突然的(例如某个软件的新版本突然使得某些请求消耗的资源大幅减少)。这种不断变动的目标,使得设计和实现良好的负载均衡策略使用起来非常困难。更好的解决方案是直接以可用资源来衡量可用容量。

客户端侧的节流机制

  • 拒绝一个执行简单内存查询的请求可能跟实际执行该请求消耗内存差不多(因为这里主要的消耗是在应用层协议解析中,结果的产生部分很简单)。就算在某些情况下,拒绝请求可以节省大量资源,发送这些拒绝回复仍然会消耗一定数量的资源。

  • 当某个客户端检测到最近的请求错误中的一大部分都是由于“配额不足”错误导致时,该客户端开始自行限制请求速度,限制它自己生成请求的数量。超过这个请求数量限制的请求直接在本地回复失败,而不会真正发到网络层。

  • 我们使用一种称为自适应节流的技术来实现客户端节流。具体地说,每个客户端记录过去两分钟内的以下信息:请求数量(requests)应用层代码发出的所有请求的数量总计(指运行于自适应节流系统之上的应用代码)。请求接受数量(accepts)后端任务接受的请求数量。

重要性

  • 重要性重要性(criticality)是另外一个在全局配额和限制机制中比较有用的信息。某个发往后端的请求都会被标记为以下4类中的一种,这说明了请求的重要性。最重要 CRITICAL_PLUS为最重要的请求预留的类型,拒绝这些请求会造成非常严重的用户可见的问题。重要 CRITICAL生产任务发出的默认请求类型。拒绝这些请求也会造成用户可见的问题,但是可能没有CRITICAL_PLUS那么严重。我们要求服务必须为所有的CRITICAL和CRTICAL_PLUS流量配置相应的资源。可丢弃的SHEDDABLE_PLUS这些流量可以容忍某种程度的不可用性。这是批量任务发出的请求的默认值。这些请求通常可以过几分钟,或者几小时之后重试。可丢弃的SHEDDABLE这些流量可能会经常遇到部分不可用情况,偶尔会完全不可用。

  • 我们同时增强了RPC系统,可以自动传递重要性信息。如果后端接收到请求A,在处理过程中发出了请求B 和C给其他后端,请求B和C会使用与A相同的重要性属性。

  • 在过去一段时间内,Google内部的许多系统都逐渐产生了一种与重要性类似的属性,但是通常不能跨服务兼容。通过标准化和在RPC系统中自动传递,我们现在可以在某些特定节点处统一设置重要性属性。这意味着,我们相信依赖的服务在过载情况下可以按正确的优先级来拒绝请求,不论它们处于整个处理栈中多深的位置。于是我们一般在离浏览器或者客户端最近的地方设置优先级—通常在HTTP前端服务器上。同时,我们可以在特殊情况下在处理栈的某处覆盖优先级设置。

资源利用率信号

  • 资源利用率信号我们的任务过载保护是基于资源利用率(utilization)实现的。

  • 在多数情况下,资源利用率仅仅是指目前CPU的消耗程度(目前CPU使用量除以全部预留CPU数量)。但是在某些情况下,同时也会考虑内存的使用率。随着资源利用率的上升,我们开始根据请求的重要性来拒绝一些请求(高重要性的请求对应高阈值)。

处理过载错误

  • 决定何时重试

连接造成的负载

  • 连接造成的负载连接造成的负载是最后一个值得一提的因素。有时候我们仅仅考虑后端处理接收的请求所造成的负载(这也是用QPS来建模负载的一个问题),然而却忽略了其他因素,比如维护一个大型连接池的CPU和内存成本,或者是连接快速变动的成本。这样的问题在小型系统中可以忽略不计,但是在大型RPC系统中很快就会造成问题。

  • 我们的RPC协议需要不活跃的客户端定期执行健康检查。当某个连接空闲一段可配置的时间后,客户端放弃TCP连接,转为UDP健康检查。不幸的是,这种行为会对大量请求率很低的客户端造成问题:健康检查需要比实际处理请求更多的资源。

  • 通过仔细调节连接参数(如,大幅降低健康检查频率)或者动态创建和销毁连接可以优化这些场景。

小结

  • 利用不同的技术手段(确定性算法、加权轮询、客户端侧的节流、用户配额等)更平均地将负载分散到数据中心中。然而这些手段都依赖于在分布式下的状态传递机制。虽然大部分情况下都表现良好,但是在真实情况下,某些应用遇到了一些困难。所以,我们认为保护某个具体任务,防止过载是非常重要的。简单地说:一个后端任务被配置为服务一定程度的流量,不管多少额外流量被指向这个任务,它都应该保证服务质量。

  • 一个常见的错误是认为过载后端应该拒绝和停止接受所有请求。然而,这个假设实际上是与可靠的负载均衡目标相违背的。我们实际上希望客户端可以尽可能地继续接受请求,然后在有可用资源时才处理。某个设计良好的后端程序,基于可靠的负载均衡策略的支持,应该仅仅接受它能处理的请求,而优雅地拒绝其他请求。

个人总结

  • RPC框架设计的重要参考 TODO
  • 如何优雅地重试:https://mp.weixin.qq.com/s/6IkTnUbBlHjM3GM_bT35tA 这个实现类似
    策略 说明
    重试熔断 请求失败 / 成功 > 0.1 时停止重试
    链路上传错误标志 下层重试失败后上传错误标志,上层不再重试
    链路下传重试标志 重试请求特殊标记,下层对重试请求不会重试
    DDL 当剩余时间不够时不再发起重试请求
    框架熔断 微服务框架本身熔断、过载保护等机制也会影响重试效果


第22章 处理连锁故障

  • 如果请求没有成功,以指数型延迟重试。

  • 连锁故障是由于正反馈循环(positive feedback)导致的不断扩大规模的故障。[72]连锁故障可能由于整个系统的一小部分出现故障而引发,进而导致系统其他部分也出现故障。例如,某个服务的一个实例由于过载出现故障,导致其他实例负载升高,从而导致这些实例像多米诺骨牌一样一个一个全部出现故障。

连锁故障产生的原因和如何从设计上避免

  • RPC超时服务器过载时,对客户端RPC的回复会变慢,最终会超过客户端所设置的超时时间。这会导致服务器对请求实际进行的处理都被浪费了,而客户端可能会重试RPC,造成更严重的过载。

  • CPU缓存效率下降CPU使用得越多,任务被分配到多个CPU核心上的几率越大,从而导致CPU核心本地缓存的失效,进而降低CPU处理的效率。

  • 缓存命中率下降可用内存的减少可能会导致应用层缓存的命中率降低,导致向后端发送更多的RPC,可能会导致后端任务过载。

  • 线程线程不足可能会导致错误或者导致健康检查失败。如果服务器为此增加更多线程,这些线程可能会占用更多内存。在极端情况下,线程不足可能会导致进程ID数不足(Linux的进程ID数是有限的)。文件描述符文件描述符(file descriptor)不足可能会导致无法建立网络连接,进而导致健康检查失败。

  • 假设如下场景:1.某Java前端服务器GC参数没有被调优。2.在高负载(但是在期待范围内)情况下,前端由于GC问题导致CPU不足。3.CPU不足导致请求处理变慢。4.同时处理的请求增多导致内存使用上升。5.内存压力上升,同时由于固定内存分配比例的原因,用于缓存的内存数量减少。6.缓存数量降低意味着缓存中键值数量下降,从而导致命中率下降。7.缓存命中率下降导致更多的请求被发往后端进行处理。8.后端服务器CPU或者线程不足。9.CPU不足导致健康检查失败,从而触发了连锁故障。在上述这个复杂情景下,发生故障时可能没有时间仔细分析因果关系。尤其是在前端和后端由不同团队运维时,判断后端崩溃是由于前端缓存命中率下降可能非常困难。

  • 会自动避免产生错误的软件服务器的负载均衡策略会将这个问题加剧—某几个后端任务产生了错误,会导致负载均衡器不再向它们发送请求,进而使得其余软件服务器的负载上升,从而再次触发滚雪球效应。

防止软件服务器过载

  • 防止软件服务器过载下面描述了避免过载的几种策略,大致以优先级排序。使用负载压力测试得出服务器的极限,同时测试过载情况下的失败模式

  • 提供降级结果给用户返回低质量的,但是更容易计算的结果。

  • 在过载情况下主动拒绝请求软件服务器应该保护自己不进入过载崩溃状态。

上层系统应该主动拒绝请求

  • 在反向代理层,通过针对请求的某种特性进行数量限制(如IP地址),来缓解和避免拒绝服务攻击,避免攻击性客户端的影响。● 在负载均衡器层,在服务进入全局过载时主动丢弃请求。

  • 进行容量规划好的容量规划可以降低连锁反应发生的可能性。容量规划应该伴随着性能测试进行,以确定可能导致服务失败的负载程度。

  • 进行容量规划只能减少触发连锁反应的可能性,但是并不能完全避免。当一个计划内或者计划外的事件导致大部分集群容量同时下线时,连锁反应是不可避免的。负载均衡问题、网络分区事件,或者突发性流量增长,都会创造意料之外的负载问题。有些系统可以根据需要动态增加容量,这可能防止过载发生,但是适当地进行容量规划还是必要的。

  • 在这个理想化的情景下,只有在请求速率超过单个请求的处理速率时,请求才会进入队列,这种情况会导致线程池和队列的同时饱和。

  • 对一个流量基本稳定的服务来说,队列长度比线程池大小更小会更好(如 50% 或更小)。当服务处理速度无法跟上请求到达速率时,尽早拒绝请求会更好。

  • 流量抛弃和优雅降级流量抛弃(load shedding)是指在软件服务器临近过载时,主动抛弃一定量的负载。

  • 一种简单的流量抛弃实现方式是根据CPU使用量、内存使用量及队列长度等进行节流。

  • 简化了一些细节,[75]但是这里很好地展现了重试是如何摧毁一个系统的。注意临时性的过载升高,或者使用量的缓慢增加都有可能造成这种情况。

  • 一定要使用随机化的、指数型递增的重试周期。

  • 如果重试不是随机分布在重试窗口里的,那么系统出现的一个小故障(某个网络问题)就可能导致重试请求同时出现,这些请求可能会逐渐放大

  • 限制每个请求的重试次数。不要将请求无限重试。● 考虑使用一个全局重试预算。例如,每个进程每分钟只允许重试60次,如果重试预算耗尽,那么直接将这个请求标记为失败,而不真正发送它。这个策略可以在全局范围内限制住重试造成的影响,容量规划失败可能只是会造成某些请求被丢弃,而不会造成全球性的连锁故障。

  • 从多个视角重新审视该服务,决定是否需要在某个级别上进行重试。这里尤其要避免同时在多个级别上重试导致的放大效应:高层的一个请求可能会造成各层重试次数的乘积数量的请求。如果服务器由于过载不能提供服务,后端、前端、JavaScript层各发送3次重试(总计4次请求),那么一个用户的动作可能会造成对数据库的64 次请求(43)。在数据库由于过载返回错误时,这种重试只会加重问题。

  • 使用明确的返回代码,同时详细考虑每个错误模式应该如何处理。例如,将可重试错误和不可重试错误分开。

  • 设置一个截止时间通常是明智的。不设置截止时间,或者设置一个非常长的截止时间通常会导致某些短暂的、已经消失的问题继续消耗服务器资源,直到重启。

  • 截止时间设置得太长可能会导致框架中的高层级部分由于低层级的问题而持续消耗资源。截止时间设置得太短可能会导致某些比较重型的请求持续失败。恰当的截止时间设置,需要在多个限制条件中选择一个平衡点。

  • 截止时间传递与其在发送RPC给后端服务器时自拟一个截止时间,不如让软件服务器采用截止时间传递和取消传递的策略。

  • 可使用截止时间传递机制,截止时间在整个服务栈的高层设置(如,前端服务器)。由初始请求触发的整个RPC树会设置同样的绝对截止时间。

  • 同时,我们可能还会将传递出去的截止时间减少一点(如几百毫秒),以便将网络传输时间和客户端收到回复之后的处理时间考虑在内。

  • RPC取消的传递可以避免某些泄露情况,如果某个初始RPC设置了一个很长的截止时间,但是底层之间的RPC只有短暂的截止时间,超时失败了。使用简单的截止时间传递可能会导致初始RPC虽然无法继续处理,却继续消耗服务器资源直到超时。

  • 请求延迟的双峰分布(Bimodal)

  • 使用100s的截止时间,5%的请求会消耗5000个线程(50QPS * 100 seconds),但是前端服务器并没有这么多可用线程。忽略副作用,前端也仅能够处理19.6%的请求(1000可用线程 /(5000+95)线程工作),这会造成 80.4%的错误率。因此,不仅 5% 的请求受到了影响(那些由于后端问题不可能成功的请求),实际上大部分的请求都受到了影响。

  • 如果无法完成的请求能够尽早返回一个错误而不是等完整个截止时间,我们就可以避免这个问题。例如,如果一个后端服务器不可用,经常立刻返回一个错误值是最好的,而不是等待这个后端服务器变得可用。如果RPC层支持快速失败的选项,一定要启用它。

  • 假设你的后端要处理来自不同客户端的性能和特征各异的请求,我们可以考虑限制一个客户端只能占用25% 的线程总数,以便在某个异常客户端大量产生负载的情况下提供一些公平性。

慢启动和冷缓存

  • 慢启动和冷缓存进程在刚刚启动之后通常要比稳定状态下处理请求的速度慢一点。慢的原因可能是由下列一个或多个原因导致:必需的初始化过程在接收到第一个请求后,需要跟后端服务器建立连接。运行时性能优化,尤其是JavaJIT 编译过程,热点优化,以及类延迟加载机制。同样的,有些服务器会在缓存没有充满之前效率很低。

  • 过量配备(overprovision)该服务。区分延迟类缓存和容量类缓存是很重要的:当使用延迟类缓存时,服务器可以在空缓存的情况下仍然处理预期的请求负载,但是使用容量类缓存时,该服务将不能够在空缓存下处理请求负载。

保持调用栈永远向下

  • 假设一个用户有一个主后端和一个预先选择好的另外集群中的一个热备后端,主后端在底层出现错误或者延迟上升的情况下将请求代理给热备后端。如果整个系统都处于过载状态,那么从主到副的这种代理可能会增多,会给系统带来更多的负载,因为请求通常要被解析两次,还需要主后端消耗资源等待副后端任务。

  • 在用户的请求路径中最好能够避免使用同层通信—也就是避免通信路径中出现环。

  • 应该由客户端来进行这种通信。例如,如果一个前端需要和后端通信,但是猜错了后端任务,后端不会代理请求给正确的后端,而是通过返回错误使得前端在正确的后端任务上重试它的请求。

连锁故障的触发条件

  • 根据请求数量和可用容量来动态调节任务的同时更新数量可能是个好办法。

  • 在发生连锁故障时,检查最近的改变以及回滚通常是明智的,尤其在这些改变会影响容量或者更改请求特点的情况下。

  • 自然增长在很多情况下,连锁故障不是由于某个特定的服务改变导致的,而是由于使用量的天然上升,却没有进行对应的容量调整导致的。

连锁故障的测试

  • 设计良好的组件应该可以拒绝一小部分请求而继续存活。

解决连锁故障的立即步骤

  • 莎士比亚搜索服务的连锁故障某个关于莎士比亚作品的纪录片在日本上映了,同时特别指明了莎士比亚搜索服务是进行进一步研究的最佳工具。随着这次广播,亚洲数据中心的流量激增,超过了服务容量。服务容量的问题伴随着当时正在进行的大型更新而变得更严重了。幸运的是,一些安全防护措施帮助缓解了可能的故障。生产环境准备评审(production readiness review)流程指出了一些问题,开发团队已经解决。例如,开发者为服务加入了优雅降级功能。当容量不够时,服务不再返回照片,或者不再返回解释故事发生位置的小地图。取决于RPC的目的,超时的RPC要么不再重试(例如,之前提到的图片),要么采用随机指数型延迟进行重试。即使有这些保护措施的存在,任务还是一个接一个地失败了,然后被Borg系统重启,这导致正常工作的任务数量持续减少。由于这个原因,服务监控页面上的某些图表变成了红色,并且SRE收到了紧急警报。为了解决这个问题,SRE临时向亚洲数据中心增加了一些服务容量,调整了莎士比亚搜索任务的任务数量。通过这种操作,成功恢复了亚洲数据中心的莎士比亚搜索服务。接下来,SRE书写了一篇事后总结,详细说明了触发问题的事件,哪些做得好,哪些可以做得更好,和一系列待办事项来避免这个情景重现。例如,在服务过载的情况下,GSLB负载均衡器可以将一些流量导入邻近的数据中心。同时,SRE团队启用了自动伸缩机制,于是任务的数量可以自动跟着流量增长,这样他们就不用再操心这类问题了。

小结

  • 小结当一个系统过载时,某些东西总是要被牺牲掉。一旦一个服务越过了临界点,服务一些用户可见错误,或者低质量结果要比尝试继续服务所有请求要好。理解这些临界点所在,以及超过临界点系统的行为模式,是所有想避免连锁故障的运维人员所必需的。如果不加小心,某些原本为了降低服务背景错误率或者优化稳定状态的改变反而会让服务更容易出现事故。在请求失败的时候重试、负载自动转移、自动杀掉不健康的服务器、增加缓存以提高性能或者降低延迟:这些手段原本都是为了优化正常情况下的服务性能,但是也可能会提高大规模的服务故障的几率。一定要小心评估这些改变,否则灾难就会接踵而至。

第22章 处理连锁故障

  • 如果请求没有成功,以指数型延迟重试。

  • 连锁故障是由于正反馈循环(positive feedback)导致的不断扩大规模的故障。[72]连锁故障可能由于整个系统的一小部分出现故障而引发,进而导致系统其他部分也出现故障。例如,某个服务的一个实例由于过载出现故障,导致其他实例负载升高,从而导致这些实例像多米诺骨牌一样一个一个全部出现故障。

连锁故障产生的原因和如何从设计上避免

  • RPC超时服务器过载时,对客户端RPC的回复会变慢,最终会超过客户端所设置的超时时间。这会导致服务器对请求实际进行的处理都被浪费了,而客户端可能会重试RPC,造成更严重的过载。

  • CPU缓存效率下降CPU使用得越多,任务被分配到多个CPU核心上的几率越大,从而导致CPU核心本地缓存的失效,进而降低CPU处理的效率。

  • 缓存命中率下降可用内存的减少可能会导致应用层缓存的命中率降低,导致向后端发送更多的RPC,可能会导致后端任务过载。

  • 线程线程不足可能会导致错误或者导致健康检查失败。如果服务器为此增加更多线程,这些线程可能会占用更多内存。在极端情况下,线程不足可能会导致进程ID数不足(Linux的进程ID数是有限的)。文件描述符文件描述符(file descriptor)不足可能会导致无法建立网络连接,进而导致健康检查失败。

  • 假设如下场景:1.某Java前端服务器GC参数没有被调优。2.在高负载(但是在期待范围内)情况下,前端由于GC问题导致CPU不足。3.CPU不足导致请求处理变慢。4.同时处理的请求增多导致内存使用上升。5.内存压力上升,同时由于固定内存分配比例的原因,用于缓存的内存数量减少。6.缓存数量降低意味着缓存中键值数量下降,从而导致命中率下降。7.缓存命中率下降导致更多的请求被发往后端进行处理。8.后端服务器CPU或者线程不足。9.CPU不足导致健康检查失败,从而触发了连锁故障。在上述这个复杂情景下,发生故障时可能没有时间仔细分析因果关系。尤其是在前端和后端由不同团队运维时,判断后端崩溃是由于前端缓存命中率下降可能非常困难。

  • 会自动避免产生错误的软件服务器的负载均衡策略会将这个问题加剧—某几个后端任务产生了错误,会导致负载均衡器不再向它们发送请求,进而使得其余软件服务器的负载上升,从而再次触发滚雪球效应。

防止软件服务器过载

  • 防止软件服务器过载下面描述了避免过载的几种策略,大致以优先级排序。使用负载压力测试得出服务器的极限,同时测试过载情况下的失败模式。

  • 提供降级结果给用户返回低质量的,但是更容易计算的结果。

  • 在过载情况下主动拒绝请求软件服务器应该保护自己不进入过载崩溃状态。

上层系统应该主动拒绝请求

  • 在反向代理层,通过针对请求的某种特性进行数量限制(如IP地址),来缓解和避免拒绝服务攻击,避免攻击性客户端的影响。● 在负载均衡器层,在服务进入全局过载时主动丢弃请求。

  • 进行容量规划好的容量规划可以降低连锁反应发生的可能性。容量规划应该伴随着性能测试进行,以确定可能导致服务失败的负载程度。

  • 进行容量规划只能减少触发连锁反应的可能性,但是并不能完全避免。当一个计划内或者计划外的事件导致大部分集群容量同时下线时,连锁反应是不可避免的。负载均衡问题、网络分区事件,或者突发性流量增长,都会创造意料之外的负载问题。有些系统可以根据需要动态增加容量,这可能防止过载发生,但是适当地进行容量规划还是必要的。

  • 在这个理想化的情景下,只有在请求速率超过单个请求的处理速率时,请求才会进入队列,这种情况会导致线程池和队列的同时饱和。

  • 对一个流量基本稳定的服务来说,队列长度比线程池大小更小会更好(如 50% 或更小)。当服务处理速度无法跟上请求到达速率时,尽早拒绝请求会更好。

  • 流量抛弃和优雅降级流量抛弃(load shedding)是指在软件服务器临近过载时,主动抛弃一定量的负载。

  • 一种简单的流量抛弃实现方式是根据CPU使用量、内存使用量及队列长度等进行节流。

  • 简化了一些细节,[75]但是这里很好地展现了重试是如何摧毁一个系统的。注意临时性的过载升高,或者使用量的缓慢增加都有可能造成这种情况。

  • 一定要使用随机化的、指数型递增的重试周期。

  • 如果重试不是随机分布在重试窗口里的,那么系统出现的一个小故障(某个网络问题)就可能导致重试请求同时出现,这些请求可能会逐渐放大

  • 限制每个请求的重试次数。不要将请求无限重试。● 考虑使用一个全局重试预算。例如,每个进程每分钟只允许重试60次,如果重试预算耗尽,那么直接将这个请求标记为失败,而不真正发送它。这个策略可以在全局范围内限制住重试造成的影响,容量规划失败可能只是会造成某些请求被丢弃,而不会造成全球性的连锁故障。

  • 从多个视角重新审视该服务,决定是否需要在某个级别上进行重试。这里尤其要避免同时在多个级别上重试导致的放大效应:高层的一个请求可能会造成各层重试次数的乘积数量的请求。如果服务器由于过载不能提供服务,后端、前端、JavaScript层各发送3次重试(总计4次请求),那么一个用户的动作可能会造成对数据库的64 次请求(43)。在数据库由于过载返回错误时,这种重试只会加重问题。

  • 使用明确的返回代码,同时详细考虑每个错误模式应该如何处理。例如,将可重试错误和不可重试错误分开。

  • 设置一个截止时间通常是明智的。不设置截止时间,或者设置一个非常长的截止时间通常会导致某些短暂的、已经消失的问题继续消耗服务器资源,直到重启。

  • 截止时间设置得太长可能会导致框架中的高层级部分由于低层级的问题而持续消耗资源。截止时间设置得太短可能会导致某些比较重型的请求持续失败。恰当的截止时间设置,需要在多个限制条件中选择一个平衡点。

  • 截止时间传递与其在发送RPC给后端服务器时自拟一个截止时间,不如让软件服务器采用截止时间传递和取消传递的策略。

  • 可使用截止时间传递机制,截止时间在整个服务栈的高层设置(如,前端服务器)。由初始请求触发的整个RPC树会设置同样的绝对截止时间。

  • 同时,我们可能还会将传递出去的截止时间减少一点(如几百毫秒),以便将网络传输时间和客户端收到回复之后的处理时间考虑在内。

  • RPC取消的传递可以避免某些泄露情况,如果某个初始RPC设置了一个很长的截止时间,但是底层之间的RPC只有短暂的截止时间,超时失败了。使用简单的截止时间传递可能会导致初始RPC虽然无法继续处理,却继续消耗服务器资源直到超时。

  • 请求延迟的双峰分布(Bimodal)

  • 使用100s的截止时间,5%的请求会消耗5000个线程(50QPS * 100 seconds),但是前端服务器并没有这么多可用线程。忽略副作用,前端也仅能够处理19.6%的请求(1000可用线程 /(5000+95)线程工作),这会造成 80.4%的错误率。因此,不仅 5% 的请求受到了影响(那些由于后端问题不可能成功的请求),实际上大部分的请求都受到了影响。

  • 如果无法完成的请求能够尽早返回一个错误而不是等完整个截止时间,我们就可以避免这个问题。例如,如果一个后端服务器不可用,经常立刻返回一个错误值是最好的,而不是等待这个后端服务器变得可用。如果RPC层支持快速失败的选项,一定要启用它。

  • 假设你的后端要处理来自不同客户端的性能和特征各异的请求,我们可以考虑限制一个客户端只能占用25% 的线程总数,以便在某个异常客户端大量产生负载的情况下提供一些公平性。

慢启动和冷缓存

  • 慢启动和冷缓存进程在刚刚启动之后通常要比稳定状态下处理请求的速度慢一点。慢的原因可能是由下列一个或多个原因导致:必需的初始化过程在接收到第一个请求后,需要跟后端服务器建立连接。运行时性能优化,尤其是JavaJIT 编译过程,热点优化,以及类延迟加载机制。同样的,有些服务器会在缓存没有充满之前效率很低。

  • 过量配备(overprovision)该服务。区分延迟类缓存和容量类缓存是很重要的:当使用延迟类缓存时,服务器可以在空缓存的情况下仍然处理预期的请求负载,但是使用容量类缓存时,该服务将不能够在空缓存下处理请求负载。

保持调用栈永远向下

  • 假设一个用户有一个主后端和一个预先选择好的另外集群中的一个热备后端,主后端在底层出现错误或者延迟上升的情况下将请求代理给热备后端。如果整个系统都处于过载状态,那么从主到副的这种代理可能会增多,会给系统带来更多的负载,因为请求通常要被解析两次,还需要主后端消耗资源等待副后端任务。

  • 在用户的请求路径中最好能够避免使用同层通信—也就是避免通信路径中出现环。

  • 应该由客户端来进行这种通信。例如,如果一个前端需要和后端通信,但是猜错了后端任务,后端不会代理请求给正确的后端,而是通过返回错误使得前端在正确的后端任务上重试它的请求。

连锁故障的触发条件

  • 根据请求数量和可用容量来动态调节任务的同时更新数量可能是个好办法。

  • 在发生连锁故障时,检查最近的改变以及回滚通常是明智的,尤其在这些改变会影响容量或者更改请求特点的情况下。

  • 自然增长在很多情况下,连锁故障不是由于某个特定的服务改变导致的,而是由于使用量的天然上升,却没有进行对应的容量调整导致的。

连锁故障的测试

  • 设计良好的组件应该可以拒绝一小部分请求而继续存活。

小结

  • 小结当一个系统过载时,某些东西总是要被牺牲掉。一旦一个服务越过了临界点,服务一些用户可见错误,或者低质量结果要比尝试继续服务所有请求要好。理解这些临界点所在,以及超过临界点系统的行为模式,是所有想避免连锁故障的运维人员所必需的。如果不加小心,某些原本为了降低服务背景错误率或者优化稳定状态的改变反而会让服务更容易出现事故。在请求失败的时候重试、负载自动转移、自动杀掉不健康的服务器、增加缓存以提高性能或者降低延迟:这些手段原本都是为了优化正常情况下的服务性能,但是也可能会提高大规模的服务故障的几率。一定要小心评估这些改变,否则灾难就会接踵而至。

个人总结

  1. 使用负载压力测试得出服务器的极限以及过载降级策略
  2. 一定要使用随机化的、指数型递增的重试周期。
  3. 使用明确的返回代码,同时详细考虑每个错误模式应该如何处理。例如,将可重试错误和不可重试错误分开。
  4. 设置一个截止时间通常是明智的。RPC之间传递。
  5. 限制一个客户端对线程总数占有比例,以便在某个异常客户端大量产生负载的情况下提供一些公平性。
  6. 慢启动和冷缓存;区分延迟类缓存和容量类缓存。
  7. 保持调用栈永远向下(减少转发次数和后端资源消耗)。
  8. 设计良好的组件应该可以拒绝一小部分请求而继续存活。

第23章 管理关键状态:利用分布式共识来提高可靠性

  • 在构建可靠的、高可用的系统过程时,我们发现分布式共识系统适合用来维护某个强一致的系统状态

  • 当你需要实现领头人选举(leader election)、关键性共享状态或分布式锁等时,我们建议采用某种正式证明过的、详尽测试过的分布式共识系统来实现。如果不严肃对待这个问题很有可能会导致事故,在更糟的情况下,将造成某种非常难以修复的数据一致性问题,这可能会大大加长系统事故的处理时间。

  • 网络分区问题迟早会发生(光缆被切断,数据包由于拥塞造成丢失或延迟升高,硬件故障,网络组件配置错误等),理解如何构建分布式共识实际上就是理解某个服务应用如何实现强一致性和高可用。由于商业的压力,很多服务都需要很高的可用性,这些应用程序通常都需要对数据访问的强一致性。

  • 最终一致可能会带来意想不到的问题(参见文献[Lu15]),尤其是当时钟漂移(在分布式系统中,这是不可避免的),或者网络分区(参见文献[Kin15])发生的时候。

  • Jeff Shute(参见文献[Shu13])曾经说过,“我们发现开发者通常花费了大量的时间构建一个极为复杂和容易出错的机制来应对最终一致性下可能过时的数据。我们认为对开发者来说这是一种无法接受的负担,数据一致性的问题应该在数据库层解决。”

使用共识系统的动力:分布式系统协调失败

  • 在网络分区情况下—造成问题不一定是由完全分区导致的,而是:● 网络非常慢。● 某些消息可以通过,但是某些消息被丢弃了。● 单方面的节流措施。

  • 如果网络质量真的很差,分布式共识系统也无法正确选举出主实例时,作为工程师可能也没有什么好办法来做出正确决策。

分布式共识是如何工作的

  • 拜占庭式问题指的是当某个进程由于程序Bug或者恶意行为发送不正确的消息的问题,这种问题相对来说处理成本更高,同时也更少见。

  • 严格来讲,在有限时间内解决异步式分布式共识问题是不可能的。正如Dijkstra 的获奖文章—FLP impossibility result(参见文献[Fis85])写的那样,在不稳定的网络条件下,没有任何一种异步式分布式共识算法可以保证一定能够达成共识。在实际操作中,我们通过保证给系统提供足够的健康的副本,以及良好的网络连接状态来保障分布式共识算法在大多数情况下是可以在有限时间内达成共识的。同时整个系统还需要加入随机指数型延迟。

  • 这样,我们可以保障重试不会造成连锁反应,以及本章后面会提到的角斗士(dueling proposers)问题。

  • 这个协议在一个有利环境下能够保障安全性,同时提供足够的冗余度。

  • 最初的分布式共识问题的解决方案是Lamport的Paxos 协议(参见文献[Lam98]),但是也有其他的协议能够解决这个问题,包括 Raft(参见文献[Ong14])、Zab(参见文献[Jun11]),以及 Mencius(参见文献[Mao08])。Paxos本身也有很多变种尝试提升性能(参见文献[Zoo14])。这些变种常常只是在某一个小细节上不同,例如给某个进程一个特殊的领头人角色以简化协议实现等。

分布式共识的系统架构模式

  • 可靠的复制状态机一个复制状态机(replicated state machine,RSM)是一个能在多个进程中用同样顺序执行同样的一组操作的系统。RSM 是一个有用的分布式系统基础组件,可以用来构建数据和配置存储,执行锁机制和领头人选举(接下来会详细描述)。

  • 在RSM系统上进行的操作是通过共识算法来全局排序的。

  • 时间戳在分布式系统中问题非常大,因为在多个物理机器上保证时间同步是不可能的。Spanner(参见文献[Cor12])通过针对最差情况下的不确定性建模,同时在必要时减慢处理速度来解决这个问题。

  • 分布式系统的领头人选举是跟分布式共识等价的问题。复制多份服务并使用一个唯一的领头人(leader)来进行某种类型的工作是很常见的设计。唯一的领头人是一种保证粗粒度互斥性的方法。

分布式共识系统的性能问题

  • 世界上并没有某个性能“最优”的分布式共识和状态机复制算法,因为算法性能通常取决于与系统负载有关的多个因子,以及系统性能的目标和系统的部署情况。

  • 复合式Paxos:消息流过程详解复合式Paxos(Multi-Paxos)协议采用了一个强势领头人(strong leader)进程的概念:除非目前没有选举出任何的领头人进程,或者发生了某种错误,在正常情况下,提议者只需要对满足法定人数的进程发送一次消息就可以保障系统达成共识。这种强势领头人在很多共识协议中都适用,在系统需要传递大量消息的时候非常合适。

  • 很多共识系统使用TCP/IP作为通信协议。TCP/IP是面向连接的,同时针对消息的先进先出(FIFO)顺序提供了强保障。但是在发送任何数据之前,都需要先建立新的TCP/IP连接,也就是一次网络往返需要进行三次握手协议。TCP/IP慢启动机制也限制了连接的初始带宽。常见的TCP/IP窗口大小在4~15KB之间。

  • TCP/IP的慢启动问题对共识组中的进程来说可能不是问题:因为这些进程会互相建立一个连接而且保持这些连接,因为通信将会很频繁。但是,对拥有非常多的客户端的系统来说,可能不能做到让每个客户端都和共识组所有进程都保持一个活动连接。因为TCP/IP需要消耗一定资源,包括文件描述符,以及对应的KeepAlive数据流量。对高度分片、拥有几千个副本的数据存储系统,以及更大规模的客户端来说,这种成本是无法接受的。一个解决方案是使用地域性的代理池,如图23-9所示。该代理池的进程与共识组建立持久的TCP/IP进程,以降低客户端的开销。同时代理池也是包装分片逻辑和负载均衡逻辑,以及共识系统的服务发现逻辑的好地方。

  • 快速Paxos协议:性能优化快速Paxos协议(Fast Paxos,参见文献[Lam06])是Paxos协议的一个变种,意在优化Paxos算法在广域网络中的性能。使用该协议的每个客户端可以直接向组内的接收者们发送Propose消息,而不需要像经典Paxos和复合Paxos那样通过一个领头人进程发送。

  • 稳定的领头人机制上文已经描述过,复合Paxos是如何通过选举一个稳定的领头人进程来提高性能的。Zab(参见文献[Jun11])和Raft(参见文献[Ong14])协议是其他两个例子,它们也通过选举稳定的领头人进程来提高性能。这种方案可以进一步进行读操作优化,因为领头人始终拥有最新信息,但是也存在以下几个问题:● 所有的改变状态的操作都必须经由该领头人,这个要求使得距离领头人进程较远的客户端必须要增加额外的网络延迟。● 领头人进程的外发网络带宽是系统性能的瓶颈(参见文献[Mao08]),因为该领头人的Accept消息包含了每个提议的所有数据,而其他进程发送的消息仅仅包含了交易数字,而没有真正的数据。● 如果领头人进程恰好处于一台有性能问题的机器上,整个系统的吞吐量都会受到影响。几乎在所有关注性能的分布式共识系统设计中,都采用了单个稳定领头人进程机制,或者是某种领头人轮换机制—预先分配给每个副本一个独立的分布式算法编号(通常是对交易编号取模)。使用轮换机制的算法包括Mencius(参见文献[Mao08])和Egalitarian Paxos(参见文献[Mor12a0])。

  • 复合Paxos的信息流的过程在本章前面“复合式Paxos:消息流过程详解”一节中描述过了,但是并没有展示在什么情况下协议要求必须在磁盘中记录状态改变。在进程做任何一个承诺之前都必须进行磁盘写操作。在复合Paxos性能的关键点—协议的第二部分中,这些操作点处于接收者针对某个提议发送Accepted消息之前,以及提议者发送Accept消息之前—因为这条Accept消息其实是一个隐含的Accepted消息

  • 这就意味着在单个共识操作的延迟中,有以下几个操作:● 提议者的一次硬盘写入操作。● 并行消息发送给接收者。● 每个接收者的磁盘写操作(并行)。● 回复消息的传递。

  • 分布式共识算法经常用来实现一个复制状态机。RSM需要保留一份交易日志,以便用于灾难恢复(正如其他数据存储那样)。共识算法的日志可以和RSM的交易日志合为一个。将这两个日志合并可以避免不停地向磁盘上的两个不同位置交替写入(参见文献[Bol11]),也就降低了磁盘寻址操作消耗的时间。这些磁盘于是可以每秒处理更多操作,该系统也可以每秒处理更多操作。

  • 在数据存储系统中,磁盘在维护日志之外还有其他用处:数据通常也是存放于磁盘中的。日志的写操作必须直接刷写到硬盘,但是数据的改变通常可以写入内存缓存中,稍后再写入磁盘,可以进行重新排序以便更有效地写入(参见文献[Bol11])。

分布式共识系统的部署

  • 系统设计者部署共识系统时,最重要的决策在于选择副本的数量和对应的部署位置。

  • 副本的数量一般来说,共识系统都要采用“大多数”的法定过程。也就是说,一组2f+1副本组成的共识组可以同时承受f个副本失败而继续运行(如果需要容忍拜占庭式失败,也就是要能够承受副本返回错误结果的情况,则需要3f+1个副本来承受f个副本失败(参看文献[Cas99]))。针对非拜占庭式失败的情况,最小的副本数量是3—如果仅仅部署两个副本,则不能承受任何一个副本失败)。3个副本可以承受1个副本失败。大部分系统的不可用时间都是由计划内维护造成的

  • 3个副本使该系统可以在1个副本维护时继续工作(这里假设其余两个副本可以承受系统负载)。如果某个非计划内的失败情况在常规维护时发生了,那么共识系统就会不可用。而共识系统的不可用通常是不可接受的,于是在这种情况下,我们需要5个副本同时运行,于是可以允许系统在两个副本不可用的情况下继续运行。如果5个副本中有4个能正常运行,则不需要进行任何人工干预操作,但是如果仅仅只有3个能正常运行,那么我们需要立刻增加一个或者两个额外的副本。

  • 如果共识系统中大部分的副本已经无法访问,以至于无法完成一次法定进程,那么该系统理论上来说已经进入了一个无法恢复的状态。因为至少有一个副本的持久化日志无法访问。在这个状态下,有可能某个操作仅仅只在无法访问的副本上执行了。这时,系统管理员可以强行改变小组成员列表,将新的副本添加到小组中,使用其他可用成员的信息来填充。但是数据丢失的可能性永远存在—这种情况应该全力避免。在灾难处理过程中,系统管理员需要决定是否需要进行这种强制性的重新配置,或者仅仅是等待那些机器恢复可用。当决策做出后,对系统日志的处理(以及对应的监控)就变得非常重要。

  • 针对任何系统的副本数量的考虑都是基于以下几个因素中的一个进行妥协:● 对可靠性的要求● 计划内维护操作的频率● 危险性● 性能● 成本最后的决策每个系统都会不同:每个系统都有不同的可用性服务水平目标;某些组织会更频繁地进行维护性操作;而某些组织使用的硬件成本、质量和可靠性都有所不同。

  • 一个故障域(failure domain)是指系统中可能由于一个故障而同时不可用的一组组件。常见的故障域包括:● 物理机器。● 数据中心中用同一个供电设施的一个机柜。● 数据中心中的数个机柜,使用同一个网络设备连接。● 受单个光纤影响的一个数据中心。● 处于同一个地理区域的一组数据中心,可能被同一个自然灾害所影响。

  • 一般来说,随着副本之间的距离增加,副本之间的网络往返时间也会增加,但是系统能够承受的失败程度也会增加。对多数共识系统来说,网络往返时间的增加会导致操作延迟的增加。

  • 对延迟的敏感性和对灾难的承受程度,每个系统很不一样。在某些共识系统架构下并不需要特别高的吞吐量,或者特别低的延迟:例如,某个共识系统如果只是为了提供成员信息和领头人选举,服务一般不会负载很高,如果共识操作时间仅仅是领头人租约的一小部分时,性能并不是关键点。批处理居多的系统也很少会被延迟所影响:可以通过批处理数量的增加来提高吞吐量。

  • 当处理关键数据时,即使已经有了可靠的共识系统部署在不同的故障域范围内,我们也必须经常将数据备份在其他地方。因为有两个故障域是我们永远无法逃避的:软件本身和系统管理员的人为错误。软件系统中的Bug可能会在罕见情况下浮现造成数据丢失,而错误的系统配置也可能会造成类似情况。而人工操作可能会执行错误的命令,从而造成数据损坏。

  • 当决定副本位置的时候,记得最关键的因素是客户端可见性能:理想情况下,客户端和共识系统之间的RTT应该是最小化的。在广域网络中,无领头人的协议,例如 Mencius和 Egalitarian Paxos可能有一定的性能优势,尤其是当应用程序设计为可以对任意副本进行读操作而不需要进行共识操作时。

  • 向一个采取“大多数”法定仲裁过程系统中增加新的副本可能会降低系统的可用性,如图23-10所示。对ZooKeeper和Chubby来说,一个常见的部署使用5个副本,一个法定仲裁过程需要3个副本参与。整个系统可以在两个副本,也就是40% 不可用的情况下仍然正常工作。当使用6个副本时,仲裁过程需要4个副本:也就是超过33% 的副本不可用就会导致系统无法工作。

  • 为了将系统流量分布得更为均匀,系统设计者可以考虑采用5个副本,将其中2个副本放置于美国中心地带,1个放置于东海岸,另外两个放置于欧洲。这样分布可基本保证共识过程在北美洲的副本上完成,而不需要等待欧洲的回复。而从欧洲起始的共识过程可以仅仅跟美国东海岸的副本完成。东海岸的副本就像两个可能的仲裁组的关键轴,将两个组连接在了一起。

个人总结

  1. 复合式Paxos、快速Paxos协议
  2. 一个常见的部署使用5个副本,一个法定仲裁过程需要3个副本参与。
  3. 分布式共识协议学习参考 TODO

数据完整性的强需求

  • 数据完整性意味着用户可以维持对云服务的访问,用户对数据的访问能力是最重要的,所以这种访问能力的完整性非常重要。

  • 大多数云计算应用都是优化以下5项的某种组合:在线时间、延迟、规模、创新速度和隐私。

  • 为了避免应用程序的数据出现问题,影响到最终用户,一个带外系统、专门负责检查和平衡各种数据存储的系统就很有必要了。

  • 一般来说,公司会采用某种备份策略来“预防”数据丢失。然而,真正应该被关注的重点其实是数据恢复,这是区分备份与存档的重要区别。就像一句流行语说的那样:没有人真的想要备份数据,他们只想恢复数据。

  • 备份与存档最重要的区别就是,备份是可以直接被应用程序重新加载的。因此备份和存档的使用场景非常不同。

  • 存档的目的是将数据长时间安全保存,以满足审核、取证和合规要求。在这些情况下的数据恢复通常不需要满足服务的在线率要求。例如,我们可能需要将财务交易数据保存七年时间。为了达到这个目标,我们可以每个月将累积的审核日志迁移到离线的、异地的长期存档存储系统上。读取和恢复这样的信息可能需要一周时间,在长达一个月的财务审核过程中,这是可以接受的。相对存档,当灾难来临的时候,数据必须从真实的备份中快速恢复,最好能维持服务在线率的要求。否则的话,受影响的用户将由于数据问题无法使用应用程序,直到数据恢复过程完成。

  • 尤其考虑到大多数最新产生的数据直到安全备份结束之前都存在丢失的风险,这就意味着备份(而不是存档)应该至少每天进行一次,或者每小时,甚至更短时间内进行一次。备份应该同时使用完整备份、增量备份,或者甚至是流式持续备份手段。因此,当选择备份策略时,一定要考虑针对某个问题需要的恢复时间,以及可以丢失多少最新数据。

保障数据完整性和可用性:Google SRE的目标

  • 数据完整性是手段,数据可用性是目标数据完整性指的是在其生命周期中,数据的准确性和一致性。从数据被记录的那一刻开始,一直到数据被访问的时候,数据应该保持正确,不会以某种未预知的方式改变。

  • 从用户的角度来看,仅仅保障数据完整性,而没有保障数据的可用性是没有意义的。

  • 交付一个恢复系统,而非备份系统针对系统进行备份是一个长期被忽视、被拖延的系统管理任务。任何人都不会将备份作为一个高优先级任务进行—备份需要长期消耗时间和资源,却不能带来任何现在可见的好处。由于这个原因,在备份策略具体实施中如果出现了某些被忽视的细节问题,大家一般都能理解。甚至有的人说,就像其他针对低可能性的危险的保护措施那样,这样的态度是必然发生的。这里的核心问题其实是,我们没有意识到备份措施防护的问题虽然是不太可能发生的,但却是会造成严重问题的。当该服务的数据不可用时,备份系统对整个服务、整个产品,甚至整个公司来说,都是生死攸关的。

  • 相对于关注没人愿意做的备份任务来说,我们应该通过关注更重要的(但并不是更简单的)恢复任务来鼓励用户进行备份。备份其实就像纳税一样,是一个服务需要持久付出的代价,来保障其数据的可用性。我们不应该强调应该纳多少税,而是应该强调这些税会用来提供什么服务:数据可用性的保障。因此,我们不会强迫团队进行“备份”,而是要求:● 各团队需要为不同的失败场景定义一系列数据可用性SLO。● 各团队需要定期进行演练,以确保他们有能力满足这些SLO。

造成数据丢失的事故类型

  • 根源问题某种无法恢复的用户数据丢失是由以下几个因素造成的:用户行为、管理员的错误、应用程序的Bug、基础设施中的问题、硬件故障和部署区的大型事故。

  • Google根据一次针对19次数据恢复过程的研究得出,最常见的用户可见数据丢失场景是由于数据删除和软件Bug造成的引用完整性问题。

  • 在设计数据完整性保障机制时,必须要认识到复制机制和冗余并不意味着可恢复性。

扩展性问题:全量、增量以及相互竞争的备份和恢复机制

  • 当别人问你“是否有备份”的时候,一个经典的错误回答是“我们有比备份更好的机制—复制机制!”复制机制很重要,包括提高数据的本地性(locality),保护某个部署点单点事故等。但是有很多数据丢失场景是复制机制无法保护的。一个自动同步多个副本的数据存储多半会在你发现问题之前,将损坏的数据记录以及错误的删除动作更新到多个副本上。

Google SRE保障数据完整性的手段

  • 24种数据完整性的事故组合

  • 由于数据丢失类型很多(如上文所述),没有任何一种银弹可以同时保护所有事故类型,我们需要分级进行。分级防护会引入多个层级,随着层级增加,所保护的数据丢失场景也更为罕见。

  • 软删除机制可以大幅度减少支持人员的压力。软删除意味着被删除的数据立刻被标记为已删除,这样除了管理后台代码之外其他代码不可见。

  • 软删除还意味着一旦数据被标记为已删除,在某段时间以后会真的被删除。这个时间的长度,尤其是在有很多短期数据的情况下,取决于某个组织的政策和相关的法律条文、可用的存储空间和存储成本,以及产品的价格和市场定位等。常见的软删除时间是15、30、45或者60天。在Google的经验中,大部分账号劫持和数据完整性问题都会在60天内汇报或者检测到。因此,为软删除保存超过60天的需求可能没有那么强烈。

  • 应用中实现一个回收站机制,作为用户错误的主要防护手段。

  • 就算不考虑成本问题,频繁地进行全量备份也是非常昂贵的。最主要的是,这会给线上服务用户的数据存储造成很大的计算压力,以至于影响服务的扩展性和性能。为了缓解这种压力,我们需要在非峰值时间段进行全量备份,同时在繁忙时间段进行增量备份。

  • 没有任何办法可以100% 确定数据是正确的:虽然我们相信存储系统的实现,但是还是要校验!使这个问题变得更复杂的是,为了从低级数据损坏或者删除场景中恢复数据,我们必须从不同恢复点的不同备份中恢复不同子集的数据。同时,我们还要考虑到在一个不断变化的环境中,代码和数据结构的变化对老的备份数据的影响。

  • 安排一些开发者来开发一套数据校验流水线可能会在短期内降低业务功能开发的速度。然而,在数据校验方面投入的工程资源可以在更长时间内保障其他业务开发可以进行得更快。因为工程师们可以放心数据损坏的Bug没那么容易流入生产环境。和在项目早期引入单元测试效果类似,数据校验流水线可以在整个软件开发过程中起到加速作用。

  • 举一个具体的例子:Gmail拥有一系列数据校验器,每一个校验器都曾经在生产环境中检测到真实的数据完整性问题。Gmail开发者由于知道每个新引入的数据一致性问题都可以在24小时之内被检测到而感到非常安心。这些数据校验器的存在,结合单元测试及回归测试的文化使得Gmail开发者有足够的勇气每周多次修改Gmail生产存储系统的实现。

  • 带外数据校验比较难以正确实现。当校验规则太严格的时候,一个简单的合理的修改就会触发校验逻辑而失败。这样一来,工程师就会抛弃数据校验逻辑。如果规则不够严格,那么就可能漏过一些用户可见的数据问题。为了在两者之间取得恰当的平衡,我们应该仅仅校验那些对用户来说具有毁灭性的数据问题。

  • 大规模部署带外检测器可能成本较高。Gmail计算资源的很大一部分都被用来支持每日数据检测的运行。使这个问题变得更严重的是,校验器本身可能会造成软件服务器缓存命中率的下降,这就会造成用户可见响应速度的下降。为了避免这种问题,Gmail提供了一系列针对校验器的限速机制,同时定期重构这些校验器,以降低磁盘压力。

  • 我们可以保证每天运行的校验器持续寻找毁灭性的问题,而其他更严格的校验器可以以更低频率运行,以便满足延迟和成本的要求。

  • 大部分高速进行的小型开发团队都负担不起设计、构建和维护这样的系统所需的资源。就算强迫他们进行,最后的结果也经常是不可靠、不全面的,以及浪费性、一次性的方案,很快就会被抛弃。因此,最好的办法是单独组织一个中央基础设施组为多个产品和工作组提供一套数据校验框架。该基础设施组负责维护带外数据校验框架,而产品开发组负责维护对应的业务逻辑,以便和他们不断发展的产品保持一致。

  • 数据恢复流程中任何一个环节都有可能出问题,只有建立一个完善的端到端测试才能让人晚上放心睡觉。即使最近刚刚成功进行过一次数据恢复,下次执行时某些步骤仍然可能出问题。

  • 只有真正进行恢复操作之后,才能确定到底能否恢复最新数据。

  • 失败是不可避免的,不去主动寻找这些数据恢复失败的场景等于玩火自焚。只有通过进行测试来主动寻找这些弱点,才能够提前修复,关键时刻才不至于追悔莫及!

SRE的基本理念在数据完整性上的应用

  • 信任但要验证我们依赖的任何API都不可能一直完美工作。不管测试有多么复杂,工程质量有多么高,API都一定会存在某些问题。应该通过使用带外数据检测器来检查数据最关键、最核心的部分,哪怕API语义中说明了这些问题不存在。要记住,完美的理论不代表实现也是完美的。

个人总结

  • 备份与存档最重要的区别就是,备份是可以直接被应用程序重新加载的。
  • 通过使用带外数据检测器来检查数据最关键、最核心的部分。

不完美的机器

  • 不完美的机器从某种意义上讲,人类可以被称为不完美的机器。人会感觉无聊,人的处理器(指思维方式)工作原理不清楚,用户界面(指沟通方式)也不太友好,效率也不高。在工作安排中,能够认识到人的这些缺陷,取长补短是最好的。

  • 分心指数工程师能够被分心的方法太多了。例如,某个名字是Fred的SRE 星期一正常上班,他今天不是on-call,也不负责处理中断性事务。很明显,他希望能够进行他自己的项目工作。倒一杯咖啡,带上“不要打扰”的耳机,开始干活,一切正常,对吗?但是,下列任何一件事情都可能随时发生:● Fred的团队使用随机工单分配系统,某个需要今天处理的工单被分配给了他。● Fred的同事目前负责on-call,收到了一个紧急警报。发出警报的组件Fred最熟悉,所以这个同事过来咨询意见。● 某个用户将某个工单的优先级提高了,这个工单是上周Fred轮值on-call的时候就在处理的。● 某个已经持续了三四周的发布到Fred的时候突然出现了问题,Fred需要停下手中的事情检查发布的状态,回滚这个发布等。● 某个用户过来咨询一个问题,因为Fred为人和善。● 等等。最后结果是

  • 虽然Fred有一整天的时间分配给项目工作,他还是很容易被分心。他可以通过关闭E-mail、关掉IM,或者其他手段来减少一定的干扰。但是某些干扰是政策造成的,以及一些无法避免的责任造成的。我们可以说某种程度的干扰是不可避免的,也是有意为之的。这是正确的:每个人都有甩不掉的Bug,同事之间也会有关系的产生与职责的分配。然而,作为团队来说,是有一些管理方式能使更多的员工不受干扰的。

  • 极化时间为了限制干扰数量,我们应该减少上下文切换(指工作类型、环境等的改变)。某些中断性任务是无法避免的。然而,将工程师当成是可以随时中断、上下文切换没有成本是不正确的。给每次上下文切换加上成本的考虑。在项目工作中,一次20分钟的中断性任务需要进行两次上下文切换,而这种切换会造成数个小时的生产力的丧失。为了避免这种经常性的生产力丧失,我们应该延长每种工作模式的时间,一天甚至半天都可以。这种策略与“挤时间”(参见文献[Gra09])策略工作得很好。

  • 极化时间意味着当每个人来上班时,他们应该清晰地知道自己今天是否只是做项目工作,还是只是做中断性工作。这意味着他们可以更长时间地专注于手上的工作,不会不停地被那些他们本不应该负责的事情所打扰。

  • 一般性建议不论哪种中断性任务,如果中断性任务的量对一个人来说太高,那么应该增加一个人负责。这个概念很显然适用于工单,但是也适用于紧急警报。on-call工程师可以将紧急警报降级为工单,也可以找副on-call来共同处理。

  • 主on-call工程师应该专注于on-call工作。如果目前紧急警报较少,那么一些可以随时放下的工单,或者其他中断性事务应该由on-call人员处理。当某个工程师on-call一周时,这一周他应该完全排除在项目进度之外。如果某个项目非常重要,不能等待一周,那么这个人就不应该参与on-call。管理层应该介入,安排其他人替代on-call。管理层不应该期望员工在on-call的同时还能在项目上有所进展(或者其他高上下文切换成本的活动)。

  • 另外:总有一些清理工作要做,工单数量可能是0,但是总会有需要更新的文档,需要整理的配置文件等。未来的on-call工程师可以从这些活动中受益。他们就不会再来打扰你了!

  • 工单如果目前你是随机分配工单给团队成员,请立刻停止。这样做对团队的时间非常不尊重,和让成员尽可能不被打扰的目标背道而驰。工单处理应该由全职人员负责,同时保证占用合理的时间。如果团队目前的工单主oncall和副on-call都处理不完,那么需要重新架构整个工单的处理流程,保障任何时间都有两个全职员工处理工单。不要将复杂分散到整个团队中去。人不是机器,这样做只会干扰员工,降低他们的工作效率。

  • 持续性的运维工作尽可能地让整个团队共同负责这件事。例如变更发布、配置文件修改等,如果这项工作有非常清晰的流程定义,那么就没有任何理由让任何一个人专门负责这件事情。定义一个发布管理者的角色来由on-call和其他中断性任务处理者来承担。同时应该进行正式化交接过程—这有一定成本,但是却保障了接下来的人不受打扰。负责中断性事务,或者不负责有的时候,某个中断性任务只有某个目前不在值班的成员能够妥善处理。虽然理想情况下,这种情况不应该发生,但是还是偶尔会发生,我们应该尽一切努力避免这种情况。

个人总结

  1. 极化时间,减少干扰;整理必要的文档。
  2. 不要将复杂分散到整个团队中去。人不是机器,这样做只会干扰员工,降低他们的工作效率。

第30章 通过嵌入SRE的方式帮助团队从运维过载中恢复

  • Google的SRE团队的标准政策要求团队在项目研发和被动式工作之间均匀分配时间。在实际工作中,这种平衡可能会被每天工单数量的不断增加而被打乱,甚至持续数月之久。长时间承担非常繁重的Ops类型的工作是非常危险的,团队成员可能会劳累过度,不能在项目工作上取得任何进展。当一个团队被迫花费过多时间解决工单的时候,他们就会减少花在改进服务上的时间,服务的可扩展性和可靠性肯定会随之变差。解决这个问题的一种方式是给处于过载状态的团队临时调入一个SRE。调入该团队后,该SRE应该关注于改善这个团队的行事方式,而不是简单地帮助团队清理积压的工单。通过观察团队的日常工作,提出改善性意见,该SRE可以帮助团队用全新的视角来审视自己的日常工作。这往往是团队本身的成员做不到的。注意,当采用这个方法的时候,只需要调入一名SRE就够了。同时调入两名SRE不一定会产生更好的结果。对那些防御性比较强的团队来说,可能会激发问题的发生。

  • 当你开始构建自己的第一个SRE团队时,采用本章中介绍的方法可以帮助避免团队走入歧途,退化成那种只专注于工单处理的传统运维团队。

  • 在决定采用这种嵌入SRE的方式之前,你应该花一些时间来回顾一下Ben Treynor Sloss的介绍中提到的SRE理念和实践经验,同时应该看一下本书的第6章。

第一阶段:了解服务,了解上下文

  • 第一阶段:了解服务,了解上下文在嵌入团队的过程中,你的主要工作是清楚地表达团队目前的流程和工作习惯对于该服务的可扩展性有利或者有弊的原因。你应该提醒该团队,日益增加的工单不应需要更多的SRE来处理。SRE模型的目标是仅仅在系统复杂度上升的时候才增加新人。你应该尝试引导团队建立健康的工作习惯,这样能够减少花费在工单上的时间。这与指出该服务目前还可以自动化或者进一步简化同样重要。

  • SRE团队陷入Ops模式的原因是过分关注如何快速解决紧急事件而不是如何减少紧急事件的数量。

第二阶段:分享背景知识

  • 第二阶段:分享背景知识在了解团队内部动态和找到痛点之后,你应该通过建立一些最佳实践来为改善状况做准备。例如,建立书写事后总结的机制,辨别琐事的来源,确定每个琐事的最佳解决方案等。书写一个好的事后总结作为示范

  • 你应该将团队遇到的紧急事件分为琐事的和非琐事的。最后,将这个列表交给团队并且清晰地解释哪些紧急事件应该被自动化,而其他的紧急事件则是运维服务必须承担的工作。

第三阶段:主导改变

  • 第三阶段:主导改变保持团队健康是一个持续的过程。正因为此,这不是你可以通过个人英雄主义来解决的问题。为了确保团队在未来可以进行自我调节,我们需要帮助他们建立一个良好的SRE心理模型。

  • 对某项决策进行充分解释的例子:●“我反对最新版本的原因不是因为测试结果有问题,而是因为我们对发布所制定的错误预算已经耗尽了。”● “发布需要能够安全回退,因为我们的SLO非常高。要想达到SLO的要求,平均恢复时间必须非常短,这样在回退之前进行深入的现场调查是不现实的。”对于某项决策不充分的解释的例子:●“我不认为每一个服务器自己生成自身的路由配置是安全的,因为我不信。”这一决策可能是正确的,但是分析论证上是很薄弱的(也没有详细解释)。团队无法知道你心中所想,所以他们很可能模拟你这种不良行为。相反,尝试“[……]不安全,因为在该代码中的一个错误会导致其他服务同时受到影响,同时额外的代码也可能带来减慢回滚的速度的问题。”●“自动化机制在遇到部署不一致的情况时应该放弃进行。”

  • 和之前的例子一样,这一决策可能是正确的,但是是不充分的。相反,尝试“[……]因为我们这里的一个简化假设是全部的改变都通过自动化进行,这种情况的发生说明了有什么东西违反了这一规则。如果这种情况经常发生,我们应该找到和消除造成这种变化的根源。”

  • 提出引导性问题应该提出引导性问题,而非指责性的问题。当你和SRE团队交流时,试着用一种可以鼓励别人思考基本理念的方式来提出问题。

  • 引导性问题的例子:● “我看到任务失败的警报经常发生,但是on-call工程师通常什么都不做。这样会对SLO有什么影响?”● “这个上线过程看起来非常复杂。你知道为什么创建一个新的服务实例需要这么多的配置文件的更新吗?”引导性问题的反例:●“这些旧的、停滞的发布是什么情况?”●“为什么某个组件要做这么多的事情?”

小结

  • 你的最后一个任务是书写一份报告。报告中应该重申你的观点、例子和逻辑推理过程。同时,该报告应该向团队提供一些待办事项,来保证他们会实践你所传授的东西。你应该将报告组织成一份检查报告[12],解释成功路上的每一个重要的决策。大部分的工作现在已经完成了。虽然你的嵌入式工作正式结束,但仍然应该参与设计评审和代码评审。在未来几个月中持续关注这个团队,确保他们正在慢慢提高自己的容量规划能力、紧急事件处理能力和部署发布过程。

第31章 SRE与其他团队的沟通与协作

沟通:生产会议

  • 在两个SRE团队视频会议时,如果一个团队比另外一个大很多,我们建议从较小的团队一边选择主席。更大的一方会自然而然地安静下来,一些不平衡的团队规模所造成的不良影响(这些会由于视频会议的延迟而变得更糟)将得到改善。[14]我们不知道这是否有任何科学依据,但它确实有效。

SRE的内部协作

  • 不管这些角色定义得是否清晰,基本来说,技术负责人是负责团队技术方向的,可以以多种方式进行领导。他可以通过仔细评审每个人的代码,组织进行季度工作汇报,或者是在团队中推进共识的建立来引领团队方向等方式来领导团队。

  • 在Google内部,技术负责人和SRE经理的工作几乎一样,因为我们的SRE经理也具有高度的技术能力。但SRE经理在这之外还有两个特殊责任:绩效管理,以及其他一切其他人不能处理的事情。好的技术负责人、SRE经理以及项目经理组成了一个完整的管理团队,可以很好地组织项目进行讨论设计文档,必要的时候甚至亲自书写代码。

  • 建议只有在不得已的情况下才应该跨地域开发项目,但是这同时也会带来一定的好处。跨地域工作需要更多的沟通,工作完成得也更慢;但是好处是—如果你能协调好的话—则会有更高的产能。单地项目其实也可能会导致其他人不知道你正在做什么,所以这两种做法其实都有一定成本。


第33章 其他行业的实践经验

  • 在本章中,我们会讨论到许多SRE的核心指导思想。为了简化与其他行业最佳实践的比较,我们将这些理念分为4大类:● 灾难预案与演习● 书写事后总结的文化● 自动化与降低日常运维负载● 结构化的、理智的决策

附录A 系统可用性

附录B 生产环境运维过程中的最佳实践

附录C 事故状态文档示范

附录D 事后总结示范

附录E 发布协调检查列表


其他


看见有人不赞同,其实这些问题你们没有遇见过,产品着急上线,出现了一个极低概率的case,修复成本很高。上线后的回报很大,而这个case带来的损失很小,这个时候肯定带着bug上线啊。很多时候产品上线发现商业模式跑不通,这个bug就不用修了,或者迭代过程中才用了另外的实现方式避免了这个问题。错误预算都不一定能用上。
——评论

阿里的要求:可灰度、可监控、可回滚,大厂的思路基本都是一致的。
——评论

产品在不同的发展阶段,可用性目标应该是动态变化的,快速发展期的可用性往往低于成熟稳定期,以保证新功能的发布、试错等顺利进行。——评论

打天下和守天下,需要不同的节奏,优先级不同,方案和指标也不同。——评论

量化风险成本,建立一致性目标,风险共担,减少非必要的沟通扯皮成本,比较好的双赢模式。
对照着看传统运维和开发的独立承担指标就属于零和博弈了,只有政治斗争能化解。——评论

  • 一文帮你理解 Google SRE 体系
  • 我对 SRE 的理解
  • 云原生背景运维转型之 SRE 实践
  • SRE的主要职责是什么?

开发阶段checklist

发表于 2020-12-23
  • 方案补全
  • 索引复查; 主库从库查询检查;事务检查;写语句死锁风险检查
    (更新语句要注意index_merge问题,避免死锁)
  • 缓存策略
  • 降级检查
  • 异常捕捉处理
  • 使用代码扫描工具
  • 错误码整理
  • 灰度策略;是否可回滚;是否可监控
  • 日志补全
  • 考虑异步操作
  • 监控数据状态,比如某些任务表,监控失败率及时发现异常
  • git仓库包含多模块项目,要注意是否都发版了或注意改动范围,否则会导致代码已经合并到主干了,其他的项目却没上线,没能及时发现问题
  • 测试用例
    • 注意实体转换的关键字段也需要覆盖用例,不然漏设置都无法察觉
  • 不能迷信单元测试,需要整条链路断点走一遍
    • 有时可能因为少传一个参数,参数是下一个方法用到的,局部的单元测试很难发现
  • 复查测试环境和生产环境的表结构是否一致
    • 有时为了方便快速在测试执行变更语句,而忘记同步到线上的变更SQL中
  • 上线申请事项记录

《枪炮、病菌与钢铁:人类社会的命运》-笔记

发表于 2020-12-19
  • 为什么最后带来枪炮、凶恶的病菌和钢铁的竟是欧洲人,而不是非洲人或印第安人?
  • 对本书来说,这样的一句话就是:“不同民族的历史遵循不同的道路前进,其原因是民族环境的差异,而不是民族自身在生物学上的差异。”

第一章 走上起跑线

  • 随着人类在澳大利亚/新几内亚的定居,现在人类已占据了可以居住的5个大陆中的3个。(在本书中,我始终把欧亚大陆算作一个大陆,我没有把南极大陆计算在内,因为南极大陆直到19世纪才有人到达,而且从来没有任何自给自足的居民。)这样就只剩下两个大陆:北美洲和南美洲。它们无疑是最后两个有人定居的大陆,这原因很明显,因为从旧世界到达美洲要么用船(甚至在印度尼西亚直到4万年前才有证据表明已有了船,而欧洲要晚得多才有船)去渡海,要么得先占有西伯利亚(直到大约2万年前才有人居住)以便通过白令陆桥。
  • 克罗维的猎人们在美洲向南推进,遇到了以前从未见过人的大型动物。他们可能发现这些美洲动物很容易杀死,于是就把它们消灭了。
  • 在可以住人的5个大陆中,北美洲和南美洲是人类史前史最短的两个大陆。
  • 在过去的700年中,唯一的无人居住、等待欧洲探险者光顾的地区就只剩下大西洋和印度洋中那些最偏远的岛屿(如亚速尔群岛和塞舌尔群岛)和南极大陆了。
  • 如果现代人类的确是在大约10万年前出现在非洲,然后向其他大陆扩散,那么其他地方在这期间积累起来的优势都会被一扫而光,从而使非洲人取得新的领先优势。而且,人类遗传的多样性以非洲为最高;也许更多样的人类集体会带来更多样的发明创造。不过,我们的这位考古学家那时可能会想:就本书的论题来说,究竟什么是“领先优势”?

第二章 历史的自然实验

  • 一般地说,人口越多,人口密度越高,技术和组织就越复杂,专业程度就越高。简言之,人口密度高时,只有一部分人最后成为农民,但他们被调动起来去专门从事集约型的粮食生产,从而生产出剩余粮食去养活非生产者。能够调动农民的非生产者包括首领、神职人员、官员和战士。

总结

  • 人口密度的重要性!

第三章 卡哈马卡的冲突

  • 现代最大的人口变迁是欧洲人对新大陆的移民,以及随之发生的对美洲土著(美洲印第安人)的征服、土著人数的减少或完全消失。
  • 阿塔瓦尔帕在卡哈马卡的出现突出了世界史上的一个关键因素:具有相当免疫力的入侵民族把疾病传染给没有免疫力的民族。天花、麻疹、流行性感冒、斑疹伤寒、腺鼠疫以及其他一些在欧洲流行的传染病,毁灭了其他大陆的许多民族,从而在欧洲人的征服中起了一种决定性的作用。
  • 我并不是要暗示历史上疾病的作用只限于为欧洲人的扩张铺平道路。疟疾、黄热病以及热带非洲、印度、东南亚和新几内亚的一些其他疾病,是欧洲在这些热带地区进行殖民的最大障碍。
  • 皮萨罗成功的直接原因包括:以枪炮、钢铁武器和马匹为基础的军事技术;欧亚大陆的传染性流行病;欧洲的航海技术;欧洲国家集中统一的行政组织和文字。
  • 为什么这种直接优势总是在欧洲一边,而不是在新大陆一边。为什么不是印加人发明枪炮和钢刀,骑上像战马一样的令人生畏的牲口,携带对欧洲人来说没有抵抗力的疾病,修造远洋船只和建立先进的行政组织,并能从几千年有文字记载的历史吸取经验?

总结

  • 病菌的影响

第四章 农民的力量

  • 从间接的意义说,粮食生产是枪炮、病菌和钢铁发展的一个先决条件。
  • 第一个因果关系是最直接的因果关系:能够获得更多的可消耗的卡路里就意味着会有更多的人。在野生的动植物物种中,只有很少一部分可供人类食用,或值得猎捕或采集。
  • 通往使某些民族能够征服另一些民族的近似因素(如枪炮、马匹和疾病)的因果关系链。例如,人类的各种各样疾病是在有许多适于驯化的动植物物种的地区演化的,这一部分是由于生产出的农作物和饲养的牲畜帮助养活了使流行疾病得以保持的人口稠密的社会;一部分是由于这些疾病是从驯化的动物身上的病菌演化而来。
  • 作为传统社会中的一个燃料来源,动物粪便也有其价值。此外,最大的驯化哺乳动物与驯化植物相互作用,以增加粮食产量,这表现在它们可以用来拉犁,从而使人们可以去耕种以前如用来耕种则代价太高的土地。
  • 许多狩猎采集社会里的人经常跑来跑去寻找野生食物,但农民必须留在他们的田地和果园附近。因此而产生的固定居所由于缩短了生育间隔期而促使人口变得更稠密起来。
  • 事实上,到处流浪的以狩猎采集为生的人通过哺乳期无月经、禁欲、杀婴和堕胎等办法,把孩子出生的间隔安排为大约每4年一个。相比之下,定居的部族由于没有在迁移途中携带小孩这种问题的限制,他们可以多生多养,只要养得活就行。
  • 评论:采集社会时期的人类同样采取抛弃弱小,这样做用现代评价标准就是灭绝人性,但在当时是不得不采取的手段。
  • 定居生活的另一个结果是人们可以把多余的粮食贮藏起来,因为如果人们不能留在附近看管贮藏的粮食,那么贮藏就是毫无意义的。虽然有些到处流浪的狩猎采集部族可能偶尔也把几天吃不完的食品收藏起来,但这种富余对他们几乎毫无用处,因为他们不能保护它。但贮藏的粮食对于养活不生产粮食的专门人材是必不可少的,而对于养活全村社的人肯定是必不可少的。因此,到处流浪的狩猎采集社会几乎没有或完全没有这类专职的专门人材,这种人材首先出现在定居社会中。
  • 一旦有了粮食储备,行政上层人物就可以控制别人生产的粮食,维护征税的权利,无需去养活自己,而以全部时间从事行政活动
  • 通过税收建立剩余粮食储备,除了养活国王和官员外,还能养活其他专职的专门人材。与征服战争关系最直接的是,剩余粮食储备可以用来养活职业军人。这是不列颠帝国最终打败新西兰武装精良的本土毛利人的决定性因素。
  • 至此,我已着重指出了作为粮食的农作物和家畜的直接和间接的价值。然而,它们还有其他用途,例如帮我们保暖和向我们提供有价值的材料。农作物和家畜生产出的天然纤维,可以用来做衣服、毯子、网和绳子。
  • 驯化的大型哺乳动物在19世纪铁路发展起来之前成为我们主要的陆路运输手段,从而进一步使人类社会发生了革命性的剧变。最后以蒙古人于公元13世纪和14世纪征服亚洲和俄罗斯的许多地方而达到高潮。只是由于在第一次世界大战中采用了卡车和坦克,马的作用才最后被取代,而不再是战争中主要的突击手段和快速运输的工具。
  • 在征服战争中同样重要的是在驯养动物的社会中演化的病菌。像天花、麻疹和流行性感冒这类传染病作为人类的专化病菌出现了,它们原是动物所感染的十分类似的祖代病菌由于突变而衍生出来的
    驯养动物的人成了这些新演化出来的病菌的第一个受害者,而这些人接着又逐步形成了对这些新的疾病的强大的抵抗力。当这些有部分免疫力的人与以前从来没有接触过这种病菌的人接触时,流行病于是产生了,99%的以前没有接触过这种病菌的人因之而丧命。从驯养的动物那里最后获得的病菌,在欧洲人对美洲、澳大利亚、南非和太平洋诸岛的土著的征服中起了决定性的作用。
  • 总之,动植物的驯化意味着人类的粮食越来越多,因而也就意味着人口越来越稠密。因此而带来的粮食剩余和(在某些地区)利用畜力运输剩余粮食,成了定居的、行政上集中统一的、社会等级分明的、经济上复杂的、技术上富有革新精神的社会的发展的先决条件。因此,能否利用驯化的动植物,最终说明了为什么帝国、知书识字和钢铁武器在欧亚大陆最早发展起来,而在其他大陆则发展较晚,或根本没有发展起来。

总结

  1. 粮食生产的重要
  2. 定居生活
  3. 病菌的产生

第五章 历史上的穷与富

  • 为什么粮食生产首先在看似相当贫瘠的土地上形成,只是到后来才在今天最肥沃的农田和牧场发展起来?所有这些问题都涉及不同的发展阶段,而正是这些不同的发展阶段决定了哪些民族成了历史上的贫穷民族,哪些民族成了历史上的富有民族。
  • 世界上只有几个地区发展了粮食生产,而且这些地区发展粮食生产的时间也差异甚大。一些邻近地区的狩猎采集族群从这些核心地区学会了粮食生产,而其他一些邻近地区的族群则被来自这些核心地区的粮食生产者所更替了——更替的时间仍然差异甚大。最后,有些族群虽然生活在一些生态条件适于粮食生产的地区,但他们在史前期既没有发展出农业,也没有学会农业;他们始终以狩猎采集为生,直到现代世界最后将他们淘汰。在粮食生产上具有领先优势的那些地区里的族群,因而在通往枪炮、病菌和钢铁的道路上也取得了领先的优势。其结果就是富有社会与贫穷社会之间一系列的长期冲突。

第六章 种田还是不种田

  • 由于粮食生产较狩猎采集有更高的每英亩可摄入卡路里数,人口密度也倾向于更高。而在另一方面,整个更新世后期的人口密度本来就在持续上升,归因于人类采集加工野生食物的技术进步。随着人口增长,粮食生产越来越受青睐,因为它提供了给养所有人所必需的粮食增产。这就是说,采纳粮食生产为所谓的自身催化过程提供了例证——这是一个在正反馈循环中自身催化的过程,这个过程一旦开始,速度就越来越快。人口密度的逐步增加,迫使人们去奖励那些无意中增加了粮食产量的人,以获得更多的粮食。一旦人们开始生产粮食并过定居的生活,他们就能够缩短生育间隔期,生出更多的人来,从而也就需要更多的粮食。粮食生产与人口密度之间的这种双向关系,说明了一种矛盾的现象,即粮食生产一方面增加了每英亩可摄入的卡路里的数量,一方面却又使这些粮食生产者的营养不及他们所承继的那些以狩猎采集为生的人。这种矛盾之所以产生,是因为人口密度的增加速度要稍高于粮食的增加速度。

第七章 怎样识别杏仁

  • 植物驯化可以定义为:栽种某一植物并由此有意或无意地使其发生不同于其野生祖先的、更有利于人类消费的遗传变化。
  • 不同的植物由于十分不同的或甚至相反的特点而得到选择。有些植物(如向日葵)由于大得多的种子而得到选择,而另一些植物(如香蕉)则由于种子小或没有种子而得到选择。选择生菜则取其茂盛的叶子而舍其种子或果实;选择小麦和向日葵则取其种子而舍其叶;选择南瓜则取其果实而舍其叶。特别有意思的是,由于不同的目的,对于一种野生植物可以有不同的选择,从而产生了外观十分不同的作物。
  • 新月沃地的小麦和大麦是被称之为谷物(属禾本科)的那类作物的代表,而新月沃地的豌豆和兵豆则是豆类(属豆科,包括大豆)的代表。谷类作物的优点是生长快,碳水化合物含量高,每公顷耕地可产1吨食物。因此,今天的谷物占人类消耗的全部卡路里的半数以上,并包括现代世界上12种主要作物中的5种(小麦、玉米、稻米、大麦和高粱)。许多谷类作物蛋白质含量低,但这一缺陷可以由豆类来弥补,因为豆类的蛋白质通常达25%(大豆为38%)。因此,谷物和豆类一起为均衡饮食提供了许多必不可少的成分。

第八章 问题在苹果还是在印第安人

  • 人类历史的主要事实之一,是西南亚的那个叫做新月沃地的地区(因其在地图上的新月状高地而得名,见图8.1)在人类发展早期的重要性。那个地区似乎是包括城市、文字、帝国以及我们所说的文明(不论是福是祸)在内的一连串新情况发生的地方。而所有这些新情况之所以发生,都是由于有了稠密的人口,有了剩余粮食的贮存,以及可以养活不从事农业的专门人材,凡此种种之所以可能又都是由于出现了以作物栽培和牲口饲养为形式的粮食生产。粮食生产是新月沃地出现的那些重要新事物中的第一个新事物。因此,如果想要了解现代世界的由来,就必须认真对待这样的问题,即为什么新月沃地的驯化动植物使它获得了如此强大的领先优势。
  • 原来地中海气候带,尤其是在新月沃地那个地区,具有胜过其他地中海气候带的5个有利条件。第一,欧亚大陆西部显然是世界上属于地中海气候带的最大地区。因此,那里的野生动植物品种繁多,超过了澳大利亚西南部和智利这些比较小的地中海气候带。第二,在地中海气候带中,欧亚大陆西部的地中海气候带的气候变化最大,每一季、每一年气候都有不同。这种气候变化有利于植物群中数量特别众多的一年生植物的演化。物种多和一年生植物多这两个因素结合起来,就意味着欧亚大陆西部的地中海气候带显然是一年生植物品种最繁多的地区。智利的地中海型气候带只有2种,加利福新月沃地的地中海气候带的第三个有利条件,是它在短距离内高度和地形的富于变化。它的高度从地球上的最低点(死海)到18000英尺的高山(在德黑兰附近),应有尽有,从而保证了环境的相应变化,也因此保证品种繁多的野生植物可供成为作物的祖先。
  • 地球上不同地区的当地作物并不是同样多产的。
  • 采纳粮食生产涉及粮食生产的生活方式与狩猎采集的生活方式之间的竞争。而受到损害的则是那些没有这种机会或意愿的部落。因此,新几内亚土生土长的粮食生产所受到的限制与新几内亚的族群没有任何关系,而是与新几内亚的生物区系和环境有着最密切的关系。
  • 事实上,在整个大陆和其他一些包含数以百计的互相竞争的广大地区,有些社会对新事物可能比较开放,有些社会对新事物可能比较抵制。那些接受新作物、新牲畜或新技术的社会因而可能吃得更好,繁殖得更快,从而取代、征服或杀光那些抵制新事物的社会。
  • 澳大利亚这个据称最“落后的”大陆很好地说明了这个问题。澳大利亚东南部是这个大陆上水源充足、最适合粮食生产的地方。那里的土著社会在最近的几千年里似乎一直在按照一种可能最终导致本地粮食生产的发展轨迹在演化。它们已经建立了过冬的村庄。它们已经开始加强利用它们的环境,建造渔栅、编织渔网,甚至挖掘长长的水渠来从事渔业生产。如果欧洲人没有在1788年向澳大利亚殖民,从而中途破坏了那个独立的发展轨迹,那么澳大利亚土著也许不消几千年就可成为粮食生产者,照料一池池驯化了的鱼,种植驯化了的澳大利亚薯蓣和小籽粒的禾本科植物。根据这一点,我现在就能够回答包含在本章标题里的那个问题。我提出的那个问题是:北美印第安人未能驯化北美苹果的原因是在印第安人还是在苹果。
  • 在欧洲人到达时印第安人仍未能驯化北美的苹果,其原因不在印第安人,也不在苹果。就苹果驯化必要的生物条件而言,北美印第安农民和欧亚大陆农民一样,北美的野生苹果也和欧亚大陆的野生苹果一样。事实上,本章读者现在正在津津有味地吃着的从超市上买来的苹果,有些品种就是不久前将欧亚大陆的苹果同北美的野生苹果进行杂交而培育出来的。印第安人未能驯化苹果的原因却是在于印第安人所能得到的整个野生动植物组合。这个组合的微弱的驯化潜力,就是北美粮食生产很晚才开始的主要原因。

总结

  1. 新月沃地(地中海气候)
  2. 美洲整个野生动植物组合,微弱的驯化潜力。

第九章 斑马、不幸的婚姻和安娜·卡列尼娜原则

  • 驯养的大型哺乳动物对那些拥有它们的人类社会产生重大影响的那许多方面。最显著的是,这些动物提供了肉食、奶制品、肥料、陆上运输、皮革、军事突击手段、犁具牵引、毛绒以及使先前没有抵抗力的民族失去生命的病菌。
  • 各大陆之间野生祖先的这种十分不均匀的分布,成了欧亚大陆人而不是其他大陆的人最后得以拥有枪炮、病菌和钢铁的一个重要原因。
  • 欧亚大陆何以一直是大型哺乳动物驯化的主要场所,对这个问题的部分解释是:它是一个一开始就拥有最多的可供驯化的野生哺乳动物的大陆,在过去的40000年中,那里这样的动物因绝种而消失的也最少。
  • 在驯化动物方面现代尝试的失败,提供了最后一个证据,表明过去在驯化剩下的大批候补野生动物方面的失败是由于这些动物本身的缺点,而不是由于古代人的缺点。
  • 每一次某种动物在吃某种植物或另一种动物时,食物生物量转换为取食者生物量的效率远远低于100%:通常在10%左右。就是说,要花费10000磅左右的玉米才能喂养出一头1000磅重的牛。
  • 动物的日常食物、生长速度、交配习惯、性情、容易受惊的倾向以及群居组织的几个不同的特点。只有很少一部分野生哺乳动物由于在上述所有这些方面都能协调一致而最终得以和人类结成美满的婚姻。
  • 欧亚大陆的民族碰巧比其他大陆的民族继承了多得多的可驯化的大型野生的哺乳类食草动物。这一结果及其为欧亚大陆社会带来的全部利益,来自哺乳动物地理学、历史和生物学这3个基本事实。
  • 欧亚大陆由于其广大面积和生态的多样性,一开始就拥有最多的可供驯化的候补动物。其次,澳大利亚和美洲,而不是欧亚大陆或非洲,在更新世晚期动物灭绝的大规模浪潮中失去了它们大多数可供驯化的候补动物——这可能是因为前两个大陆的哺乳动物不幸首先突然接触到人类,而且这时已是我们的进化史的后期阶段,我们的狩猎技巧已经得到了高度的发展。

总结

  • 欧亚大陆的民族碰巧比其他大陆的民族继承了多得多的可驯化的大型野生的哺乳类食草动物。

第十章 辽阔的天空与偏斜的轴线

  • 轴线走向的差异所产生的巨大的、有时是悲剧性的后果。轴线走向影响了作物和牲口的传播速度,可能还影响文字、车轮和其他发明的传播速度。
  • 某种作物的迅速传播可能不但抢先阻止了同一植物的野生祖先在其他某个地方的驯化,而且也阻止了有亲缘关系的野生植物的驯化。
  • 许多不同的现象归结为同一个结论:粮食生产从西南亚向外传播的速度要比在美洲快,而且也可能比在非洲撒哈拉沙漠以南的地区快。这些现象包括:粮食生产完全未能到达某些生态条件适合于粮食生产的地区;粮食生产传播的速度和选择性方面存在着差异;以及最早驯化的作物是否抢先阻止了对同一种植物的再次驯化或对近亲植物的驯化方面也存在着差异。
  • 提出所有这些差异,并不就是说分布很广的作物是值得赞美的,也不是说这些差异证明了欧亚大陆早期农民具有过人的智慧。这些差异只是反映了欧亚大陆轴线走向与美洲或非洲大陆轴线相比较的结果。历史的命运就是围绕这些轴线旋转的。

第十一章 牲畜的致命礼物

  • 粮食生产这个终极原因是如何导致病菌、文化修养、技术和集中统一的政府这些直接原因的。
  • 过去战争中的胜利者并不总是那些拥有最优秀的将军和最精良的武器的军队,而常常不过是那些携带有可以传染给敌人的最可怕病菌的军队。
  • 虽然被那些杀人不眨眼的西班牙征服者杀死的印第安人不计其数,但凶恶的西班牙病菌杀死的印第安人却要多得多。为什么在欧洲和美洲之间这种可怕的病菌的交流是这样不对等?为什么印第安人的疾病没有大批杀死西班牙入侵者,并传回欧洲,消灭掉欧洲95%的人口?同样的问题也出现在被欧亚病菌大批杀死的其他土著身上,以及企图征服亚非热带地区的欧洲人身上。
  • 作为流行病有几个共同的特点。首先,它们从一个受感染的人迅速而高效地传给近旁健康的人,结果使整个人口在很短时间内受到感染。其次,它们都是“急性”病:在很短时间内,你要么死掉,要么完全康复。第三,我们当中的确获得康复的那些幸运的人产生了抗体,使我们在很长时间内,也可能是一辈子不用担心这种病会复发。最后,这些病往往只在人类中传播;引起这些病的病菌往往不是生活在土壤中或其他动物身上。所有这4个特点也适用于美国人所认为的那些习见的儿童急性传染病,其中包括麻疹、风疹、急性腮腺炎、百日咳和天花。这4个特点结合起来往往造成了某种疾病的流行,其原因不难理解。简单地说,情况是这样的:病菌的迅速传播和症状的迅速发展,意味着当地人口中的每一个人很快就受到感染,之后不久他或者死去,或者康复并获得免疫力。仍然会受到感染的人都不会活下来。但由于这种病菌除了在活人体内是不可能生存的,所以人死了这种病也就消失了,直到又一批儿童达到易受感染的年纪——直到一个受到感染的外来人使一场流行病重新开始。
  • 关于这些疾病是怎样流行起来的,有一个典型的事例是大西洋上叫做法罗群岛[插图]的与世隔绝的岛屿上的麻疹病史。1781年,一场严重的麻疹流行病到达法罗群岛,接着又消失了,其后该群岛就不再有麻疹发生,直到1846年,一个受到感染的木匠从丹麦坐船到来。不出3个月,法罗群岛的几乎全部人口(7782人)都得了麻疹,于是有的人死去,有的人康复,麻疹病毒又一次消失,直到下一次流行。一些研究表明,麻疹可能会在任何少于50万人的人口中消失。只有在比较多的人口中,这种病才会从一个地区转移到另一个地区,直到原先受感染地区里出生的婴儿达到足够的数目,麻疹又会卷土重来。适用于法罗群岛上麻疹的情况,也适用于世界上其他一些我们所熟悉的急性传染病。为了维持自身的存在,这些病需要有足够多的人口,足够拥挤的稠密人口,这样,到这种病不然就会衰退的时候,又有一大批易受感染的儿童成为感染对象。因此,麻疹和一些类似的疾病也叫做人群病。
  • 人群病不可能在小群狩猎采集族群和刀耕火种的农民中存在下去。现代亚马孙河地区印第安人和太平洋岛民的悲惨经历表明,整个小部落可能被一个外来人带来的一种流行病几乎全部消灭——因为这个小部落中没有一个人有任何抵抗这种病菌的抗体。小部落人口少,这一点不但说明了为什么他们承受不住从外面带来的流行病,而且也说明了为什么他们没有能演化出自己的流行病去回敬外来人。
  • 为什么农业的出现会成为我们人群传染病形成的开端?其中一个原因前面已经提到,那就是农业比狩猎采集的生活方式维持了高得多的人口密度——平均要高10倍到100倍。另外,狩猎采集族群经常变换营地,留下了一堆堆排泄物,上面聚集了大量病菌和寄生虫的幼虫。但农民是定居的,他们生活在自己排放出来的污水之中,从而为病菌从一个人的身体进入另一个人的饮用水源提供了捷径。有些农业人口把自己的粪便收集起来,当作肥料撒到人们劳动的田里,从而使粪便中的病菌和寄生虫去感染新的受害者变得甚至更加容易。
  • 在动物中,流行病同样需要稠密的大种群,而不是只去折磨任何某一只动物:这些流行病主要发生在需要有大的种群的群居动物中。因此,当我们驯养牛和猪这类群居动物时,它们已经受到了一些流行病的折磨,只不过在等待着转移给我们罢了。所有这些病菌仍然处在向人类病原体演化的早期阶段。
  • 美洲之所以未能出现流行的致命的人群病的主要原因就一定会变得很清楚。这个问题就是,想象一下这些疾病可能会从什么病菌演化而来?我们已经看到,欧亚大陆的人群病是从欧亚大陆驯化的群居动物的疾病演化而来的。尽管欧亚大陆有许多这样的动物,但在美洲驯化的动物只有5种:墨西哥和美国西南部的火鸡、安第斯山脉地区的美洲驼/羊驼和豚鼠、热带南美的美洲家鸭和整个美洲的狗。反过来,我们也看到,新大陆驯化动物的这种极端缺乏,反映了用以启动驯化的野生动物的缺乏。在大约13000年前上一次冰期结束时,美洲有大约80%的大型野生哺乳动物便已灭绝了。
  • 源于动物的疾病在历史上的重要性,远远超过了旧大陆与新大陆之间的冲突。欧亚大陆的病菌在大量消灭世界上其他许多地方的土著民族方面起了关键的作用。
  • 毫无疑问,欧洲人在武器、技术和行政组织方面拥有对他们所征服的大多数非欧洲民族的巨大优势。但仅仅这种优势还不能完全说明开始时那么少的欧洲移民是如何取代美洲和世界上其他一些地区那么多的土著的。如果没有欧洲送给其他大陆的不祥礼物——从欧亚大陆人与家畜的长期密切关系中演化出来的病菌,这一切也许是不会发生的。

总结

  1. 流行病(人群病)

第十二章 蓝图和借用字母

  • 知识带来力量。因此,文字也给现代社会带来了力量,用文字来传播知识可以做到更准确、更大量和更详尽,在地域上可以做到传播得更远,在时间上可以做到传播得更久。
  • 文字同武器、病菌和集中统一的行政组织并驾齐驱,成为一种现代征服手段。
  • 发明的传播形式有一系列形式。形式的一端是“蓝图复制”,就是对现有的一幅详尽的蓝图进行复制或修改。另一端是“思想传播”,就是仅仅把基本思想接受过来,然后必须去重新创造细节。
  • 虽然蓝图的复制和修改是传播技术的最直接的选择,但有时候这种选择不一定能够得到。蓝图可能被隐藏起来,而且不是深于此道的人对蓝图也不一定能够读懂。对于在远处某个地方发明了某个东西,人们可能有所耳闻,但详细情况则可能无从知晓。也许所知道的只是这样的基本思想:某人以某种方法成功地取得了某种最后的成果。然而,知道了这一点,可能就是通过思想传播去启发别人设计他们自己的取得此种成果的途径。
  • 大多数有文字的社会之所以获得文字,或是通过向邻近的社会借用,或是由于受到它们的启发而发明出文字,而不是靠自己独立创造出来的。
  • 文字史引人注目地表明了类似的情况:地理和生态条件影响了人类发明的传播。

第十三章 需要之母

  • 事实上,许多发明或大多数发明都是一些被好奇心驱使的人或喜欢动手修修补补的人搞出来的,当初并不存在对他们所想到的产品的任何需要。一旦发明了一种装置,发明者就得为它找到应用的地方。只有在它被使用了相当一段时间以后,消费者才会感到他们“需要”它。还有一些装置本来是只为一个目的而发明出来的,最后却为其他一些意料之外的目的找到了它们的大多数用途。
  • 寻求使用的这些发明包括现代大多数重大的技术突破,从飞机和汽车到内燃机和电灯泡再到留声机和晶体管,应有尽有。了解到这一点,也许会令人感到吃惊。因此,发明常常是需要之母,而不是相反。
  • 有一项大发明最终得到使用,就会有不计其数的其他发明得不到使用。甚至有些发明当初本来是为了满足特定的需要而设计的,后来可能在满足意外需要方面证明是更有价值的。虽然詹姆士·瓦特设计他的蒸汽机是为了从煤矿里抽水,但它很快就为棉纺厂提供动力,接着又(以大得多的利润)推动着机车和轮船前进。
  • 技术的发展是长期积累的,而不是靠孤立的英雄行为;技术在发明出来后大部分都得到了使用,而不是发明出来去满足某种预见到的需要。
  • 一旦发明家发现了一项新技术的用途,下一步就是说服社会来采用它。仅仅有一种更大、更快、更有效的工作装置还不能保证人们会乐于接受。无数的此类技术要么根本没有被采用,要么只是在长期的抵制之后才被采用。
  • 即使在同一个大陆上,各社会之间在发展和接受新事物方面也是大不相同的。即使是在同一个社会内,在时间上也会有所不同。在任何时候,在任何大陆上都有富于创新精神的社会,也有保守的社会。此外,在同一个地区内,对新事物的接受能力迟早会产生波动。
  • 一个用途广泛的发明在一个社会出现后,接着它便往往以两种方式向外传播。一种方式是:其他社会看到或听说了这个发明,觉得可以接受,于是便采用了。另一种方式是:没有这种发明的社会发现与拥有这种发明的社会相比自己处于劣势,如果这种劣势大到一定程度,它们就会被征服并被取而代之。
  • 在发明的传播中最容易接受发明的社会是大陆上的一些根基深厚的社会。在这些社会中技术发展最快,因为它们不但积累了自己的发明,而且也积累了其他社会的发明。
  • 我们往往想当然地认为,有用的技术一旦获得,就必然会流传下去,直到有更好的技术来取而代之。事实上,技术不但必须获得,而且也必须予以保持,而这也取决于许多不可预测的因素。任何社会都要经历一些社会运动和时尚,此时一些没有经济价值的东西变得有价值起来,而一些有用的东西也变得暂时失去了价值。今天,当地球上几乎所有社会相互联系在一起的时候,我们无法想象某种时尚会发展到使人们竟然抛弃一项重要的技术。一个著名的例子是日本放弃枪支。
  • 在同时代的欧洲也有一些鄙视枪支并竭力限制枪支使用的统治者。但这些限制措施在欧洲并未发生多大作用,因为任何一个欧洲国家,哪怕是短暂地放弃了火器,很快就会被用枪支武装起来的邻国打垮。只是因为日本是一个人口众多的孤立的海岛,它才没有因为拒绝这种具有巨大作用的新军事技术而受到惩罚。日本拒绝枪支和中国抛弃远洋船只(以及抛弃机械钟和水力驱动纺纱机),是历史上孤立或半孤立社会技术倒退的著名例子。
  • 在这漫长的加速发展的历史中,我们可以挑出两次意义特别重大的飞跃。第一次飞跃发生在100000年到50000年前,其所以能够发生,大概是由于我们身体的遗传变化,即人体的现代解剖学进化使现代语言或现代大脑功能或两者成为可能。这次飞跃产生了骨器、专用石器和复合工具。第二次飞跃来自我们选定的定居生活方式,这种生活方式在世界的不同地区发生的时间不同,在有些地区早在13000年前就发生了,在另一些地区即使在今天也还没有发生。就大多数情况而言,选定定居的生活方式是同我们采纳粮食生产联系在一起的,因为粮食生产要求我们留在我们的作物、果园和剩余粮食储备的近旁。
  • 定居生活对技术史具有决定性的意义,因为这种生活使人们能够积累不便携带的财产。四处流浪的狩猎采集族群只能拥有可以携带的技术。如果你经常迁移而且又没有车辆或役畜,那么你的财产就只能是小孩、武器和最低限度的其他一些便于携带的小件必需品。你在变换营地时不能有陶器和印刷机之类的累赘。这种实际困难或许可以说明何以有些技术出现得惊人地早,接着停了很长时间才有了进一步的发展。

总结

  1. 发明常常是需要之母,而不是相反。
  2. 不接受新事物,很可能会被征服并被取而代之。
  3. 日本放弃枪支;
  4. 定居生活的意义;

总结(重点!!!)

  • 现在,让我们来总结一下,粮食生产开始的时间、技术传播的障碍和人口的多寡这3大因素的变化,是怎样直接导致我们所看到的各大陆之间在技术发展方面的差异的。
  • 欧亚大陆(实际上也包括北非在内)是世界上最大的陆块,包含有数量最多的互相竞争的社会。它也是最早开始拥有粮食生产的两个中心的陆块,这两个中心就是新月沃地和中国。它的东西向的主轴线,使欧亚大陆一个地区采用的许多发明得以较快地传播到欧亚大陆具有相同纬度和气候的其他地区的社会。它的沿次轴线(南北轴线)的宽度,同美洲巴拿马地峡的狭窄形成了对照。它没有把美洲和非洲的主轴线切断的那种严峻的生态障碍。因此,对技术传播的地理和生态障碍,在欧亚大陆没有在其他大陆那样严峻。由于所有这些因素,后更新世技术的加速发展,在欧亚大陆开始得最早,从而导致了本地最大的技术积累。
  • 北美洲和南美洲在传统上被看作是两个不同的大陆,但它们连接在一起已有几百万年之久,有着类似的历史问题,因此可以把它们放在一起来考虑,以便和欧亚大陆相比较。美洲构成了世界上第二大的陆块,但比欧亚大陆小得多。不过,它们在地理和生态上却支离破碎:巴拿马地峡宽不过40英里,等于在地理上把美洲给腰斩了,就像这个地峡上的达里安雨林和墨西哥北部的沙漠在生态上所做的那样。墨西哥北部的沙漠把中美洲人类的先进社会同北美洲的社会分隔开了,而巴拿马地峡则把中美洲的先进社会同安第斯山脉地区和亚马孙河地区的社会分隔开了。此外,美洲的主轴线是南北走向,从而使大部分的技术传播不得不逆纬度(和气候)的梯度而行,而不是在同一纬度内发生。例如,轮子是在中美洲发明的,而美洲驼是不迟于公元前3000年在安第斯山脉中部驯化的,但过了5000年,美洲的这唯一的役畜和唯一的轮子仍然没有碰头,虽然中美洲玛雅社会同印加帝国北部边界之间的距离(1200英里)比同时享有轮子和马匹的法国同中国之间6000英里的距离要短得多。在我看来,这些因素足以说明美洲在技术上落后于欧亚大陆这个事实。
  • 非洲撒哈拉沙漠以南地区是世界上第三大的陆块,但比美洲小得多。在人类的大部分历史中,到欧亚大陆比到美洲容易多了,但撒哈拉沙漠仍然是一个主要的生态障碍,把非洲撒哈拉沙漠以南地区同欧亚大陆和北非隔开。非洲的南北轴线造成了欧亚大陆与非洲撒哈拉沙漠以南地区之间以及撒哈拉沙漠以南地区本身内部技术传播的又一障碍。作为后一障碍的例子,陶器和炼铁术出现在或到达非洲撒哈拉沙漠以南的萨赫勒地带(赤道以北),至少同它们到达西欧一样早。然而,陶器直到公元元年才到达非洲的南端,而冶金术在从欧洲由海路到达非洲南端时,还不曾由陆路传播到那里。最后,澳大利亚是最小的一个大陆。澳大利亚大部分地区雨量稀少,物产贫乏,因此,就其所能养活的人口来说,它实际上就显然甚至更小。它也是一个最孤立的大陆。加之,粮食生产也从来没有在澳大利亚本地出现过。这些因素加在一起,就使澳大利亚成为唯一的在现代仍然没有金属制品的大陆。
  • 人口多意味着搞发明的人和互相竞争的社会也多。
  • 各大陆之间在面积、人口、技术传播的难易程度和粮食生产的开始时间等方面存在着差异,而这些差异又对技术的出现产生了种种影响,但所有这些影响都被夸大了,因为技术可以催化自身。欧亚大陆在开始时的巨大优势因此就变成了自1492年[插图]起的巨大的领先优势——其原因是欧亚大陆独特的地理条件,而不是那里的人特别聪明。我所认识的那些新几内亚人中就有潜在的爱迪生。不过,他们把自己的聪明才智用于解决适合自己情况的技术问题:不靠任何进口物品而在新几内亚丛林中生存的问题,而不是发明留声机的问题。

第十四章 从平等主义到盗贼统治

  • 有些社会最早实现了集中统一的政府和有组织的宗教,而这些社会的子孙后代最后主宰了现代世界。政府和宗教就是这样结合起来发挥了作用,它们是产生历史最广泛模式的4组主要的直接动力之一,另外3组动力是病菌、文字和技术。

  • 超越族群的那些阶段中的第一个阶段是部落。部落与族群的区别是它比较大(一般有几百人,而不是几十人),而且通常有固定的居住地。然而,有些部落,甚至有些由酋长管辖的部落,却是由随季节而迁移的牧人组成的部落是由不止一个的得到正式承认的亲属群体所组成,这些群体称为氏族,氏族之间互相通婚。土地属于某个氏族,不属于整个部落。然而,部落的人数仍然很少,每一个人都知道另外每一个人的名字和他的各种亲属关系。对人类其他类型的群体来说也是一样,在一个群体里如要做到彼此了解,这个群体的人数最多似乎以“几百人”为宜。

  • 早期的国王本人就是国家宗教的领袖,否则就另外设立一个大祭司。美索不达米亚的寺庙不但是宗教活动的中心,而且也是经济再分配、文字和手工技术的中心。

  • 同样明显的是,国家在与较简单的实体发生冲突时所以能取得胜利,部分原因是国家拥有武器和其他技术方面的优势,同时也拥有人口数量上的优势。但酋长管辖地和国家还有另外两个固有的潜在优势。首先,中央决策者拥有集中军队和资源的优势。其次,许多国家的官方宗教和爱国热忱使它们的军队在作战中视死如归,心甘情愿地为国捐躯。在现代国家中,乐于为国牺牲的思想由我们的学校、教会和政府大力灌输给我们公民,使我们忘记了它标志着同以往人类历史的彻底决裂。

  • “几百人”是个界限,在这个界限内每个人能够认识另外每个人,一旦超过这个界限,越来越多的两人组合就成了一对对没有亲属关系的陌生人了。当陌生人打架时,在场的人很少会是打架双方的朋友或亲属,没有什么私利要他们去制止打架。相反,如果许多旁观者是打架一方的朋友或亲属,他们就会站在他的一边,这样,本来是两个人的打架结果就逐步升级为一场乱哄哄的群殴。因此,一个继续把冲突交给全体成员去解决的大型社会必然会分崩离析。随着人口的增加,共同决策越来越难以做到。

  • 部落之间进行征服或兼并以达到了酋长管辖地的规模,酋长管辖地之间进行征服或兼并以达到了国家的规模,国家之间进行征服或兼并以形成帝国。更一般地说,大的单位可能拥有对各个小的单位的某种优势。小型社会的领袖和大型社会的领袖一样,珍惜自己的独立和特权。合并的发生不外乎下面的两种方式之一:在外力的威胁下合并,或通过实际的征服。

  • 战败民族的命运取决于人口的密度,这有3种可能的后果:凡是人口密度很低的地方,就像在狩猎采集族群占据的地区所常见的那样,战败群体的幸存者只要离开他们的敌人远一点就行了。新几内亚和亚马孙河地区游牧部族之间战争的结果往往就是这样。凡是人口密度中等的地方,就像粮食生产部落占据的地区那样,没有大片空旷的地方可以让战败族群的幸存者逃避。但是,

  • 没有集约型粮食生产的部落社会不使用奴隶,也不能生产出可以作为很大一部分贡品的足够的剩余粮食。因此,战败部落的幸存者对胜利者来说毫无用途,除非娶他们的女人为妻。战败的男人都被杀死了,他们的地盘也可能为胜利者所占有。

  • 凡是人口密度高的地方,就像国家或酋长管辖地所占有地区那样,被打败的人仍然无处可逃,但胜利者不杀死他们而有了利用他们的两种选择。由于酋长管辖地社会和国家社会已出现了经济专业化,被打败的人可以当奴隶来使用,就像在《圣经》时代通常发生的那样。或者,由于许多这样的社会已经有了能够生产大量剩余粮食的集约型粮食生产系统,胜利者可以让战败者仍然从事原来的劳作,只是剥夺了他们的政治自主权,要他们定期地用粮食或货物来纳贡,并把他们的社会合并入获胜的国家或酋长管辖地。

  • 粮食生产及社会之间的竞争与混合,产生了征服的直接原动力:病菌、文字、技术和中央集权的政治组织。这些原动力往往是相互联系着一起出现的,不过这种联系并不是绝对的。

总结

  1. 中央集权政治组织
  2. 精神控制手段
  3. 大型社会必须要有复杂组织主要有四个原因:陌生人之间的人际冲突调节,共同决策越来越难做到,货物交换变得复杂,土地面积不变人口的增加使得人们从别的地区获得需要品。

第十五章 耶利的族人

  • 澳大利亚是最干燥、最小、最平坦、最贫瘠、气候最变化无常、生物品种最稀少的大陆。
  • 由于新几内亚人是粮食生产者,不是以狩猎采集为生的人,所以他们的平均人口密度比澳大利亚人高得多:新几内亚的面积只有澳大利亚的十分之一,但它所养活的当地人口却数倍于澳大利亚。
  • 新几内亚有几个不利于它的生物因素和地理因素。首先,虽然本地的粮食生产的确是在新几内亚高原地区出现的,但我们已在第八章中看到,它产出的蛋白质很少。当地的主食都是低蛋白的根用作物,而仅有的驯化动物(猪和鸡)的产量又太低,不能为人们提供大量的蛋白质。既然无法把猪或鸡套起来拉车,高原地区的居民除了两臂力气外,仍然没有其他动力来源,而且也未能发展出流行疾病以击退终于侵入的欧洲人。对高原地区人口数量的第二个限制,是能够利用的土地面积有限:新几内亚高原地区只有几处宽阔的谷地(最显著的是瓦吉谷地和巴利姆谷地)能够养活稠密的人口。第三个限制是这样的现实,即4000英尺至9000英尺之间的中间山地森林地带,是新几内亚唯一适于集约型粮食生产的高程地带。在9000英尺以上的新几内亚高山生境根本没有任何粮食生产,在4000英尺至1000英尺之间的山坡上几乎没有什么粮食生产,而在低地地区也只有低密度的刀耕火种农业。因此,在不同海拔高度专门从事不同类型粮食生产的一些社会之间对粮食的大规模经济交换,在新几内亚从未发展起来。在安第斯山脉、阿尔卑斯山脉和喜马拉雅山脉,这种交换不但向各个海拔高度的人提供一种比较均衡的饮食,从而增加了这些地区的人口密度,而且也促进了地区的经济和政治一体化。
  • 任何在澳大利亚本地出现的粮食生产,都可能会由于可驯化的动植物的缺乏以及土壤贫瘠和气候恶劣而受到限制。流浪的生活、狩猎采集的生活方式以及对住所和财物的最小的投资,是因受澳大利亚厄尔尼诺南移影响而无法预知可以得到何种资源时的明智的适应行为。在当地条件恶化时,土著居民只是迁往一个暂时条件较好的地区。

总结

  • 新几内亚地区环境比澳大利亚大陆好,相对比较”先进”,但还是比不上亚欧大陆。
  • 总结一下,新几内亚社会发展落后的几大原因:一是可供驯化的动植物品种少且蛋白质低;二是可利用的土地资源少;三是(基于以上原因)人口少密度低,且分散化严重,彼此争斗不休;四是外部地理的隔绝,难以传入借鉴先进的技术和思想。

第十六章 中国是怎样成为中国人的中国的

  • 近代民族大熔炉这一普遍现象的重大例外是世界上人口最多的国家——中国。
  • 中国过去也曾经是形形色色、变化多端的,就像其他所有人口众多的国家现在仍然表现出来的那样。中国的不同之处仅仅在于它在早得多的时候便已统一了。
  • 虽然中国的南北梯度妨碍了作物的传播,但这种梯度在中国不像在美洲或非洲那样成为一种障碍,因为中国的南北距离较短;同时也因为中国的南北之间既不像非洲和墨西哥北部那样被沙漠阻断,也不像中美洲那样被狭窄的地峡隔开。倒是中国由西向东的大河(北方的黄河、南方的长江)方便了沿海地区与内陆之间作物和技术的传播,而中国东西部之间的广阔地带和相对平缓的地形最终使这两条大河的水系得以用运河连接起来,从而促进了南北之间的交流。所有这些地理因素促成了中国早期的文化和政治统一,而西方的欧洲虽然面积和中国差不多,但地势比较高低不平,也没有这样连成一体的江河,所以欧洲直到今天都未能实现文化和政治的统一。
  • 在中国,有些新事物是由南向北传播的,尤其是铁的冶炼和水稻的栽培。但主要的传播方向是由北向南。这个趋向在文字上表现得最为明显:欧亚大陆西部曾产生过太多的书写系统,如苏美尔的楔形文字、埃及的象形文字、赫梯文字[插图]、弥诺斯文字和闪语字母。中国则不同,它只产生了一种得到充分证明的书写系统。它在华北得到完善,并流传各地,预先制止了任何其他不成熟的书写系统的发展或取而代之,最后演化为今天仍在中国使用的文字。
  • 中国的3个最早的王朝——夏、商、周都是在公元前第二个一千年间在华北兴起的。现存的公元前第一个千年的著作表明,当时的华夏族就已常常(就像今天许多人仍然在做的那样)觉得在文化上比非华夏族的“野蛮人”优越,而华北人也常常甚至把华南人也看作野蛮人。
  • 由于东亚最早的农民所取得的成就,中国成了中国人的中国。

总结

  1. 南北距离较短,且无地理隔离
  2. 很早就文化统一
  3. 有粮食生产

第十七章 驶向波利尼西亚的快艇

  • 南岛语系的4个语族中有3个集中在台湾,这表明台湾就是今天各地南岛语的故乡,在过去几千年的大部分时间里,这些语言一直在台湾使用,因此有最长的时间来产生分化。这样看来,从马达加斯加到复活节岛,所有其他南岛语可能都起源于台湾向外的人口扩张。
  • 南岛人在新几内亚地区扩张的结果与在印度尼西亚和菲律宾扩张的结果全然不同。在印度尼西亚和菲律宾,当地的人口消失了——大概是被这些入侵者赶走、杀死、用传染病害死或甚至同化了。而在新几内亚,当地的人口多半把这些入侵者挡在外面。
  • 中国的华南人发展了本地的粮食生产和技术,接受了华北的文字、更多的技术和政治组织,又进而向热带东南亚和台湾移民,大规模地取代了这些地区的原有居民。
  • 与澳大利亚和美洲不同,东亚和大多数太平洋岛屿仍然为东亚民族和太平洋民族所占有。

总结

  • 学会粮食生产的重要性。可以很大程度确保最终不会被替代。

第十八章 两个半球的碰撞

  • 过去13000年中最大的人口更替是新、旧大陆社会之间新近的碰撞引起的。我们在第三章看到,这种碰撞的最富戏剧性也最具决定性的时刻,是皮萨罗的小小西班牙军队俘虏了印加帝国皇帝阿塔瓦尔帕。阿塔瓦尔帕是最大、最富有、人口最多、管理和技术最先进的印第安国家的独裁统治者,他的被俘成了欧洲人征服美洲的象征,因为造成这一事件的相同的各种近似因素,也是欧洲人征服其他印第安社会的部分原因。
  • 为什么是欧洲人到达了印第安人的国家并征服了它,而不是相反?我们讨论的起始点就是把欧亚大陆社会和印第安社会作一比较,时间是到公元1492年即哥伦布“发现”美洲的那一年为止。
  • 粮食生产方面的这些差异,构成了欧亚大陆社会与印第安社会之间差异的一个重要的终极原因。在由此而产生的实现征服的近似因素中,最重要的因素包括病菌、技术、政治组织和文字方面的差异。
  • 其中与粮食生产方面的差异关系最直接的差异是病菌。有些传染病经常光顾人口拥挤的欧亚大陆社会,许多欧亚大陆人因而逐步形成了免疫力或遗传抵抗力。这些传染病包括历史上所有最致命的疾病:天花、麻疹、流行性感冒、瘟疫、肺结核、斑疹伤寒、霍乱、疟疾和其他疾病。对照这个令人望而生畏的疾病名单,唯一可以有把握归之于哥伦布以前印第安人社会的群众传染病是非梅毒密螺旋体病。
  • 大陆之间在有害的病菌方面的这种差异竟是来自有用的牲畜方面的差异。在拥挤的人类社会引起传染病的大多数病菌,是从引起家畜传染病的那些十分相似的祖代病菌演化而来的,而在大约10000年前,粮食生产者就已开始每天同这些家畜进行密切的接触了。
  • 在帮助欧洲征服美洲的一些直接因素中,可与病菌相提并论的是技术的各方面的差距。这些差距归根到底是由于欧亚大陆有历史悠久得多的依靠粮食生产的人口稠密、经济专业化、政治集中统一、相互作用、相互竞争的社会。
  • 为什么所有主要发展结果的发展轨迹在年代上美洲要晚于欧亚大陆?这有4组原因:起步晚,可用于驯化的野生动植物系列比较有限,较大的传播障碍,以及稠密的人口在美洲生活的地区可能比在欧亚大陆小,或者可能比在欧亚大陆孤立。
  • 与欧亚大陆始终如一的东西宽度不同,新大陆在中美洲的那一段特别是在巴拿马变窄了。尤其是,美洲被一些不适于粮食生产也不适于稠密人口的地区分割开来。
    这些生态障碍包括:把中美洲社会同安第斯山脉地区和亚马孙河地区社会分隔开来的巴拿马地峡雨林;把中美洲社会同美国西南部和东南部社会分隔开来的墨西哥北部沙漠;把美国西南部同东南部分隔开来的得克萨斯州干旱地区;把本来可能适于粮食生产的美国太平洋沿岸地区隔开的沙漠和高山。因此,在中美洲、美国东部、安第斯山脉地区和亚马孙河地区这些新大陆的中心之间,完全没有家畜、文字和政治实体方面的交流,以及只有在作物和技术方面的有限的缓慢的交流。
  • 起源于东地中海的字母从英格兰到印度尼西亚,传遍了欧亚大陆的各个复杂社会,只有东亚地区是例外,因为中国书写系统派生出来的文字已在那里占主导地位。
  • 我们已经找到了3组有利于欧洲人入侵美洲的终极因素:欧亚大陆人类定居时间长的领先优势;由于欧亚大陆可驯化的野生植物尤其是动物的资源比较丰富而引起的比较有效的粮食生产;欧亚大陆范围内对传播交流的地理和生态障碍并非那样难以克服。
  • 在不适宜大部分粮食生产的纬度过高地区,在欧洲穷国之一的无力支持下,几个古挪威人手中的铁器没有斗得过爱斯基摩人和印第安狩猎采集族群手中的石器、骨器和木器,要知道这后两种人是世界上掌握在北极地区生存技巧的最杰出的大师!在适合欧洲的粮食生产和欧洲人生理机能的气候最温和的地区,人口众多的印第安社会被消灭了。

总结

  • 粮食生产差异->病菌差异
  • 在帮助欧洲征服美洲的一些直接因素中,可与病菌相提并论的是技术的各方面的差距。这些差距归根到底是由于欧亚大陆有历史悠久得多的依靠粮食生产的人口稠密、经济专业化、政治集中统一、相互作用、相互竞争的社会。
  • 3组有利于欧洲人入侵美洲的终极因素:欧亚大陆人类定居时间长的领先优势;由于欧亚大陆可驯化的野生植物尤其是动物的资源比较丰富而引起的比较有效的粮食生产;欧亚大陆范围内对传播交流的地理和生态障碍并非那样难以克服。

第十九章 非洲是怎样成为黑人的非洲的

  • 在白人殖民主义者来到之前,已经生活在非洲的不仅有黑人,还有(我们将要看到)世界上6大人种中的5种,其中3种只生活在非洲。世界上的语言,有四分之一仅仅在非洲才有人说。没有哪一个大陆在人种的多样性方面可以与非洲相提并论。

  • 非洲多样化的人种来自它的多样化的地理条件和悠久的史前史。非洲是唯一的地跨南北温带的大陆,同时它也有几处世界上最大的沙漠、最大的热带雨林和最高的赤道山脉。人类在非洲生活的时间比在任何其他地方都要长得多。

  • 到公元1000年,这5个主要的人类群体已经把非洲当作自己的家园。外行人不严密地把他们称为黑人、白人、非洲俾格米人、科伊桑人和亚洲人。

  • 地球上所有人类群体只要和其他每一个群体中的人接触,就会发生婚配关系。

  • 俾格米人的家园被淹没在入侵的黑人农民的汪洋大海之中,硕果仅存的一些俾格米人采用了这些农民的语言,而他们原来的语言只在某些词和发音上留下了一些蛛丝马迹。

  • 科伊桑人和俾格米人之所以未能发展出农业,不是由于他们没有农民的资格,而仅仅是由于碰巧非洲南部的野生植物大都不适于驯化。无论是班图农民还是白人农民,尽管他们继承了几千年的农业经验,后来还是没有能把非洲南部的本地植物培育成粮食作物。

  • 至于非洲的驯化动物,概括地介绍起来可以比介绍植物快得多,因为那里的驯化动物实在太少。

  • 现代南非的问题至少一部分源自地理上的偶然因素。好望角科伊桑人的家园碰巧很少有适于驯化的野生植物;班图人碰巧从他们5000年前的祖先那里继承了适应夏雨的作物;而欧洲人碰巧从他们近10000年前的祖先那里继承了适应冬雨的作物。

  • 正如他们与印第安人遭遇时的情况一样,进入非洲的欧洲人拥有三重优势:枪炮和其他技术、普及的文化以及为维持探险和征服的昂贵计划所必不可少的政治组织。从历史上看,所有这三者都来自粮食生产的发展。但粮食生产在非洲撒哈拉沙漠以南地区被延误了(与欧亚大陆相比),其原因是非洲缺少可以驯化的本地动植物物种,它的适于本地粮食生产的小得多的面积,以及它的妨碍粮食生产和发明的传播的南北轴向。第二个因素是非洲撒哈拉沙漠以南地区和欧亚大陆之间在可驯化的植物方面的一种虽然不是那样极端但也相当大的差异。第三个因素是非洲的面积仅及欧亚大陆的面积的一半左右。

  • 总之,欧洲在非洲的殖民并不像某些白人种族主义者所认为的那样与欧洲民族和非洲民族本身之间的差异有关。恰恰相反,这是由于地理学和生物地理学的偶然因素所致——特别是由于这两个大陆之间不同的面积、不同的轴线方向和不同的动植物品种所致。就是说,非洲和欧洲的不同历史发展轨迹归根到底来自它们之间的“不动产”的差异。

  • 非洲历史悠久,是人类生活时间最长的大陆,拥有世界上最大的沙漠、最大的热带雨林和最高的赤道山脉,人种多样化,世界上6大人种有5种生活在非洲(其中3种只生活在非洲),语言、文化也是世界最复杂的。但非洲过去没有文字,使得我们很难正确还原非洲历史。

  • 非洲的5个主要人类群体:黑人、白人、俾格米人、科伊桑人、亚洲人,在1000年已经定居在非洲。其中俾格米人与黑人一样有着深色皮肤和浓密头发,但身材矮小得多、批复为红色较多黑色较少、体毛较多、前额眼镜和牙齿较突出,分布在中非的雨林中,大多过着狩猎采集生活;科伊桑人以前分布在非洲南部,他们皮肤微黄,头发浓密且卷曲,由于被欧洲殖民者入侵,科伊桑人数量大大减少,幸存者与欧洲人生下混血种;非洲白人主要分布在北部,因为与近东和欧洲邻近地区往来较多。

  • 非洲落后于欧亚大陆的原因与美洲类似:缺乏可驯化的动物,驯化的植物无法大规模种植,撒哈拉沙漠阻止了南北的传播,大陆面积较小。归根到底也是地理学和生物地理学的偶然因素所致。

  • 非洲是怎样成为黑人的非洲的:天意。黑人的这个部落在恰当的时间和恰当的地点得到了恰当的作物,促进了人口和其他一切的增长,开始了“淹没”。

总结

  • 非洲有粮食生产(黑人),但有地理原因,没能发展壮大。所以也没被完全替换。

尾声 人类史作为一门科学的未来

  • 耶利的问题触及了人类现状的实质,也是更新世后人类历史的关键所在。既然我们已经完成了这次对各大陆的短暂的巡视,我们将怎样来回答耶利呢?我会对耶利这样说:各大陆民族长期历史之间的显著差异,不是源自这些民族本身的天生差异,而是源自他们环境的差异。

  • 耶利的问题触及了人类现状的实质,也是更新世后人类历史的关键所在。既然我们已经完成了这次对各大陆的短暂的巡视,我们将怎样来回答耶利呢?我会对耶利这样说:各大陆民族长期历史之间的显著差异,不是源自这些民族本身的天生差异,而是源自他们环境的差异。我猜想,如果在更新世晚期能够使澳大利亚土著人口和欧亚大陆土著人口互换位置,那么,原来的澳大利亚土著现在可能不但占领了欧亚大陆,而且也占领了美洲和澳大利亚的大部分地区,而原来的欧亚大陆土著现在可能已沦为澳大利亚的一些遭受蹂躏的零星分散的人口。对于这种说法,你一开始可能会认为毫无意义而不屑一顾,因为这个实验是想象出来的,而我所说的那种结果也是不可能被证明的。但历史学家却能用回溯试验法对有关的假说进行评价。

  • 第一组差异是各大陆在可以用作驯化的起始物种的野生动植物品种方面的差异。这是因为,粮食生产之所以具有决定性的意义,在于它能积累剩余粮食以养活不从事粮食生产的专门人材,同时也在于它能形成众多的人口,从而甚至在发展出任何技术和政治优势之前,仅仅凭借人多就可以拥有军事上的优势。由于这两个原因,从小小的不成熟的酋长管辖地阶段向经济上复杂的、社会上分层次的、政治上集中的社会发展的各个阶段,都是以粮食生产为基础的。但大多数野生的动植物品种证明是不适于驯化的:粮食生产的基础一直是比较少的几种牲畜和作物。原来,各大陆在可以用于驯化的野生动植物的数量方面差异很大,因为各大陆的面积不同,而且在更新世晚期大型哺乳动物灭绝的情况也不同。大型哺乳动物灭绝的情况,在澳大利亚和美洲要比在欧亚大陆或非洲严重得多。因此,就生物物种来说,欧亚大陆最为得天独厚,非洲次之,美洲又次之,而澳大利亚最下,就像耶利的新几内亚那种情况(新几内亚的面积为欧亚大陆的七十分之一,而且其原来的大型哺乳动物在更新世晚期即已灭绝)。

  • 在每一个大陆,动植物的驯化集中在只占该大陆总面积很小一部分的几个条件特别有利的中心地。就技术创新和政治体制来说,大多数社会从其他社会获得的要比它们自己发明的多得多。因此,一个大陆内部的传播与迁移,对它的社会的发展起着重要的促进作用,而从长远来看,由于毛利人的新西兰火枪战争以如此简单的形式所揭示的过程,这些社会又(在环境许可的情况下)分享彼此的发展成果。就是说,起初缺乏某种有利条件的社会或者从拥有这种条件的社会那里得到,或者(如果做不到这一点)被其他这些社会所取代。

  • 因此,第二组因素就是那些影响传播和迁移速度的因素,而这种速度在大陆与大陆之间差异很大。在欧亚大陆速度最快,这是由于它的东西向主轴线和它的相对而言不太大的生态与地理障碍。对于作物和牲畜的传播来说,这个道理是最简单不过的,因为这种传播大大依赖于气候因而也就是大大依赖于纬度。同样的道理也适用于技术的发明,如果不用对特定环境加以改变就能使这些发明得到最充分的利用的话。传播的速度在非洲就比较缓慢了,而在美洲就尤其缓慢,这是由于这两个大陆的南北向主轴线和地理与生态障碍。在传统的新几内亚,这种传播也很困难,因为那里崎岖的地形和高山漫长的主脉妨碍了政治和语言统一的任何重大进展。

  • 与影响大陆内部传播的这些因素有关的,是第三组影响大陆之间传播的因素,这些因素也可能有助于积累一批本地的驯化动植物和技术。大陆与大陆之间传播的难易程度是不同的,因为某些大陆比另一些大陆更为孤立。在过去的6000年中,传播最容易的是从欧亚大陆到非洲撒哈拉沙漠以南地区,非洲大部分牲畜就是通过这种传播得到的。但东西两半球之间的传播,则没有对美洲的复杂社会作出过任何贡献,这些社会在低纬度与欧亚大陆隔着宽阔的海洋,而在高纬度又在地形和适合狩猎采集生活的气候方面与欧亚大陆相去甚远。对于原始的澳大利亚来说,由于印度尼西亚群岛的一道道水上障碍把它同欧亚大陆隔开,欧亚大陆对它的唯一的得到证明的贡献就是澳洲野狗。

  • 第四组也是最后一组因素是各大陆之间在面积和人口总数方面的差异。更大的面积或更多的人口意味着更多的潜在发明者,更多的互相竞争的社会,更多的可以采用的发明创造——以及更大的采用和保有发明创造的压力,因为任何社会如果不这样做就往往会被竞争对手所淘汰。

  • 非洲的俾格米人和其他许多被农民取代的狩猎采集群体,就曾碰到这样的命运。相反的例子是格陵兰岛上顽固保守的古挪威农民,他们也碰到了被爱斯基摩狩猎采集族群所取代的命运,因为在格陵兰的条件下,这些爱斯基摩人的生存方法和生存技术都比这些古挪威人优越得多。在全世界的陆块中,欧亚大陆的面积最大,相互竞争的社会的数量也最多,澳大利亚和新几内亚在这方面就差得多,而塔斯马尼亚更是瞠乎其后。美洲的总面积虽然很大,但却在地理上和生态上支离破碎,实际上就像几个没有紧密联系的较小的大陆。这4组因素构成了环境的巨大差异,这些差异可以客观地用数量来表示,而且不会引起争议。

  • 所有的人类社会都拥有有发明才能的人。事情恰恰是有些环境比另一些环境提供了更多的起始物种和利用发明的更有利的条件。

  • 在欧亚大陆范围内,为什么是欧洲社会,即在美洲和澳大利亚殖民的那些社会,而不是新月沃地的社会或中国和印度的社会,在技术上领先,并在现代世界上占据政治和经济的支配地位?如果一个历史学家生活在从公元前8500年到公元1450年的任何一段时间内,如果他当时试图预测未来的历史发展轨迹,他肯定会认为,欧洲最终的支配地位是最不可能发生的结果,因为欧洲在过去那1万年的大部分时间里是旧大陆的那3个地区中最落后的一个地区。

  • 从公元前8500年开始,直到公元500年后希腊与意大利的先后兴起这一段时间里,欧亚大陆西部几乎所有的重大发明——动物驯化、植物驯化、文学、冶金术、轮子、国家等等——都是在新月沃地或其附近出现的。在水磨于大约公元900年后大量传播之前,阿尔卑斯山以西或以北的欧洲没有对旧大陆的技术或文明作出过任何有意义的贡献,它只是一个从地中海以东、新月沃地和中国接受发展成果的地方。甚至从公元1000年到1450年,科学和技术绝大多数都是从印度与北非之间的伊斯兰社会传入欧洲,而不是相反。就在那几个世纪中,中国在技术上走在世界的前列,几乎和新月沃地一样早地开始了粮食生产。那么,为什么新月沃地和中国把它们几千年的巨大的领先优势最后让给了起步晚的欧洲?当然,人们可以指出促使欧洲兴起的一些直接因素:它的商人阶级、资本主义和对发明的专利保护的逐步形成,它未能产生的专制独裁君主和使人不堪重负的税收,以及它的希腊——犹太教——基督教的批判经验主义调查研究的传统。不过,对于所有这些直接原因,人们一定会提出关于终极原因的问题:为什么这些直接因素出现在欧洲,而不是出现在中国或新月沃地?

  • 新月沃地和东地中海社会不幸在一个生态脆弱的环境中兴起。它们破坏了自己的资源基础,无异于生态自杀。从东方(新月沃地)最古老的社会开始,每一个东地中海社会都在轮流地自挖墙脚,而就在这个过程中,权力西移了。欧洲北部和西部没有遭到同样的命运,这不是因为那里的居民比较明智,而是因为他们运气好,碰巧生活在一个雨量充沛、植被再生迅速的好环境里。在粮食生产传入7000年之后,欧洲北部和西部的广大地区今天仍能维持高产的集约农业。事实上,欧洲是从新月沃地得到它的作物、牲畜、技术和书写系统的,而新月沃地后来反而使自己失去了作为一个主要的权力和发明中心的地位。这就是新月沃地失去它对欧洲的巨大的早期领先优势的情形。

  • 为什么中国也失去了这种领先优势呢?中国的落后起初是令人惊讶的,因为中国拥有无可置疑的有利条件:粮食生产的出现似乎同在新月沃地一样早;从华北到华南,从沿海地区到西藏高原的高山地区的生态多样性,产生了一批不同的作物、动物和技术;幅员广阔,物产丰富,养活了这一地区世界上最多的人口;一个不像新月沃地那样干旱或生态脆弱的环境,使中国在将近10000年之后仍能维持高产的集约农业,虽然它的环境问题日益增多,而且比欧洲西部严重。

  • 这些有利条件和领先优势使得中世纪的中国在技术上领先世界。中国一长串重大的技术第一包括铸铁、罗盘、火药、纸、印刷术以及前面提到过的其他许多发明。它在政治权力、航海和海上管制方面也曾在世界上领先。15世纪初,它派遣宝船队[插图]横渡印度洋,远达非洲东海岸,每支船队由几百艘长达400英尺的船只和总共28000名船员组成。这些航行在时间上也比哥伦布率领3艘不起眼的小船渡过狭窄的大西洋到达美洲东海岸要早好几十年。法斯科·达·伽马率领他的3艘不起眼的小船,绕过非洲的好望角向东航行,使欧洲开始了对东亚的殖民。为什么中国的船只没有在伽马之前绕过好望角向西航行并在欧洲殖民?为什么中国的船只没有横渡太平洋到美洲西海岸来殖民?简而言之,为什么中国把自己在技术上的领先优势让给原先十分落后的欧洲呢?

  • 中国西洋舰队的结局给了我们一条线索。从公元1405年到1433年,这些船队一共有7次从中国扬帆远航。后来,由于世界上任何地方都可能发生的局部政治变化,船队出海远航被中止了:中国朝廷上的两派(太监和反对他们的人)之间发生了权力斗争。前一派支持派遣和指挥船队远航。因此,当后一派在权力斗争中取得上风时,它停止派遣船队,最后还拆掉船坞并禁止远洋航运。这一事件使我们想起了19世纪80年代伦敦的扼杀公共电灯照明的立法、第一次和第二次世界大战之间美国的孤立主义和许多国家全都由于局部的政治争端而引发的许多倒退措施。但在中国,情况有所不同,因为那整个地区在政治上是统一的。一个决定就使整个中国停止了船队的航行。那个一时的决定竟是不可逆转的,因为已不再有任何船坞来造船以证明那个一时决定的愚蠢,以及用作重建新船坞的中心。

  • 现在来对比一下中国的这些事件,和探险船队开始从政治上分裂的欧洲远航时所发生的事情。克里斯托弗·哥伦布出生在意大利,后来转而为法国的昂儒公爵服务,又后来改事葡萄牙国王。哥伦布曾请求国王派船让他向西航行探险。他的请求被国王拒绝了,于是他就求助于梅迪纳——塞多尼亚公爵,也遭到了拒绝,接着他又求助于梅迪纳——塞利伯爵,依然遭到拒绝,最后他又求助于西班牙的国王和王后,他们拒绝了他的第一次请求,但后来在他再次提出请求时总算同意了。如果欧洲在这头3个统治者中任何一个的统治下统一起来,它对美洲的殖民也许一开始就失败了。事实上,正是由于欧洲是分裂的,哥伦布才成功地于第五次在几百个王公贵族中说服一个来赞助他的航海事业。一旦西班牙这样开始了欧洲对美洲的殖民,其他的欧洲国家看到财富滚滚流入西班牙,立刻又有6个欧洲国家加入了对美洲殖民的行列。对于欧洲的大炮、电灯照明、印刷术、小型火器和无数的其他发明,情况也是如此:每一项发明在欧洲的一些地方由于人们的习性起先或者被人忽视,或者遭人反对,但一旦某个地区采用了它,它最后总能传播到欧洲的其余地区。

  • 欧洲分裂所产生的这些结果与中国统一所产生的结果形成了鲜明的对比。除了作出停止海外航行的决定外,中国的朝廷还作出停止其他一些活动的决定:放弃开发一种精巧的水力驱动的纺纱机,在14世纪从一场产业革命的边缘退了回来,在制造机械钟方面领先世界后又把它拆毁或几乎完全破坏了,以及在15世纪晚期以后不再发展机械装置和一般技术。统一的这些潜在的有害影响在现代中国又死灰复燃,特别是20世纪60年代和70年代“文化大革命”中的那种狂热,当时一个或几个领导人的决定就把全国的学校系统关闭了5年之久。

  • 中国的经常统一与欧洲的永久分裂都由来已久。

  • 现代中国的最肥沃地区于公元前221年第一次在政治上统一起来,并从那时以来的大部分时间里一直维持着这个局面。中国自有文字以来就一直只有一种书写系统,长期以来只有一种占支配地位的语言,以及2000年来牢固的文化统一。相比之下,欧洲与统一始终相隔十万八千里:14世纪时它仍然分裂成1000个独立的小国,公元1500年有小国500个,20世纪80年代减少到最低限度的25国,而现在就在我写这句话的时候又上升到将近40个国家。欧洲仍然有45种语言,每种语言都有自己的经过修改的字母表,而文化的差异甚至更大。欧洲内部的分歧今天在继续挫败甚至是想要通过欧洲经济共同体(EEC)来实现欧洲统一的并不过分的企图,这就表明欧洲对分裂的根深蒂固的执著。

  • 了解中国把政治和技术的卓越地位让给欧洲的关键所在就是去了解中国的长期统一和欧洲的长期分裂的问题。答案又一次用地图表示出来(见下图)。欧洲海岸线犬牙交错,它有5大半岛,每个半岛都近似孤悬海中的海岛,在所有这些半岛上形成了独立的语言、种族和政府:希腊、意大利、伊比利亚半岛、丹麦和挪威/瑞典。中国的海岸线则平直得多,只有附近的朝鲜半岛才获得了作为单独岛屿的重要性。欧洲有两个岛(大不列颠岛和爱尔兰岛),它们的面积都相当大,足以维护自己的政治独立和保持自己的语言和种族特点,其中的一个岛(大不列颠岛)因为面积大,离欧洲大陆又近,所以成了一个重要的欧洲独立强国。

  • 但即使是中国的两个最大的岛——台湾岛和海南岛,面积都不到爱尔兰岛的一半,这两个岛都不是重要独立的政体;而日本在地理上的孤立地位使它在现代以前一直处于与亚洲大陆的政治隔绝状态,其程度远远超过了大不列颠与欧洲大陆的政治隔绝状态。欧洲被一些高山(阿尔卑斯山脉、比利牛斯山脉、喀尔巴阡山脉和挪威边界山脉)分隔成一些独立的语言、种族和政治单位,而中国在西藏高原以东的山脉则不是那样难以克服的障碍。中国的中心地带从东到西被肥沃的冲积河谷中两条可通航的水系(长江和黄河)连接了起来,从南到北又由于这两大水系(最后有运河连接)之间比较方便的车船联运而成为一体。因此,中国很早就受到了地域广阔的两个高生产力核心地区的决定性影响,而这两个地区本来彼此只有微不足道的阻隔,最终又合并为一个中心。欧洲的两条最大的河流——莱茵河与多瑙河则比较小,在欧洲流经的地方也少得多。与中国不同,欧洲有许多分散的小的核心地区,没有一个大到足以对其他核心地区产生长期的决定性影响,而每一个地区又都是历史上一些独立国家的中心。中国一旦于公元前221年最后获得统一,就再没有任何其他的独立国家有可能在中国出现并长期存在下去。虽然在公元前221年后有几个时期出现了分裂局面,但最后总是重新归于统一。但欧洲的统一就连查理曼[插图]、拿破仑和希特勒这些下定决心的征服者都无能为力;甚至罗马帝国在其鼎盛时期所控制的地区也没有超过欧洲的一半。因此,地理上的四通八达和非常一般的内部障碍,使中国获得了一种初始的有利条件。华北、华南、沿海地区和内陆的不同作物、牲畜、技术和文化特点,为中国的最后统一作出了贡献。例如,黍的栽培、青铜技术和文字出现在华北,而水稻的栽培和铸铁技术则出现在华南。我用本书的很大篇幅着重讨论了在没有难以克服的障碍的情况下技术的传播问题。但中国在地理上的四通八达最后却成了一个不利条件,某个专制君主的一个决定就能使改革创新半途而废,而且不止一次地这样做了。相比之下,欧洲在地理上的分割形成了几十个或几百个独立的、相互竞争的小国和发明创造的中心。如果某个国家没有去追求某种改革创新,另一个国家会去那样做的,从而迫使邻国也这样去做,否则就会被征服或在经济上处于落后地位。欧洲的地理障碍足以妨碍政治上的统一,但还不足以使技术和思想的传播停止下来。欧洲还从来没有哪一个专制君王能够像在中国那样切断整个欧洲的创造源泉。

  • 这些比较表明,地理上的四通八达对技术的发展既有积极的影响,也有消极的影响。因此,从长远来看,在地理便利程度不太高也不太低而是中等适度的地区,技术可能发展得最快。中国、欧洲,可能还有印度次大陆的过去1000多年的技术发展过程便是例子,它分别表明了高、中、低3种不同程度的地理便利条件所产生的实际效果。

  • 新月沃地的居间的地理位置,控制了把中国和印度与欧洲连接起来的贸易路线,以及中国距离欧亚大陆其他先进的文明国家路途遥远,使中国实际上成为一个大陆内的一个巨大孤岛。中国的相对孤立状态与它先是采用技术后来又排斥技术这种做法有着特别重要的关系,这使人想起了塔斯马尼亚岛和其他岛屿排斥技术的情形(第十三章和第十五章)。不过,这一简略的讨论至少可以表明,环境因素不但与历史的最广泛模式有关,而且也与较小规模和较短时期的历史模式有关。

  • 新月沃地和中国的历史还为现代世界留下了一个有益的教训:环境改变了,过去是第一并不能保证将来也是第一

  • 物理学家和化学家能够在宏观的层次上系统地阐述带有普遍性的决定论的规律,但生物学家和历史学家只能系统地阐述统计学上的趋势。

  • 历史系统尽管有其终极的确定性,但其复杂性和不可预测性是不待言的。描述这种复杂性和不可预测性的另一个办法就是指出,长长的一连串因果关系可能把最后结果同存在于那一科学领域之外的终极原因分开。例如,一颗小行星对地球的撞击可能导致了恐龙的灭绝,但那颗小行星的轨道却是完全由古典力学的定律决定的。但如果有古生物学家生活在6700万年前,他们也不可能预测到恐龙的灭亡迫在眉睫,因为小行星属于一个在其他方面都与恐龙生物学关系疏远的科学领域研究的对象。同样,公元1300年至1500年之间的小冰期也是格陵兰岛上古挪威人灭绝的部分原因,但没有哪个历史学家,也许甚至也没有哪一个现代气候学家能够预测到小冰期的到来。

  • 因此,历史学家在确定人类社会史的因果关系时所碰到的困难,大致上类似于天文学家、气候学家、生态学家、演化生物学家、地质学家和古生物学家所碰到的困难。

日本人乃何许人也

  • 至于阿伊努人,他们独特的相貌也招致了关于其由来和亲缘的众多研究,数量之多超过了地球上任何其他的民族。阿伊努男子胡须浓密,体毛之丰居各人种之首。这个体征,加上其它一些遗传性状如指纹和耳垢类型,使得他们常常被归入不知何故从欧亚东迁最终落脚日本的高加索人种(即所谓白人)。但是,纵观其基因特征,阿伊努人和包括日本人、朝鲜人和冲绳人这些东亚人种还是有瓜葛。
  • 阿伊努人是日本以采集涉猎为生的原住民后代,而日本人则是晚近从亚洲大陆而来的入侵者。
  • 边境之外的日本最北端岛屿北海道和居住在那里的阿伊努涉猎采集者甚至都不被视为日本国的一部分,直到19世纪这里才加入日本。
  • 来自朝鲜的移民确实对现代日本民族作出了巨大贡献,虽然我们尚不能确信起因是因为移民本就人数众多,还是因为数量不多的移民凭借高速人口增长而扩张的结果。阿伊努人的基因更接近日本古代绳纹居民,又掺杂了弥生殖民者和现代日本人的朝鲜基因。
  • 早期的朝鲜编年史告诉我们,不同的王国拥有不同的语言。虽然被新罗打败的两个王国的语言已鲜为人知,但作为战败国之一的高句丽(Koguryo),其留存下的少数几个词汇与古日语词汇的近似程度远胜于现代朝鲜语词汇。在政治统一进程到达三足鼎立的阶段之前,公元前400年的朝鲜语,也许多样性更甚。我怀疑公元前400年传入日本并发展成为现代日语的朝鲜语,与发展成为现代朝鲜语的新罗语大不相同。因此,现代日本人和朝鲜人之间的外形和基因的相似性远超两种语言的近似性,我们对此不应该感到奇怪。
  • 考虑到日本民族和朝鲜民族目前的相互仇视,这个结论很可能在日本和朝鲜都同样不受欢迎。历史给了他们对彼此产生厌恶的充分原因,而其中又尤以朝鲜人对日本人的厌恶为甚。正如阿拉伯人和犹太人,朝鲜人和日本人是血脉相连的民族,但又深陷积怨的迷障。而积怨具有双向的毁灭性,这一点在东亚和中东都是如此。即便日本人和朝鲜人都不愿意承认,事实上,他们就像一对共享了成长岁月的孪生兄弟。在很大程度上,东亚的政治前景取决于他们是否能够成功地重新找回联系彼此的古老纽带。

总结

  1. 日本来自朝鲜的移民
  2. 和日本当地其他族融合并替换

附录 2003后记:《枪炮、病菌与钢铁》今日谈

  • 公元前400年左右,朝鲜农民扩张到了日本西南,继而朝着日本列岛的东北挺进。迁移的农民带来了精细的水稻农业和铁制工具,并与日本原住民(现代阿伊努人的祖先)融合产生了现代日本人,就像扩张的新月沃地农民与欧洲的土著狩猎采集人群融合产生了现代欧洲人。
  • 我认为欧洲超越中国的背后原因,比多数历史学家所提出的直接因素要来得深远(例如中国的儒家理论vs.欧洲的犹太基督教传统,西方科学的崛起,欧洲重商主义和资本主义的崛起,英国的毁林兴矿等等)。在上述以及其他直接因素背后,我看到了一个“最优分裂原则”:伴随着欧洲始终的分裂,导致中国较早统一并保持相对统一的终极地理因素。促成技术、科学的进步,带来推动各国竞争,以可替代的资源支持并提供给发明者,并为他们提供可躲避迫害的庇护所,并由此孕育资本主义的,不是中国的统一,而是欧洲的分裂。历史学家们后来也向我指出,欧洲的分裂,中国的统一,还有欧洲和中国的相对优势之复杂程度,都超出我书中的讲述。可以被分成“欧洲”和“中国”的政治/社会半径的地理边界在过去几个世纪内一直处于变动之中。直到至少15世纪以前,中国在技术上一直走在欧洲前面,在未来也有可能重续辉煌,那样的话,“为什么是欧洲,而非中国?”的问题可能就只是一种转瞬即逝的现象,没有深层原因可挖。政治分裂的复杂影响远不止提供一个用于竞争的建设性平台,例如,竞争有可能是建设性的,也有可能是破坏性的(想想一战和二战)。分裂本身是个多层面而非单一的概念,其对于创新的影响力依赖于自由等要素,如此,创意和人员才能在各个碎片之间跨界流动,不管这些碎片是独一无二的,还是彼此的克隆。至于分裂是否最优也随使用的最优衡量尺度而异,对于技术创新最优的政治分裂程度,也许就经济生产力、政治稳定或人类福祉而言并非最优。
  • 印度在地理上较欧洲更为分裂,但是其技术创新却不及欧洲。这让我想到了“最优分裂原则”:创新在带有最优中间程度分裂的社会里发展得最快:太过统一的社会处于劣势,太过分裂的社会也不占优。

不完全总结

美洲落后的原因?

  1. 整个野生动植物组合微弱的驯化潜力;
  2. 南北轴线,且南北接触地狭小,地理和生态上却支离破碎 (影响传播);
  3. 人少和可驯化动植物少导致不能产生很多流行病

澳大利亚落后的原因?

  1. 澳大利亚是最干燥、最小、最平坦、最贫瘠、气候最变化无常、生物品种最稀少的大陆;澳大利亚是最小的一个大陆。澳大利亚大部分地区雨量稀少,物产贫乏,因此,就其所能养活的人口来说,它实际上就显然甚至更小。它也是一个最孤立的大陆。

非洲落后的原因?

1.表面上是一个大陆,实际上由于气候和地形原因,隔离成很多个地区(影响传播);
撒哈拉沙漠仍然是一个主要的生态障碍,把非洲撒哈拉沙漠以南地区同欧亚大陆和北非隔开。非洲的南北轴线造成了欧亚大陆与非洲撒哈拉沙漠以南地区之间以及撒哈拉沙漠以南地区本身内部技术传播的又一障碍。

欧亚大陆

  1. 欧亚大陆何以一直是大型哺乳动物驯化的主要场所,对这个问题的部分解释是:它是一个一开始就拥有最多的可供驯化的野生哺乳动物的大陆,在过去的40000年中,那里这样的动物因绝种而消失的也最少。
  2. 东西轴线,同纬度易于传播

各大陆的差异

  • 各大陆之间在面积、人口、技术传播的难易程度和粮食生产的开始时间等方面存在着差异,而这些差异又对技术的出现产生了种种影响,但所有这些影响都被夸大了,因为技术可以催化自身。欧亚大陆在开始时的巨大优势因此就变成了自1492年[插图]起的巨大的领先优势——其原因是欧亚大陆独特的地理条件,而不是那里的人特别聪明。
  • 第一组差异是各大陆在可以用作驯化的起始物种的野生动植物品种方面的差异。
  • 第二组因素就是那些影响传播和迁移速度的因素,而这种速度在大陆与大陆之间差异很大。
  • 与影响大陆内部传播的这些因素有关的,是第三组影响大陆之间传播的因素,这些因素也可能有助于积累一批本地的驯化动植物和技术。大陆与大陆之间传播的难易程度是不同的,因为某些大陆比另一些大陆更为孤立。
  • 第四组因素是各大陆之间在面积和人口总数方面的差异。更大的面积或更多的人口意味着更多的潜在发明者,更多的互相竞争的社会,更多的可以采用的发明创造

欧洲领先的原因

  • 在欧亚大陆范围内,为什么是欧洲社会,即在美洲和澳大利亚殖民的那些社会,而不是新月沃地的社会或中国和印度的社会,在技术上领先,并在现代世界上占据政治和经济的支配地位?如果一个历史学家生活在从公元前8500年到公元1450年的任何一段时间内,如果他当时试图预测未来的历史发展轨迹,他肯定会认为,欧洲最终的支配地位是最不可能发生的结果,因为欧洲在过去那1万年的大部分时间里是旧大陆的那3个地区中最落后的一个地区。
  • 新月沃地和东地中海社会不幸在一个生态脆弱的环境中兴起。它们破坏了自己的资源基础,无异于生态自杀。
  • 但在中国,情况有所不同,因为那整个地区在政治上是统一的。一个决定就使整个中国停止了船队的航行。那个一时的决定竟是不可逆转的,因为已不再有任何船坞来造船以证明那个一时决定的愚蠢,以及用作重建新船坞的中心。
  • 事实上,正是由于欧洲是分裂的,哥伦布才成功地于第五次在几百个王公贵族中说服一个来赞助他的航海事业。一旦西班牙这样开始了欧洲对美洲的殖民,其他的欧洲国家看到财富滚滚流入西班牙,立刻又有6个欧洲国家加入了对美洲殖民的行列。对于欧洲的大炮、电灯照明、印刷术、小型火器和无数的其他发明,情况也是如此:每一项发明在欧洲的一些地方由于人们的习性起先或者被人忽视,或者遭人反对,但一旦某个地区采用了它,它最后总能传播到欧洲的其余地区。
  • 欧洲分裂所产生的这些结果与中国统一所产生的结果形成了鲜明的对比。除了作出停止海外航行的决定外,中国的朝廷还作出停止其他一些活动的决定:放弃开发一种精巧的水力驱动的纺纱机,在14世纪从一场产业革命的边缘退了回来,在制造机械钟方面领先世界后又把它拆毁或几乎完全破坏了,以及在15世纪晚期以后不再发展机械装置和一般技术。统一的这些潜在的有害影响在现代中国又死灰复燃,特别是20世纪60年代和70年代“文化大革命”中的那种狂热,当时一个或几个领导人的决定就把全国的学校系统关闭了5年之久。
  • 中国的经常统一与欧洲的永久分裂都由来已久。
  • 欧洲海岸线犬牙交错,它有5大半岛,每个半岛都近似孤悬海中的海岛,在所有这些半岛上形成了独立的语言、种族和政府:希腊、意大利、伊比利亚半岛、丹麦和挪威/瑞典。中国的海岸线则平直得多,只有附近的朝鲜半岛才获得了作为单独岛屿的重要性。欧洲有两个岛(大不列颠岛和爱尔兰岛),它们的面积都相当大,足以维护自己的政治独立和保持自己的语言和种族特点,其中的一个岛(大不列颠岛)因为面积大,离欧洲大陆又近,所以成了一个重要的欧洲独立强国。
  • 中国在地理上的四通八达最后却成了一个不利条件,某个专制君主的一个决定就能使改革创新半途而废,而且不止一次地这样做了。相比之下,欧洲在地理上的分割形成了几十个或几百个独立的、相互竞争的小国和发明创造的中心。如果某个国家没有去追求某种改革创新,另一个国家会去那样做的,从而迫使邻国也这样去做,否则就会被征服或在经济上处于落后地位。欧洲的地理障碍足以妨碍政治上的统一,但还不足以使技术和思想的传播停止下来。欧洲还从来没有哪一个专制君王能够像在中国那样切断整个欧洲的创造源泉。
  • 地理上的四通八达对技术的发展既有积极的影响,也有消极的影响。因此,从长远来看,在地理便利程度不太高也不太低而是中等适度的地区,技术可能发展得最快。中国、欧洲,可能还有印度次大陆的过去1000多年的技术发展过程便是例子,它分别表明了高、中、低3种不同程度的地理便利条件所产生的实际效果。
  • 新月沃地的居间的地理位置,控制了把中国和印度与欧洲连接起来的贸易路线,以及中国距离欧亚大陆其他先进的文明国家路途遥远,使中国实际上成为一个大陆内的一个巨大孤岛。中国的相对孤立状态与它先是采用技术后来又排斥技术这种做法有着特别重要的关系
  • 新月沃地和中国的历史还为现代世界留下了一个有益的教训:环境改变了,过去是第一并不能保证将来也是第一
  • “最优分裂原则”:创新在带有最优中间程度分裂的社会里发展得最快:太过统一的社会处于劣势,太过分裂的社会也不占优。
  • 总结:
    1. 过于统一
    2. “最优分裂原则”;有时需要多种声音,多样化,才能更加健康发展。

中国落后的原因?

  1. 中央集权决策错误闭关锁国
  2. 日本拒绝枪支和中国抛弃远洋船只(以及抛弃机械钟和水力驱动纺纱机),是历史上孤立或半孤立社会技术倒退的著名例子。

新月沃地落后的原因?

  • 新月沃地从领先到落后欧洲发展的原因:农业领先优势从技术传到西方后,由于西方的面积地域优势,逐步发展起来,并超过了新月沃地。另一方面,由于新月沃地过渡自然砍伐破坏,地貌上发生了根本性的变化,成为了沙漠,不利于更长久的发展,最终落后于欧洲。

个人总结

  1. 一旦发明家发现了一项新技术的用途,下一步就是说服社会来采用它。仅仅有一种更大、更快、更有效的工作装置还不能保证人们会乐于接受。无数的此类技术要么根本没有被采用,要么只是在长期的抵制之后才被采用。(现在的游戏直播,打赏,虚拟主播等,都是想办法让用户逐步接受)
  2. 事实上,在整个大陆和其他一些包含数以百计的互相竞争的广大地区,有些社会对新事物可能比较开放,有些社会对新事物可能比较抵制。那些接受新作物、新牲畜或新技术的社会因而可能吃得更好,繁殖得更快,从而取代、征服或杀光那些抵制新事物的社会。(接收新事物才不会被淘汰)
  3. 流行病(人群病)。拥有流行病的族类是战胜没流行病族类的重要因素。
    • 拥有流行病首先要死一部分人,你愿意成为拥有你族类的炮灰吗?
    • 如果处于无流行病的族群,那么可能当前是好的,但是对你后代不利;有流行病的族类则相反;
    • 族类之间相互融合,有时很难说清你究竟属于哪个族类;
    • 认清你觉得最重要的事情或人,作出相应的选择;在某些大环境下,作出选择后,可能需要欺骗自己,以更好的实施,保持政治正确;族类的精神控制
  4. “最优分裂原则”
  5. 一个人的命运,要靠自我奋斗,但也要考虑到历史的进程。外部环境则是历史进程的基础与原生动力。

其他网友

  • 读到这里对教育有了一些思考,终归还是要把孩子放出去多与其他人接触交流合作,才会受到伤害,才会由此产生自我认知,才会成长。而如果只是把孩子孤立起来,与社会少有接触,那就会像免疫力差的一碰即亡。
  • 人总是要吃饱饭才有力气干活,事实证明当劳动的效率越高,人们获得的能量也就越多也就促进了文明的发展,毕竟只有劳动之后的能量有剩余才能够养得起专职人员。
  • 让一群各自为战的人团结起来的最好办法 就是树立一个共同的敌人和威胁!

单元测试使用总结

发表于 2020-11-11

mockito使用

mock实例

  • Test test = mock(Test.class);
  • Test test = spy(new Test());

在Spring中使用

  • @Mock Test test;
  • @Spy Test test = new Test();
  • @MockBean Test test; (起spring容器时)
  • @InjectMocks Test test; (起mockito容器时的测试对象)
  • Spring boot使用@MockBean和@SpyBean来定义Mockito的mock和spy。
  • 使用@mockBean,未mock的方法可能会导致返回默认值,从而导致异常的逻辑造成脏数据(可能是代码本来不完善);可以注意尽量@SpyBean

mock使用

  • when().thenReturn()模式 和 doReturn().when()模式
    两种模式都用于模拟对象方法,在mock实例下使用时,基本上是没有差别的。但是,在spy实例下使用时,when().thenReturn()模式会执行原方法,而doReturn().when()模式不会执行原方法。

  • when(test.do(anyString())).thenReturn(true);

  • 返回值为void: doAnswer((Answer<Void>)invocation -> null).when(test).do(anyString(),anyLong(),anyString());

  • 抛异常:doThrow(new RuntimeException("error")).when(test).do(anyLong(), anyString());

  • doReturn(expected).when(spyList).get(100); (使用spy时)
    - org.mockito.Mockito.doReturn; 注意不要导入powermock的包
    - doReturn().when()模式: 用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法

  • 直接调用真实方法:Mockito.doCallRealMethod().when(userService).getUser(userId);, Mockito.when(userService.getUser(userId)).thenCallRealMethod();

  • 模拟可空参数方法: Mockito.doReturn(user).when(userDAO).queryCompany(Mockito.anyLong(), Mockito.nullable(Long.class));

  • 匹配null对象,可以使用isNull方法,或使用eq(null):Mockito.doReturn(user).when(userDAO).queryCompany(Mockito.anyLong(), Mockito.isNull());, Mockito.when(userDAO.queryCompany(Mockito.anyLong(), Mockito.eq(null))).thenReturn(user);

  • 模拟final方法: PowerMock提供对final方法的模拟,方法跟模拟普通方法一样。但是,需要把对应的模拟类添加到@PrepareForTest注解中。

  • 模拟私有方法: PowerMock提供提对私有方法的模拟,但是需要把私有方法所在的类放在@PrepareForTest注解中。

    1
    2
    3
    4
    5
    PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected);
    //通过模拟方法stub(存根),也可以实现模拟私有方法。但是,只能模拟整个方法的返回值,而不能模拟指定参数的返回值。
    PowerMockito.stub(PowerMockito.method(UserService.class, "isSuperUser", Long.class)).toReturn(!expected);
    Method method = PowerMockito.method(UserService.class, "isSuperUser", Long.class);
    Object actual = method.invoke(userService, userId);
  • 模拟构造方法: PowerMock提供PowerMockito.whenNew方法来模拟构造方法,但是需要把使用构造方法的类放在@PrepareForTest注解中。

    1
    2
    PowerMockito.whenNew(MockClass.class).withNoArguments().thenReturn(expectedObject);
    PowerMockito.whenNew(MockClass.class).withArguments(someArgs).thenReturn(expectedObject);
  • 调用无访问权限的构造方法: 调用无访问权限的构造方法,可以使用PowerMock提供的Whitebox.invokeConstructor方法。

  • 调用无权限访问的普通方法: 调用无访问权限的普通方法,可以使用PowerMock提供的Whitebox.invokeMethod方法。

  • 附加匹配器
    Mockito的AdditionalMatchers类提供了一些很少使用的参数匹配器,我们可以进行参数大于(gt)、小于(lt)、大于等于(geq)、小于等于(leq)等比较操作,也可以进行参数与(and)、或(or)、非(not)等逻辑计算等。
    PowerMockito.when(mockList.get(AdditionalMatchers.geq(0))).thenReturn(expected);

  • Whitebox.setInternalState方法
    现在使用PowerMock进行单元测试时,可以采用Whitebox.setInternalState方法设置私有属性值: Whitebox.setInternalState(Foo.class, "FIELD_NAME", "value");

mock静态方法

  • 返回值为void: PowerMockito.doNothing().when(FileUtils.class, "writeStringToFile", any(File.class), anyString());
  • BDDMockito.given(FileUtils.readFileToString(eq(new File(file)))).willReturn(IOUtils.toString(getClass().getClassLoader().getResourceAsStream("test.json")));
  • 前置初始化:
    1
    2
    3
    4
    5
    6
    7
    @RunWith(PowerMockRunner.class)
    @PrepareForTest({FileUtils.class})
    @Before
    {
    MockitoAnnotations.initMocks(this)
    PowerMockito.mockStatic(FileUtils.class);
    }

设置静态常量字段值

  • 有时候,我们需要对静态常量对象进行模拟,然后去验证是否执行了对应分支下的方法。比如:需要模拟Lombok的@Slf4j生成的log静态常量。但是,Whitebox.setInternalState方法和@InjectMocks注解并不支持设置静态常量,需要自己实现一个设置静态常量的方法:
1
2
3
4
5
6
7
public final class FieldHelper {
public static void setStaticFinalField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
FieldUtils.removeFinalModifier(field);
FieldUtils.writeStaticField(field, fieldValue, true);
}
}

使用Answer来生成期望的返回

1
2
3
4
5
6
7
8
9
10
11
12
13
when(test.do(anyList())).thenAnswer(
(Answer)invocation -> {
Object[] args = invocation.getArguments();
//Object mock = invocation.getMock();
return list.stream().filter(v -> ((List<Integer>)args[0]).contains(v.getAid())).collect(
Collectors.toList());
});
doReturn(resp).when(test).do(argThat(new ArgumentMatcher<List<Long>>() {
@Override
public boolean matches(Object argument) {
return ((List<Long>)argument).size() == 1;
}
}));

verify使用

  • 校验执行次数:Mockito提供vertify关键字来实现校验方法是否被调用,从未被调用never(),调用次数times(1), verify(test).do(any());
  • 验证私有方法
    PowerMockito.verifyPrivate(userService).invoke("isSuperUser", userId);
  • 验证方法调用并捕获参数值: Mockito提供ArgumentCaptor类来捕获参数值,通过调用forClass(Class clazz)方法来构建一个ArgumentCaptor对象,然后在验证方法调用时来捕获参数,最后获取到捕获的参数值并验证。如果一个方法有多个参数都要捕获并验证,那就需要创建多个ArgumentCaptor对象。
  • verify语句, 除times外,Mockito还支持atLeastOnce、atLeast、only、atMostOnce、atMost等次数验证器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class ListTest {
@Test
public void testAdd() {
List<Integer> mockedList = PowerMockito.mock(List.class);
PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt());
mockedList.add(1);
mockedList.add(2);
mockedList.add(3);
InOrder inOrder = Mockito.inOrder(mockedList);
inOrder.verify(mockedList).add(1);
inOrder.verify(mockedList).add(2);
inOrder.verify(mockedList).add(3);
}
}
  • 验证调用参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class ListTest {
@Test
public void testArgumentCaptor() {
Integer[] expecteds = new Integer[] {1, 2, 3};
List<Integer> mockedList = PowerMockito.mock(List.class);
PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt());
for (Integer expected : expecteds) {
mockedList.add(expected);
}
ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);
Mockito.verify(mockedList, Mockito.times(3)).add(argumentCaptor.capture());
Integer[] actuals = argumentCaptor.getAllValues().toArray(new Integer[0]);
Assert.assertArrayEquals("返回值不相等", expecteds, actuals);
}
}
  • 确保验证完毕.
    Mockito提供Mockito.verifyNoMoreInteractions方法,在所有验证方法之后可以使用此方法,以确保所有调用都得到验证。如果模拟对象上存在任何未验证的调用
  • 验证静态方法。Mockito没有静态方法的验证方法,但是PowerMock提供这方面的支持。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {
@Test
public void testVerifyStatic() {
PowerMockito.mockStatic(StringUtils.class);
String expected = "abc";
StringUtils.isEmpty(expected);
PowerMockito.verifyStatic(StringUtils.class);
ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
StringUtils.isEmpty(argumentCaptor.capture());
Assert.assertEquals("参数不相等", argumentCaptor.getValue(), expected);
}
}

其他

  • ReflectionTestUtils.setField(config, "id", id);
  • Mockito.reset(test);
  • @VisibleForTesting
  • Mocking a method in the same test class using - Mockito:https://towardsdatascience.com/mocking-a-method-in-the-same-test-class-using-mockito-b8f997916109
1
2
3
4
5
6
7
8
9
10
11
12
13
public class PersonTest{

@Test
public void playTest() {
Person person = new Person("name", 15, "23435678V");

Person person1 = Mockito.spy(person);

Mockito.doReturn(true).when(person1).runInGround("ground");

Assert.assertEquals(true, person1.isPlay());
}
}

一般不建议在同个类mock自己的方法,如果一定要,可以使用spy

  • @Captor注解在字段级别创建参数捕获器。但是,在测试方法启动前,必须调用MockitoAnnotations.openMocks(this)进行初始化。
  • @PowerMockIgnore注解
    为了解决使用PowerMock后,提示ClassLoader错误。

异常测试

1
2
3
4
5
6
7
8
   @Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void test() {
thrown.expect(new BizExceptionCodeMatches(ExceptionCodeEnum.FAIL_CODE.code()));
//do
}
1
@Test(expected = IndexOutOfBoundsException.class)

异步测试

  • 异步系统的两种测试方法:https://mp.weixin.qq.com/s/ft7LDsLmJByxunPuqGUOuQ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ExampleTest {

private final Object lock = new Object();

@Before
public void init() {
new Thread(new Runnable() {
public void run() {
synchronized
(lock) {
//获得锁
monitorEvent();
//监听异步事件的到来
lock.notifyAll();
//事件到达,释放锁
}
}
}).start();
}

@Test
public void testAsynchronousMethod() {
callAsynchronousMethod();
//调用异步方法,需要较长一段时间才能执行完,并触发事件通知
/**
* 事件未到达时由于init已经获得了锁而阻塞,事件到达后因init中的锁释放而获得锁,
* 此时异步任务已执行完成,可以放心的执行断言验证结果了
*/
synchronized
(lock) {
assertTestResult();
}
}
}

基于Spock的数据驱动测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class SpockTest  extends Specification{

// 初始化
def setupSpec() {
println ">>>>>> setupSpec"
}
def setup() {
println ">>>>>> setup"
}
def cleanup() {
println ">>>>>> cleanup"
}
def cleanupSpec() {
println ">>>>>> cleanupSpec"
}

def void testAdd(int a, int b, int expect) {

expect:
assert expect == a + b

where:
a | b | expect
1 | 1 | 2
2 | 2 | 4
}

}

使用rest-assured进行接口层测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RestAssuredMockMvcTest {

@Before
public void before(){
RestAssured.registerParser("text/plain", Parser.JSON);
}

///https://github.com/rest-assured/rest-assured/wiki/Usage#spring-mock-mvc-module
@Test
public void test(){

given().
standaloneSetup(new GreetingController()).
param("name", "Johan").
when().
get("/greeting").
then().
statusCode(200).
body("code", equalTo(0));
//body("data", equalTo("Hello, Johan!"));
}
}

事务测试

  • TODO

其他

  • 单元测试的运行速度重要吗?
    违背这个原则的典型反例,就是在单测中启动 Spring。
  • 数据驱动测试(Data Driven Test)
  • mockito只能用于纯逻辑的验证,涉及事务那些还是没办法。单个类的单元测试逻辑正确不代表逻辑就覆盖全了,所以针对重要的接口需要单独集成测试用例
  • ContiPerf:: 更为优雅和方便的单元压力测试工具。

扩展

  • [JDK11下Mock框架进化:从PowerMockito到Mockito Only]https://mp.weixin.qq.com/s/OsySrzocrMmJdk6C0_h60A

Reference

  • https://www.baeldung.com/mockito-void-methods
  • https://stackoverflow.com/questions/2276271/how-to-make-mock-to-void-methods-with-mockito
  • https://github.com/eugenp/tutorials/tree/master/testing-modules/mockito
  • </https://stackoverflow.com/questions/9585323/how-do-i-mock-a-static-method-that-returns-void-with-powermock>
  • https://www.cnblogs.com/Ming8006/p/6297333.html#c2.9
  • https://www.baeldung.com/mockito-annotations
  • https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/
  • https://github.com/eugenp/tutorials/tree/master/testing-modules/mockito
  • https://github.com/rest-assured/rest-assured/wiki/Usage#spring-mock-mvc-module
  • 单元测试,只是测试吗?
  • 单元测试难?来试试这些套路
  • Spock单元测试框架以及在美团优选的实践
  • [阿里开源的 Mock 工具:TestableMock]
  • 收藏!Java编程技巧之单元测试用例编写流程 – 这个很全了。。。
  • https://stackoverflow.com/questions/30890011/whats-the-difference-between-mockito-matchers-isa-any-eq-and-same
  • https://stackoverflow.com/questions/24295197/is-there-mockito-eq-matcher-for-varargs-array
  • https://stackoverflow.com/questions/5385161/powermock-testing-set-static-field-of-class
  • Java单元测试技巧之PowerMock:

  • 转:分享一个观点:区分集成测试和单元测试的最本质差别在于,单元测试没有不可控外部依赖,也就是不会因为外部的原因导致测试失败。其它差别都不是能有效区分单元测试和集成测试的。

《我的第一本金融入门书》-笔记

发表于 2020-11-03

银行的生财之道——存款与贷款

  • 银行的存款不可能全部贷放出去,按照法律规定,银行必须要留存一定比例的存款作为应对储户提款的准备金,并且贷款在很多时候存在不能及时收回的情况,甚至会出现坏账,这会对银行造成损失,此外,这笔钱还没有扣除税收等因素

货币创造的秘密——商业银行的派生存款

  • 我们时常听到财经新闻报道说,中央银行上调法定存款准备金率0.5个百分点,冻结了约3000万元的银行存款货币。在我国,这是当市场流动性泛滥、通货膨胀严重的时候政府所采用的紧缩货币供给的手段。银行存款也是货币,并且银行账户上的存款数量远远大于现金,那么这些多出来的没有现金支撑的存款货币是怎么创造出来的呢?为什么法定存款准备金率的上调就能冻结那么多的存款货币呢?
  • 我们知道,现在人们使用的货币都是信用货币,而信用货币最早的形式是商业银行的银行券。后来,银行券的发行逐渐集中到了一国的中央银行。也就是说,现在人们手中的所有现金货币都是由中央银行发出来的,而商业银行能够创造的只是存款货币。存款货币的创造与银行以支票存款为依据组织的转账结算有直接的联系。一般来说,人们把现金存入银行之后,并不一定再把现金全数提出;从银行取得贷款的客户通常也并不要求银行支付现金,而是要求把贷款记在自己的存款账户中。当客户的存款账户上存有款项时,既可以在必要的时候提取现金,又可以开出支票履行支付义务;当一位客户取得支票时,他往往也不是到付款银行提取现金,而是委托自己开有存款账户的往来银行代收并把收来的款项记入存款账户。对银行来说,客户开出支票,因此有应该付出的款项,同时客户交来支票委托收款,因此有应该收入的款项
  • 银行对现金货币的需要归结为两类:一是客户从存款中提取现金用于发放工资、小额零星支付等;二是结清支票结算中应收应付的差额。在长期经营中,银行发现,相对于存款,现金只是一小部分,而且两者的比例关系相对稳定。也就是说,只要按存款的一定百分比保持现金库存即可应对客户对于现金的需要。
  • 在这样的现代银行支付体系下,存款货币就产生出来,或者说是派生出来了。
  • 假如有一位客户甲,他持有10000元的现金,并将其存入开立了活期存款账户的A银行。从而,A银行负债业务中的存款一项就多了10000元。根据经验,保存相当于90%的现金就足以应对客户日常提取现金的需要,那么A银行可以把9000元贷出。假如这时候正好有一位客户乙要从A银行贷款9000元,那么此时A银行的资产账户上就多出来9000元的贷款,以及剩余的1000元用于应对取现的准备金,实际上,这1000元的准备金是按照法律要求要存到中央银行的账户上的。
  • 如果客户乙将这9000元的贷款用签发支票的方式支付给与自己有业务往来的客户丙,客户丙在B银行开立了账户,于是这9000元则由A银行转到了B银行,B银行的存款就多了9000元。同样的道理,B银行将这9000元中的10%留作准备金,将其余的90%(8100元)贷放出去。如果这时正好有客户丁要从B银行贷款8100元,那么B银行的资产账户就多出来8100元的贷款,同时B银行将900元的准备金上缴中央银行。
  • 以此类推,B银行的客户丁向C银行的客户戊用支票支付8100元的应付款,于是C银行就又多了8100元的存款,在这8100元中,90%用于C银行的贷款,10%用作准备金,无限地循环下去,最终我们可以得到一个总的存款数量和准备金数量。
  • 将A、B、C……银行的存款数量相加,得到10000+9000+8100+7290+…=100000(元),准备金数量为1000+900+810+729+…=10000(元),从中我们可以看出最初的10000元原始存款最终变成了100000元派生存款,扩大了10倍。经过简单归纳,我们发现这个倍数正好是准备金率的倒数,也就是1/10%=10。
  • 至此,我们就明白了商业银行派生货币的秘密,在银行之间的非现金转账制度和不完全准备金制度下,这样的存款货币派生机制就是有效的。在这一制度下,某单位的现金经过多次存款转账支付,可以创造出(1/准备金率)倍的存款。我们也能够回答中央银行调整存款准备金率对存款货币数量影响巨大的原因了。假如存款准备金率由10%调整到20%,那么存款的最终创造数量就变为1/20%,扩大了5倍,即10000元的现金只能创造出50000元的存款,这个影响相当巨大。
  • 在非现金转账制度和不完全准备金制度的条件下,商业银行可以创造出数倍于现金数量的存款,而这个倍数就是存款准备金率的倒数。

利率是怎样调整的——固定利率与浮动利率

  • 根据在借贷期内利率是否会发生变化,我们又可以将利率分为固定利率和浮动利率。
  • 浮动利率是一种在借贷期内可定期调整的利率。
  • 浮动利率尽管可以为债权人减少损失,但也因手续复杂、计算依据多样而增加费用开支。因此,浮动利率多用于3年以上的国际金融市场的借贷。
  • 固定利率和浮动利率是利率的两种设置方式。采用固定利率可以使计算简单方便,而采用浮动利率可以使长期借贷行为更加公平。

为什么会有负利率——名义利率与实际利率

  • 实际利率是指物价水平不变,从而货币购买力保持不变条件下得到的利息率。
  • 为了避免通货膨胀的损失,假设仍然要取得3%的利息,那么粗略地计算,乙必须把贷款利率提高到8%,这样才能保证收回的本金和利息之和与以前的物价水平相当,并且保证购买力不变。其中,这个8%就是名义利率。从这个例子我们可以看出,名义利率是包括物价水平变化情况的利率,实际利率和名义利率之间的关系可以大致表示为:名义利率=实际利率+通货膨胀率。市场上各种利率都是名义利率,实际利率却不容易观察到。而利用上述公式,就可以根据已知的名义利率和通货膨胀率推出实际利率。例如考察我国现在的银行存款,1年期定期储蓄存款的利率是3.25%,这是名义利率。而2011年3月的消费物价指数为4.9%,这可以代替通货膨胀率,这样我们可以算出1年期储蓄存款的实际利率为-1.65%,也就是说,我们年初把钱存进银行,年末取出后按购买力计算还亏了1.65%。所以,有些人不愿意把钱存进银行,而是去炒股或者买房,因为一般认为股市和房市是可以实现保值的市场。
  • 实际利率是物价水平不变条件下的利率,而名义利率是包括了物价变化水平的利率,两者的关系为:名义利率=实际利率+通货膨胀率

没有风险的利率——基准利率

  • 生活中,我们经常听到所谓基准利率的说法,比如,在国内,老百姓习惯上把银行的1年期定期储蓄存款的利率作为基准利率;而银行从业人员则把银行间隔夜拆借利率作为基准利率;在国外,基准利率又成了中央银行的再贴现率。那么,到底什么是基准利率呢?顾名思义,基准利率是指在多种利率并存的条件下起决定作用的利率,也就是说这种利率发生了变动,其他利率也会相应变动。因此,了解了这种关键性的利率水平的变化趋势,也就可以了解全部利率体系的变化趋势

什么是有价证券的价值——票面价值、内在价值、市场价格

  • 票面价值也称面值,是在有价证券票面上标明的金额数值
  • 内在价值就是一种有价证券未来收益的现值,它是对有价证券进行价值评估的核心。
  • 对于债券,其本身并没有对应的实物资产。股票,有其对应的实物资产,但具有同样估值金额的实物资产绝不等于对应的股票有同等的内在价值。
  • 市场价格也称市值,它的形成是以其内在价值为准的
  • 票面价值、内在价值、市场价格是三个不同的概念。票面价值是标明在有价证券票面上的金额数量,内在价值是有价证券未来收益的现值,而市场价格是围绕有价证券内在价值上下波动而形成的。

怎样评估有价证券的价值——净现值

  • 现值发行的债券票面价格为1000元,每年按8%付息,即利息为80元,每年付息一次,10年还本。如果市场利率达到9%,那么这张债券的现值,或者说内在价值实际只有935元,如果这张债券的定价超过935元,则将不具有投资价值,如果定价低于935元,则可以买入。
  • 净现值是将有价证券未来可预期到的收益折现到当前所得到的金额再减去投资的成本。

一个简单的股票价值评价指标——市盈率

  • 所谓市盈率,就是股票的市场价格与每股盈利的比值。
  • 市盈率=每股市场价格/每股盈利
  • 如果市盈率太高,则可能意味着股票的价格高于价值,在这种情况下,投资者需要卖出手中的股票或不购买这种股票。如果市盈率太低,则可能意味着股票的价值被低估,此时正是投资的好时机
  • 从理论上来说,股票的市盈率愈低,愈值得投资
  • 只有比较同类股票的市盈率才会有实用价值
  • 市盈率的高低也不能作为投资选择的绝对标准。因为高市盈率还可能意味着股票的收益有很大的增长潜力,投资者对股票特别看好;低市盈率可能是因为股票未来的收益前景不好,投资人对股票不那么看好导致的。
  • 证券业监管部门也十分关注市盈率的高低。如果市场的平均市盈率太高,那么,政府可能得出市场泡沫过大的判断,然后采取一定的措施帮助消除泡沫。
  • 市盈率是判断股票价值的一种十分简便的方法,它的数值等于股票的市场价格与每股盈利的比值,含义是,每得到1元的收益需要付出多少元的投资。

实际收益的损失——通货膨胀风险

  • 通货膨胀风险也称为购买力风险。它是由于通货膨胀、货币贬值给投资者带来的实际收益下降的风险
  • 因为通货膨胀的增长,居民的收入或工资可能会出现上涨。大部分民众对通货膨胀不敏感,但是对自己的工资上涨很敏感。人们的收入增加,大多数的人会选择增加消费,这种现象就是货币幻觉
  • 股票的通货膨胀风险相对较小。
  • 当一国的股票市场完全瘫痪,不可以保证人们财富的安全时,人们就会放弃股票和本国货币,大量持有有信用的外币和黄金等贵金属。

购买金融资产就是投资吗——投资与投机

  • 交易者根据交易手段、交易结果、交易目的的不同应该被分成两类:投资者与投机者。
  • 拥有安全性、盈利性、研究性特征的是投资者,反之则是投机者。
  • 投资者深入分析金融资产的价值,他们寻找价值被低估的金融产品并长期持有以获得稳定的收入。投机者关注行情指标,他们购买金融资产的目的就是为了在短期内获得暴利。

行为金融学典型现象——羊群效应

  • 羊群效应是行为金融学的经典内容。羊群是一种比较散乱的动物组织,如果有一只羊躁动起来,那么其余的羊会跟随着这只羊一起躁动起来。羊群效应说的是一种从众心理。
  • 羊群效应的教训告诫我们,做任何事都要有自己的判断。总是跟着别人投资,也不会得到多大的收益
  • 投资者应该相信自己的判断,千万不要盲目跟风投资。

股票的生财之道——分红与价差

  • 股票的获利方式有两种,一种是通过股价的变动获利,另一种是通过股票的分红获利。
  • 在长期中获得合理的收益,而不是短期内的一夜暴富,这是我们股票的一个重要原则。
  • 只有坚定这一个原则,我们在投资股票时,才能够以一个平和的心态去面对股市带来的波动与股价的涨涨落落。

未来标准化的商品买卖——期货合约

  • 之前讲到远期合约的优势是规避未来的风险,其实远期合约也存在一些不足,那就是远期合约往往是买卖双方商议好就可以达成的一种合约,而如果第三方甚至其他各方想要介入这份合约或者买卖双方想要将合约中的金融产品卖给其他人的话,则会比较麻烦。比如双方规定好要交的货是一等大豆1000吨,但是第三方只想要200吨,或者第三方只想要二等大豆等,这样就不能转卖这个远期合约。为了避免这种麻烦,使交易更为顺畅地进行,人们又发明了标准化的远期合约,即期货合约。
  • 期货是现在进行买卖,但是在将来进行交收或交割的标的物,这个标的物可以是某种商品(如黄金、原油、农产品),也可以是金融工具,还可以是金融指标。
  • 期货实际上是一种可以反复转让、反复买卖的标准化合同。
  • 交易这些“将运到”合约比交易谷物本身要更为有用
  • 期货市场的两类关键参与人就是套期保值者与投机者。期货市场建立的目的是出于对保值的需要。
  • 所谓套期保值,就是以现在的价格卖出未来生产的产品,或是以现在的价格买入未来所需要的原料。
  • 套期保值者一般是产品的生产商或者是需要原料的加工商。他们进入期货市场购买或卖出期货合约的目的是为了避免未来的损失。举例来说,棉花生产商为了避免在棉花收获时因棉花丰收而引起的价格下跌中受到损失,在收获期前三个月就在期货市场卖出期货合约,或者做空该种类的期货。而到了棉花收获期,他们再买入期货合约,也就是进行平仓。由于期货合约越到履约期越接近现货价格,如果三个月后棉花价格下跌,那么棉花生产商便在期货市场盈利,从而弥补在现货市场的亏损;如果三个月后棉花价格上涨,那么棉花生产商便在现货市场盈利,从而弥补在期货市场的亏损,即将利润在三个月前就锁定。
  • 而投机者在期货交易中则是扮演着“价格发现者”的角色。投机者进入期货市场购买或卖出期货合约的目的只有一个——就是获利。因此,在期货市场中时刻关注价格的波动与走向,如果发现期货市场价格与现货市场价格出现偏离,那么就会毫不犹豫地利用价差进行套利交易。但是同时,他们的不断套利也使得期货市场与现货市场的价格不会出现扭曲。
  • 期货采用保证金制度,即只需要付部分货款就可以订购全部的商品
  • 期货市场更适合专业的投资者进行投资。
  • 中国有上海期货交易所、郑州商品交易所、大连商品交易所、中国金融期货交易所四大交易所
  • 期货合约是将要买卖的东西的单位、品质、样式等在合约中事先规定好,在规定的日期进行交割的一种远期合约。期货的出现有助于保障原材料供应商与需求商的利益不会因原材料价格波动而受损。期货交易中使用保证金制度,属于杠杆交易,风险较大。

金融政策的神奇魔力

  • 政府调节经济的目的是为了预防由于单纯依靠市场力量所出现的种种市场失灵的情况。无论是由于人们的盲目乐观而产生的经济泡沫,还是由于人们信心不足而出现的经济衰退,政府都可以通过运用金融政策使经济重回正轨

市场失灵的历史表现——1929年美国经济危机回顾

  • 经济学家凯恩斯认为经济危机的根源在于有效需求不足。
  • 劳动者工资率的上升却慢于劳动生产率的增长。其结果是,生产商品的能力大大超过了购买力,“消费不足”带来了“生产过剩”的必然后果。

金融政策魔力的源泉——货币、利率与汇率

  • 一个国家的宏观金融政策应主要包括三大政策:货币政策、利率政策和汇率政策。
  • 政府的目标是确保三个市场的均衡,即保证国内的商品市场的均衡、货币市场的均衡以及国际收支的均衡。
  • 对于商品市场的均衡,从总体上说,就是国内商品的总供给与总需求的均衡,让所供给的商品满足所产生的需求,这样一方面可以不出现供过于求的局面,不至于产生商品积压,引起通货紧缩,使得经济发展陷入停滞的局面;另一方面可以防范供不应求的局面,不至于出现需求旺盛而供给不足,产生物价上涨,人民的需求难以得到有效保障的困境。政府可以运用财政政策对市场加以调整。在货币市场的均衡方面,就是要使一国对于货币的需求等于货币的供给。就货币的需求而言,根据凯恩斯的理论,人们有三种倾向去持有货币,即预防动机、交易动机与投机动机。这三方面的因素使得人们对货币有相应的需求。而中央银行作为货币的发行机构,可以通过控制货币的供给引导货币市场的均衡。在国际收支方面,如果一国的出口大于进口,则会出现顺差局面,会对本国的货币产生升值压力,进而影响本国的出口行业,并同时容易造成输入型通货膨胀,对国内经济产生影响;而如果进口大于出口,则会产生贸易赤字,使得本国货币不断走弱,进而影响本国的国际购买力水平与经济发展前景。因此,政府应该通过有效的汇率政策等调控方式引导国际收支平衡。

是痛苦的根源还是繁荣的表象——通货膨胀

  • 如果通货膨胀保持在一个比较低的水平,那么就说明市场上对于各种产品的需求比较旺盛,至少是供不应求。这样的话,整个经济的流转就会比较顺畅,企业生产的产品不会因为卖不出去而滞销;企业可以继续生产,生产过程中也会雇佣一定的劳动者,因此也不会有人失业。劳动者有了工作,也就有了收入,有了收入,也就有了消费的基础。
  • 一般而言,国际上通常认为通货膨胀率(一般用消费者物价指数CPI进行衡量)在2%以下是可以接受的范围,而在3%以上就认为已存在问题。如果通货膨胀率高于5%,则认为存在着比较严重的问题。而通货膨胀率如果达到20%,就认为这个国家面临着经济崩溃的风险。
  • 通货膨胀是指一个经济体在一段时间内货币数量增速大于实物数量增速,普遍物价水平上涨,单位货币的购买力下降的状况。较低水平的通货膨胀反映经济的蓬勃发展,但较高水平的通货膨胀就是经济痛苦的根源

一个艰难的抉择——失业与通货膨胀的跷跷板

  • 失业率的存在是让人苦恼的,通货膨胀的爆发是让人忧愁的,西方一些学者甚至将两者的数值进行加总,创造出一个“痛苦指数”来反映人们对于现行经济状况的担忧。而更让一国政府与中央银行郁闷的是,往往他们通过相应的金融政策调整,在解决其中一个问题的时候,会引发另一个问题的产生。就如同跷跷板一样,压下了这一头,那一头又升了起来。
  • 事实正是如此,当中央银行通过宽松的货币政策甚至是降息对不景气的经济进行刺激以消除失业压力的同时,会引发投资的加速,使得原材料的成本大幅度提高,进而引发一般商品价格上涨,并促使工资上涨,形成成本推动的通货膨胀。相反,当中央银行通过提高利率或收紧货币为一个过热的经济体降温时,常常会让投资者犹豫不决,导致投资不足,进而使企业不能进行有效的扩大再生产,并开始裁员,导致失业人数增加。
  • 上述这种情况正好反映了金融政策制定者所处的两难境地。金融政策的调整既要保证老百姓不会因为物价的快速上涨而怨声载道,又要避免老百姓因找不到工作或失业而痛苦不堪。所以说,对一国政府与中央银行而言,如何让通货膨胀与失业的跷跷板在一个能够接受的范围内达到平衡,是一个十分艰难的抉择。小贴士根据经济学家的研究,一国的失业率与通货膨胀率常常存在着此消彼长的替代关系。这种现象使得政府在制定相应的宏观经济政策时常常面临着两难的抉择,往往只能就重避轻,而难以全部满足。

来自全球化的挑战——输入型通货膨胀与汇率变动的压力

  • 当今世界市场的发展使得各国的联系更为紧密,因此单单考虑本国的情况显然难以完全把握经济的命脉。因此,政府在进行相应的金融政策调整时,还要考虑外部因素,也就是考虑国际市场带来的压力。而这种压力也来自两个相对应的方面,或者说,来自汇率的两种调整方式的压力。一种方式是实行固定汇率制,也就是保持汇率稳定。保持汇率稳定可以预防经济的波动,但是相应地,由于本国货币的升值或贬值压力,一国的中央银行必须动用其外汇储备进行汇率干预以保持汇率的稳定,这就对一国的外汇储备状况提出了很高的要求。如果一个国家的货币有贬值压力,那么中央银行就需要不断地用本国的外汇储备换回本国货币以保持市场上对本国货币的需求,从而稳定本国汇率。这样做对本国的外汇储备是极大的消耗。而另一方面,如果本国货币有升值压力,那么中央银行必须不断地在国际市场中供给本国货币以换回外汇储备,这样做的话会使本国的外汇储备不断膨胀,而当外汇储备贬值时,就会造成本国外汇储备的巨大损失。另一种方式是实行浮动汇率制,是指政府允许市场作为调节汇率的手段,允许汇率进行波动。这样,政府就可以不动用外汇储备对汇率市场进行干预,使汇率可以较为自由地变动。然而,在这种方式下,汇率的波动会对经济产生很大的影响。如果本国货币升值,那么会使本国的相关出口行业面临着巨大的汇率压力,因为出口企业在国内用本国货币购买原材料、雇佣劳动力,而出口到国外的产品却是用外币进行结算,本国货币升值会造成产品的成本上升,对利润产生冲击。如果本国货币贬值,那么会引发进口产品的价格上升,进而形成输入型通货膨胀。也就是说,我们购买同样的国外原材料或产品时,需要付出更多的本国货币,就如同通货膨胀一样,同样的产品需要更多的钱才能买到,表现为进口原材料与商品的价格普遍提高,对于对外依赖度很高的国家,这将会严重干扰该国的经济生产过程。因此,无论采用哪种汇率制度,汇率的波动或者汇率的变化压力都会对经济造成一定的冲击。而对汇率的调节机构来说,制定相应的汇率政策是一个取舍的过程。汇率政策对一国汇率的影响可以分为三种,即有计划的升值、保持汇率不变以及有计划的贬值。有计划的升值可以防止输入型通货膨胀,使本国的进口商品、原材料价格趋于稳定,进而使一般商品价格不至于出现较大的波动,但是,这是以牺牲出口行业的利益为代价的。而有计划的贬值则恰恰相反,一国政府为了推动出口企业的快速发展,通过实施一定程度的货币贬值手段对外贸行业进行刺激,使外贸企业由于能够更为便宜的使用国内资源而获得一定的成本优势。但是,贬值手段所带来的可能后果是输入型通货膨胀的压力,会对本国的一般性生产行业造成一定的冲击,同时也会影响本国的一般商品价格。保持汇率稳定不变则如前文所述,会促使中央银行动用大量的外汇储备进行干预,这样会对一国的外汇储备造成一定的压力。

对魔法的质疑——金融政策是否是万能的

  • 金融政策在实行中往往存在着滞后性与信息不完全的问题。
  • 金融政策的调整不是立竿见影的,而是有一定的滞后性。这就对金融政策的制定者提出了较高的要求,需要他们对未来的趋势有明确的判断,不能为了解决短期的问题而影响长期的发展。
  • 经济的波动往往呈现出复苏—繁荣—衰退—萧条—再复苏的周期性特征,这是因为,在繁荣阶段,企业往往增加投资并且成本较高,形成泡沫,在随后的衰退期中很可能会遭受损失。如果金融政策的制定者能够把握这种经济周期规律,合理运用金融政策,那么就可以获得良好的效果
  • 逆周期政策就是一种金融调控的合理手段。简单地说,逆周期政策就如同开车一般,在经济向着繁荣方向发展时就踩下经济的刹车,而在经济向着衰退方向发展时就踩下经济的油门。具体而言,当经济进入复苏阶段,企业重新开始走上扩张道路时,政府不应继续通过宽松的金融政策对其进行刺激,而是应该适当紧缩货币与信贷。这样,由于金融政策有滞后性,当经济走向高涨时期时,货币与信贷政策正好可以发挥作用,避免经济过热。而当经济增长比较平缓但并未出现下滑趋势时,政府就应对其进行刺激。逆周期政策可以避免由于滞后性造成的调控不当的问题,是金融政策制定中一个比较好的调控思路。

扩展

美国国债

  • 中国为什么要购买美国国债?https://www.zhihu.com/question/23117022
  • 根本原因是中国是实行外汇管制和人民币汇率控制。也就是说钞票进出中国都要经过一个机构,更换对应的货币,这个机构叫央行。
  • 中国是世界工厂,生产很多东西卖到全世界;同时中国的经济发展和巨大的市场规模,很多外国公司到中国投资。所以中国现在每年的贸易都是顺差(进来的钱多,出去的钱少),这样一年一年累计,中国银行就聚集了一大笔美元。

买美国国债

  1. 钱太多,花不掉。
    中国的外汇也不全是买美国国债,也有部分是做其他的,比如买黄金,投资等。但是这么一笔巨额的资金,只做这些是消化不了的。
  2. 选来选去,只有买美国国债比较合适:1,有保障(美国后台,安全),2,用的掉(美国国债发行量大),3,回报率尚可。
  3. 购买美国国债是稳赚不赔的吗?
    不是,比如美元贬值可能导致缩水
  4. 那么大笔的重要的资金,安全是第一位的;回报率是第二位的

中国的外汇储备并不都是中国人赚的

  • 中国的外汇储备并不全部都是中国人自己赚的,里面有一部分,而且是很大一部分,是外国投资引来的外汇,这笔美元只是暂存在央行,其所有权并不归属于中国。中国每年的巨额出口顺差,赚的钱也并不全是中国的,相当大一部分,都是属于外国资方的,但是我们不能因此排斥外国资方,相反还要拼命引资,没有这些外国投资,中国不可能发展到今天这个高度。
  • 所以,为了在外汇储备激增的前提下尽可能的保护中国的利益,买美国国债,是保护中国外汇储备最佳的选择。

《半小时漫画经济学》-笔记

发表于 2020-11-02

一、开篇:我们为什么要读经济学

  • 历史上没有一场仗是因为爱和正义打起来的,所有的流血背后,深层次的原因都是经济
  • 如果我们想搞明白人类是怎么活成这样的、世界是怎么运转起来的,就要先搞明白这个问题:经济是怎么来的?一、经济起源首先,经济是什么?简单地讲,就是资源最优化配置。比如一瓶水,只有到口渴的人手里,价值才能发挥到最大。
  • 这些余粮,就是大家的财产,我们也可以将之理解成经济学里的资源。
  • 农业是骨架,工业填充了血肉,其他行业是各种器官,它们一起组成了经济这副躯体。

二、货币的起源:钱打哪儿来的

  • 用金银当货币,就叫——金银本位。用羊当货币就是羊本位,用贝壳当货币就是贝本位,以此类推。
  • 经济越来越好,商品就越来越多,这就需要更多的钱去买。于是有了比金银更方便带、更方便造的纸币。到后来,人们觉得纸币也麻烦,于是有了方便付款的电子货币。
  • 货币随着经济增长一直在变多!
  • 钱源于债务!因为人类学家发现了很多证据,研究了半天,得出个结论:这种物物交换的社会是不存在的!
  • 二狗子拿着鸡要换三胖子的铁锤,三胖子虽然不想要鸡,但他还是把铁锤给了二狗子,大家都熟,打个欠条就行。比如欠条到了老四手里,就相当于二狗子欠老四债。后来,大家也都这么做,用着用着顺手了,欠条就在村里流通了起来,谁还在乎它原先是谁的?为什么大家相信这张欠条呢?原来,二狗子人品好,财产多,不管谁拿着欠条来换铁锤,他都给换。这种有借有还的品质,就是传说中的——信用。人们交换、欠债什么的,其实玩的都是信用,只要有人相信,贝壳、石头、金银,甚至纸,都能当钱花。这就是信用货币理论的一部分。

三、那些年缴过的“五险一金”到底是什么

  • 政府、工厂、工人各拿出点钱来存在一起,哪个工人出意外了,就拿钱出来,让他能维持生活,大概是这样:工人们没后顾之忧了,干起活儿来更带劲,德国经济一路雄起。现代社保制度从此诞生了!
  • 如果社保不够年限,那恐怕享受不到福利了。你自己缴的会退给你,不过公司帮你缴的钱,只能和它们说再见了。

四、养老金能养老吗

  • 计算养老金时,是以当时的社会平均工资为基础的,所以不用担心钱贬值的问题。
  • 真实情况是,人口正在老龄化,上班族越来越少,退休的大爷大妈越来越多。[插图]这样下去,总有一天,社保基金入不敷出,到时候嘛。。

五、医保到底保了个啥

六、一口气搞懂到底要缴哪些税

  • 从收入里拿出来的这部分钱就叫个人所得税。正确叫法应该是免征额
  • 在商品流通过程中缴的税,就叫流转税。
  • 无论占有哪种财富,都有可能要缴税,这叫财产和资源税。
  • 税很多事和特定行为有关,把它们打包一下收的税,就叫行为税

七、买房前必须知道的二三事

  • 宅基地所有权归集体,使用权归农民,不存在什么年限,住到宇宙爆炸都没问题。但宅基地的房子有个问题,没房产证,没产权,没土地使用证,俗称小产权房。这种房子自己住没问题,但要想卖给外村人或者城里人,就难喽!
  • 一般买这些地的,都是房地产公司,交的钱就叫土地出让金,但使用权是有期限的,比如70年,相当于租地。房地产公司怎么赚钱呢?很简单,盖楼卖给消费者,这就是我们熟悉的商品房。产权时间是从开发商拿地开始算的,以70年产权为例,实际你买的房子的产权到期时间可能要比70年短。
  • 国家收回土地土地产权到期了,如果国家想收回来用,那你的房子就要拆迁了,你的人生可能就不一样了。

九、借钱那些事儿:如何避开借贷中的那些坑

  • 个人、公司和政府,三者之间的不同组合,就对应了我们身边不同的金融产品。
  • 按传统经济学家的说法,人是理性的,大家都只会把钱借给靠谱的人,也只会借自己有能力还的钱。次贷危机首先得弄清楚什么叫次贷:[插图]一边美国银行敢给,另一边矮矬穷真敢借,也不想想自己能不能还上
  • 法律就说了,民间借贷的利率高过36%属于高利贷,是非法的。

十、消费心理学:消费者防剁手指南

  • 大家购物时,会先找差不多的货比一比,一般不会选高价和低价,而是选中间价。这就是托奥斯基的“价格锚点”理论。
  • 特沃斯基和卡尼曼的“损失规避”理论。简单来说就是比起得到,大家更怕失去。所以就算是一件好东西,一旦发现会给自己带来损失,很多人就宁愿不要。

十一、传销大起底:珍爱生命,远离传销

  • 整个过程都没有真正的产品,赚钱全靠拆东墙补西墙。这个简单的骗术,就是金融界大名鼎鼎的庞氏骗局。这个骗局的死穴就在于:需要不断有新钱注入,一旦没有蠢萌新人进来,大家全都要玩儿完。四个字形容它的特点,那就是:一旦停药,骗子就会赶紧拿钱跑路,大家的投资款就打水漂了。
  • 如果有一天,再也发展不到下线了,整个系统就崩溃了。也就是说,传销的本质就是:不要产品,只要人。

一、开篇:我们为什么要了解金融危机

  • 经济有个定义:价值的创造、转化和实现。简单地说,经济就是资源配置,但是人类总是在这条路上跑偏。
  • 博大精深的中华文化告诉我们,把“危机”拆开来看,就是:“危险”和“机会。
  • 二战之所以能打起来,部分原因是美国的国内危机,演变成了全球危机。
  • 金融危机不是绝对的坏事,也可能是社会的崩溃疗法

二、金融危机就要来了:明斯基时刻

  • 人类社会的经济是有规律的,每次繁荣后面,都跟着一波波衰退。人们把这种周期性经济衰退的现象叫:经济危机。而经济的衰退常常是由于金融危机导致的。
  • 经济是有周期的,金融危机经常发生,原因可能是金融不稳定。经济周期有大有小,很多小周期构成了一个大周期,明斯基时刻说的是大周期的转折点。
  • 借的这部分钱,就叫杠杆。借钱用来扩充资产,就是加杠杆。杠杆让你赚得多,但赔起来也多。
  • 杠杆越来越高,最后还不起了,可能就会引发危机。所以该咋做知道了吧?去杠杆啊!简单说就是还钱,一般政府都会做这些事:方法1:债务违约重组钱暂时还不上是吧?银行为了把钱要回来,只好想点别的办法,比如少还点,延长还款期限。方法2:财富再分配给土豪加税,把收来的钱用来发福利、创造就业岗位,这样失业的人就慢慢有了钱,就能把欠的还上。方法3:货币调节为了解决问题,政府会和中央银行商量,让中央银行多印一些钱,然后花到了大家身上。

三、一朵花酿成的惨案:郁金香泡沫(上)

  • 郁金香泡沫,堪称金融危机鼻祖。
  • 赚了钱就按比例分红,赔了钱就只承担有限责任,这就是世界上第一家股份有限公司

四、一朵花酿成的惨案:郁金香泡沫(下)

  • 合同到期了,就能拿到郁金香,反正不会亏,大家都乐意。这种玩法就是期货的雏形。荷兰人全国总动员炒郁金香,船不造了,生意黄了,钱投进去连个响都没有,经济急剧下滑。
  • 郁金香泡沫过去了,后来又有南海泡沫、密西西比泡沫,甚至次贷危机。人们好像从来不会吸取教训,直到现在咱们身边也还在发生这种事:所以看完这个故事,对咱们还是很有用的,起码你再看到有些东西很贵,但明显不值那个价,人们却一直往里砸钱投资时,就应该知道那很可能就是金融泡沫,要保持冷静。价格虚高的东西别碰,要投就投有价值的,这叫理性。

五、连牛顿也算不出的疯狂:南海泡沫

  • 对南海公司来说,“韭菜”就是一茬茬的钱,问题是怎么收割呢?南海贸易特许权本来就是个金字招牌,再加上政府的担保,就能让不明真相的老百姓信得不要不要的。“韭菜们”都看好南海公司,纷纷买入他们的股票。
  • 上到国王,下到家庭主妇,全在炒股,却不管不问公司经营啥情况,这就很容易——出大事啊!

六、股票连涨13个月会怎么样:密西西比泡沫

  • 他首先要做的不是跑业务,而是开始发股票,而且能用国债来换股票。大家都很看好公司,因此股票老值钱了。现在能用国债换,简直是睡觉都能笑醒!这样一换,国债被抵销,国家就不用还老百姓钱啦!法国经济繁荣靠股市,但股市其实很虚,因此所谓的经济繁荣就是个泡沫啊!最后路易十六被送上断头台,这就是著名的法国大革命。

七、房价是怎么被炒起来的:1837年美国大恐慌

  • 1837年美国大恐慌从本质上说就是一个美国人瞎炒房的故事。简单来说,贷款变难了,这叫信贷紧缩,缩得太紧,导致市场上没钱,美国经济就容易不景气。总结一下,美国跟英国房东打了两架,欠了外债,为了还钱开银行,薅了老百姓的黄金,最后把自己的经济搞坏了。
  • 很快国内经济一落千丈,纸币没人信,黄金又没有,股票暴跌,很多行业破产,大家都找不到工作。这就是美国第一次萧条,史称美国大恐慌。
  • 1848年,恐慌了11年的美国在加州的一个地方,发现了大金矿。本来缺黄金,一下子却变成了矿主,美国突然不慌了,这就是历史上著名的淘金热。
  • 简单来说,美国大恐慌其实就是一个炒房的故事,房子是用来住的,不是用来炒的,可惜美国人并不懂这个道理。

八、美联储的诞生:1907年美国银行危机

  • 南方要搞农业,北方要搞工业,一言不合就开撕,这就是咱们熟悉的南北战争。
  • 他来自传说中的摩根家族,据说当时身家达13亿美元。于是摩根撸起袖子,着手拯救美国:他先找了几家银行,成立了一个由富翁们组成,专门给发愁的信托和银行送钱的联盟——摩根干的其实是中央银行的活,所以美国觉得需要一家中央银行来统一管钱。成立新机构要取个新名字,叫啥好呢?美国老百姓已经听到银行就害怕了,所以不能有银行两个字。那就叫美国联邦储备局吧

九、实现美国梦的三大法宝:泰罗制、福特制和猪

十、1929—1933年美国大萧条

  • 自由放任主义。结果小公司一个个倒下,而强势的公司却越做越大,逐渐成了江湖的不败神话:垄断的意思大家都知道吧:一家公司赶走竞争者,占领市场一家独大。但垄断常常会产生不好的结果,少数人资源独占,而多数人吃糠咽菜,贫富差距很快就被拉大了。
  • 当时的资本家一心想赚钱,玩命搞生产,可问题是:多数人穷得连饭都吃不起,他们能买得起啥?所以当时的社会背景就是:资本家们一瞅,一个个的都不消费,多影响自己赚钱国家经济啊!
  • 消费力虚了,农业也跟着倒了血霉,农产品卖不出去,只能销毁。很多农民宁可把牛奶倒进河里,也不降价或者送给穷人。为什么呢?因为一旦牛奶降价,其他货为了卖出去,只能被迫降价,这样会导致卖家利润更低。另外,想卖货得先砸钱,比如投入运费,货倒了反而损失小。
  • 经济危机最严重的问题就是失业。
  • 由生产过剩引发的经济危机。

十一、拯救美国的两位“救市主”:胡佛和罗斯福

  • 胡佛是一个坚定的自由放任主义者,这种说法其实不太对,因为他在经济危机之后一直在积极干预经济。

  • 新上任的总统就是罗斯福。罗大爷觉得,政府应该对市场插手,不能只当看大门的保安,想解决危机,就要做连你家垃圾都要管的——这套想法其实就是凯恩斯主义,当然里面的内容还有很多,这里简单理解一下就好。既然胡佛搞砸了,那这一套还好使吗?咱看看罗斯福怎么做的就知道了。
    罗斯福放了个大招——废除金本位。金本位制度规定美元和黄金挂钩,两者价值要对等,不能瞎印美元。现在脱钩了,政府就能多印点美元给银行,而且美元一多就贬值。对其他国家来讲,美国商品便宜了,秒变抢手货。

  • 政府搞基建招工,然后给工人们发工资。

  • 胡佛和罗斯福的很多政策其实差不多,都整顿了银行,都不让降工资,但为啥一个失败而另一个却成功了呢?这里就要说一下经济周期了。啥叫经济周期?简单说就是:下滑期,再厉害也没用;上升期,躺赢不是梦。

重构-改善既有代码的设计-摘要

发表于 2020-11-01







《重新定义团队:谷歌如何工作》-笔记

发表于 2020-10-24

自序

  • 韦尔奇和康纳迪采用了20-70-10的绩效排名体系,在这种体系下,他们将通用电气的员工分为三类:最优秀的20%,中间层的70%,末尾的10%。最优秀的员工得到赞扬,作为奖励可以选择工作任务,参加领导力培训项目和享有优先认股权。末尾的10%会遭到解雇。

  • 清晰明了的“前20%”“中间70%”和“末尾10%”被更加委婉的描述方式取代:“顶尖人才”“极具价值”和“需要改进”。

  • 因为自由的状态是以自由表达为基础的,而自由表达又依靠对信息和真实情况的了解。

  • 在这里有时会精疲力竭,有时会倍感沮丧,但永远奋发进取,创造有目的性的、自由的和充满创造力的环境。

前言 为什么谷歌的原则也对你适用

  • 当员工信任领导层的时候,他们就会成为品牌的代言人,从而为其家庭、所处的群体和环境带来积极的改变。员工生产效率变高,企业发展增速,顾客购买热情高涨,商业投资回报也就自然而然地实现了。
  • 从管理的核心角度来讲,权力的动态方向恰与自由背道而驰。员工要依靠管理者,希望取悦他们。然而,注重取悦管理者意味着与其进行开诚布公的探讨是有风险的。如果你不取悦他,内心就可能惶恐不安或焦躁愤恨。同时他还要保证你实现某些工作成果。
  • 谷歌应对此类问题的方法是割开这个结。我们刻意剥夺了管理者对员工的控制权。下面一些例子是谷歌的管理者不能单方面做出的一些决定:•雇用谁•解雇谁•如何评估一个人的表现•给某个人加薪多少,给多少分红或分配多少股权•选谁来拿最佳管理奖•给谁升职•代码何时才算合格,可以纳入到公司的软件代码库中•一种产品的最终设计以及何时投放市场。
    上述决定都是由一组同事、一个委员会或一个特别任命的独立团队做出。
  • 升职的问题又摆到了眼前,这时他们又会惊愕地发现自己没有权力独自决定给他们认为团队里最优秀的员工升职。问题在于,你和我对“最优秀的员工”的认识有所不同。也有可能你的团队中最差的成员比我的团队最优秀的员工还要好,这种情况下,你的整个团队都应该升职,而我的团队成员都不应该升职。
  • 一名管理者到底该做些什么呢?只有一件事情可以做。按照我们的执行总裁埃里克·施密特的话说就是“管理者服务于团队”。和其他企业一样,我们当然也遭遇过意外和失败,但是在谷歌这种不干预的领导方式下,管理者的关注重点不是惩罚或奖励,而是清除路障,鼓励团队。
  • 只有公司采用了给员工充分授权的经营方式(比如,剥夺管理者的决定权,并将该权力分配给一些个体或团队),为员工提供工作之外的学习机会,提高团队信任度(给团队足够的自主权,允许员工自行组队),或是组合利用上述方法,这样业绩才能得到提升。
    简而言之,只有当企业着手给员工更多的自由时,业绩才能提升。
  • 我们所做的绝大多数事情的费用都极低。即便是只拿死工资的时候,也能把工作做得更好,使员工更有幸福感。其实,越是在经济状况不好的时候,善待员工越是重要。
  • 我们只需要坚信员工都是好的,再就是要有足够的勇气,把员工看成是企业的主人翁,而不是把他们当成机器。机器会完成工作;主人翁会竭尽所能帮助企业和团队获得成功。人的一生大部分时间都在工作,但是对多数人而言,工作是一件痛苦的事情,只是一种谋生的手段。可以不必如此的。
  • 如何更好地探寻和发展自由、富有创造力和宽松的环境,使员工在这种环境下工作。

第一章 成为一名创始人

  • 他们都希望创造出这样一家公司:工作有意义,员工可以尽情发挥自己的激情,他们和他们的家人都得到关怀。

  • 建立杰出的团队或机构的起点是有一位创始人。但是成为一名创始人并不意味着要建立一家新的公司。任何人都有能力成为一名创始人,也可以成为所在团队的文化创造者,不管你是一家公司的第一名雇员还是一家数十年历史的公司中的一员。

  • 我写作本书的愿望之一就是,希望阅读本书的人都能站在创始人的角度看待自己。或许不是一家公司的创始人,但是也可以成为一个团队、一个家庭或一种文化的创始人。谷歌的经历带来的最根本的一点经验就是你必须先决定自己想要成为一名创始人还是一名雇员。这个问题关乎的不是实际的所有权而是做事的态度。

  • 谷歌工作法则:成为一名创始人□把自己看成是一名创始人□像创始人一样行动

第二章 “文化可以把战略当早餐一样吃掉”

  • 如果你给员工以自由,他们将还你以惊喜

  • 公司文化的三个根本元素:使命、透明和发声的权利。

  • 我们的使命是“整合全球信息,使人人都能访问并从中受益”

  • 谷歌的使命与众不同,既在于其简洁明了,也在于其未曾言及的方面。没有言及利润或市场。没有言及顾客、股东或用户。没有言及为何选此作为公司使命,也未曾言及如何实现这些目标。相反,谷歌的使命整合全球信息,使人人都能访问并从中受益是一件不言自明的好事。这样的使命使个人的工作有了意义,因为它不是一种商业目标而是一种道德目标。史上最有影响力的运动都要有道德动机,或是追求独立,或是追求平等权利。

  • 归根结底,我们永远也无法达成我们的使命,因为总有更多的信息需要集成,总有更多的方式可以使人们从中受益。这样就给我们创造了动机,促使我们不断创新,探索新的领域。要成为“市场领导者”这种公司使命,一旦实现,就难以再带来更多的激励。

  • 我们在践行公司使命时,也带来了令人惊喜的实用价值。

  • 每一类职业的人里面都大约有三分之一将自己的工作看作一种使命。这样做的人不仅更快乐,而且也更健康。

  • 如果你相信员工,就不必害怕与他们分享信息透明是我们公司文化的第二块基石。“默认开放”(Default to open)是在开源社区中时常会听到的一个短语。

  • 如果你能给员工以自由,他们就会为你创造惊喜。他们有时也会令你失望,但是我们也都知道人无完人。这并非宣扬自由的檄文,只不过是权衡利弊后的选择。

  • 谷歌工作法则:打造了不起的文化□将工作看作是一种命运的召唤,而且工作要有富于意义的使命。□给人以稍多于你的舒适区的信任、自由和自主权。如果你没有感到紧张,那是因为你给的还不够。

第三章 只聘用比你更优秀的人

  • 如果我们前期选人的时候能做到更好,也就意味着聘用他们之后在这些人身上投入的精力就会减少。聘用水平超过90%应聘者的员工,最糟的情况他们也能有平均水平的表现。这些员工几乎不可能成为公司里表现最差的

  • 你如何能够判断自己到底有没有找到一名非凡的人才?我所遵循的首要原则——也是你在招聘时需要做出的第二个改变——是:“只聘用比你更优秀的人。”我所聘用的人都在某些特定的方面比我更优秀的。

  • 从《人才的谬见》一文中得到的教训不是“不要聘用聪明人”。而是“不要只聘用聪明人”。至理名言。出色的招聘工作不仅在于聘请到名头很大的人、顶尖的销售人员或最聪明的工程师,而且在于搜寻到在你所处组织的环境下能够成功的最优人才,在于找到能使周围每个人都更加成功的人才。

  • 谷歌工作法则:关于招聘□资源有限的情况下,将人力资源费用首先投入到招聘上。□慢慢来,聘用最优秀的人才,只聘用在某些特定的方面比你更优秀的人,不要让经理独自做团队人员聘用决策。

第四章 搜寻最优人才

  • 最优秀的人并不在寻找工作。表现极为优秀的人在现在的工作岗位上很开心,满足感很强。他们不会进入人们的推荐人名单中,因为人们会想为什么要推荐一些在现在岗位上很开心的人呢?而且他们肯定也不会考虑新的工作。

  • 谷歌工作法则:搜寻非凡的应聘者?□要详细说明寻找人才的标准,依此找到最优秀的被推荐人□使招聘成为每个人的工作□不要害怕尝试疯狂的事情,以此引起最优秀人才的注意

第五章 不要相信你的直觉

  • 根据头10秒钟的印象做出的预测是没有任何意义的。这头10秒钟的预测使我们在整个面试过程中都在试图证明我们对某个人的印象,而不是真正地去评估他们。心理学家将这种现象称作证实偏见(Confirmation Bias),“倾向于寻找、解释或优先考虑那些能够支持我们观点或假设的信息”。

  • 多数的面试都是在浪费时间,因为99.4%的时间都用在证实面试官最初10秒钟的印象,不论印象好坏。“请做一下自我介绍。”“你最大的缺点是什么?”“你最大的优势是什么?”毫无价值。

  • 你不仅要评估应聘者,还需要让他们喜欢上你。真的。你得让他们有一次非常棒的体验,处理好他们关心的问题,使他们感觉刚刚经历过一生中最快乐的一天。

  • 谷歌工作法则:筛选新雇员?□设定高质量标准□寻找自己的应聘者□客观评估应聘者□给应聘者一个加入的理由

第六章 打造最幸福的公司

  • 权力导致腐败,绝对的权力导致绝对的腐败

  • 如果要做假设,也应该认为掌握权力的人是恶的,权力越大,恶念越深……伟人多数是恶人,即便他们不滥用权力,而只是施加影响力;如果你再考虑到权力带来腐败的可能性或必然性,他们的恶会更甚。

  • 回想一下你参加过的会议。我敢打赌,级别最高的那个人总是坐在会议桌的上首。是因为他们匆匆地从一间办公室冲到另一间办公室,抢先来到会议室,才占到这个最好的位置吗?下次仔细观察一下。随着参会者陆续到场,他们会刻意将上首的座位空着。此种现象证明了我们一些不自觉的微妙举动都创造了等级制度。没有指示,没有讨论,甚至没有有意识的思考,我们就会为“上级”留出位置。

  • 我们最高层的一些领导对这种现象也非常熟悉,并尝试打破这种状态,选择坐到会议桌某一侧的中间。

  • 经理都倾向于累积和运用权力。员工都倾向于服从命令。

  • 不可否认的是,我们很多人都同时扮演着经理和员工两个角色。我们都遇到过控制欲很强的经理,也都遇到过不服从管理的员工,这样的挫败感我们每个人都曾有过。

  • 授权于群众的第一步就是要保证人们能够安全地发表意见。俗话说“枪打出头鸟”,就是警示人不要随便发表评论。正是因为这个原因我们才尽可能削弱经理的权力。他们拥有的正式授权越少,就越难利用萝卜加大棒的政策辖制团队,这个团队的创新范围便会越广。

  • 为了减轻人类内在寻求等级划分的倾向,我们尝试除去显示权力和地位的象征符号。

  • 谷歌坚持的核心准则中一直都有一条“不要耍政治手腕。用数据说话”

  • 经理们忽略的是,每次他们放弃一些控制权,就可以为团队创造一次提升的好机会,也给自己节省出更多时间应对新的挑战。找出某个令你的团队感到沮丧的领域,让他们改变现状。如果有限制,比如时间或资金有限制,就告诉他们。要对员工透明,在塑造团队或公司的过程中给他们发言权。你会惊讶于他们的成就。

  • 谷歌工作法则:授权于员工□消除地位象征□依靠数据而不是根据经理的想法做决定□探寻方法,让员工塑造自己的工作和公司□高期待

第七章 为什么每个人都讨厌绩效管理

  • 关注个人成长而不是评分和奖励,以此改善绩效

  • OKRs(Objectives and Key results,目标和主要结果)。目标必须具体、可度量、可检验;如果你达成所有结果,就能完成目标。

  • 正如普拉萨德·塞迪解释的:“传统的绩效管理体系犯了一个大错。他们将两件应该彻底分开的事情合到了一起:绩效评估和人员发展。评估有其必要性,可确定加薪或奖金等有限资源的分配。发展也同样很有必要性,可以促进员工的成长与提高。”如果你希望员工成长,不要同时进行这两项谈话。确保发展成为你与团队成员之间不断往返的一个过程,而不是年底的一次惊喜。

  • 为了确保员工与经理的交谈更有效,我们整理出一份一页的讲义,分发给他们,在绩效交谈的时候使用。做这一份讲义的目的还是为了使对话更具体,更切合实际。我们给员工分发这些讲义只是为了稳妥;我们希望经理能够覆盖恰当的话题,但是让员工准备好引导讨论也没有什么坏处。

  • 把奖励分配谈话与员工发展谈话分开。两项谈话混为一谈会扼杀学习的动力。不管公司规模多大,这一点都适用。

  • 谷歌工作法则:绩效管理□正确地设定目标□收集同事的反馈意见□通过校准流程确定考评结果□把奖励分配谈话与员工发展谈话分开

第八章 管理团队的两端——最优员工和最差员工

  • 从统计学上讲,这些现象更适合用“幂律分布”(power law distribution)解释。

  • 大多数公司在管理员工时都采用正态分布,大多数员工被列为平均水平,两端为表现差和表现优秀的员工。两端并不像身高分布那样对称,因为失败的员工都被解雇了,最差的应聘者根本就进不了公司,因此左侧的一段很短。但是很多公司认为员工的表现还会符合同样的正态分布。这样的认识是一个错误。事实上,组织中大多数的个人表现符合幂律分布。

  • 并非大批平均水平的员工通过数量优势做出主要贡献,而是由少数精英员工通过强大的表现做出主要贡献。

  • 采用一种不同的方式:我们的目标在于告诉底端5%的每一位员工,他们处于这样一个群体。这种对话不会是幽默风趣的。但是我们向这些员工传递出的信息使这项工作简单了一些:“你在整个谷歌处于底端的5%。我知道这样的感觉不好。我之所以要告诉你是因为我想要帮助你成长,变得更好。”换言之,这不是一次“要么好好干,要么走人”的谈话;这是一次感性的谈话,目的是帮助一个人发展。有一位同事曾经将其形容为“富有同情心的实用主义”。绩效表现糟糕极少是因为某人的能力不足或品性不佳。更多的是由于技能的缺陷(或许可以改进,或许不能)或意愿不足(员工没有做工作的动力)。在后一种情况下,可能是由于个人问题,也可能预示着团队中出现了某个更大的问题需要修正。

  • 通常,调岗之后这个人的绩效能够提升到平均水平。这听起来或许不算什么,但是反过来这样想想:100个人的团队中,吉姆是表现最差的5个人之一。经过这次干预之后,吉姆的绩效表现进入了前50位。

  • 余下的一些员工,有的选择主动离职,有的就只能解雇了。听起来很残酷,但是最后他们通常会更开心一些,因为我们表现出对他们状况的理解,并与他们一道投入了改进过程,而且我们给他们时间寻找一家能够发挥专长的公司。

  • 在分布底端投入时间精力的这个循环意味着你们的团队能够提升很多。员工或是得到大幅的提升,或是离职去别的地方寻找成功。

  • 对员工直接一些实际上是仁慈的表现

  • 我要强调一点,谷歌识别底端5%的员工并非“员工大排名”,不是要按照固定的分布将员工的绩效表现分类。那种考评方式下,员工为了不落在底端会激烈竞争,最终搅乱了公司文化。
    我采访过的每一位现在和过去的微软员工——每一位——都认为员工大排名是微软内部最有害的政策,在这种政策下,无数的员工被迫离职……“如果你的团队中有10个人,你开始工作的第一天就了解到,不管每一位员工多么优秀,都将有两个人获得好评,7个人获得中评,另外有一个人获得差评,”一位前微软软件开发工程师说,“这使员工的注意力都放在内部互相竞争上,而不是与其他公司竞争。”

  • 如果你相信员工本质都是好的,认为他们值得信任,那就必须对他们坦诚相待,保持透明度。这就包括让他们知道自己的绩效拖了后腿。但是在一家使命导向性、有目标的公司,处理人力问题时要有敏感性。多数表现不佳的员工能够认识自己的表现,想要变得更好。给他们改进的机会非常重要。

  • 顶端的员工生活在高产出、良好的反馈意见、更高的产出和更好的反馈意见这样一个良性循环中。他们每天都沐浴在爱的环境中,给他安排的额外工作也使他更加开心。更重要的是要从最优秀的员工身上学习。

  • 最优秀经理手下工作的谷歌人在十几项Googlegeist评估维度上要比最差经理手下工作的谷歌人高5%至18%。除此之外,他们在以下几方面的认可度明显更高:•职业决策更加公正。绩效评估公正,得到升职的都是实至名归的人选。•个人的职业目标能够达成,他们的经理是非常有帮助的支持者和引导者。•工作高效,决策迅速,资源分配合理,从多种视角考虑问题。•团队成员之间没有等级制度,互相尊重,决策依据数据做出而不是靠耍手段,团队内部各人的工作和信念都保持透明。•他们适当地参与到决策制定过程中,并且得到一定的授权去完成工作。•他们可以自由地平衡工作和私人生活。

  • 最优秀经理领导的团队绩效表现也更好,人员流动率更低。

  • 调查显示高分经理具备八种低分经理所不具备的共性:8个氧气项目特性1. 做一名好的导师。2. 给团队授权,不随便插手下属工作。3. 表达出对团队成员的成功和个人幸福的兴趣和关心。4. 高效/结果导向型。5. 善于沟通——聆听和分享信息。6. 在职业发展方面助力团队。7. 对团队有清晰的愿景和战略。8. 具备重要的技术技能,可为团队提供建议。

  • 我们发现在伟大的经理中间,技术专业性是8种特性里重要性最低的一项。不要误会,技术专业性非常关键。一名不会编代码的经理不可能在谷歌领导一个团队。但是在区分最优秀的经理行为时,技术能力在不同团队中是差异最小的。

  • 这是对经理–员工关系的一次华丽反转。想要提高,最好的方法是与那些提供反馈意见的员工进行交谈,询问他们希望自己做出哪些改变。

  • 让处于绩效分布底端的人了解真相,但是不要将绩效与薪酬或职业成果直接挂钩,尽可能用一种积极的方式警示并激励他们。数百名经理需要面对自己并非好经理的现实。

  • 谷歌工作法则:管理团队的两端—最优员工和最差员工?□助力有难处的员工□将最优秀的人放在显微镜下观察□利用调查和检查清单寻找真相,推动员工学习□与人分享员工对你的反馈意见,以身作则采取行动解决问题,身先示范

第九章 打造学习型组织

  • 在某一领域精熟的人,不管是小提琴家、外科医生、运动员[插图]还是拼字比赛冠军[插图],学习的方法都有异于常人。他们将活动分解成细小的动作,比如连续数小时在雨中练习同一种击球动作,不断重复。每一次,他们都会观察效果,做微小的——几乎难以觉察的——调整,逐步改进。埃里克森将这种方式称作刻意练习:有意重复类似的小任务,即时反馈、修正和实验。

  • 不过或许你不想要手下最优秀的销售人员去教学。毕竟,不应该让她全心全意做销售吗?我认为这是一种短视的想法,因为个人的绩效表现的提升是线性的,而培训授课则会带来几何级数的增长。

  • 学习型组织发端于一种认识,即我们所有人都渴望成长,也都希望帮助他人成长。然而,在很多组织中却是员工受教,专业人士负责教学。为什么不让员工同时做两件事情?

  • 谷歌工作法则:打造学习型组织□进行刻意练习:将课程分成易于消化的小块,给出明晰的反馈意见,并不断重复这个过程□请最优秀的员工教学□只在已经证明能够改变员工行为的课程上进行投入

培训的效果衡量四个层次,一是反应,课后调查反馈课堂效果及氛围。二是学习,调查学员学到了那些知识,学习效果如何。三是形行为,接受学员学以致用的反馈,是否有提升,同时调查团队周边或客户的客观评价。四是结果,最终是否导致了效率提升,绩效比变好等等

第十章 不公平薪酬

  • 总结下来共4条原则:1. 不公平薪酬。2. 以成就为荣,不以报酬为荣。3. 创造易于传播爱的环境。4. 精心筹划却遭受失败的要奖励。

  • 如何庆祝成功的同时不滋生嫉妒

  • 这样做带来了不良的后果,假如你是非常优秀的员工,将会得到几次大幅加薪,之后加薪的速度会越来越慢,直到最后你接近容许的薪酬范围上限,加薪也会随之停止。最优秀的队员除了需要高报酬之外,也能持续创造优异的成果。

  • 对于那些快速学习成长和表现最顶尖的人来说,确保你的薪水与所创造的价值相适应有一种方法,就是离开这种垄断的内部市场,进入自由市场。即寻找一份新工作,以你的真正价值为基础,协商薪酬,然后离开现在的公司。这也是你在人才市场上看到的真实情况。

  • 为什么公司不设计一种体系,避免最优秀和潜力最大的员工辞职呢?因为他们对公平有一种错误认识,没有勇气坦诚面对自己的员工。薪酬的公平并不是说所有在同级别岗位上的人都要拿同样的薪水或是上下差不到20%。薪酬与贡献相匹配才能算得上公平。因此,个人的薪酬应该有巨大的差异

  • 正态分布(又称高斯分布)与幂律分布最大的区别在于,某些现象中,正态分布严重低估了极端事件发生的概率。

  • 个人的表现符合幂律分布。事实上,大多数员工都在平均水平以下:•66%的研究员发表论文的数量低于平均水平。•84%艾美奖提名演员获得提名数低于总提名平均数。•68%的美国参议院议员的任职届数要低于平均数。•71%的NBA球员得分低于平均分。低于平均数并非坏事。这只不过是一种数学统计而已。数据显示,非凡贡献者的表现水平要远高于大多数人,他们可以拉动平均数远高于中位数。

  • “10%的产出来自最顶尖1%的员工,26%的产出来自最顶尖5%的员工。”换言之,他们发现最顶尖1%员工的产出是平均产出的10倍,最顶尖5%的员工的产出是平均产出的4倍多。当然,这种算法并非在所有地方都适用。恰如奥博伊尔和阿吉斯所指出的:“工业和以体力劳动工作为主的组织,技术能力有限,对最低和最高产量有严格的标准。”在这些地方的员工表现更接近于正态分布。在这种环境下,极少有机会能做出非凡的成就。但除此种情况之外,幂律分布都占据主导。

  • 那些拿到100万美元奖励的人一定、一定是狂喜的吧?他们确实很开心。我的意思是说,拿到这样的奖励非常激动人心。人生就此改变。之后,我们最优秀的、最有创造力的、又有洞察力的技术人员中有一些(虽然不是全部)——他们曾创造出谷歌历史上最具有影响力的一些产品——意识到自己不太可能通过同样的产品两次获得创始人奖,因此立刻会想要转移到新的产品领域。虽然并非本意,但是我们创造出的这种激励体系,使公司里几乎所有人都不如以前开心,即使有少数人开心了,但也动了念头,不愿继续从事为他们赢得奖励的关键的创新性工作!

  • 我们公开地进行体验奖励,私下里进行差异化奖金和股权奖励。这样的结果使谷歌人变得比以前更开心。

  • 在奖励员工的时候,一定不能只用现金奖励,还要考虑体验奖励。很少有人回顾人生时会只看到一张张薪水单。他们会记住一些谈话、一些午餐,与同事和朋友共度的一些事件。不要用金钱庆祝,要用行动庆祝。

  • 谷歌工作法则:不公平薪酬□控制情感,做到不公平薪酬。薪酬差异化要明显,应符合绩效表现的幂律分布□以成就为荣,不以报酬为荣□创造易于传播爱的环境□精心筹划却遭受失败的要奖励

来源微信读书
分章读书总结:
1、在绩效方面,更多要参考幂律分布而不是传统的正态分布,平均数不等于中位数,实际上组织内大部分员工处于平均值之下,组织10%绩效产出来自1%的员工,出于人才保留和激励,不公平薪酬完全是合理且必要的;
2、人对于公正性的感知非常强,会极大影响他对于自身价值的认识、工作满意度、上级信任度和组织忠诚度,所以极端奖励体系要同时满足分配公正和程序公正;
3、在奖励员工时,不能只考虑现金奖励,还要考虑体验奖励;金钱激励是即时性的,所以在金钱激励时要辅以绩效谈话或颁奖等仪式性行为,以加深记忆,拉长激励的保质期,但总体来讲,用行动庆祝比用金钱庆祝更有持续记忆力;
4、要在组织内创造易于传播爱的环境,公开的赞许是最有效的一种管理工具,相信员工能做正确的事,结果通常他们会去做正确的事;
5、奖励成功也要奖励失败,否则员工会失去创造方面的冒险性;

第十一章 世上最好的东西是免费的

  • 我们做的几乎所有事情都是免费或费用很低的。所有这些项目都是为了提升效率,创造社区意识或创新精神。

  • 我不能给出数据证明有多少经济价值是因为免费洗衣机创造的,因为我根本就不在乎这些。我还记得职业生涯早期的麻烦经历,从我公寓到地下室的公用洗衣机要经过堆满杂物的楼道和摆满清洁用品的楼梯,而后要困在家里好几个小时,生怕别人来偷走我的衬衫。超级烦人。我们为什么不在园区找一间空房间,放上几台洗衣机和一些清洁剂,让生活稍微愉悦一些呢?我们为什么不请一些演讲者来园区给我们做演讲呢?

  • 我们进行过离职调查,从来没有任何人说这些服务能够使他们留下,也没有人因为这些服务才加入谷歌。这其中并没有什么大秘密:我们所做的这些事情(大多数)仅是举手之劳,但却收获巨大,而且我们感觉这样做是应该的。

  • 微小的关怀和资源投入也能带来巨大的成果。

  • 谷歌工作法则:效率、社区意识和创新精神?□使员工的生活容易一些□想办法说可以□生命中的不幸罕有发生……一旦员工遭遇不幸,要伸出援手

第十二章 助推

  • 诺贝奖获得者、普林斯顿大学荣誉退休教授丹尼尔·卡尼曼在他的《思考,快与慢》(Thinking, Fast and Slow)一书中描述人类有两套思维系统。其中一套慢、有深度、有思索、以数据为导向,而另外一套快、依靠本能、属于直觉思维系统。多数时候我们会依赖第二套思维系统,因此即便我们认为自己理性的时候,其实恐怕也并非如此。

  • 在面对对自我观念和自我认同的威胁时,防御是一种自然的反应。

  • 只需简单地提供信息,然后依靠人的本性——好胜的本性和利他主义的本性——就能改变一个机能失调的团队,看到这种现象真是既有趣,又令人振奋。

  • 谷歌工作法则:助推走向健康、富有和快乐□区分“实是”和“应是”的不同□进行许多小的实验□助推,不要硬推

第十三章 谷歌的教训

  • 任何想法走了极端都会变得愚蠢可笑。

  • 因此外界批评从原则上来讲是对的,但在实践中却并非如此。每年我们都要遭受一次重大的信息泄露。每一次都要进行一次调查,而且每一次信息泄露不管是刻意而为还是意外事故,不管是出于善意还是恶意,当事人都会被解雇。我们不会宣布泄露信息的人是谁,但是我们会让公司里的所有人都知道泄露的信息是什么,以及后果怎样。很多人了解到很多信息,总不可避免地有几个人会搞砸。但这样是值得的,因为泄露信息造成的损失相比我们享受的开放性而言并不算重大。

  • 一次失败的绩效管理变革。每一次我们对谷歌的绩效管理体系做出改变的时候,都会遭遇两个不证自明的真理:1. 没人喜欢当下的体系。2. 没人喜欢改变当下体系的提议。

  • 谷歌工作法则:搞砸的时候□承认错误。坦诚面对错误□吸取各个方面的意见□不管什么坏掉了,修好□找出错误中的寓意,加以传播

第十四章 从明天起你可以做些什么

  • 问题并非管理体系需要如何改变人性,而是如何改变工作的性质。

  • 一家组织的经营方式可以遵循两种极端的模型。本书的核心在于我的信念,相信你可以选择出期望打造何种类型的组织,而我所做的只是展示一些实现目标的工具。“低自由度”的一端是指挥控制型组织,对员工的管理很严格,工作强度大,公司对员工弃之如敝屣。“高自由度”的一端以自由为基础,员工受到尊重,对公司如何发展有一定的话语权。

  • 如果你希望建立高度自由的环境,下面有10个步骤可以帮助你的团队和组织实现转型。1. 赋予工作意义2. 相信员工3. 只聘用比你更优秀的人4. 不要将职业发展与管理绩效混为一谈5. 关注团队的两端—最优员工和最差员工6. 既要节俭又要慷慨7. 不公平薪酬8. 助推9. 管理日益提升的期望10. 享受!然后回到第1条,再来一遍

  1. 赋予工作意义工作至少占据了我们生活三分之一的时间和清醒时的一半时间。工作可以——也应该——不仅仅是一种达成结果的手段。非营利组织从很久以前就已将工作的意义作为吸引和激励员工的方法。比如,帮助难民的非营利组织避难通道(Asylum Access)的创始人艾米丽·阿诺德–费尔南德斯建立起一个世界一流的全球团队,这个团队的建立完全基于成员的共同愿景,即帮助难民找到工作,送他们的孩子上学,帮助他们在新的国家中建立起新的家园。在很多环境下,工作仅仅是为了得到薪水,但是亚当·格兰特的研究成果证明,只需与那些因你的工作而受益的人建立起微小的联系,便能大幅提升生产效率,而且还能使人更开心。所有的人都希望自己的工作有一定的目的。将工作与一种超越日常但却能真实反映所做事情的理念或价值观联系在一起。谷歌立志整合全球信息,使人人都能访问并从中受益。任何在这里工作的人都要践行这项使命,不管职位多么低微。这种使命吸引来了人才,激励他们留下来,去冒险,以最高水平的表现去工作。如果你是一名鲑鱼切片工,你就是在养育他人;如果你是一名管道工,你就是在改善人们的生活质量,保持他们家园的清洁和健康;如果你在生产线上工作,不管生产的产品是什么都将为人所用,帮助到他们。不管你在做什么,都会对某人有重要的意义。而你所做的这项工作对你也应有重要的意义。作为一名经理,你的工作就是帮助员工发现这种意义。
  2. 相信员工如果你相信人本善,就应如此行动。要对员工保持透明和真诚,给他们话语权,决定如何行事。从小事做起也可以。真的,你之前表现出的信任越少,小的举动就会令人感到越重大的意义。对于一家传统上一直进行不透明管理的公司而言,一个意见箱,员工知道其中的意见真正地有人读过且有人处理,会令人有革命性的感觉。请团队成员问你是什么促使你做出最近的一些决定的。如果你拥有的是一家小商店,要经常询问员工他们认为做出哪些改变能使商店更好,或者问他们如果这是他们的公司,他们会怎么做。因为你希望他们能这样做。就好似这是他们的公司一样。要实现这种状态唯一的方法就是你放弃一小部分权力,给他们朝这个方向发展的空间。这听起来或许有些令人望而却步,但其实并不需要冒太大的风险。管理层随时都可以拿走意见箱,或告诉员工不再需要他们的意见,或者甚至可以解雇一些人。如果你担心这样做会有损你的权威,那么就告诉员工每一种改变都只是试行几个月。如果可行,就继续。如果不可行,就停下来。即便仅仅是尝试,你的员工也会心怀感激的。如果你是团队的成员,就向你的老板提出这样的请求:给我一个机会。帮助我理解你的目标是什么,让我理清如何达成这些目标。这样的小举动将创造通往主人翁文化的途径。
  3. 只聘用比你更优秀的人企业总会认为尽快填补一个空缺岗位比耐心寻找最适合一个岗位的人更重要。有销售人员对我说过:“宁滥毋缺”,意思是说他们宁愿由一名领域内中等水平的人完成70%的限定销售额,也不愿让一个岗位空缺。但是在招聘质量要求上的妥协就已经是一个错误了。聘用糟糕的员工就好似在锅里扔进了一颗老鼠屎,不仅自身的表现不佳,还会拖累周围人的表现、士气和精力。如果拒绝一个人意味着其他每个人在短期内都需要更努力地工作,只需要提醒他们回想一下与上一个浑蛋同事共事时的遭遇就好了。成立委员会完成招聘工作,预先设定客观的标准,永远不要妥协,定期查看新聘用的员工是否优于以往聘用的员工。能够证明你的招聘工作做得很好的是新聘用的员工中十有八九都比你更优秀。如果他们不及你优秀,暂时不要聘用,直到找到一个更优秀的人。短期内你们的工作会放缓,但最终你将建立一个更加强大的团队。
  4. 不要将职业发展与管理绩效混为一谈克里斯·阿基里斯向我们展示了,即便最成功的人也有学不会的时候。如果他们都无法学习,那么余下的我们又能有什么希望呢?面对自己的缺点时总是难以令人愉悦。如果你将后果与批评结合在一起,如果员工感觉犯了一个错误就意味着在职业或经济上受损,那么他们就会争辩而不是保持开放的态度去学习和成长。发展谈话要随时进行,确保平稳且富有成效,恰如我以前的经理在每次会后进行的谈话一样。开启一次发展谈话的时候永远要保持这样的态度:“我能做些什么帮你取得更大成功?”否则,员工的防御心理就会增强,学习将中断。在实现目标的道路上,要确保发展谈话的平稳进行。不管目标有没有实现,两种谈话都应在空间和时间上分开。一个绩效考评阶段结束之后,立刻直入主题就设定的目标进行讨论,探讨哪些目标已经实现,以及奖励如何与绩效挂钩。但是这一次交谈应该只针对成果,而不是过程。可能没有达成目标,可能完成了目标,也可能超额完成了目标,每一种结果都应该对应不同的奖励或鼓励。如果处理好这方面的工作,绩效讨论就不再会是突然袭击,因为在整个过程中你们都在进行沟通,员工也能感觉到你在每一步工作上对他们的支持。不管在什么情况下,都不要完全依赖经理确定员工表现的确切情况。为了团队的发展,恳请同事贡献意见,即使是简单地问询一些问题或发布一些简单问卷也可以。至于绩效考评,要求经理们坐在一起组成团队,共同校准考评结果,确保公正。
  5. 关注团队的两端——最优员工和最差员工将最优秀的人放在显微镜下观察。他们结合了环境和技能,精心打磨才理清了如何成就超常表现。不仅要识别出最佳全能员工,还要识别出特定方面最突出的员工。不要寻找最优秀的销售人员;寻找面向特定规模的新用户销售量最大的人。找到能在夜雨中练习高尔夫球那样的优秀人才。在专业方面分得越精细,就越利于研究你的明星员工,发现他们比其他人更成功的原因。然后不仅要让他们成为其他人的榜样,围绕他们所做之事制定检查清单,还要请他们做老师。教授一项技能是掌握它的最好方法之一。请明星员工做教员,即使是半小时的咖啡交谈时间,也要促使他们清楚地讲述自己是如何开展工作的,而这个过程也有助于他们的成长。如果你身边有这样的同事,要仔细观察他们,多向他们提问题,利用这个机会从他们身上获取知识。与此同时,对表现最糟糕的员工也要心怀怜悯。如果你的招聘工作没有犯错,那么大多数陷入困境的员工都是因为没有找到合适的岗位,而不是因为自身笨拙。帮助他们学习或找到新的角色。但是如果上述努力失败,立刻辞退他们。让他们留在公司里并非仁慈,在一个自己并非最差员工的环境中,他们会更加快乐。
  6. 既要节俭又要慷慨我们为员工做的大多数事情都不需要任何花费。请供应商来公司为员工服务或与当地三明治店协商为公司送午餐。TGIF和嘉宾演讲者需要的仅仅是一个房间和一支麦克风。然而却带来了无比丰富的财富:启发谷歌人开发出一种新的服务或引发讨论。省下钱来,在员工最需要的时候,在他们遇到灾难或大喜之时使用。当某人需要急诊医疗护理或迎接家庭新成员之时,你的慷慨会带来最大的影响力。关注人类最重大的一些时刻能够突出你们的组织关心每一个员工。了解到自己在人生低谷和顶峰之时背后都有整个机构的力量做后盾,每个人都会感到宽慰。这一点对很小的公司也同样适用。我的父亲成立过一家工程公司,他亲自领导了30年。他深切关怀每一位员工,不仅付给他们薪水,而且善意赞扬,为他们提建议,做引导。团队中任何一个人任职5年之后,他都会拉他们出来私聊一番。他告诉他们公司有一项退休金计划,5年时间的投入已经满额。除了员工自己存下来的积蓄,他还为他们每个人额外存了一笔钱。有些人欢呼雀跃,有些人感动流涕,有些人只是简单地谢过了他。他没有提早告诉员工这项计划,因为他不希望人们为了钱才留下来工作。他希望员工留下来是因为喜欢创造东西,是因为喜爱这个团队。关键时刻他很慷慨,因此也使结果大不相同。
  7. 不公平薪酬不管你们的人力资源部门是怎么对你说的,要记住大多数工作中的绩效表现都是符合幂律分布的。你们的团队中90%甚至更高的价值都是由顶尖的10%的人创造的。因此,最优秀的员工远比平均水平的员工更有价值。他们的价值或许比平均水平的员工高50%,或许高50倍,但是不管高多少肯定值得你为他们付出更多。一定要让他们感觉到这些。即使你没有足够的资金为他们提供超高额的薪水,但是更高一些的薪水也算一种心意表达。另外一位员工对这种奖励或许会有些不高兴,但是你可以坦诚相对,解决这个问题:向他们解释薪酬差异化的原因,以及他们怎么做才能改变现状。与此同时,在公众认可方面要慷慨投入。团队的成就要庆贺,虽然失败但却学到重要经验教训的时候也要鼓励。
  8. 助推本书中提到的各种想法中对你未来的人生能够带来最大切实改善的一种就是改变每笔收入中存下来的金额。如果比较30年里赚到的钱同样多的一些人,他们累积的财富却可能有3000%的差异,而这一切几乎完全取决于你存下了多少钱。存钱从来都不是一件简单的事情。除非你比克罗伊斯[插图]还要富有,节省下来的每一个美元都像是一种利弊权衡。我是要买品牌货还是一般产品?是买3美元的花生酱还是吃甜点?换一辆新车还是再凑合一年?我毕业后的第一年,当演员的同时还做服务生,经常光顾小镇附近的女主人廉价商店(Hostess Thrift shop),店里售卖一些马上就要过期的面包和点心。我有了零食蛋糕(有节制的!),而且还能每周多省下几美元。要记住,督促谷歌人提升不到3%的存钱比例,每位谷歌人的退休基金将增加262000美元。很多人听到下面的事情或许会觉得很疯狂。我认识一些人,将度假胜地汉普顿斯的10万美元夏日出租房看成生活必需品;我的一些银行家朋友虽然在2008年丢掉了工作,但还是能躲到海滨别墅里度假。我一直反复强调这一点,但人们还是不愿意改变存款比例。计算出当前你存下来的钱占收入的比例,从现在起再多存一些。不论何时这都不是一件容易的事情。但这样做肯定是值得的。上面是对你个人而言。现在环顾四周,看看你所处的环境是如何助推你与周围的人的。你能很容易地看到其他人,与他们建立联系吗?你们冰箱里最不健康的零食放在与人的视线平齐的位置上吗?你给同事和朋友发邮件或短信的时候是分享好消息还是抱怨发火?我们都时刻受到环境的助推,也时刻助推着周围的人。利用这一点,使你自己和你的团队更快乐、更高效。工作场所的空间布置要鼓励你所期望的行为:如果你需要员工协作,但却受困于工位是小隔间,那么就推倒隔断。向员工传递讯息的时候要深思熟虑。分享一些积极的数据,比如参加当地慈善活动志愿者的人数,鼓励其他人参与。你将惊异于同一个工作场所给人带来的感觉会有如此大的不同。
  9. 管理日益提升的期望有时你会犯错误,这时就需要倒退几步。要准备好吃下你们自己的枸杞派。明白了这一点之后,在开始实验之前,告诉周围的人你打算实验本书中的一些想法。这样做有助于促使他们从批判者转变为支持者,实验走上了弯路的时候,他们的质疑将给你带来更多的益处。
  10. 享受!然后回到第1条,再来一遍拉里和谢尔盖立志创立一个他们都希望为之工作的地方。你也可以做同样的事情。即使你刚毕业加入一家公司,还只是一名初级职员,或者是第1000006号职员,你也可以像一位创始人一样选择与周围人的沟通方式,选择如何设计自己的工作场所,选择如何领导。你这样做可以帮助创造一个能够吸引地球上最优秀人才的场所。
  • 这并非一劳永逸的努力。想要打造了不起的公司文化和环境要求我们不断地学习和革新。不要担心立刻尝试所有事情。实验本书中介绍的一种或多种想法,从实验中学习经验,对项目进行调整,然后再次尝试。这种方式的美妙之处在于,良好的环境可以自我强化:所有这些努力可以互相支持,共同创造出一个有创造力、有趣、努力且效率极高的组织。如果你相信人本善,那么就应在工作中践行自己的信念。谷歌已经30多次被卓越职场研究所评为最佳雇主,另外还获得数百种支持女性、非裔美国人、老兵等人群的组织和政府、社会机构颁发的荣誉。但是我们并非第一个“最佳雇主”,也不会是最后一个,甚至在今日也不是唯一一个。谷歌真正擅长的是大规模运营,建立起的体系服务20亿人也如服务10人一样周到可靠。员工的创新得益于一批有先见之明的创始人、狂热的企业文化捍卫者、周密的学术研究,以及具有创造力的公司和政府。数千名谷歌人共同塑造了我们运营的方式,推动我们找到最具有创造力和最公平的方式解决与人相关的问题,使我们肩负起了责任。我有幸与见解深刻、勇于担当、富于创造力的同事和人力运营团队共事,竭尽全力才跟得上他们的步伐。我每天都能从他们身上获得启发。每年有成千上万的人参观我们的园区,问我们:“为什么这里的人这么开心?”“谷歌的秘密是什么?”“我在我的组织里做些什么才能使其更具创新性?”答案就在你的手中。谷歌工作法则1. 赋予工作意义2. 相信员工3. 只聘用比你更优秀的人4. 不要将职业发展与管理绩效混为一谈5. 关注团队的两端——最优员工和最差员工6. 既要节俭又要慷慨7. 不公平薪酬8. 助推9. 管理日益提升的期望10. 享受!然后回到第1条,再来一遍

后记

  • 我们一直围绕着4条基本原则构建谷歌人力运营部:1. 为实现极乐天堂而奋斗。2. 利用数据预测和塑造未来。3. 不遗余力地提高生产力。4. 创建非传统型团队。
  • 我选择血细胞来图示“人力资源正在做的工作”旨在强调我们的项目就如人体的循环系统一样无处不在且同样可靠。
  • 大多数公司,包括几年前的谷歌都会向升职的人道贺,但却毫不关注那些没有得到升职机会的人。这是非常愚蠢的行为。只需要一两个小时的时间找出你认为可能会因此沮丧的人,告诉他们如何才能得到持续发展。人们都希望受到这样的待遇。这样做从程序上讲更公正,有助于员工认同流程的开放性和可靠性。这样远比导致某人辞职,失去他们带来的产值,再寻找新人替代,聘用新人,引领新人走上高速通道这个过程对公司更有利。而且,在某人职业生涯非常脆弱的时刻,你这样做是帮助他们理解了发生的事情,利用一个消极事件激发他的动力。要构建这种能力需要花费一些时间,但是不管你所在组织的规模大小,开始这项工作都不是难事。
  • 她完全不知道电子数据表有一项功能可以做计算。我们需要留心所属专业的两端,并针对他们采取相应的行动。她的例子解释了为什么越来越多的公司将非人力资源从业人员安排为人力资源部的主管。
  • 情商高的人通常有更明晰的自我认知,因此也不会那么傲慢。这也使他们更容易转移到新的领域。
  • 教会整个团队一些传统人力资源团队不会学到的技巧,比如使用sQL或r等编程语言,或将员工面试中搜集的定性数据进行编码的方法。
  • 通过三分招聘模型的使用,我们招聘到具备各种能力的人:人力资源专业人员教会我们如何对员工和组织施加影响,识别不同的形态模式;咨询师可以提升我们对商业的理解力以及我们解决问题的水平;分析人员能提高我们所做各项工作的质量。
  • 在人力运营部中将我们所有人团结在一起的最重要因素在于我们共同的愿景,认为工作不必令人痛苦。工作可以令人更高雅、更有活力、更兴奋。这是推动我们努力的原因。

总结

  • 自由的状态是以自由表达为基础的,而自由表达又依靠对信息和真实情况的了解。(自由需要控制在一定的合理范围内?)
  • 从管理的核心角度来讲,权力的动态方向恰与自由背道而驰。
  • 剥夺管理者对员工的控制权。由一组同事、一个委员会或一个特别任命的独立团队做出。
  • 管理者服务于团队。管理者的关注重点不是惩罚或奖励,而是清除路障,鼓励团队。
  • 只有当企业着手给员工更多的自由时,业绩才能提升。
  • 坚信员工都是好的,再就是要有足够的勇气,把员工看成是企业的主人翁,而不是把他们当成机器。
  • 让处于绩效分布底端的人了解真相,但是不要将绩效与薪酬或职业成果直接挂钩,尽可能用一种积极的方式警示并激励他们。数百名经理需要面对自己并非好经理的现实。
  • 有效的管理不是简单粗暴地不允许你犯错,而是预见到可能发生的错误,提供一个合理的标准和方法,在错误发生之前就避免它。
  • 情商高的人通常有更明晰的自我认知,因此也不会那么傲慢。这也使他们更容易转移到新的领域。

管理借鉴

  1. 尽量创造自由的环境;(高自由度)
  2. 不能把员工当作机器;
  3. 会议时不坐在上首座位,避免潜在的等级关系;(除去显示权力和地位的象征符号)
  4. 尽量保持透明;
  5. 放弃一些控制权,就可以为团队创造一次提升的好机会,也给自己节省出更多时间应对新的挑战;
  6. 关注个人成长而不是评分和奖励,以此改善绩效;
  7. 把奖励分配谈话与员工发展谈话分开;
  8. 绩效交谈前发讲义,覆盖恰当的话题,引导讨论;
  9. 打造学习型组织;
最优秀经理手下工作的谷歌人在十几项Googlegeist评估维度上要比最差经理手下工作的谷歌人高5%至18%。除此之外,他们在以下几方面的认可度明显更高:
•职业决策更加公正。绩效评估公正,得到升职的都是实至名归的人选。
•个人的职业目标能够达成,他们的经理是非常有帮助的支持者和引导者。
•工作高效,决策迅速,资源分配合理,从多种视角考虑问题。
•团队成员之间没有等级制度,互相尊重,决策依据数据做出而不是靠耍手段,团队内部各人的工作和信念都保持透明。
•他们适当地参与到决策制定过程中,并且得到一定的授权去完成工作。
•他们可以自由地平衡工作和私人生活。

调查显示高分经理具备八种低分经理所不具备的共性:8个氧气项目特性
1.  做一名好的导师。
2.  给团队授权,不随便插手下属工作。
3.  表达出对团队成员的成功和个人幸福的兴趣和关心。
4.   高效/结果导向型。
5.   善于沟通——聆听和分享信息。
6.   在职业发展方面助力团队。
7.   对团队有清晰的愿景和战略。
8.   具备重要的技术技能,可为团队提供建议。

招聘借鉴

  1. 只聘用比你更优秀的人(至少某些特定的方面比你更优秀的。)
  2. 最优秀的人通常并不在寻找工作;
  3. 让面试者开心;
  4. 谷歌工作法则:筛选新雇员?□设定高质量标准□寻找自己的应聘者□客观评估应聘者□给应聘者一个加入的理由
<1…111213…17>

166 日志
191 标签
RSS
© 2025 Kingson Wu
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4