【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 作者: 马若飞,lead software engineer in FreeWheel,《Istio实战指南》作者,ServiceMesher社区管委会成员。
前言
近两年随着微服务架构的流行,服务网格(Service Mesh)技术受到了越来越多的人关注,并拥有了大批的拥趸。目前市面上比较成熟的开源服务网格主要有下面几个:Linkerd,这是第一个出现在公众视野的服务网格产品,由Twitter的finagle库衍生而来,目前由Buoyant公司负责开发和维护;Envoy,Lyft开发并且是第一个从CNCF孵化的服务网格产品,定位于通用的数据平面或者单独作为Sidecar代理使用;Istio,由Google、IBM、Lyft联合开发的所谓第二代服务网格产品,控制平面的加入使得服务网格产品的形态更加完整。
服务网格技术作为构建云原生应用的重要一环,逐渐的被越来越多的人和厂商认可,并看好它的发展前景。在Istio大红大紫的今天,作为和Google在云服务市场竞争的Amazon来说,自然不愿错失这块巨大的蛋糕。他们在今年4月份发布了自己的服务网格产品:AWS App Mesh。本文会聚焦于Istio和App Mesh这两个产品,通过横向的对比分析让大家对它们有一个更深入的认识。
概念
产品定位
从官方的介绍来看,Istio和App Mesh都比较明确的表示自己是一种服务网格产品。Istio强调了自己在连接、安全、控制和可视化4个方面的能力;而App Mesh主要强调了一致的可见性和流量控制这两方面能力,当然也少不了强调作为云平台下的产品的好处:托管服务,无需自己维护。
从某种程度上讲,Istio是一个相对重一点的解决方案,提供了不限于流量管理的各个方面的能力;而App Mesh是更加纯粹的服务于运行在AWS之上的应用并提供流控功能。笔者认为这和它目前的产品形态还不完善有关(后面会具体提到)。从与AWS内部开发人员的沟通中可以感觉到,App Mesh应该是一盘很大的棋,目前只是初期阶段而已。
核心术语
和AWS里很多产品一样,App Mesh也不是独创,而是基于Envoy开发的。AWS这样的闭环生态必然要对其进行改进和整合。同时,也为了把它封装成一个对外的服务,提供适当的API接口,在App Mesh这个产品中提出了下面几个重要的技术术语,我们来一一介绍一下。 服务网格(Service mesh):服务间网络流量的逻辑边界。这个概念比较好理解,就是为使用App mesh的服务圈一个虚拟的边界。 虚拟服务(Virtual services):是真实服务的抽象。真实服务可以是部署于抽象节点的服务,也可以是间接的通过路由指向的服务。 虚拟节点(Virtual nodes):虚拟节点是指向特殊工作组(task group)的逻辑指针。例如AWS的ECS服务,或者Kubernetes的Deployment。可以简单的把它理解为是物理节点或逻辑节点的抽象。 Envoy:AWS改造后的Envoy(未来会合并到Envoy的官方版本),作为App Mesh里的数据平面,Sidecar代理。 虚拟路由器(Virtual routers):用来处理来自虚拟服务的流量。可以理解为它是一组路由规则的封装。 路由(Routes):就是路由规则,用来根据这个规则分发请求。
上面的图展示了这几个概念的关系:当用户请求一个虚拟服务时,服务配置的路由器根据路由策略将请求指向对应的虚拟节点,这些节点本质上是AWS里的EKS或者ECS的节点。
那么这些App Mesh自创的术语是否能在Istio中找到相似甚至相同的对象呢?我归纳了下面的表格来做一个对比:
App Mesh Istio
服务网格(Service mesh) | Istio并未显示的定义这一概念,我们可以认为在一个集群中,由Istio管理的服务集合,它们组成的网络拓扑即是服务网格。 虚拟服务(Virtual services) | Istio中也存在虚拟服务的概念。它的主要功能是定义路由规则,使请求可以根据这些规则被分发到对应的服务。从这一点来说,它和App Mesh的虚拟服务的概念基本上是一致的。 |
---|
虚拟节点(Virtual nodes) | Istio没有虚拟节点的概念,可以认为类似Kubernetes里的Deployment。 虚拟路由器(Virtual routers) | 路由(Routes) Istio也没有虚拟路由器的概念。 | Istio中的目标规则(DestinationRule)和路由的概念类似,为路由设置一些策略。从配置层面讲,其中的子集(subset)和App Mesh路由里选择的目标即虚拟节点对应。但Istio的目标规则更加灵活,也支持更多的路由策略。 | 从上面的对比看出,App Mesh目前基本上实现了最主要的流量控制(路由)的功能,但像超时重试、熔断、流量复制等高级一些的功能还没有提供,有待进一步完善。 架构 AWS App Mesh是一个商业产品,目前还没有找到架构上的技术细节,不过我们依然可以从现有的、公开的文档或介绍中发现一些有用的信息。 从这张官网的结构图中可以看出,每个服务的橙色部分就是Sidecar代理:Envoy。而中间的AWS App Mesh其实就是控制平面,用来控制服务间的交互。那么这个控制平面具体的功能是什么呢?我们可以从今年的AWS Summit的一篇PPT中看到这样的字样: 控制平面用来把逻辑意图转换成代理配置,并进行分发。 熟悉Istio架构的朋友有没有觉得似曾相识?没错,这个控制平面的职责和Pilot基本一致。由此可见,不管什么产品的控制平面,也必须具备这些核心的功能。 那么在平台的支持方面呢?下面这张图展示了App Mesh可以被运行在如下的基础设施中,包括EKS、ECS、EC2等等。当然,这些都必须存在于AWS这个闭环生态中。 而Istio这方面就相对弱一些。尽管Istio宣称是支持多平台的,但目前来看和Kubernetes还是强依赖。不过它并不受限于单一的云平台,这一点有较大的优势。 从可观测性来看,App Mesh依然发挥了自家生态的优势,可以方便的接入CloudWatch、X-Ray对服务进行观测。另外,App Mesh也提供了更大的灵活性,可以在虚拟节点里配置服务后端(可以是虚拟服务或者ARN),流量可以出站到这些配置的服务。这一点来说,和Istio的Mixer又有了异曲同工之妙。Mixer通过插件方式为Istio提供了极大的可扩展性,App Mesh在这一点上也不算落下风。 Istio的架构大家都非常熟悉了,这里就不再赘述了,感兴趣的同学可以直接去 官网 查看。 功能与实现方式 部署 Istio部署后类似一个网一样附着在你的Kubernetes集群上, 控制平面会使用你设置的资源;而App Mesh是一种托管方式,只会使用Envoy代理。完整安装后的Istio需要添加50个左右的CRD,而App Mesh只添加了3个CRD: meshes.appmesh.k8s.aws , virtualnodes.appmesh.k8s.aws 和 virtualservices.appmesh.k8s.aws 。这一点也反映出了功能上的区别。 流量控制 尽管两者的数据平面都是基于Envoy,但它们提供的流量控制能力目前还是有比较大的差距的。在路由的设置方面,App Mesh提供了相对比较丰富的匹配策略,基本能满足大部分使用场景。下面是App Mesh控制台里的路由配置截图,可以看出,除了基本的URI前缀、HTTP Method和Scheme外,也支持请求头的匹配。 Istio的匹配策略更加完善,除了上面提到的,还包括HTTP Authority,端口匹配,请求参数匹配等,具体信息可以从官方文档的虚拟服务 设置 查看。下面两段yaml分别展示了两个产品在虚拟服务配置上的差异。 App Mesh配置: apiVersion: appmesh.k8s.aws/v1beta1 kind: VirtualService metadata: name: my-svc-a namespace: my-namespace spec: meshName: my-mesh routes: - name: route-to-svc-a http: match: prefix: / action: weightedTargets: - virtualNodeName: my-app-a weight: 1 Istio配置: apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings-route spec: hosts: - ratings.prod.svc.cluster.local http: - match: - headers: end-user: exact: jason uri: prefix: "/ratings/v2/" ignoreUriCase: true route: - destination: host: ratings.prod.svc.cluster.local 另外一个比较大的不同是,App Mesh需要你对不同版本的服务分开定义(即定义成不同的虚拟服务),而Istio是通过目标规则 DestinationRule 里的子集 subsets 和路由配置做的关联。本质上它们没有太大区别。 除了路由功能外,App Mesh就显得捉襟见肘了。就在笔者撰写本文时,AWS刚刚添加了重试功能。而Istio借助于强大的Envoy,提供了全面的流量控制能力,如超时重试、故障注入、熔断、流量镜像等。 安全 在安全方面,两者的实现方式具有较大区别。默认情况下,一个用户不能直接访问App Mesh的资源,需要通过AWS的 IAM策略 给用户授权。比如下面的配置是容许用户用任意行为去操作网格内的任意资源: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appmesh:*" ], "Resource": "*" } ] } 而虚拟节点间的授权方面,App Mesh目前只有TLS访问的支持,且仅仅是预览版(Preview)并未正式发布。下面的配置展示了一个虚拟节点只容许 tls 方式的访问: { "meshName" : "app1", "spec" : { "listeners" : [ { "portMapping" : { "port" : 80, "protocol" : "http" }, "tls" : { "mode" : "STRICT", "certificate" : { "acm" : { "certificateArn" : "arn:aws:acm:us-west-2:123456789012:certificate/12345678-1234-1234-1234-123456789012" } } } } ], "serviceDiscovery" : { "dns" : { "hostname" : "serviceBv1.mesh.local" } } }, "virtualNodeName" : "serviceBv1" } 而Istio中端到端的认证是支持mTLS的,同时还支持JWT的用户身份认证。下面的配置分别展示了这两种认证方式: apiVersion: "authentication.istio.io/v1alpha1" kind: "Policy" metadata: name: "reviews" spec: targets: - name: reviews peers: - mtls: {} origins: - jwt: issuer: "https://accounts.google.com" jwksUri: "https://www.googleapis.com/oauth2/v3/certs" trigger_rules: - excluded_paths: - exact: /health Istio的授权是通过RBAC实现的,可以提供基于命名空间、服务和HTTP方法级别的访问控制。这里就不具体展示了,大家可以通过官网 文档 来查看。 可观察性 一般来说,可以通过三种方式来观察你的应用:指标数据、分布式追踪、日志。Istio在这三个方面都有比较完整的支持。指标方面,可以通过Envoy获取请求相关的数据,同时还提供了服务级别的指标,以及控制平面的指标来检测各个组件的运行情况。通过内置的Prometheus来收集指标,并使用Grafana展示出来。分布式追踪也支持各种主流的OpenTracing工具,如Jaeger、Zipkin等。访问日志一般都通过ELK去完成收集、分析和展示。另外,Istio还拥有Kiali这样的可视化工具,给你提供整个网格以及微服务应用的拓扑视图。总体来说,Istio在可观察方面的能力是非常强大的,这主要是因为Mixer组件的插件特性带来了巨大的灵活性。 App Mesh在这方面做的也不错。在如下图虚拟节点的配置中可以看到,你可以配置服务的后端基础设施,这样流量就可以出站到这些服务。同时,在日志收集方面,也可以配置到本地日志,或者是其他的日志系统。 另一方面,AWS又一次发挥了自己闭环生态的优势,提供了App Mesh与自家的CloudWatch、X-Ray这两个监控工具的整合。总的来说,App Mesh在可观察性上也不落下风。 总结 AWS App Mesh作为一个今年4月份才发布的产品,在功能的完整性上和Istio有差距也是情有可原的。从App Mesh的 Roadmap 可以看出,很多重要的功能,比如熔断已经在开发计划中。以笔者与AWS的开发人员了解的信息来看,他们还是相当重视这个产品,优先级很高,进度也比较快,之前还在预览阶段的重试功能在上个月也正式发布了。另外,App Mesh是可以免费使用的,用户只需要对其中的实例资源付费即可,没有额外费用。App Mesh一部分的开发重点是和现有产品的整合,比如Roadmap列出的使用AWS Gateway作为App Mesh的Ingress。借助着自己的生态优势,这种整合即方便快捷的完善了App Mesh,同时又让生态内的产品结合的更紧密,使得闭环更加的牢固,不得不说是一步好棋。 和App Mesh目前只强调流控能力不同,Istio更多的是把自己打造成一个更加完善的、全面的服务网格系统。架构优雅,功能强大,但性能上受到质疑。在产品的更迭上貌似也做的不尽如人意(不过近期接连发布了1.3到1.3.3版本,让我们对它的未来发展又有了期待)。Istio的优势在于3大顶级技术公司加持的强大资源,加上开源社区的反哺,控制好的话容易形成可持续发展的局面,并成为下一个明星级产品。但目前各大厂商都意识到了网格的重要性并推出自己的产品(AWS App Mesh,Kong的Kuma等),竞争也会逐渐激烈。未来是三分天下还是一统山河,让我们拭目以待。 参考 what is app mesh aws app mesh roadmap Redefining application communications with AWS App Mesh istio offical 关于 ServiceMesher 社区 ServiceMesher 社区是由一群拥有相同价值观和理念的志愿者们共同发起,于 2018 年 4 月正式成立。 社区关注领域有:容器、微服务、Service Mesh、Serverless,拥抱开源和云原生,致力于推动 Service Mesh 在中国的蓬勃发展。 社区官网: https://www.servicemesher.com
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 下周六,深圳,阔别已久的线下技术沙龙要和你见面啦! 现场有Rancher Labs研发经理demo刚刚发布的Rancher 2.3中的Istio、Windows容器、集群模板等功能及使用,还有k3s首次线下workshop,由Rancher Labs资深架构师带你一起玩转k3s!还有长城证券的运维负责人分享数字化浪潮下传统金融IT的转型。 访问链接 即可了解详情及报名啦! 诸如容器、Kubernetes等云原生架构和技术的成熟推动了服务网格架构的极速增长以及广泛采用。尽管云原生环境可以为企业带来一系列好处,但是其复杂性也对负责开发维护这类系统的人员,如软件开发人员、网络运维人员、基础架构工程师以及CIO、CTO等带来了重大挑战。 服务网格框架能够为跨不同云原生环境的应用程序整合一致的服务和网络管理能力,它还极大地加快了DevOps实践的进程,正缘于此,服务网格近年来可谓是发展迅猛。云原生普及的加快,要求拥有云原生应用程序的工程团队必须熟悉服务网格功能,以判断该技术将来是否能为企业提供价值。 什么是服务网格? 服务网格可以连接、保护、控制以及监控在编排平台上的服务。“服务网格”这一术语本身用于分布式应用程序中服务之间的一组搭接网络连接,也适用于管理该组连接服务的一系列工具。如果你有两个通过网络连接进行交互的微服务,那就意味着你有了一个服务网格。下图是一个十分简单的示例,一个网格和两个服务: 更有可能的是,由于在环境中微服务的数量会继续增长,你的服务网格会如下图所示: 随着云环境扩展到混合云和多云部署,开发人员将会使用微服务来加速开发并且确保在多个容器和分布式云资源中的的可移植性。随着微服务生态系统的复杂性增长,我们需要高效且智能地管理它,并且深入了解微服务如何交互以及保护微服务之间的通信。 什么是Istio? 如果你已经听说了服务网格,那么你一定顺带听说了Istio。Istio是一个开源的服务网格,它可以部署在已有的云原生应用程序上。它还具有类似于平台的功能——可以将集成到日志平台、遥测或策略系统中。策略集成使得Istio在创建一个统一的方法来保护、连接以及监控既定环境中的微服务中扮演一个安全工具的角色。当泛指“Istio服务网格”时,通常是指Istio中的一系列工具,而特指“某个Istio 服务网格”时则表明由Istio安装管理的指定应用程序集群。Istio的许多CRD允许对应用程序网络层的行为进行编程配置(通过使用Kubernetes API),其中应用程序是相互依赖的微服务集。Istio在某种程度上可以称为当今云原生堆栈中服务网格的同义词,因为它的功能最丰富、最标准化。 我是否需要一个服务网格? 尽管服务网格的采用率可能会持续快速增长,特别是当功能设置和类似Istio的管理工具进一步完善之后,但并不是每个云原生环境都需要服务网格。所以你如何知道一个服务是否适合你的企业或者环境呢?如果你需要解决下面所描述的一个或多个需求或问题的方案,那么你应该考虑部署一个服务网格: 你在基于分布式微服务的应用程序中遇到性能问题 你需要为所有微服务收集并交付一致的请求和连接指标 你想直接默认在线加密设置,而无需直接管理TLS证书 你需要比Kubernetes网络策略提供的更细粒度的解决方案进行服务到服务的控制 你想使用金丝雀发布和应用程序API多版本支持进行自动release 你想无需修改应用程序就可以添加用户的身份验证和授权认证信息 另一方面,如果在你的堆栈中不需要服务网格,那么你需要做一些权衡。考虑到这些环境的复杂性,部署一个服务网格(包括Istio)需要大量的迁移工作和运维成本。如果你的微服务部署数量不会增长,或者如果有其他解决方案可以满足你内部的HTTP请求路由的需求,或者如果你已经有了一个可管理且高效的解决方案可以解决上述的关键需求,那么此刻服务网格对你来说真的不是一个最佳选择。 但是如果服务网格继续极速被广泛采用,为支持它而开发的功能生态系统将会继续扩展。这种增长将提升可管理性和功能性,以便将来DevOps团队可以更加轻松地访问更强大的服务网格工具,而不必担心将新的基础架构层部署到云原生堆栈中而出现棘手的问题或花费很高的成本。 Istio工作原理 Istio组件被分为两部分——控制平面和数据平面。控制平面是指管理配置和监控数据平面的服务。数据平面由作为sidecar由在应用程序pod中的智能代理(proxy)组成,这是Kubernetes对象模型中最小的可部署对象。这些Istio proxy有助于控制和监控微服务间的网络连接。从控制平面接收路由和策略规则,然后数据平面报告回连接处理遥测。 通过创建Kubernetes资源来配置Istio服务网格。此外,有许多Kubernetes CRD可以映射到Istio各种功能上。接下来,我们会讨论更多关于控制和数据平面的作用,但在此之前我们先了解关于Istio的潜在能力,以及它的不足。 潜力与不足 Istio通过其可动态配置代理的网格提供了一系列用于处理和控制网络连接的特性。但这些功能配置繁重并且拥有陡峭的学习曲线。并且有时把已有的应用程序迁移到Istio架构时依旧会出现一些常见的问题,尽管这些架构已经是Kubernetes原生的微服务。 此外,Istio缺乏对如何将用户提供的配置转换为Envoy路由的了解。Envoy是作为服务网格中服务的入站和出站流量的中介开发的一种高性能的代理,是由来自共享出行服务公司Lyft的开发人员创建的,可以用于从单体架构转变为服务网格架构。其他在使用中的问题还包括部署和服务资源配置要求所需的学习曲线、在打开mTLS时中断Kubernetes readiness和liveness探针以及使用没有ClusterIP的Kubernetes服务或绕开Kubernetes服务发现流程的服务。 Istio的优势在于可以让你在不修改微服务源代码的情况之下,很轻松地给微服务加上诸如负载均衡、身份验证、监控等等的功能。而且目前它正在快速发展迭代,频繁发布新版本,并且积极征求用户反馈。尽管目前Envoy还有很多局限,但是随着Istio持续发展,它也会积极开发和完善自己的功能。 配置控制平面 在Kubernetes集群中,一个典型的Istio部署应该包含以下服务: Pilot,在Istio网络自定义资源中集合流量管理规范配置,并将该配置交付到istio-proxy sidecar。 Mixer,用于处理由proxy sidecar生成的请求指标的遥测,并将其发送到已配置完成的后端,并执行授权策略。如果开启了策略检查(Istio 1.1中默认关闭),proxy sidecar将会连接到Mixer以确认连接是被允许的。但是,这个方法会稍微增加网络延迟。 Citadel,这个是Istio的公钥基础设施(PKI)服务,它可以生成、轮换和吊销用于身份验证的客户端TLS证书。 Galley,它是大多数Istio CRD的Kubernetes controller,使用户可以更改自定义资源并将内容分配到其他Istio服务中。 数据平面 数据平面由Envoy服务代理提供支持,该代理使用Istio扩展构建。Proxy会拦截到pod服务端口的传入流量,并默认拦截来自pod其他容器的所有创出TCP流量。在大部分情况下,无需更改应用程序代码,仅对应用程序的Kubernetes部署和服务资源规范进行较小的更改,proxy sidecar 就可以在pod中运行。Proxy sidecar的配置由在Istio 控制面板中的服务进行动态管理。 最终,也许会在某个时间点你需要部署服务网格以确保你的云原生环境完全正常运行并得到充分保护。因此,熟悉有关服务网格的基础只是将可以帮助你做出准确的判断——什么时候应该部署服务网格以及应该如何部署。如果你正在计划在Kubernetes和其他容器平台上进行扩展计划,那么你通过了解Istio的设计和功能以及它如何降低容器化微服务和云原生环境的固有复杂性,你可以知道Istio是一个功能强大且快速改进的解决方案并且正在积极增强弹性伸缩能力、安全性以及管理的简易性。 如果企业继续采用云原生和分布式架构,那么Istio的服务网格功能以及底层基础架构的网络控制和Kubernetes的安全实践将会极大程度解放DevOps团队在弹性伸缩和管理应用程序基础架构上的压力。 在10月9日 GA的Rancher 2.3版本 中,正式集成了Istio,极大简化了Istio的安装和配置。你只需要在UI中使用工具菜单,即可启动Istio。Rancher中现已内置支持: 用于流量和遥测可视化的Kiali仪表板 用于追踪的Jaeger 用于监控和可观察性的Prometheus和Grafana 如果你还想了解更多关于Rancher 2.3的新功能,欢迎参加我们在下周六(10月26日)举办的技术沙龙,坐标深圳。届时将有Rancher Labs大中华区的研发经理现场介绍并demo Rancher 2.3的新功能, 点击此处 ,赶紧报名啦! 欢迎添加小助手(wx:rancher2),进官方技术群,了解更多Kubernetes使用攻略 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 在vShpere中,访问ESXi主机的途径很多,如下: ESXi DCUI ESXi Shell ESXi SSH ESXi Host Client vCenter --> vSphere web client / vSphere Client VMware vSphere ESXi主机的访问控制,除了强制要求使用https加密连接之外,还可以关闭DCUI通道,设置为Normal Lockdown(普通锁定)或Strict Lockdown。 大别阿郎,原名张瑞旗,河南光山县人,广东省作家协会会员。1994年焦作矿业学院英语专业毕业,获西悉尼大学工商管理硕士学位。做过翻译、秘书、销售、程序员、服务器管理员;创办公司13载;创作了长篇小说《神级宅男网管》《枪手》。出版过非虚构文学作品《从大别山到修水河》《午后三点的乡愁》《请与我同框》。2018年根据自己的长篇小说《行辘》改编的同名电影剧本获中国首届工业文学作品大赛推荐作品奖。现在广州腾科任红帽培训讲师。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 来源:Redislabs 作者:Martin Forstner 翻译:Kevin (公众号:中间件小哥) 以我的经验,将某些应用拆分成更小的、松耦合的、可协同工作的独立逻辑业务服务会更易于构建和维护。这些服务(也被称为微服务)各自管理自己的技术栈,因此很容易独立于其他服务进行开发和部署。前人已经总结了很多关于使用这种架构设计的好处,在此我就不再赘述了。关于这种设计,有一个方面我一直在重点关注,因为如果没有它,将会导致一些有趣的挑战。虽然构建松耦合的微服务是一个非常轻量级和快速的开发过程,但是这些服务之间共享状态、事件以及数据的通信模型却不那么简单。我使用过的最简单的通信模型就是服务间直接通信,但是这种模型被 Fernando Dogio 明确地证明一旦服务规模扩大就会失效,会导致服务崩溃、重载逻辑以及负载增加等问题,从而可能引起的巨大的麻烦,因此应该尽量避免使用这种模型。还有一些其他通信模型,比如通用的发布/订阅模型、复杂的 kafka 事件流模型等,但是最近我在使用 Redis 构建微服务间的通信模型。 拯救者 Redis! 微服务通过网络边界发布状态,为了跟踪这种状态,事件通常需要被保存在事件存储中。由于事件通常是一种异步写入操作的不可变流的记录(又被称为事务日志),因此适用于以下场景: 1. 顺序很重要(时间序列数据) 2. 丢失一个事件会导致错误状态 3. 回放状态在任何给定时间点都是已知的 4. 写操作简单且快捷 5. 读操作需要更多的时间,以至于需要缓存 6. 需要高可扩展性,服务之间都是解耦的,没有关联 使用 Redis,我始终可以轻松实现发布-订阅模式。但现在,Redis 5.0 提供了新的Streams 数据类型,我们可以以一种更加抽象的方式对日志数据结构进行建模-使之成为时间序列数据的理想用例(例如最多一次或最少一次传递语义的事务日志)。基于双主功能,轻松简单的部署以及内存中的超快速处理能力,Redis 流成为一种管理大规模微服务通信的必备工具。基本的模型被称为命令查询职责分离(CQRS),它将命令和查询分开执行,命令使用 HTTP 协议,而查询采用 RESP(Redis 序列化协议)。让我们使用一个例子来说明如何使用 Redis 作为事件存储。 OrderShop简单应用概述 我创建了一个简单但是通用的电子商务应用作为例子。当创建/删除客户、库存物品或订单时,使用 RESP 将事件异步传递到 CRM 服务,以管理 OrderShop 与当前和潜在客户的互动。像许多常见应用程序的需求一样,CRM 服务可以在运行时启动和停止,而不会影响其他微服务。这需要捕获在其停机期间发送给它的所有消息以进行后续处理。 下图展示了 9 个解耦的微服务的互连性,这些微服务使用由 Redis 流构建的事件存储进行服务间通信。他们通过侦听事件存储(即 Redis 实例)中特定事件流上的任何新创建的事件来执行此操作。 图1. OrderShop 架构 我们的 OrderShop 应用程序的域模型由以下 5 个实体组成: 顾客 产品 库存 订单 账单 通过侦听域事件并保持实体缓存为最新状态,事件存储的聚合功能仅需调用一次或在响应时调用。 图2. OrderShop 域模型 安装并运行OrderShop 按照如下步骤安装并运行 OrderShop 应用: 1. 从这里下载代码仓库: https://github.com/Redislabs-Solution-Architects/ordershop 2. 确保已经安装了 Docker Engine和Docker Compose 3. 安装 Python3: https://python-docs.readthedocs.io/en/latest/starting/install3/osx.html 4. 使用 docker-compose up启动应用程序 5. 使用 pip3 install -r client / requirements.txt 安装需求 6. 然后使用 python3 -m unittest client / client.py 执行客户端 7. 使用 docker-compose stop crm-service 停止 CRM 服务 8. 重新执行客户端,您会看到该应用程序正常运行,没有任何错误 深入了解 以下是来自 client.py 的一些简单测试用例,以及相应的 Redis 数据类型和键。 我选择流数据类型来保存这些事件,因为它们背后的抽象数据类型是事务日志,非常适合我们连续事件流的用例。我选择了不同的键来分配分区,并决定为每个流生成自己的条目 ID,ID 包含秒“-”微秒的时间戳(为了保持 ID 的唯一,并保留了键/分区之间事件的顺序)。我选择集合来存储 ID(UUID),并选择列表和哈希来对数据建模,因为它反映了它们的结构,并且实体缓存只是域模型的简单投影。 结论 Redis 提供的各种数据结构-包括集合,有序集合,哈希,列表,字符串,位数组,HyperLogLogs,地理空间索引以及现在的流-可以轻松适应任何数据模型。流包含的元素不仅是单个字符串,而且是由字段和值组成的对象。范围查询速度很快,并且流中的每个条目都有一个 ID,这是一个逻辑偏移量。流提供了针对时间序列等应用的解决方案,并可为其他应用提供流消息,例如,替换需要更高可靠性的通用发布/ 订阅应用程序,以及其他全新的应用。 您可以通过分片(聚集多个实例)来扩展 Redis 实例并提供容灾恢复的持久性选项,所以 Redis 可以作为企业级应用的选择。 更多优质中间件技术资讯/原创/翻译文章/资料/干货,请关注“中间件小哥”公众号! 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 自从我接触 云桌面 以来,听到和看到最多的就是云桌面是如何如何的好,特别是这几年来在传统PC销量下滑的情况下,更是被很多人说云桌面才是未来办公的首要选择,这究竟是一些厂家在夸大云桌面的功能来达到宣传的效果,还是说它确实有一些独到之处的。看完以下这些就知道它到底有没有被夸大的了。 首先桌面性能,说到云桌面我们不得不先来介绍下它的桌面性能的,虽然说很多情况下连接显示器的云终端配置都不高,但是云终端在很多情况下是不进行计算的,而是通过调用服务器的资源使用,通过充分调用服务器的资源使得虚拟桌面达到i3、i5甚至i7配置电脑的速度,完美适应办公、教学、播放高清视频等应用场景。同时云终端采用全闪存SSD设计不安装操作系统,开机速度比PC更快的。 其次维护难度,传统的PC配置是很强大,但是随着使用年限和数量的增加,管理和维护起来是比较麻烦的,而云桌面的维护难度相对来说就比较简单了,不管是软件还是硬件的维护都会比PC简单方便很多的,硬件方面云终端故障直接换新即可,而系统软件方面,数据和计算都在服务器上进行,所以我们只需要管理和维护好服务器即可,终端不需要进行系统维护的。 第三使用成本,相比于传统PC来说,云桌面的硬件组成上虽然多了服务器,但是云终端成本不到PC的一半,虽然多了服务器但是评价下来单台的成本是不会比购买PC高的,同时云终端低功耗只有5W功率和免维护的特点,在使用过程中可以节省90%的用电成本和95%的用电成本。而这些都是使用传统PC无法相比的。 最后应用场景,虽然说当前云桌面还不能像传统PC的应用场景这么的广泛,但是它所涉及的应用场景正在慢慢的增加并且是越来越广的,学校计算机教室、企业办公、工厂车间办公、医院等各个行业几乎只要是要用到电脑的地方,云桌面都是可以应用的,并且随着技术的发展它的应用场景还在继续增加的。 而正是因为云桌面可以达到传统PC一样的运行速度,又比传统PC管理维护更简单和更节省成本以及一样广泛的应用场景,所以说云桌面确实有它的独到之处的而不是被夸大的。 来源禹龙云 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 随着社会的进步与技术的发展,人们对资源的高效利用有了更为迫切的需求。近年来,互联网、移动互联网的高速发展与成熟,大应用的微服务化也引起了企业的热情关注,而基于Kubernetes+Docker的容器云方案也随之进入了大众的视野。开普勒云是一个基于Kubernetes+Docker+Istio的微服务治理解决方案。 一、Microservices 1.1 解决大应用微服务化后的问题 现在各大企业都在谈论微服务,在微服务的大趋势之下技术圈里逢人必谈微服务,及微服务化后的各种解决方案。 1.2 当我们在讨论微服务的时候我们在讨论什么? 使用微服务架构有很多充分的理由,但天下没有免费的午餐,微服务虽有诸多优势,同时也增加了复杂性。团队应该积极应对这种复杂性,前提是应用能够受益于微服务。 1.2.1 如何微服务化的问题 微服务要如何拆分 业务API规则 数据一致性保证 后期可扩展性考虑 当然这不是本文主要讨论的问题,我不讲微服务具体要如何拆分,每个企业每个应用的情况都不太一样,适合自己的方案就是最好的拆分方案。我们主要来解决微服务化后所带来的一些问题。 1.2.2 微服务化后带来的问题 环境一致性 如何对资源快速分配 如何快速度部署 怎么做基本监控 服务注册与发现 负载均衡如何做 以上都是大应用微服务化所需要解决的基础问题,如果还按照传统的方式使用虚拟机来实现,资源开支将会非常大。那么这些问题要怎么解决呢?比如: 流量管理 服务降级 认证、授权 当然面对上述这些问题我们广大的猿友们肯定是有解决方案的。 1.3 Service governance 1.3.1 Java 体系 假设我们是Java体系的应用,那解决起来就很方便了,比如我们可以考虑使用SpringCloud全家桶系列。也可以拆分使用: Eureka Hystrix Zuul Spring-cloud Spring-boot ZipKin Java体系下能很方便的做以我们微服务化后的基础部分,但依然不能非常舒服地解决环境一致性,并且如果有其他语系的服务将很难融入进去。 我们来看基础编程语言一般有什么组合方式来解决基础问题。 1.3.2 其他体系 Consul Kong Go-kit Jaeger/Zipkin 假设我们是使用Golang语言,这里再捧一下Golang语言。go语言简直就是天生为微服务而生的语言,实在不要太方便了。高效的开发速度及相当不错的性能,简单精悍。 跑题了~我们使用上面这些工具也可以组成一套还不错的微服务架构。 Consul: 当作服务发现及配置中心来使 Kong: 作为服务网关 Jaeger: 作为链路追踪来使 Go-kit: 开发组件 但是这种方案也有问题,对服务的侵入性太强了,每个服务都需要嵌入大量代码,这还是很头疼的。 二、Docker & Kubernetes 基于Docker+k8s搭建平台的实践方案。 2.1 Docker Docker 是一个非常强大的容器。 资源利用率的提升 环境一致性、可移植性 快速度扩容伸缩 版本控制 使用了Docker之后,我们发现可玩的东西变多了,更加灵活了。不仅仅是资源利用率提升、环境一致性得到了保证,版本控制也变得更加方便了。 以前我们使用Jenkins进行构建,需要回滚时,又需要重新走一次jenkins Build过程,非常麻烦。如果是Java应用,它的构建时间将会变得非常长。 使用了Docker之后,这一切都变得简单了,只需要把某个版本的镜像拉下来启动就完事了(如果本地有缓存直接启动某个版本就行了),这个提升是非常高效的。 (图片来源网络) 既然使用了Docker容器作为服务的基础,那我们肯定需要对容器进行编排,如果没有编排那将是非常可怕的。而对于Docker容器的编排,我们有多种选择:Docker Swarm、Apache Mesos、Kubernetes,在这些编排工具之中,我们选择了服务编排王者Kubernetes。 2.1.1 Docker VS VM VM: 创建虚拟机需要1分钟,部署环境3分钟,部署代码2分钟。 Docker: 启动容器30秒内。 2.2 Why choose Kubernetes 我们来对比这三个容器编排工具。 2.2.1 Apache Mesos Mesos的目的是建立一个高效可扩展的系统,并且这个系统能够支持各种各样的框架,不管是现在的还是未来的框架,它都能支持。这也是现今一个比较大的问题:类似Hadoop和MPI这些框架都是独立开的,这导致想要在框架之间做一些细粒度的分享是不可能的。 但它的基础语言不是Golang,不在我们的技术栈里,我们对它的维护成本将会增高,所以我们首先排除了它。 2.2.2 Docker Swarm Docker Swarm是一个由Docker开发的调度框架。由Docker自身开发的好处之一就是标准Docker API的使用。Swarm的架构由两部分组成: (图片来源网络) 它的使用,这里不再具体进行介绍。 2.2.3 Kubernetes Kubernetes是一个Docker容器的编排系统,它使用label和pod的概念来将容器换分为逻辑单元。Pods是同地协作(co-located)容器的集合,这些容器被共同部署和调度,形成了一个服务,这是Kubernetes和其他两个框架的主要区别。相比于基于相似度的容器调度方式(就像Swarm和Mesos),这个方法简化了对集群的管理. 不仅如此,它还提供了非常丰富的API,方便我们对它进行操作,及玩出更多花样。其实还有一大重点就是符合我们的Golang技术栈,并且有大厂支持。 Kubernetes 的具体使用这里也不再过多介绍,网站上有大把资料可以参考。 2.3 Kubernetes in kubernetes kubernetes(k8s)是自动化容器操作的开源平台,这些操作包括部署、调度和节点集群间扩展。 自动化容器的部署和复制 随时扩展或收缩容器规模 将容器组织成组,并且提供容器间的负载均衡 很容易地升级应用程序容器的新版本 提供容器弹性,如果容器失效就替换它,等等... 2.4 Kubernetes is not enough either 到这里我们解决了以下问题: Docker: 环境一致性、快速度部署。 Kubernetes: 服务注册与发现、负载均衡、对资源快速分配。 当然还有监控,这个我们后面再说。我们先来看要解决一些更高层次的问题该怎么办呢? 在不对服务进行侵入性的代码修改的情况下,服务认证、链路追踪、日志管理、断路器、流量管理、错误注入等等问题要怎么解决呢? 这两年非常流行一种解决方案:Service Mesh。 三、Service Mesh 处理服务间通信的基础设施层,用于在云原生应用复杂的服务拓扑中实现可靠的请求传递。 用来处理服务间通讯的专用基础设施层,通过复杂的拓扑结构让请求传递的过程变得更可靠。 作为一组轻量级高性能网络代理,和程序部署在一起,应用程序不需要知道它的存在。 在云原生应用中可靠地传递请求可能非常复杂,通过一系列强大技术来管理这种复杂性: 链路熔断、延迟感知、负载均衡,服务发现、服务续约及下线与剔除。 市面上的ServiceMesh框架有很多,我们选择了站在风口的Istio。 3.1 Istio 连接、管理和保护微服务的开放平台。 平台支持: Kubernetes, Mesos, Cloud Foundry。 可观察性:Metrics, logs, traces, dependency 。visualisation。 Service Identity & Security: 为服务、服务到服务的身份验证提供可验证的标识。 Traffic 管理: 动态控制服务之间的通信、入口/出口路由、故障注入。 Policy 执行: 前提检查,服务之间的配额管理。 3.2 我们为什么选择Istio? 因为有大厂支持~其实主要还是它的理念是相当好的。 虽然它才到1.0版本,我们是从 0.6 版本开始尝试体验,测试环境跑,然后0.7.1版本出了,我们升级到0.7.1版本跑,后来0.8.0LTS出了,我们开始正式使用0.8.0版本,并且做了一套升级方案。 目前最新版已经到了1.0.4, 但我们并不准备升级,我想等到它升级到1.2之后,再开始正式大规模应用。0.8.0LTS在现在来看小规模还是可以的。 3.3 Istio 架构 我们先来看一下Istio的架构。 其中Istio控制面板主要分为三大块,Pilot、Mixer、Istio-Auth。 Pilot: 主要作为服务发现和路由规则,并且管理着所有Envoy,它对资源的消耗是非常大的。 Mixer: 主要负责策略请求和配额管理,还有Tracing,所有的请求都会上报到Mixer。 Istio-Auth: 升级流量、身份验证等等功能,目前我们暂时没有启用此功能,需求并不是特别大,因为集群本身就是对外部隔离的。 每个Pod都会被注入一个Sidecar,容器里的流量通过iptables全部转到Envoy进行处理。 四、Kubernetes & Istio Istio可以独立部署,但显然它与Kuberntes结合是更好的选择。基于Kubernetes的小规模架构。有人担心它的性能,其实经过生产测试,上万的QPS是完全没有问题的。 4.1 Kubernetes Cluster 在资源紧缺的情况下,我们的k8s集群是怎么样的? 4.1.1 Master集群 Master Cluster: ETCD、Kube-apiserver、kubelet、Docker、kube-proxy、kube-scheduler、kube-controller-manager、Calico、 keepalived、 IPVS。 4.1.2 Node节点 Node: Kubelet、 kube-proxy 、Docker、Calico、IPVS。 (图片来源网络) 我们所调用的Master的API都是通过 keepalived 进行管理,某一master发生故障,能保证顺滑的飘到其他master的API,不影响整个集群的运行。 当然我们还配置了两个边缘节点。 4.1.3 Edge Node 边缘节点 流量入口 边缘节点的主要功能是让集群提供对外暴露服务能力的节点,所以它也不需要稳定,我们的IngressGateway 就是部署在这两个边缘节点上面,并且通过Keeplived进行管理。 4.2 外部服务请求流程 最外层是DNS,通过泛解析到Nginx,Nginx将流量转到集群的VIP,VIP再到集群的HAproxy,将外部流量发到我们的边缘节点Gateway。 每个VirtualService都会绑定到Gateway上,通过VirtualService可以进行服务的负载、限流、故障处理、路由规则及金丝雀部署。再通过Service最终到服务所在的Pods上。 这是在没有进行Mixer跟策略检测的情况下的过程,只使用了Istio-IngressGateway。如果使用全部Istio组件将有所变化,但主流程还是这样的。 4.3 Logging 日志收集我们采用的是低耦合、扩展性强、方便维护和升级的方案。 节点Filebeat收集宿主机日志。 每个Pods注入Filebeat容器收集业务日志。 Filebeat会跟应用容器部署在一起,应用也不需要知道它的存在,只需要指定日志输入的目录就可以了。Filebeat所使用的配置是从ConfigMap读取,只需要维护好收集日志的规则。 上图是我们可以从Kibana上看到所采集到的日志。 4.4 Prometheus + Kubernetes 基于时间序列的监控系统。 与kubernetes无缝集成基础设施和应用等级。 具有强大功能的键值数据模型。 大厂支持。 4.4.1 Grafana 4.4.2 Alarm 目前我们支持的报警有Wechat、kplcloud、Email、IM。所有报警都可在平台上配置发送到各个地方。 4.4.3 整体架构 整个架构由外围服务及集群内的基础服务组成,外围服务有: Consul作为配置中心来使用。 Prometheus+Grafana用来监控K8s集群。 Zipkin提供自己定义的链路追踪。 ELK日志收集、分析,我们集群内的所有日志会推送到这里。 Gitlab代码仓库。 Jenkins用来构建代码及打包成Docker镜像并且上传到仓库。 Repository 镜像仓库。 集群有: HAProxy+keeprlived 负责流量转发。 网络是Calico, Calico对kube-proxy的ipvs代理模式有beta级支持。如果Calico检测到kube-proxy正在该模式下运行,则会自动激活Calico ipvs支持,所以我们启用了IPVS。 集群内部的DNS是 CoreDNS。 我们部署了两个网关,主要使用的是Istio的 IngressGateway,TraefikIngress备用。一旦IngressGateway挂了我们可以快速切换到TraefikIngress。 上面是Istio的相关组件。 最后是我们的APP服务。 集群通过Filebeat收集日志发到外部的ES。 集群内部的监控有: State-Metrics 主要用来自动伸缩的监控组件 Mail&Wechat 自研的报警服务 Prometheus+Grafana+AlertManager 集群内部的监控,主要监控服务及相关基础组件 InfluxDB+Heapster 流数据库存储着所有服务的监控信息 4.5 有了Kubernetes那怎么部署应用呢? 4.5.1 研发打包成镜像、传仓库、管理版本 学习Docker。 学习配置仓库、手动打包上传麻烦。 学习k8s相关知识。 4.5.2 用Jenkins来负责打包、传镜像、更新版本 运维工作增加了不少,应用需要进行配置、服务需要做变更都得找运维。 需要管理一堆的YAML文件。 有没有一种傻瓜式的,不需要学习太多的技术,可以方便使用的解决方案? 五、Kplcloud platform 5.1 开普勒云平台 开普勒云平台是一个轻量级的PaaS平台。 为微服务化的项目提供一个可控的管理平台。 实现每个服务独立部署、维护、扩展。 简化流程,不再需要繁琐的申请流程,最大限度的自动化处理。 实现微服务的快速发布、独立监控、配置。 实现对微服务项目的零侵入式的服务发现、服务网关、链路追踪等功能。 提供配置中心,统一管理配置。 研发、产品、测试、运维甚至是老板都可以自己发布应用。 5.2 在开普勒平台部署服务 为了降低学习成本及部署难度,在开普勒平台上部署应用很简单,只需要增加一个Dockerfile 就好了。 Dockerfile 参考: 以上是普通模式,Jenkins代码Build及Docker build。 这是一种相对自由的部署方式,可以根据自己的需求进行定制,当然有学习成本。 5.2.1 为什么不自动生成Dockerfile呢? 其实完全可以做到自动生成Dockerfile,但每个服务的要求可能不一样,有些需要增加文件、有些在Build时需要增加参数等等。我们不能要求所有的项目都是一样的,这会阻碍技术的发展。所以退而求其次,我们给出模版,研发根据自己的需求调整。 5.3 工具整合 开普勒云平台整合了 gitlab,Jenkins,repo,k8s,istio,promtheus,email,WeChat 等API。 实现对服务的整个生命周期的管理。 提供服务管理、创建、发布、版本、监控、报警、日志已及一些周边附加功能,消息中心、配置中心、还能登陆到容器,服务下线等等。 可对服务进行一健调整服务模式、服务类型、一键扩容伸缩,回滚服务API管理以及存储的管理等操作。 5.4 发布流程 用户把自己的Dockerfile跟代码提交到Gitlab,然后在开普勒云平台填写一些参数创建自己的应用。 应用创建完后会在Jenkins创建一个Job,把代码拉取下来并执行Docker build(如果没有选择多阶构建会先执行go build或mvn),再把打包好的Docker image推送到镜像仓库,最后回调平台API或调用k8s通知拉取最新的版本。 用户只需要在开普勒云平台上管理好自己的应用就可以,其他的全部自动化处理。 5.5 从创建一个服务开始 我们从创建一个服务开始介绍平台。 平台主界面: 点击“创建服务”后进入创建页面。 填写基本信息: 填写详细信息: 基本信息以Golang为例,当选择其他语言时所需填写的参数会略有不同。 如果选择了对外提供服务的话,会进入第三步,第三步是填写路由规则,如没有特殊需求直接默认提交就行了。 5.5.1 服务详情
Build 升级应用版本: 调用服务模式,可以在普通跟服务网格之间调整。 服务是否提供对外服务的能力: 扩容调整CPU、内存: 调整启动的Pod数量: 网页版本的终端: 5.5.2 定时任务
5.5.3 持久化存储
管理员创建StorageClass跟PersistentVolumeClaim,用户只需要在自己服务选择相关的PVC进行绑写就行了。 存储使用的是NFS。 5.5.4 Tracing
5.5.5 Consul Consul当作配置中心来使用,并且我们提供Golang的客户端。 $ go get github.com/lattecake/consul-kv-client 它会自动同步consul的目录配置存在内存,获取配置只需要直接从内存拿就行了。 5.5.6 Repository Github: https://github.com/kplcloud/kplcloud Document: https://docs.nsini.com Demo: https://kplcloud.nsini.com 作者:王聪 首发:宜技之长 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> DNS服务器,即域名服务器,它作为域名和IP地址之间的桥梁,在互联网访问中,起到至关重要的作用。每一个互联网上的域名,背后都至少有一个对应的DNS。对于一个企业来说,如果你的DNS服务器因为攻击而无法使用,整个企业的网站、邮箱、办公系统将全部瘫痪,这意味着对你造成的是成千上万个用户的不可访问,将产生不可估量的影响。你的DNS服务器是否受到过安全威胁,它现在真的安全么? DNS面临着各种各样的网络安全威胁,它一直是网络基础架构中较弱的一环。 我们先来看看DNS服务器的威胁之一:DDoS攻击。DDoS攻击,即分布式拒绝服务攻击,攻击者通过控制大量的傀儡机,对目标主机发起洪水攻击,造成服务器瘫痪。 DDoS攻击通常分为流量型攻击和应用型攻击。以bps为单位的流量型的攻击,这类攻击会瞬间堵塞网络带宽;以qps为单位的应用层攻击,主要是将服务器的资源耗尽,攻击将造成DNS服务器反应缓慢直至再也无法响应正常的请求。DNS服务易受攻击的原因,除了专门针对DNS服务器发起的;还有就是利用DNS服务的特点,用“DNS放大攻击”对其他受害主机发起攻击。如下就是DNS放大攻击的示意图,DNS方法攻击的危害极大。 16年美国针对 DNS 供应商Dyn,爆发来史上最大规模的DDoS攻击,这一攻击造成了半个美国网络瘫痪的严重影响。这次严重的攻击事件,就是DNS放大攻击造成的。 接下来,我们再来看看DNS劫持对DNS服务器会造成哪些威胁。DNS劫持是指攻击者在劫持的网络范围内拦截域名解析的请求,篡改了某个域名的解析结果。比如用户本来想访问www.aliyun.com,却被指引到了另一个假冒的地址上,从而达到非法窃取用户信息或其他不正常的网络服务的目的。对用户而言,DNS劫持是很难感知的,因此造成的影响极大。如2013年就爆发过一次大型的DNS钓鱼攻击事件,导致约800万用户感染。攻击者通常会通过两种方式实现DNS劫持,一种是直接攻击域名注册商或者域名站点获取控制域名的账户口令,这样可以修改域名对应的IP地址,另外一种方式是攻击权威名称服务器,直接修改区域文件内的资源记录。如下图,中国电信发现域名劫持时,给访问用户的提醒。 DNS劫持主要是将NS纪录指向到黑客可以控制的DNS服务器,而DNS投毒却是指一些有意或无意制造出来的域名服务器数据包,利用控制DNS缓存服务器,将域名指引到了不正确的IP地址。一般DNS服务器为了加快解析速度,通常都会把访问过的域名服务器数据暂存起来。待下次其他用户需要解析域名时,如果发现缓存中有该数据,就立刻调出来提供服务。如果DNS的缓存受到了污染,就会把访问的域名指引到错误的服务器上。简单点说,DNS污染是指把自己伪装成DNS服务器,在检查到用户访问某些网站后,使域名解析到错误的IP地址。 DNS服务如此重要,当对DNS的解析配置操作时,也必须谨慎小心。这里最后介绍的一种DNS常见威胁,就是人为误操作造成的。大家都知道域名做好解析后并不能立即访问到您的网站,因为解析生效需要时间。由于各地的dns服务器刷新时间不同,各地的大概时间范围为0-24小时。最长的生效时间,达到24小时,如果不小心改错了域名的解析配置,也将造成极大的影响。这就要求我们对域名的管理需要精细化,分配好使用者的权限,存留好操作记录等日志信息。在域名修改配置或进行迁移时,操作一定要谨慎。 以上简单介绍了DNS服务器可能遇到的常见威胁。作为互联网最基础、最核心的服务,DNS服务安全至关重要。打垮DNS服务能够间接打垮一家公司的全部业务,或者打垮一个地区的网络服务。面对重重威胁,怎么才能保障好我们的DNS服务器呢?接下来的专题,我们会针对DNS服务器常受到的攻击一一突破,敬请期待。 原文链接 本文为云栖社区原创内容,未经允许不得转载。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 2019年10月15日,由上海市软件行业协会、中国云体系产业创新战略联盟、拓普会展和国际云安全联盟联合主办的ECSC 2019第二届企业云服务大会在上海盛大举办。作为 中国领先的实干型数字化中台SaaS快速开发领导者的JEPaaS云平台 应邀出席本届大会。 在企业数字化转型升级不断推进的当下,企业云化已成为下一阶段商业竞赛的重要角力点。企业如何部署多元化云服务架构,驱动数字化转型和重构,推进企业创新和管理变革成为迫切思考的问题? 针对这些问题,本届大会以“企业智变—云化未来”为主题,聚焦数字化变革、多云管理、边缘计算、云安全、SaaS应用、数据智能、云网融合、5G新机遇等热点话题,邀请了来自金融、教育、医疗、零售、制造等各领域各行业企业的领导、专家和学者等业内资深人士,共同探讨和分享了企业数字化转型中上云时遇到的难题及对云服务需求的变化。 在15日下午大会举行的“技术驱动企业新动能”分论坛中,JEPaaS创始人/凯特伟业CEO云凤程先生受邀出席,与业内专家就“用云之道---如何在安全/效率/成本三者之间寻求平衡点”的话题进行了深入探讨。云总在致辞中分享了他对于当下企业数字化转型趋势的看法,并就企业在上云过程中所遇到的难题分享了JEPaaS的成功案例。
JEPaaS创始人/凯特伟业CEO云凤程先生发表致辞 JEPaaS作为北京凯特科技着力打造的一款企业级信息化应用,凝聚了十多年来服务于国企、央企、集团公司等大型集团企业的深厚技术积累与丰富服务经验。多年的技术积淀和对客户业务流程以及应用管理难点的深刻理解,以及数万家企业的成功实践,让JEPaaS构建了一套专业的数字化开发体系。其中,JEPaaS拥有的四大核心承载力:数字化中台工具、SaaS应用开发管理体系、低代码快速开发以及可实现万物互联的物联网接口引擎,这四大核心承载力为企业提高核心业务生产率发挥着显著作用。 与此同时,在大会现场的展会区域,JEPaaS展台也吸引众多参会嘉宾和企业代表们前来进行交流互动,JEPaaS的专家顾问团队和现场的嘉宾代表们进行了详细、深入的洽谈。 随着数字经济的快速创新发展,企业上云已成为各企业转型发展的共识。JEPaaS此次参加大会,将JEPaaS对于企业数字化创新的思考、理解及服务展示给业界,JEPaaS的创新思路、观点和技术也必将给业界带来更多新启发。 未来,JEPaaS将持续不断为各行业提供优质、可靠的企业数字化解决方案和服务,为推动中国企业的信息化和数字化,引领和服务更多企业迈向成功而不懈努力! 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 前言 首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute) : 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息 参考 。 Fun : Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档 参考 。 备注: 本文介绍的技巧需要 Fun 版本大于等于 3.6.7。 函数计算运行环境中内置一些常用字体,但仍不满足部分用户的需求。如果应用中需要使用其它字体,需要走很多弯路。本文将介绍如何通过 Fun 工具将自定义字体部署到函数计算,并正确的在应用中被引用。 你需要做什么? 在代码(CodeUri)目录新建一个 fonts 目录 将字体复制到 fonts 目录 使用 fun deploy 进行部署 工具安装 建议直接从这里下载二进制可执行程序,解压后即可直接使用。 下载地址 。 执行 fun --version 检查 Fun 是否安装成功。 $ fun --version 3.7.0 示例 demo 涉及的代码,托管在 github 上。项目目录结构如下: $ tree -L -a 1 ├── index.js ├── package.json └── template.yml index.js 中代码: 'use strict'; var fontList = require('font-list') module.exports.handler = async function (request, response, context) { response.setStatusCode(200); response.setHeader('content-type', 'application/json'); response.send(JSON.stringify(await fontList.getFonts(), null, 4)); }; index.js 中借助 node 包 font-list 列出系统上可用的字体。 template.yml: ROSTemplateFormatVersion: '2015-09-01' Transform: 'Aliyun::Serverless-2018-04-03' Resources: fonts-service: # 服务名 Type: 'Aliyun::Serverless::Service' Properties: Description: fonts example fonts-function: # 函数名 Type: 'Aliyun::Serverless::Function' Properties: Handler: index.handler Runtime: nodejs8 CodeUri: ./ InstanceConcurrency: 10 Events: http-test: Type: HTTP Properties: AuthType: ANONYMOUS Methods: - GET - POST - PUT tmp_domain: # 临时域名 Type: 'Aliyun::Serverless::CustomDomain' Properties: DomainName: Auto Protocol: HTTP RouteConfig: Routes: /: ServiceName: fonts-service FunctionName: fonts-function template.yml 中定义了名为 fonts-service 的服务,此服务下定义一个名为 fonts-function 的 http trigger 函数。tmp_domain 中配置自定义域名中路径(/)与函数(fonts-service/fonts-function)的映射关系。 1. 下载字体 你可以通过 这里 下载自定义字体 Hack ,然后将 复制 字体到 fonts 目录。 此时 demo 目录结构如下: $ tree -L 2 -a ├── fonts(+) │ ├── Hack-Bold.ttf │ ├── Hack-BoldItalic.ttf │ ├── Hack-Italic.ttf │ └── Hack-Regular.ttf ├── index.js ├── package.json └── template.yml 2. 安装依赖 $ npm install 3. 部署到函数计算 可以通过 fun deploy 直接发布到远端。 4. 预览线上效果 fun deploy 部署过程中,会为此函数生成有时效性的临时域名: 打开浏览器,输入临时域名并回车: 可以看到字体 Hack 已生效!!! 原理介绍: fun deploy 时,如果检测到 CodeUri 下面有 fonts 目录,则为用户在 CodeUri 目录生成一个 .fonts.conf 配置文件。在该配置中,相比于原来的 /etc/fonts/fonts.conf 配置,添加了 /code/fonts 作为字体目录。 自动在 template.yml 中添加环境变量,FONTCONFIG_FILE = /code/.fonts.conf,这样在函数运行时就可以正确的读取到自定义字体目录。 如果依赖过大,超过函数计算的限制(50M)则: 将 fonts 目录添加到 .nas.yml 将 fonts 对 nas 的映射目录追加到 .fonts.conf 配置 fun deploy 对大依赖的支持可参考 《开发函数计算的正确姿势——轻松解决大依赖部署》 总结 你只需要在代码(CodeUri)目录新建一个 fonts 目录,然后复制所有字体到该目录即可。Fun 会自动帮你处理配置文件(.fonts.conf),环境变量以及大依赖场景的情况。如果大家在使用 Fun 的过程中遇到了一些问题,可以在 github 上提 issue ,或者加入我们的钉钉群 11721331 进行反馈。 “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。” 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 上一篇《 玩转阿里云Terraform(一):Terraform 是什么 》介绍了 Terraform 的基本定义和特点之后,本文将着重介绍几个Terraform中的关键概念。 Terraform 关键概念 在使用Terraform的过程中,通常接触到很多名词,如configuration,provider,resource,datasource,state,backend,provisioner等,本文将一一跟大家介绍这些概念。 Configuration:基础设施的定义和描述 “基础设施即代码(Infrastructure as Code)”,这里的Code就是对基础设施资源的代码定义和描述,也就是通过代码表达我们想要管理的资源。 # VPC 资源 resource "alicloud_vpc" "vpc" { name = "tf_vpc" cidr_block = "172.16.0.0/16" } # VSwitch 资源 resource "alicloud_vswitch" "vswitch" { vpc_id = alicloud_vpc.vpc.id cidr_block = "172.16.1.0/24" availability_zone = "cn-beijing-a" } 对所有资源的代码描述都需要定义在一个以 tf 结尾的文件用于Terraform加载和解析,这个文件我们称之为“Terraform模板”或者“Configuration”。 Provider:基础设施管理组件 Terraform 通常用于对云上基础设施,如虚拟机,网络资源,容器资源,存储资源等的创建,更新,查看,删除等管理动作,也可以实现对物理机的管理,如安装软件,部署应用等。 【Provider】 是一个与Open API直接交互的后端驱动,Terraform 就是通过Provider来完成对基础设施资源的管理的。不同的基础设施提供商都需要提供一个Provider来实现对自家基础设施的统一管理。目前Terraform目前支持超过160多种的providers,大多数云平台的Provider插件均已经实现了,阿里云对应的Provider为 alicloud 。 在操作环境中,Terraform和Provider是两个独立存在的package,当运行Terraform时,Terraform会根据用户模板中指定的provider或者resource/datasource的标志自动的下载模板所用到的所有provider,并将其放在执行目录下的一个隐藏目录 .terraform 下。 provider "alicloud" { version = ">=1.56.0" region = "cn-hangzhou" configuration_source = "terraform-alicloud-modules/classic-load-balance" } 模板中显示指定了一个阿里云的Provider,并显示设置了provider的版本为 1.56.0+ (默认下载最新的版本),指定了需要管理资源的region,指定了当前这个模板的标识。 通常Provider都包含两个主要元素 resource 和 data source。 Resource:基础设施资源和服务的管理 在Terraform中,一个具体的资源或者服务称之为一个resource,比如一台ECS 实例,一个VPC网络,一个SLB实例。每个特定的resource包含了若干可用于描述对应资源或者服务的属性字段,通过这些字段来定义一个完整的资源或者服务,比如实例的名称(name),实例的规格(instance_type),VPC或者VSwitch的网段(cidr_block)等。 定义一个Resource的语法非常简单,通过 resource 关键字声明,如下: # 定义一个ECS实例 resource "alicloud_instance" "default" { image_id = "ubuntu_16_04_64_20G_alibase_20190620.vhd" instance_type = "ecs.sn1ne.large" instance_name = "my-first-vm" system_disk_category = "cloud_ssd" ... } 其中 alicloud_instance 为 资源类型(Resource Type) ,定义这个资源的类型,告诉Terraform这个Resource是阿里云的ECS实例还是阿里云的VPC。 default 为 资源名称(Resource Name) ,资源名称在同一个模块中必须唯一,主要用于供其他资源引用该资源。 大括号里面的block块为 配置参数(Configuration Arguments) ,定义资源的属性,比如ECS 实例的规格、镜像、名称等。 显然这个Terraform模板的功能为在阿里云上创建一个ECS实例,镜像ID为 ubuntu_16_04_64_20G_alibase_20190620.vhd ,规格为 ecs.sn1ne.large ,自定义了实例名称和系统盘的类型。 除此之外,在Terraform中,一个资源与另一个资源的关系也定义为一个资源,如一块云盘与一台ECS实例的挂载,一个弹性IP(EIP)与一台ECS或者SLB实例的绑定关系。这样定义的好处是,一方面资源架构非常清晰,另一方面,当模板中有若干个EIP需要与若干台ECS实例绑定时,只需要通过Terraform的 count 功能就可以在无需编写大量重复代码的前提下实现绑定功能。 resource "alicloud_instance" "default" { count = 5 ... } resource "alicloud_eip" "default" { count = 5 ... } resource "alicloud_eip_association" "default" { count = 5 instance_id = alicloud_instance.default[count.index].id allocation_id = alicloud_eip.default[count.index].id } 显然这个Terraform模板的功能为在阿里云上创建5个ECS实例和5个弹性IP,并将它们一一绑定。 Data Source:基础设施资源和服务的查询 对资源的查询是运维人员或者系统最常使用的操作,比如,查看某个region下有哪些可用区,某个可用区下有哪些实例规格,每个region下有哪些镜像,当前账号下有多少机器等,通过对资源及其资源属性的查询可以帮助和引导开发者进行下一步的操作。 除此之外,在编写Terraform模板时,Resource使用的参数有些是固定的静态变量,但有些情况下可能参数变量不确定或者参数可能随时变化。比如我们创建ECS 实例时,通常需要指定我们自己的镜像ID和实例规格,但我们的模板可能随时更新,如果在代码中指定ImageID和Instance,则一旦我们更新镜像模板就需要重新修改代码。 在Terraform 中,Data Source 提供的就是一个查询资源的功能,每个data source实现对一个资源的动态查询,Data Souce的结果可以认为是动态变量,只有在运行时才能知道变量的值。 Data Sources通过 data 关键字声明,如下: // Images data source for image_id data "alicloud_images" "default" { most_recent = true owners = "system" name_regex = "^ubuntu_18.*_64" } data "alicloud_zones" "default" { available_resource_creation = "VSwitch" enable_details = true } // Instance_types data source for instance_type data "alicloud_instance_types" "default" { availability_zone = data.alicloud_zones.default.zones.0.id cpu_core_count = 2 memory_size = 4 } resource "alicloud_instance" "web" { image_id = data.alicloud_images.default.images[0].id instance_type = data.alicloud_instance_types.default.instance_types[0].id instance_name = "my-first-vm" system_disk_category = "cloud_ssd" ... } 如上例子中的ECS Instance 没有指定镜像ImageID和实例规格,而是通过 data 引用,Terraform运行时将首先根据镜像名称前缀选择系统镜像,如果同时有多个镜像满足条件,则选择最新的镜像。实例规格也是类似,在某个可用区下选择2核4G的实例规格进行返回。 State:保存资源关系及其属性文件的数据库 Terraform创建和管理的所有资源都会保存到自己的数据库上,这个数据库不是通常意义上的数据库(MySQL,Redis等),而是一个文件名为 terraform.tfstate 的文件,在Terraform 中称之为 state ,默认存放在执行Terraform命令的本地目录下。这个 state 文件非常重要,如果该文件损坏,Terraform 将认为已创建的资源被破坏或者需要重建(实际的云资源通常不会受到影响),因为在执行Terraform命令是,Terraform将会利用该文件与当前目录下的模板做Diff比较,如果出现不一致,Terraform将按照模板中的定义重新创建或者修改已有资源,直到没有Diff,因此可以认为Terraform是一个有状态服务。 当涉及多人协作时不仅需要拷贝模板,还需要拷贝 state 文件,这无形中增加了维护成本。幸运的是,目前Terraform支持把 state 文件放到远端的存储服务 OSS 上或者 consul 上,来实现 state 文件和模板代码的分离。具体细节可参考 官方文档Remote State 或者关注后续文章的详细介绍。 Backend:存放 State 文件的载体 正如上节提到,Terraform 在创建完资源后,会将资源的属性存放在一个 state 文件中,这个文件可以存放在本地也可以存放在远端。存放 state 文件的载体就是 Backend 。 Backend 分为本地(local)和远端(remote)两类,默认为本地。远端的类型也非常多,目前官方网站提供的有13种,并且阿里云的 OSS 就位列其中。 使用远端的Backend,既可以降低多人协作时对state的维护成本,而且可以将一些敏感的数据存放在远端,保证了数据的安全性。 Provisioner:在机器上执行操作的组件 Provisioner 通常用来在本地机器或者登陆远程主机执行相关的操作,如 local-exec provisioner 用来执行本地的命令, chef provisioner 用来在远程机器安装,配置和执行chef client, remote-exec provisioner 用来登录远程主机并在其上执行命令。 Provisioner 通常跟 Provider一起配合使用,provider用来创建和管理资源,provisioner在创建好的机器上执行各种操作。 小结 Terraform中涉及到的概念非常多,用法也多种多样,本文只是介绍了其中的几个关键的概念,如果想要了解更多的概念,可以访问Terraform的官方网站。学习Terraform最好的方式是要多动手,使用terraform去实现和操作一些特定的场景,在不断操作的过程中,持续解决遇到的问题可以帮助大家更快和更好的使用Terraform。下一篇将向大家介绍Terraform的运行原理和基本操作。 最后,欢迎大家关注 Terraform,关注阿里云 Provider,如有任何使用问题,可直接在 terraform-provider-alicloud 提交您的问题,我们将尽快解决。 阅读原文 本文为云栖社区原创内容,未经允许不得转载。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 作者 | 元毅 阿里巴巴高级开发工程师 阿里巴巴云原生公众号后台回复 Knative ,免费下载《Knative 云原生应用开发指南》电子书! 想必大家都比较了解 RocketMQ 消息服务,那么 RocketMQ 与 Serverless 结合会碰撞出怎样的火花呢?我们今天介绍一下如何基于 RocketMQ + Knative 驱动云原生 Serverless 应用 。本文主要从以下几个方面展开介绍: 云原生与 Serverless Knative 简介 RocketMQSource 餐饮配送场景示例 云原生 先看一下 CNCF 对云原生的定义: 云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式 API。 这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。 其实云原生旨在以标准化云服务的提供方式衔接云厂商和客户。这种方式对于客户而言降低了上云和跨云迁移的成本,让客户始终保有和云厂商议价的能力;对云厂商而言,因为客户跨云迁移的成本低,所以只要能提供性价比更高的云服务,就能很容易的聚集大量用户。 Serverless Serverless(无服务器架构)是指服务端逻辑由开发者实现,运行在无状态的计算容器中,由事件触发,完全被第三方管理,其业务层面的状态则存储在数据库或其他介质中。 Serverless 可以理解为云原生技术发展的高级阶段,使开发者更聚焦在业务逻辑,而减少对基础设施的关注。 这里提到的是 Functions Serverless, 其实除了 Functions Serverless, 还有另外一种 Serverless 形态:容器化的Serverless。相较于 Function Serverless,容器化的 Serverless, 可移植性更强, 开发者对复杂应用程序能进行更好的掌控。除此之外,对于那些经历过容器时代洗礼的用户,容器化的 serverless或许是一种更好的选择。 对于Serverless, 有如下几点需要关注一下: 事件(event)驱动:Serverless 是由事件(event)驱动(例如 HTTP、pub/sub)的全托管计算服务; 自动弹性:按需使用,削峰填谷; 按使用量计费:相对于传统服务按照使用的资源(ECS 实例、VM 的规格等)计费,Serverless 场景下更多的是按照服务的使用量(调用次数、时长等)计费; 绿色的计算: 所谓绿色的计算其实就是最大化的提升资源使用效率,减少资源浪费,做的“节能减排”。 Knative 上面提到了容器化的 Serverless,那么有没有这样的 Serveless 平台框架呢?答案就是:Knative。 Knative 是在 2018 的 Google Cloud Next 大会上发布的一款基于 Kubernetes 的 Serverless 编排引擎。Knative 一个很重要的目标就是制定云原生、跨平台的 Serverless 编排标准。Knative 是通过整合容器构建(或者函数)、工作负载管理(弹性)以及事件模型这三者来实现的这一 Serverless 标准。Knative 社区的当前主要贡献者有 Google、Pivotal、IBM、RedHat。另外像 CloudFoundry、OpenShift 这些 PaaS 提供商都在积极的参与 Knative 的建设。 1. Knative 核心模块 Knative 核心模块主要包括事件驱动框架 Eventing 和部署工作负载的 Serving。 2. Serverless 服务引擎 - Serving Knative Serving 核心能力就是其简洁、高效的应用托管服务,这也是其支撑 Serverless 能力的基础。Knative 提供的应用托管服务可以大大降低直接操作 Kubernetes 资源的复杂度和风险,提升应用的迭代和服务交付效率。当然作为 Severlesss Framework 就离不开按需分配资源的能力,阿里云容器服务 Knative 可以根据您应用的请求量在高峰时期自动扩容实例数,当请求量减少以后自动缩容实例数,可以非常自动化的帮助您节省成本。 Serving 通过与 Istio 结合还提供了强大的流量管理能力和灵活的灰度发布能力。流量管理能力可以根据百分比切分流量,灰度发布能力可以根据流量百分比进行灰度,同时灰度发布能力还能通过自定义 tag 的方式进行上线前的测试,非常便于和自己的 CICD 系统集成。 Serving 应用模型 Service: 对应用 Serverless 编排的抽象,通过 Service 管理应用的生命周期; Configuration: 当前期望状态的配置。每次更新 Service 就会更新 Configuration; Revision: configuration 的每次更新都会创建一个快照,用来做版本管理; Route: 将请求路由到 Revision,并可以向不同的 Revision 转发不同比例的流量。 3. 事件驱动框架 - Eventing 1.采用 CloudEvent 作为事件传输协议: CloudEvent 以通用的格式描述事件数据,提供跨平台的服务交互能力。KnativeEventing 使用 CloudEvent 作为事件传输标准,极大的提升了应用的跨平台可移植性; 2.外部事件源接入和注册: 提供 Github、RocketMQ 以及 Kafka 等事件源的支持,当然用户可以自定义事件源。 3.事件的订阅和触发:引入 Broker 和 Trigger 模型意义,不仅将事件复杂的处理实现给用户屏蔽起来,更提供丰富的事件订阅、过滤机制; 4.兼容现有消息系统:KnativeEventing 充分解耦了消息系统的实现,目前除了系统自身支持的基于内存的消息通道 InMemoryChannel 之外,还支持 Kafka、NATSStreaming 等消息服务,此外可以方便的对接现有的消息系统。 Eventing 中 Broker/Trigger模型 这里介绍一下 Eventing 中 Broker/Trigger 模型, 其实并不复杂。外部事件源将事件发送给 Broker, Broker 接收事件之后发送给对应的 Channel(也就是消息缓存,转发的地方,如 Kafka,InMemoryChannel 等),通过创建 Trigger 订阅 Broker 实现事件的订阅,另外在 Trigger 中定义对应的服务,实现最终的事件驱动服务。 消息队列 RocketMQ 消息队列 RocketMQ 版是阿里云基于 Apache RocketMQ 构建的低延迟、高并发、高可用、高可靠的分布式消息中间件。消息队列 RocketMQ 版既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。 RocketMQSource RocketMQSource 是 Knative 平台的 RocketMQ 事件源。其可以将 RocketMQ 集群的消息以 Cloud Event 的格式实时转发到 Knative 平台,是 Apahe RocketMQ 和 Knative 之间的连接器。 Knative + RocketMQ 场景示例-餐饮配送场景 我们接下来以餐饮配送为例进行演示,餐饮配送场景具有以下特征: 餐饮配送一天之内存在明显的高峰、低谷; 高峰时间下单量很大。 针对这样的情况,我们采用消息驱动 Serverless, 在高峰的时候自动扩容资源,在低谷的时候缩减资源,按需使用能极大的提升资源使用率,从而降低成本。 1. 典型架构 如上图所示,当用餐时间来临,客户点餐生成下单消息发送到 RocketMQ, 通过 RocketMQSource 获取下单消息转换成事件发送到 Broker,通过 Trigger 订阅下单事件最终驱动订单服务生成订餐单。采用该方案具有以下优势: 通过 Knative 技术以 RocketMQ 为核心将餐饮配送系统 Serverless 化可以极大程度降低服务器运维与成本; Knative 的弹性可以帮你轻松应对早、中、晚三餐资源高峰需求; 系统以 RocketMQ 做异步解耦,避免长链路调用等问题,提高系统可用性。 2. 操作 部署 Knative 参见 阿里云容器服务部署 Knative 。 部署 RocketMQSource 在 Knative 组件管理中,选择 RocketMQSource 点击部署。 部署订单服务 参考示例 代码仓库 。 一键部署服务命令如下: kubectl apply -f 200-serviceaccount.yaml -f 202-clusterrolebinding.yaml -f 203-secret.yaml -f alirocketmqsource.yaml -f broker.yaml -f ksvc-order-service.yaml -f trigger.yaml 模拟高峰订餐下单 通过模拟下单,往 RocketMQ 中并发发送消息即可。消息格式参考: {"orderId":"123214342","orderStatus":"completed","userPhoneNo":"152122131323","prodId":"2141412","prodName":"test","chargeMoney":"30.0","chargeTime":"1584932320","finishTime":"1584932320"} 3. 演示效果 如下图所示: 其它应用场景 1. Knative + RocketMQ 典型场景 - 构建 Serverless 电商系统 Knative 弹性可以帮你轻松应对团购、双11 等电商的大促活动; 系统以 RocketMQ 为中心做异步解耦,避免长链路调用等问题,提高系统可用性。 2. Knative + RocketMQ 典型场景- 构建监控告警平台 Metric、Log 等数据通过 RocketMQ 集群推送到 Knative 服务; Knative 服务通过数据分析将告警内容推送钉钉或 slack 等通讯工具; Knative 服务可以将 Metric 或 logs 数据进行处理,推送第三方系统。 3. Knative + RocketMQ 典型场景- 多数据格式转换 处理数据日志以生成多个结果派生词,这些结果派生词可用于运营,营销,销售等; 将内容从一种格式转换为另一种格式,例如,将 Microsoft Word 转换为 PDF; 需要转换为多种格式的主媒体文件。 总结 通过以上 RocketMQ 事件驱动 Knative Serverless 应用的介绍,是否也给你碰撞出了火花?可以结合自身的应用场景不妨一试,相信会给你带来不一样的体验。 欢迎钉钉扫码加入交流群 <关注阿里巴巴云原生公众号,回复 Knative 即可下载电子书> “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。” 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 作者 | 冬岛 阿里云容器平台工程师 **导读:**虽然说 Knative 默认就支持 WebSocket 和 gRPC,但在使用中会发现,有时想要把自己的 WebSocket 或 gRPC 部署到 Knative 中,还是存在各种不顺利。虽然最后排查发现,大多是自己的程序问题或是配置错误导致的。本文分别给出了一个 WebSocket 和 gRPC 的例子,当需要在生产或者测试环境部署相关服务时,可以使用本文给出的示例进行 Knative 服务的测试。 WebSocket 如果自己手动的配置 Istio Gateway 支持 WebSocket 就需要开启 websocketUpgrade 功能。但使用 Knative Serving 部署其实就自带了这个能力。本示例的完整代码放在 https://github.com/knative-sample/websocket-chat ,这是一个基于 WebSocket 实现的群聊的例子。 使用浏览器连接到部署的服务中就可以看到一个接收信息的窗口和发送信息的窗口。当你发出一条信息以后所有连接进来的用户都能收到你的消息。所以你可以使用两个浏览器窗口分别连接到服务中,一个窗口发送消息一个窗口接收消息,以此来验证 WebSocket 服务是否正常。 本示例是在 gorilla/websocket 基础之上进行了一些优化: 代码中添加了 vendor 依赖,你下载下来就可以直接使用 添加了 Dockerfile 和 Makefile 可以直接编译二进制和制作镜像 添加了 Knative Sevice 的 yaml 文件( service.yaml ),你可以直接提交到 Knative 集群中使用 也可以直接使用编译好的镜像 registry.cn-hangzhou.aliyuncs.com/knative-sample/websocket-chat:2019-10-15 Knative Service 配置: apiVersion: serving.knative.dev/v1 kind: Service metadata: name: websocket-chat spec: template: spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/websocket-chat:2019-10-15 ports: - name: http1 containerPort: 8080 代码 clone 下来以后执行 kubectl apply -f service.yaml 把服务部署到 Knative 中,然后直接访问服务地址即可使用。 查看 ksvc 列表,并获取访问域名。 └─# kubectl get ksvc NAME URL LATESTCREATED LATESTREADY READY REASON websocket-chat http://websocket-chat.default.serverless.kuberun.com websocket-chat-7ghc9 websocket-chat-7ghc9 True 现在使用浏览器打开 http://websocket-chat.default.serverless.kuberun.com 即可看到群聊窗口。 打开两个窗口,在其中一个窗口发送一条消息,另外一个窗口通过 WebSocket 也收到了这条消息。 gRPC gRPC 不能通过浏览器直接访问,需要通过 client 端和 server 端进行交互。本示例的完整代码放在 https://github.com/knative-sample/grpc-ping-go ,本示例会给一个可以直接使用的镜像,测试 gRPC 服务。 Knative Service 配置: apiVersion: serving.knative.dev/v1 kind: Service metadata: name: grpc-ping spec: template: spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/grpc-ping-go:2019-10-15 ports: - name: h2c containerPort: 8080 代码 clone 下来以后执行 kubectl apply -f service.yaml 把服务部署到 Knative 中。 获取 ksvc 列表和访问域名: └─# kubectl get ksvc NAME URL LATESTCREATED LATESTREADY READY REASON grpc-ping http://grpc-ping.default.serverless.kuberun.com grpc-ping-p2tft Unknown RevisionMissing websocket-chat http://websocket-chat.default.serverless.kuberun.com websocket-chat-6hgld websocket-chat-6hgld True 现在我们已经知道 gRPC server 的地址是 grpc-ping.default.serverless.kuberun.com,端口是 80,那么我们可以发起测试请求: └─# docker run --rm registry.cn-hangzhou.aliyuncs.com/knative-sample/grpc-ping-go:2019-10-15 /client -server_addr="grpc-ping.default.serverless.kuberun.com:80" -insecure 2019/10/16 11:35:07 Ping got hello - pong 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854794231 +0800 CST m=+73.061909052 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854827273 +0800 CST m=+73.061942072 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854835802 +0800 CST m=+73.061950606 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854842843 +0800 CST m=+73.061957643 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854849211 +0800 CST m=+73.061964012 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854855249 +0800 CST m=+73.061970049 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854861659 +0800 CST m=+73.061976460 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854875071 +0800 CST m=+73.061989873 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854905416 +0800 CST m=+73.062020221 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.85491183 +0800 CST m=+73.062026630 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.85492533 +0800 CST m=+73.062040133 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854932285 +0800 CST m=+73.062047083 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854946977 +0800 CST m=+73.062061782 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854953311 +0800 CST m=+73.062068112 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854966639 +0800 CST m=+73.062081440 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854973939 +0800 CST m=+73.062088739 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854985463 +0800 CST m=+73.062100268 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854993275 +0800 CST m=+73.062108073 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.854999812 +0800 CST m=+73.062114613 2019/10/16 11:35:07 Got pong 2019-10-16 11:35:07.855012676 +0800 CST m=+73.062127479 小结 本文通过两个例子分别展示了 WebSocket 和 gRPC 的部署方法: WebSocket 示例通过一个 chat 的方式展示发送和接受消息 gRPC 通过启动一个 client 的方式展示 gRPC 远程调用的过程 作者简介: 冬岛,阿里云容器平台工程师,负责阿里云容器平台 Knative 相关工作。 了解更多 ACK 详情: https://www.aliyun.com/product/kubernetes 欢迎加入 Knative 交流群 “ 阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术公众号。” 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 从去年4月份开始我们公司就开始使用 云桌面 来进行上网办公的,在这一年多的使用过程中并没有出现像网上和有些用户说的那样,说云桌面各种坑老是出现这样和那样的问题,而我们之所以用的还不错,主要是因为我们在部署云桌面的时应用了一些小技巧的,这里就拿来和大家分享下我们部署云桌面时的几个小技巧的。 首先明确定位,首先一点我认为很重要的就是我们公司在部署云桌面的时候就明确了我们部署云桌面的目的是什么,想要达到什么样的效果。我们是一家互联网公司应用最多的就是浏览网页和QQ以及PS这些,基本不涉及3D类应用,而公司目前最看重的就是数据安全,确保数据不被拷贝和泄露出去的,我们后续的工作都是围绕这些进行的。 其次多找厂家,这里说的多找厂家并不是满世界的厂家都找过来的,而是说在部署云桌面的时候不能只找一家厂家的,而是多找几家符合自己需求和定位的厂家进行多方面的对比,从这些厂家中找一个性价比各个方面都符合公司需求的厂家的,当然也不能找太多厂家对比这样自己会更茫然的,我的建议是找3到4家左右就可以的,这样比较容易对比。 第三进行测试,这一点很重要,在选好厂家之后我们并没有说价格合适就马上部署的,而是进行1个星期左右的测试使用的,测试厂家说的这些云桌面功能是否都可以实现的,以及在使用过程是否有需要改进和调整的地方,以及系统桌面的稳定性这些都需要测试一下的。确保部署后的正常使用的。 第四先小后大,测试没问题部署的时候要采取先小后大的原则,即先在某个部门部署云桌面使用的,等这个部门使用没有问题之后,然后在慢慢普及到全部员工统一使用的,因为云桌面毕竟是一个新的应用,如果一开始就大规模的部署容易造成某些的员工不适应,也能防止在部署过程中出现问题而影响全部员工的正常使用的。 最后必要培训,虽然说云桌面的使用习惯和传统的PC差不多,但是对于使用前的一些培训还是很有必要的,我们在部署的时候就专门叫云桌面厂家的工程师专门给我们IT部门运维人员做了管理和维护上培训的,同时给员工做了一些使用上的培训。确保云桌面的管理维护和使用都是没有问题的。 其实当前云桌面的功能和技术是已经很成熟的了,在很多的应用场景中都是可以使用的,而之所以有人说它坑,主要还是因为在部署的时候没有做好准备工作的。 来源禹龙云 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 作者 | 临石、元毅、冬岛、衷源、天元 业界要闻 全球首个开放应用模型 OAM 开源 2019 年 10 月 17 日,阿里巴巴合伙人、阿里云智能基础产品事业部总经理蒋江伟(花名:小邪)在 Qcon 上海重磅宣布,阿里云与微软联合推出开放应用模型 Open Application Model (OAM)开源项目。OAM 的愿景是以标准化的方式沟通和连接应用开发者、运维人员、应用基础设施,让云原生应用管理与交付变得更加简洁,高效,并且可控。 KubeVirt 进入 CNCF Sandbox KubeVirt 尽管容器技术提供了各种便利,但是在特定情况下使用 VM 仍然是不可避免的(编者按:VM 只是选择之一,不同的安全容器解决方案都可以在不同方面代替单纯的 VM)。来自 Redhat 的 KubeVirt 项目可以提供在 K8s 集群内部准备、部署、运行和管理 VM 的能力,让用户可以使用 Pod 一样来使用一个 VM。现在,这个项目已经正式进入了 CNCF 的 Sandbox 了。 Megalix 发布 KubeAdvisor 1.0 版本 KubeAdvisor 日前发布了 1.0 版本。KubeAdvisor 一款辅助 K8s 运维的工具,能够扫描 K8s 集群的资源、状态、配置等,通过提供恰当的“可观测性”(编者按:眼花缭乱的监控数据和曲线图不等于可观测性),为集群使用者提供最有价值的信息,来辅助保障基础设施和上层应用的可靠性和稳定性。 上游重要进展 1. 允许动态调整 kube-scheduler 的 log level 。kube-scheduler 作为 K8s 重要的默认调度组件,在一些情况下它们的日志可以反映出不少信息,这个 PR 允许 kube-scheduler 的日志打印级别可以动态被调整。 2.调度器方面最近的一个举动是将一些 Prioritizing 的逻辑插件化(Score Plugin)。最近几个相关的PR如下,感兴趣的同学可以关注。 BalancedResourceAllocation MostRequestedPriority LeastResourcePriority 3. 为 scheduler 添加一个跟踪 Binding 和 Prioritizing 的 goroutine 数目的 metric 。 在大规模应用部署等一些特殊场景中,调度器可能成为整体性能的瓶颈。 4.这里有两个优化 kubeadm 使用体验的 PR: kubeadm 的 structured output :结构化输出 kubeadm 的一些输出信息(yaml、json 等),方便被继续处理; kubeadm 添加 --with-ca flag 来显示 ca 的过期信息 :ca 过期是常见的造成组件不可用的愿意之一,有了这个 flag 可以更方便的指导 ca 的过期信息。 5.下面是几个比较重要 / 有意思的 KEP,感兴趣的朋友可以关注一下: 为 Kube API Server 的 network proxy 添加了 beta 版毕业条件 。KAS 允许配置 Kube API Server 的网络流量到(或者不到)指定的 proxy; insecure kubelet log :通过一个开关,使得在 kubelet 的 serving cert 过期(kube apiserver 不认识 kubelet)但kube-apiserver 的 client cert 没有过期(kubelet 认识 kube apiserver)的条件下,允许 kubelet 通过跳过 tls 验证返回一些 log。这个功能在测试和debug的场景是有用的。 规范化 conformanece test 的内容的实施 (文档、API schema、代码检验、专家知识等) 扩展 NodeRestriction Controller 来限制更多 Node 可以 Pod 进行的操作(主要是来自安全上的考虑); HPA 的状态达到 implementable 两个计划中的 GA,对于想要参与到 K8s 社区的新手贡献者来说,这两个 KEP 是不错的切入点(目标、方法很明确的需求) 1. 根据 Node condition 给 Node 打上 taint 标记 ,自动化帮助调度器识别出不适合的调度节点; 1. 之前 DaemonSet 的调度逻辑是在 DaemonSet controller 中的, 这个 KEP 希望把调度逻辑移动到调度器中实现。 开源项目推荐 VMware-Tanzu VMware 已经开始全面支持 K8s,最近该公司在开源方面的一个举措是将几个自己拥有的云原生开源项目迁移到了新的 Organization:VMware-Tanzu。这个项目中目前包括下面几个项目: velero:应用迁移工具 octant:一个集群状态展示的 dashboard sonobuoy:一个 K8s 分析工具 ······ k8s-transmogrifier K8s 1.16 中废弃了大量的 API,影响到很多的已经用于生产的集群配置和 Helm chart 等。这里有一个自动转换 K8s 1.16 中的 depreciated 的 API 的 工具 ,有需要的人可以了解一下。 本周阅读推荐 1.《 阿里巴巴的研发模式是如何演进的? 》 随着云计算的不断发展,很多开发者都对这一技术将为开发方式带来的变化充满兴趣,云计算解决的是从 CAPEX 到 OPEX 的转变问题。云可以带来哪些切实的好处?在云环境下应该怎么做应用架构?基于从传统架构到云架构的亲身经历,阿里巴巴合伙人、阿里云智能基础产品事业部总经理蒋江伟(小邪)阐述了企业由新架构和新研发模式带来的价值点。本文整理自正在召开的 QCon 上海 2019 蒋江伟(小邪)的演讲内容。 2.《 How Zalando manages 140+ Kubernetes Cluster 》 本文介绍了 Zalando 的团队在(公有云上)管理数量棒庞大的 K8s 集群中得到的一些实践经验,例如每个 domain 或者 production community 总是部署双集群(prod & non-prod)、使用 Github 托管配置文件、通过 CLM(Cluster Lifecycle Manager)管理升级等等。 3.《 Liveness Probes are Dangerous 》 Liveness Probe 和 Readiness Probe 是 K8s 中判断应用是否可用的重要工具,然而不正确的使用 Liveness Probe 或者 Readiness Probe 会带来的风险(例如不小心使用了外部依赖、Liveness Probe 有时候反而会阻止 Pod 正确的进入失败状态从而无法彻底恢复健康等),这篇文章总结了使用这两者的最佳实践。 4.《 A Practical Guide to Setting Kubernetes Requests and Limits 》 Kubernetes 资源定义中的 request 和 limit 是老生常谈的问题了,本文除了更好的解释这些概念意外,还从 SLA 的角度提供了如何配置它们的建议。 5.《 Protecting Kubernetes API Against CVE-2019-11253 (Billion Laughs Attack) and Other Vulnerabilities 》 本文通过介绍 K8s 中的一个可能导致 billions laughs attacks 漏洞的例子,讲解了在生产环境中使用 K8s 的常规安全操作,包括正确配置 RBAC、定期检查 Role 和 RoleBinding、永远不要暴露 Master host 的地址等等。 6.《 基于 Knative Serverless 技术实现天气服务-下篇 》 上一期我们介绍了如何 基于 Knative Serverless 技术实现天气服务-上篇 ,本篇文章我们介绍如何通过表格存储提供的通道服务,实现 Knative 对接表格存储事件源,订阅并通过钉钉发送天气提醒通知。关于 Knative 更多精彩文章请看 《 Knative 系列文章 》。 “ 阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术公众号。” 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 从本文起,我将陆续推出一系列有关 Terraform 的文章,从概念,特点,工作机制,用法以及最佳实践等多个方面由浅入深的向大家介绍如何在阿里云上玩转 Terraform。同时也希望借此机会,与感兴趣的开发者相互探讨和学习。 作为本系列的引子,本文将从 Terraform 是什么开始介绍。 Terraform 是什么 如果两年前问 Terraform 是什么,国内熟悉它的人可能并不是很多,毕竟它是2014年的新生儿,提到资源编排,大家能优先想到的也是 AWS 的 CloudFormation,Azure 的 ARM 以及 阿里云的 ROS 。但是如果今天再问“Terraform 是什么”,相信很大一部分人对它不再陌生,并且一些公司或者感兴趣的开发者已经开始使用 Terraform 来管理他们的基础设施。不论是 Hashicorp 的官方宣传,还是 阿里云云栖社区 的布道;不论是 Devops的持续发展,还是云原生的火热传播;不论是国内最初只有阿里云支持,还是后来国内其他云厂商的陆续支持,Terraform 近两年在国内被越来越多的提及。 在此之前,不止一篇云栖文章介绍过Terraform是什么,本文将结合Terraform的官方文档,对这些文章中的介绍做一个整理和总结。 2014年, Hashicorp 公司推出了一款产品 Terraform Terraform 官方的定义 如下: Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions. Configuration files describe to Terraform the components needed to run a single application or your entire datacenter. Terraform generates an execution plan describing what it will do to reach the desired state, and then executes it to build the described infrastructure. As the configuration changes, Terraform is able to determine what changed and create incremental execution plans which can be applied. The infrastructure Terraform can manage includes low-level components such as compute instances, storage, and networking, as well as high-level components such as DNS entries, SaaS features, etc. 总结起来,三句话: Terraform 是一个可以安全、高效地建立,变更以及版本化管理基础设施的工具,并可在主流的服务提供商上提供自定义的解决方案。 Terraform 以配置文件为驱动,在文件中定义所要管理的组件(基础设施资源),以此生成一个可执行的计划(如果不可执行,会提示报错),通过执行这个计划来完成所定义组件的创建,增量式的变更和持续的管理。 Terraform不仅可以管理IaaS层的资源,如计算实例,网络实例,存储实例等,也可以管理更上层的服务,如DNS 域名和解析记录,SaaS 应用的功能等 更通俗的讲,Terraform 就是运行在客户端的一个开源的,用于资源编排的自动化运维工具。以代码的形式将所要管理的资源定义在模板中,通过解析并执行模板来自动化完成所定义资源的创建,变更和管理,进而达到自动化运维的目标。 Terraform 主要特点 Terraform 具备以下几个主要特点: 基础设施即代码(IaC, Infrastructure as Code) Terraform 基于一种特定的配置语言(HCL, Hashicorp Configuration Language)来描述基础设施资源。由此,可以像对待任何其他代码一样,实现对所描述的解决方案或者基础架构的版本控制和管理。同时,通用的解决方案和基础架构可以以模板的形式进行便捷的共享和重用。 resource "alicloud_instance" "instance" { count = 5 instance_name = "my-first-tf-vm" image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" instance_type = "ecs.sn1ne.small" security_groups = ["sg-abc12345"] vswitch_id = "vsw-abc12345" tags = { from = "terraform" } } 执行计划(Execution Plans) Terraform 在执行模板前,运行 terraform plan 命令会先通过解析模板生成一个可执行的计划,这个计划展示了当前模板所要创建或变更的资源及其属性。操作人员可以预览这个计划,在确认无误后执行 terraform apply 命令,即可完成对所定义资源的快速创建和变更,以免发生一些超预期的问题。 An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # alicloud_instance.instance[0] will be created + resource "alicloud_instance" "instance" { + availability_zone = (known after apply) + id = (known after apply) + image_id = "ubuntu_140405_64_40G_cloudinit_20161115.vhd" + instance_name = "my-first-tf-vm" + instance_type = "ecs.sn1ne.small" + security_groups = [ + "sg-abc12345", ] + tags = { + "from" = "terraform" } + vswitch_id = "vsw-abc12345" ... } # alicloud_instance.instance[1] will be created + resource "alicloud_instance" "instance" { ... } # alicloud_instance.instance[2] will be created + resource "alicloud_instance" "instance" { ... } ... Plan: 5 to add, 0 to change, 0 to destroy. 资源拓扑图(Resource Graph) Terraform 会根据模板中的定义,构建所有资源的图形,并且以并行的方式创建和修改那些没有任何依赖资源的资源,以保证执行的高效性。对于有依赖资源的资源,被依赖的资源优先执行。 自动化变更(Change Automation) 不论多复杂的资源,当模板中定义的资源内容发生变更时,Terraform 都会基于新的资源拓扑图将变更的内容plan 出来,在确认无误后,只需一个命令即可完成数个变更操作,避免了人为操作带来的错误。 Terraform 活跃的社区 Terraform 作为 DevOps 的一个利器,大大降低了基础设施的管理成本,并且随着业务架构的不断复杂,基础设施资源及其资源关系的不断复杂,Terraform 所带来的便利性和优势将越来越明显。 正因为如此,云计算火热发展的今天,Terraform 受到越来越多的云厂商和其他软件服务商的青睐。目前,Terraform 累计提供了超过100个 Providers 和 Provisioners ,支持15个 Backend Types 。阿里云作为国内第一家与 Terraform 集成的云厂商,目前已经提供了超过 138 个 resource 和 92 个 datasource ,覆盖计算,存储,网络,负载均衡,CDN,中间件,访问控制,数据库等多个领域。并且从 Terraform 0.12.2 版本开始,阿里云 OSS 作为 标准的 Backend 开始提供远端存储 State 的能力,持续提升开发者的使用体验,降低使用成本。 欢迎大家关注 Terraform,关注阿里云 Provider,如有任何使用问题,可直接在 terraform-provider-alicloud 提交您的问题,我们将尽快解决。 阅读原文 本文为云栖社区原创内容,未经允许不得转载。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Flannel是cereos开源的CNI网络插件,下图flannel官网提供的一个数据包经过封包、传输以及拆包的示意图,从这个图片中可以看出两台机器的docker0分别处于不同的段:10.1.20.1/24 和 10.1.15.1/24 ,如果从Web App Frontend1 pod(10.1.15.2)去连接另一台主机上的Backend Service2 pod(10.1.20.3),网络包从宿主机192.168.0.100发往192.168.0.200,内层容器的数据包被封装到宿主机的UDP里面,并且在外层包装了宿主机的IP和mac地址。这就是一个经典的overlay网络,因为容器的IP是一个内部IP,无法从跨宿主机通信,所以容器的网络互通,需要承载到宿主机的网络之上。 flannel支持多种网络模式,常用的是vxlan、UDP、hostgw、ipip以及gce和阿里云等,vxlan和UDP的区别是:vxlan是内核封包,而UDP是flanneld用户态程序封包,所以UDP的方式性能会稍差;hostgw模式是一种主机网关模式,容器到另外一个主机上容器的网关设置成所在主机的网卡地址,这个和calico非常相似,只不过calico是通过BGP声明,而hostgw是通过中心的etcd分发,所以hostgw是直连模式,不需要通过overlay封包和拆包,性能比较高,但hostgw模式最大的缺点是必须是在一个二层网络中,毕竟下一跳的路由需要在邻居表中,否则无法通行。 在实际的生产环境中,最常用的还是vxlan模式,我们先看工作原理,然后通过源码解析实现过程。 安装的过程非常简单,主要分为两步: 第一步安装flannel yum install flannel 或者通过kubernetes的daemonset方式启动,配置flannel用的etcd地址 第二步配置集群网络 curl -L http://etcdurl:2379/v2/keys/flannel/network/config -XPUT -d value="{\"Network\":\"172.16.0.0/16\",\"SubnetLen\":24,\"Backend\":{\"Type\":\"vxlan\",\"VNI\":1}}" 然后启动每个节点的flanned程序。 一、工作原理 1、容器的地址如何分配 Docker容器启动时通过docker0分配IP地址,flannel为每个机器分配一个IP段,配置在docker0上,容器启动后就在本段内选择一个未占用的IP,那么flannel如何修改docker0网段呢? 先看一下 flannel的启动文件 /usr/lib/systemd/system/flanneld.service [Service] Type=notify EnvironmentFile=/etc/sysconfig/flanneld ExecStart=/usr/bin/flanneld-start $FLANNEL_OPTIONS ExecStartPost=/opt/flannel/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker 文件里面指定了flannel环境变量和启动脚本和启动后执行脚本 ExecStartPost 设置的mk-docker-opts.sh,这个脚本的作用是生成/run/flannel/docker,文件内容如下: DOCKER_OPT_BIP="--bip=10.251.81.1/24" DOCKER_OPT_IPMASQ="--ip-masq=false" DOCKER_OPT_MTU="--mtu=1450" DOCKER_NETWORK_OPTIONS=" --bip=10.251.81.1/24 --ip-masq=false --mtu=1450" 而这个文件又被docker启动文件/usr/lib/systemd/system/docker.service所关联, [Service] Type=notify NotifyAccess=all EnvironmentFile=-/run/flannel/docker EnvironmentFile=-/etc/sysconfig/docker 这样便可以设置docker0的网桥了。 在开发环境中,有三台机器,分别分配了如下网段: host-139.245 10.254.44.1/24 host-139.246 10.254.60.1/24 host-139.247 10.254.50.1/24 2、容器如何通信 上面介绍了为每个容器分配IP,那么不同主机上的容器如何通信呢,我们用最常见的vxlan举例,这里有三个关键点,一个路由,一个arp,一个FDB。我们按照容器发包的过程,逐一分析上面三个元素的作用,首先容器出来的数据包会经过docker0,那么下面是直接从主机网络出去,还是通过vxlan封包转发呢?这是每个机器上面路由设定的。 #ip route show dev flannel.1 10.254.50.0/24 via 10.254.50.0 onlink 10.254.60.0/24 via 10.254.60.0 onlink 可以看到每个主机上面都有到另外两台机器的路由,这个路由是onlink路由,onlink参数表明强制此网关是“在链路上”的(虽然并没有链路层路由),否则linux上面是没法添加不同网段的路由。这样数据包就能知道,如果是容器直接的访问则交给flannel.1设备处理。 flannel.1这个虚拟网络设备将会对数据封包,但下面一个问题又来了,这个网关的mac地址是多少呢?因为这个网关是通过onlink设置的,flannel会下发这个mac地址,查看一下arp表 # ip neig show dev flannel.1 10.254.50.0 lladdr ba:10:0e:7b:74:89 PERMANENT 10.254.60.0 lladdr 92:f3:c8:b2:6e:f0 PERMANENT 可以看到这个网关对应的mac地址,这样内层的数据包就封装好了 还是最后一个问题,外出的数据包的目的IP是多少呢?换句话说,这个封装后的数据包应该发往那一台机器呢?难不成每个数据包都广播。vxlan默认实现第一次确实是通过广播的方式,但flannel再次采用一种hack方式直接下发了这个转发表FDB # bridge fdb show dev flannel.1 92:f3:c8:b2:6e:f0 dst 10.100.139.246 self permanent ba:10:0e:7b:74:89 dst 10.100.139.247 self permanent 这样对应mac地址转发目标IP便可以获取到了。 这里还有个地方需要注意, 无论是arp表还是FDB表都是permanent ,它表明写记录是手动维护的,传统的arp获取邻居的方式是通过广播获取,如果收到对端的arp相应则会标记对端为reachable,在超过reachable设定时间后,如果发现对端失效会标记为stale,之后会转入的delay以及probe进入探测的状态,如果探测失败会标记为Failed状态。之所以介绍arp的基础内容,是因为老版本的flannel并非使用本文上面的方式,而是采用一种临时的arp方案,此时下发的arp表示reachable状态,这就意味着,如果在flannel宕机超过reachable超时时间的话,那么这台机器上面的容器的网络将会中断,我们简单回顾试一下之前(0.7.x)版本的做法,容器为了为了能够获取到对端arp地址,内核会首先发送arp征询,如果尝试 /proc/sys/net/ipv4/neigh/$NIC/ucast_solicit 此时后会向用户空间发送arp征询 /proc/sys/net/ipv4/neigh/$NIC/app_solicit 之前版本的flannel正是利用这个特性,设定 # cat /proc/sys/net/ipv4/neigh/flannel.1/app_solicit 3 从而flanneld便可以获取到内核发送到用户空间的L3MISS,并且配合etcd返回这个IP地址对应的mac地址,设置为reachable。从分析可以看出,如果flanneld程序如果退出后,容器之间的通信将会中断,这里需要注意。Flannel的启动流程如下图所示: Flannel启动执行newSubnetManager,通过他创建后台数据存储,当前有支持两种后端,默认是etcd存储,如果flannel启动指定“kube-subnet-mgr”参数则使用kubernetes的接口存储数据。 具体代码如下: func newSubnetManager() (subnet.Manager, error) { if opts.kubeSubnetMgr { return kube.NewSubnetManager(opts.kubeApiUrl, opts.kubeConfigFile) } cfg := &etcdv2.EtcdConfig{ Endpoints: strings.Split(opts.etcdEndpoints, ","), Keyfile: opts.etcdKeyfile, Certfile: opts.etcdCertfile, CAFile: opts.etcdCAFile, Prefix: opts.etcdPrefix, Username: opts.etcdUsername, Password: opts.etcdPassword, } // Attempt to renew the lease for the subnet specified in the subnetFile prevSubnet := ReadCIDRFromSubnetFile(opts.subnetFile, "FLANNEL_SUBNET") return etcdv2.NewLocalManager(cfg, prevSubnet) } 通过SubnetManager,结合上面介绍部署的时候配置的etcd的数据,可以获得网络配置信息,主要指backend和网段信息,如果是vxlan,通过NewManager创建对应的网络管理器,这里用到简单工程模式,首先每种网络模式管理器都会通过init初始化注册, 如vxlan func init() { backend.Register("vxlan", New) 如果是udp func init() { backend.Register("udp", New) } 其它也是类似,将构建方法都注册到一个map里面,从而根据etcd配置的网络模式,设定启用对应的网络管理器。 3、注册网络 RegisterNetwork,首先会创建flannel.vxlanID的网卡,默认vxlanID是1.然后就是向etcd注册租约并且获取相应的网段信息,这样有个细节,老版的flannel每次启动都是去获取新的网段,新版的flannel会遍历etcd里面已经注册的etcd信息,从而获取之前分配的网段,继续使用。 最后通过WriteSubnetFile写本地子网文件, # cat /run/flannel/subnet.env FLANNEL_NETWORK=10.254.0.0/16 FLANNEL_SUBNET=10.254.44.1/24 FLANNEL_MTU=1450 FLANNEL_IPMASQ=true 通过这个文件设定docker的网络。细心的读者可能发现这里的MTU并不是以太网规定的1500,这是因为外层的vxlan封包还要占据50 Byte。 当然flannel启动后还需要持续的watch etcd里面的数据,这是当有新的flannel节点加入,或者变更的时候,其他flannel节点能够动态更新的那三张表。主要的处理方法都在handleSubnetEvents里面 func (nw *network) handleSubnetEvents(batch []subnet.Event) { . . . switch event.Type {//如果是有新的网段加入(新的主机加入) case subnet.EventAdded: . . .//更新路由表 if err := netlink.RouteReplace(&directRoute); err != nil { log.Errorf("Error adding route to %v via %v: %v", sn, attrs.PublicIP, err) continue } //添加arp表 log.V(2).Infof("adding subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC)) if err := nw.dev.AddARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil { log.Error("AddARP failed: ", err) continue } //添加FDB表 if err := nw.dev.AddFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil { log.Error("AddFDB failed: ", err) if err := nw.dev.DelARP(neighbor{IP: event.Lease.Subnet.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil { log.Error("DelARP failed: ", err) } continue }//如果是删除实践 case subnet.EventRemoved: //删除路由 if err := netlink.RouteDel(&directRoute); err != nil { log.Errorf("Error deleting route to %v via %v: %v", sn, attrs.PublicIP, err) } else { log.V(2).Infof("removing subnet: %s PublicIP: %s VtepMAC: %s", sn, attrs.PublicIP, net.HardwareAddr(vxlanAttrs.VtepMAC)) //删除arp if err := nw.dev.DelARP(neighbor{IP: sn.IP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil { log.Error("DelARP failed: ", err) } //删除FDB if err := nw.dev.DelFDB(neighbor{IP: attrs.PublicIP, MAC: net.HardwareAddr(vxlanAttrs.VtepMAC)}); err != nil { log.Error("DelFDB failed: ", err) } if err := netlink.RouteDel(&vxlanRoute); err != nil { log.Errorf("failed to delete vxlanRoute (%s -> %s): %v", vxlanRoute.Dst, vxlanRoute.Gw, err) } } default: log.Error("internal error: unknown event type: ", int(event.Type)) } } } 这样flannel里面任何主机的添加和删除都可以被其它节点所感知到,从而更新本地内核转发表。 作者:陈晓宇 来源:宜信技术学院 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 本期课程: 制造行业客户痛点 常见场景与解决方案 典型客户案例等 关注“ ZStack云计算 ”历史图文参与直播学习 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 预置 harbor url: https://harbor.test.com:6443 image url: harbor.test.com:6443/test/test-secret:latest namespace: harbor-test 将harbor的ca.crt复制到node的/etc/docker/certs.d/harbor.test.com:6443目录下 cp ca.crt /etc/docker/certs.d/harbor.test.com:6443/ 创建Pod拉取镜像需使用的secret kubectl create secret docker-registry my-harbor-secret --docker-server=harbor.test.com:6443 --docker-username= --docker-password= --docker-email= --namespace=harbor-test secret默认仅在default namespace下有效,若不加--namespace,非default namespace无法使用该secret,会显示拉取镜像失败 Failed to pull image "harbor.test.com:6443/test/test-secret:latest": rpc error: code = Unknown desc = Error response from daemon: pull access denied for harbor.test.com:6443/test/test-secret:latest, repository does not exist or may require 'docker login': denied: requested access to the resource is denied 创建测试用Pod private-reg-pod.yaml apiVersion: v1 kind: Pod metadata: name: private-reg namespace: harbor-test spec: containers: - name: private-reg-container image: harbor.test.com:6443/test/test-secret:latest imagePullSecrets: - name: my-harbor-secret kubectl create -f private-reg-pod.yaml 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 作者 | 孙健波(天元)阿里云技术专家 导读 :OAM Spec 经历了近 3 个月的迭代, v1alpha2 版本终于发布啦!新版本在坚持 OAM Spec 平台无关的基础上,整体变得更 Kubernetes 友好化,很大程度上平衡了标准与可扩展性,更好的支持 CRD。如果你已经编写了现成的 CRD Operator,可以平滑的接入到 OAM 体系中,并且享受到 OAM 模型的红利。 目前 OAM 已经成为了包括阿里、微软、Upbond、谐云等多家公司构建云产品的核心架构。他们通过 OAM 构建了“以应用为中心”、用户友好化的 Kubernetes PaaS;充分发挥 OAM 的标准化与可扩展性,实现 OAM 核心 Controller 的同时,快速接入了已有的 Operator 能力;通过 OAM 横向打通多个模块,破除了原有 Operator 彼此孤立、无法复用的窘境。 了解 OAM 的背景及由来,可以参考 《深度解读!阿里统一应用管理架构升级的教训与实践》 ; OAM 能为终端用户带来哪些价值?可以参考 《OAM 深入解读:OAM 为云原生应用带来哪些价值?》 下面言归正传,让我们来看一下 v1alpha2 到底做了哪些改动? 主要改动说明 为了方便大家阅读,这里只罗列了最主要的改动点,一些细节还是以上游 OAM Spec Github 仓库为准。 术语说明 CRD(Custom Resource Definition):在 OAM 中说的 CRD 是一种泛指的自定义资源描述定义。在 K8s 的 OAM 实现中可以完全对应 K8s 的 CRD,在非 K8s 的实现中,OAM 的 CRD 需要包含 APIVersion/Kind 并且能够描述字段进行校验; CR (Custom Resource),OAM 中的 CR 是 CRD 的一个实例,是符合 CRD 中字段格式定义的一个资源描述。在 K8s 的 OAM 实现中可以完全对应 K8s 的 CR,在 非 K8s 的实现中,可以需要对齐 APIVersion/Kind 和字段格式定义。 主要改动 1 使用 Reference 模型定义 Workload、Trait 和 Scope v1alpha1 原先的方式是这样的: // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: WorkloadType metadata: name: OpenFaaS annotations: version: v1.0.0 description: "OpenFaaS a Workload which can serve workload running as functions" spec: group: openfaas.com version: v1alpha2 names: kind: Function singular: function plural: functions workloadSettings: | { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": [ "name", "image" ], "properties": { "name": { "type": "string", "description": "the name to the function" }, "image": { "type": "string", "description": "the docker image of the function" } } } 在原先的模式中,group/version/kind 分别是字段,spec 的校验通过 jsonschema 表示,整体的格式实际上类似 CRD,但不完全一致。 新版 v1alpha2 中彻底改为了引用模型,通过 WorkloadDefinition TraitDefinition ScopeDefinition 的形式,描述了一个引用关系。可以直接引用一个 CRD,name 就是 CRD 的名称。对于非 K8s 的 OAM 实现来说,这里的名字则是一个索引,可以找到类似 CRD 的校验文件,校验文件中包含 apiVersion 和 kind,以及相应的 schema 校验。 Workload apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: containerisedworkload.core.oam.dev spec: definitionRef: # Name of CRD. name: containerisedworkload.core.oam.dev Trait apiVersion: core.oam.dev/v1alpha2 kind: TraitDefinition metadata: name: manualscalertrait.core.oam.dev spec: appliesToWorkloads: - containerizedworkload.core.oam.dev definitionRef: name: manualscalertrait.core.oam.dev Scope apiVersion: core.oam.dev/v1alpha2 kind: ScopeDefinition metadata: name: networkscope.core.oam.dev spec: allowComponentOverlap: true definitionRef: name: networkscope.core.oam.dev 注意: 这里对于 K8s 的 OAM 实现来说,name 就是 K8s 里面 CRD 的 name,由 . 组成。社区的最佳实践是一个 CRD 只有一个 version 在集群中运行,一般新 version 都会向前兼容,升级时都一次性替换到最新 version。如果确实有 2 个 version 同时存在的场景,用户也可以通过 kubectl get crd 的方式进一步选择; Definition 这一层不面向 end user,主要给平台实现使用,对于非 K8s 实现来说,如果存在多个 version 的场景,OAM 的实现平台可以给终端用户展示不同 version 的选择。 主要改动 2 直接嵌入 K8s CR 作为 Component 和 Trait 实例 原先的方式在 Workload 和 Trait 层面我们都只把 CR 的 spec 部分拿出来,分别放在 workloadSettings 和 properties 字段里。 这样的方式虽然已经可以“推导”出 K8s CR,但是不利于 K8s 生态内的 CRD 接入,需要换种格式重新定义一遍 spec。 // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: workloadType: cache.crossplane.io/v1alpha1.RedisCluster workloadSettings: engineVersion: 1.0 region: cn // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: custom-single-app annotations: version: v1.0.0 description: "Customized version of single-app" spec: variables: components: - componentName: frontend instanceName: web-front-end parameterValues: traits: - name: manual-scaler properties: replicaCount: 5 现在的方式则直接嵌入 CR,可以看到,在 workload 和 trait 字段下面是完整的 CR 描述。 apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: example-server spec: prameters: - name: xxx fieldPaths: - "spec.osType" workload: apiVersion: core.oam.dev/v1alpha2 kind: Server spec: osType: linux containers: - name: my-cool-server image: name: example/very-cool-server:1.0.0 ports: - name: http value: 8080 env: - name: CACHE_SECRET apiVersion: core.oam.dev/v1alpha2 kind: ApplicationConfiguration metadata: name: cool-example spec: components: - componentName: example-server traits: - trait: apiVersion: core.oam.dev/v1alpha2 kind: ManualScalerTrait spec: replicaCount: 3 这样的好处很明显: 可以很容易的对接现有 K8s 体系里的 CRD,甚至包括 K8s 原生的 Deployment (作为自定义 workload 接入)等资源; K8s CR 层面的字段定义是成熟的,解析和校验也完全交由 CRD 体系。 这里大家注意到 traits 下面是 []trait{CR} 而不是 []CR 的结构,多了一层看似无用的 trait 字段,主要由如下 2 个原因: 为后续在 trait 这个维度做扩展留下空间,比如可能的编排( ordering ) 等。 非 K8s 体系在这一层可以不严格按照 CR 的写法来,完全自定义,不绑定 K8s 描述格式。 主要改动 3 参数传递使用 jsonPath 替换原先的 fromParam 研发能够留出字段给运维覆盖 ,一直是 OAM 很重要的功能。 体现在 OAM Spec 的流程上就是:研发在 Component 里面定义 parameter,运维在 AppConfig 里面通过 parameterValue 去覆盖对应的参数。 最初的参数传递,是在每个字段后面有个 fromParam 字段,对于支持了自定义 schema 后,这样的方式显然是无法覆盖所有场景的: // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: workloadType: cache.crossplane.io/v1alpha1.RedisCluster parameters: - name: engineVersion type: string workloadSettings: - name: engineVersion type: string fromParam: engineVersion 后来我们曾经提议过这样的方案: // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: workloadType: cache.crossplane.io/v1alpha1.RedisCluster parameters: - name: engineVersion type: string workloadSettings: engineVersion: "[fromParam(engineVersion)]" 这个方案最大的问题是 静态的 IaD (Infrastructure as Data) 里面加入了动态的函数,给理解和使用带来了复杂性。 经过多方面的讨论,在新方案里我们通过 JsonPath 的形式描述要注入的参数位置,在用户理解上保证了 AppConfig 是静态的。 apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: example-server spec: workload: apiVersion: core.oam.dev/v1alpha2 kind: Server spec: containers: - name: my-cool-server image: name: example/very-cool-server:1.0.0 ports: - name: http value: 8080 env: - name: CACHE_SECRET value: cache parameters: - name: instanceName required: true fieldPaths: - ".metadata.name" - name: cacheSecret required: true fieldPaths: - ".workload.spec.containers[0].env[0].value" fieldPaths 是个数组,每个元素定义了参数和对应 Workload 里的字段。 apiVersion: core.oam.dev/v1alpha2 kind: ApplicationConfiguration metadata: name: my-app-deployment spec: components: - componentName: example-server parameterValues: - name: cacheSecret value: new-cache 在 AppConfig 中还是走 parameterValues 去覆盖 Component 中的 parameter。 主要改动 4 ComponentSchematic 名称改为 Component 原先组件这个概念叫 ComponentSchematic,这样命名的主要原因是里面混了一些语法描述和选择,比如针对 Core Workload( container )和针对扩展 Workload ( workloadSettings ),写法上不一样的, container 里是定义具体的参数, workloadSettings 更像是 schema(参数怎么填的说明)。v1alpha1 版本的 WorkloadSetting 还融入了 type/description之类的,更显得模棱两可。 // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: rediscluster spec: containers: ... workloadSettings: - name: engineVersion type: string description: engine version fromParam: engineVersion ... 在 v1alpha2 版本中,组件这个概念改为 Component,明确为 Workload 的实例,所有语法定义都是在WorkloadDefinition 中引用的实际 CRD 定义的。 在 K8s 实现中,WorkloadDefinition 就是引用 CRD ,Component.spec.workload 里就是写 CRD 对应的实例 CR。 apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: example-server spec: workload: apiVersion: core.oam.dev/v1alpha2 kind: Server spec: ... 主要改动 5 Scope 由 CR 单独创建,不再由 AppConfig 创建 v1alpha1 中的 Scope 是由 AppConfig 创建的,从例子中可以看出,本质上它也是个 CR,可以“推导”创建出 CR来。但是由于 Scope 的定位是可以容纳不同 AppConfig 中的 Component,且 Scope 本身并非一个 App,所以使用 AppConfig 创建 Scope 一直是不太合适的。 // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: my-vpc-network spec: variables: - name: networkName value: "my-vpc" scopes: - name: network type: core.oam.dev/v1alpha1.Network properties: network-id: "[fromVariable(networkName)]" subnet-ids: "my-subnet1, my-subnet2" v1alpha2 新版本全面使用 CR 来对应实例,为了让 Scope 的概念更清晰,更方便对应不同类型的 Scope,将 Scope 拿出来直接由 ScopeDefinition 定义的 CRD 对应的 CR 创建。例子如下所示: apiVersion: core.oam.dev/v1alpha2 kind: ScopeDefinition metadata: name: networkscope.core.oam.dev spec: allowComponentOverlap: true definitionRef: name: networkscope.core.oam.dev apiVersion: core.oam.dev/v1alpha2 kind: NetworkScope metadata: name: example-vpc-network labels: region: us-west environment: production spec: networkId: cool-vpc-network subnetIds: - cool-subnetwork - cooler-subnetwork - coolest-subnetwork internetGatewayType: nat 在 AppConfig 中使用 scope 引用如下所示: apiVersion: core.oam.dev/v1alpha2 kind: ApplicationConfiguration metadata: name: custom-single-app annotations: version: v1.0.0 description: "Customized version of single-app" spec: components: - componentName: frontend scopes: - scopeRef: apiVersion: core.oam.dev/v1alpha2 kind: NetworkScope name: my-vpc-network - componentName: backend scopes: - scopeRef: apiVersion: core.oam.dev/v1alpha2 kind: NetworkScope name: my-vpc-network 主要改动 6 移除 Variable 列表和 [fromVariable()] 动态函数 v1alpha1 版本中有 Variable 是为了 AppConfig 里开源引用一些公共变量减少冗余,所以加入了 Variable 列表。但实践来看,减少的冗余并没有明显减少 OAM spec 的复杂性,相反,增加动态的函数显著增加了复杂性。 另一方面,fromVariable 这样的能力完全可以通过 helm template/ kustomiz 等工具来做,由这些工具渲染出完整的 OAM spec,再进行使用。 所以这里 variables 列表和相关的 fromVariable 均去掉,并不影响任何功能。 // 老版本,仅对比使用 apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: my-app-deployment spec: variables: - name: VAR_NAME value: SUPPLIED_VALUE components: - componentName: my-web-app-component instanceName: my-app-frontent parameterValues: - name: ANOTHER_PARAMETER value: "[fromVariable(VAR_NAME)]" traits: - name: ingress properties: DATA: "[fromVariable(VAR_NAME)]" 主要改动 7 用 ContainerizedWorkload 替代原来的六种核心 Workload 因为现在已经统一用 WorkloadDefinition 定义 Workload,Component 变成了实例,所以原先的六种核心Workload 实际上都变成了同一个 WorkloadDefinition,字段描述完全一样,唯一的不同是对 trait 约束和诉求不一样。因此原先的六种核心 Workload 的 spec,统一修改为一种名为 ContainerizedWorkload 的 Workload 类型。 同时,这里计划要通过增加 policy 这样的概念,来让研发表达对运维策略的诉求,即 Component 中可以表达希望增加哪些 trait。 apiVersion: core.oam.dev/v1alpha2 kind: WorkloadDefinition metadata: name: containerizedworkloads.core.oam.dev spec: definitionRef: name: containerizedworkloads.core.oam.dev 一个使用 ContainerizedWorkload 示例: apiVersion: core.oam.dev/v1alpha2 kind: Component metadata: name: frontend annotations: version: v1.0.0 description: "A simple webserver" spec: workload: apiVersion: core.oam.dev/v1alpha2 kind: ContainerizedWorkload metadata: name: sample-workload spec: osType: linux containers: - name: web image: example/charybdis-single:latest@@sha256:verytrustworthyhash resources: cpu: required: 1.0 memory: required: 100MB env: - name: MESSAGE value: default parameters: - name: message description: The message to display in the web app. required: true type: string fieldPaths: - ".spec.containers[0].env[0].value" 下一步计划 应用级别的组件间参数传递和依赖关系( workflow ); Policy 方案 ,便于研发在 Component 对 trait 提出诉求; Component 增加版本的概念,同时给出 OAM 解决应用版本发布相关方式 。 常见 FAQ 我们原有平台改造为 OAM 模型实现需要做什么? 对于原先是 K8s 上的应用管理平台,接入改造为 OAM 实现可以分为两个阶段: 实现 OAM ApplicationConfiguration Controller(简称 AppConfig Controller),这个 Controller 同时包含 OAM 的 Component、WorkloadDefinition、TraitDefinition、ScopeDefinition 等 CRD。AppConfig Controller 根据 OAM AppConfig 中的描述,拉起原有平台的 CRD Operator; 逐渐将原先的 CRD Operator 根据关注点分离的思想,分为 Workload 和 Trait。同时接入和复用 OAM 社区中更多的 Workload、Trait,丰富更多场景下的功能。 现有的 CRD Operator 为接入 OAM 需要做什么改变? 现有的 CRD Operator** 功能上可以平滑接入 OAM 体系,比如作为一个独立扩展 Workload 接入。但是为了更好的让终端用户体会到 OAM 关注点分离的好处,我们强烈建议 CRD Operator 根据研发和运维不同的关注点分离为不同的 CRD,研发关注的 CRD 作为 Workload 接入 OAM,运维关注的 CRD 作为 Trait 接入 OAM。 目前,OAM 规范和模型实际已解决许多现有问题,但它的路程才刚刚开始。OAM 是一个中立的开源项目,我们欢迎更多的人参与其中,共同定义云原生应用交付的未来。 参与方式: 钉钉扫码进入 OAM 项目中文讨论群 通过 Gitter 直接参与讨论 OAM 开源实现地址 点击 star 一下 作者简介 孙健波(花名:天元)阿里云技术专家,是 OAM 规范的主要制定者之一,致力于推动云原生应用标准化。同时也在阿里巴巴参与大规模云原生应用交付与应用管理相关工作。团队诚邀应用交付、Serverless、PaaS 领域的专家加入,欢迎联系 jianbo.sjb AT alibaba-inc.com 招人啦! 云原生应用平台诚邀 Kubernetes / Serverless / PaaS / 应用交付领域专家( P6-P8 )加盟: 工作年限:建议 P6-7 三年起,P8 五年起,具体看实际能力; 工作地点:国内(北京 / 杭州 / 深圳);海外(旧金山湾区 / 西雅图); 岗位包含:架构师、技术专家、全栈工程师等。 简历立刻回复,2~3 周出结果,简历投递:jianbo.sjb AT alibaba-inc.com。 “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。” 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Pod安全策略对于强化K8S集群安全至关重要。本文将延续之前的文章继续深入介绍Pod安全策略。 首先,简单介绍了如何将Pod与Pod安全策略相关联,并使用RBAC来展示具体步骤。然后介绍如何在Rancher中启用默认的PSP和创建自定义PSP。最后将使用一种工具来简化生产中Pod安全策略的使用,极大提升生产力,赶紧戳文咯~ 本文来自 RancherLabs 在 之前的文章 中,我们演示了如何使用受限的PSP策略作为默认值在Rancher中启用PSP。我们还展示了如何防止特权Pod被接纳到集群中。 我们有意省略了有关基于角色的访问控制(RBAC)以及如何将Pod与特定PSP连接的具体细节。那么,这篇文章让我们继续深入研究PSP。 将Pod与Pod 安全策略匹配 你可能已经注意到,PSP模式没有与任何Kubernetes命名空间、Service Account或Pod相关联。实际上,PSP是集群范围的资源。那么,我们如何指定哪些Pod应该由哪些PSP来管理呢?下图显示了所有参与组件、资源以及准入流程的工作方式。 也许一开始听起来很复杂。现在,我们来详细介绍一下。 部署Pod时,准入控制将根据请求deployment的对象来应用策略。 Pod本身没有任何关联的策略——执行该Deployment的是service account。在上图中,Jorge使用webapp-sa service account部署了pod。 RoleBinding将service account与Roles(或ClusterRoles)相关联,Role是指定可以使用PSP的资源。在该图中,webapp-sa与webapp-role关联,后者为特定的PSP资源提供使用许可。部署Pod时,将根据webapp-sa PSP对Pod进行检查。实际上,一个service account可以使用多个PSP,并且其中一个可以验证Pod就足够了。你可以在官方文档中查看详细信息: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#policy-order 然后,准入控制将决定Pod是否符合其中任何一个PSP。如果Pod符合要求,准入控制将调度Pod;如果Pod不符合规定,则会阻止部署。 以上内容可以总结为以下几点: Pod身份由其service account确定 如果规范中未声明任何service account,则将使用默认账户 你需要允许使用声明Role或ClusterRole 最后,需要有一个RoleBinding,它将Role(从而允许访问使用PSP)与Pod规范中声明的Servcie Account相关联。 让我们用一些例子来说明。 RBAC的真实示例 假设你已经有一个启用了PSP的集群,这是采用PSP创建限制性PSP的常用方法,该PSP可以被任意Pod使用。然后你将添加更为特定的PSP,该PSP有绑定到特定service account的其他特权。拥有默认、安全且严格的策略有助于集群的管理,因为大多数Pod不需要特殊的特权或功能,并且在默认情况下即可运行。然后,如果你的某些工作负载需要其他特权,我们可以创建一个自定义PSP并将该工作负载的特定service account绑定到限制较少的PSP。 但是,如何将Pod绑定到特定的PSP而不是默认的受限PSP?以及如何使用不自动添加RoleBindings的普通Kubernetes集群来做到这一点? 让我们看一个完整的示例,在该示例中,我们定义一些安全的默认值(集群中任何service account都可以使用的受限PSP),然后为需要该服务的特定deployment向单个service account提供其他特权。 首先,我们手动创建一个新的命名空间。它不会由Rancher管理,所以不会自动创建RoleBindings。然后我们在该命名空间中尝试部署一个受限的Pod: $ kubectl create ns psp-test $ cat deploy-not-privileged.yaml apiVersion: apps/v1 kind: Deployment metadata: labels: app: not-privileged-deploy name: not-privileged-deploy spec: replicas: 1 selector: matchLabels: app: not-privileged-deploy template: metadata: labels: app: not-privileged-deploy spec: containers: - image: alpine name: alpine stdin: true tty: true securityContext: runAsUser: 1000 runAsGroup: 1000 $ kubectl -n psp-test apply -f deploy-not-privileged.yaml $ kubectl -n psp-test describe rs ... Warning FailedCreate 4s (x12 over 15s) replicaset-controller Error creating: pods "not-privileged-deploy-684696d5b5-" is forbidden: unable to validate against any pod security policy: [] 由于命名空间psp-test中没有RoleBinding,且该命名空间绑定到允许使用任何PSP的角色,因此无法创建pod。我们将通过创建集群范围的ClusterRole和ClusterRoleBinding来解决此问题,以允许任何Service Account默认使用受限的PSP。之前的文章中启用PSP时,Rancher创建了受限PSP。 resourceNames: - restricted-psp --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: restricted-role-bind roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: use-restricted-psp subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:serviceaccounts $ kubectl apply -f clusterrole-use-restricted.yaml 我们应用这些更改之后,非特权deployment应正常工作。 但是,如果我们需要部署特权Pod,则不会被现有策略允许: $ cat deploy-privileged.yaml apiVersion: v1 kind: ServiceAccount metadata: name: privileged-sa namespace: psp-test --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: privileged-deploy name: privileged-deploy namespace: psp-test spec: replicas: 1 selector: matchLabels: app: privileged-deploy template: metadata: labels: app: privileged-deploy spec: containers: - image: alpine name: alpine stdin: true tty: true securityContext: privileged: true hostPID: true hostNetwork: true serviceAccountName: privileged-sa $ kubectl -n psp-test apply -f deploy-privileged.yaml $ kubectl -n psp-test describe rs privileged-deploy-7569b9969d Name: privileged-deploy-7569b9969d Namespace: default Selector: app=privileged-deploy,pod-template-hash=7569b9969d Labels: app=privileged-deploy ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedCreate 4s (x14 over 45s) replicaset-controller Error creating: pods "privileged-deploy-7569b9969d-" is forbidden: unable to validate against any pod security policy: [spec.securityContext.hostNetwork: Invalid value: true: Host network is not allowed to be used spec.securityContext.hostPID: Invalid value: true: Host PID is not allowed to be used spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed] 在这种情况下,由于我们创建了ClusterRoleBinding,所以Pod可以使用PSP,但是restricted-psp不会验证Pod,因为它需要privileged 、hostNetwork等参数。 我们已经在前文中看到了restricted-psp策略。让我们检查一下default-psp的细节,这是由Rancher在启用PSP允许这些特权时创建的: $ kubectl get psp default-psp -o yaml apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: annotations: seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' creationTimestamp: "2020-03-10T08:45:08Z" name: default-psp resourceVersion: "144774" selfLink: /apis/policy/v1beta1/podsecuritypolicies/default-psp uid: 1f83b803-bbee-483c-8f66-bfa65feaef56 spec: allowPrivilegeEscalation: true allowedCapabilities: - '*' fsGroup: rule: RunAsAny hostIPC: true hostNetwork: true hostPID: true hostPorts: - max: 65535 min: 0 privileged: true runAsUser: rule: RunAsAny seLinux: rule: RunAsAny supplementalGroups: rule: RunAsAny volumes: - '*' 你可以看到这是一个非常宽松的策略,特别是我们允许privileged、hostNetwork、hostPID、hostIPC、hostPorts以及以root身份运行以及其他功能。 我们只需要明确允许该Pod使用PSP。我们通过创建类似于现有restricted-clusterrole的ClusterRole,但允许使用default-psp资源,然后为我们的psp-test 命名空间创建RoleBinding来将privileged-sa ServiceAccount绑定到该ClusterRole: $ cat clusterrole-use-privileged.yaml --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: use-privileged-psp rules: - apiGroups: ['policy'] resources: ['podsecuritypolicies'] verbs: ['use'] resourceNames: - default-psp --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: privileged-role-bind namespace: psp-test roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: use-privileged-psp subjects: - kind: ServiceAccount name: privileged-sa $ kubectl -n psp-test apply -f clusterrole-use-privileged.yaml 一会儿之后,特权Pod将会被创建。然后你会注意到restricted-psp和default-psp现在已经可以直接使用。接下来,我们来详细介绍一下它们。 在Rancher上默认的PSP 在Rancher中通过编辑集群设置来启用PSP准入控制,并选择其中一个已经定义好的PSP作为默认选项: Rancher将在集群中创建一对PSP资源: restricted-psp:如果你选择“受限(restricted)”作为默认的PSP default-psp:默认的PSP,允许创建特权Pod。 除了restricted-psp和default-psp,Rancher还会创建名为 restricted-clusterrole的ClusterRole: annotations: serviceaccount.cluster.cattle.io/pod-security: restricted creationTimestamp: "2020-03-10T08:44:39Z" labels: cattle.io/creator: norman name: restricted-clusterrole rules: - apiGroups: - extensions resourceNames: - restricted-psp resources: - podsecuritypolicies verbs: - use 此ClusterRole允许使用restricted-psp策略。那么Binding在何处才可以允许授权使用pod ServiceAccount 呢? 对于属于Rancher中项目的命名空间,它还在其中为你设置RoleBinding配置: $ kubectl -n default get rolebinding default-default-default-restricted-clusterrole-binding -o yaml apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: annotations: podsecuritypolicy.rbac.user.cattle.io/psptpb-role-binding: "true" serviceaccount.cluster.cattle.io/pod-security: restricted labels: cattle.io/creator: norman name: default-default-default-restricted-clusterrole-binding namespace: default ... roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: restricted-clusterrole subjects: - kind: ServiceAccount name: default namespace: default 资源名称(default-default-default-restricted-clusterrole-binding)可能会令人感到困惑,实际上它的构成为: default-serviceaccountname-namespace-restricted-clusterrole-binding 并且如果你创建一个类似myserviceaccount的新的service account,那么将会自动创建一个新的角色绑定: $ kubectl create sa myserviceaccount serviceaccount/myserviceaccount created $ kubectl get rolebinding NAME AGE --- default-default-default-restricted-clusterrole-binding 13m default-myserviceaccount-default-restricted-clusterrole-binding 4s 借助这种神奇的功能,你无需为不需要任何提升特权的安全pod配置RBAC。 在Rancher中创建你的PSP PSP是一个标准的Kubernetes资源,全程是Pod安全策略,所以你可以通过Kubernetes API或kubectl CLI来使用它。 你可以通过在YAML文件中定义它们来创建你的自定义PSP,然后使用kubectl在集群中创建资源。查看官方文档即可了解所有可用控件( https://kubernetes.io/docs/concepts/policy/pod-security-policy/ )。在YAML中定义了控件集之后,你可以运行: $ kubectl create psp my-custom-psp 以创建PSP资源。 使用Rancher,你可以从UI中直接查看或添加新的策略: PSP十分强大,但也十分复杂 配置Pod安全策略是一个十分乏味的过程。你在Kubernetes集群中启用PSP之后,你想要部署的任意Pod都必须经由其中一个PSP允许。实施强大的安全策略可能十分耗时,而且为每个应用程序的每个deployment生成一个策略也是一种负担。如果你的策略十分宽松,那么不强制执行最小特权访问方法;然而,如果它存在很多限制,你可能会破坏你的应用程序,因为Pod无法在Kubernetes中成功运行。 能够以最少的访问要求集自动生成Pod安全策略,将帮助你更轻松地安装PSP。并且你不能只在生产环境中部署它们,而不验证你的应用程序是否可以正常工作,而且进行手动测试既繁琐又效率低下。如果你可以针对Kubernetes工作负载的运行时行为验证PSP呢? 如何简化生产环境中PSP的使用? Kubernetes Pod安全策略 Advisor(又名kube-psp-advisor)是一个Sysdig的开源工具。kube-psp-advisor会从Kubernetes资源(如deployment、daemonset、replicaset等)中扫描现有的安全上下文,将其作为我们想要执行的reference模型,然后在整个集群中为所有资源自动生成Pod安全策略。kube-psp-advisor通过查看不同的属性以创建推荐的Pod安全策略: allowPrivilegeEscalation allowedCapabilities allowedHostPaths hostIPC hostNetwork hostPID Privileged readOnlyRootFilesystem runAsUser Volume 查看kube-psp-advisor教程,以获取有关其工作原理的更多详细信息: https://sysdig.com/blog/enable-kubernetes-pod-security-policy/ 使用Sysdig Secure自动生成PSP Sysdig Secure Kubernetes Policy Advisor可以帮助用户创建和验证Pod安全策略。 第一步是设置新的PSP模拟环境。你可以针对不同范围内的不同策略(例如Kubernetes命名空间)进行多种模拟。 Sysdig在你的Deployment定义中分析Pod规范的要求,并为你的应用程序创建权限最小的PSP。这可以控制是否允许特权Pod,用户将其作为容器、volume等运行。Ni 可以微调PSP并针对你将要运行的模拟环境定义命名空间: 左侧的策略会破坏应用程序,因为nginx Deployment是特权Pod,并且具有主机网络访问权限。你必须决定是扩大PSP以允许这种行为,还是选择减少Deployment的特权以适应该策略。无论如何,你在应用PSP之前都会检测到此情况,这会阻止Pod运行并在应用程序部署上造成破坏。 结 论 如这些示例所示,通过授予或拒绝对特定资源的访问,PSP使你可以对在Kubernetes中运行的Pod和容器进行精细控制。这些策略相对来说容易创建和部署,并且应该是任何Kubernetes安全策略的有用组件。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Serverless 架构是云发展的产物,是一种去服务器化更加明显的架构。然而,细心的朋友可能会发现,有一个开发者工具也叫 Serverless,那么 Serverless 到底是一个架构,还是一个开发者工具呢?这个开发者工具和 Serverless 架构又有什么关系呢? 初探 Serverless 开发者工具 Serverless 架构开始发展没多久,就有一群人注册了 serverless.com 的域名,成立了一家叫 Serverless 的公司,同时还开发了一款同名工具。 Serverless 架构和 Serverless 开发者工具是两个不同的东西,如果类比一下的话,就相当于中国电信,一方面指的是中国电信行业,另一方面也指的是中国电信运营商。 从 Serverless 的公司名称,我们也可以推断出其推出的产品与 Serverless 架构紧密相关。在各个云厂商都有自己函数计算业务的时候,Serverless 团队做了一个类似多云管理平台的工具,可以认为是多 Serverless 管理的工具。利用这个工具,可以快速直接使用 AWS 的 Lambda、Azure 的 Funtions 以及腾讯云 SCF 等众多云厂商的函数计算相关服务,大体支持的功能如下: 通过这个上表,大家也可以感觉到这其实是个开发者工具,帮助用户快速使用多个云厂商的函数服务,打包、部署、回滚…当然,各个厂商也都推出类似的工具,例如 AWS 的 SAM、腾讯云的 SCFCLI 等。 除了一个以函数计算为核心的多云开发者工具之外,Serverless 公司还推出了组件化工具:Components。换句话说,Serverless 开发者工具不仅仅关注 Serverless 中的 FaaS,也要关注 BaaS,将 API 网关、对象存储、CDN、数据库等众多的后端服务和函数计算有机集合,让用户可以一站式开发,一站式部署,一站式更新,一站式维。 Serverless Framework 开发者工具可以被一分为二:Plugin 和 Components。 如果说最初的 Serverless Cli 更多是一种以插件(Plugin)形式提供各个云厂商的函数计算功能,那么这个叫 Components 的功能更多就是以各个云厂商整体服务为基础,来帮助用户快速将项目部署到 Serverless 架构上。 所谓的 Components 可以认为是很多 Component 的组合,例如如果部署一个网站,可能会需要有以下部分:静态资源部分、函数计算部分、API 网关部分、CDN 部分、域名解析部分等,而 Components 就可以帮我们一站式部署这些资源,将静态资源部署到对象存储中,将函数计算部分部署到函数中,将 API 网关、CDN 等业务部署到对应的产品或者服务中,如果有域名解析需求,会自动解析域名,同时将整个项目的所有资源进行关联。 除了一键部署、自动关联之外,Components 还提供了若干的传统 Web 框架部署到 Serverless 架构的解决方案,用户可将自己已有的或者使用这些框架新开发的项目,直接一键部署到云端,对开发者来说这是一个巨大的便利。 用户如何使用 Plugin 和 Components 呢?其实这两个功能都是 Serverless Cli 作为承载,也就是说,只要我们安装了 Serverless Framework 这个开发者工具,就可以同时使用这两个功能。 安装 Serverless Framework 开发者工具的过程也很简单: 安装 Nodejs,官方说的 nodejs 只需要 6 以上就好,但是在实际使用过程中,发现 6 不行,至少 8 以上才可以。 安装 Serverless 开发者工具: npm install -g serverless ,安装完成之后可以通过 serverless -v 查看版本号,来确定是否成功的安装该工具。 至于如何使用 Serverless Framework 开发者工具,可以参考接下来的 Plugin 和 Components 部分。 什么是 Serverless Plugin 首先,什么是 Plugin,Serverless Framework Plugin 实际上是一个函数的管理工具,使用这个工具,可以很轻松的部署函数、删除函数、触发函数、查看函数信息、查看函数日志、回滚函数、查看函数数据等。 Plugin 的使用比较简单,可以直接使用 Serverlss Framework 进行创建,例如: serverless create -t tencent-python -p mytest 然后就会生成下图: 这其中,-t 指的是模板,-p 指的是路径,在 Serverless Plugin 操作下,可以在任何指令中使用 -h 查看帮助信息,例如查看 Serverless Plugin 的全部指令,可以直接: 如果想查看 Create 的帮助: 创建完 Serverless Plugin 的项目之后,我们可以看一下它的 Yaml 长什么样子: 通过 Yaml,我们可以看到其从上到下包括了几个主要的 Key:Service、Provider、Plugins 以及 Functions。 Service 可以认为是一个服务或分组,即在一个 Service 下面的函数是可以被统一管理的,例如部署、删除、查看统计信息等。 Provider 可以认为是供应商以及全局变量的定义场景,这里使用的是腾讯云的云函数,供应商是腾讯云,所以就要写 tencent,同时在这里还可以定义全局变量,这样在部署的时候,会将这些全局变量分别配置到不同的函数中。 Plugin 就是插件,Serverless 团队提供了超级多的 Plugin,例如上文提到的 serverless-tencent-scf。 最后就是 Functions,是定义函数的地方。 创建项目,完成代码编写和 Yaml 的配置之后,接下来就是安装 Plugin: npm install 使用相关功能,例如部署服务: serverless deploy 在使用这个工具部署的时候,我们并没事先指定账号信息,所以它会自动唤起扫码登录,登陆之后会继续进行操作: 操作完成会看到 Service 信息,这里要注意,如果是使用 CICD,就没办法扫码了,必须手动配置账户信息,格式是: [default] tencent_appid = appid tencent_secret_id = secretid tencent_secret_key = secretkey 配置完成之后,在 Yaml 中指定这个文件路径: 完成部署之后,触发函数: 服务信息: 除此之外,还有很多其它操作,大家有兴趣可以都试一下: 创建服务 打包服务 部署服务 部署函数 云端调用 查看日志 回滚服务 删除服务 获取部署列表 获取服务详情 获取统计数据 … 需要注意的是,Plugin 是函数开发者工具,只针对对函数资源的管理(触发器除外),不包括 API 网关、COS、数据库、CDN 等。另外,在腾讯云函数中只有命名空间和函数的概念,但是在 Serverless Framework Plugin 中却有 Service、Stage 以及函数的三层概念,同时云函数在 Plugin 不支持命名空间,所以我们可以理解为,云函数只有函数的概念,而工具却有服务、阶段和函数的三层概念,这就会产生问题:Service 和 Stage 是什么?在函数中怎么体现? 以我们刚才部署的 hello_world 为例: 从上图可以看到,Service 和 Stage 体现在函数名和标签两个地方。函数名在简单使用时可能没有影响,但如果涉及到函数间调用或者是云 API 使用函数时,就要注意,这里的函数名并不是在 Yaml 中的函数名! 当然,这里也会出现另一个问题,即如果用户已经有一个函数,且这个函数不是按照三段式命名的,那么可能没有办法使用 Plugin 进行部署,除非把函数进行迁移,将原函数删掉,使用 Serverless 重新进行部署。 什么是 Serverless Component Plugin 主要是对函数的管理,那么 Component 呢?Component 可以认为是云产品的工具,因为通过 Componnt 可以对所有的组件进行组合使用,甚至还可以很简单开发出自己的 Component 来满足需求。 Component 的 Yaml 是一段一段的,而 Plugin 的 Yaml 是一个整体,Component 中前后两个组件可能是完全没有任何关系的,例如: test1: component: "@gosls/tencent-website" inputs: code: src: ./public index: index.html error: index.html region: ap-shanghai bucketName: test1 test2: component: "@gosls/tencent-website" inputs: code: src: ./public index: index.html error: index.html region: ap-shanghai bucketName: test2 通过 Yaml 我们可以看到整个的代码可以分为两部分,是把一个网站的代码放到了不同的 Bucket。 目前腾讯云的 Component 的基础组件包括: @serverless/tencnet-scf @serverless/tencnet-cos @serverless/tencnet-cdn @serverless/tencnet-apigateway @serverless/tencnet-cam-role @serverless/tencnet-cam-policy 封装的上层 Component 包括: @serverless/tencnet-express @serverless/tencnet-bottle @serverless/tencnet-django @serverless/tencnet-egg @serverless/tencnet-fastify @serverless/tencnet-flask @serverless/tencnet-koa @serverless/tencnet-laravel @serverless/tencnet-php-slim @serverless/tencnet-pyramid @serverless/tencnet-tornado @serverless/tencnet-website 基础 Component 指的是可通过相关的 Component 部署相关的资源,例如 tencent-scf 就可以部署云函数,tencent-cos 就可以部署一个存储桶;上层的 Component 实际上就是对底层 Component 的组合,同时增加一些额外的逻辑,实现一些高阶功能,例如 tencent-django 就可以通过对请求的 WSGI 转换,将 Django 框架部署到云函数上,其底层依赖了 tencent-scf/tencent-apigateway 等组件。 相对于 Plugin 而言,Component 并没有那么多的操作,只有两个:部署和移除。 例如部署操作: serverless --debug 移除操作: serverless remove --debug 相对于 Plugin 而言,Component 的产品纬度是增加了,但是实际功能数量是缩减了。不过,这也不是大的问题,毕竟 Plugin 可以和 Component 混用,真正需要解决的问题是,这两者的 Yaml 不一样,如何混用? 总结 Plugin 部署到线上的函数,会自动变更名字,例如函数是 myFunction,服务和阶段是 myService-Dev,那么函数部署到线上就是 myService-Dev-myFunction,这样的函数名,很可能会让函数间调用产生很多不可控因素:如果环境是 Dev,函数间调用就要写函数名是 myService-Dev-myFunction,如果环境是 Test,此时就要写 myService-Test-myFunction。在我看来,环境更改只需要更改配置,无需更改更深入的代码逻辑,因此这一点会让我觉得不友好; Plugin 也是有优势的,例如如果有 Invoke、Remove 以及部署单个函数的功能,同时 Plugin 也有全局变量,它像是一个开发者工具,可以进行开发、部署、调用、查看信息、指标以及删除回滚等操作; Components 可以看作是一个组件集,这里面包括了很多的 Components,有基础的 Components,例如 cos、scf、apigateway 等,也有一些拓展的 Components,例如在 cos 上拓展出来的 website,可以直接部署静态网站等,还有一些框架级的,例如 Koa,Express; Components 除了支持的产品多,可以部署框架之外,对我来说,最大吸引力在于其部署到线上的函数名字就是指定的名字,不会出现额外的东西; Components 相对 Plugin 在功能上略显单薄,除了部署和删除,再没有其他功能。当你需要部署多个东西,并写在了某个 Components 的 yaml 上,那么即使你只修改了一个函数,它都需要全部重新部署一遍; Components 更多的定义是组件,所以在 Components 中是没有全局变量的。 Serverless Framework 30 天试用计划 我们诚邀您来体验最便捷的 Serverless 开发和部署方式。在试用期内,相关联的产品及服务均提供免费资源和专业的技术支持,帮助您的业务快速、便捷地实现 Serverless! 详情可查阅: Serverless Framework 试用计划 One More Thing 3 秒你能做什么?喝一口水,看一封邮件,还是 —— 部署一个完整的 Serverless 应用? 复制链接至 PC 浏览器访问: https://serverless.cloud.tencent.com/deploy/express 3 秒极速部署,立即体验史上最快的 Serverless HTTP 实战开发! 传送门: GitHub: github.com/serverless 官网: serverless.com 欢迎访问: Serverless 中文网 ,您可以在 最佳实践 里体验更多关于 Serverless 应用的开发! 推荐阅读: 《Serverless 架构:从原理、设计到项目实战》 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 学员点评@肖强: 相对于基于OpenStack架构的云厂商来说,ZStack更容易部署,基于全异步架构,并发创建虚拟机的速度更快更稳定,后期升级也十分方便。总的来说,结合我接触过的其他云产品,ZStack更简单易用,功能也比较完善,这契合了ZStack简单Simple、健壮Strong、弹性Scalable、智能Smart的“4S”特性理念。 接触 作为一名即将上岗实习的云计算专业大三学生,受疫情影响,我无法进入公司实习,学校安排我们在家里自主学习。正巧,我看到几名网友的朋友圈晒出了ZCCT和ZCCC的证书。通过和他们联系,我了解到ZStack疫情期间推出了ZCCT在线认证免费开放的活动,出于了解业内云计算厂商产品与技术、提升自身技能水平等目的,我仔细研究了ZCCT和ZCCC认证课程大纲,然后马不停蹄的开始了学习。 学习 从周三上午到周四凌晨,我一鼓作气拿下了ZCCT与ZCCC的理论考试与上机实验考试,看到我的学习成果和邮箱里发来的证书,当天晚上兴奋的睡不着觉哈哈哈。虽然还没有参加工作,但是作为相关专业的学生,我对云计算还是很感兴趣的,在校期间也有学习其他云厂商的产品,所以在学习ZStack初级认证课程的时候还比较轻松。而且ZStack培训课程讲的非常好,知识很系统全面,同时还结合实战演练,提供免费的线上实验环境。ZStack老师讲课很温柔,我当时半夜直接笑场了,还回放了好几次。整个学习过程很轻松,很快乐。 实战 ZCCT与ZCCC考试都有理论笔试题,考前我特意复习了上课做的笔记,但还是太粗心和大意了,用了很短的时间把题目做完了,最后都只考了80多分。最让我纠结的是ZCCT的LAB,看完实验步骤后,我规划了两台虚拟机,一台部署ZStack云平台,而另一台作为iSCSI服务器向云平台提供sharedblock存储,根据实验指导一步一步来。 在“将嵌套云平台添加Sharedblock主存储”步骤,一直显示添加集群失败。根据日志分析,我仔细检查了iSCSI服务器的配置,确认无误后发现并不是文档错了,而是需要在iSCSI服务器上打开lvm某配置,经过多次找资料并尝试无果后,我最终还是向ZStack申请了线上资源。 本想着睡个觉明日再战!没想到ZStack很快就给我准备好了实验资源,我迫不及待登陆了上去,想着今天一定要拿下它。果然根据ZStack提供的标准环境,我半小时就顺利做完了实验,提交日志,审核通过! 总结 本次学习过程,最大的收获就是了解了ZStack的产品与技术,并且在ZStack官网看了部分技术白皮书与技术类的文章,更进一步加深了对ZStack架构和产品特性的掌握。相对于很多基于OpenStack架构的云厂商来说,ZStack更容易部署,基于全异步架构,并发创建虚拟机的速度更快更稳定,后期升级也十分方便。总的来说,结合我接触过的其他云产品,ZStack更简单易用,功能也比较完善,这契合了ZStack简单Simple、健壮Strong、弹性Scalable、智能Smart的“4S”特性理念。我相信ZStack未来发展潜力很大,我也会密切关注ZStack,期待未来ZStack能提供更多的培训课程。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 作者 | 王旭 蚂蚁金服资深技术专家 本文整理自《CNCF x Alibaba 云原生技术公开课》第 28 讲, 点击直达课程页面 。 关注“阿里巴巴云原生”公众号,回复关键词**“入门”**,即可下载从零入门 K8s 系列文章 PPT。 一、缘起:安全容器的命名 Phil Karlton 有一句名言:“计算机科学界只有两个真正的难题——缓存失效和命名。” 对我们容器圈而言,我相信「命名」绝对配得上这句话。这毫无疑问是一件让老开发者沉默、让新人落泪的事情。仅就系统软件而言,我们当今比较通行地称为**「Linux 容器技术」**这个概念,它曾经用过的名字还有 Jail, Zone, Virtual Server, Sandbox 等。同样,在早期虚拟化的技术栈里也把一类虚拟机叫做容器,毕竟这个词本身就指代那些用来包容、封装和隔离的器物。它实在太过常见了,以至于以严谨著称的 Wikipedia,它的词条叫做「OS-Level Virtualization」(系统级虚拟化) ,从而回避了「什么是容器」这个问题。 在 2013 年,Docker 问世之后,容器这个概念伴随着不可变基础设施、云原生这一系列概念在随后的几年间以摧枯拉朽之势颠覆了基于“软件包+配置”这种细粒度组合的应用部署,用简单的声明式策略和不可变的容器就清爽地定义了软件栈。应用怎么部署,在这儿似乎有点离题了,我在这里想要强调的是: “云原生语境下的容器,实质是「应用容器」——是以标准格式封装的,运行于标准操作系统环境(常常是 Linux ABI)上的应用打包——或运行这一应用打包的程序/技术。” 这个定义是我下的,但它并不是我的个人意志,是基于 OCI 规范这一共识写出来的。这个规范规定了容器之中应用被放到什么样的环境下、如何运行,比如说容器的根文件系统上哪个可执行文件会被执行,是用什么用户执行,需要什么样的 CPU,有什么样的内存资源、外置存储,还有什么样的共享需求等等。 所以说,标准格式的封装、标准的操作系统环境在一起以应用为中心就构成了应用容器的打包。 以这个共识为基础,就可以来说说安全容器了。当年,我和我的联合创始人赵鹏使用「虚拟化容器」这个名字来命名我们的技术的,不过为了博人眼球,我们用了「Secure as VM, Fast as Container」这样的 Slogan,于是,被容器安全性问题戳中心坎的人们立刻用「Secure Container」或者说「安全容器」来称呼这种东西了,一发而不可收。虽然在我们的内心里,这个技术是一层额外的隔离,它只是安全中的一环,但是呢,用户还是愿意用安全容器这个名字来称呼它。我们给安全容器下的定义就是: 安全容器是一种运行时技术,为容器应用提供一个完整的操作系统执行环境(常常是 Linux ABI),但将应用的执行与宿主机操作系统隔离开,避免应用直接访问主机资源,从而可以在容器主机之间或容器之间提供额外的保护。 这就是我们的安全容器。 二、间接层:安全容器的精髓 说安全容器的时候,就要提到「间接层」这个词。它出自于 Linus Torvalds 在 2015 年的 LinuxCon 上提出的: “安全问题的唯一正解在于允许那些(导致安全问题的)Bug 发生,但通过额外的隔离层来阻挡住它们。” 为了安全,为什么要引入隔离层呢?其实 Linux 本身这样的规模是非常大的,无法从理论上来验证程序是没有 Bug 的,于是,一旦合适的 Bug 被利用,安全性风险就变成安全性问题了。安全性的框架和修补并不能确保安全,所以我们需要进行一些额外的隔离来减少漏洞以及因为这些漏洞造成的被彻底攻破的风险。 这就是安全容器的由来。 三、Kata Containers:云原生化的虚拟化 2017 年 12 月,我们在 KubeCon 上对外发布了 Kata Containers 的安全容器项目,这个项目有两个前身:由我们之前开始的 runV 以及 Intel 的 Clear Container 项目。这两个项目都是 2015 年 5 月开始开展的,实际上是早于 Linus 在 KubeCon 2015 说的那番话的。 它们的思路都很简单: 操作系统本身的容器机制没法解决安全性问题,需要一个隔离层; 虚拟机本身,VM,它是一个现成的隔离层,比如说像阿里云、AWS,它们都使用了虚拟化技术,所以对于全世界来说,大家已经普遍地相信,对于用户来说,只要能做到「secure of VM」,那这个安全性就可以满足公有云的需求了; 虚拟机中如果有个内核,就可以支持我们刚才所提到的 OCI 的定义,也就是说提供了 Linux ABI 的运行环境,在这个运行环境中跑一个 Linux 应用不太难实现。 现在的问题是虚机不太够快,阻碍了它在容器环境中的应用,如果能拥有「speed of container」的话,那我们就可能可以有一个用虚拟机来做隔离的安全容器技术了。这个也就是 Kata Containers 本身的一个思路,就是用虚拟机来做 Kubernetes 的 PodSandbox。在 Kata 里面被拿来做 VM 的先后有 qemu, firecracker, ACRN, cloud-hypervisor 等。 下图就是 Kata Containers 怎么去和 Kubernetes 集成的,这里的例子用的是 containerd,当然 CRI-O 也是一样的。 目前,Kata Containers 通常是在 Kubernetes 中使用。首先 Kubelet 通过 CRI 接口找到 containerd 或者 CRI-O,这个时候比如镜像这样的操作一般也是由 containerd 或者 CRI-O 来执行的。根据请求,它会把 runtime 部分的需求变成一个 OCI spec,并交给 OCI runtime 执行。比如说上图上半部分中的 kata-runtime,或者说下半部分精简过后的 containerd-shim-kata-v2。具体的过程是这样的: 当 containerd 拿到一个请求的时候,它会首先创建一个 shim-v2。这个 shim-v2 就是一个 PodSandbox 的代表,也就是那个VMM 的代表; 每一个 Pod 都会有一个 shim-v2 来为 containerd/CRI-O 来执行各种各样的操作。shim-v2 会为这个 Pod 启动一个虚拟机,在里面运行着一个 linux kernel,也就是图里面的 Guest kernel。如果这个里面用的是 qemu 的话,我们会通过一些配置和一些补丁,让它变得小一些。同时这个里面也没有额外的 Guest 操作系统,不会跑一个完整的像 CentOS, Ubuntu 这样的操作系统; 后我们会把这个容器的 spec 以及这个容器本身打包的存储,包括 rootfs 和文件系统,交给这个 PodSandbox。这个 PodSandbox 会在虚机中由 kata-agent 把容器启动起来; 依照 CRI 语义和 OCI 规范,在一个 Pod 里面是可以启动多个相关联的容器的。它们会被放到同一个虚拟机里面,并且可以根据需求共享某些 namespace; 除了这些之外,其它的一些外置的存储和卷也可以通过热插拔的方式来插到这个 PodSandbox 里面来; 对于网络来说,目前使用 tcfilter 就可以无缝地接入几乎所有的 Kubernetes 的 CNI 插件。而且我们还提供了一个 enlightened 的模式,这样的话会有一个特制的 CNI 插件来提高容器的网络能力。 可以看到,在我们的 PodSandbox 里面,实际上只有一个 Guest Kernel 跑着一些容器本身的打包和容器应用,并不包含一个完整的操作系统。就是说,这个过程,它用起来并不像是传统的虚拟机,对于容器来说,它只有容器的引擎,并且通过少用不必要的内存、共享能共享的内存来进一步地降低内存的开销。 与传统的虚拟机比起来,开销更小、启动更轻快,对于大部分的场景来说,它可以做到「secure as VM」、「fast as container」。同时,在安全性技术以外,相比传统的虚机,它有更多的弹性,更少了机器的那种物理操作的手感,比如说这里面说过的包括动态资源的插拔以及使用 virtio-fs 这样的技术等。它是一个专门为我们这种场景、为像 kata 这样的场景来做的一个把 host 的基本文件系统的内容(比如说容器的 rootfs )共享给虚拟机的这样一个技术。 通过其中一些之前为非易失存储、非易失内存来做的 DAX 的技术,能够在不同的 PodSandbox 之间,也就是不同的 Pod 之间、不同的容器之间,共享一些可以共享的只读的内存部分。这样可以在不同的 PodSandbox 之间去节省很多的内存。同时所有的 Pod 的管理都是通过 Kubernetes 从外部进行的容器管理,并且从外部来获取 metrics 和 debug 信息,并没有登陆虚拟机这样一种手感。所以它看起来是一种非常容器化的操作,虽然从底层来看,它还是一个虚拟机,但是实际上它是一个面向云原生的虚拟化。 四、gVisor:进程级虚拟化 gVisor,我们又把它叫做进程级的虚拟化,它是和 kata 不一样的另外一种方式。 在 2018 年的 5 月份,哥本哈根的 KubeCon 上,Google 开源了他们内部开发了 5 年的 gVisor 安全容器作为对 kata containers 的回应,表明了他们有一种不同的安全容器的解决方案。 如果说 Kata Containers 是通过对现有的隔离技术进行组合和改造来构建容器间的隔离层的话,那么 gVisor 的设计显然是更加简洁的。 如上图右侧所示,它是一个用 Go 语言重写的运行在用户态的操作系统内核,这个内核的名字叫做 sentry,它并不依赖于虚拟化和虚拟机技术,相反,它是借助一个它们内部叫做一个 Platform(平台)的能力,让宿主机的操作系统做一个操作,把应用所有的期望对操作系统的操作都转交给 sentry 来进行,sentry 做处理之后会把其中的一部分交给操作系统来帮它完成,大部分则由自己来完成。 gVisor 是一个纯粹的面向应用的隔离层,从一开始就不是一个完全等同于虚拟机的东西,它就是用来在 Linux 上面跑一个 Linux 程序的。作为一个隔离层,它的安全性依据在于: gVisor 的开发者们首先要把攻击面变小,宿主机的操作系统将只为沙箱里的应用提供大约 20% 的系统调用; Linux 大概有 300 多个 Syscall,实际上 sentry 最后向操作系统发起的调用只会集中在 60 多个 Syscall 上。这个是源于 gVisor 的开发者们对操作系统的安全做了一些研究,他们发现,大多数对操作系统的成功的攻击都是来自于不常用的系统调用的。 这个很容易理解,因为不常用的系统调用,它的实现路径一般都是比较老的路径,也就是说这些部分的开发一般不是太积极,只有很少的开发者来维护,那些热门路径上的代码要更安全一些,因为那些代码被 review 的次数比较多。所以 gVisor 的设计就是让应用对那些并不常用的 Syscall 的访问根本就到不了操作系统层面,而只在 sentry 里就把它处理掉。 从 sentry 访问宿主机的,只使用那些被验证过的、比较成熟、比较热的路径上的系统调用,这样的话,安全性就会比原来看起来好很多。我们现在 Syscall 是原来的 1/5,但是被攻击的可能性是并不到 1/5 的。 其次,他们发现,一些经常被攻击的系统调用需要把它隔离出来,比如 open(),就是打开文件的那个操作; 在 Unix 系统里面,大部分东西都是一些文件,所以 open 可以做太多的事情了,大部分的攻击都是通过 open 来进行的。gVisor 的开发者就单独地把 open 放到了一个独立的进程里面去实现,这个进程叫做 Gofer。一个独立的进程实际上是更容器被 seccomp、被一些系统的限制、一些 "capbility drop" 来保护。Gofer 可以做更少的事情,可以用非 root 去执行,如此一来整个系统的安全性就被进一步地被提高了。 最后,sentry 和 Gofer 都是用 Go 语言来实现的,不是用传统的 C 语言实现的。 Go 语言本身是一个内存更安全的一个实现,因此整个 gVisor 就更不容易被攻击,更不容易发生一些内存上的问题。当然,Go 语言在有些地方还是不够太系统级的,gVisor 的开发者也坦言,他们为了做这件事情,也对 Go Runtime 做了很多调整,并把这些东西也反馈回给了 Go 语言的社区。 可以说 gVisor 的架构很漂亮,有很多开发者跟我坦诚,他们其实很喜欢 gVisor 的架构,觉得这个更简单、更纯粹、更干净。当然了,虽然它的架构很漂亮,但重新实现一个内核这件事情也只有 Google 这样的巨头能做得出来,类似的可能还有微软的 WSL 1。而且这个设计是比较超前的,它其实存在一些问题: 首先,sentry 并不是 Linux,所以在兼容性方面与 kata 这样的方案比起来还是有一定的差距的。这个没有办法,但是对于特定应用来说,这个可能并不是问题; 其次,对于当前的系统调用的实现方式,还有 CPU 的指令系统来说,我们从应用去拦截 Syscall,再把这个 Syscall 送给 sentry 去执行,这个过程本身是有相当大的开销的。在一定场景之下,gVisor 是可以有更好的性能的。但是,在大部分的场景之下,gVisor 的性能仍然是比不上 kata 这样的解决方案的。 所以短时间之内,gVisor 这样的解决方案并不能成为一个终极的解决方案,不过它可以适应一些特定的场景,并且它也带来一些启示性。我觉得这个启示性对未来的操作系统、CPU 指令集的发展都可能会有一些作用。而且我相信,在未来,不管是 kata 还是 gVisor,都会有一个演进,我们期待着最后会有一个公共的解决方案来统一地解决应用的执行问题。 五、安全容器:不止于安全 安全容器的名字虽叫安全,但是它提供的是一个隔离性。它的作用是不止于安全的。 安全容器通过隔离层让应用的问题——不管是来自于外部的恶意攻击还是说意外的错误,都不至于影响主机,也不会在不同的 Pod 之间相互影响,所以实际上,这个额外的隔离层,它所带来的影响不只是安全,还有其它的方面。它对于系统的调度、服务质量,还有应用信息的保护都是有好处的。 我们说传统的操作系统容器技术是内核进程管理的一个延伸,容器进程本身是一组相关联的进程,对于宿主机的调度系统来说,它是完全可见的,一个 Pod 里的所有容器或进程,同时也都被宿主机调度和管理。这就意味着,如果你有一个大量容器的环境,宿主机本身内核的负担就会很重,在很多实际环境中已经可以观察到这个负担带来的开销了。 尤其是现在计算机技术的不断发展,一个操作系统会有大量的内存,大量的 CPU,几百 G 的内存都是可以见到的。在这个情况下,如果分配的容器数量很多,调度系统就会有非常沉重的开销。在采纳安全容器之后,在宿主机上就看不到这些完整的信息了,这个隔离层同时承担了一些对隔离层上面应用的调度,于是在主机上面就只需要调度这些沙箱本身,降低了宿主机的调度开销,这也就是它为什么会提高调度效率的原因。 提高调度效率的同时,它会把所有的应用彼此隔离起来,这样就避免了容器之间、容器和主机之间的干扰,提高了服务质量。从另外一个方向来看,我们做安全容器的初衷是为了保护宿主机不受到容器内恶意或者有问题的应用的影响,反过来,作为一个云来说,我们有可能会面对有恶意的攻击,所以也是保护我们自己。 同时用户也不愿意让我们过多地去访问用户的资源,用户需要使用资源,但它并不需要我们看到它的数据。安全容器可以把用户运行的东西完全封装在容器里,这样的话可以让主机的运维管理操作并不能访问到应用的数据,从而把应用的数据保护在沙箱里,不需要去碰到用户数据。如果我们要访问用户数据,作为一个云的话,那就必须得让用户给你授权,这个时候,用户不确定你是不是有什么恶意的操作,如果我们的沙箱封装得很好的话,那也就不需要额外的对用户授权的要求,这对于保护用户的私密性是更好的。 当我们把目光看向未来的时候,可以看到,安全容器不仅仅是在做安全隔离,安全容器隔离层的内核相对于宿主机的内核是独立的,专门对应用服务,从这个角度来说,主机和应用的功能之间实际上是一个合理的功能分配与优化。它可以展现出很多的潜力,未来的安全容器,可能不仅仅是隔离性能开销的降低,同时也是在提高应用的性能。隔离技术会让云原生基础设施更加完美。 六、本文总结 本文的主要内容就到此为止了,这里为大家简单总结一下: 现在,所谓“安全容器”是指一种容器运行时技术,为容器应用提供一个完整的操作系统执行环境(常常是 Linux ABI),但将应用的执行与宿主机操作系统隔离开,避免应用直接访问主机资源,从而可以在容器主机之间或容器之间提供额外的保护; Kata Containers 是一个使用虚拟化来提供隔离层的开源安全容器项目,完全兼容 Kubernetes 等云原生生态系统,项目托管在OpenStack Foundation,由蚂蚁金服和Intel共同领导; gVisor 是一种利用进程级虚拟化技术实现的安全容器技术,由 Google 开发并开源,用 Go 语言实现了一个用户态的兼容内核; 最后,安全容器提供的隔离性不止是安全中的一环,也可以提供性能、调度、管理方面的隔离。 “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。” 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 这是 Cgroup 系列的第四篇,往期回顾: Linux Cgroup 入门教程:基本概念 Linux Cgroup 入门教程:CPU Linux Cgroup 入门教程:内存 通过 上篇文章 的学习,我们学会了如何查看当前 cgroup 的信息,如何通过操作 /sys/fs/cgroup 目录来动态设置 cgroup,也学会了如何设置 CPU shares 和 CPU quota 来控制 slice 内部以及不同 slice 之间的 CPU 使用时间。本文将继续探讨对 CPU 使用时间的限制。 对于某些 CPU 密集型的程序来说,不仅需要获取更多的 CPU 使用时间,还要减少工作负载在节流时引起的上下文切换。现在的多核系统中每个核心都有自己的缓存,如果频繁的调度进程在不同的核心上执行势必会带来缓存失效等开销。那么有没有方法针对 CPU 核心进行隔离呢?准确地说是把运行的进程绑定到指定的核心上运行。虽然对于操作系统来说,所有程序生而平等, 但有些程序比其他程序更平等。 对于那些更平等的程序来说,我们需要为它分配更多的 CPU 资源,毕竟人都是很偏心的。废话少说,我们来看看如何使用 cgroup 限制进程使用指定的 CPU 核心。 1. 查看 CPU 配置 CPU 核心的编号一般是从 0 开始的,4 个核心的编号范围是 0-3 。我们可以通过查看 /proc/cpuinfo 的内容来确定 CPU 的某些信息: $ cat /proc/cpuinfo ... processor : 3 vendor_id : GenuineIntel cpu family : 6 model : 26 model name : Intel(R) Xeon(R) CPU X5650 @ 2.67GHz stepping : 4 microcode : 0x1f cpu MHz : 2666.761 cache size : 12288 KB physical id : 6 siblings : 1 core id : 0 cpu cores : 1 apicid : 6 initial apicid : 6 fpu : yes fpu_exception : yes cpuid level : 11 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc eagerfpu pni ssse3 cx16 sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer hypervisor lahf_lm ssbd ibrs ibpb stibp tsc_adjust arat spec_ctrl intel_stibp flush_l1d arch_capabilities bogomips : 5333.52 clflush size : 64 cache_alignment : 64 address sizes : 43 bits physical, 48 bits virtual processor : 表示核心的编号,但这不是物理 CPU 的核心,更确切地可以称之为**逻辑核编号。 physical id : 表示当前逻辑核所在的物理 CPU 的核心,也是从 0 开始编号,这里表示这个逻辑核在第 7 个 物理 CPU 上。 core id : 如果这个值大于 0,你就要注意了,你的服务器可能开启了超线程。如果启用了超线程,每个物理 CPU 核心会模拟出 2 个线程,也叫逻辑核(和上面的逻辑核是两回事,只是名字相同而已)。如果你想确认服务器有没有开启超线程,可以通过下面的命令查看: $ cat /proc/cpuinfo | grep -e "core id" -e "physical id" physical id : 0 core id : 0 physical id : 2 core id : 0 physical id : 4 core id : 0 physical id : 6 core id : 0 如果 physical id 和 core id 皆相同的 processor 出现了两次,就可以断定开启了超线程。显然我的服务器没有开启。 2. NUMA 架构 这里需要涉及到一个概念叫 NUMA(Non-uniform memory access) ,即 非统一内存访问架构 。如果主机板上插有多块 CPU,那么就是 NUMA 架构。每块 CPU 独占一块面积,一般都有独立风扇。 一个 NUMA 节点包含了直连在该区域的 CPU、内存等硬件设备,通信总线一般是 PCI-E 。由此也引入了 CPU 亲和性的概念,即 CPU 访问同一个 NUMA 节点上的内存的速度大于访问另一个节点的。 可以通过下面的命令查看本机的 NUMA 架构: $ numactl --hardware available: 1 nodes (0) node 0 cpus: 0 1 2 3 node 0 size: 2047 MB node 0 free: 1335 MB node distances: node 0 0: 10 可以看出该服务器并没有使用 NUMA 架构,总共只有一个 NUMA 节点,即只有一块 CPU,4 个逻辑核心均在此 CPU 上。 3. isolcpus Linux 最重要的职责之一就是调度进程,而进程只是程序运行过程的一种抽象,它会执行一系列指令,计算机会按照这些指令来完成实际工作。从硬件的角度来看,真正执行这些指令的是中央处理单元,即 CPU。默认情况下,进程调度器可能会将进程调度到任何一个 CPU 核心上,因为它要根据负载来均衡计算资源的分配。 为了增加实验的明显效果,可以隔离某些逻辑核心,让系统默认情况下永远不会使用这些核心,除非我指定某些进程使用这些核心。要想做到这一点,就要使用到内核参数 isolcpus 了,例如:如果想让系统默认情况下不使用逻辑核心 2,3 和 4,可以将以下内容添加到内核参数列表中: isolcpus=1,2,3 # 或者 isolcpus=1-3 对于 CnetOS 7 来说,可以直接修改 /etc/default/grub : $ cat /etc/default/grub GRUB_TIMEOUT=5 GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)" GRUB_DEFAULT=saved GRUB_DISABLE_SUBMENU=true GRUB_TERMINAL_OUTPUT="console" GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet isolcpus=1,2,3" GRUB_DISABLE_RECOVERY="true" 然后重新构建 grub.conf : $ grub2-mkconfig -o /boot/grub2/grub.cfg 重启系统之后,系统将不再使用逻辑核心 2,3 和 4,只会使用核心 1。找个程序把 CPU 跑满( 上篇文章 用的程序),使用命令 top 查看 CPU 的使用状况: 执行 top 命令后,在列表页按数字 1 键,就可以看到所有 CPU 了。 可以看到系统只使用了核心 1,下面我们来看看如何将程序绑到特定的 CPU 核心上。 4. 创建 cgroup 将程序绑到指定的核心其实很简单,只需设置好 cpuset 控制器就行了。 systemctl 可以管理受其控制资源的 cgroup 控制器,但只能管理有限的控制器(CPU、内存和 BlockIO),不能管理 cpuset 控制器。虽然 systemd 不支持 cpuset,但是相信以后会支持的,另外,现在有一个略显笨拙,但是可以实现同样的目标的方法,后面会介绍。 cgroup 相关的所有操作都是基于内核中的 cgroup virtual filesystem,使用 cgroup 很简单,挂载这个文件系统就可以了。文件系统默认情况下都是挂载到 /sys/fs/cgroup 目录下,查看一下这个目录: $ ll /sys/fs/cgroup 总用量 0 drwxr-xr-x 2 root root 0 3月 28 2020 blkio lrwxrwxrwx 1 root root 11 3月 28 2020 cpu -> cpu,cpuacct lrwxrwxrwx 1 root root 11 3月 28 2020 cpuacct -> cpu,cpuacct drwxr-xr-x 2 root root 0 3月 28 2020 cpu,cpuacct drwxr-xr-x 2 root root 0 3月 28 2020 cpuset drwxr-xr-x 4 root root 0 3月 28 2020 devices drwxr-xr-x 2 root root 0 3月 28 2020 freezer drwxr-xr-x 2 root root 0 3月 28 2020 hugetlb drwxr-xr-x 2 root root 0 3月 28 2020 memory lrwxrwxrwx 1 root root 16 3月 28 2020 net_cls -> net_cls,net_prio drwxr-xr-x 2 root root 0 3月 28 2020 net_cls,net_prio lrwxrwxrwx 1 root root 16 3月 28 2020 net_prio -> net_cls,net_prio drwxr-xr-x 2 root root 0 3月 28 2020 perf_event drwxr-xr-x 2 root root 0 3月 28 2020 pids drwxr-xr-x 4 root root 0 3月 28 2020 systemd 可以看到 cpuset 控制器已经默认被创建并挂载好了。看一下 cpuset 目录下有什么: $ ll /sys/fs/cgroup/cpuset 总用量 0 -rw-r--r-- 1 root root 0 3月 28 2020 cgroup.clone_children --w--w--w- 1 root root 0 3月 28 2020 cgroup.event_control -rw-r--r-- 1 root root 0 3月 28 2020 cgroup.procs -r--r--r-- 1 root root 0 3月 28 2020 cgroup.sane_behavior -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.cpu_exclusive -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.cpus -r--r--r-- 1 root root 0 3月 28 2020 cpuset.effective_cpus -r--r--r-- 1 root root 0 3月 28 2020 cpuset.effective_mems -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.mem_exclusive -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.mem_hardwall -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.memory_migrate -r--r--r-- 1 root root 0 3月 28 2020 cpuset.memory_pressure -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.memory_pressure_enabled -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.memory_spread_page -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.memory_spread_slab -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.mems -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.sched_load_balance -rw-r--r-- 1 root root 0 3月 28 2020 cpuset.sched_relax_domain_level -rw-r--r-- 1 root root 0 3月 28 2020 notify_on_release -rw-r--r-- 1 root root 0 3月 28 2020 release_agent -rw-r--r-- 1 root root 0 3月 28 2020 tasks 该目录下只有默认的配置,没有任何 cgroup 子系统。接下来我们来创建 cpuset 子系统并设置相应的绑核参数: $ mkdir -p /sys/fs/cgroup/cpuset/test $ echo "3" > /sys/fs/cgroup/cpuset/test/cpuset.cpus $ echo "0" > /sys/fs/cgroup/cpuset/test/cpuset.mems 首先创建了一个 cpuset 子系统叫 test ,然后将核心 4 绑到该子系统,即 cpu3 。对于 cpuset.mems 参数而言,每个内存节点和 NUMA 节点一一对应。如果进程的内存需求量较大,可以把所有的 NUMA 节点都配置进去。这里就用到了 NUMA 的概念。出于性能的考虑,配置的逻辑核和内存节点一般属于同一个 NUMA 节点,可用 numactl --hardware 命令获知它们的映射关系。很显然,我的主机没有采用 NUMA 架构,只需将其设为节点 0 就好了。 查看 test 目录: $ cd /sys/fs/cgroup/cpuset/test $ ll 总用量 0 -rw-rw-r-- 1 root root 0 3月 28 17:07 cgroup.clone_children --w--w---- 1 root root 0 3月 28 17:07 cgroup.event_control -rw-rw-r-- 1 root root 0 3月 28 17:07 cgroup.procs -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.cpu_exclusive -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.cpus -r--r--r-- 1 root root 0 3月 28 17:07 cpuset.effective_cpus -r--r--r-- 1 root root 0 3月 28 17:07 cpuset.effective_mems -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.mem_exclusive -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.mem_hardwall -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.memory_migrate -r--r--r-- 1 root root 0 3月 28 17:07 cpuset.memory_pressure -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.memory_spread_page -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.memory_spread_slab -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.mems -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.sched_load_balance -rw-rw-r-- 1 root root 0 3月 28 17:07 cpuset.sched_relax_domain_level -rw-rw-r-- 1 root root 0 3月 28 17:07 notify_on_release -rw-rw-r-- 1 root root 0 3月 28 17:07 tasks $ cat cpuset.cpus 3 $ cat cpuset.mems 0 目前 tasks 文件是空的,也就是说,还没有进程运行在该 cpuset 子系统上。需要想办法让指定的进程运行在该子系统上,有两种方法: 将已经运行的进程的 PID 写入 tasks 文件中; 使用 systemd 创建一个守护进程,将 cgroup 的设置写入 service 文件中(本质上和方法 1 是一样的)。 先来看看方法 1,首先运行一个程序: $ nohup sha1sum /dev/zero & [1] 3767 然后将 PID 写入 test 目录的 tasks 中: $ echo "3767" > /sys/fs/cgroup/cpuset/test/tasks 查看 CPU 使用情况: 可以看到绑核生效了, PID 为 3767 的进程被调度到了 cpu3 上。 下面再来看看方法 2,虽然目前 systemd 不支持使用 cpuset 去指定一个 Service 的 CPU,但我们还是有一个变相的方法,Service 文件内容如下: $ cat /etc/systemd/system/foo.service [Unit] Description=foo After=syslog.target network.target auditd.service [Service] ExecStartPre=/usr/bin/mkdir -p /sys/fs/cgroup/cpuset/testset ExecStartPre=/bin/bash -c '/usr/bin/echo "2" > /sys/fs/cgroup/cpuset/testset/cpuset.cpus' ExecStartPre=/bin/bash -c '/usr/bin/echo "0" > /sys/fs/cgroup/cpuset/testset/cpuset.mems' ExecStart=/bin/bash -c "/usr/bin/sha1sum /dev/zero" ExecStartPost=/bin/bash -c '/usr/bin/echo $MAINPID > /sys/fs/cgroup/cpuset/testset/tasks' ExecStopPost=/usr/bin/rmdir /sys/fs/cgroup/cpuset/testset Restart=on-failure [Install] WantedBy=multi-user.target 启动该服务,然后查看 CPU 使用情况: 该服务中的进程确实被调度到了 cpu2 上。 5. 回到 Docker 最后我们回到 Docker , Docker 实际上就是将系统底层实现的 cgroup 、 namespace 等技术集成在一个使用镜像方式发布的工具中,于是形成了 Docker ,这个想必大家都知道了,我就不展开了。对于 Docker 来说,有没有办法让容器始终在一个或某几个 CPU 上运行呢?其实还是很简单的,只需要利用 --cpuset-cpus 参数就可以做到! 下面就来演示一下,指定运行容器的 CPU 核心编号为 1: 🐳 → docker run -d --name stress --cpuset-cpus="1" progrium/stress -c 4 查看主机 CPU 的负载: 只有 Cpu1 达到了 100% ,其它的 CPU 并未被容器使用。 如果你看过该系列的 第一篇文章 ,应该知道,在新的使用 systemd 实现 init 的系统中(比如 ConetOS 7 ),系统默认创建了 3 个顶级 slice : System , User 和 Machine ,其中 machine.slice 是所有虚拟机和 Linux 容器的默认位置,而 Docker 其实是 machine.slice 的一个变种,你可以把它当成 machine.slice 。 如果系统中运行的是 Kubernetes, machine.slice 就变成了 kubepods : 为了便于管理 cgroup, systemd 会为每一个 slice 创建一个子系统,比如 docker 子系统: 然后再根据容器的设置,将其放入相应的控制器下面,这里我们关心的是 cpuset 控制器,看看它的目录下有啥: 查看 docker 目录: 可以看到 Docker 为每个容器创建了一个子目录, 7766.. 对应的就是之前我们创建的容器: 🐳 → docker ps|grep stress 7766580dd0d7 progrium/stress "/usr/bin/stress --v…" 36 minutes ago Up 36 minutes stress 我们来检验一下该目录下的配置: $ cd /sys/fs/cgroup/cpuset/docker/7766580dd0d7d9728f3b603ed470b04d0cac1dd923f7a142fec614b12a4ba3be $ cat cpuset.cpus 1 $ cat cpuset.mems 0 $ cat tasks 6536 6562 6563 6564 6565 $ ps -ef|grep stress root 6536 6520 0 10:08 ? 00:00:00 /usr/bin/stress --verbose -c 4 root 6562 6536 24 10:08 ? 00:09:50 /usr/bin/stress --verbose -c 4 root 6563 6536 24 10:08 ? 00:09:50 /usr/bin/stress --verbose -c 4 root 6564 6536 24 10:08 ? 00:09:50 /usr/bin/stress --verbose -c 4 root 6565 6536 24 10:08 ? 00:09:50 /usr/bin/stress --verbose -c 4 当然,你也可以将容器绑到多个 CPU 核心上运行,这里我就不赘述了。下篇文章将会介绍如何通过 cgroup 来限制 BlockIO 。 微信公众号 扫一扫下面的二维码关注微信公众号,在公众号中回复◉加群◉即可加入我们的云原生交流群,和孙宏亮、张馆长、阳明等大佬一起探讨云原生技术
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 近年来,IT 技术的更新迭代速度非常快,每个时间点都有典型的代表名词以及概念,就目前而言,人工智能领域中的机器学习、深度学习、强化学习等名词和概念就非常热,同时区块链、物联网等技术发展也是异常火热。 在云计算领域,有这样一个技术被众多云厂商认为是“风口项目”,甚至可以颠覆现有云计算中的某些格局,为此包括 AWS、谷歌以及腾讯云、阿里云等在内的云厂商,都为此投入了重大人力以及精力进行相关产品建设,它就是 Serverless 技术。 自 2006 年 8 月 9 日,Google 首席执行官埃里克·施密特(Eric Schmidt)在搜索引擎大会(SESSanJose2006)首次提出“云计算”(Cloud Computing)的概念之后,云计算的发展可以用日新月异这个词来形容。 在短短十几年的发展过程中,云计算也从 IaaS 到 PaaS,再到 SaaS,逐渐将去服务器化趋势表现得愈发明显。就目前的情况来看,全球各大 IT 企业,都在紧罗密布的部署自己的“云事业”,尤其是 Serverless 相关概念的推广和产品的推出以及项目的落地,包括 AWS、Google Cloud、Azure、阿里云、腾讯云、华为云等在内的云厂商,无一例外的向 Serverless 进军。或许云计算下一个阶段,可能就是 BaaS+FaaS+Others,即 Serverless,当然也可能这个阶段就是! 什么是 Serverless Serverless 可以说是一种架构,一种云计算发展的产物,至于具体说什么是 Serverless,可能没有谁无法给一个明确的概念,如果非要给出一个概念,那或许可以参考 Martin Fowler 在《Serverless Architectures》中对 Serverless 这样定义: Serverless was first used to describe applications that significantly or fully incorporate third-party, cloud-hosted applications and services, to manage server-side logic and state. These are typically “rich client” applications—think single-page web apps, or mobile apps—that use the vast ecosystem of cloud-accessible databases (e.g., Parse, Firebase), authentication services(e.g., Auth0, AWS Cognito), and so on. These types of services have been previously described as “(Mobile) Backend as a service", and I use “BaaS” as shorthand in the rest of this article. Serverless can also mean applications where server-side logic is still written by the application developer, but, unlike traditional architectures, it’s run in stateless compute containers that are event-triggered, ephemeral (may only last for one invocation), and fully managed by a third party. One way to think of this is “Functions as a Service” or “FaaS”.(Note: The original source for this name—a tweet by @marak—isno longer publicly available.) AWS Lambda is one of the most popular implementations of a Functions-as-a-Service platform at present, but there are many others, too. 当然这个描述貌似很长,读起来也有点干涩难懂。不过,大家可以简单粗暴的把 Serverless 认为是 BaaS + FaaS,如果用一张图来表示上面的描述,可以是: 说到这里,不同的人可能已经对 Serverless 有了不同的勾勒,但是可能普遍还有一个疑问,我怎么用 Serverless?向云服务器上传我项目?还是像一种框架,用来写代码?用了它我可以得到什么?性能的提升?效率的提高?成本的降低? 首先,我们以一个常见的 Web 服务为例: 在这个图中,服务器中可能涉及路由规则、鉴权逻辑以及其他各类复杂的业务代码,同时,开发团队要付出很大的精力在这个服务器的运维上面,包括客户量突然增多时是否需要扩容服务器;服务器上的脚本,业务代码等是否还在健康运行;是否有黑客在不断地对服务器发起攻击;也就是说,当我们把这个思路切换到 Serverless 的逻辑之后,上图就变成了这样: 可以认为,当客户端和数据库未发生变化的前提下,服务器变化巨大,之前需要开发团队维护的路由模块以及鉴权模块都将接入服务商提供的 API 网关系统以及鉴权系统,开发团队无须再维护这两部分的业务代码,只需要持续维护相关规则即可。同时业务代码也被拆分成了函数粒度,不同函数表示不同的功能。同时,在这个结构下,我们已经看不到服务器的存在,是因为 Serverless 的目的是让使用者只关注自己的业务逻辑即可,所以一部分安全问题、资源调度问题(例如用户量暴增、如何实现自动扩容等)全都交给云厂商负责,并且相对于传统项目而言,传统项目无论是否有用户访问,服务都在运行中,都是有成本支出,而 Serverless 而言,只有在用户发起请求时,函数才会被激活并且执行,按量收费,相对来说,可以在有流量的时候才有支持,没有流量的时候就没有支出,成本会进一步降低。 通过分析和描述,不难看出,Serverless 架构相对于传统的开发模式有什么区别。但是问题来了,很多工作都不需要我们做了,都交给云厂商做了,那么我们做什么? 使用 Serverless 架构,用户不需要自己维护服务器,也不需要自己操心服务器的各种性能指标和资源利用率,而是可以让用户付出更多的时间和精力去关心应用程序本身的状态和逻辑。同时 Serverless 应用本身的部署十分容易,我们只要上传基本的代码,例如 Python 程序只需要上传其逻辑与依赖包,C/C++、Go 等语言只需上传其二进制文件,Java 只需要上传其 Jar 包等即可,同时不需使用 Puppet、Chef、Ansible 或 Docker 来进行配置管理,大大降低了运维成本。对于运维来说,Serverless 架构也不再需要监控底层的数据,例如不再需要监控磁盘使用量、CPU 使用率等,可以更加专注的将监控目光放到监控应用程序本身的度量。同时在 Serverless 架构上,运维人员的工作角色会有所转变,部署将变得更加自动化,监控将更加面向应用程序本身。 总而言之,Serverless 是在传统容器技术和服务网格上发展起来,它更多指的是后端服务与函数服务的结合,对于开发者而言,会更多关注在函数服务商,让使用者只关注自己的业务逻辑即可。Serverless 是云计算发展到一定阶段的必然产物,云计算作为普惠科技,发展到最后一定是绿色科技(最大程度利用资源,减少空闲资源浪费),大众科技(成本低,包括学习成本及使用成本)的产品,而 Serverless 将很好的诠释这些!Serverless 架构被称为是“真正实现了当初云计算的目标”,这种说法虽然有些夸张,但是也从另一方面表现出了大家对 Serverless 架构的期盼和信心,自 2012 年被提出至今,Serverless 架构也是经历了 7 年多的时间,正在逐渐的走向成熟。 入门 Serverless 说起 Serverless,就不得不说 BaaS 和 FaaS,BaaS 服务更多是云厂商给我们提供 / 维护,所以开发者精力可以更多放在 FaaS 层面,或者说是在函数计算层面。 接下来,我们来体验一下 Serverless。以腾讯云为例,我们通过腾讯云控制台,选择 Serverless 分类下的云函数: 接下来就可以看到 Serverless 中的一部分:函数计算部分。此时,我们可以新建一个函数,进行基本的测试,体验一下 Serverless 下的 Hello World 和我们传统的 Hello World 有什么不同。 新建函数: 选择运行时(就是我们要用的编程语言): 进行代码的编写: 点击完成,即可保存代码 进行代码测试: 可以看到测试结果: 至此,我们完成了一个函数的基本编写,但是仔细想一下:貌似和一些在线编程工具差不多,可以在线编写代码、运行。BaaS 体现在了哪里?体现在提供了运行环境?除了写了一个 hello world,我还能干什么? 接下来,我们进行触发器的体验。所谓的触发器,是指我们的函数一般情况下都是 " 休息 " 的,只有在一个 " 东西触碰它 ",“激活它”,才会起来干活。刚刚我们是怎么让函数 " 起来工作的 "?是通过屏幕上的 " 测试按钮 ",所以说这也算是一个触发器。那么除了这个触发器,还有那些? 可以看到,目前腾讯云提供给我们的触发器包括: 定时触发器 (顾名思义,就是定好时间进行函数的触发,例如说每天几点触发一次,或者说每隔多久触发一次,这类操作适合我们做定时任务,例如进行数据采集 / 数据聚合,消息推送等。) COS 触发器 我们可能会将文件存储到文件系统,在传统的云主机中,我们可以存到机器本身,但是 Serverless 架构下,由于函数是无状态的,所以我们不能做持久化,那么就需要一个外部的媒体," 对象存储 " 就是我们常用的持久化文件产品,可以将一些文件存储在上面,例如图片、文档、可执行程序…,同时也可以通过存入到上面一个文件,来触发我们的函数。例如当有图片上传到对象存储中,函数计算会下载这个图片,进行图片压缩和水印等处理。 CMQ 主题订阅触发器 CMQ 主题订阅是指,当我们 CMQ 中有队列存在,就可以将内容发给云函数,云函数来进行消费处理。 Ckafka 触发 与上面说的 CMQ 主题订阅触发器基本一样,只不过这个是 Ckafka。当 Ckafka 中消息出现(可以是每条触发也可以是最多多少条触发),会让函数 " 起来工作 ",进行数据处理、完成消费。 API 网关触发器 是和函数关系非常紧密的一个服务。通过 API 网关触发,可以让函数具备被访问能力。什么叫做被访问呢?就是说可以通过浏览器 / 接口直接使用,所以 API 网关触发器和云函数结合通常可以作网站、后台服务等。 此时,我们可以建立一个 API 网关触发器,看看函数和 API 网关结合所带来的有趣碰撞: 初探 API 网关与函数 我们新建一个 API 网关服务: 创建完成,系统会给我们分配一个地址: 通过浏览器打开这个地址: 这时,我们就成功的搭建了一个 Web 服务,后台会展示 Hello World ,如果是传统开发条件下,做一个这样的页面,需要做哪些工作? 使用框架开发一个 Hello World 购买服务器,并配置服务器的环境 将本地开发好的项目上传到服务器中 购买域名 / 使用服务器 IP,绑定我们的项目 这个过程可能涉及到的有常用的 Web 框架(例如 Django,Spring,Express…),服务器的软件(Nginx,Apache,Tomcat…)等等,甚至我们还要考虑网站的流量有多大,买多大内存的机器,启动多少进程,多少线程,还要想办法对服务器进行各种优化。 但我们刚刚做的操作只有: 建立函数 增加 API 网关触发器 其余的一切操作都不用我们关心,我们可以将更多的精力放在了 "Coding"。 用函数和 API 网关做点有趣的 在生产生活中,我们经常需要获取 IP 地址进行某些工作,例如我之前做了一个网站,这个网站的用户签名体系包括了用户的 IP,而客户端想获得用户 IP 是一个比较复杂的过程。一般情况下是需要通过访问服务端的获取 IP 接口来获得客户端对应的 IP 地址。那么通过函数计算和 API 网关,我们应该怎么做呢? 刚才说到了触发器,每种触发器都会和函数有一个规约,我给你一种什么样的格式数据,通过函数下面的测试模板可以看到: 通过这里,可以看到 API 网关和函数约定的一个结构: { "requestContext": { "serviceId": "service-f94sy04v", "path": "/test/{path}", "httpMethod": "POST", "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", "identity": { "secretId": "abdcdxxxxxxxsdfs" }, "sourceIp": "10.0.2.14", "stage": "release" }, "headers": { "Accept-Language": "en-US,en,cn", "Accept": "text/html,application/xml,application/json", "Host": "service-3ei3tii4-251000691.ap-guangzhou.apigateway.myqloud.com", "User-Agent": "User Agent String" }, "body": "{\"test\":\"body\"}", "pathParameters": { "path": "value" }, "queryStringParameters": { "foo": "bar" }, "headerParameters":{ "Refer": "10.0.2.14" }, "stageVariables": { "stage": "release" }, "path": "/test/value", "queryString": { "foo" : "bar", "bob" : "alice" }, "httpMethod": "POST" } 同时,函数会将这个结构作为入参之一传递给开发人员,例如腾讯云将这个参数命名为 event ,也就是说,开发者可以通过函数入口的 event 参数进行 API 网关相关内容的解析。 那么什么是函数的入口呢? 入口函数实际上就是用户代码中的文件名 + 方法名,这里面默认设定就是 index 文件中的 main_handler 方法,可以看到 main_handler 方法,确实有一个参数是 event,这个参数就是触发器传递过来的数据结构。另外一个 context 参数是上下文,用户对上下文内容的处理,例如上游资源产生的 RequestId、环境信息、密钥信息等。 通过上面的数据接口,可以看到在 requestContext 中 sourceIp,是用户的 IP 地址,那么我们是否就可以把这个 IP 直接返回给用户,实现 IP 查询功能呢? # -*- coding: utf8 -*- import json def main_handler(event, context): return({"ip": event['requestContext']['sourceIp']}) 通过 4 行代码编写之后,我们绑定 API 网关,并且通过浏览器访问可以看到: 是的,这样一个功能,只需要 4 行代码就可以搞定。 再说说 Serverless 刚刚我们已经入门了云函数,对云函数也有了一个初步的了解了,那么接下来,我们说说 Serverless 架构有哪些优点和缺点。 优点 弹性伸缩 传统意义上,一台服务器能接受多大的流量,峰值是多少,是需要我们进行评估的,同时后期也要不断维护和更新数据的。但是在 Serverless 架构下,用户不需要考虑这个问题,云厂商将会为用户实现弹性伸缩的能力。当平台接收到第一个触发函数的事件时,将启动容器来运行你的代码。如果此时收到了新的事件,而第一个容器仍在处理上一个事件,平台将启动第二个代码实例来处理第二个事件。SCF 的这种自动零管理水平缩放,将持续到有足够的代码实例来处理所有的工作负载。当并发出现的时候,云厂商会启动多个容器来应对 " 流量洪峰 ",相对于传统服务器来说,在这一层面上,Serverless 架构或者说云函数真的是很方便了。 按量付费 按量付费是 Serverless 架构的一个优点,传统服务器,无论是否有流量,我们都要进行成本支出,并且服务器配置还要按照某个时间段最大流量来进行配置,所以支出情况实际上是不合理的。但是函数计算实际上是按量收费,而且相对来说价格很低,尤其对不同时间段资源消耗峰值低谷有较大差距的项目而言,是真的很棒。 缺点 冷启动 说到 Serverless 架构的缺点就不得不说冷启动问题,冷启动无论是 AWS 还是 Google 还是腾讯云、阿里云,都是普遍存在的。一般情况下来说,冷启动就是函数在 " 睡觉 ",突然有一个触发的过程,后台拉起容器、下载代码、启动进程、触发入口方法的一个过程,所以一般情况下,容器在首次启动的时候都会有冷启动,通过上图可以看到,函数冷启动可能达到几百毫秒甚至数秒,这对一些业务可能是致命打击,当然各个云厂商也在努力通过各种策略、方案降低冷启动率。 调试困难 云函数的另一个缺点是调试困难,由于它提供给我们的是一个函数运行的容器,而且很多基本业务又是和厂商绑定的,这就导致我们调试困难。例如,我们要调试一个函数,本来可以通过模拟一些触发器情况进行调试,但是,如果函数中涉及到了一些内网资源,例如与 redis 相关,只能通过 vpc 访问的资源,那么这个时候进行本地调试困难度就会成倍增加,在线调试又可能因为日志输出过慢,导致调试整体体验极差。 总结 云计算的发展,Serverless 是一个必然的产物。Serverless 作为一个新技术或者说是一个新架构,很难通过一篇文章进行描述清楚,其优点和缺点都不只是上文中描述的那两个,我们只是挑了比较典型的列出了而已。Serverless 在使用的时候也会有很多坑,有的时候真的是从入门到放弃,有的时候也会觉得真的很方便,又从放弃到入坑,但是无论怎么说,作为一个相对来说比较新鲜的事物,Serverless 有更多的领域和价值在等待我们去开发和探索,包括 Serverless 的应用领域、使用经验等。 Serverless 架构被称为是“真正实现了当初云计算的目标”,这种说法虽然有些夸张,但是也从另一方面表现出了大家对 Serverless 架构的期盼和信心,自 2012 年被提出至今,Serverless 架构也是经历了 7 年多时间,正在逐渐的走向成熟。随着容器技术、IoT、5G、区块链等技术的快速发展, 技术上对去中心化、轻量虚拟化、细粒度计算等技术需求愈发强烈,相信未来 Serverless 将在云计算的舞台上大放异彩! Serverless Framework 30 天试用计划 我们诚邀您来体验最便捷的 Serverless 开发和部署方式。在试用期内,相关联的产品及服务均提供免费资源和专业的技术支持,帮助您的业务快速、便捷地实现 Serverless! 详情可查阅: Serverless Framework 试用计划 One More Thing 3 秒你能做什么?喝一口水,看一封邮件,还是 —— 部署一个完整的 Serverless 应用? 复制链接至 PC 浏览器访问: https://serverless.cloud.tencent.com/deploy/express 3 秒极速部署,立即体验史上最快的 Serverless HTTP 实战开发! 传送门: GitHub: github.com/serverless 官网: serverless.com 欢迎访问: Serverless 中文网 ,您可以在 最佳实践 里体验更多关于 Serverless 应用的开发! 推荐阅读: 《Serverless 架构:从原理、设计到项目实战》 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 后疫情时代,企业如何以高效、敏捷的速度在数字化浪潮中成长壮大,从而能有条不紊地应对诸如疫情等突发事件,是CIO必须关心的问题。随着移动互联网的发展和应用云化的普及,微服务已经成为企业应用服务化架构最流行的设计理念,然而企业对推行微服务架构的认知各有不同。 Nebulogy纳比云特邀K2中国研发副总裁焦锟,为您带来 《微服务架构下,流程平台解耦的最佳实践》 的主题分享。赶快报名吧! 直播时间:2020年4月10日(周五)15:00-16:00 直播方式:45分钟左右的直播+互动交流 01 主题简介 本期直播将从微服务架构和传统平台架构的区别讲起,通过企业流程平台建设中面临的问题以及推行微服务架构后带来的改变,向听众介绍微服务架构的优势。进而从流程平台解耦的应用场景为企业推行微服务带来借鉴思路。 02 扫码报名 Nebulogy 品牌介绍 Nebulogy致力于通过云原生理念,帮助企业构建PaaS平台,提高开发资源利用率,满足应用快速上线和迭代需求,助力企业实现真正应用云化、业务互联网化。 网站: http://www. nebulogy.com 邮箱:service@nebulogy.com 电话:400-105-0300
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 本期嘉宾: ZStack资深技术工程师 李朗 本期主题: ZStack VPC网络相关介绍: 1、VPC网络定义 2、VPC防火墙和安全组 3、VPC网络特点和应用场景 4、VPC网络服务及演示 5、VPC 与云路由的区别 6、典型VPC 网络场景案例 7、VPC 网络创建流程 教学现场部分Q&A Q1:安全组作用在物理机上,如果用户私自修改了云主机内网IP地址,安全组规则会发生改变吗?有没有规避用户修改云主机地址的方法? A1:安全组是基于iptables针对云主机的虚拟网卡进行安全策略的配置,不会随着云主机系统内部IP地址的变化而变化。 用户在云主机操作系统内部修改云主机的IP地址,安全组规则不会发生改变; ZStack支持网络防欺诈,可以在云主机详情页或者全局设置开启,开启并重启云主机后,修改云主机IP或者MAC地址后,将无法对外通信。 Q2:请问不同VPC下的云主机,在同一个宿主机上的网络报文是如何隔离的? A2:不同VPC下的云主机在相同的宿主机上可以通过二层隔离,可以通过划分不同的VLAN或者VXLAN进行网络报文隔离。 Q3:组播网络下,如果RP配置在内网云主机上,是否可行?是否会与VPC路由器的分布式冲突? A3:组播RP不建议配置在内网的云主机,一般ZStack VPC路由器去对接物理环境的组播RP路由器并加入到组播组中来提供组播服务。 以上是本期直播的主要内容,欢迎点击链接获取课程视频回放: https://www.zstack.io/plus/view.php?aid=2061 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 作者 | 良名 阿里巴巴技术专家 背景 相信很多人都使用过 start.spring.io 来初始化自己的 Spring Boot 工程,这个工具为开发者提供了丰富的可选组件,并且可以选择多种打包方式,大大方便了开发人员的使用。最近,阿里的 Nacos、Sentinel 也进入 start.spring.io 的选项中,进一步的方便开发者使用阿里云的产品。 但是,生成的工程骨架中,只有组件坐标信息,缺少对应的使用方法和 Demo 代码;于是,开发者还是需要去寻找相关使用教程,或者样例代码;如果找的不对,或者是版本不匹匹配,还需要花费不少时间去排查和解决问题;这些问题都在无形中增加用户的工作量。 我们将对软件工程的抽象层次自上而下进行切分,会得到如下的几个层级:行业、解决方案、应用、功能、组件;明显的, start.spring.io 目前只能提供组件级别的支持。再将组件这层展开,会发现这样一个生命周期:组件引入、组件配置、功能开发、线上运维。start.spring.io 也只实现了“组件引入”这一功能。 我们的目标是**“让阿里云成为广大 Java 开发者最好用的云”**。要实现这个目标,是否可以再向前走几步,在解决“组件引入”问题的基础上,将组件的典型使用方法、样例代码、使用说明也加入到工程中呢? 基于这种思考,我们上线了自己的 bootstrap 站点 start.aliyun.com : https://start.aliyun.com/ 当然,本着不重复造轮子的原则,我们不再构建一套工程生成底层框架,而是使用 Spring Initializr 来实现这部分功能。在此之上专注于增加新特性,实现服务广大开发者的目标。 Spring Initializr: https://github.com/spring-io/initializr 在 start.aliyun.com 中,我们为广大开发者带来了如下便利特性: 为每个组件提供了单独的 DemoCode 和对应的配置样例(本次已发布); 工程内置说明,减少用户查找文档的困难(部分实现); 开发者只需要做减法,而非加法的使用方式(部分实现); 提供多组件集成的解决方案(开发中); 定期跟进 start.spring.io 的更新,方便大家使用到 spring 的最新功能。 start.aliyun.com: https://start.aliyun.com/ 未来,我们还需要再助力开发者这条路上继续发力,不仅仅是做好组件集成的工作,还要需要继续向上支持,提供更多功能、服务、应用层级的快速构建能力。 本文,围绕 spring initializr 框架,以 start.spring.io 为例,全面的给大家介绍如何使用和扩展这个框架,以及背后的运行原理。 使用篇 由于 spring-initializr 提供了灵活的扩展能力,以及丰富的默认实现;其使用方式也是非常的灵活多变;为了便于说明,我们直接通过 start.spring.io ,看看 Spring 自己是怎么使用这套框架的。 1. 基本用法 基本用法的原则,是尽量少写代码,甚至是不写代码。只通过配置就可以实现 initializr 工程的创建。 依赖引入 要使用 spring-initializr ,首先要引入这套框架。很简单,直接依赖 bom 即可: io.spring.initializr initializr-bom 0.9.0.BUILD-SNAPSHOT pom import 有了这个 bom 依赖,我们就不用再关心内部组件的版本等信息了。 一般来说,我们还需要引入具体组件: io.spring.initializr initializr-generator-spring io.spring.initializr initializr-version-resolver io.spring.initializr initializr-web 具体每个子模块的用途,这里列出来,供读者参考: initializr-actuator: 监控诊断的附加信息,这个暂时忽略; initializr-bom: 便于外部使用的bom依赖; initializr-docs: 使用文档; initializr-generator: 核心工程生成库; initializr-generator-spring: 用于生成典型的spring boot工程; initializr-generator-test: 测试框架; initializr-metadata: 项目各个方面的元数据基础结构; initializr-service-sample: 基本使用案例; initializr-version-resolver:版本号解析能力; initializr-web: 提供给三方客户端使用的web入口。 基本配置 完成了框架引入,就需要做一些基础配置了 支持哪些语言:Java、groovy、Kotlin 支持哪些版本:1.8、11、13 支持哪些打包方式:jar、war 将这些信息全部配置到 application.yml 文件中,如下: initializr: packagings: - name: Jar id: jar default: true - name: War id: war default: false javaVersions: - id: 13 default: false - id: 11 default: false - id: 1.8 name: 8 default: true languages: - name: Java id: java default: true - name: Kotlin id: kotlin default: false - name: Groovy id: groovy default: false 其中 name 是可选的, id 是必填的。 每个配置项下,可以有一个默认值(将 default 这是为 true 即可),除了这些基本配置,我们还需要定义可以支持的项目类型: initializr: types: - name: Maven Project id: maven-project description: Generate a Maven based project archive. tags: build: maven format: project default: true action: /starter.zip - name: Maven POM id: maven-build description: Generate a Maven pom.xml. tags: build: maven format: build default: false action: /pom.xml - name: Gradle Project id: gradle-project description: Generate a Gradle based project archive. tags: build: gradle format: project default: false action: /starter.zip - name: Gradle Config id: gradle-build description: Generate a Gradle build file. tags: build: gradle format: build default: false action: /build.gradle 默认情况下, initializr 已经支持 4 种项目类型: /pom.xml 生成一个 Maven 的 pom.xml 配置文件 /build.gradle 生成 Gradle 的配置文件 /starter.zip 生成 zip 方式压缩的工程文件 /starter.tgz 生成以 tgz 方式压缩的工程文件 通过 tags 标签,我们可以定义不同配型的编译方式 (build) 和打包格式(format)。 配置基本依赖 完成了基本配置以后,就可以配置可选的依赖组件了。 依赖配置以 dependency 为 key ,同样配置在 application.yml 的 initializr 下面,这里给出一个简单的样例: initializr: dependencies: - name: Web content: - name: Web id: web description: Full-stack web development with Tomcat and Spring MVC - name: Developer Tools content: - name: Spring Boot DevTools id: devtools groupId: org.springframework.boot artifactId: spring-boot-devtools description: Provides fast application restarts, LiveReload, and configurations for enhanced development experience. - name: Lombok id: lombok groupId: org.projectlombok artifactId: lombok description: Java annotation library which helps to reduce boilerplate code. dependencies 下定义分组。分组的作用是便于展示和快速查找,所以不需要 id ,只需要 name 信息;每个分组的 content 是分组的具体内容,也就是这个分组下的组件定义;支持以列表形式定义多个;另外,每个分组都可以设置当前分组内组件公用的配置信息。 每一依赖,包含如下的基本信息: id:组件的唯一标识符 groupId & artifactId:组件的坐标 name:显示名称 description:描述信息,主要用于展示用途 version:组件版本 关于 groupId & artifactId:如果设置了坐标,生成的项目里会使用这里的坐标定位组件;但是如果没有设置坐标,框架会认为这是一个标准的 spring-boot 组件,自动添加 spring-boot-starter-{id} 作为生成的依赖坐标。 关于 version:如果直接在组件上设置版本信息,框架会直接使用这个值作为组件依赖的版本;但是很多时候,组件的版本会受到 spring-boot 版本的影响,此时就需要对版本做特殊的定义 & 管理。 配置依赖版本管理 这里需要先了解一下版本命名规则:一个典型的版本,一般包含如下 4 个信息:大版本、小版本、修正版本、版本限定符。 版本范围有一个上界和下界,可以方括号 [] 或者圆括号 () 表示。方括号代表上下界的闭区间,圆括号代表上下界的开区间。 例如: “[1.1.6.RELEASE,1.3.0.M1)”代表所有从 1.1.6.RELEASE 到 1.3.0.M1 之间所有的版本(包含 1.1.6.RELEASE ,但不包含 1.3.0.M1 )。 同时,可以使用单一版本号作为版本范围,例如 “1.2.0.RELEASE”。单一版本号的版本范围代表“从这个版本以及之后的所有版本”。 如果需要使用“最新的 Release 版本”的概念,可以使用一个字母 x 代表具体的版本号。 例如, 1.4.x.BUILD-SNAPSHOT 代表 1.4.x 的最新快照版本。 再比如:如果需要表达,从 1.1.0.RELEASE 到 1.3.x 之间的所有版本,可以用[1.1.0.RELEASE,1.3.x.RELEASE]来表达。 另外,版本限定符也是有顺序的(升序): M:里程碑版本 RC:发布候选版本 RELEASE:发布版本 BUILD-SNAPSHOT:为开发构建的快照版本 所以快照版本是所有限定符里优先级最高的。假设某个组件需要 Spring Boot 的最新版本,可以使用 1.5.x.BUILD-SNAPSHOT (假设 1.5 版是 Spring Boot 的最新版本)。 最后,版本范围中讨论的版本,指的都是 Spring Boot的版本,而不是组件自己的版本。 前面介绍了,可以使用 version 属性定义组件的具体版本号;但是,如果组件版本与Spring Boot 的版本存在关联关系,就需要使用 compatibilityRange 来配置依赖的版本范围。 compatibilityRange 可以定义在两个地方: 直接定义在组件(或 Bom )上 这种定义方式,代表组件只支持 Spring Boot 的某一个版本范围,例如下面的配置: initializr: dependencies: - name: Stuff content: - name: Foo id: foo ... compatibilityRange: 1.2.0.M1 - name: Bar id: bar ... compatibilityRange: "[1.5.0.RC1,2.0.0.M1)" Foo 可以支持 Spring boot 1.2.0 之后的所有版本;而Bar只能支持 Spring Boot 1.5.0 到 2.0.0 之间的版本,且不包含 2.0.0 ; 定义在组件的 mappgin 属性下 可以支持在 Spring Boot 不同版本之下对组件做不同的设置(可以重置组件部分或者是所有的属性),下面的例子中对 artifactId 做了特殊定义: initializr: dependencies: - name: Stuff content: - name: Foo id: foo groupId: org.acme.foo artifactId: foo-spring-boot-starter compatibilityRange: 1.3.0.RELEASE mappings: - compatibilityRange: "[1.3.0.RELEASE,1.3.x.RELEASE]" artifactId: foo-starter - compatibilityRange: "1.4.0.RELEASE" 这个例子中, foo 在 Spring Boot 的 1.3 使用 foo-starter 作为坐标的 artifactId ;在 1.4.0.RELEASE 以及之后的版本中,还是使用 foo-spring-boot-starter 作为 artifactId 的值; **使用 Bom 管理版本:**有时候,需要使用 Bom 的方式管理组件版本;此时不需要对组件单独设置版本号。 要使用 Bom ,首先要配置 Bom 定义: initializr: env: boms: my-api-bom: groupId: org.acme artifactId: my-api-dependencies version: 1.0.0.RELEASE repositories: my-api-repo-1 注意:Bom 信息,定义在 initializr.env.boms下面。 其属性和依赖组件基本一致,都是坐标、版本;同时, Bom 也支持版本范围管理。 完成了 Bom 的定义,就需要在组件中引用 Bom : initializr: dependencies: - name: Other content: - name: My API id : my-api groupId: org.acme artifactId: my-api bom: my-api-bom 一旦用户选择了 my-api 组件,框架会自动为生成的项目添加了 my-api-dependencies 的 Bom 依赖; 2. 高级定制 启用缓存 如果你启动过 start.spring.io 项目,你会在日志里发现这样的输出 “Fetching boot metadata from spring.io/project_metadata/spring-boot” 为了避免过于频繁的检查 Spring Boot 版本,官方是建议配合缓存一起使用。 首先需要引入缓存框架: javax.cache cache-api org.ehcache ehcache 然后,在 SpringBootApplication 类上增加 @EnableCaching 注解: 如果需要自己定义缓存,可以调整如下缓存配置: **增加 Demo代码:**由于不同的组件有不同的功能,如果需要为项目增加 Demo 代码。 **为不同的组件增加独立配置:**还记得原理篇中提到的 spring.factories 吗?对,我们要增加自己的配置项,就需要在这里增加针对不同组件样例代码的扩展入口。 io.spring.initializr.generator.project.ProjectGenerationConfiguration=\ com.alibaba.alicloud.initializr.extension.dependency.springboot.SpringCloudProjectGenerationConfiguration 在 SpringCloudProjectGenerationConfiguration 中,我们通过 ConditionalOnRequestedDependency 注解来识别不同组件: @ProjectGenerationConfiguration public class SpringCloudAlibabaProjectGenerationConfiguration { private final InitializrMetadata metadata; private final ProjectDescription description; private final IndentingWriterFactory indentingWriterFactory; private final TemplateRenderer templateRenderer; public SpringCloudAlibabaProjectGenerationConfiguration(InitializrMetadata metadata, ProjectDescription description, IndentingWriterFactory indentingWriterFactory, TemplateRenderer templateRenderer) { this.metadata = metadata; this.description = description; this.indentingWriterFactory = indentingWriterFactory; this.templateRenderer = templateRenderer; } @Bean @ConditionalOnRequestedDependency("sca-oss") public OSSDemoCodeContributor ossContributor() { return new OSSDemoCodeContributor(description, templateRenderer); } ...... } 上面的代码,会在选择了 sca-oss 组件时,创建一个 OSSDemoCodeContributor 用于对应 Demo 代码的生成。 **生成具体的 Demo 代码:**继续以 OSSDemoCodeContributor 为例,它是一个 ProjectContributor ,会在项目文件空间创建完成了调用。我们需要为这个 Contributor 在实例化时增加生成过程中需要的元数据信息,例如 ProjectDescription 。 代码生成过程,比较简单,可以直接复用框架中就提供的 mstache 模板引擎。 我们直接将 Demo 代码,以模板的形式,放置在 resources 文件夹之下: 然后,我们再通过模板引擎,解析这些模板文件,再拷贝到项目目录下即可: private void writeCodeFile(TemplateRenderer templateRenderer, Language langeuage, Map params, Path path, String temp) throws IOException { ...... Path pkgPath = 生成包路径 Path filePath = 成成代码文件路径 // 渲染模板 String code = templateRenderer.render(temp, params); // demo 文件写入 Files.createDirectories(pkgPath); Files.write(filePath, code.getBytes("UTF-8")); } 除了模板代码以外,我们通常还需要在 applicatioin.properties 文件写入模块的配置信息。 这里,我们依然可以使用代码生成的方式:创建模板、解析模板,追加文件的方式来实现。具体代码这里就不贴了,读者可以自己发挥。 原理篇 原理篇,主要介绍 spring.initializr 是如何实现项目工程构建的,以及作为一个框架,如何提供丰富的扩展能力的。 在原理篇,我们将 initializr 的执行分为两个阶段:启动阶段和生成阶段。 启动阶段:启动应用,加载配置,扩展信息初始化; 生成阶段:一个项目生成,从收到请求,到返回内容的完整流程。 1. 启动阶段 再开始启动流程之前,先要看一下 initializr 的扩展体系。 整个架构大量使用了 spring 的 spi 机制,我们来看一下一共有哪些 spring.factories : initializr-generator/src/main/resources/META-INF/spring.factories initializr-generator-spring/src/main/resources/META-INF/spring.factories initializr-web/src/main/resources/META-INF/spring.factories initializr-actuator/src/main/resources/META-INF/spring.factories start-site/src/main/resources/META-INF/spring.factories 其中只有一个在 start.spring.io 中,其他 4 个都在 initializr 工程中(各 spring.factories 的具体内容见参考资料)。 不过要注意,这些 spring.factories 定义,仅仅代表了各个 SPI 有哪些扩展。不同spi的实现创建和使用完全是在不同的阶段进行的。 在应用启动阶段,其实只有一个 spi 会被加载(暂不考虑 actuator):io.spring.initializr.web.autoconfigure.InitializrAutoConfiguration 。 @Configuration @EnableConfigurationProperties(InitializrProperties.class) public class InitializrAutoConfiguration { @Bean @ConditionalOnMissingBean public ProjectDirectoryFactory projectDirectoryFactory() @Bean @ConditionalOnMissingBean public IndentingWriterFactory indentingWriterFactory() @Bean @ConditionalOnMissingBean(TemplateRenderer.class) public MustacheTemplateRenderer templateRenderer(Environment environment, ObjectProvider cacheManager) @Bean @ConditionalOnMissingBean public InitializrMetadataUpdateStrategy initializrMetadataUpdateStrategy(RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper) @Bean @ConditionalOnMissingBean(InitializrMetadataProvider.class) public InitializrMetadataProvider initializrMetadataProvider(InitializrProperties properties, InitializrMetadataUpdateStrategy initializrMetadataUpdateStrategy) @Bean @ConditionalOnMissingBean public DependencyMetadataProvider dependencyMetadataProvider() @Configuration @ConditionalOnWebApplication static class InitializrWebConfiguration { @Bean InitializrWebConfig initializrWebConfig() @Bean @ConditionalOnMissingBean ProjectGenerationController projectGenerationController( InitializrMetadataProvider metadataProvider, ApplicationContext applicationContext) @Bean @ConditionalOnMissingBean ProjectMetadataController projectMetadataController(InitializrMetadataProvider metadataProvider, DependencyMetadataProvider dependencyMetadataProvider) @Bean @ConditionalOnMissingBean CommandLineMetadataController commandLineMetadataController(InitializrMetadataProvider metadataProvider, TemplateRenderer templateRenderer) @Bean @ConditionalOnMissingBean SpringCliDistributionController cliDistributionController(InitializrMetadataProvider metadataProvider) } } 这里会做如下几件事情: 初始化元数据 Provider 创建模板引擎 创建目录、缩进工厂 初始化 web 配置 创建 spring mvc 的 web 入口 各种 ProjectGenerationController 其中最关键的元数据加载部分,使用了 EnableConfigurationProperties 注解,将 spring 环境中的配置项写到 InitializrProperties 上: 在 application.yml 文件中,可以找到如下的配置信息,这里就是实际的项目依赖关系元数据的配置存储点: 整体来看,启动阶段的动作还是比较简单的,这也是为什么 start.spring.io 启动只需要数秒的原因。 更多的逻辑,都被留在了工程生成阶段。 2. 生成阶段 生成阶段,spring-initializr 使用了一个很有意思的实现方式:initializr 框架会为每一次项目生成,创建一个独立的 context 用于存放生成流程中需要使用到的各种 bean 。 先来一张时序图: 蓝色的类,是在应用启动阶段就完成了创建和数据填充;其生命周期和整个应用一致; 黄色的类,会在具体的项目构建过程中生成;其生命周期在一次项目生成流程之内结束。 从上面的时序图中可以看出:一个典型的创建行为,通常从 ProjectGenerationController收到web端的创建请求开始,通过 ProjectGenerationInvoker 这个中间层转换,最终进入 ProjectGenerator 的核心构建流程。 主干流程 下图,是 ProjectGenerator 的核心构建流程: 106 行,通过 contextFactory 构建了一个新的 ProjectGenerationContext 。 看一下这个context的继承关系,原来于spring提供的AnnotationConfigApplicationContext 。 再结合 110 行的 refresh() 方法,是不是发现了什么?就是 spring 的 ApplicationContext 的刷新流程。 107 行的 resolve 方法,向 context 中注册了一个 ProjectDescription的Provider,代码如下: 由于注册的是 Provider ,所以这段逻辑会在 Context 执行 refresh 时运行。 这里的 ProjectDescriptionCustomizer 就是针对 ProjectDescription 的扩展,用于对用户传入的 ProjectDescription 做调整。这里主要是一些强制依赖关系的调整,例如语言版本等。 这时候再看 108 行,这里向 Context 注册一个 Configuration 。 那么这个 Configuration 包含了什么内容呢?一起来看下面这段代码: ProjectGenerationConfiguration!!!前面提到的 spring.factories 中有很多这个 SPI 的实现(参见参考资料)。 原来,initializr 的整个扩展体系,在这里才开始创建实例; ProjectGenerator 的 109 行,对一个 consumer 做了 accept 操作;其实就是调用了下面的代码: 这里通过 setParent 将应用的主上下文设置为这次 ProjectGenerationContext 的父节点。 并且向这次 ProjectGenerationContext 中注册了元数据对象。 最后,在 ProjectGenerator 的 112 行,调用了 projectAssetGenerator 的 generate 方法,实现如下: 通过上面的代码可以发现,这里对实际的工程构建工作,其实就是很多的 ProjectContributor 共同叠加; 至此,主干流程已经结束了。 我们可以发现,在主干流程中,没有做任何写文件的操作(只创建了根文件夹);它仅仅是定义了一套数据加载、扩展加载的机制与流程,将所有的具体实现都作为扩展的一部分。 扩展流程 spring-initializr 提供了 2 种主要扩展途径:ProjectContributor 和 xxxxxCustomizer。 从方法签名就可以看出,入参只有一个项目的根路径,其职责就是向这个路径下些人项目文件。这个扩展点非常的灵活,几乎可以支持任何的代码、配置文件写入工作。 实现过程中,可以通过 ProjectGenerationContext 获取相关依赖,然后通过自定义逻辑完成文件生成工作。 下面是 initializr 和 start.spring.io 提供的 ProjectContributor 实现: 拿几个主要的实现看看: MavenBuildProjectContributor:写入 maven 项目 pom.xml 文件; WebFoldersContributor:创建 web 项目的资源文件夹; ApplicationPropertiesContributor:写入 application.properties 文件; MainSourceCodeProjectContributor:写入应用入口类 xxxApplication.java 文件; HelpDocumentProjectContributor:写入帮助文档 HELP.md 文件。 相对于 ProjectContributor,xxxxxCustomizer 不是一个统一的接口,我把他理解为一种感念和与之对应的命名习惯;每个 Customizer 都有自己明确的名字,同时也对应了明确的触发逻辑和职责边界。 下面列出框架提供的 Customizer 的说明: MainApplicationTypeCustomizer:自定义 MainApplication 类; MainCompilationUnitCustomizer:自定义 MainApplication 编译单元; MainSourceCodeCustomizer:自定义 MainApplication 源码; BuildCustomizer:自定义项目构建工具的配置内容; GitIgnoreCustomizer:自定义项目的 .gitignore 文件; HelpDocumentCustomizer:自定义项目的帮助文档; InitializrMetadataCustomizer:自定义项目初始化配置元数据;这个 Customizer 比较特殊,框架会在首次加载元数据配置时调用; ProjectDescriptionCustomizer:自定义 ProjectDescription ;即在生成项目文件之前,允许调整项目描述信息; ServletInitializerCustomizer:自定义 web 应用在类上的配置内容; TestApplicationTypeCustomizer:自定义测试 Application 类; TestSourceCodeCustomizer:自定义测试 Application 类的源码。 参考资料 1. 相关链接 initializr 说明文档 https://docs.spring.io/initializr/docs/current-SNAPSHOT/reference/html/ spring-initializr 项目地址 https://github.com/spring-io/initializr start.spring.io 项目地址 https://github.com/spring-io/start.spring.io 2. spring.factories 明细 initializr-generator/src/main/resources/META-INF/spring.factoriesio.spring.initializr.generator.buildsystem.BuildSystemFactory=\ io.spring.initializr.generator.buildsystem.gradle.GradleBuildSystemFactory,\ io.spring.initializr.generator.buildsystem.maven.MavenBuildSystemFactory io.spring.initializr.generator.language.LanguageFactory=\ io.spring.initializr.generator.language.groovy.GroovyLanguageFactory,\ io.spring.initializr.generator.language.java.JavaLanguageFactory,\ io.spring.initializr.generator.language.kotlin.KotlinLanguageFactory io.spring.initializr.generator.packaging.PackagingFactory=\ io.spring.initializr.generator.packaging.jar.JarPackagingFactory,\ io.spring.initializr.generator.packaging.war.WarPackagingFactory initializr-generator-spring/src/main/resources/META-INF/spring.factories: io.spring.initializr.generator.project.ProjectGenerationConfiguration=\ io.spring.initializr.generator.spring.build.BuildProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.build.gradle.GradleProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.build.maven.MavenProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.code.SourceCodeProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.code.groovy.GroovyProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.code.java.JavaProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.code.kotlin.KotlinProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.configuration.ApplicationConfigurationProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.documentation.HelpDocumentProjectGenerationConfiguration,\ io.spring.initializr.generator.spring.scm.git.GitProjectGenerationConfiguration initializr-web/src/main/resources/META-INF/spring.factories: org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ io.spring.initializr.web.autoconfigure.InitializrAutoConfiguration org.springframework.boot.env.EnvironmentPostProcessor=\ io.spring.initializr.web.autoconfigure.CloudfoundryEnvironmentPostProcessor initializr-actuator/src/main/resources/META-INF/spring.factories: org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ io.spring.initializr.actuate.autoconfigure.InitializrActuatorEndpointsAutoConfiguration,\ io.spring.initializr.actuate.autoconfigure.InitializrStatsAutoConfiguration start-site/src/main/resources/META-INF/spring.factories: io.spring.initializr.generator.project.ProjectGenerationConfiguration=\ io.spring.start.site.extension.build.gradle.GradleProjectGenerationConfiguration,\ io.spring.start.site.extension.build.maven.MavenProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.DependencyProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springamqp.SpringAmqpProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springboot.SpringBootProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springcloud.SpringCloudProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springdata.SpringDataProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springintegration.SpringIntegrationProjectGenerationConfiguration,\ io.spring.start.site.extension.dependency.springrestdocs.SpringRestDocsProjectGenerationConfiguration,\ io.spring.start.site.extension.description.DescriptionProjectGenerationConfiguration,\ io.spring.start.site.extension.code.kotin.KotlinProjectGenerationConfiguration 作者信息 : 陈曦(花名:良名)阿里巴巴技术专家。目前在应用容器&服务框架团队,Spring Cloud Alibaba 项目成员,致力于将阿里云打造为Java开发者最好用的云。2014 年加入 B2B,多次参与 双11、618 作战。 “ 阿里巴巴云原生 关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。” / 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> Linux Namespace 是 Linux 提供的一种内核级别环境隔离的方法。用官方的话来说,Linux Namespace 将全局系统资源封装在一个抽象中,从而使 namespace 内的进程认为自己具有独立的资源实例。这项技术本来没有掀起多大的波澜,是容器技术的崛起让他重新引起了大家的注意。 Linux Namespace 有如下 6 个种类: 分类 系统调用参数 相关内核版本 | Mount namespaces | CLONE_NEWNS | Linux 2.4.19 UTS namespaces | CLONE_NEWUTS | Linux 2.6.19 |
---|
IPC namespaces | CLONE_NEWIPC | Linux 2.6.19 | PID namespaces | CLONE_NEWPID | Linux 2.6.24 Network namespaces | User namespaces CLONE_NEWNET | CLONE_NEWUSER 始于Linux 2.6.24 完成于 Linux 2.6.29 | 始于 Linux 2.6.23 完成于 Linux 3.8 | namespace 的 API 由三个系统调用和一系列 /proc 文件组成,本文将会详细介绍这些系统调用和 /proc 文件。为了指定要操作的 namespace 类型,需要在系统调用的 flag 中通过常量 CLONE_NEW* 指定(包括 CLONE_NEWIPC , CLONE_NEWNS , CLONE_NEWNET , CLONE_NEWPID , CLONE_NEWUSER 和 `CLONE_NEWUTS),可以指定多个常量,通过 | (位或)操作来实现。 简单描述一下三个系统调用的功能: clone() : 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述系统调用参数达到隔离的目的。 unshare() : 使某进程脱离某个 namespace。 setns() : 把某进程加入到某个 namespace。 具体的实现原理请往下看。 1. clone() clone() 的原型如下: int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg); child_func : 传入子进程运行的程序主函数。 child_stack : 传入子进程使用的栈空间。 flags : 表示使用哪些 CLONE_* 标志位。 args : 用于传入用户参数。 clone() 与 fork() 类似,都相当于把当前进程复制了一份,但 clone() 可以更细粒度地控制与子进程共享的资源(其实就是通过 flags 来控制),包括虚拟内存、打开的文件描述符和信号量等等。一旦指定了标志位 CLONE_NEW* ,相对应类型的 namespace 就会被创建,新创建的进程也会成为该 namespace 中的一员。 clone() 的原型并不是最底层的系统调用,而是封装过的,真正的系统调用内核实现函数为 do_fork() ,形式如下: long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) 其中 clone_flags 可以赋值为上面提到的标志。 下面来看一个例子: /* demo_uts_namespaces.c Copyright 2013, Michael Kerrisk Licensed under GNU General Public License v2 or later Demonstrate the operation of UTS namespaces. */ #define _GNU_SOURCE #include #include #include #include #include #include #include /* A simple error-handling function: print an error message based on the value in 'errno' and terminate the calling process */ #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) static int /* Start function for cloned child */ childFunc(void *arg) { struct utsname uts; /* 在新的 UTS namespace 中修改主机名 */ if (sethostname(arg, strlen(arg)) == -1) errExit("sethostname"); /* 获取并显示主机名 */ if (uname(&uts) == -1) errExit("uname"); printf("uts.nodename in child: %s\n", uts.nodename); /* Keep the namespace open for a while, by sleeping. This allows some experimentation--for example, another process might join the namespace. */ sleep(100); return 0; /* Terminates child */ } /* 定义一个给 clone 用的栈,栈大小1M */ #define STACK_SIZE (1024 * 1024) static char child_stack[STACK_SIZE]; int main(int argc, char *argv[]) { pid_t child_pid; struct utsname uts; if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); exit(EXIT_FAILURE); } /* 调用 clone 函数创建一个新的 UTS namespace,其中传出一个函数,还有一个栈空间(为什么传尾指针,因为栈是反着的); 新的进程将在用户定义的函数 childFunc() 中执行 */ child_pid = clone(childFunc, child_stack + STACK_SIZE, /* 因为栈是反着的, 所以传尾指针 */ CLONE_NEWUTS | SIGCHLD, argv[1]); if (child_pid == -1) errExit("clone"); printf("PID of child created by clone() is %ld\n", (long) child_pid); /* Parent falls through to here */ sleep(1); /* 给子进程预留一定的时间来改变主机名 */ /* 显示当前 UTS namespace 中的主机名,和 子进程所在的 UTS namespace 中的主机名不同 */ if (uname(&uts) == -1) errExit("uname"); printf("uts.nodename in parent: %s\n", uts.nodename); if (waitpid(child_pid, NULL, 0) == -1) /* 等待子进程结束 */ errExit("waitpid"); printf("child has terminated\n"); exit(EXIT_SUCCESS); } 该程序通过标志位 CLONE_NEWUTS 调用 clone() 函数创建一个 UTS namespace。UTS namespace 隔离了两个系统标识符 — 主机名 和 NIS 域名 —它们分别通过 sethostname() 和 setdomainname() 这两个系统调用来设置,并通过系统调用 uname() 来获取。 下面将对程序中的一些关键部分进行解读(为了简单起见,我们将省略其中的错误检查)。 程序运行时后面需要跟上一个命令行参数,它将会创建一个在新的 UTS namespace 中执行的子进程,该子进程会在新的 UTS namespace 中将主机名改为命令行参数中提供的值。 主程序的第一个关键部分是通过系统调用 clone() 来创建子进程: child_pid = clone(childFunc, child_stack + STACK_SIZE, /* Points to start of downwardly growing stack */ CLONE_NEWUTS | SIGCHLD, argv[1]); printf("PID of child created by clone() is %ld\n", (long) child_pid); 子进程将会在用户定义的函数 childFunc() 中开始执行,该函数将会接收 clone() 最后的参数(argv[1])作为自己的参数,并且标志位包含了 CLONE_NEWUTS ,所以子进程会在新创建的 UTS namespace 中执行。 接下来主进程睡眠一段时间,让子进程能够有时间更改其 UTS namespace 中的主机名。然后调用 uname() 来检索当前 UTS namespace 中的主机名,并显示该主机名: sleep(1); /* Give child time to change its hostname */ uname(&uts); printf("uts.nodename in parent: %s\n", uts.nodename); 与此同时,由 clone() 创建的子进程执行的函数 childFunc() 首先将主机名改为命令行参数中提供的值,然后检索并显示修改后的主机名: sethostname(arg, strlen(arg); uname(&uts); printf("uts.nodename in child: %s\n", uts.nodename); 子进程退出之前也睡眠了一段时间,这样可以防止新的 UTS namespace 不会被关闭,让我们能够有机会进行后续的实验。 执行程序,观察父进程和子进程是否处于不同的 UTS namespace 中: $ su # 需要特权才能创建 UTS namespace Password: # uname -n antero # ./demo_uts_namespaces bizarro PID of child created by clone() is 27514 uts.nodename in child: bizarro uts.nodename in parent: antero 除了 User namespace 之外,创建其他的 namespace 都需要特权,更确切地说,是需要相应的 Linux Capabilities ,即 CAP_SYS_ADMIN 。这样就可以避免设置了 SUID(Set User ID on execution)的程序因为主机名不同而做出一些愚蠢的行为。如果对 Linux Capabilities 不是很熟悉,可以参考我之前的文章: Linux Capabilities 入门教程:概念篇 。 2. proc 文件 每个进程都有一个 /proc/PID/ns 目录,其下面的文件依次表示每个 namespace, 例如 user 就表示 user namespace。从 3.8 版本的内核开始,该目录下的每个文件都是一个特殊的符号链接,链接指向 $namespace:[$namespace-inode-number] ,前半部份为 namespace 的名称,后半部份的数字表示这个 namespace 的句柄号。句柄号用来对进程所关联的 namespace 执行某些操作。 $ ls -l /proc/$$/ns # $$ 表示当前所在的 shell 的 PID total 0 lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 ipc -> ipc:[4026531839] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 mnt -> mnt:[4026531840] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 net -> net:[4026531956] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 pid -> pid:[4026531836] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 user -> user:[4026531837] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 uts -> uts:[4026531838] 这些符号链接的用途之一是用来 确认两个不同的进程是否处于同一 namespace 中 。如果两个进程指向的 namespace inode number 相同,就说明他们在同一个 namespace 下,否则就在不同的 namespace 下。这些符号链接指向的文件比较特殊,不能直接访问,事实上指向的文件存放在被称为 nsfs 的文件系统中,该文件系统用户不可见,可以使用系统调用 stat() 在返回的结构体的 st_ino 字段中获取 inode number。在 shell 终端中可以用命令(实际上就是调用了 stat())看到指向文件的 inode 信息: $ stat -L /proc/$$/ns/net File: /proc/3232/ns/net Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: 4h/4d Inode: 4026531956 Links: 1 Access: (0444/-r--r--r--) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-01-17 15:45:23.783304900 +0800 Modify: 2020-01-17 15:45:23.783304900 +0800 Change: 2020-01-17 15:45:23.783304900 +0800 Birth: - 除了上述用途之外,这些符号链接还有其他的用途, 如果我们打开了其中一个文件,那么只要与该文件相关联的文件描述符处于打开状态,即使该 namespace 中的所有进程都终止了,该 namespace 依然不会被删除 。通过 bind mount 将符号链接挂载到系统的其他位置,也可以获得相同的效果: $ touch ~/uts $ mount --bind /proc/27514/ns/uts ~/uts 3. setns() 加入一个已经存在的 namespace 可以通过系统调用 setns() 来完成。它的原型如下: int setns(int fd, int nstype); 更确切的说法是: setns() 将调用的进程与特定类型 namespace 的一个实例分离,并将该进程与该类型 namespace 的另一个实例重新关联。 fd 表示要加入的 namespace 的文件描述符,可以通过打开其中一个符号链接来获取,也可以通过打开 bind mount 到其中一个链接的文件来获取。 nstype 让调用者可以去检查 fd 指向的 namespace 类型,值可以设置为前文提到的常量 CLONE_NEW* ,填 0 表示不检查。如果调用者已经明确知道自己要加入了 namespace 类型,或者不关心 namespace 类型,就可以使用该参数来自动校验。 结合 setns() 和 execve() 可以实现一个简单但非常有用的功能:将某个进程加入某个特定的 namespace,然后在该 namespace 中执行命令。直接来看例子: /* ns_exec.c Copyright 2013, Michael Kerrisk Licensed under GNU General Public License v2 or later Join a namespace and execute a command in the namespace */ #define _GNU_SOURCE #include #include #include #include #include /* A simple error-handling function: print an error message based on the value in 'errno' and terminate the calling process */ #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) int main(int argc, char *argv[]) { int fd; if (argc < 3) { fprintf(stderr, "%s /proc/PID/ns/FILE cmd [arg...]\n", argv[0]); exit(EXIT_FAILURE); } fd = open(argv[1], O_RDONLY); /* 获取想要加入的 namespace 的文件描述符 */ if (fd == -1) errExit("open"); if (setns(fd, 0) == -1) /* 加入该 namespace */ errExit("setns"); execvp(argv[2], &argv[2]); /* 在加入的 namespace 中执行相应的命令 */ errExit("execvp"); } 该程序运行需要两个或两个以上的命令行参数,第一个参数表示特定的 namespace 符号链接的路径(或者 bind mount 到这些符号链接的文件路径);第二个参数表示要在该符号链接相对应的 namespace 中执行的程序名称,以及执行这个程序所需的命令行参数。关键步骤如下: fd = open(argv[1], O_RDONLY); /* 获取想要加入的 namespace 的文件描述符 */ setns(fd, 0); /* 加入该 namespace */ execvp(argv[2], &argv[2]); /* 在加入的 namespace 中执行相应的命令 */ 还记得我们之前已经通过 bind mount 将 demo_uts_namespaces 创建的 UTS namespace 挂载到 ~/uts 中了吗?可以将本例中的程序与之结合,让新进程可以在该 UTS namespace 中执行 shell: $ ./ns_exec ~/uts /bin/bash # ~/uts 被 bind mount 到了 /proc/27514/ns/uts My PID is: 28788 验证新的 shell 是否与 demo_uts_namespaces 创建的子进程处于同一个 UTS namespace: $ hostname bizarro $ readlink /proc/27514/ns/uts uts:[4026532338] $ readlink /proc/$$/ns/uts # $$ 表示当前 shell 的 PID uts:[4026532338] 在早期的内核版本中,不能使用 setns() 来加入 mount namespace、PID namespace 和 user namespace,从 3.8 版本的内核开始, setns() 支持加入所有的 namespace。 util-linux 包里提供了 nsenter 命令,其提供了一种方式将新创建的进程运行在指定的 namespace 里面,它的实现很简单,就是通过命令行(-t 参数)指定要进入的 namespace 的符号链接,然后利用 setns() 将当前的进程放到指定的 namespace 里面,再调用 clone() 运行指定的执行文件。我们可以用 strace 来看看它的运行情况: # strace nsenter -t 27242 -i -m -n -p -u /bin/bash execve("/usr/bin/nsenter", ["nsenter", "-t", "27242", "-i", "-m", "-n", "-p", "-u", "/bin/bash"], [/* 21 vars */]) = 0 ………… ………… pen("/proc/27242/ns/ipc", O_RDONLY) = 3 open("/proc/27242/ns/uts", O_RDONLY) = 4 open("/proc/27242/ns/net", O_RDONLY) = 5 open("/proc/27242/ns/pid", O_RDONLY) = 6 open("/proc/27242/ns/mnt", O_RDONLY) = 7 setns(3, CLONE_NEWIPC) = 0 close(3) = 0 setns(4, CLONE_NEWUTS) = 0 close(4) = 0 setns(5, CLONE_NEWNET) = 0 close(5) = 0 setns(6, CLONE_NEWPID) = 0 close(6) = 0 setns(7, CLONE_NEWNS) = 0 close(7) = 0 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4deb1faad0) = 4968 4. unshare() 最后一个要介绍的系统调用是 unshare() ,它的原型如下: int unshare(int flags); unshare() 与 clone() 类似,但它运行在原先的进程上,不需要创建一个新进程,即:先通过指定的 flags 参数 CLONE_NEW* 创建一个新的 namespace,然后将调用者加入该 namespace。最后实现的效果其实就是将调用者从当前的 namespace 分离,然后加入一个新的 namespace。 Linux 中自带的 unshare 命令,就是通过 unshare() 系统调用实现的,使用方法如下: $ unshare [options] program [arguments] options 指定要创建的 namespace 类型。 unshare 命令的主要实现如下: /* 通过提供的命令行参数初始化 'flags' */ unshare(flags); /* Now execute 'program' with 'arguments'; 'optind' is the index of the next command-line argument after options */ execvp(argv[optind], &argv[optind]); unshare 命令的完整实现如下: /* unshare.c Copyright 2013, Michael Kerrisk Licensed under GNU General Public License v2 or later A simple implementation of the unshare(1) command: unshare namespaces and execute a command. */ #define _GNU_SOURCE #include #include #include #include /* A simple error-handling function: print an error message based on the value in 'errno' and terminate the calling process */ #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ } while (0) static void usage(char *pname) { fprintf(stderr, "Usage: %s [options] program [arg...]\n", pname); fprintf(stderr, "Options can be:\n"); fprintf(stderr, " -i unshare IPC namespace\n"); fprintf(stderr, " -m unshare mount namespace\n"); fprintf(stderr, " -n unshare network namespace\n"); fprintf(stderr, " -p unshare PID namespace\n"); fprintf(stderr, " -u unshare UTS namespace\n"); fprintf(stderr, " -U unshare user namespace\n"); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { int flags, opt; flags = 0; while ((opt = getopt(argc, argv, "imnpuU")) != -1) { switch (opt) { case 'i': flags |= CLONE_NEWIPC; break; case 'm': flags |= CLONE_NEWNS; break; case 'n': flags |= CLONE_NEWNET; break; case 'p': flags |= CLONE_NEWPID; break; case 'u': flags |= CLONE_NEWUTS; break; case 'U': flags |= CLONE_NEWUSER; break; default: usage(argv[0]); } } if (optind >= argc) usage(argv[0]); if (unshare(flags) == -1) errExit("unshare"); execvp(argv[optind], &argv[optind]); errExit("execvp"); } 下面我们执行 unshare.c 程序在一个新的 mount namespace 中执行 shell: $ echo $$ # 显示当前 shell 的 PID 8490 $ cat /proc/8490/mounts | grep mq # 显示当前 namespace 中的某个挂载点 mqueue /dev/mqueue mqueue rw,seclabel,relatime 0 0 $ readlink /proc/8490/ns/mnt # 显示当前 namespace 的 ID mnt:[4026531840] $ ./unshare -m /bin/bash # 在新创建的 mount namespace 中执行新的 shell $ readlink /proc/$$/ns/mnt # 显示新 namespace 的 ID mnt:[4026532325] 对比两个 readlink 命令的输出,可以知道两个shell 处于不同的 mount namespace 中。改变新的 namespace 中的某个挂载点,然后观察两个 namespace 的挂载点是否有变化: $ umount /dev/mqueue # 移除新 namespace 中的挂载点 $ cat /proc/$$/mounts | grep mq # 检查是否生效 $ cat /proc/8490/mounts | grep mq # 查看原来的 namespace 中的挂载点是否依然存在? mqueue /dev/mqueue mqueue rw,seclabel,relatime 0 0 可以看出,新的 namespace 中的挂载点 /dev/mqueue 已经消失了,但在原来的 namespace 中依然存在。 5. 总结 本文仔细研究了 namespace API 的每个组成部分,并将它们结合起来一起使用。后续的文章将会继续深入研究每个单独的 namespace,尤其是 PID namespace 和 user namespace。 参考链接 Namespaces in operation, part 2: the namespaces API Docker 基础技术:Linux Namespace(上) 微信公众号 扫一扫下面的二维码关注微信公众号,在公众号中回复◉加群◉即可加入我们的云原生交流群,和孙宏亮、张馆长、阳明等大佬一起探讨云原生技术
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 本文为 Serverless 社区成员撰稿。作者云洋,从事信息管理工作,多年电子政务信息系统建设管理经验,对 Serverless 技术和架构有浓厚兴趣。 这个假期挺长的,不过有幸在腾讯云 Serverless 在线直播里看到了 Serverless 的相关课程,从第一期学完,还是凭添了很多学习乐趣。 前面三节课学了一些 Serverless 的基本知识和架构特点,也跟着开发部署,其实都蛮有趣的,唯一就是都没有管理后台。第四期课程很好的弥补了这一不足。刘宇老师给大家带来的项目 Python+HTML 的动态博客,后台是基于 Flask 的,虽然我不太熟悉这个框架,但是老师给提供了课程的源码,所以可以先用后学。 这节动态博客的直播回放地址是: https://cloud.tencent.com/edu/learning/live-1926 刘宇老师的项目的 Github 地址是: https://github.com/anycodes/ServerlessBlog 直播那天我提前准备好手机和电脑,就进教室了,不过当天课程收到网络的干扰很大,一直很卡,后来刘老师重新录制了课程,据说录到凌晨两点,这一点必须点赞,敬业精神太感人啦。在老师的热情带领下,我们的学习劲头也是十足的啊。 我的学习路径可谓十分曲折,整整折腾了三天,不过最后终于实现了项目的成功部署,很开心! 整体来说,其实项目部署就是四个步骤: 下载源文件 执行init.py 修改 serverless.yaml 文件 sls --debug 部署到云端。 我在每一个步骤都踩过坑,我基本上按照顺序把坑和解决方案列出来,感兴趣的同学可以基于上面的链接尝试开发部署,如果遇到相关的坑,可以对照参考。 坑一:Git 下载报错 Git下载报错,错误信息是这样如下: remote: Counting objects: 100% (1438/1438), done.remote: Compressing objects: 100% (1100/1100), done.error: RPC failed; curl 18 transfer closed with outstanding read data remainingfatal: The remote end hung up unexpectedlyfatal: early EOFfatal: index-pack failed 百度之后,发现针对RPC错误需要修改Git的buffer,调到700M后依然报了上面的fatal错误,和网络有关,最后放弃用git,选择老师发在文档里的压缩包,地址上面列出来了,大家可以去自行取用。 坑二:pip 的源设置和 pymysql 部署过程中因为 init.py 里面用到了 pymysql,可是我没有安装,于是打开Anaconda Prompt 开始用 pip 安装,然而 pip 好像死了一样,完全没有反应,最后报错说找不到包。后来经同学和老师提醒,修改 pip 的源到国内,我用了清华的源,速度一下很快啊,安装成功。修改源的代码是: pip install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple 如果大家需要安装其他包,就把 pymysql 换成要安装的包。 但是 init.py 依然报错,于是就吧 pymysql 的包放在项目目录中,解决了问题。 坑三:yaml 没有 FullLoader 属性 init.py 的执行一致不顺利,pymysql 的坑填完后继续报错。 于是以为自己没有安装对 yaml,后来老师建议删掉该语句,大家要注意,这个语句里 Fullloader 出现在一个逗号后面,所以我们只删掉逗号后面的 loader=yaml.FullLoader 就好了。 坑四:数据库连接 其实如果用老师提供的测试库的话,这个坑就不是问题,但是我觉得那么多同学做作业,老师的库压力会很大,正好我自己买了一个腾讯的云数据库是 MySql的,可以直接拿来用。 于是我就天真的把数据库地址改成了自己的,以为它可以连上,但是连接超时,于是我有把用户和密码改成自己的,结果还是拒绝访问,一直到我去云数据库控制台去测试连接,才发现原来我的数据库端口没有写对。 云数据库的外网链接设置还是很方便的,这次作业让我还进入了 PMA,进行了在线数据库操作,挺好用的。到这里,init.py 的坑就填平了,我看到新建数据库的成功。 坑五:网络问题 这个坑有点深,我在里面爬了两天才爬出来,而且之所以爬出来,完全是因为换了网络哦。 不得不说移动的家庭宽带真的不给力啊,Git 连不上,部署函数总是断线。我之所以在这个坑里没有很快出来,还有一个原因,就是每次的报错信息不一样,这深深的吸引了我。 我的报错信息五花八门,给大家分享一下: 这个错误出现的原因其实也很难想通,因为给的信息太少,并不知道到底是哪里有问题。后来还报过一个类似的错误,印象里是说读不到 'admin_add_article',我后来发现 git 上面又更新了一个文件夹 picture,里面有这个名字对应的图片,所以我就重新 git clone 了项目文件,把picture文件夹拷贝了过来。
这类 443 的错误,我是一直没有解决啦,直到我更换了网络。不过大家可以看到上面有一个存储桶的部署信息,那个桶不是我的。 关于 InvalidParameterValue 不太理解,什么样的 ID 是合法的呢?我考虑应该是网络中断丢失了数据,导致有些字符没了。 ECONNRESET 这个错误报了很多次,具体是什么意思不太明白,socket hang up应该还是网络不通畅吧。 还有一个是超时了,估计是网络情况不好。 这个 undefined RequestId 也是出现了多次的错误,很奇特。估计是网络数据丢包造成的吧。 以上所有的问题都是网络问题,解决方法:同学们提醒我不要部署到 hongkong 区域,可能海外服务器会有网络不稳定的情况,于是我换到了beijing,依然不行,我还换到多 guangdong,网络错误依然不断。 这个网络问题引发了我的不少猜疑啊:比如,我是不是频繁部署系统被封IP了?是不是防止勒索病毒,443端口封闭啦?我家的宽带是不是该换运营商了? 显然最后一个猜疑是并确认了的。我利用手机数据流量包部署系统就很快,240s 左右解决问题。 这里面其实报错信息不是很友好,首先,我不知道具体到哪一个文件的时候网络断开的,所以我不能肯定是不是某一个文件有问题,如果这时候能够定位一个断开的时候处理在哪一个文件,会对用户更有帮助。 而关于那个存储桶,应该是老师的,因为后来在部署成功的一次我在信息里看到了从老师的存储桶里上传了代码到一个存储桶里,而我在自己的存储桶列表里看到了那几个桶。 之所以有几个,是因为我换过几个区来部署。有一些程序老师可能部署在他的桶里,这样是不是省我们的流量呢?也可能是为了部署 Flask Admin 的过程更加平顺,老师把一些依赖部署到了线上的桶里。 记得上节课的老师也用了 Flask,他说 Flask 需要对应 Python 的精确版本,我们每个人的版本可能都会有些细小差别,造成部署过程的颠簸,老师为了避免这种状况,就额外处理了。老师,您用心啦! 坑六:UploadPicture 因为每次网络问题都发生在部署uploadPicture这个部分,时间很长,所以我都会忍不住cancel重来。 于是这个部分后来被我注释掉了。注释之后,部署很顺利,因为换用了手机的网络。这突如其来的顺利让我觉得 uploadPicture 可能是无罪的,我应该把它放回来。于是我就取消了注释,这时候灾难发生了,10000s 之后它还在部署。 我很好奇,为什么这个部分这么特殊,于是我打开一些 uploadPicture 的源文件来看,发现在 demo.py 里面有很多设置和我们的 global 设置不一样,于是我开始七七八八的修改起来,然而,老师说那个文件不在全局发挥最用,只是qqcloud_cos 这个依赖的一个 demo,于是我又改了回去。 后来,有同学说清空存储桶可以解决这种超长时间部署的问题,于是我试了一下。恩,真的管用哦!感谢同学的提醒。 PS:我后来回想很可能就是缺少 picture 那个文件夹吧,我猜测部署的过程可能会跑有些 test 程序,文件的代码会被执行,而如果里面的参数读取不到就会一直停在那里。 坑七:后台无法使用 部署成功之后,我访问后台,发现只有登陆页可用,其他页面基本上一点就报错,Internal error。在老师的提示下,我到了函数的控制台,查看了Blog_Admin的报错信息,发现是数据库连接不到。
可是我之前明明在数据库里新增数据的,于是我打开了 serverless.yml 文件和数据库里的库名称对照, 发现数据库的名称多了一个字母 l,哎,粗心啊粗心。修改了 yaml 文件后再次部署,成功了! 应该说控制台的日志挺详细的,我觉得如果能把成功信息和报错信息在颜色上区分一下就更好啦,目前看来是用时间戳来区分的,不过有时候请求多起来,很多正确和报错信息在一起,找起来很麻烦啊。要是有丰富一些的查询选项也许更好用。 最后,我终于跳过了这些坑,上了岸! 部署成功后的页面如下: http://blogdemo-1253166145.cos-website.ap-beijing.myqcloud.com/ 于是,我带着刘宇老师布置的作业来投稿,信息化资产复用最大化,感觉是一次非常开心的学习体验! 传送门: GitHub: github.com/serverless 官网: serverless.com 欢迎访问: Serverless 中文网 ,您可以在 最佳实践 里体验更多关于 Serverless 应用的开发! 推荐阅读: 《Serverless 架构:从原理、设计到项目实战》 |