我们知道,软件工程和传统工程的显著区别之一是扩展性。为大家熟知的一些建筑工程在历经几百、几千年的风雨后,其建筑架构、功能依然不会偏离最初的设计。而软件项目第一版的交付往往就是第二版改动的开始,甚至第一版设计的时候业务已经对第二版第三版等未来充满了各种美好的设想。
而SOA和微服务都是在解决扩展性问题的过程中对可扩展软件架构的一种模式的思想总结。它们应对“可扩展”这个问题的的拆分方式都是“面向服务拆分”。这也是我们很难区分它们的原因之一。
我的观点是,“SOA和微服务是不同的”。“SOA”的概念要远远早于“微服务”的概念出现,从它们出现的历史背景和最初要解决的问题,可以发现它们架构思想的主要区别。
1996年,Gartner公司最早提出了SOA的概念
2000年之后,基于XML的WEB服务协议(soap、wsdl、uddi)和标准促进SOA发展
2002年12月,Gartner提出了SOA是“现代应用开发领域最重要的课题”
2005年后,soa推广和普及工作加速,各大厂商通过协作组织开始合作制定soa标准,涉及后续的sca、sdo、ws-policy
可以看到SOA的概念在很久前就被提出了,而当时并不是如当下的互联网蓬勃发展期。其背景是传统企业内部的各个IT系统中有大量重复建设工作,系统扩展困难,用于IT整合的预算占比太过庞大。比如,营销、财务、生产、办公等各个系统可能采购于不同的厂商,各自都有独立的人员管理等功能。企业又希望信息能够在各个系统间互通,需要这些异构系统大量的集成改造工作。
为了更好的解决这些问题,SOA中有几个核心概念:服务、松耦合 、ESB(Enterprise service bus-企业服务总线)。我们来看一个典型的架构图来理解SOA的核心概念:
(图片来自于网络)
SOA的架构中,原各个系统通过可以被ESB理解的协议开放自己的系统功能作为服务,或者使用其他系统开放的服务。ESB屏蔽各个系统间的异构性,并有公共的服务路由、异常处理、协议转换等功能,满足不同系统服务间的互联互通。各个服务间要尽量的松耦合,降低互相依赖和影响。
将系统功能通过协议暴露为服务不算复杂。松耦合就是对服务的业务设计层面解耦要求。但是技术层面,实现SOA架构的重任主要都在ESB上面。它要求ESB全知全能,协议转换更是要求对HTTP、WS、RPC、JMS等协议,XML、JSON、HTML、二进制等数据格式通通都能够解析和转换。其工作量、复杂度、计算量要求都很高,尤其当参与的系统增多,消息量愈大的时候,ESB往往成为架构瓶颈,也是SOA架构中被诟病的点。
之所以有ESB这样的设计,也是现实的无奈。正如我们上面提到的背景所述,企业IT系统中往往有很多的遗留系统,采购自不同的厂商,各家企业完全去重写或改造的成本非常巨大,自然而然,需要引入ESB这样的中间件作为核心的解决方案。
Gartner公司当初曾预言,“预计到2009年,SOA将成为占有绝对优势的软件工程实践方法, 2008年全球将有近7成企业导入SOA”。而实际上,在08、09年,各家厂商联合制定的soa标准趋于成熟的阶段,它们(厂商)预想的SOA在互联网企业中的应用依然很少,更多的是借鉴一些概念和思路。主要的应用还是在传统行业。
现在普遍认为微服务是由Martin Fowler 与 James Lewis 在2014年提出来的。实际上微服务这几年的兴起的确主要归功于两位大师在14的合写的文章,其中详细阐述了微服务的概念。Google Trends也很明显,在14年后,“microservices”一词热度上升趋势明显。
但是向前追溯的话,早在2005年Peter Rodgers在Web Services Edge大会上就提到了“Micro-Web-Services”的概念,在11和12年,一个软件架构工作组就直接使用了“microservice”这个词来描述架构模式了。12年3月ThoughtWorks的James Lewis分享了一些关于微服务的想法。
可见,微服务这种架构是也是历经很多年的演进,在一个适当时机由Martin Fowler在文章中明确定义被被大家广泛熟知。在Martin Fowler的这篇原文 https://martinfowler.com/articles/microservices.html 中,开篇就说明了此事:
The term “Microservice Architecture” has sprung up over the last few years to describe a particular way of designing software applications as suites of independently deployable services. While there is no precise definition of this architectural style, there are certain common characteristics around organization around business capability, automated deployment, intelligence in the endpoints, and decentralized control of languages and data.
而关于微服务,文中的定义为:
In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.
把上面的定义简单翻译如下:
简而言之,微服务架构风格是将单应用开发分为一组小的服务,每个服务运行在独立的进程中,并使用轻量级的机制(通常是HTTP资源API)通讯。这些服务围绕业务功能构建,并通过自动化的部署机制被独立部署。这些服务可以用不同的编程语言编写,使用不同的数据存储技术,只需要最低限度的集中管理。
(下面引用流传甚广的一个单体应用和微服务应用架构的对比图,方便直观理解)。
Martin Fowler对微服务的定义可以说简单清晰,典型的面向服务拆分的架构。但要弄清楚和SOA的区别需要特别注意几点:
首先,微服务考虑的核心场景是将单体应用开发拆分为一组小的服务。是解决业务场景愈来愈复杂的情况下,单体应用不仅业务过重架构难以保持良好模块清晰,同时人员配合困难、变更应用影响太大,无法匹配业务发展等问题。这些问题在快速发展的互联网企业架构中愈来愈凸显。
其次,微服务间的通讯要求轻量级,各种服务采用相同的,通用的协议。Martin Fowler 文章中有一节的标题即“Smart endpoints and dumb pipes”(智能端点和愚蠢的管道/富终端瘦通信),并用 ESB做了对比。微服务推崇的消息通讯仅根据约定的协议做消息传输,其他依赖的各项功能由客户端或客户端使用其他更“纯粹”的中间件各自负责。
更重要的,微服务围绕单应用中业务功能的细粒度的拆分强依赖于自动化的部署机制,也可以讲很是依赖于现代的DevOps 基础设施。微服务之所以可以在近些年有很多成功案例并火热,也得益于容器化、DevOps的发展,否则要求快速响应,持续交付,细粒度拆分且不断增长的微服务应用对开发、部署、测试、上线等流程就是无底深渊。
总结来讲,不同于SOA为了应对传统IT企业的多系统集成场景,微服务从高速发展的互联网单体应用拆分场景演进而来,架构思想的出发点不同,形似但神不似;不同于SOA由各大厂商主推,核心解决方案中主要的产品ESB做服务间通讯,微服务倾向于简单统一的协议,简单纯粹。可以直接采用HTTP或采用开源方案甚至自己实现,也不必向厂商购买重量级的中间件产品;不同于SOA主要关注与系统集成、通讯等技术层面,微服务思想本身包括了团队组织、基础设施等DevOps领域的理念,并且不可或缺。
上一节,我们从SOA和微服务的历史背景和最初要解决的问题,相对严格的将SOA和微服务思想的差异做了分析。此节,我们再来看看他们之间的关联。
其实,从上一节的内容中我们都能体会到,两种架构的核心思想中很重要的一点,都是面向“服务”进行可扩展的设计。微服务概念出现的土壤也是SOA中这种“服务化”的思想已经深入人心,并且这种思想被更彻底贯彻在微服务理念中。所以,目前很多人认为微服务就是SOA实现的一种,甚至称之为“细粒度的SOA”。不讲绝对意义上的是或不是,总归,微服务承继了SOA的核心理念的主要部分是没错的。
上文也提到,SOA中的ESB被广为诟病,它是在那当时特定场景下一种解决方案。随着发展,在更多新的场景中,ESB并无必要,加之一些弊端,自然而然就被新的方式替代,也就是现在的“富终端瘦通信”。所以,从历史来看,微服务是慢慢从SOA演化而来,去掉其中的ESB,也没错。
在SOA架构思想向微服务演化过程中,容器化相关技术高速发展,DevOps也愈来愈火热。应用运行环境可以整体打包交付,资源调度简单高效,自动化智能化的部署、监控运维在愈来愈多的场景中实施。这些使得服务更微型化,并作为应用被自动化独立部署,需要人工参与管理却更少,这些都变的可行和合理。自然,面向服务的拆分思想吸收了营养后生长也就更加“野蛮”、“激进”。所以,不仅新理念中在服务粒度上可以更“微”,也考虑了在这种架构中开发组织和基础设施的一些转变。
总结来讲,就是微服务承继了SOA面向服务拆分的核心思想,并随着演化,去掉了ESB,结合了新兴的DevOps理念和容器化等基础设施发展优势,将服务拆分做的更彻底。所以他们的关系大概如下,是交集的关系。
微服务既然可以说是从SOA演化而来,将其定义为新的,特殊的SOA也并无不可,通常来讲,现在大家说的SOA也不是特指最初的狭义的SOA概念了。但毕竟微服务和最初的SOA理念方向有差异,在演化到一个新的阶段有明确的区分后,使用新的名词作也很合理。相信我们在搞清楚其差异和联系后,就不用过于纠结两个名词是否一个含义了。
本文只是讲述了作者自己的理解,难免会有错漏,欢迎留言讨论。作者在接触相关概念至今参考了很多朋友的资料文章,难以在此处清晰细致的一一回忆,就在此一并感谢所有愿意分享的同行,谢谢!
如果你是用的是 minikube,可以跳过这一段,直接看下面。
官方文档的Tasks中,《Debug Service》这一篇有 A Pod cannot reach itself via Service IP 一节,讲了这种情况。主要意思就是我们的网络没有正确配置 hairpin-mode
,这个参数是在 kubelet
上的。可以通过在node上运行如下的命令,是不是有相关配置:
1 | ps auxw|grep kubelet |
后续的问题可以参照官方文档中解决。整段翻译如下:
TODO,暂时貌似不需要。如果需要翻译,可以回复。
minikube 只需要执行下面这行命令,设置集群所在虚拟的docker网卡就OK了:
1 | minikube ssh -- sudo ip link set docker0 promisc on |
此文为提取大纲,按照互动教程进行分享教学用。另补充一些知识点,和关联操作。
原文:
Kubernetes Pods 不是永久存在的,它有一个生命周期。当一个工作Node宕机是,运行在这个Node上的Pods也会丢失。ReplicatSet 可能会通过创建新的Pod,动态地调度集群使之恢复到目标状态来保证您的应用继续运行。比如说,有个拥有3个副本的图形处理后端程序,这些副本是可以被替换的(简单理解为无状态的)。其前端系统不应该关心后端的副本情况,即使副本的Pod丢失后被重建。Kubernetes集群中每一个Pod有一个独立的IP,即使在同一个Node上也是这样,这样的话我们就需要一种能够自动应对Pod间变化的机制来保证我们的程序能持续的运行。
Kubernetes 中的Service是一个抽象资源,它定义了一组Pod的逻辑集合和通过Service访问这些Pod的策略。Services支持有依赖关系的Pods松耦合。Service 像所有Kubernetes对象一样,支持使用Yaml(推荐)或JSON。Service 指向的一组Pods通常由 LabelSelector 指定(为什么你可能不想要在Service 的 spce 中包含 selector
请参见下文)。
尽管每个Pod都有一个唯一的IP地址,但不通过Service这些IP不会被暴露到集群外部(一般意义上是这样,但存在网络互通方案)。Services允许你的应用接收流量。通过指定ServiceSpec中的 type
字段,Services可以被不同的方式暴露:
<NodeIP>:<NodePort>
的形式从外部访问,这种方式是 ClusterIP 的超集。externalName
指定),其行为是返回使用这个名称的CNAME记录。无需使用代理。这种方式要求 V1.7或更高版本的 kube-dns
。关于不同类型 Services 的更多信息,可以参见Using Source IP教程和Connecting Applications with Services。
另外,需要注意的是有些情况下Services不在spec中定义 selector
。没有 selector
定义创建的Service也不会创建相应的端点(Endpoints)。这样用户可以手动映射Service到特定的endpoints。另外一种没有selector的情况是严格用了 type: ExternalName
类型的Service。
服务可以路由流量到一组Pods上。服务是一个抽象资源,允许Kubernetes中Pod在宕掉和复制的情况下不对应用造成冲击。有依赖关系的Pods间(比如一个应用的前端和后端组件)发现和路由通过Kubernetes Services处理。
Services使用labels and selectors匹配一组Pods。lables和selectors是Kubernetes可以在对象上进行逻辑运算的分组原语。Labels 是附加到对象上的键值对,会在多种场景中被使用:
你可以使用kubectl 的
--expose
参数,在创建 Deployment 的同时创建 Service。
Labels 可以创建对象的时候或之后附加到对象上。可以在任何时候被修改。我们一起使用Service暴露我么的应用,并应用一些labels吧。
我们先看下基于上一节之后我们系统里有哪些Pods:
1 | kubectl get pods |
1 | NAME READY STATUS RESTARTS AGE |
然后再看看已有的 Services:
1 | kubectl get services |
1 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE |
我们可以看到一个已经存在的名叫kubernetes的服务,这个是minikube在集群启动的时候自动创建的。这个是 API Server 的 Service。非 minikube 创建的集群default namespace下也有。
我们来创建如下几个不同的 Service:
1 | kubectl expose deployment first-deploy-whoami --name first-deploy-whoami-svc-1 --type LoadBalancer --port 8080 --target-port 80 |
1 | kubectl expose deployment first-deploy-whoami --name first-deploy-whoami-svc-2 --type NodePort --port 80 |
1 | kubectl expose deployment first-deploy-whoami --name first-deploy-whoami-svc-3 --type ClusterIP --port 8080 --target-port 80 |
我们使用的方式是暴露我们前两节创建的 deployment 对应的 Pods。 --name
后就是我们指定的 Service name, --type
是指定Service的暴露方式。 --port
是Service对外服务的端口。 --target-port
是Service映射的Endpoints 端口部分(Pods的对外服务端口)。需要注意的是,如果--port
或 --target-port
没有指定,将默认取另一项的值。都没指定则取Pods暴露的端口。
当然, expose
可以将很多kubernetes资源对应的Pods暴露为服务,包含:
1 | pod (po), service (svc), replicationcontroller (rc), deployment (deploy), replicaset (rs) |
我们之前暴露的几个服务通过
1 | kubectl get services |
和
1 | kubectl describe services first-deploy-whoami-svc-1 first-deploy-whoami-svc-2 first-deploy-whoami-svc-3 |
来看一下。着重关注IP,Port,TargetPort,NodePort和 Endpoints字段。
1 | ➜ clark kubectl get services |
--port
和 --target-port
根据我们创建的不同Service类型,也可以印证我们之前提到的“超集”概念。LoadBalancer 包含 NodePort 包含 ClusterIP。
minikube 并不支持 LoadBalancer
类型的Service,所以其 External-IP
一直是 <pending>
状态。对于有node port的Service,minikube 可以通过 service
命令来在浏览器打开或者显示其访问地址:
1 | minikube.exe service first-deploy-whoami-svc-1 |
1 | minikube.exe service first-deploy-whoami-svc-2 --url |
也可以列出所有的Service的访问地址:
1 | ➜ clark minikube.exe service list |
通过上面的地址,你应该可以访问到Pod的应用,如:
1 | ➜ clark curl http://192.168.99.101:30095/ |
我们可以尝试在集群中访问这几个服务,启动新的一次性Pods访问:
1 | kubectl run test-service-pod -it --rm --restart=Never --image=busybox --port=80 --generator=run-pod/v1 -- wget -q -O - http://first-deploy-whoami-svc-1:8080 |
如果你想在Service映射的Pod中通过Service访问Pod自己,可能会遇到无法访问的情况,请参照这篇文章:
Pods 无法通过 Service 访问自己。
复习获取 deployments 的命令:
1 | kubectl describe deployments |
我们可以看到类似的输出:
1 | ➜ clark kubectl describe deployments |
可以看到其中有如下两行:
1 | Pod Template: |
我们尝试用这个 Labels 获取 Pods:
1 | ➜ clark kubectl get pods -l run=first-deploy-whoami |
同样用这个 Labels 获取 Services:
1 | ➜ clark kubectl get services -l run=first-deploy-whoami |
我们把pod放到 POD_NAME
变量中:
1 | POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}') |
1 | echo $POD_NAME |
我们可以先看下这个Pod的Labels:
1 | kubectl describe pod $POD_NAME |
其中有如下输出:
1 | Labels: pod-template-hash=68dd7db55 |
现在我们给这个Pod增加一个应用版本号 app_version
:
1 | kubectl label pod $POD_NAME app_version=v1 |
再来看一下Pod的Labels,会看到如下输出:
1 | ➜ clark kubectl describe pod $POD_NAME |
我们可以用新的Labels来获取Pods:
1 | ➜ clark kubectl get pods -l app_version=v1 |
我们之前创建了几个Service,我们当然可以沿用笨方法去删除,但有了Labels后,我们就可以这样做:
1 | kubectl delete service -l run=first-deploy-whoami |
在我们的例子中,你会看到如下输出:
1 | service "first-deploy-whoami-svc-1" deleted |
通过下面的命令应该发现Service不见了:
1 | kubectl get services |
删除Service之后,讲道理,我们之前的访问方式应该是不可用了,我们可以使用之前的命令校验下:
1 | curl http://192.168.99.101:30095/ |
1 | minikube.exe service list |
1 | kubectl run test-service-pod -it --rm --restart=Never --image=busybox --port=80 --generator=run-pod/v1 -- wget -q -O - http://first-deploy-whoami-svc-1:8080 |
但是,Pod中访问自己,应该还是一样:
1 | ➜ clark kubectl exec -it $POD_NAME -- wget -q -O - http://localhost |
Pod 的 status
字段是一个 PodStatus 对象,这个对象有个 phase
字段。
Pod的阶段(phase)是对Pod在其生命周期中所处位置的简单的宏观的概述。这个阶段(phase)字段不是为了全面归纳容器或 Pod的状态,也不打算成为全面的状态机。
Pod 阶段的数量和含义是严格指定的。除了本文档列举的内容外,不应该再假设Pod会有其他的 phase
值。
下面是所有 phase
的可能值:
Value | Description |
---|---|
Pending | The Pod has been accepted by the Kubernetes system, but one or more of the Container images has not been created. This includes time before being scheduled as well as time spent downloading images over the network, which could take a while. Pod 已经被Kubernetes 系统接受,但是有一个或多个 Container 镜像还没被创建。这包含Pod被调度前和调度后通过网络下载镜像的时间,根据情况可能需要等一会。 |
Running | The Pod has been bound to a node, and all of the Containers have been created. At least one Container is still running, or is in the process of starting or restarting. Pod 已经被绑定到一个节点,而且所有的 Containers 已经被创建。至少有一个 Container 是正在运行的,或正处于启动中或重启中的状态。(也就是只要没都挂掉退出,没结束执行) |
Succeeded | All Containers in the Pod have terminated in success, and will not be restarted. Pod 中所有的容器都成功的退出了(执行Job结束),而且不会被重启)。 |
Failed | All Containers in the Pod have terminated, and at least one Container has terminated in failure. That is, the Container either exited with non-zero status or was terminated by the system. Pod 中所有的 Containers都结束了,但不是所有的都成功,至少有一个失败了。也就是以非零的返回码退出或被系统终止。 |
Unknown | For some reason the state of the Pod could not be obtained, typically due to an error in communicating with the host of the Pod. 由于某种原因无法取得 Pod 的状态,通常是由于Pod所在的宿主机(和master之间)的通信失败导致。(kubelet 无法正确上报状态) |
A Pod has a PodStatus, which has an array of PodConditions through which the Pod has or has not passed. Each element of the PodCondition array has six possible fields:
The lastProbeTime
field provides a timestamp for when the Pod condition was last probed.
The lastTransitionTime
field provides a timestamp for when the Pod last transitioned from one status to another.
The message
field is a human-readable message indicating details about the transition.
The reason
field is a unique, one-word, CamelCase reason for the condition’s last transition.
The status
field is a string, with possible values “True
”, “False
”, and “Unknown
”.
Thetype
field is a string with the following possible values:
PodScheduled
: the Pod has been scheduled to a node;Ready
: the Pod is able to serve requests and should be added to the load balancing pools of all matching Services;Initialized
: all init containers have started successfully;Unschedulable
: the scheduler cannot schedule the Pod right now, for example due to lack of resources or other constraints;ContainersReady
: all containers in the Pod are ready.ExecAction: Executes a specified command inside the Container. The diagnostic is considered successful if the command exits with a status code of 0.
TCPSocketAction: Performs a TCP check against the Container’s IP address on a specified port. The diagnostic is considered successful if the port is open.
HTTPGetAction: Performs an HTTP Get request against the Container’s IP address on a specified port and path. The diagnostic is considered successful if the response has a status code greater than or equal to 200 and less than 400.
Each probe has one of three results:
The kubelet can optionally perform and react to two kinds of probes on running Containers:
livenessProbe
: Indicates whether the Container is running. If the liveness probe fails, the kubelet kills the Container, and the Container is subjected to its restart policy. If a Container does not provide a liveness probe, the default state is Success
.
readinessProbe
: Indicates whether the Container is ready to service requests. If the readiness probe fails, the endpoints controller removes the Pod’s IP address from the endpoints of all Services that match the Pod. The default state of readiness before the initial delay is Failure
. If a Container does not provide a readiness probe, the default state is Success
.
If the process in your Container is able to crash on its own whenever it encounters an issue or becomes unhealthy, you do not necessarily need a liveness probe; the kubelet will automatically perform the correct action in accordance with the Pod’s restartPolicy
.
If you’d like your Container to be killed and restarted if a probe fails, then specify a liveness probe, and specify a restartPolicy
of Always or OnFailure.
If you’d like to start sending traffic to a Pod only when a probe succeeds, specify a readiness probe. In this case, the readiness probe might be the same as the liveness probe, but the existence of the readiness probe in the spec means that the Pod will start without receiving any traffic and only start receiving traffic after the probe starts succeeding. If your Container needs to work on loading large data, configuration files, or migrations during startup, specify a readiness probe.
If you want your Container to be able to take itself down for maintenance, you can specify a readiness probe that checks an endpoint specific to readiness that is different from the liveness probe.
Note that if you just want to be able to drain requests when the Pod is deleted, you do not necessarily need a readiness probe; on deletion, the Pod automatically puts itself into an unready state regardless of whether the readiness probe exists. The Pod remains in the unready state while it waits for the Containers in the Pod to stop.
For more information about how to set up a liveness or readiness probe, see Configure Liveness and Readiness Probes.
For detailed information about Pod Container status, see PodStatus and ContainerStatus. Note that the information reported as Pod status depends on the current ContainerState.
Once Pod is assigned to a node by scheduler, kubelet starts creating containers using container runtime.There are three possible states of containers: Waiting, Running and Terminated. To check state of container, you can use kubectl describe pod [POD_NAME]
. State is displayed for each container within that Pod.
Waiting
: Default state of container. If container is not in either Running or Terminated state, it is in Waiting state. A container in Waiting state still runs its required operations, like pulling images, applying Secrets, etc. Along with this state, a message and reason about the state are displayed to provide more information.
1 | ... |
Running
: Indicates that the container is executing without issues. Once a container enters into Running, postStart
hook (if any) is executed. This state also displays the time when the container entered Running state.
1 | ... |
Terminated
: Indicates that the container completed its execution and has stopped running. A container enters into this when it has successfully completed execution or when it has failed for some reason. Regardless, a reason and exit code is displayed, as well as the container’s start and finish time. Before a container enters into Terminated, preStop
hook (if any) is executed.
1 | ... |
FEATURE STATE: Kubernetes v1.14
stable
In order to add extensibility to Pod readiness by enabling the injection of extra feedbacks or signals into PodStatus
, Kubernetes 1.11 introduced a feature named Pod ready++. You can use the new field ReadinessGate
in the PodSpec
to specify additional conditions to be evaluated for Pod readiness. If Kubernetes cannot find such a condition in the status.conditions
field of a Pod, the status of the condition is default to “False
”. Below is an example:
1 | Kind: Pod |
The new Pod conditions must comply with Kubernetes label key format. Since the kubectl patch
command still doesn’t support patching object status, the new Pod conditions have to be injected through the PATCH
action using one of the KubeClient libraries.
With the introduction of new Pod conditions, a Pod is evaluated to be ready only when both the following statements are true:
ReadinessGates
are “True
”.To facilitate this change to Pod readiness evaluation, a new Pod condition ContainersReady
is introduced to capture the old Pod Ready
condition.
In K8s 1.11, as an alpha feature, the “Pod Ready++” feature has to be explicitly enabled by setting the PodReadinessGates
feature gate to true.
In K8s 1.12, the feature is enabled by default.
A PodSpec has a restartPolicy
field with possible values Always, OnFailure, and Never. The default value is Always. restartPolicy
applies to all Containers in the Pod. restartPolicy
only refers to restarts of the Containers by the kubelet on the same node. Exited Containers that are restarted by the kubelet are restarted with an exponential back-off delay (10s, 20s, 40s …) capped at five minutes, and is reset after ten minutes of successful execution. As discussed in the Pods document, once bound to a node, a Pod will never be rebound to another node.
In general, Pods do not disappear until someone destroys them. This might be a human or a controller. The only exception to this rule is that Pods with a phase
of Succeeded or Failed for more than some duration (determined by terminated-pod-gc-threshold
in the master) will expire and be automatically destroyed.
Three types of controllers are available:
restartPolicy
equal to OnFailure or Never.restartPolicy
of Always.All three types of controllers contain a PodTemplate. It is recommended to create the appropriate controller and let it create Pods, rather than directly create Pods yourself. That is because Pods alone are not resilient to machine failures, but controllers are.
If a node dies or is disconnected from the rest of the cluster, Kubernetes applies a policy for setting the phase
of all Pods on the lost node to Failed.
Liveness probes are executed by the kubelet, so all requests are made in the kubelet network namespace.
1 | apiVersion: v1 |
Pod is running and has one Container. Container exits with success.
restartPolicy
is:phase
stays Running.phase
becomes Succeeded.phase
becomes Succeeded.Pod is running and has one Container. Container exits with failure.
restartPolicy
is:phase
stays Running.phase
stays Running.phase
becomes Failed.Pod is running and has two Containers. Container 1 exits with failure.
restartPolicy
is:phase
stays Running.phase
stays Running.phase
stays Running.restartPolicy
is:phase
stays Running.phase
stays Running.phase
becomes Failed.Pod is running and has one Container. Container runs out of memory.
restartPolicy
is:phase
stays Running.phase
stays Running.phase
becomes Failed.Pod is running, and a disk dies.
phase
becomes Failed.Pod is running, and its node is segmented out.
Get hands-on experience attaching handlers to Container lifecycle events.
Get hands-on experience configuring liveness and readiness probes.
Learn more about Container lifecycle hooks.
此文为提取大纲,按照互动教程进行分享教学用。另补充一些知识点,和关联操作。
原文:
查看已部署的 Pod 和配置
查看应用日志
我们在 上一节 中创建了 Deployment 的时候,Kubernetes 创建了一个 Pod 来托管我们的应用实例。Pod 是 Kubernetes 的资源抽象,代表了一个或多个应用容器(如 Docker 或 rkt )以及用于这些容器的一些共享资源集合。这些共享资源包含:
共享存储,就是 Volumes
网络,会被分配唯一的集群IP地址
每个容器如何运行的信息,比如容器镜像版本或要被使用的特定端口
Pod 相当于是应用程序特定的“逻辑主机”的一个建模,可以包含相对紧耦合的不同的应用容器。所以可以认为Pod地位是和曾经的虚拟化世界中的虚拟主机对等,而容器和虚拟主机中的进程对等。比如,Pod 中包含两个容器,一个是 Web应用程序的容器,会接受用户请求和处理数据,并产生一些处理记录,如日志。另外一个容器就是对第一个容器产生的数据进行再次处理,但不是一个领域,如收集归档日志的 fluentd
。这样两个容器就是紧密协作的,需要共享存储等上下文环境、统一调度。
Pod 是 Kubernetes 平台上的原子单元。当我们在 Kubernetes 上创建一个 Deployment 的时候,这个部署会创建包含容器的Pod(而不是由 Deployment 直接创建容器)。每一个Pod会绑定到它被调度的 Node 上,始终在那里,直到(根据重启策略)或被删除。在 Node 故障的情况下,相同的 Pod (新的)会被调度到集群中其他可用节点。
Pod 总是运行在 Node 上的。Node 是 Kubernetes 集群中的工作机器,取决于集群情况,可以是虚拟机或物理机。所有的Node都是被 Master 管理。一个Node上可以运行多个Pods,Kubernetes master会自动在集群中的 Node 之间处理调度 Pod。Mater 的自动调度会考虑到每个节点的可用资源。
每个 Kubernetes Node 上至少运行一下组件:
如果一些容器是紧耦合的并且需要共享类似磁盘这样的资源,那么这些容器应该被放到一个Pod 中一起被调度。
在上一节 我们使用了Kubectl 命令行接口。我们在本章节继续使用它来获取已部署的应用及其环境的信息。最常用的操作可以通过以下命令完成:
我们可以用这些命令来查看我们是在啥时候部署的应用,它们当前的状态是咋样的,它们在哪里运行以及它们的配置是个啥。
现在,我们已经进一步的了解集群的组件和命令行,下面进入互动教程开始探索吧。
先按照上一节将我们的应用启动起来。
之后运行
1 | kubectl get pods |
如果还没启动好,等一等在运行这个命令。
我们可以通过 describe
来看 Pod 的详细信息,包括 Pod 运行的是哪些镜像:
1 | kubectl describe pods |
我们可以根据输出看到 Pod及其 container 的详细信息,包括 IP 地址,container 端口,Pod 生命周期的事件(events)记录。 describe
的输出包含了很多我们还没有覆盖到的内容,不过不用担心,教程结束后,你肯定会很熟悉。
注意,
describe
命令可用于 Kubernetes 的绝大多数资源类型,包括 Node,Pod,Deployment 等。其输出被设计为可读的,而不是脚本化的,所以,不妨经常用用看。
和 Docker 一样。应用程序任何向标准输出(STDOUT)输出的内容都会成为 Pod 中对应 container 的日志。我们可以通过 logs
命令来查看日志。
首先,上一节一样,把 Pod 名称放到变量中(你也可以手动 copy):
1 | POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}') |
然后,我们查看这个 Pod 的日志:
1 | kubectl logs $POD_NAME |
需要注意的是,我们此处之所以直接查看 Pod 的日志,而不是 container 是因为我们的 Pod 中只有一个 container。
其常用的格式是:
1 | kubectl logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER] [options] |
你也可以使用类似下面的命令查看日志:
1 | kubectl logs $POD_NAME -c first-deploy-whoami |
同样,类似于 Docker,Pod 中的 container 启动后我们可以直接在容器中运行命令。使用的 kubctl
命令也是 exec
。
1 | kubectl exec $POD_NAME env |
上面这条命令可以直接将容器内的 env 信息返回。如 logs
一样,我们省略了 container 名称,你也可以用同样方式加上。
1 | kubectl exec $POD_NAME env -c first-deploy-whoami |
让我们直接启动一个 session 看一看:
1 | kubectl exec -it $POD_NAME sh |
-i
和 -t
分别用于传递 stdin 到容器和让你的 stdin 成为容器的 TTY。
我们的容器用的基础镜像是 busybox,默认有 sh
和一些基础工具(curl 是没有的,用 wget 代替)。进入容器后你就可以“为所欲为”了:
1 | pwd && ls -al |
我们可以尝试通过 localhost 访问一下我们启动的 Http 服务器:
1 | wget -q -O - http://localhost |
应该可以返回类似如下的信息:
1 | Hostname: first-deploy-whoami-68dd7db55-bpr52 |
kubectl
会使用 $HOME/.kube/config
文件作为默认配置(也就是 kubeconfig)。我们可以通过设置环境变量 KUBECONFIG
或命令行选项 --kubeconfig
来指定 kubeconfig。本文概述kubectl语法,介绍命令操作,并提供常见的示例。有关每个命令的详细信息,包括所有支持的 falgs 和子命令,请参阅kubectl参考文档 。有关安装说明,请参阅安装kubectl。
命令行使用可以总结为如下语法:
1 | kubectl [command] [TYPE] [NAME] [flags] |
解释如下:
command
指定对一个或多个资源的操作,如 create
,get
,describe
,delete
。
TYPE
指明了要操作的资源类型。资源类型不区分大小写,可以使用单数、复数、缩写形式(注意,缩写是严格的,不能单复数大小写)。比如,如下几条命令有相同的输出:
1 | kubectl get pod pod1 |
NAME
指明资源名称。名称区分大小写,如果省略了资源名称,则展示此类所有资源的详情。如 kubectl get pods
。
在对多个资源应用一个操作的时候,可以按照资源的类型和名称分别指定每个资源,或指定一个或多个文件:
TYPE1 name1 name2 name<#>
kubectl get pod example-pod1 example-pod2
TYPE1/name1 TYPE1/name2 TYPE2/name3 TYPE<#>/name<#>
kubectl get pod/example-pod1 replicationcontroller/example-rc1
-f file1 -f file2 -f file<#>
kubectl get pod -f ./pod.yaml
注意:命令行指定的 flags 优先级最高,会覆盖默认值和对应的环境变量
下表包含所有 kubectl
操作的简短描述和通常的语法结构:(TODO: 官方表格命令过期,新命令未添加全,后续补充)
Operation | Syntax | Description |
---|---|---|
annotate | kubectl annotate (-f FILENAME | TYPE NAME | TYPE/NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--overwrite] [--all] [--resource-version=version] [flags] | Add or update the annotations of one or more resources. 添加或更新一个或多个资源的 annotations。 |
api-versions | kubectl api-versions [flags] | List the API versions that are available. 查看可用的 API 版本。 |
apply | kubectl apply -f FILENAME [flags] | Apply a configuration change to a resource from a file or stdin. 通过文件或输入将配置变更应用到资源。 |
attach | kubectl attach POD -c CONTAINER [-i] [-t] [flags] | Attach to a running container either to view the output stream or interact with the container (stdin). attach 到运行中的容器,以查看容器输出或和容器交互。类似于 docker 的 attach 操作。 |
autoscale | kubectl autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU] [flags] | Automatically scale the set of pods that are managed by a replication controller. 对通过 replication controller 管理的 pods 进行自动的扩缩容。(使pods的负载在指定范围内。) |
cluster-info | kubectl cluster-info [flags] | Display endpoint information about the master and services in the cluster. 展示集群 master 和服务(非K8S资源的 Service类型)的端点信息。 |
config | kubectl config SUBCOMMAND [flags] | Modifies kubeconfig files. See the individual subcommands for details. 修改 kubeconfig 配置内容。查看子命令获取更多信息。 |
create | kubectl create -f FILENAME [flags] | Create one or more resources from a file or stdin. 通过文件或标准输入创建一个或多个资源。 |
delete | kubectl delete (-f FILENAME | TYPE [NAME | /NAME | -l label | --all]) [flags] | Delete resources either from a file, stdin, or specifying label selectors, names, resource selectors, or resources. 删除资源。可以通过文件,标准输入或指定 label selectors,names,resource selectors, resources 的方式。 |
describe | kubectl describe (-f FILENAME | TYPE [NAME_PREFIX | /NAME | -l label]) [flags] | Display the detailed state of one or more resources. 展示一个或多个资源的详细信息。 |
diff | kubectl diff -f FILENAME [flags] | Diff file or stdin against live configuration (BETA) 对比文件或标准输入与当前配置的区别。 |
edit | kubectl edit (-f FILENAME | TYPE NAME | TYPE/NAME) [flags] | Edit and update the definition of one or more resources on the server by using the default editor. 通过默认的编辑器修改或更新集群中一个或多个资源 |
exec | kubectl exec POD [-c CONTAINER] [-i] [-t] [flags] [-- COMMAND [args...]] | Execute a command against a container in a pod. 在 pod 的容器中执行一个命令。和 docker 的 exec 命令相似。 |
explain | kubectl explain [--recursive=false] [flags] | Get documentation of various resources. For instance pods, nodes, services, etc. 获取各种资源类型的文档,比如,pods,nodes,services 等。 |
expose | kubectl expose (-f FILENAME | TYPE NAME | TYPE/NAME) [--port=port] [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type] [flags] | Expose a replication controller, service, or pod as a new Kubernetes service. 将 replication controller(包括 deployment),service,pod暴露为一个新的 kubernetes service。 |
get | kubectl get (-f FILENAME | TYPE [NAME | /NAME | -l label]) [--watch] [--sort-by=FIELD] [[-o | --output]=OUTPUT_FORMAT] [flags] | List one or more resources. 列出一个或多个资源。 |
label | kubectl label (-f FILENAME | TYPE NAME | TYPE/NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [--overwrite] [--all] [--resource-version=version] [flags] | Add or update the labels of one or more resources. 添加或更新一个或多个资源的 lables。 |
logs | kubectl logs POD [-c CONTAINER] [--follow] [flags] | Print the logs for a container in a pod. 输出pod中某个container的日志。和 docker logs 类似。 |
patch | kubectl patch (-f FILENAME | TYPE NAME | TYPE/NAME) --patch PATCH [flags] | Update one or more fields of a resource by using the strategic merge patch process. 更新资源的一个或多个字段。默认用策略性合并补丁(–type=’strategic’)方式。 |
port-forward | kubectl port-forward POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N] [flags] | Forward one or more local ports to a pod. 转发一个或多个本地端口到pod。 |
proxy | kubectl proxy [--port=PORT] [--www=static-dir] [--www-prefix=prefix] [--api-prefix=prefix] [flags] | Run a proxy to the Kubernetes API server. 启动一个到 Kubernetes API server 的代理。 |
replace | kubectl replace -f FILENAME | Replace a resource from a file or stdin. 从文件或标准输入替换一个资源。 |
rolling-update | kubectl rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f NEW_CONTROLLER_SPEC) [flags] | Perform a rolling update by gradually replacing the specified replication controller and its pods. 通过逐步替换 replication controller 和它的 pods 来实现一个滚动更新。(废弃了,用 rollout替代)。 |
run | kubectl run NAME --image=image [--env="key=value"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [flags] | Run a specified image on the cluster. 在集群上运行特定的镜像。 |
scale | kubectl scale (-f FILENAME | TYPE NAME | TYPE/NAME) --replicas=COUNT [--resource-version=version] [--current-replicas=count] [flags] | Update the size of the specified replication controller. 更新指定 replication controller 的副本数量。 |
stop | kubectl stop | Deprecated: Instead, see kubectl delete .废弃,使用 kubectl delete 替代。 |
version | kubectl version [--client] [flags] | Display the Kubernetes version running on the client and server. 展示 Kubernetes 服务集群和本地客户端的版本。 |
提示:关于命令操作的更多信息参见kubectl参考文档 。
The following table includes a list of all the supported resource types and their abbreviated aliases.
(This output can be retrieved from kubectl api-resources
, and is accurate as of Kubernetes 1.13.3.)
Resource Name | Short Names | API Group | Namespaced | Resource Kind |
---|---|---|---|---|
componentstatuses | cs | false | ComponentStatus | |
configmaps | cm | true | ConfigMap | |
endpoints | ep | true | Endpoints | |
limitranges | limits | true | LimitRange | |
namespaces | ns | false | Namespace | |
nodes | no | false | Node | |
persistentvolumeclaims | pvc | true | PersistentVolumeClaim | |
persistentvolumes | pv | false | PersistentVolume | |
pods | po | true | Pod | |
podtemplates | true | PodTemplate | ||
replicationcontrollers | rc | true | ReplicationController | |
resourcequotas | quota | true | ResourceQuota | |
secrets | true | Secret | ||
serviceaccounts | sa | true | ServiceAccount | |
services | svc | true | Service | |
mutatingwebhookconfigurations | admissionregistration.k8s.io | false | MutatingWebhookConfiguration | |
validatingwebhookconfigurations | admissionregistration.k8s.io | false | ValidatingWebhookConfiguration | |
customresourcedefinitions | crd , crds | apiextensions.k8s.io | false | CustomResourceDefinition |
apiservices | apiregistration.k8s.io | false | APIService | |
controllerrevisions | apps | true | ControllerRevision | |
daemonsets | ds | apps | true | DaemonSet |
deployments | deploy | apps | true | Deployment |
replicasets | rs | apps | true | ReplicaSet |
statefulsets | sts | apps | true | StatefulSet |
tokenreviews | authentication.k8s.io | false | TokenReview | |
localsubjectaccessreviews | authorization.k8s.io | true | LocalSubjectAccessReview | |
selfsubjectaccessreviews | authorization.k8s.io | false | SelfSubjectAccessReview | |
selfsubjectrulesreviews | authorization.k8s.io | false | SelfSubjectRulesReview | |
subjectaccessreviews | authorization.k8s.io | false | SubjectAccessReview | |
horizontalpodautoscalers | hpa | autoscaling | true | HorizontalPodAutoscaler |
cronjobs | cj | batch | true | CronJob |
jobs | batch | true | Job | |
certificatesigningrequests | csr | certificates.k8s.io | false | CertificateSigningRequest |
leases | coordination.k8s.io | true | Lease | |
events | ev | events.k8s.io | true | Event |
ingresses | ing | extensions | true | Ingress |
networkpolicies | netpol | networking.k8s.io | true | NetworkPolicy |
poddisruptionbudgets | pdb | policy | true | PodDisruptionBudget |
podsecuritypolicies | psp | policy | false | PodSecurityPolicy |
clusterrolebindings | rbac.authorization.k8s.io | false | ClusterRoleBinding | |
clusterroles | rbac.authorization.k8s.io | false | ClusterRole | |
rolebindings | rbac.authorization.k8s.io | true | RoleBinding | |
roles | rbac.authorization.k8s.io | true | Role | |
priorityclasses | pc | scheduling.k8s.io | false | PriorityClass |
storageclasses | sc | storage.k8s.io | false | StorageClass |
volumeattachments | storage.k8s.io | false | VolumeAttachment |
Use the following sections for information about how you can format or sort the output of certain commands. For details about which commands support the various output options, see the kubectl reference documentation.
The default output format for all kubectl
commands is the human readable plain-text format. To output details to your terminal window in a specific format, you can add either the -o
or --output
flags to a supported kubectl
command.
1 | kubectl [command] [TYPE] [NAME] -o <output_format> |
Depending on the kubectl
operation, the following output formats are supported:
Output format | Description |
---|---|
-o custom-columns=<spec> | Print a table using a comma separated list of custom columns. |
-o custom-columns-file=<filename> | Print a table using the custom columns template in the <filename> file. |
-o json | Output a JSON formatted API object. |
-o jsonpath=<template> | Print the fields defined in a jsonpath expression. |
-o jsonpath-file=<filename> | Print the fields defined by the jsonpath expression in the <filename> file. |
-o name | Print only the resource name and nothing else. |
-o wide | Output in the plain-text format with any additional information. For pods, the node name is included. |
-o yaml | Output a YAML formatted API object. |
In this example, the following command outputs the details for a single pod as a YAML formatted object:
1 | kubectl get pod web-pod-13je7 -o yaml |
Remember: See the kubectl reference documentation for details about which output format is supported by each command.
To define custom columns and output only the details that you want into a table, you can use the custom-columns
option. You can choose to define the custom columns inline or use a template file: -o custom-columns=<spec>
or -o custom-columns-file=<filename>
.
Inline:
1 | kubectl get pods <pod-name> -o custom-columns=NAME:.metadata.name,RSRC:.metadata.resourceVersion |
Template file:
1 | kubectl get pods <pod-name> -o custom-columns-file=template.txt |
where the template.txt
file contains:
1 | NAME RSRC |
The result of running either command is:
1 | NAME RSRC |
kubectl
supports receiving specific column information from the server about objects. This means that for any given resource, the server will return columns and rows relevant to that resource, for the client to print. This allows for consistent human-readable output across clients used against the same cluster, by having the server encapsulate the details of printing.
This feature is enabled by default in kubectl
1.11 and higher. To disable it, add the --server-print=false
flag to the kubectl get
command.
To print information about the status of a pod, use a command like the following:
1 | kubectl get pods <pod-name> --server-print=false |
Output looks like this:
1 | NAME READY STATUS RESTARTS AGE |
To output objects to a sorted list in your terminal window, you can add the --sort-by
flag to a supported kubectl
command. Sort your objects by specifying any numeric or string field with the --sort-by
flag. To specify a field, use a jsonpath expression.
1 | kubectl [command] [TYPE] [NAME] --sort-by=<jsonpath_exp> |
To print a list of pods sorted by name, you run:
1 | kubectl get pods --sort-by=.metadata.name |
Use the following set of examples to help you familiarize yourself with running the commonly used kubectl
operations:kubectl apply
- Apply or Update a resource from a file or stdin.
1 | # Create a service using the definition in example-service.yaml. |
kubectl get
- List one or more resources.
1 | # List all pods in plain-text output format. |
kubectl describe
- Display detailed state of one or more resources, including the uninitialized ones by default.
1 | # Display the details of the node with name <node-name>. |
Note: The
kubectl get
command is usually used for retrieving one or more resources of the same resource type. It features a rich set of flags that allows you to customize the output format using the-o
or--output
flag, for example. You can specify the-w
or--watch
flag to start watching updates to a particular object. Thekubectl describe
command is more focused on describing the many related aspects of a specified resource. It may invoke several API calls to the API server to build a view for the user. For example, thekubectl describe node
command retrieves not only the information about the node, but also a summary of the pods running on it, the events generated for the node etc.
kubectl delete
- Delete resources either from a file, stdin, or specifying label selectors, names, resource selectors, or resources.
1 | # Delete a pod using the type and name specified in the pod.yaml file. |
kubectl exec
- Execute a command against a container in a pod.
1 | # Get output from running 'date' from pod <pod-name>. By default, output is from the first container. |
kubectl logs
- Print the logs for a container in a pod.
1 | # Return a snapshot of the logs from pod <pod-name>. |
Use the following set of examples to help you familiarize yourself with writing and using kubectl
plugins:
1 | # create a simple plugin in any language and name the resulting executable file |
1 | hello world |
1 | # we can "uninstall" a plugin, by simply removing it from our PATH |
In order to view all of the plugins that are available to kubectl
, we can use the kubectl plugin list
subcommand:
1 | kubectl plugin list |
1 | The following kubectl-compatible plugins are available: |
1 | # this command can also warn us about plugins that are |
1 | The following kubectl-compatible plugins are available: |
We can think of plugins as a means to build more complex functionality on top of the existing kubectl commands:
1 | cat ./kubectl-whoami |
Running the above plugin gives us an output containing the user for the currently selected context in our KUBECONFIG file:
1 | # make the file executable |
To find out more about plugins, take a look at the example cli plugin.
1 | ➜ kubectl --help |
本文是《浅入浅出k8s实战-k8s初体验篇的第三篇 》
此文为提取大纲,按照互动教程进行分享教学用。另补充一些知识点,和关联操作。
原文:
kubectl 基础,查看 k8s 集群基本信息
命令方式部署应用
创建一个 Deployment,实际是在集群中创建 Deployment 配置,或者说把配置应用到集群中
Deployment 指示 K8S 如何去创建和更新应用程序实例
Kubernetes master 会将上述应用程序实例调度到集群中的各个 Node。
Kubernetes Deployment Controller 会持续监视这些实例,如果托管它的 Node 不可用或被删除,则 Deployment Controller 将替换原实例为集群中其他 Node 上的新实例。(自愈机制)
kubectl 命令通常的格式是 kubectl action resource
。就是 kubectl
后跟动作/命令,紧接着是要操作的资源。而资源由类型和名称定义。详细可以参照另一篇文章Kubectl 命令参考。
每一个命令都可以通过 --help
获得更多的信息,比如 kubectl get --help
, get
是我们要经常用的命令。
首先,通过 kubectl version
看一下我们的 kubectl 版本信息和他所对应的集群版本信息。
通过 kubectl get nodes
看一下集群里面的节点信息。
我们这节要操作的资源是 Deployment
,所以我们可以先通过 kubectl get deployment
,如果你看过我们上面提到的Kubectl 命令参考,你就知道,命令中的 deployment
属于资源类型,它的大小写,单复数并不重要。在我们集群中有了 Deployment 类型的资源后(下面的操作就会告诉你如何创建),你可以通过 kubectl get deployment
, kubectl get deployments
, kubectl get deploy
, kubectl get Deployment
等获得一样的效果。
如果你是新的集群,你应该看到 no resources found
提示,否则可能已经有一些资源存在了。记得后面的命令自己修改名字来避免重复。
kubectl run
命令可以创建一个 Deployment 来管理容器。其常用的命令格式是:
1 | kubectl run NAME --image=image [--env="key=value"] [--port=port] [--replicas=replicas] [--dry-run=bool] |
参数大多根据名字就能理解,主要的两个参数,NAME
是 Deployment 的名字, --image
指定镜像。进一步理解你肯定记得 --help
大法。
我们接下来通过 kubectl run
运行一个容器 registry.cn-hangzhou.aliyuncs.com/khan/whoami。
这是一个可以输出 Http 请求信息的 docker 镜像,后面我们会经常使用。
其输出信息如下:
1 | Hostname : 6e0030e67d6a |
OK,启动这个镜像:
1 | kubectl run first-deploy-whoami --image=registry.cn-hangzhou.aliyuncs.com/khan/whoami:1.0 --port=80 |
我们可能会看到如下的提示信息:
1 | kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead. |
最后是提示创建成功,然而成功提示的上面的是什么呢。容我埋个伏笔,我们稍后再解释。
成功启动容器后,我们可以通过 kubectl get deployments
来查看刚刚创建的资源。你应该能看到这样的输出:
1 | NAME READY UP-TO-DATE AVAILABLE AGE |
容器化的应用运行在集群内部(Kubernetes 集群中,我们称之为 Pod,是最小的调度单元),使用的是集群内部的网络。这个网通通常默认是私有的隔离的,对同一个Kubernetes 集群内部其他的容器和服务可见,但是集群外并不可以。当我们使用 kubectl
的时候,我们是通过一个 API 端点和集群进行通信。这个 API 端点是需要额外的认证和配置的,用外部工具如 curl
或浏览器等没有权限。
后续,我们会在互动教程4中来看如何通过其他的方式把应用(Pod)暴露到 kubernetes 集群之外。此节我们先通过 API 的本地 proxy 来实现。
kube proxy
命令能够创建一个本地的代理,转发通信请求到集群的私有网络。control-C可以关闭这个代理。
我们启动这个代理,默认情况下是绑定到 8001 端口,我们可以改为其他段扣,此处指定 8081:
1 | kubectl proxy --port 8081 |
然后我们通过 http://localhost:8081 就可以访问到这个服务器信息,其会返回 JSON 格式的接口列表信息。可以通过 [http://localhost:8081/version](http://localhost:8081/version)
访问到版本号信息,和我们 kubectl version
拿到的服务器信息一致。
API Server 会自动地为每个 Pod 创建一个基于 Pod 名称的可访问端点。首先,我们需要获取这个 Pod 的名称。将其存储到上下文变量 POD_NAME 中,由于上一个命令行正在运行 proxy,我们打开新的命令行运行:
1 | POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}') |
现在,我们可以通过 Http 请求来访问这个pod:
1 | curl http://localhost:8081/api/v1/namespaces/default/pods/$POD_NAME/proxy/ |
应该可以输出如下的信息:
1 | Hostname: first-deploy-whoami-68dd7db55-bpr52 |
你也可以通过浏览器访问 http://localhost:8081/api/v1/namespaces/default/pods/ 可以看到所有 pod,然后将名称复制下来,组成同样的链接访问。
在上面的信息中,我们可以看到, X-Forwarded-For
和 X-Forwarded-Uri
两个应用收到的请求头表名这是一个通过代理的访问。
可以通过 kubectl delete
命令删除我们刚刚的 Deployment:
1 | kubectl delete deployment fisrt-deploy-whoami |
命令简单易懂,就不再解释了。
回到上面我们埋下的伏笔, kubectl run
中 DEPRECATED
的信息。其文档中有说明
1 | --generator='': The name of the API generator to use, see |
实际上, --generator
是指示 kubectl run
命令最终生成什么资源。用不用的生成器,生成的资源就不同。目前(1.14.2)默认生成器是 deployment/apps.v1
我们可以访问 https://kubernetes.io/docs/reference/kubectl/conventions/#generators 查看所有的生成器。
如果切换为如下的命令,将不再有提示,但请注意命令执行后的描述,而且,执行这条命令并不会和同名的 deployment 冲突,之前的 deployment删除和不删除都无印象。你能知道为什么以及如何查看新生产的资源吗?
1 | kubectl run first-deploy-whoami --image=registry.cn-hangzhou.aliyuncs.com/khan/whoami:1.0 --port=80 --generator=run-pod/v1 |
IngressRoute
类型资源的时候遇到错误,通过查找相关资料测试,发现 CRD 需要额外的配置。其他的版本可能也存在同样的情况,此文记录相关信息及如何解决。部署自定义资源失败,提示信息:
1 | Exception ( Monitor Deploy ) |
在官方的 Issues 中找到了类似的问题:
可以看到,一开始是的确有 bug 的,但后来修复了。然后我根据 issue 中提到的需要配置的customResources
到官方文档中搜索,没有搜索到有用的信息。又通过 Halyard
的命令行帮助也没有查找到有相关设置 customResources
的地方…… 这就很迷了啊,难道手工改 ~/.hal/config
么,总感觉不太对。
之后就发现了这个 issue:https://github.com/spinnaker/spinnaker/issues/4345
果然,官方就是没有提供对应的操作命令。
要解决这个问题,需要满足:
customResources
定义了对应的资源我是手工修改该配置的,大概就是如下这样:
1 | deploymentConfigurations: |
其他无关配置忽略了,就是在 deploymentConfigurations -> providers ->kubernetes -> customResources 下添加自定义资源列表就 ok 了。
配置后,通过
1 | hal deploy apply |
重新部署,就可以支持了。
]]>1 | Helm template failed. Error: stat template-dir: no such file or directory : exit status 1 |
spinnaker 则无法正确解析 artifact:
1 | com.netflix.spinnaker.clouddriver.artifacts.helm.HelmArtifactCredentials$FailedDownloadException: Unable to download the contents of artifact |
该问题官方 Issues 中已有提出:
除了等待官方的兼容之外,如果我们用的自己的私库,可控的话,可是考虑通过配置使生成的 index.yaml
为绝对路径来解决。
通过 chartmuseum --help
命令我们可以看到 chartmuseum
支持 --chart-url value
这样的一个配置:
1 | --chart-url value absolute url for .tgzs in index.yaml [$CHART_URL] |
即,可以通过这个参数指定 index.yaml
中的 .tgzs
包绝对路径。
所以,如果你通过二进制运行,则:
1 | chartmuseum --port 8080 --storage local --storage-local-rootdir /path/to/charts --chart-url http://host:8080 |
。如果通过 docker
方式运行则这样指定:
1 | docker run --rm -it \ |
注:阿里云Kubernetes服务已经内置提供了Helm/Chart支持,可以直接使用
https://help.aliyun.com/document_detail/58587.html
Helm 可以理解为 Kubernetes 的包管理工具,可以方便地发现、共享和使用为Kubernetes构建的应用,它包含几个基本概念
Helm 采用客户端/服务器架构,有如下组件组成:
因为 helm 服务端(Tiller)是部署在 k8s 集群上面,客户端需要的 k8s 集群信息就是本地的集群信息。所以需要预先安装 kubectl ,配置好 k8s 集群通信。
安装方式可参考Kubernetes 本地测试环境 MiniKube 介绍及国内安装配置#kubectl 安装一节。
如果是 minikube 集群则 minikube 会自动配置集群信息,其他集群请从集群管理员获得集群信息配置在 ~/.kube/config 中。
配置后,通过 kubectl cluster-info
检查是否OK。
参照官方文档:https://helm.sh/docs/using_helm/#installing-helm
tar -zxvf helm-v2.0.0-linux-amd64.tgz
helm
放到执行路径 mv linux-amd64/helm /usr/local/bin/helm
搞定。
此时如果不需要安装服务端(Tiller)的话我们通过如下命令来初始化客户端就ok:
1 | helm init --client-only --stable-repo-url https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts/ |
chart 仓库使用: https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts/,参考的是阿里的 helm 文档。
官方文档:https://helm.sh/docs/using_helm/#installing-tiller
这个由于要下载镜像,国内网络你懂的。索性阿里提供国内镜像,所以参照阿里的文章: 利用Helm简化Kubernetes应用部署 安装。
其中镜像在阿里云杭州区的 google_containers/tiler
仓库 https://cr.console.aliyun.com/images/cn-hangzhou/google_containers/tiller/detail
1 | helm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.13.1 --stable-repo-url https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts/ |
1 | kubectl create serviceaccount --namespace kube-system tiller |
前两行也可以替换为应用 k8s 配置:
1 |
|
1 | helm version |
微软也提供了helm 仓库的镜像, 找到这儿的朋友推荐使用微软的镜像:
Helm 催生了社区的发展壮大,越来越多的软件提供商,如 Bitnami 等公司,开始提供高质量的 Charts。您可以在 https://kubeapps.com/ 中寻找和发现已有的 Charts。
]]>这些特点,让其在云原生的场景下更贴合。而traefik本身的特性远不止如此。大家有兴趣可阅读它的官方文档:https://docs.traefik.io/,而且国内的资料介绍也很多。
本篇文章的背景是因为工作中用到了版本2中的一些特性,但国内关于新版的介绍资料较少,遂对 back to traefik 2.0 这篇文章的核心部分做了简单翻译提取。如有理解错漏,请指正。
目前(2019年5月21日)2.0 正在开发中,alpha4 版本在 4月17日发布。我们一起看看 2.0 有哪些东西。
从图中可以看到,配置氛围静态配置和动态配置。
历时三年,经过大量的讨论和开发,目前 traefik 完全支持 TCP 协议。
示例如下:
1 | [entrypoints] |
使用 FileProvider,重定向所有 27010 端口的请求到数据库服务。
来看更厉害的,Traefik 支持基于 SNIs 的路由:
服务器名称指示(SNI)是TLS计算机网络协议的扩展,通过该协议,客户机在握手过程开始时指示要连接到哪个主机名。这允许服务器在相同的IP地址和TCP端口号上显示多个证书,因此允许由相同的IP地址提供多个安全(HTTPS)站点(或TLS上的任何其他服务),而不需要所有这些站点使用相同的证书。它在概念上等同于HTTP/1.1基于名称的虚拟主机,但适用于HTTPS。所需的主机名在原始SNI扩展中没有加密,因此窃听者可以看到请求的是哪个站点。
1 | [entrypoints] |
更更厉害的呢?Traefik 可以牛到在同一个端口上同时支持 HTTP 和 TCP。
1 | [entrypoints] |
在它的第一个alpha版本中,Traefik 只在 FileProvider 中可以启用TCP路由,但是请做好准备,因为它很快就会对其他 Providers 可用!
过去很多特性被嵌入到 Traefik 中,但没有针对性的定制和对应选项去调整。
Middlewares 填补了这一空白。你可以做到针对每一个 router 开启和调整相关特性。
Middlewares 是在请求实际转发到服务之前对其进行操作的组件,如果不满足要求的条件,甚至可以决定不转发请求。
在发布时,Traefik附带了以下中间件:
更重要的,项目重构了代码,使未来更方便地提供更多的中间件。
在过去两年里,社区围绕“更好的 ingress”进行了大量的讨论,回看之前的 kubernettes provider,我们认为还有改进的空间。对于V2,如果我们想让 k8s 用户直接从中受益(比如 TCP 和 middleware),而不被大量 annotations 困扰,我们必须提供新的选择。
在备选方案中,CRD 越来越受欢迎,因为它们解决了ingress规范的缺点。受 Heptio 在 Contour 项目中的 IngressRoute 启发,我们扩展了这个规范来实现每个Traefik特性。
下面是一个例子:
1 | apiVersion: traefik.containo.us/v1alpha1 |
Traefik 现在支持更有表现力的语法来定义 router rules,包含 and
, or
和 parenthesis
(括号)!
可用的 matchers 有 Headers, HeadersRegexp, Host, HostRegexp, Method, Path, PathPrefix, and Query.
由于 TCP 是完全不同的东东,所以目前只支持专用的 matcher:HostSNI。
Since TCP is a whole different world, for now, it only supports a dedicated matcher: HostSNI.
来看个例子:
1 | rule = (Host(`api.domain`) && PathPrefix(`/v2`)) || Host(`api-v2.domain`) |
Traefik 一直与很多 providers 兼容, 这是他的优势之一:无论你的基础设施是什么,从裸机到各种协调器和集群方案,Traefik 都能搞定!
但从 V2 开始,我们更进一步做到了让用户在一个 provider 中声明元素(middlewares, services, routers)在其他不同的 provider 中使用。
让我们来看一个例子,例子中在配置文件中(file provider)声明了一个认证 middleware,在 Docker label(Docker Provider)中被使用。
1 | # somewhere in a configuration file for the file provider |
1 | # somewhere in a docker compose file |
当然,你可以在一个 provider 中声明 router 指向其他 provider 中定义的 services。
为了添加 TCP 支持到 Traefik 中,我们几乎重新思考了所有事情,从集群的大门-entrypoint 开始。
我们起初的想法是每个入口点(包括其端口)指定一种类型的协议。不幸的是,在某些情况下,特别是当人们使用Traefik为多个集群路由请求时,我们认为它的限制太大。因此,我们允许每个入口点有多个协议。
之后,讨论依然存在其他方面,我们一直在寻找让用户在配置TLS termination 或 passthrough 时有更好的控制方式。在路由器级别启用TLS的想法赢得了头脑风暴,然后通过了概念证明的测试(我们团队中有相当多的概念证明分支,以至于它几乎成为了一个迷因)。
下面是三个路由器监听同一个入口点的例子,第一个 router 完成TLS连接(在HTTPS上),第二个 router 完成TLS连接(在TCP上),第三个 router 向后传递,将 TLS连接的细节处理交给服务本身。
1 | [entrypoints] |
这只是表面上的一个小调整,但是对代码有很大的影响,我们重新编写了配置解析器。这个新系统确保 Traefik 中的每个选项都有相同的路径,无论是否用TOML [something.that.is.here]
或是容器上的一个 label something.that.is.there
,或是一个键(放在键值存储) something/that/is/somewhere
,或者是将来可能可用的任何东西。
我们更新了文档结构来帮助用户快速的理解如何配置 Traefik。同时,我们列出了配置结构的大纲,让专业用户更方便的学习配置的细节。
是的,更多的内容还在路上,只是我们(团队)迫不及待去分享了。
新的 WebUI, 剩余的 providers, metrics, UDP, YAML, TLS stores & options, canary(金丝雀), 更多的文档 — 什么都是有可能的.
除了重新启用其他 providers (alpha只支持 file、Docker和k8s),并为所有这些 provider 启用TCP(目前只支持 file)之外,路线图还具有很多特性。
首先,最明显的是,一个贡献者正在积极地开发一个经过改进的WebUI,从我们看到的模型来看,它看起来非常有前途。我们希望这个新UI能够帮助您无缝地浏览数百条路由。
其次,我们正在对TLS配置进行改进,包括存储和选项,他们应该自解释。
再次,既然我们已经尝试了一种新的协议,那么我们不妨添加其他协议(如UDP)。
2.0 文档地址:https://docs.traefik.io/v2.0/,大的变更地址:https://github.com/containous/traefik/blob/master/CHANGELOG.md.
]]>chrome 安装插件DNS Flusher for Chrome
或者 Flush DNS & close sockets
DNS Flusher for Chrome
点击清除并且刷新当前页面,或者,在浏览器输入 reload
然后 tab
按键之后再输入网址,就是清除DNS 后访问.
Flush DNS & close sockets
这个插件点击清除,但是不刷新.但是好处是可以用快捷键 ctrl + shrift + f
完成刷新
这两款插件,都需要指定 --enable-net-benchmarking
的情况下运行 chrome, 如果不知到如何指定请继续往下看
--enable-net-benchmarking
参数运行修改 chrome 的快捷方式, 在目标项后面添加--enable-net-benchmarking
这样,要求必须通过此快捷方式启动 chrome.
ctrl + r
运行 regedit
,
找到HKEY_LOCAL_MACHINE–>SOFTWARE–>Classes–>ChromeHTML–>shell–>open–>command
对应的配置,在其默认的值中添加 --enable-net-benchmarking
这个配置是为了让各种方式启动都能让配置生效
配置后,我们需要关闭原浏览器。注意,如果开启了 google 的桌面通知等后台功能,仅仅是关闭浏览器窗口是无效的,必须完全退出进程才可以,通常检查任务栏通知区域确保无 chrome 图标。进一步可以检查进程管理。如果是锁定到任务栏的 chrome,需要先解除锁定.
然后我们使用配置了参数的快捷方式运行 chrome.
运行后,请打开 chrome://version/
页面,确认其中”命令行”一段有 --enable-net-benchmarking
,这样就表示配置成功了.
保证成功后,我们再在任务栏中选择当前浏览器,锁定到任务栏.之前的快捷方式可以选择删除了.
现在,使用插件测试下吧~
关于 chrome DNS 缓存,可参考另一篇《关于 chrome DNS 缓存》
]]>对于初学者来讲,即使有一些教程,从零搭建 Kubernetes 具有一定的门槛。尤其是其中的很多原理概念无法快速掌握,搭建会遇到各种坑,这会非常打击学习者的积极性。好在 Kubernetes 社区提供了可以在本地开发和体验的极简集群实现 MiniKube,对于入门学习来说很方便。本文介绍 Minikube 的简单信息和安装方式。
Minikube 是一个轻量级的 Kubernetes 实现,会在本机创建一台虚拟机,并部署一个只包含一个节点的简单集群。 Minikube 适用于 Linux, Mac OS 和 Windows 系统。Minikube CLI 提供了集群的基本引导操作,包括启动、停止、状态和删除。
目前 Minikube 支持的 kubernetes features 有:
minikube tunnel
minikube start -p <name>
minikube service
minikube dashboard
start --container-runtime
在实际学习体验中,绝大部分的 kubernetes 操作和很多社区工具都是兼容 Minikube 的,可以放心采用 Minikube。
注:
- 更新或切换安装包,需要
minikube delete
删除现有虚拟机rm -rf ~/.minikube
删除原本地缓存的文件- 替换新的安装包,重新创建 minikube 环境
- 本文使用 2019年5月14日 最新的 v1.0.1 版本介绍
先决条件,直接摘录官方 README,其中 kubectl
略作说明,虚拟化支持大家请自行配置。
--vm-driver=none
option that runs the Kubernetes components on the host and not in a VM. Docker is required to use this driver but no hypervisor. If you use --vm-driver=none
, be sure to specify a bridge network for docker. Otherwise it might change between network restarts, causing loss of connectivity to your cluster.kubectl 安装比较简单,根据系统环境参照官方文档直接选择一种合适的方式即可。 最新版本号就是 kubernetes 版本号,通过 https://storage.googleapis.com/kubernetes-release/release/stable.txt
查看。
本人 windows 直接选用最新的二进制文件,下载后放入 PATH
:
https://storage.googleapis.com/kubernetes-release/release/v1.14.1/bin/windows/amd64/kubectl.exe
如果速度太慢,可以考虑镜像:http://mirror.azure.cn/kubernetes/kubectl/v1.14.1/bin/windows/amd64/kubectl.exe
重点来了。国内网络原因,免去后面镜像下载等各种麻烦,我们使用阿里修改版的 minikube 安装包(感谢阿里小哥的勤劳)。
如果你看这篇文章的时候有更新,和 kubectl 下载地址一样,将地址中版本号修改掉就OK。
1 | http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.0.1/minikube-windows-amd64.exe |
根据如下地址下载,并如 kubectl
一样,添加到 PATH
中。
从 http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v0.27.0/minikube-windows-amd64.exe
下载并重命名为minikube.exe
。
1 | curl -Lo minikube http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.0.1/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ |
1 | curl -Lo minikube http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v1.0.1/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ |
缺省Minikube使用VirtualBox驱动来创建Kubernetes本地环境,无代理环境使用如下命令:
1 | minikube start --registry-mirror=https://dockerhub.azk8s.cn |
有代理的话,在命令后面加入代理配置:
1 | minikube start --registry-mirror=https://dockerhub.azk8s.cn --docker-env HTTP_PROXY=http://proxy_user:proxy_password@proxy_host:proxy_port --docker-env HTTPS_PROXY=https://proxy_user:proxy_password@proxy_host:proxy_port |
如果有私库需要配置 --insecure-registry
的话,也同样直接加在后面。
如:
1 | minikube start --registry-mirror=https://dockerhub.azk8s.cn --insecure-registry docker.examplea.com --insecure-registry docker.exampleb.com |
--registry-mirror=
可以配置为你觉得好用的国内 docker 镜像地址。这个可以让你后续下载各种 docker hub 镜像的时候少去很多烦恼。
启动的过程需要下载搭建集群的镜像和创建虚拟机及相关配置,需要一点时间,适用阿里的 Minikube
应该可以看到速度还是不错的。
看到成功之后,我们就可以测试一下了。
kubectl version
查看客户端和服务端(kubernetes)集群的版本号,看看 Minikube 给我们安装的是什么版本,目前应该是最新版本 1.14.1
,也可以通过 kubectl cluster-info
查看集群的基本信息:
kubernetes 官方有配套的 dashboard 项目,可以帮我们直观了解集群运行情况。Minikube 可以很方便帮助我们安装:
1 | minikube dashboard |
安装完成后会自动帮我们在浏览器中打开 dashboard。
如果,此处控制台成功启动,就可以说是大功告成,后续的镜像下载(gcr之类镜像的还是要单独配置)什么的基本就不会有问题了。
本文参考:
]]>之前的 hostname (主机名)修改可以直接通过 hostname
命令临时修改(重启失效),或通过在 /etc/sysconfig/network
文件添加 HOSTNAME=xxx
永久修改等方式。
centos/redhat 7 中有个 hostnamectl
命令,很好用,使用方式如下:
1 | hostnamectl [--static|--transient|--pretty] set-hostname <host-name> |
如果是需要查看当前系统状态可以用:
1 | hostnamectl [--static|--transient|--pretty] status |
上面两条命令中的可选参数 [--static|--transient|--pretty]
是限定命令是否只针对特定类型的 hostname,不指定的话则是所有。修改后不用重启,立即生效。
当然,这条命令只会修改主机名,/etc/hosts 中的配置需要自己跟随修改。
参考:
]]>文中的内容主要是 Centos7 下的操作,其他系统如果有不共通地方,请自行调整。
hostname自己命名即可,但集群中的hostname主机名必须不同!HostName是为了区分不同的主机,防止冲突。云服务商的ECS默认Host的名称一般不会冲突,但是我们最好自己设置下,便于管理。
Centos7 参考Centos 7 Hostname 配置,其他系统自行Google。
在每台主机上执行以下命令关闭防火墙
1 | systemctl stop firewalld |
如果开启SELinux,很多情况要进行相关的配置,我们这里直接关闭SELinux,减少配置。
在每台主机上执行以下命令关闭SELinux。
1 | setenforce 0 |
上面的是临时设置,重启后就无效了。如果需要永久生效,编辑 /etc/selinux/config
文件,修改 SELINUX=disabled
1 | sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config |
如通不配置,安装K8S会提示:
1 | [ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1 |
先执行:
1 | cat <<EOF > /etc/sysctl.d/k8s.conf |
然后执行:
1 | sysctl -p |
使用如下命令关闭 Swap:
1 | swapoff -a && sed -i '/swap/d' /etc/fstab |
前面关闭当前,后面永久关闭。
否则在当前机器上安装Kubernetes会提示:
1 | [ERROR Swap]: running with swap on is not supported. Please disable swap |
如果机器已经存在老版本Docker,用如下命令删除(不确定也可直接执行):
1 | sudo yum remove docker \ |
1 | sudo yum install -y yum-utils \ |
1 | sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo && \ |
stable
仓库(和上面二选一)1 | sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && \ |
1 | sudo yum install docker-ce docker-ce-cli containerd.io |
1 | # 查看版本 |
1 | sudo systemctl start docker |
1 | mkdir -p /etc/systemd/system/docker.service.d |
1 | vim /usr/lib/systemd/system/docker.service |
关于镜像加速包括 gcr.io、quay.io 建议看文章:Docker 国内镜像加速
1 | sudo mkdir -p /etc/docker |
1 | vim /etc/docker/daemon.json |
1 | systemctl daemon-reload |
1 | docker stop $(docker ps -aq) |
国内目前可用的加速器有很多,Azure 是我使用下来较为全面和稳定的,阿里云的我也常用,但是需要注册且不包含gcr和quay的mirror统一入口。如果你已经有阿里云的加速器地址,可以DockerHub官方镜像使用阿里云配置,gcr和quay另选。更多的加速器在后面提供,大家可以根据情况自由选择。
Azure 不仅有镜像加速,还有其他的安装包等加速,官方:http://mirror.azure.cn/
DockerHub 的官方镜像可以通过配置 /etc/docker/daemon.json
的 registry-mirrors
,然后正常使用即可,拉取(pull)镜像的地址并不用更改。非DockerHub官方的镜像,无法通过此方式获得加速,必须用修改镜像地址为加速器专用地址的方式进行。修改方式后面根据情况会分别讲述。
1 | sudo mkdir -p /etc/docker |
registry-mirrors
可配置多个镜像加速器,我用阿里云和Azure基本可达到近8-10M/s,也很稳定。多个镜像加速器是否会有很大的速度或稳定性提升没有严格测试过,我配置两个用的话小镜像略有影响,所以还是配一个。如果你有测试结论,请告诉我。
对于非 k8s.gcr.io 的gcr.io镜像,一般将域名修改为加速器地址,仓库地址不变即可。
比如,原来要拉取的镜像是 gcr.io/<repo-name>/<image-name>:<version>
形式,使用命令:
1 | docker pull gcr.io/<repo-name>/<image-name>:<version> |
使用Azure加速修改后的地址是 gcr.azk8s.cn/<repo-name>/<image-name>:<version>
,命令如下:
1 | docker pull gcr.azk8s.cn/<repo-name>/<image-name>:<version> |
当然,下载后的镜像tag和原tag自然不同,如果需要按照原tag使用,执行如下命令重新tag就OK:
1 | docker tag gcr.azk8s.cn/<repo-name>/<image-name>:<version> gcr.io/<repo-name>/<image-name>:<version> |
如果tag后不希望 images 里保留过多tag,可以删除:
1 | docker rmi gcr.azk8s.cn/<repo-name>/<image-name>:<version> |
其他的加速方式一致
k8s.gcr.io 下的镜像被映射到加速器的 google_containers
仓库,
即原: k8s.gcr.io/<image-name>:<version>
被映射为: gcr.azk8s.cn/google_containers/<image-name>:<version>
。
如:
1 | k8s.gcr.io/pause-amd64:3.1 |
quay.io 和 gcr.io一致,
原: quay.io/<repo-name>/<image-name>:<version>
修改后: quay.azk8s.cn/<repo-name>/<image-name>:<version>
镜像加速器 | 镜像加速器地址 | 专属加速器? | 其它加速? |
---|---|---|---|
Docker 中国官方镜像 | https://registry.docker-cn.com | Docker Hub | |
DaoCloud 镜像站 | http://<your_code>.m.daocloud.io | 可登录,系统分配 | Docker Hub |
Azure 中国镜像 | https://dockerhub.azk8s.cn | Docker Hub、GCR、Quay | |
科大镜像站 | https://docker.mirrors.ustc.edu.cn | Docker Hub、GCR、Quay | |
阿里云 | https://<your_code>.mirror.aliyuncs.com | 需登录,系统分配 | Docker Hub |
七牛云 | https://reg-mirror.qiniu.com | Docker Hub、GCR、Quay | |
网易云 | https://hub-mirror.c.163.com | Docker Hub | |
腾讯云 | https://mirror.ccs.tencentyun.com | Docker Hub |
“微服务架构”这一术语在前几年横空出世,用于描述这样一种特定的软件设计方法,即以若干组可独立部署的服务的方式进行软件应用系统的设计。尽管这种架构风格尚无明确的定义,但其在下述方面还是存在一定的共性,即围绕业务功能的组织、自动化部署、端点智能、以及在编程语言和数据方面进行去中心化的控制。
“微服务”——这是在“软件架构”这条熙熙攘攘的大街上出现的又一个新词语。我们很容易对它不屑一顾,但是这个小小的术语却描述了一种引人入胜的软件系统风格。在近几年中,我们越来越多的看到许多项目使用了这种风格,而且就目前来说结果都是不错的,以至于许多ThoughtWorker都把它看作构建企业应用系统的默认风格。然而,很不幸的是,我们找不到有关它的概要信息,即什么是微服务风格,以及如何设计微服务风格的架构。
简而言之,微服务架构风格[1]这种开发方法,是以开发一组小型服务的方式来开发一个独立的应用系统。其中每个小型服务都运行在自己的进程中,并经常采用HTTP资源API这样轻量的机制来相互通信。这些服务围绕业务功能进行构建,并能通过全自动的部署机制来进行独立部署。这些微服务可以使用不同的语言来编写,并且可以使用不同的数据存储技术。对这些微服务,我们仅做最低限度的集中管理。
在开始介绍微服务风格之前,将其与单块(monolithic)风格进行对比还是很有意义的:一个单块应用系统是以一个单个单元的方式来构建的。企业应用系统经常包含三个主要部分:客户端用户界面、数据库和服务端应用系统。客户端用户界面包括HTML页面和运行在用户机器的浏览器中的JavaScript。数据库中包括许多表,这些表被插入一个公共的且通常为关系型的数据库管理系统中。这个服务端的应用系统就是一个单块应用——一个单个可执行的逻辑程序[2]。对于该系统的任何改变,都会涉及构建和部署上述服务端应用系统的一个新版本。
这样的单块服务器是构建上述系统的一种自然的方式。处理用户请求的所有逻辑都运行在一个单个的进程内,因此能使用编程语言的基本特性,来把应用系统划分为类、函数和命名空间。通过精心设计,得以在开发人员的笔记本电脑上运行和测试这样的应用系统,并且使用一个部署流水线来确保变更被很好地进行了测试,并被部署到生产环境中。通过负载均衡器运行许多实例,来将这个单块应用进行横向扩展。
单块应用系统可以被成功地实现,但是渐渐地,特别是随着越来越多的应用系统正被部署到云端,人们对它们开始表现出不满。软件变更受到了很大的限制,应用系统中一个很小部分的一处变更,也需要将整个单块应用系统进行重新构建和部署。随着时间的推移,单块应用逐渐难以保持一个良好的模块化结构,这使得它变得越来越难以将一个模块的变更所产生的影响控制在该模块内。当对系统进行扩展时,不得不扩展整个应用系统,而不能仅扩展该系统中需要更多资源的那些部分。
图1: 单块应用和微服务
这些不满催生出了微服务架构风格:以构建一组小型服务的方式来构建应用系统。除了这些服务能被独立地部署和扩展之外,每一个服务还能提供一个稳固的模块边界,甚至能允许使用不同的编程语言来编写不同的服务。这些服务也能被不同的团队来管理。
我们并不认为微服务风格是一个新颖或创新的概念,它的起源至少可以追溯到Unix的设计原则。但是我们觉得,考虑微服务架构的人还不够多,并且如果对其加以使用,许多软件的开发工作能变得更好。
虽然不能说存在微服务架构风格的正式定义,但是可以尝试描述我们所见到的、能够被贴上“微服务”标签的那些架构的共性。下面所描述的这些共性,并不是所有的微服务架构都完全具备,但是我们确实期望大多数微服务架构都具备这些共性中的大多数特性。尽管我们两位作者已经成为这个相当松散的社区中的活跃成员,但我们的本意还是描述我们两人在自己所在和所了解的团队工作中所看到的情况。特别要指出,我们不会制定大家需要遵循的微服务的定义。
自我们从事软件行业以来,发现大家都有“把组件插在一起来构建系统”的愿望,就像在物理世界中所看到的那样。在过去几十年中,我们已经看到,在公共软件库方面已经取得了相当大的进展,这些软件库是大多数编程语言平台的组成部分。
当谈到组件时,会碰到一个有关定义的难题,即什么是组件?我们的定义是:一个组件就是一个可以独立更换和升级的软件单元。
微服务架构也会使用软件库,但其将自身软件进行组件化的主要方法是将软件分解为诸多服务。我们将软件库(libraries)定义为这样的组件,即它能被链接到一段程序,且能通过内存中的函数来进行调用。然而,服务(services)是进程外的组件,它们通过诸如web service请求或远程过程调用这样的机制来进行通信(这不同于许多面向对象的程序中的service object概念[3])。
以使用服务(而不是以软件库)的方式来实现组件化的一个主要原因是,服务可被独立部署。如果一个应用系统[4]由在单个进程中的多个软件库所组成,那么对任一组件做一处修改,都不得不重新部署整个应用系统。但是如果该应用系统被分解为多个服务,那么对于一个服务的多处修改,仅需要重新部署这一个服务。当然这也不是绝对的,一些变更服务接口的修改会导致多个服务之间的协同修改。但是一个良好的微服务架构的目的,是通过内聚的服务边界和服务协议方面的演进机制,来将这样的修改变得最小化。
以服务的方式来实现组件化的另一个结果,是能获得更加显式的(explicit)组件接口。大多数编程语言并没有一个良好的机制来定义显式的发布接口。通常情况下,这样的接口仅仅是文档声明和团队纪律,来避免客户端破坏组件的封装,从而导致组件间出现过度紧密的耦合。通过使用显式的远程调用机制,服务能更容易地规避这种情况。
如此使用服务,也会有不足之处。比起进程内调用,远程调用更加昂贵。所以远程调用API接口必须是粗粒度的,而这往往更加难以使用。如果需要修改组件间的职责分配,那么当跨越进程边界时,这种组件行为的改动会更加难以实现。
近似地,我们可以把一个个服务映射为一个个运行时的进程,但这仅仅是一个近似。一个服务可能包括总是在一起被开发和部署的多个进程,比如一个应用系统的进程和仅被该服务使用的数据库。
当在寻求将一个大型应用系统分解成几部分时,公司管理层往往会聚焦在技术层面上,这就意味着要组建用户界面团队、服务器端团队和数据库团队。当团队沿着这些技术线分开后,即使要实现软件中一个简单的变更,也会发生跨团队的项目时延和预算审批。在这种情况下,聪明的团队会进行局部优化,“两害相权取其轻”,来直接把代码逻辑塞到他们能访问到的任意应用系统中。换句话说,这种情况会导致代码逻辑散布在系统各处。这就是康威定律[5]的鲜活实例。
任何设计(广义上的)系统的组织,都会产生这样一个设计,即该设计的结构与该组织的沟通结构相一致。——梅尔文•康威(Melvyn Conway), 1967年
图2:康威定律在起作用
微服务使用不同的方法来分解系统,即根据业务功能(business capability)来将系统分解为若干服务。这些服务针对该业务领域提供多层次、广泛的软件实现,包括用户界面、持久性存储以及任何对外的协作性操作。因此,团队是跨职能的,它拥有软件开发所需的全方位的技能:用户体验、数据库和项目管理。
图3:被团队边界所强化的服务边界
以上述方式来组织团队的公司是www.comparethemarket.com。跨职能团队负责构建和运维每个产品,而每个产品被拆分为多个独立的服务,彼此通过一个消息总线来通信。
一个微服务应该有多大?
尽管许多人已经习惯于用“微服务”来概括描述这种这种架构风格,但是这个名字确实会不幸地引发大家对服务规模的关注,并且产生有关什么是“微”的争论。在与微服务从业者的交谈中,我们看到了有关服务的一系列规模。所听到的最大的一个服务规模,是遵循了亚马逊的“两个比萨团队”(即一个团队可以被两个比萨所喂饱)的理念所形成的,这意味着这个团队不会多于12人。对于规模较小的服务,我们已经看到一个6人的团队在支持6个服务。
这引出了一个问题,即“每12人做一个服务”和“每人做一个服务”这样有关服务规模的差距,是否已经大到不能将两者都纳入微服务之下?此时,我们认为最好还是把它们归为一类,但是随着探索的深入,我们将来极有可能会改变主意。
大型单块应用系统也可以始终根据业务功能来进行模块化设计,虽然这并不常见。当然,我们会敦促构建单块应用系统的大型团队根据业务线来将自己分解为若干小团队。在这方面,我们已经看到的主要问题是,他们往往是一个团队包含了太多的业务功能。如果这个“单块”跨越了许多模块的边界,那么这个团队的每一个成员都难以记住所有模块的业务功能。此外,我们看到这些模块的边界需要大量的团队纪律来强制维持。而实现组件化的服务所必要的更加显式的边界,能更加容易地保持团队边界的清晰性。
我们所看的大部分应用系统的开发工作都使用项目模型:目标是交付某一块软件,之后就认为完工了。一旦完工后,软件就被移交给维护团队,接着那个构建该软件的项目团队就会被解散。
微服务的支持者们倾向于避免使用上述模型,而宁愿采纳“一个团队在一个产品的整个生命周期中都应该保持对其拥有”的理念。通常认为这一点源自亚马逊的“谁构建,谁运行”的理念,即一个开发团队对一个在生产环境下运行的软件负全责。这会使开发人员每天都关注软件是如何在生产环境下运行的,并且增进他们与用户的联系,因为他们必须承担某些支持工作。
这样的“产品”理念,是与业务功能的联动绑定在一起的。它不会将软件看作是一个待完成的功能集合,而是认为存在这样一个持续的关系,即软件如何能助其客户来持续增进业务功能。
当然,单块应用系统的开发工作也可以遵循上述“产品”理念,但是更细粒度的服务,能让服务的开发者与其用户之间的个人关系的创建变得更加容易。
当在不同的进程之间构建各种通信结构时,我们已经看到许多产品和方法,来强调将大量的智能特性纳入通信机制本身。其中一个典型例子,就是“企业服务总线”(Enterprise Service Bus, ESB)。ESB产品经常包括高度智能的设施,来进行消息的路由、编制(choreography)、转换,并应用业务规则。
微服务社区主张采用另一种做法:智能端点(smart endpoints)和傻瓜管道(dumb pipes)。使用微服务所构建的各个应用的目标,都是尽可能地实现“高内聚和低耦合”——他们拥有自己的领域逻辑,并且更像是经典Unix的“过滤器”(filter)那样来工作——即接收一个请求,酌情对其应用业务逻辑,并产生一个响应。这些应用通过使用一些简单的REST风格的协议来进行编制,而不去使用诸如下面这些复杂的协议,即”WS-编制”(WS-Choreography)、BPEL或通过位于中心的工具来进行编排(orchestration)。
微服务最常用的两种协议是:带有资源API的HTTP“请求-响应”协议,和轻量级的消息发送协议[6]。对于前一种协议的最佳表述是:
成为Web,而不是躲着Web (Be of the web, not behind the web)——Ian Robinson
这些微服务团队在开发中,使用在构建万维网(world wide web)时所使用的原则和协议(并且在很大程度上,这些原则和协议也是在构建Unix系统时所使用的)。那些被使用过的HTTP资源,通常能被开发或运维人员轻易地缓存起来。
最常用的第二种协议,是通过一个轻量级的消息总线来进行消息发送。此时所选择的基础设施,通常是“傻瓜”(dumb)型的(仅仅像消息路由器所做的事情那样傻瓜)——像RabbitMQ或ZeroMQ那样的简单实现,即除了提供可靠的异步机制(fabric)以外不做其他任何事情——智能功能存在于那些生产和消费诸多消息的各个端点中,即存在于各个服务中。
在一个单块系统中,各个组件在同一个进程中运行。它们相互之间的通信,要么通过方法调用,要么通过函数调用来进行。将一个单块系统改造为若干微服务的最大问题,在于对通信模式的改变。仅仅将内存中的方法调用转换为RPC调用这样天真的做法,会导致微服务之间产生繁琐的通信,使得系统表现变糟。取而代之的是,需要用更粗粒度的协议来替代细粒度的服务间通信。
使用中心化的方式来对开发进行治理,其中一个后果,就是趋向于在单一技术平台上制定标准。经验表明,这种做法会带来局限性——不是每一个问题都是钉子,不是每一个方案都是锤子。我们更喜欢根据工作的不同来选用合理的工具。尽管那些单块应用系统能在一定程度上利用不同的编程语言,但是这并不常见。
如果能将单块应用的那些组件拆分成多个服务,那么在构建每个服务时,就可以有选择不同技术栈的机会。想要使用Node.js来搞出一个简单的报表页面?尽管去搞。想用C++来做一个特别出彩的近乎实时的组件?没有问题。想要换一种不同风格的数据库,来更好地适应一个组件的读取数据的行为?可以重建。
微服务和SOA
当我们谈起微服务时,一个常见的问题就会出现:是否微服务仅仅是十多年前所看到的“面向服务的架构”(Service Oriented Architecture, SOA)?这样问是有道理的,因为微服务风格非常类似于一些支持SOA的人所赞成的观点。然而,问题在于SOA这个词儿意味着太多不同的东西。而且大多数时候,我们所遇到的某些被称作”SOA”的事物,明显不同于本文所描述的风格。这通常由于它们专注于ESB,来集成各个单块应用。
特别地,我们已经看到如此之多的面向服务的拙劣实现——从将系统复杂性隐藏于ESB中的趋势[7],到花费数百万进行多年却没有交付任何价值的失败项目,到顽固抑制变化发生的中心化技术治理模型——以至于有时觉得其所造成的种种问题真的不堪回首。
当然,在微服务社区投入使用的许多技术,源自各个开发人员将各种服务集成到各个大型组织的经验。“容错读取”(Tolerant Reader)模式就是这样一个例子。对于Web的广泛使用,使得人们不再使用一些中心化的标准,而使用一些简单的协议。坦率地说,这些中心化的标准,其复杂性已经达到令人吃惊的程度。(任何时候,如果需要一个本体来管理其他各个本体,那么麻烦就大了。)
这种常见的SOA表现,已使得一些微服务的倡导者完全拒绝将自己贴上SOA的标签。尽管其他人会将微服务看作是SOA的一种形式[8],也许微服务就是以正确的形式来实现面向服务的SOA。不管是哪种情况,SOA意味着如此之多的不同事物,这表明用一个更加干净利落的术语来命名这种架构风格是很有价值的。
当然,仅仅能做事情,并不意味着这些事情就应该被做——不过用微服务的方法把系统进行拆分后,就拥有了技术选型的机会。
相比选用业界一般常用的技术,构建微服务的那些团队更喜欢采用不同的方法。与其选用一组写在纸上已经定义好的标准,他们更喜欢编写一些有用的工具,来让其他开发者能够使用,以便解决那些和他们所面临的问题相似的问题。这些工具通常源自他们的微服务实施过程,并且被分享到更大规模的组织中,这种分享有时会使用内部开源的模式来进行。事实上,现在git和github已经成为首选版本控制系统。在企业内部,开源的做法正在变得越来越普遍。
Netflix公司是遵循上述理念的好例子。将实用且经过实战检验的代码以软件库的形式共享出来,能鼓励其他开发人员以相似的方式来解决相似的问题,当然也为在需要的时候选用不同的方案留了一扇门。共享软件库往往聚焦于解决这样的常见问题,即数据存储、进程间的通信和下面要进一步讨论的基础设施的自动化。
对于微服务社区来说,日常管理开销这一点不是特别吸引人。这并不是说这个社区并不重视服务契约。恰恰相反,它们在社区里出现得更多。这正说明这个社区正在寻找对其进行管理的各种方法。像“容错读取”和“消费者驱动的契约”(Consumer-Driven Contracts)这样的模式,经常被运用到微服务中。这些都有助于服务契约进行独立演进。将执行“消费者驱动的契约”做为软件构建的一部分,能增强开发团队的信心,并提供所依赖的服务是否正常工作的快速反馈。实际上,我们了解到一个在澳洲的团队就是使用“消费者驱动的契约”来驱动构建多个新服务的。他们使用了一些简单的工具,来针对每一个服务定义契约。甚至在新服务的代码编写之前,这件事就已经成为自动化构建的一部分了。接下来服务仅被构建到刚好能满足契约的程度——这是一个在构建新软件时避免YAGNI[9]困境的优雅方法。这些技术和工具在契约周边生长出来,由于减少了服务之间在时域(temporal)上的耦合,从而抑制了对中心契约管理的需求。
多种编程语言,多种选择可能
做为一个平台,JVM的发展仅仅是一个将各种编程语言混合到一个通用平台的最新例证。近十年以来,通过在平台外层实现更高层次的编程语言,来利用更高层次的抽象,已经成为一个普遍做法。同样,在平台底层以更低层次的编程语言编写性能敏感的代码也很普遍。然而,许多单块系统并不需要这种级别的性能优化,另外DSL和更高层次的抽象也不常用(这令我们感到失望)。相反,许多单块应用通常就使用单一编程语言,并且有对所使用的技术数量进行限制的趋势[10]。
或许去中心化地治理技术的极盛时期,就是亚马逊的“谁构建,谁运行”的理念开始普及的时候。各个团队负责其所构建的软件的所有工作,其中包括7x24地对软件进行运维。“将运维这一级别的职责下放到团队”这种做法,目前绝对不是主流。但是我们确实看到越来越多的公司,将运维的职责交给各个开发团队。Netflix就是已经形成这种风气的另一个组织[11]。避免每天凌晨3点被枕边的寻呼机叫醒,无疑是在程序员编写代码时令其专注质量的强大动力。而这些想法,与那些传统的中心化技术治理的模式具有天壤之别。
去中心化地管理数据,其表现形式多种多样。从最抽象的层面看,这意味着各个系统对客观世界所构建的概念模型各不相同。当在一个大型的企业中进行系统集成时,这是一个常见的问题。比如对于“客户”这个概念,从销售人员的视角看,就与从支持人员的视角看有所不同。从销售人员的视角所看到的一些被称之为“客户”的事物,或许在支持人员的视角中根本找不到。而那些在两个视角中都能看到的事物,或许各自具有不同的属性。更糟糕的是,那些在两个视角中具有相同属性的事物,或许在语义上有微妙的不同。
上述问题在不同的应用程序之间经常出现,同时也会出现在这些应用程序内部,特别是当一个应用程序被分成不同组件时就会出现。思考这类问题的一个有效方法,就是使用领域驱动设计(Domain-Driven Design, DDD)中的“限界上下文”(Bounded Context)的概念。DDD将一个复杂的领域划分为多个限界上下文,并且将其相互之间的关系用图画出来。这一划分过程对于单块和微服务架构两者都是有用的,而且就像前面有关“业务功能”一节中所讨论的那样,在服务和各个限界上下文之间所存在的自然的联动关系,有助于澄清和强化这种划分。
“实战检验”的标准与“强制执行”的标准
微服务的下述做法有点泾渭分明的味道,即他们趋向于避开被那些企业架构组织所制定的硬性实施标准,而愉快地使用甚至传播一些开放标准,比如HTTP、ATOM和其他微格式的协议。
这里的关键区别是,这些标准是如何被制定以及如何被实施的。像诸如IETF这样的组织所管理的各种标准,只有达到下述条件才能称为标准,即该标准在全球更广阔的地区有一些正在运行的实现案例,而且这些标准经常源自一些成功的开源项目。
这些标准组成了一个世界,它区别于来自下述另一个世界的许多标准,即企业世界。企业世界中的标准,经常由这样特点的组织来开发,即缺乏用较新技术进行编程的经验,或受到供应商的过度影响。
如同在概念模型上进行去中心化的决策一样,微服务也在数据存储上进行去中心化的决策。尽管各个单块应用更愿意在逻辑上各自使用一个单独的数据库来持久化数据,但是各家企业往往喜欢一系列单块应用共用一个单独的数据库——许多这样的决策是被供应商的各种版权商业模式所驱动出来的。微服务更喜欢让每一个服务来管理其自有数据库。其实现可以采用相同数据库技术的不同数据库实例,也可以采用完全不同的数据库系统。这种方法被称作“多语种持久化”(Polyglot Persistence)。在一个单块系统中也能使用多语种持久化,但是看起来这种方法在微服务中出现得更加频繁。
图4:微服务更喜欢让每一个服务来管理其自有数据库
在各个微服务之间将数据的职责进行“去中心化”的管理,会影响软件更新的管理。处理软件更新的常用方法,是当更新多个资源的时候,使用事务来保证一致性。这种方法经常在单块系统中被采用。
像这样使用事务,有助于保持数据一致性。但是在时域上会引发明显的耦合,这样一来,在多个服务之间处理事务时会出现一致性问题。分布式事务实现难度之大是不必多言的。为此,微服务架构更强调在各个服务之间进行“无事务”的协调。这源自微服务社区明确地认识到下述两点,即数据一致性可能只要求数据在最终达到一致,并且一致性问题能够通过补偿操作来进行处理。
对于许多开发团队来说,选择这种方式来管理数据的“非一致性”,是一个新的挑战。但这通常也符合在商业上的实践做法。通常情况下,为了快速响应需求,商家们都会处理一定程度上的数据“非一致性”,通过做某种反向过程来进行错误处理。只要修复错误的成本低于“保持更大的数据一致性却导致丢了生意所产生”的成本相比,那么进行这种“非一致性”地数据管理就是值得的。
基础设施自动化技术在过去几年里已经得到长足的发展。云的演进,特别是AWS的发展,已经降低了构建、部署和运维微服务的操作复杂性。
许多使用微服务构建的产品和系统,正在被这样的团队所构建,即他们都具备极其丰富的“持续交付”和其前身“持续集成”的经验。用这种方法构建软件的各个团队,广泛采用了基础设施的自动化技术。如下图的构建流水线所示:
图5:基本的构建流水线
由于本文并不是一篇有关持续交付的文章,所以下面仅提请大家注意两个持续交付的关键特点。为了尽可能地获得对正在运行的软件的信心,需要运行大量的自动化测试。让可工作的软件达到“晋级”(Promotion)状态、从而“推上”流水线,就意味着可以在每一个新的环境中,对软件进行自动化部署。
一个单块应用程序,能够相当愉快地在上述各个环境中,被构建、测试和推送。其结果是,一旦在下述工作中进行了投入,即针对一个单块系统将其通往生产环境的通道进行自动化,那么部署更多的应用系统似乎就不再可怕。记住,持续交付的目的之一,是让“部署”工作变得“无聊”。所以不管是一个还是三个应用系统,只要部署工作依旧很“无聊”,那么就没什么可担心的了[12]。
让“沿着正确的方向做事”更容易
那些因实现持续交付和持续集成所增加的自动化工作的副产品,是一些对开发和运维人员有用的工具。现在,能完成下述工作的工具已经相当常见了,即创建工件(artefacts)、管理代码库、启动一些简单的服务、或增加标准的监控和日志功能。Web上最好的例子可能是Netflix提供的一套开源工具集,但也有其他一些好工具,包括我们已经广泛使用的Dropwizard。
我们所看到的各个团队在广泛使用基础设施自动化实践的另一个领域,是在生产环境中管理各个微服务。与前面我们对比单块系统和微服务所说的正相反,只要部署工作很无聊,那么在这一点上单块系统和微服务就没什么区别。然而,两者在运维领域的情况却截然不同。
使用各个微服务来替代组件,其结果是各个应用程序需要被设计的能够容忍这些服务所出现的故障。如果服务提供方不可用,那么任何对该服务的调用都会出现故障。客户端要尽可能优雅地应对这种情况。与一个单块设计相比,这是一个劣势。因为在处理这种情况时会引入额外的复杂性。为此,各个微服务团队在不断地反思:这些服务故障是如何影响用户体验的。Netflix公司所研发的开源测试工具Simian Army,能够诱导服务发生故障,甚至能诱导一个数据中心在工作日发生故障,来测试该应用的弹性和监控能力。
这种在生产环境中所进行的自动化测试,足以让大多数运维组织兴奋得浑身颤栗,就像即将迎来一周的长假那样。这并不是说单块架构风格不能构建先进的监控系统——只是根据我们的经验,这在单块系统中并不常见罢了。
“断路器”与“可随时上线的代码”
“断路器”(Circuit Breaker)一词与其他一些模式一起出现在《Release It!》一书中,例如隔板(Bulkhead)和超时(Timeout)。当构建彼此通信的应用系统时,将这些模式加以综合运用就变得至关重要。Netflix公司的这篇很精彩的博客解释了这些模式是如何应用的。
因为各个服务可以在任何时候发生故障,所以下面两件事就变得很重要,即能够快速地检测出故障,而且在可能的情况下能够自动恢复服务。各个微服务的应用都将大量的精力放到了应用程序的实时监控上,来检查“架构元素指标”(例如数据库每秒收到多少请求)和“业务相关指标”(例如系统每分钟收到多少订单)。当系统某个地方出现问题,语义监控系统能提供一个预警,来触发开发团队进行后续的跟进和调查工作。
这对于一个微服务架构是尤其重要的,因为微服务对于服务编制(choreography)和事件协作的偏好,会导致“突发行为”。尽管许多权威人士对于偶发事件的价值持积极态度,但事实上,“突发行为”有时是一件坏事。在能够快速发现有坏处的“突发行为”并进行修复方面,监控是至关重要的。
单块系统也能构建的像微服务一样来实现透明的监控系统——实际上,它们也应该如此。差别是,绝对需要知道那些运行在不同进程中的服务,在何时断掉了。而如果在同一个进程内使用软件库的话,这种透明的监控系统就用处不大了。
“同步调用”有害
一旦在一些服务之间进行多个同步调用,就会遇到宕机的乘法效应。简而言之,这意味着整个系统的宕机时间,是每一个单独模块各自宕机时间的乘积。此时面临着一个选择:是让模块之间调用异步,还是去管理宕机时间?在英国卫报网站,他们在新平台上实现了一个简单的规则——每一个用户请求都对应一个同步调用。然而在Netflix公司,他们重新设计的平台API将异步性构建到API的机制(fabric)中。
那些微服务团队希望在每一个单独的服务中,都能看到先进的监控和日志记录装置。例如显示“运行/宕机”状态的仪表盘,和各种运维、业务相关的指标。另外我们经常在工作中会碰到这样一些细节,即断路器的状态、当前的吞吐率和延迟,以及其他一些例子。
那些微服务的从业者们,通常具有演进式设计的背景,而且通常将服务的分解,视作一个额外的工具,来让应用开发人员能够控制应用系统中的变化,而无须减少变化的发生。控制变化并不一定意味着要减少变化——在正确的态度和工具的帮助下,软件中的变化也可以发生得频繁、快速且得到良好的控制。
每当要试图将软件系统分解为各个组件时,就会面临这样的决策,即如何进行切分——我们决定切分应用系统时应该遵循的原则是什么?一个组件的关键属性,是具有独立更换和升级的特点[13]——这意味着,需要寻找这些点,即想象着能否在其中一个点上重写该组件,而无须影响该组件的其他合作组件。事实上,许多做微服务的团队会更进一步,他们明确地预期许多服务将来会报废,而不是守着这些服务做长期演进。
英国卫报网站是一个好例子。原先该网站是一个以单块系统的方式来设计和构建的应用系统,然而它已经开始向微服务方向进行演进了。原先的单块系统依旧是该网站的核心,但是在添加新特性时他们愿意以构建一些微服务的方式来进行添加,而这些微服务会去调用原先那个单块系统的API。当开发那些本身就带有临时性特点的新特性时,这种方法就特别方便,例如开发报道一个体育赛事的专门页面。当使用一些快速的开发语言时,这样的网站页面就能被快速地整合起来。而一旦赛事结束,这样页面就可以被删除。在一个金融机构中,我们已经看到了一些相似的做法,即针对一个市场机会,一些新的服务可以被添加进来。然后在几个月甚至几周之后,这些新服务就作废了。
这种强调“可更换性”的特点,是模块化设计一般性原则的一个特例,通过“变化模式”(pattern of change)[14]来驱动模块化的实现。大家都愿意将那些能在同时发生变化的东西,放到同一个模块中。系统中那些很少发生变化的部分,应该被放到不同的服务中,以区别于那些正在经历大量变动(churn)的部分。当发现两个服务需要被同时、反复变更,就意味着它们两个需要被合并。
把一个个组件放入一个个服务中,提高了软件发布精细化的程度。对于一个单块系统,任何变化都需要做一次整个应用系统的全量构建和部署。然而,对于一个个微服务来说,只需要重新部署修改过的那些服务就够了。这能简化并加快发布过程。但缺点是:必须要考虑当一个服务发生变化时,依赖它并对其进行消费的其他服务将无法工作。传统的集成方法是使用版本化来解决这个问题。但在微服务世界中,大家更喜欢将版本化作为最后万不得已的手段来使用。我们可以通过下述方法来避免许多版本化的工作,即把各个服务设计得尽量能够容错,来应对其所依赖的服务所发生的变化。
我们写这篇文章的主要目的,是解释有关微服务的主要思路和原则。在花了一点时间做了这件事后,我们清楚地认识到,微服务架构风格是一个重要的理念——在研发企业应用系统时,值得对它进行认真考虑。我们最近已经使用这种风格构建了一些系统,并且了解到其他一些团队也赞同并正在使用这种方法。
我们所了解到的那些在某种程度上可以被称作这种架构风格的实践先驱包括:亚马逊、Netflix、英国卫报、英国政府数字化服务中心、realestate.com.au、Forward和comparethemarket.com。2013年的技术大会圈子充满了各种各样的、正在转向可归类为微服务的公司案例——包括Travis CI。另外还有大量的组织,它们长期以来一直在做着我们认为可以归类为微服务的产品,却从未使用过这个名字(这通常被标记为SOA——尽管正如我们所说,SOA会表现出各种自相矛盾的形式[15])。
尽管有这些正面的经验,但这并不意味着我们确信微服务是软件架构未来的方向。尽管到目前为止,与单块应用系统相比,我们对于所经历过的微服务的评价是积极的,但是我们也意识到这样的事实,即能供我们做出完整判断的时间还不够长。
通常,架构决策所产生的真正效果,只有在该决策做出若干年后才能真正显现。我们已经看到由带着强烈的模块化愿望的优秀团队所做的一些项目,最终构建出一个单块架构,并在几年之内不断腐化。许多人认为,如果使用微服务就不大可能出现这种腐化,因为服务的边界是明确的,而且难以随意搞乱。然而,对于那些开发时间足够长的各种系统,除非我们已经见识得足够多,否则我们无法真正评价微服务架构是如何成熟的。
我们的同事Sam Newman花了2014年的大部分时间撰写了一本书,来记述我们构建微服务的经验。如果想对这个话题进行更深入的了解,下一步就应该是阅读这本书。
有人觉得微服务或许很难成熟起来,这当然是有原因的。在组件化上所做的任何工作的成功度,取决于软件与组件的匹配程度。准确地搞清楚某个组件的边界位置应该出现在哪里,是一件困难的工作。演进式设计承认难以对边界进行正确定位,所以它将工作的重点放到了易于对边界进行重构之上。但是当各个组件成为各个进行远程通信的服务后,比起在单一进程内调用各个软件库,此时的重构就变得更加困难。跨越服务边界的代码移动就变得困难起来。接口的任何变化,都需要在其各个参与者之间进行协调。向后兼容的层次也需要被添加进来。测试也会变得更加复杂。
另一个问题是,如果这些组件不能干净利落地组合成一个系统,那么所做的一切工作,仅仅是将组件内的复杂性转移到组件之间的连接之上。这样做的后果,不仅仅是将复杂性搬了家,它还将复杂性转移到那些不再明确且难以控制的边界之上。在观察一个小型且简单的组件内部时,人们很容易觉得事情已经变得更好了,然而他们却忽视了服务之间杂乱的连接。
最后,还要考虑团队成员的技能水平。新技术往往会被技术更硬的团队所采用。对于技术更加过硬的团队而更有效的一项技术,不一定适用于技术略逊一筹的团队。我们已经看到大量这样的案例,那些技术略逊一筹的团队构建出了杂乱的单块架构。当这种杂乱发生到微服务身上时,会出现什么情况?这需要花时间来观察。一个糟糕的团队,总会构建一个糟糕的系统——在这种情况下,很难讲微服务究竟是减少了杂乱,还是让事情变得更糟。
我们听到一个合理的说法:不要一上来就以微服务架构做为起点。相反,要用一个单块系统做为起点,并保持其模块化。当这个单块系统出现了问题后,再将其分解为微服务。(尽管这个建议并不理想,因为一个良好的单一进程内的接口,通常不是一个良好的服务接口。)
因此,我们持谨慎乐观的态度来撰写此文。到目前为止,我们已经看到足够多的有关微服务风格的内容,并且觉得这是一条值得去跋涉的道路。我们不能肯定地说,道路的尽头在哪里。但是,软件开发的挑战之一,就是只能基于“目前手上拥有但还不够完善”的信息来做出决策。
若欲获取最新参考资料列表以得到更多信息,请参见微服务资源指南:http://martinfowler.com/microservices/。
注:
[1]. 2011年5月在威尼斯附近的一个软件架构工作坊中,大家开始讨论“微服务”这个术语,因为这个词可以描述参会者们在架构领域进行探索时所见到的一种通用的架构风格。2012年5月,这群参会者决定将“微服务”作为描述这种架构风格的最贴切的名字。在2012年3月波兰的克拉科夫市举办的“33rd Degree”技术大会上,本文作者之一James在其“Microservices - Java, the Unix Way”演讲中以案例的形式谈到了这些微服务的观点,与此同时,Fred George也表达了同样的观点。Netflix公司的Adrian Cockcroft将这种方法描述为“细粒度的SOA”,并且作为先行者和本文下面所提到的众人已经着手在Web领域进行了实践——Joe Walnes, Dan North, Evan Botcher 和 Graham Tackley。
[2]. “单块”(monolith)这个术语已经被Unix社区使用一段时间了。它出现在The Art of Unix Programming一书中,来描述那些变得庞大的系统。
[3]. 许多面向对象的设计者,包括我们自己,都使用领域驱动设计中“service object”这个术语,来描述那种执行一段未被绑定到一个entity对象上的重要逻辑过程的对象。这不同于本文所讨论的”service”的概念。可悲的是,service这个术语同时具有这两个含义,我们必须忍受这样的多义词。
[4]. 我们认为一个应用系统是一个社会性的构建单元,来将一个代码库、功能组和资金体(body of funding)结合起来。
[5]. 原始论文参见梅尔文•康威的网站:http://www.melconway.com/Home/Committees_Paper.html
[6]. 在极度强调高效性(Scale)的情况下,一些组织经常会使用一些二进制的消息发送协议——例如protobuf。即使是这样,这些系统仍然会呈现出“智能端点和傻瓜管道”的特点——来在易读性(transparency)与高效性之间取得平衡。当然,大多数Web属性和绝大多数企业并不需要作出这样的权衡——获得易读性就已经是一个很大的胜利了。
[7]. 忍不住要提一下Jim Webber的说法:ESB表示Egregious Spaghetti Box(一盒极烂的意大利面条)。
[8]. Netflix让SOA与微服务之间的联系更加明确——直到最近这家公司还将他们的架构风格称为“细粒度的SOA”。
[9]. “YAGNI” 或者 “You Aren’t Going To Need It”(你不会需要它)是极限编程的一条原则和劝诫,指的是“除非到了需要的时候,否则不要添加新功能”。
[10]. 单块系统使用单一编程语言,这样讲有点言不由衷——为了在今天的Web上构建各种系统,可能要了解JavaScript、XHTML、CSS、服务器端的编程语言、SQL和一种ORM的方言。很难说只有一种单一编程语言,但是我们的意思你是懂得的。
[11]. Adrian Cockcroft在他2013年11月于Flowcon技术大会所做的一次精彩的演讲中,特别提到了“开发人员自服务”和“开发人员运行他们写的东西”(原文如此)。
[12]. 这里我们又有点言不由衷了。 很明显,在更复杂的网络拓扑里,部署更多的服务,会比部署一个单独的单块系统要更加困难。幸运的是,有一些模式能够减少其中的复杂性——但对于工具的投资还是必须的。
[13]. 事实上,Dan North将这种架构风格称作“可更换的组件架构”,而不是微服务。因为这看起来似乎是在谈微服务特性的一个子集,所以我们选择将其归类为微服务。
[14]. Kent Beck在《实现模式》(Implementation Patterns)一书中,将其作为他的一条设计原则而强调出来。
[15]. 当SOA这个词在本世纪初刚刚出现时,有人曾说:“我们很多年以来一直是这样做的。”有一派观点说,SOA这种风格,将企业级计算早期COBOL程序通过数据文件来进行通信的方式,视作自己的“根”。在另一个方向上,有人说“Erlang编程模型”与微服务是同一回事,只不过它被应用到一个企业应用的上下文中去了。
译者:ThoughtWorks 伍斌
链接:https://www.jianshu.com/p/4821a29fa998
来源:简书
如侵权,请联系博主,立即删除。
docker 很早就有了解,却一直没怎么有正儿八经的场景把玩一下。
刚开始学习 docker 的使用,是从 gitbook https://www.gitbook.com/book/yeasy/docker_practice/details 上面。
当然,这里面每一章讲的比较少,具体的命令和一些操作的含义可能还是要看官方文档:http://docs.docker.com/
docker 的目标是轻量级的操作系统虚拟化解决方案,现在已经做得很棒了。
使用 docker,我觉得主要得理解如下几个核心概念:
其实,仓库的概念很好理解,可以类比 maven 的中央仓库等。
镜像和容器就要细想一下了。镜像就好比打包好的一个系统模板,可以认为是系统安装光盘一样的东西。而容器就是安装好的系统。用什么样的安装光盘就会安装出来什么样的系统。
镜像是“静态”的,不可运行的。可以以它为模板创建很多容器。而容器是“动态”的,可运行的。状态有启停的切换。也可以用把修改后的容器制作成新的镜像“模板”。
安装可以参考 官方的安装指南 。
CentOS 7 下面只需要运行:
1 | sudo yum install docker |
然后启动 Docker 服务,设置开机启动:
1 | sudo systemctl start docker |
由于网络原因,Docker 官方的镜像仓库不太好下载。
所以,我会配置国内的镜像,目前使用 DaoCloud的免费镜像仓库。当然,这个是有限制的,每注册用户每月 2W请求数等。
如果是注册用户,在上面地址中有各个系统的配置介绍。如果有其他好的镜像欢迎大家告诉我,先谢谢了。
下载镜像 : sudo docker pull
查看镜像列表:sudo docker images
删除镜像 : sudo docker rmi
用镜像创建容器:sudo docker creat
用镜像创建容器并启动:sudo docker run
查看容器列表:sudo docker ps
停止容器:sudo docker stop
启动容器:sudo docker start
删除容器:sudo docker rm
还有其他等一些命令,可以通过 sudo docker help
来查看,或者通过官方文档 查看更详细的文档。
这次通过 docker 很方便很快速的搭建好了 GitLab 服务器。
个性化的配置都可以通过参数指定。实际上,大部分的时间都是镜像下载的时间。
普通的使用者不用关心镜像内的细节,不用关注各种软件如何安装和配置,不用关心现有环境对目标安装过程中的影响,也不用担心目标环境对当前环境的破坏。
而且,由于我的特殊需求,在原 Dockerfile 的基础上做了一些特定的修改,然后 docker build
一下,就得到了我想要的镜像。我可根据这个镜像随时再创建新的镜像或运行容器,并且分享给别人让他瞬间拥有和我一样的环境。这样的成果只需很少的操作,真的很棒。
考虑以后利用 docker 帮助我们提高开发、测试、运维等环境问题。保持关注和学习。
这篇写的仓促,只是做个记录,更多内容待以后有丰富的经验和更深刻的体会再说吧。
]]>宿主服务器是 Centos 7。之上只安装了 Git 和 docker。docker 版本是 1.6.2 。
GitLab 镜像使用的是 https://github.com/sameersbn/docker-gitlab (如果需要汉化版,请先看下面的汉化安装)。安装的时候选用的最高版本 7.13.2。
另外,这个镜像里面不包含 redis 和 数据库。所以还使用了另外两个镜像:
下载镜像比较简单
1 | sudo docker pull sameersbn/redis:latest |
其中 gitlab 的镜像比较大,有 600+M, 其他两个在 200M 左右。下载情况就靠网络了。我在使用docker镜像的情况下都失败了一次。
安装实际上比较简单。就是 docker run ……
就好了。主要是注意参数配置。
1 | sudo mkdir -p /srv/docker/gitlab/postgresql |
命令中主要注意的是两点:
1 | sudo mkdir -p /srv/docker/gitlab/redis |
这个和上面差不多,redis 没做特殊配置。
1 | sudo mkdir -p /srv/docker/gitlab/gitlab |
上面的 GitLab 的 docker 命令参数要多一点,主要是其中配置了邮箱和 LDAP 服务器。大家可以参照官方文档自行增删配置。
因为这台运行 docker 的宿主服务器就是一台专门为 GitLab 申请的虚拟机,所以后面就没有配置独立IP。
直接在命令中通过 -p 10022:22 -p 80:80
指定了服务器的端口映射。
这里面有一点比较特殊,GitLab 的默认时间是 UTC 时间,所以通过 -e 'GITLAB_TIMEZONE=Beijing'
指定了 +8 时区。
如果你使用自带的 backup 配置的话也要注意,默认的备份时间 04:00 对应到我们这边实际上是中午 12:00 了。所以我通过 -e 'GITLAB_BACKUP_TIME=20:00'
指定了备份时间为北京时间的 04:00。当然,这个备份时间大家自己看着调了。
最后的 GitLab 容器通过 --link gitlab-postgresql:postgresql --link gitlab-redis:redisio
link 到 redis 和 postgresql ,所以其他两个容器的 IP 、端口和数据库的配置信息不用指定。镜像中已经做了处理。
当然,其他两个容器肯定要比 GitLab 先启动。
容器启动后等一会(初始化时间)就可以正常访问和配置了。
账户:root
密码:5iveL!fe
开机启动只要把对应的 docker run
命令加入到 /etc/rc.d/rc.local
中就可以了。需要注意的是,docker
相关命令要在 docker
服务启动的情况下才能运行,所以记得把docker
服务加入开机启动。
rc.local 添加内容:
1 | docker start gitlab-redis |
GitLab 的汉化已经有 @larryli 同学在做,仓库地址: https://gitlab.com/larryli/gitlab 。非常感谢他的无私奉献。
当然,通过我们上面给出的 docker-gitlab 的官方镜像是安装不了汉化版的。所以我在官方镜像的基础上 fork 了一份,将安装的版本改为 @larryli 的汉化版本。
比较坑的是,由于众所周知的“网络原因”, 在国内 build 镜像会遭遇大量的失败,太打击了。所以一番折腾后又把其他我这边很难下载的东西都改到国内了,浪费了好多时间,简直心酸。修改后的仓库: https://github.com/clarkhan/docker-gitlab 。目前没有把 build 的镜像放到 docker 仓库中,大家需要可以自己 clone 下来 build 一下,应该没问题。
使用汉化的镜像,如上面介绍的一样安装就可以了。
]]>apt-get upgrade
更新了一大堆软件的原因,后来使用 apt-get update
和 apt-get install
总是遇到 “Error: Timeout was reached” 这样的错误。用 Google 大法,经历了曲折的历程后终于解决问题。所以觉得还是有必要记录下,防止小伙伴再走弯路了。
刚开始,找到一篇 wiki ,具体地址忘记了,总之解决问题的方式是:
1 | cd /etc/apt/apt.conf.d |
我不知道对其他人有没有用,总之,对我的问题是无效的。
后来,发现 这篇博客 记录的问题和我的有些相似。他讲到是由于安装了一个错误的 libapt-pkg4.12
版本引起的问题。
于是我在 http://security.ubuntu.com/ubuntu/pool/main/a/apt/重新下载安装了最新的 libapt-pkg
。因为最终还是没解决问题,所以详细的步骤就不记录了。
最后,最后了,这个一定是解决问题了。Google 大法引导我找到了这个页面 https://bugs.launchpad.net/ubuntu/+source/packagekit/+bug/1001376 Ubuntu 官方的Bug。这个 bug 说明已经被解决。于是乎,参照这个,我又安装了 packagekit
:
1 | $ sudo apt-get install packagekit |
然后,一切OK了。
]]>