从 400+ 节点 Elasticsearch 集群的运维中,我们总结了这些经验

齐达内 发表了文章 • 0 个评论 • 182 次浏览 • 2019-06-02 20:40 • 来自相关话题

在Kubernetes上运行Kafka合适吗?

whole 发表了文章 • 0 个评论 • 369 次浏览 • 2019-06-02 11:42 • 来自相关话题

【编者的话】本文介绍了在Kubernetes运行Kafka的互补性和一些坑,以及相关方案选择。 # 介绍 Kubernetes设计的初衷是运行无状态工作负载。这些通常采用微服务架构的工作负载,是轻量级,可水平扩展,遵循十二要素应用程序, ...查看全部
【编者的话】本文介绍了在Kubernetes运行Kafka的互补性和一些坑,以及相关方案选择。
# 介绍
Kubernetes设计的初衷是运行无状态工作负载。这些通常采用微服务架构的工作负载,是轻量级,可水平扩展,遵循十二要素应用程序,可以处理环形断路和随机Monkey测试。

另一方面,Kafka本质上是一个分布式数据库。这意味着你必须处理状态,它比微服务更重量级。Kubernetes支持有状态的工作负载,但你必须谨慎对待它,正如Kelsey Hightower在最近的两条推文中指出的那样:
1.png

现在你应该在Kubernetes上运行Kafka吗?我的反问是:没有它,Kafka会跑得更好吗?这就是为什么我要指出Kafka和Kubernetes之间的相互补充性以及你可能遇到的陷阱。
# 运行时
让我们先看一下基本的东西——运行时本身。
## 进程
Kafka brokers对CPU很友好。TLS可能会引入一些开销。如果Kafka客户端使用加密,则需要更多CPU,但这不会影响brokers。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
## 内存
Kafka brokers是内存消耗大户。JVM堆通常可以限制为4-5 GB,但由于Kafka大量使用页面缓存,因此还需要足够的系统内存。在Kubernetes中,可以相应地设置容器资源限制和请求。
## 存储
容器中的存储是短暂的——重启后数据将丢失。可以对Kafka数据使用emptyDir卷,这将产生相同的效果:brokers的数据将在停机后丢失。您的消息在其他broker上作为副本还是可以使用的。因此,重新启动后,失败的broker必须得复制所有的数据,这可能是一个耗时过程。

这就是你应该使用持久存储的原因。使用XFS或ext4的非本地持久性块存储更合适。我警告你:不要使用NFS。NFS v3和v4都不会起作用。简而言之,Kafka broker会因为NFS“愚蠢重命名”问题而无法删除数据目录,自行终止。如果你仍然不相信我,那么请仔细阅读这篇博文。存储必须是非本地的,以便Kubernetes在重新启动或重新定位时可以更灵活地选择另一个节点。
## 网络
与大多数分布式系统一样,Kafka性能在很大程度上取决于低网络延迟和高带宽。不要试图将所有代理放在同一节点上,因为这会降低可用性。如果Kubernetes节点出现故障,那么整个Kafka集群都会出现故障。不要跨数据中心扩展Kafka集群。这同样适用于Kubernetes集群。不同的可用区域是一个很好的权衡。
# 配置
## 清单
Kubernetes网站包含一个非常好的教程,介绍如何使用清单设置ZooKeeper。由于ZooKeeper是Kafka的一部分,因此可以通过这个了解哪些Kubernetes概念被应用在这里。一旦理解,您也可以对Kafka集群使用相同的概念。

  • Pod:Pod是Kubernetes中最小的可部署单元。它包含您的工作负载,它代表群集中的一个进程。一个Pod包含一个或多个容器。整体中的每个ZooKeeper服务器和Kafka集群中的每个Kafka broker都将在一个单独的Pod中运行。
  • StatefulSet:StatefulSet是一个Kubernetes对象,用于处理需要协调的多个有状态工作负载。StatefulSets保证Pod的有序性和唯一性的。
  • Headless Services:服务通过逻辑名称将Pod与客户端分离。Kubernetes负责负载平衡。但是,对于ZooKeeper和Kafka等有状态工作负载,客户端必须与特定实例进行通信。这就是 Headless Services发挥作用的地方:作为客户端,仍然可以获得逻辑名称,但不必直接访问Pod。
  • 持久卷:如上所述,需要配置非本地持久块存储。

Yolean提供了一套全面的清单,可以帮助您开始使用Kubernetes上的Kafka。

## Helm Charts
Helm是Kubernetes的包管理器,类似yum,apt,Homebrew或Chocolatey等OS包管理器。它允许您安装Helm Charts中描述的预定义软件包。精心设计的Helm Charts能简化所有参数正确配置的复杂任务,以便在Kubernetes上运行Kafka。有几张图表适用于Kafka的的可供选择:一个是处于演进状态的官方图表,一个来自Confluent,另一个来自Bitnami,仅举几例。
## Operators
由于Helm的一些限制,另一种工具变得非常流行:Kubernetes Operators。Operators不仅可以为Kubernetes打包软件,还可以为Kubernetes部署和管理一个软件。

评价很高的Operators名单中提到Kafka有两个,其中一个是Strimzi,Strimzi使得在几分钟内启动Kafka集群变得非常容易,几乎不需要任何配置,它增加了一些漂亮的功能,如群集间点对点TLS加密。Confluent还宣布即将推出新的Operator。
## 性能
运行性能测试以对Kafka安装进行基准测试非常重要。在您遇到麻烦之前,它会为您提供有关可能的瓶颈的地方。幸运的是,Kafka已经提供了两个性能测试工具:kafka-producer-perf-test.sh和kafka-consumer-perf-test.sh。记得经常使用它们。作为参考,可以使用Jay Kreps博客结果,或者 Stéphane Maarek在 Amazon MSK的评论
# 运维
## 监控
可见性非常重要,否则您将不知道发生了什么。如今,有一种不错的工具可以用云原生方式监控指标。Prometheus和Grafana是两种流行的工具。Prometheus可以直接从JMX导出器收集所有Java进程(Kafka,ZooKeeper,Kafka Connect)的指标。添加cAdvisor指标可为提供有关Kubernetes资源使用情况的其他信息。

Strimzi为Kafka提供了一个优雅的Grafana仪表板示例。它以非常直观的方式可视化关键指标,如未复制的和离线分区。它通过资源使用和性能以及稳定性指标来补充这些指标。因此,可以免费获得基本的Kafka集群监控!
2.png

资料来源:https://strimzi.io/docs/master/#kafka_dashboard

可以通过客户端监控(消费者和生产者指标),使用Burrow滞后监控,使用Kafka Monitor进行端到端监控,来完成这个任务
## 日志记录
日志记录是另一个关键部分。确保Kafka安装中的所有容器都记录到标准输出(stdout)和标准错误输出(stderr),并确保Kubernetes集群将所有日志聚合到中央日志记录设施中如Elasticsearch中。
## 健康检查
Kubernetes使用活跃度和就绪探测器来确定Pod是否健康。如果活跃度探测失败,Kubernetes将终止容器并在相应设置重启策略时自动重启。如果准备就绪探测失败,那么Kubernetes将通过服务从服务请求中删除该Pod。这意味着在这种情况下不再需要人工干预,这是一大优点。
## 滚动更新
StatefulSets支持自动更新:滚动更新策略将一次更新一个Kafka Pod。通过这种方式,可以实现零停机时间,这是Kubernetes带来的另一大优势。
## 扩展
扩展Kafka集群并非易事。但是,Kubernetes可以很容易地将Pod缩放到一定数量的副本,这意味着可以声明式地定义所需数量的Kafka brokers。困难的部分是在放大或缩小之前重新分配部分。同样,Kubernetes可以帮助您完成这项任务。
## 管理
通过在Pod中打开shell,可以使用现有的shell脚本完成Kafka群集的管理任务,例如创建主题和重新分配分区。这不是一个很好的解决方案。Strimzi支持与另一个Operator管理主题。这还有改进的余地。
## 备份和还原
现在Kafka的可用性还取决于Kubernetes的可用性。如果Kubernetes群集出现故障,那么在最坏的情况下Kafka群集也会故障。墨菲定律告诉我们,这也会发生在你身上,你会丢失数据。要降低此风险,请确保您具有备份想法。MirrorMaker是一种可选方案,另一种可能是利用S3进行连接备份,如Zalando的博客文章所述。
# 结论
对于中小型Kafka集群,我肯定会选择Kubernetes,因为它提供了更大的灵活性并简化了操作。如果您在延迟和/或吞吐量方面具有非常高的非功能性要求,则不同的部署选项可能更有益。

原文链接:Kafka on Kubernetes — a good fit?(译者:姜俊厚)

Kubernetes 身份管理:身份验证

老马 发表了文章 • 0 个评论 • 206 次浏览 • 2019-06-01 09:47 • 来自相关话题

与其他复杂的系统一样,Kubernetes 拥有自己的安全模型来与用户、系统进行交互。在本文中,我将介绍 Kubernetes 的一些身份验证方案,并提供有关管理集群访问权限的示例和建议。 #身份对 Kubernetes 意味着什么 ...查看全部
与其他复杂的系统一样,Kubernetes 拥有自己的安全模型来与用户、系统进行交互。在本文中,我将介绍 Kubernetes 的一些身份验证方案,并提供有关管理集群访问权限的示例和建议。
#身份对 Kubernetes 意味着什么

首先,你需要明白在 Kubernetes 中,“什么是身份”?Kubernetes 与大多数其他系统和应用程序截然不同。它是一组 API,没有 “Web 界面”、没有“登录”、“会话”或“超时”等选项框。每个 API 请求都是唯一且不同的,并且它必须包含 Kubernetes 验证和授权请求所需的所有内容。

也就是说,你要牢记这样一个道理:Kubernetes 的用户不会存在于任何持久状态中。你不需要将 Kubernetes 连接到 LDAP 目录或 Active Directory 中,但是每个请求都必须存在一种方法可以对 Kubernetes ASSERT(断言)身份。我将 ASSERT 大写,是因为它将在后文变得十分重要。Kubernetes 不会对用户进行身份验证,它验证的是断言。如果你想和更多 Kubernetes 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
##服务账户

服务帐户正好与我们刚刚提到的规则相反。Kubernetes 不存储有关用户的信息,但它存储服务帐户的信息(该信息为任何非人的事物)。在 Kubernetes 中,与任何内容交互的东西都会被作为服务帐户运行。例如,如果你要提交一个非常基本的 Pod:
1.jpg

在部署后的 Kubernetes 中,你可以通过运行 kubectl get pod myapp-pod -o yaml 来查看它:
2.jpg

你会发现这里存在 serviceAccount 和 serviceAccountName 属性,这两者都是 default。此服务帐户由入口控制器链为使用者注入,你可以在 Pod 上设置自己的服务帐户。

服务帐户易于创建和使用,但它们有一些缺点:

* 服务帐户的令牌是一个很长的字符串,需要被记录下来。如果处理不当,它就有可能会被利用;
* 授权服务帐户的唯一方法是通过 RBAC 进行绑定;
* 在服务帐户还在使用期间时,如果一个人泄露了令牌并且未被发觉。它可能会被一直滥用,直到被处理为止。

在日常工作中,如果你的应用程序在一个 Pod 中运行并且需要与 API 服务器通信,你就可以通过挂载到 Pod 上的一个 secret 来检索 Pod 的服务帐户。

通过上面示例 YAML,你会发现一个挂载的卷被添加到了 /var/run/secrets/kubernetes.io/serviceaccount 中,这其中有一个令牌文件包含 Pod 的服务帐户令牌。你需要注意:不要将服务帐户令牌嵌入到集群中运行的 Pod 的 secret 或配置中!因为这样的做法会使得你使用旋转令牌(rotating token)变得更加困难,并且难以管理。
##用户账户

Kubernetes 不连接任何类型的用户存储(至少不直接连接)。这意味着在每个请求中,你必须为 Kubernetes 提供足够的信息以验证调用者的身份。Kubernetes 并不关心你如何建立身份,它只关心它如何证明身份是有效的。
#Kubernetes 如何知道你是谁

##OpenID Connect


OpenID Connect 是你用来验证用户身份的选项:

* OpenID Connect 令牌具有短暂性。当该类令牌被拦截或泄露时,如果攻击者得知该令牌的字符串,那么令牌就没用了;
* 使用 OpenID Connect 的好处是:Kubernetes 永远不会拥有用户的凭据,它不可能泄漏 Kubernetes 没有的东西;
* OpenID Connect 提供的用户标识不仅可以提供用户名信息,还可以提供组信息。这使得它们通过 LDAP 目录或外部数据库管理访问变得更容易(你无需为单个用户创建 RBAC 绑定);
* 通过在 Kubernetes 和身份层之间添加“代理”,你可以更轻松地添加多种类型的身份验证,例如多因素身份验证;
* 大量的开源 OpenID Connect 可以与 Kubernetes 一起使用。

##OpenID Connect Primer

在深入研究如何使用 OpenID Connect 之前,你需要先了解一下该协议:

* OpenID Connect 是一个建立在 OAuth2 之上的断言生成协议;
* OAuth2 则是一个用于传输承载令牌授权的协议。

这两点似乎缺少一个词:认证!那是因为 OpenID Connect 不是身份验证协议。它不关心你如何进行身份验证。如果用户使用的是用户名和密码,那么一张智能卡或许看起来真的很值得信赖,但是 OpenID Connect 是一种用于生成、检索和刷新用户断言的协议。用户如何进行身份验证最终取决于 OpenID Connect 的实现。

OAuth2 是一个比较关键的协议,它可以用于传递令牌,但是它没有定义令牌是什么或如何使用。它只是定义了令牌和依赖方之间如何传递令牌。
#Kubernetes 如何使用 OpenID Connect


图 1 显示了 Kubernetes 身份验证页面:
3.jpg

以下为该图的基础知识点:

* 用户可以登录到用户身份的提供程序中;
* 身份提供者会生成一个 id_token 和一个 refresh_token;
* id_token 可以将用户的身份断言为 Kubernetes 的;
* 当 id_token 过期时,refresh_token 用于生成新的 id_token。

一个 id_token 是一个 JSON Web Token(JWT),它可以表示:

* 用户是谁;
* 用户所属的组(可选);
* 令牌有效时长;
* 它包含一个数字签名,用于验证 JWT 是否未被篡改。

用户的 ID 属性 sub 通常是用户的唯一标识符。用户经常会使用 Active Directory 的登录 ID(也称为 samAccountName),还有一些人更喜欢使用电子邮件地址。一般来说,这不是最佳的做法。用户的 ID 应该是唯一的和不可变的。虽然电子邮件地址也是唯一的,但它并不总是不可变的(例如,有时名称会更改)。

JWT 将在从 kubectl 到 Kubernetes 的每个请求上传递。其中 id_token 被称为 “承载令牌”,因为它会授予承载者访问权限而无需任何额外的检查。这意味着,如果 API 调用流程中的系统泄漏此令牌,攻击者就可以滥用该系统了。

因为这些令牌很容易被滥用,所以它们的寿命极短。我推荐它们的使用寿命在一分钟左右。这样,如果当一个令牌被泄露时,即使攻击者知道它是什么,但是令牌也已经过期。使用此类短期令牌,最重要的是在它过期后,系统会配置一个 refresh_token 去更新你的 id_token。

kubectl 知道如何通过使用 refresh_token 调用标识提供者的授权服务 URL,并更新 id_token 令牌。

refresh_token 是 Kubernetes 的 API 服务器永远不会使用的令牌,用户可以将它视为 secret。该令牌用于获取新的 JWT,此时可以使用 JWT 中新的 refresh_token。如果 id_token 的生命周期非常短,则 refresh_token 超时与非活动超时类似,通常为 15-20 分钟。这样,你的 Kubernetes 实现将符合企业中针对不活动超时的策略。使用一个 refresh_token 获取一个新 id_token 内容比使用更长时间更安全,因为这个 refresh_token 意味着以下内容:

* 它只能使用一次。一旦使用,就会生成一个新的令牌;
* 它只在用户和身份提供者之间传递,在极少情况下会被泄露;
* 如果它自己被泄漏了,就不能被用来识别你。因为它是不透明的,所以如果没有额外的信息,攻击者根本不知道该如何处理它。

Kubernetes 仪表板

仪表板没有自己的登录系统。所有这一切都可以使用代表用户的现有令牌。这意味着在仪表板前面放置了一个反向代理,它将 id_token 在每个请求上注入 ,然后反向代理会根据需求刷新令牌。
#我应该使用哪个身份提供商

在选择身份提供商时,Kubernetes 实际上只有两个要求:

* 它必须支持 OpenID Connect 发现;
* 它提供了一种生成令牌并将其注入到 〜/.kube/config 的机制中。

只要符合以上两个要求,你就可以使用了。因为这种类型的身份提供商,它使你不必手动告诉 Kubernetes 不同的 URL 在哪里,用于签名的密钥是什么 ...... 将 Kubernetes 指向所有具有这种信息发现的 URL 就容易多了。因为大多数身份提供商都支持开箱即用(通用标准)。

而关于如何从你的登录点(通常是 Web 浏览器)将令牌信息添加到 〜/.kube/config 中,可以有不同的方法。
##Web 浏览器注入

在此模型中,所有内容都集中在你的 Web 浏览器上。你可以通过浏览器进行身份验证,然后获得正确设置 kubectl 客户机的命令。例如,OpenUnison(我们自己的项目)为你提供了一个命令,该命令可用于身份验证后设置集群的配置(图 2)。
4.png

图 2 浏览器令牌

你可以使用 kubectl 的内置功能从命令行配置配置文件以完成设置。

这种方法有几个优点:

* 浏览器具有最多的身份验证选项。除了用户名和密码,你还可以集成 Kerberos、Multi-Factor 等;
* 你不需要管理复杂的 Kubernetes 配置;
* 这种方法适用于 stock kubectl 命令。

Kubectl 插件

你可以使用插件扩展 kubectl 命令。使用插件,你可以收集用户的凭据,然后生成令牌。我看到过一些插件可以从 CLI 收集到你的凭证,还有一些插件可以启动浏览器提示你登录。从 CLI 角度来看,这种方法很好,因为它可以让你的 CLI 驱动你的用户体验。这种方法的主要缺点是需要在每个工作站上安装插件。

下载配置

使用此方法,身份提供程序(或自定义应用程序)可以为你提供用于下载的完整生成配置文件。但是,如果你没有将某些内容保存到正确的位置上,就可能会产生支持问题。

选择身份提供者后,请按照其说明进行集成,其中关键项是发现 URL、标识符“claim”以及组的“claim”。
##X509 证书

证书身份验证利用客户端(通常是 kubectl 命令)和 Kubernetes API 服务器之间的 TLS 握手,通过向 API 服务器提供证书来声明身份。除了一个用例之外,这种方法并不是 “最佳实践”:

* 证书不能在 Kubernetes 中撤销。你需要等到证书过期或重新生成整个集群后才可以撤销;
* 证书的私钥永远不离开生成它的安全介质。通常情况下,你会一起“获得”密钥对和证书;
* 使用带证书的组很困难,你需要将它们嵌入到 subject 中。

你可以使用 X509 证书来进行身份验证的情形是:当你在引导集群时,或者在紧急情况下,你的身份提供程序不可用。

大多数发行版都会为每个主服务器部署密钥对。因此,如果你 ssh 进入该主服务器,则可以使用它 kubectl 管理集群。这意味着你需要锁定对主服务器的访问权限。
##Webhook

此方法允许你通过 Webhook 集成第三方登录或令牌系统。Kubernetes 不会告诉你 Kubernetes 如何验证身份,而是调用 webhook 并询问“这是谁”。

除非你是云提供商并拥有自己的身份解决方案,否则请勿这样做。几乎我见过的每一个实现都变成了一个糟糕的 OpenID Connect。
##带模拟的反向代理

这里客户端(kubectl 或其他)不直接与 API 服务器通信。它会与反向代理进行通信,然后反向代理会将头部注入请求以表示用户。这通常被当作处理高级身份验证的一种方法,因为从 API 服务器的角度来看,它需要的工作量最少。实施步骤如下:

* 创建服务帐户;
* 授权服务帐户进行模拟;
* 配置反向代理以将服务帐户和模拟标头注入到每个请求中。

这个方案现有的标准可能会更适合用户的需求,因为它易于管理和维护。
#如何将身份整合到 Kubernetes 中

要将身份整合到 Kubernetes 中,请遵循以下基本核对清单:

* 仅将服务帐户用于系统,而不是人员;
* 建议使用 OpenID Connect。因为它会受到多种系统的审核和支持,包括开源和专有系统;
* 仅对“紧急情况”使用证书认证。

遵循这些规则,你会发现团队中的开发人员会很乐意记住身份验证的密码,安防人员也会很高兴地遵守合规性的要求。

参考文献:https://www.linuxjournal.com/content/kubernetes-identity-management-authentication

原文链接:https://mp.weixin.qq.com/s/9_mS5eet8WDSMJcBzQCelg

利用Helm简化Kubernetes应用部署

Andy_Lee 发表了文章 • 0 个评论 • 203 次浏览 • 2019-06-01 09:19 • 来自相关话题

【编者的话】如果需要自动化处理复杂的Kubernetes任务,常常需要编写Yaml配置文件。由于Yaml文件格式比较复杂,即使是老手有时也不免会犯错或需要查询文档,也有人开玩笑这是使用 Yaml 编程。我们今天将介绍几个方法来帮助大家来简化 Kubernete ...查看全部
【编者的话】如果需要自动化处理复杂的Kubernetes任务,常常需要编写Yaml配置文件。由于Yaml文件格式比较复杂,即使是老手有时也不免会犯错或需要查询文档,也有人开玩笑这是使用 Yaml 编程。我们今天将介绍几个方法来帮助大家来简化 Kubernetes Yaml 文件创建。

Kubernetes 提供了丰富的 kubectl 命令,可以较为方便地处理常见任务。如果需要自动化处理复杂的Kubernetes任务,常常需要编写Yaml配置文件。由于Yaml文件格式比较复杂,即使是老司机有时也不免会犯错或需要查询文档,也有人开玩笑这是使用 Yaml 编程。我们今天将介绍几个方法来帮助大家来简化 Kubernetes Yaml 文件创建。
#模拟命令执行
kubectl中很多命令支持 --dry-run 和 -o yaml 参数,可以方便地模拟命令执行,并输出yaml格式的命令请求,这样我们就可以将执行结果 Copy & Paste到自己的编辑器中,修改完成自己的配置文件。
$ kubectl run myapp --image=nginx --dry-run -o yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
creationTimestamp: null
labels:
run: myapp
name: myapp
spec:
replicas: 1
selector:
matchLabels:
run: myapp
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
run: myapp
spec:
containers:
- image: nginx
name: myapp
resources: {}
status: {}

$ kubectl create secret generic mysecret --from-literal=quiet-phrase="Shh! Dont' tell" -o yaml --dry-run
apiVersion: v1
data:
quiet-phrase: U2hoISBEb250JyB0ZWxs
kind: Secret
metadata:
creationTimestamp: null
name: mysecret

#导出资源描述
kubectl get --export -o yaml 命令会以Yaml格式导出系统中已有资源描述。

比如,我们可以将系统中 mysql 部署的描述导成 Yaml 文件。
$ kubectl get deployment mysql --export -o yaml > mysql.yaml 

#Kompose转换
Docker Compose是最为流行的容器编排开发工具,可以方便地在Docker/Docker Swarm环境下开发、部署容器编排模板。

Kompose是Kubernetes社区开发的一个转换工具,可以方便地将简单的Docker Compose模板转化成为Kubernetes的Yaml描述文件,并在Kubernetes集群上部署和管理应用。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

我们可以通过 https://github.com/kubernetes/kompose 获取最新的kompose 工具。

如下 docker-compose.yaml 模板描述了一个 WordPress 编排,其中包含两个服务,wordpress服务和mysql服务,为了将 wordpress服务暴露给外部访问,我们增加了一个 Kompose 特有的标签 kompose.service.type: nodeport,将服务以NodePort方式提供对外访问方式。
version: '2'
services:
wordpress:
image: wordpress:4
environment:
- WORDPRESS_DB_PASSWORD=password
ports:
- 80:80
depends_on:
- mysql
labels:
kompose.service.type: nodeport
mysql:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=password

我们可以通过kompose convert命令转换为 Kubernetes 的 Deployment和Service的资源描述:
$ kompose convert
WARN Unsupported depends_on key - ignoring
INFO Kubernetes file "mysql-service.yaml" created
INFO Kubernetes file "wordpress-service.yaml" created
INFO Kubernetes file "mysql-deployment.yaml" created
INFO Kubernetes file "wordpress-deployment.yaml" created

也可以通过kompose convert --stdout命令输出到标准输出
#总结
Kubernetes以其开放的平台和活跃的社区征服了众多用户,然而其陡峭的学习曲线也让初学者望而生畏。今天希望通过几个技巧帮助大家简化 Kubernetes Yaml 文件的编写。

原文链接:https://yq.aliyun.com/articles/341213

Nginx反向代理与负载均衡

aoxiang 发表了文章 • 0 个评论 • 181 次浏览 • 2019-06-01 09:05 • 来自相关话题

#学到老活到老 前端圈一直很新,一直要不停的学习,而且在进入大厂的路上,还要求熟悉一门后台语言等等。用一句别人开玩笑的话来说,Java十年前的技术现在还能用,而前端的技术就不是这样的了。 突然想起了deno项目发布的 ...查看全部
#学到老活到老

前端圈一直很新,一直要不停的学习,而且在进入大厂的路上,还要求熟悉一门后台语言等等。用一句别人开玩笑的话来说,Java十年前的技术现在还能用,而前端的技术就不是这样的了。

突然想起了deno项目发布的时候,一个搞笑的issue,“求别更新了,老子学不动了”。虽然看起来是一个玩笑的issue,但却道出了前端们不得不表现出来的疲态,知识点越来越庞大,学习的内容越来越多。

也听到一些朋友们说,换成现在再面试阿里,恐怕不好进了啊。当然很多都是随便一说的玩笑话,听过一笑便可,不必当真,也不必抱怨了
好了,今天就直接来说一下主题吧,前端要了解一些运维的Nginx用法,内容不多,简单看看就好,这两个功能在工作当中就够用了,那么首先来看个问题,什么是反向代理与负载均衡。如果你想和更多Nginx技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态。
#什么是反向代理与负载均衡

##什么是反向代理

当我们有一个服务器集群,并且服务器集群中的每台服务器的内容一样的时候,同样我们要直接从个人电脑访问到服务器集群服务器的时候无法访问,必须通过第三方服务器才能访问集群。

这个时候,我们通过第三方服务器访问服务器集群的内容,但是我们并不知道是哪一台服务器提供的内容,此种代理方式称为反向代理。
##什么是负载均衡

公司会建立很多的服务器,这些服务器组成了服务器集群,然后,当用户访问网站的时候,先访问一个中间服务器,再让这个中间服务器在服务器集群中选择一个压力较小的服务器,然后将该访问请求引入选择的服务器。

所以,用户每次访问,都会保证服务器集群中的每个服务器压力趋于平衡,分担了服务器压力,避免了服务器崩溃的情况。

一句话:Nginx会给你分配服务器压力小的去访问。
#Nginx反向代理与负载均衡的实现

用户访问网站的时候首先会访问Nginx服务器,然后Nginx服务器再从服务器集群中选择压力较小的服务器,将该访问请求引向该服务器。
##Nginx配置

下面修改配置方面我就从Mac系统下来进行简单的演示,如何安装的话也暂以Mac为主了,Windows系统直接去Nginx官网下载安装即可。
安装nginx
1-进到homebrew官网,然后复制命令,预安装需要的东西
2-brew install nginx 安装nginx
3-nginx -v 显示版本号
进入nginx
cd /usr/local/etc/nginx

下图为进入Nginx文件夹下的文件内容。
1.png

当进到这个目录下,我们就可以操作Nginx了,接下来就列举一些非常非常有用的命令,多敲几遍,一定要记住。

Nginx常用命令

* 启动Nginx

* Nginx
* 当你敲完Nginx这5个键的时候,并没有任何反应,此时你只需访问localhost:8080(默认)即可
2.png


* 关闭Nginx

* 如果出现下图情况,不要惊慌,是因为之前Nginx被启动过了
* 只需nginx -s stop,停止Nginx服务
* 然后再次启动Nginx即可
3.png


* 重启Nginx

* nginx -s reload
* 每次修改完.conf文件就需要重启nginx

* 检查配置

* 检查修改的nginx.conf配置是否正确
* nginx -t
* 如果出现下面ok和successfull就代表正确了,其他的都不对

 nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful


对于我们前端来说正常工作当中,倒是不需要过多的修改Nginx的。我们之所以修改Nginx配置,是为了做一些反向代理罢了。
##proxy_pass

Nginx反向代理主要通过proxy_pass来配置,将你项目的开发机地址填写到proxy_pass后面,正常的格式为proxy_pass URL即可:
server {
listen 80;
location / {
proxy_pass http://10.10.10.10:20186;
}
}

#Upstream模块实现负载均衡


* ip_hash指令
* server指令
* upstream指令及相关变量

上面写的三个指令,我们直接通过代码来一一分析:
// 修改nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
upstream firstdemo {
server 39.106.145.33;
server 47.93.6.93;
}
server {
listen 8080;
location / {
proxy_pass http://firstdemo;
}
}
}

上面修改的nginx.conf就是上图中花圈的那个文件,Nginx配置的主要修改就在这里。化繁为简,把原本nginx.conf里的内容直接替换为上面的不到20行的代码了。

既然不到20行,那就把里面对应的内容统统解释一下吧,有个了解就好

* worker_processes

* 工作进程数,和CPU核数相同

* worker_connections

* 每个进程允许的最大连接数

* upstream模块

* 负载均衡就靠它
* 语法格式:upstream name {}
* 里面写的两个server分别对应着不同的服务器

* server模块

* 实现反向代理
* listen监督端口号
* location / {}访问根路径
* proxy_pass http://firstdemo,代理到firstdemo里两个服务器上

上面修改了nginx.conf之后,别忘了最重要的一步重启Nginx。

那么再次访问localhost:8080,会看到如下图页面:
4.png

还有另一个页面:
5.png

每次刷新都会访问不同的服务器,这样就做到了负载均衡处理
不过,更应该做到的是当用户第一次访问到其中一台服务器后,下次再访问的时候就直接访问该台服务器就好了,不用总变化了。那么就发挥了ip_hash的威力了。
// 省略...
upstream firstdemo {
ip_hash;
server 39.106.145.33;
server 47.93.6.93;
}

ip_hash它的作用是如果第一次访问该服务器后就记录,之后再访问都是该服务器了,这样比如第一次访问是33服务器,那之后再访问也会分配为33服务器访问了。
#工作中的简单使用

在公司开发项目的时候,遇到设计,产品走查环节的时候,不能每次都让他们去配一个host,毕竟这样不友好,走查起来有麻烦。所以更应该给他们直观的感受,既给一个访问地址就可以看到样子。

下面给大家看一下,我正常在公司时nginx做的反向代理配置,和咱们上面的如出一辙,只是加了一个server_name,用指定的域名去访问即可。
server {
listen 80;
server_name chd.news.so.m.qss.test.so.com ;
auth_basic off;
location / {
proxy_pass http://10.10.10.10:20186;
proxy_set_header Host $host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 60;
proxy_read_timeout 600;
proxy_send_timeout 600;
}
}

每次修改完Nginx配置后不要忘记重启Nginx才能生效,这样只需要访问chd.news.so.m.qss.test.so.com这个地址就可以查看我的开发环境,进行走查了。

这就是Nginx最大的功能,反向代理我也接触的不是很多,毕竟不是专业运维出身,可比性差了很多。略知一二,也只是方便大家工作中使用吧,再次感谢大家的收看了,哈哈。

作者:chenhongdong
链接:https://juejin.im/post/5b01336af265da0b8a67e5c9

使用 Serverless 实现日志报警

阿娇 发表了文章 • 0 个评论 • 146 次浏览 • 2019-06-01 08:13 • 来自相关话题

最近尝试将应用的页面 JS 错误报警功能通过 Serverless 来实现。本文主要介绍一下具体实现过程,以及遇到的一些问题。 报警功能的需求也很简单,就是定时(如每隔 1 分钟)去读取 ARMS 的错误日志,如果有错误日志,则通过钉 ...查看全部
最近尝试将应用的页面 JS 错误报警功能通过 Serverless 来实现。本文主要介绍一下具体实现过程,以及遇到的一些问题。

报警功能的需求也很简单,就是定时(如每隔 1 分钟)去读取 ARMS 的错误日志,如果有错误日志,则通过钉钉消息发送错误详情进行报警。

在这之前,我通过定时任务实现了该功能。从成本上来说,这种方案就需要单独申请一台服务器资源;而且定时任务只在对应的时间才执行,这件意味着,服务器有很长的时间都是空闲的,这就造成了资源的浪费。而使用 Serverless,就不需要再申请服务器,函数只需要在需要的时候执行,这就大大节省了成本。

总的来说,我觉得函数计算的优势就是:

* 对于开发者,只需要关系业务逻辑的实现,不需要关心代码所运行的环境、硬件资源、以及运维
* 节省成本

通过 Serverless 实现前端日志报警,依赖的云服务是阿里云函数计算,依赖的其他工具还有:

* 函数计算的命令行工具 fun,用于本地调试、部署函数
* 函数计算的可交互式工具 fcli,用于本地测试
* 阿里云 JS SDK aliyun-sdk-js,用于读取 SLS 日志,ARMS 的日志是存储在 SLS 中的
* 编程语言使用 Node.js
* 安装和配置 fun

#初次使用需要先安装 fun
$ npm install @alicloud/fun -g

安装完成之后,需要通过 fun config 配置一下账号信息 Aliyun Account ID Aliyun Access Key ID Aliyun Secret Access Key 以及默认的地域。地域这里有个需要注意的是,如果需要使用 SLS 记录函数日志,则需要 SLS 和函数服务在同一个地域。这里稍后会详细介绍。
$ fun config
? Aliyun Account ID ******
? Aliyun Access Key ID ******
? Aliyun Secret Access Key ******
? Default region name cn-shanghai
? The timeout in seconds for each SDK client invoking 60
? The maximum number of retries for each SDK client 6

Aliyun Account ID Aliyun Access Key ID Aliyun Secret Access Key 可以在阿里云的控制台中查找和设置。

Aliyun Account ID
1.png

Aliyun Access Key ID Aliyun Secret Access Key
2.png

#函数初始化
先通过 fun 创建一个 Node.js 的 demo,之后可以在这个 demo 的基础上进行开发。
$ fun init -n alarm helloworld-nodejs8
Start rendering template...
+ /Users/jh/inbox/func/code/alarm
+ /Users/jh/inbox/func/code/alarm/index.js
+ /Users/jh/inbox/func/code/alarm/template.yml
finish rendering template.

执行成功后,分别创建了两个文件 index.js 和 template.yml。

其中 template.yml 是函数的规范文档,在里面定义了函数需要的资源、触发函数的事件等等。
##template.yml
接下来简单看看生成的默认的 template.yml 配置文件。
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
alarm:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'helloworld'
alarm:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8
CodeUri: 'index.js'

首先定义了规范文档的版本 ROSTemplateFormatVersion 和 Transform,这两个都不用修改。

Resources 里面定义了一个名为 alarm 的函数服务(Type: Aliyun::Serverless::Service 表示该属性为函数服务),并且该服务里面定义了名为 alarm 的函数(Type: 'Aliyun::Serverless::Function'表示该属性为函数)。

函数服务里面可以包含多个函数,就相当于是一个函数组。后面我们会提到的函数日志,是配置到函数服务上的。函数服务里面的所有函数,都用同一个日志。如果你想和更多 Serverless 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

可以根据实际情况修改函数服务名和函数名。下面就将函数服务名称改为 yunzhi,函数名依旧保留为 alarm。
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
yunzhi: # 函数服务的名称
Type: 'Aliyun::Serverless::Service' # 表示 yunzhi 是一个函数服务
Properties:
Description: 'helloworld' # 函数服务的描述
alarm: # 函数的名称
Type: 'Aliyun::Serverless::Function' # 表示 alarm 是一个函数
Properties:
Handler: index.handler # 函数的调用入口
Runtime: nodejs8 # 函数的运行环境
CodeUri: 'index.js' # 代码的目录

alarm 函数里面的 Properties 定义了函数的调用入口、运行环境等,如上面的注释所示。

关于 template.yml 的配置详见 Serverless Application Model。
##index.js
index.js 文件就是函数的调用入口了。index.handler 就表示,函数的调用的是 index.[extension] 文件中的 handler 函数。
module.exports.handler = function(event, context, callback) { 
console.log('hello world');
callback(null, 'hello world');
};

初始化之后的代码就上面这几行,很简单。主要是理解上面的几个参数。

* event 调用函数时传入的参数
* context 函数运行时的一些信息
* callback 函数执行之后的回调

* 必须要要调用 callback 函数,才会被认为函数执行结束。如果没有调用,则函数会一直运行到超时
* callback 调用之后,函数就结束了
* callback 的第一个参数是 error 对象,这和 JS 回调编程的思想一致

关于 event 和 context,详见 Nodejs 函数入口

实现报警功能的主要逻辑,就写在 index.js 里面。具体的实现,就不细说,下面用伪代码来描述:
alarm/alarm.js

// alarm/alarm.js
// 实现报警功能
module.exports = function() {
return new Promise((resolve, reject) => {
// 查询 SLS 日志
// - 如果没有错误日志,则 resolve
// - 如果有错误日志,则发送钉钉消息
// - 如果钉钉消息发送失败,则 reject
// - 如果钉钉消息发送成功,则 resolve
resolve();
})
}
alarm/index.js

// alarm/index.js
// 调用报警函数
const alarm = require('./alarm');

module.exports.handler = function(event, context, callback) {
alarm()
.then(() => {
callback(null, 'success');
})
.catch(error => {
callback(error);
})
};

##CodeUri
如果函数里面引入了自定义的其他模块,比如在 index.js 里面引入了 alarm.js const alarm = require('./alarm');,则需要修改默认的 codeUri 为当前代码目录 ./。否则默认的 codeUri 只定义了 index.js,部署的时候只会部署 index.js。
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
yunzhi: # 函数服务的名称
Type: 'Aliyun::Serverless::Service' # 表示 yunzhi 是一个函数服务
Properties:
Description: 'helloworld' # 函数服务的描述
alarm: # 函数的名称
Type: 'Aliyun::Serverless::Function' # 表示 alarm 是一个函数
Properties:
Handler: index.handler # 函数的调用入口
Runtime: nodejs8 # 函数的运行环境
CodeUri: './' # 代码的目录

如果没有修改 CodeUri,则会有类似下面的报错:
$ fun local invoke alarm
FC Invoke End RequestId: 16e3099e-6a40-43cb-99a0-f0c75f3422c6
{
"errorMessage": "Cannot find module './alarm'",
"errorType": "Error",
"stackTrace": [
"Error: Cannot find module './alarm'",
"at Module._resolveFilename (module.js:536:15)",
"at Module._load (module.js:466:25)",
"at Module.require (module.js:579:17)",
"at require (internal/module.js:11:18)",
"at (/code/index.js:9:15)",
"at Module._compile (module.js:635:30)",
"at Module._extensions..js (module.js:646:10)",
"at Module.load (module.js:554:32)",
"at tryModuleLoad (module.js:497:12)",
"at Module._load (module.js:489:3)"
]
}

fun local invoke alarm 是本地调试的命令,接下来会讲到。
#本地调试
在开发过程中,肯定需要本地调试。fun 提供了 fun local 支持本地调试。

fun local 的命令格式为 fun local invoke [options] <[service/]function>,其中 options 和 service 都可以忽略。比如调试上面的报警功能的命令就是 fun local invoke alarm。

需要注意的是,本地调试需要先安装 Docker。
$ brew cask install docker

安装成功后启动 Docker。

如果 Docker 没有启动,运行 fun local 可能会有如下报错
$ fun local invoke alarm
Reading event data from stdin, which can be ended with Enter then Ctrl+D
(you can also pass it from file with -e)
connect ENOENT /var/run/docker.sock

正常的输出如下:
$ fun local invoke alarm
Reading event data from stdin, which can be ended with Enter then Ctrl+D
(you can also pass it from file with -e)
skip pulling image aliyunfc/runtime-nodejs8:1.5.0...
FC Invoke Start RequestId: 9360768c-5c52-4bf5-978b-774edfce9e40
load code for handler:index.handler
FC Invoke End RequestId: 9360768c-5c52-4bf5-978b-774edfce9e40
success

RequestId: 9360768c-5c52-4bf5-978b-774edfce9e40 Billed Duration: 79 ms Memory Size: 1998 MB Max Memory Used: 54 MB

第一次调试的话,会安装 runtime 的镜像,可能需要点时间。默认的 Docker 镜像下载会很慢,可以使用国内的加速站点加速下载。

出现 Reading event data from stdin, which can be ended with Enter then Ctrl+D 的提示时,如果不需要输入,可以按 ctrl+D 跳过。
#函数部署
开发完成之后,就需要将函数部署到阿里云的函数计算上面了。部署可以通过 fun deploy 命令。

前面已经在安装 fun 之后,通过 fun config 命令配置了阿里云的账号和地域信息,fun deploy 会将函数自动部署到对应的账号和地域下。

在 template.yml 中,也配置了函数的服务名和函数名。如果在函数计算中没有对应的服务或函数,fun deploy 会自动创建;如果已经存在,则会更新。
$ fun deploy
using region: cn-shanghai
using accountId: ***********4698
using accessKeyId: ***********UfpF
using timeout: 60

Waiting for service yunzhi to be deployed...
Waiting for function alarm to be deployed...
Waiting for packaging function alarm code...
package function alarm code done
function alarm deploy success
service yunzhi deploy success

部署成功之后,就可以在函数计算的控制台中看到对应的函数服务和函数了。目前还没有配置触发器,可以手动在控制台中点击“执行”按钮来执行函数。
3.gif

#触发器
对于应用到生产环境的函数,肯定不会像上面一样手动去执行它,而是通过配置触发器去执行。触发器就相当于是一个特定的事件,当函数计算接收到该事件的时候,就去调用对应的函数。

阿里云的函数计算支持 HTTP 触发器(接收到 HTTP 请求之后调用函数)、定时触发器(定时调用函数)、OSS 触发器等等。详见触发器列表

对于报警功能,需要用到的是定时触发器,因为需要间隔一定的时间就调用函数。

触发器是配置到函数中的,可以通过函数的 Event 属性去配置:
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
yunzhi:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'helloworld'
alarm:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8
CodeUri: './'
Events: # 配置 alarm 函数的触发器
TimeTrigger: # 触发器的名称
Type: Timer # 表示该触发器是定时触发器
Properties:
CronExpression: "0 0/1 [i] [/i] [i] [/i]" # 每 1 分钟执行一次
Enable: true # 是否启用该定时触发器

上面的配置,就为 alarm 配置了一个名为 TimeTrigger 的定时触发器,触发器每隔 1 分钟执行一次,也就是每隔 1 分钟调用一次函数。

配置完成之后,再执行 fun deploy 就可以发布函数及触发器到函数计算上。
4.gif

这里需要注意的是,阿里云函数计算服务目前支持的触发器,最小的间隔时间为 1 分钟。如果小于 1 分钟,则无法设置成功。定时触发器的详细介绍可参考文档 定时触发函数。
#函数日志
对于 serverless 应用,虽然不用关心运维了,其实我们也并不知道我们的函数运行在哪台服务器上。这个时候,函数的日志就尤为重要了。没有日志,我们很难知道程序运行状态,遇到问题更是无从下手。

所以接下来需要对函数配置日志。阿里云的函数计算可以使用阿里云日志服务 SLS来存储日志。如果要存储日志,则需要先开通 日志服务。
##不存在日志库
如果是第一次使用日志服务,则肯定不存在日志库。可以在 template.yml 像定义函数服务一样,通过 Resource 来定义日志资源。

前面也提到,函数日志是配置到对应的服务上的,具体配置也很简单,就是通过函数服务的 LogConfig 属性来配置。

完整的 template.yml 如下:
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
log-yunzhi: # 日志项目名称为 log-yunzhi
Type: 'Aliyun::Serverless::Log' # 表示该资源是阿里云的日志服务
Properties:
Description: 'yunzhi function service log project'
log-yunzhi-store: # 日志的 logstore
Type: 'Aliyun::Serverless::Log::Logstore'
Properties:
TTL: 10
ShardCount: 1
log-another-logstore: # 日志的另一个 logstore
Type: 'Aliyun::Serverless::Log::Logstore'
Properties:
TTL: 10
ShardCount: 1
yunzhi:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'helloworld'
LogConfig: # 配置函数的日志
Project: 'log-yunzhi' # 存储函数日志 SLS 项目: log-yunzhi
Logstore: 'log-yunzhi-store' # 存储函数日志的 SLS logstore: log-yunzhi-store
alarm:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8
CodeUri: './'
Events:
TimeTrigger:
Type: Timer
Properties:
CronExpression: "0 0/1 [i] [/i] [i] [/i]"
Enable: true

在上面的配置中,就定义了名为 log-yunzhi 的日志项目(Project),并且在该 Project 中创建了两个日志仓库(LogStore):log-yunzhi-store 和 log-yunzhi-store。一个 Project 可以包含多个 LogStore。

注意:日志项目的名称必须全局唯一。 即配置中,og-yunzhi 这个项目名称是全局唯一的。

执行 fun deploy 之后,就会自动在函数服务对应的地域创建日志 Project 及日志 logstore,同时也会自动为 logstore 加上全文索引,然后自动为函数服务配置日志仓库。

之后函数的运行日志都会存储在对应的 logstore 里。
$ fun deploy
using region: cn-shanghai
using accountId: ***********4698
using accessKeyId: ***********UfpF
using timeout: 60

Waiting for log service project log-yunzhi to be deployed...
Waiting for log service logstore log-yunzhi-store to be deployed...
retry 1 times
Waiting for log service logstore log-yunzhi-store default index to be deployed...
log service logstore log-yunzhi-store default index deploy success
log serivce logstore log-yunzhi-store deploy success
Waiting for log service logstore log-another-logstore to be deployed...
Waiting for log service logstore log-another-logstore default index to be deployed...
log service logstore log-another-logstore default index deploy success
log serivce logstore log-another-logstore deploy success
log serivce project log-yunzhi deploy success

Waiting for service yunzhi to be deployed...
Waiting for function alarm to be deployed...
Waiting for packaging function alarm code...
package function alarm code done
Waiting for Timer trigger TimeTrigger to be deployed...
function TimeTrigger deploy success
function alarm deploy success
service yunzhi deploy success


如果日志库已经存在,且定义了日志资源,则 fun deploy 会按照 template.yml 中的配置更新日志库。
##存在日志库
如果日志库已经存在,即已经在日志服务中创建了日志项目 Project 和日志库 Logstore ,就可以直接为函数服务添加 LogConfig,不用再定义日志资源。

注意,日志库需要和函数服务在同一个地域 Region。否则不能部署成功。

下面是一个配置函数日志到已经存在的 Project 和 Logstore 中的例子。
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
yunzhi:
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'helloworld'
LogConfig: # 配置函数的日志
Project: 'log-yunzhi-exist' # 存储函数日志到已经存在的 Project: log-yunzhi-exist
Logstore: 'logstore-exist' # 存储函数日志到已经存在的 logstore: logstore-exist
alarm:
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8
CodeUri: './'
Events:
TimeTrigger:
Type: Timer
Properties:
CronExpression: "0 0/1 [i] [/i] [i] [/i]"
Enable: true

如果日志库和函数服务不在同一个地域,函数服务就会找不到日志库,fun deploy 也会报错。如下所示,yunzhi-log-qingdao 是我创建的一个青岛地域的日志 Project。
$ fun deploy
using region: cn-shanghai
using accountId: ***********4698
using accessKeyId: ***********UfpF
using timeout: 60

Waiting for service yunzhi to be deployed...
retry 1 times
retry 2 times
retry 3 times
retry 4 times
retry 5 times
retry 6 times
retry 7 times
PUT /services/yunzhi failed with 400. requestid: 6af2afb8-cbd9-0d3e-bf16-fe623834b4ee, message: project 'yunzhi-log-qingdao' does not exist.

#其他问题
##子账号 AccessDenied
如果是使用 RAM 子账号来开发、部署函数计算,则 fun 工具的配置中 Aliyun Access Key ID Aliyun Secret Access Key 是对应子账户的信息,但 Aliyun Account ID 还是主账号的信息。RAM 子账号有一个 UID,这个不是 Account ID。

如果 Aliyun Account ID 写错了,则使用 fun 或 fcli 的时候,可能会遇到下面的错误:
Error: {
"HttpStatus": 403,
"RequestId": "b8eaff86-e0c1-c7aa-a9e8-2e7893acd545",
"ErrorCode": "AccessDenied",
"ErrorMessage": "The service or function doesn't belong to you."
}

##代码版本的管理
在实现报警功能的过程中,我依旧使用了 GitLab 来存储代码。每次开发完成之后,将代码 push 到 GitLab,然后再将代码部署到函数计算上。不过这两个过程是独立的,还是不那么方便。
##环境问题
一般我们开发的时候,需要日常、预发、线上多个环境部署、测试。阿里云函数计算是一个云产品,没有环境的区分。但对于报警整个功能,我也没有去区分环境,只是本地开发的时候,将报警消息发到一个测试的钉钉群,所以也没有特别去关注。
##经济成本
使用函数计算的经济成本,相比于购买云服务器部署应用,成本低了非常多。

本文中涉及到函数计算和日志服务两个云产品,都有一定的免费额度。其中函数计算每月前 100 万次函数调用免费,日志服务每月也有 500M 的免费存储空间和读写流量。所以只用来测试或者实现一些调用量很小的功能,基本是免费的。
#总结
配置了函数的日志之后,将函数部署到函数计算上,就算是正式发布上线了。

现在回过头来看,整个流程还算比较简单。但从零开始一步一步到部署上线的过程,还是遇到了很多问题。比如文中的许多注意事项,都是在不断尝试中得出的结论。

最近 serverless 这个话题也很火热,也期待这个技术即将带来的变革。

原文链接:https://github.com/nodejh/nodejh.github.io/issues/48

Serverless 掀起新的前端技术变革

大卫 发表了文章 • 0 个评论 • 212 次浏览 • 2019-05-31 17:48 • 来自相关话题

最近关于 Serverless 的讨论越来越多。看似与前端关系不大的 Serverless,其实早已和前端有了颇深渊源,并且将掀起新的前端技术变革。本文主要就根据个人理解和总结,从前端开发模式的演进、基于 Serverless 的前端开发案例以及 Server ...查看全部
最近关于 Serverless 的讨论越来越多。看似与前端关系不大的 Serverless,其实早已和前端有了颇深渊源,并且将掀起新的前端技术变革。本文主要就根据个人理解和总结,从前端开发模式的演进、基于 Serverless 的前端开发案例以及 Serverless 开发最佳实践等方面,与大家探讨 Serverless 中的前端开发模式。
##前端开发模式的演进
首先回顾一下前端开发模式的演进,我觉得主要有四个阶段。

* 基于模板渲染的动态页面
* 基于 AJAX 的前后端分离
* 基于 Node.js 的前端工程化
* 基于 Node.js 的全栈开发

##基于模板渲染的动态页面
在早起的互联网时代,我们的网页很简单,就是一些静态或动态的页面,主要目的是用来做信息的展示和传播。这个时候开发一个网页也很容易,主要就是通过 JSP、PHP 等技术写一些动态模板,然后通过 Web Server 将模板解析成一个个 HTML 文件,浏览器只负责渲染这些 HTML 文件。这个阶段还没有前后端的分工,通常是后端工程师顺便写了前端页面。
##基于 AJAX 的前后端分离
2005 年 AJAX 技术的正式提出,翻开了 Web 开发的新篇章。基于 AJAX,我们可以把 Web 分为前端和后端,前端负责界面和交互,后端负责业务逻辑的处理。前后端通过接口进行数据交互。我们也不再需要在各个后端语言里面写着难以维护的 HTML。网页的复杂度也由后端的 Web Server 转向了浏览器端的 JavaScript。也正因如此,开始有了前端工程师这个职位。
##基于 Node.js 的前端工程化
2009年 Node.js 的出现,对于前端工程师来说,也是一个历史性的时刻。随着 Node.js 一同出现的还有 CommonJS 规范和 npm 包管理机制。随后也出现了 Grunt、Gulp、Webpack 等一系列基于 Node.js 的前端开发构建工具。

在 2013 年前后,前端三大框架 React.js/Angular/Vue.js 相继发布第一个版本。我们可以从以往基于一个个页面的开发,变为基于一个个组件进行开发。开发完成后使用 webpack 等工具进行打包构建,并通过基于 Node.js 实现的命令行工具将构建结果发布上线。前端开发开始变得规范化、标准化、工程化。
##基于 Node.js 的全栈开发
Node.js 对前端的重要意义还有,以往只能运行在浏览器中的 JavaScript 也可以运行在服务器上,前端工程师可以用自己最熟悉的语言来写服务端的代码。于是前端工程师开始使用 Node.js 做全栈开发,开始由前端工程师向全栈工程师的方向转变。这是前端主动突破自己的边界。

另一方面,前端在发展,后端也在发展。也差不多在 Node.js 诞生那个时代,后端普遍开始由巨石应用模式由微服务架构转变。这也就导致以往的前后端分工出现了分歧。随着微服务架构的兴起,后端的接口渐渐变得原子性,微服务的接口也不再直接面向页面,前端的调用变得复杂了。于是 BFF(Backend For Frontend)架构应运而生,在微服务和前端中间,加了一个 BFF 层,由 BFF 对接口进行聚合、裁剪后,再输出给前端。而 BFF 这层不是后端本质工作,且距离前端最近和前端关系最大,所以前端工程师自然而然选择了 Node.js 来实现。这也是当前 Node.js 在服务端较为广泛的应用。
##下一代前端开发模式
可以看到,每一次前端开发模式的变化,都因某个变革性的技术而起。先是 AJAX,而后是 Node.js。那么下一个变革性的技术是什么?不言而喻,就是 Serverless。如果你想和更多 Serverless 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
#Serverless 服务中的前端解决方案
##Serverless 简介
根据 CNCF 的定义,Serverless 是指构建和运行不需要服务器管理的应用程序的概念。(serverless-overview)

Serverless computing refers to the concept of building and running applications that do not require server management. --- CNCF



其实 Serverless 早已和前端产生了联系,只是我们可能没有感知。比如 CDN,我们把静态资源发布到 CDN 之后,就不需要关心 CDN 有多少个节点、节点如何分布,也不需要关心它如何做负载均衡、如何实现网络加速,所以 CDN 对前端来说是 Serverless。再比如对象存储,和 CDN 一样,我们只需要将文件上传到对象存储,就可以直接使用了,不需要关心它如何存取文件、如何进行权限控制,所以对象存储对前端工程师来说是 Serverless。甚至一些第三方的 API 服务,也是 Serverless,因为我们使用的时候,不需要去关心服务器。

当然,有了体感还不够,我们还是需要一个更精确的定义。从技术角度来说,Serverless 就是 FaaS 和 BaaS 的结合。

Serverless = FaaS + BaaS。
1.jpg

简单来讲,FaaS(Function as a Service) 就是一些运行函数的平台,比如阿里云的函数计算、AWS 的 Lambda 等。

BaaS(Backend as a Service)则是一些后端云服务,比如云数据库、对象存储、消息队列等。利用 BaaS,可以极大简化我们的应用开发难度。

Serverless 则可以理解为运行在 FaaS 中的,使用了 BaaS 的函数。

Serverless 的主要特点有:

* 事件驱动
* 函数在 FaaS 平台中,需要通过一系列的事件来驱动函数执行。
* 无状态
* 因为每次函数执行,可能使用的都是不同的容器,无法进行内存或数据共享。如果要共享数据,则只能通过第三方服务,比如 Redis 等。
* 无运维
* 使用 Serverless 我们不需要关心服务器,不需要关心运维。这也是 Serverless 思想的核心。
* 低成本
* 使用 Serverless 成本很低,因为我们只需要为每次函数的运行付费。函数不运行,则不花钱,也不会浪费服务器资源

##Serverless 服务中的前端解决方案架构图
2.jpg

上图是当前主要的一些 Serverless 服务,以及对应的前端解决方案。

从下往上,分别是基础设施和开发工具。

基础设施主要是一些云计算厂商提供,包括云计算平台和各种 BaaS 服务,以及运行函数的 FaaS 平台。

前端主要是 Serverless 的使用者,所以对前端来说,最重要的开发工具这一层,我们需要依赖开发工具进行 Serverless 开发、调试和部署。
##框架(Framework)
如今还没有一个统一的 Serverless 标准,不同云计算平台提供的 Serverless 服务很可能是不一样的,这就导致我们的代码,无法平滑迁移。Serverless 框架一个主要功能是简化 Serverless 开发、部署流程,另一主要功能则是屏蔽不同 Serverless 服务中的差异,让我们的函数能够在不改动或者只改动很小一部分的情况下,在其他 Serverless 服务中也能运行。

常见的 Serverless 框架有 Serverless Framework、ZEIT Now、Apex 等。不过这些基本都是国外公司做的,国内还没有这样的平台。
##Web IDE
和 Serverless 紧密相关的 Web IDE 主要也是各个云计算平台的 Web IDE。利用 Web IDE,我们可以很方便地在云端开发、调试函数,并且可以直接部署到对应的 FaaS 平台。这样的好处是避免了在本地安装各种开发工具、配置各种环境。常见的 Web IDE 有 AWS 的 Cloud9、阿里云的函数计算 Web IDE、腾讯云的 Cloud Studio。从体验上来说,AWS Cloud9 最好。
##命令行工具
当然,目前最主要的开发方式还是在本地进行开发。所以在本地开发 Serverless 的命令行工具也必不可少。

命令行工具主要有两类,一类是云计算平台提供的,如 AWS 的 aws、 Azure 的 az、阿里云的 fun;还有一类是 Serverless 框架提供的,如 serverless、now。

大部分工具如 serverless、fun 等,都是用 Node.js 实现的。

下面是几个命令行工具的例子。
##创建
# serverless
$ serverless create --template aws-nodejs --path myService
# fun
$ fun init -n qcondemo helloworld-nodejs8

##部署
# serverless
$ serverless deploy
# fun
$ fun deploy

##调试
# serverless
$ serverless invoke [local] --function functionName
# fun
$ fun local invoke functionName

##应用场景
在开发工具上面一层,则是 Serverless 的一些垂直应用场景。除了使用传统的服务端开发,目前使用 Serverless 技术的还有小程序开发,未来可能还会设计物联网领域(IoT)。
##不同 Serverless 服务的对比
3.jpg

上图从支持语言、触发器、价格等多个方面对不同 Serverless 服务进行了对比,可以发现有差异,也有共性。

比如几乎所有 Serverless 服务都支持 Node.js/Python/Java 等语言。

从支持的触发器来看,几乎所有服务也都支持 HTTP、对象存储、定时任务、消息队列等触发器。当然,这些触发器也与平台自己的后端服务相关,比如阿里云的对象存储触发器,是基于阿里云的 OSS 产品的存取等事件触发的;而 AWS 的对象存储触发器,则是基于 AWS 的 S3 的事件触发的,两个平台并不通用。这也是当前 Serverless 面临的一个问题,就是标准不统一。

从计费的角度来看,各个平台的费用基本一致。在前面也提到,Serverless 的计费是按调用次数计费。对于各个 Serverless,每个月都有 100 万次的免费调用次数,之后差不多 ¥1.3/百万次;以及 400,000 GB-s 的免费执行时间,之后 ¥0.0001108/GB-s。所以在应用体量较小的时候,使用 Serverless 是非常划算的。
#基于 Serverless 的前端开发模式
在本章节,主要以几个案例来说明基于 Serverless 的前端开发模式,以及它和以往的前端开发有什么不一样。

在开始具体的案例之前,先看一下传统开发流程。
4.png

在传统开发流程中,我们需要前端工程师写页面,后端工程师写接口。后端写完接口之后,把接口部署了,再进行前后端联调。联调完毕后再测试、上线。上线之后,还需要运维工程师对系统进行维护。整个过程涉及多个不同角色,链路较长,沟通协调也是一个问题。

而基于 Serverless,后端变得非常简单了,以往的后端应用被拆分为一个个函数,只需要写完函数并部署到 Serverless 服务即可,后续也不用关心任何服务器的运维操作。后端开发的门槛大幅度降低了。因此,只需要一个前端工程师就可以完成所有的开发工作。
5.png

当然,前端工程师基于 Serverless 去写后端,最好也需要具备一定的后端知识。涉及复杂的后端系统或者 Serverless 不适用的场景,还是需要后端开发,后端变得更靠后了。
##基于 Serverless 的 BFF
一方面,对不同的设备需要使用不同的 API,另一方面,由于微服务导致前端接口调用的复杂,所以前端工程师开始使用 BFF 的方式,对接口进行聚合裁剪,以得到适用于前端的接口。

下面是一个通用的 BFF 架构。
6.jpg

最底层的就是各种后端微服务,最上层就是各种前端应用。在微服务和应用之前,就是通常由前端工程师开发的 BFF。

这样的架构解决了接口协调的问题,但也带来了一些新的问题。

比如针对每个设备开发一个 BFF 应用,也会面临一些重复开发的问题。而且以往前端只需要开发页面,关注于浏览器端的渲染即可,现在却需要维护各种 BFF 应用。以往前端也不需要关心并发,现在并发压力却集中到了 BFF 上。总的来说运维成本非常高,通常前端并不擅长运维。

Serverless 则可以帮我们很好的解决这些问题。基于 Serverless,我们可以使用一个个函数来实各个接口的聚合裁剪。前端向 BFF 发起的请求,就相当于是 FaaS 的一个 HTTP 触发器,触发一个函数的执行,这个函数中来实现针对该请求的业务逻辑,比如调用多个微服务获取数据,然后再将处理结果返回给前端。这样运维的压力,就由以往的 BFF Server 转向了 FaaS 服务,前端再也不用关心服务器了。
7.jpg

上图则是基于 Serverless 的 BFF 架构。为了更好的管理各种 API,我们还可以添加网关层,通过网关来管理所有 API(比如阿里云的网关),比如对 API 进行分组、分环境。基于 API 网关,前端就不直接通过 HTTP 触发器来执行函数,而是将请求发送至网关,再由网关去触发具体的函数来执行。
##基于 Serverless 的服务端渲染
基于当下最流行的三大前端框架(React.js/Anguler/Vue.js),现在的渲染方式大部分都是客户端渲染。页面初始化的时候,只加载一个简单 HTML 以及对应的 JS 文件,再由 JS 来渲染出一个个页面。这种方式最主要的问题就是白屏时间和 SEO。

为了解决这个问题,前端又开始尝试服务端渲染。本质思想其实和最早的模板渲染是一样的。都是前端发起一个请求,后端 Server 解析出一个 HTML 文档,然后再返回给浏览器。只不过以往是 JSP、PHP 等服务端语言的模板,现在是基于 React、Vue 等实现的同构应用,这也是如今的服务端渲染方案的优势。

但服务端渲染又为前端带来了一些额外的问题:运维成本,前端需要维护用于渲染的服务器。

Serverless 最大的优点就是可以帮我们减少运维,那 Serverless 能不能用于服务端渲染呢?当然也是可以的。

传统的服务端渲染,每个请求的 path 都对应着服务端的每个路由,由该路由实现对应 path 的 HTML 文档渲染。用于渲染的服务端程序,就是这些集成了这些路由的应用。

使用 Serverless 来做服务端渲染,就是将以往的每个路由,都拆分为一个个函数,再在 FaaS 上部署对应的函数。这样用户请求的 path,对应的就是每个单独的函数。通过这种方式,就将运维操作转移到了 FaaS 平台,前端做服务端渲染,就不用再关心服务端程序的运维部署了。
8.jpg

ZEIT 的 Next.js 就对基于 Serverless 的服务端渲染做了很好的实现。下面就是一个简单的例子。

代码结构如下:
.
├── next.config.js
├── now.json
├── package.json
└── pages
├── about.js
└── index.js
// next.config.js
module.exports = {
target: 'serverless'
}

其中 pages/about.js 和 pages/index.js 就是两个页面,在 next.config.js 配置了使用 Zeit 提供的 Serverless 服务。

然后使用 now 这个命令,就可以将代码以 Serverless 的方式部署。部署过程中,pages/about.js 和 pages/index.js 就分别转换为两个函数,负责渲染对应的页面。
9.jpg

##基于 Serverless 的小程序开发
目前国内使用 Serverless 较多的场景可能就是小程开发了。具体的实现就是小程序云开发,支付宝小程序和微信小程序都提供了云开发功能。

在传统的小程序开发中,我们需要前端工程师进行小程序端的开发;后端工程师进行服务端的开发。小程序的后端开发和其他的后端应用开发,本质是是一样的,需要关心应用的负载均衡、备份冗灾、监控报警等一些列部署运维操作。如果开发团队人很少,可能还需要前端工程师去实现服务端。

但基于云开发,就只需要让开发者关注于业务的实现,由一个前端工程师就能够完成整个应用的前后端开发。因为云开发将后端封装为了 BaaS 服务,并提供了对应的 SDK 给开发者,开发者可以像调用函数一样使用各种后端服务。应用的运维也转移到了提供云开发的服务商。
10.jpg

下面分别是使用支付宝云开发(Basement)的一些例子,函数就是定义在 FaaS 服务中的函数。
##操作数据库
// `basement` 是一个全局变量
// 操作数据库
basement.db.collection('users')
.insertOne({
name: 'node',
age: 18,
})
.then(() => {
resolve({ success: true });
})
.catch(err => {
reject({ success: false });
});

##上传图片
// 上传图片
basement.file
.uploadFile(options)
.then((image) => {
this.setData({
iconUrl: image.fileUrl,
});
})
.catch(console.error);

##调用函数
// 调用函数
basement.function
.invoke('getUserInfo')
.then((res) => {
this.setData({
user: res.result
});
})
.catch(console.error}

##通用 Serverless 架构
基于上述几个 Serverless 开发的例子,就可以总结出一个通用的 Serverless 架构。
11.jpg

其中最底层就是实现复杂业务的后端微服务(Backend)。然后 FaaS 层通过一系列函数实现业务逻辑,并为前端直接提供服务。对于前端开发者来说,前端可以通过编写函数的方式来实现服务端的逻辑。对于后端开发者来说,后端变得更靠后了。如果业务比较较淡,FaaS 层能够实现,甚至也不需要微服务这一层了。

同时不管是在后端、FaaS 还是前端,我们都可以去调用云计算平台提供的 BaaS 服务,大大降低开发难度、减少开发成本。小程序云开发,就是直接在前端调用 BaaS 服务的例子。
#Serverless 开发最佳实践
基于 Serverless 开发模式和传统开发模式最大的不同,就是传统开发中,我们是基于应用的开发。开发完成后,我们需要对应用进行单元测试和集成测试。而基于 Serverless,开发的是一个个函数,那么我们应该如何对 Serverless 函数进行测试?Serverless 函数的测试和普通的单元测试又有什么区别?

还有一个很重要的点是,基于 Serverless 开发的应用性能如何?应该怎么去提高 Serverless 应用的性能?

本章主要就介绍一下,基于 Serverless 的函数的测试和函数的性能两个方面的最佳实践。
##函数的测试
虽然使用 Serverless 我们可以简单地进行业务的开发,但它的特性也给我们的测试带来了一些挑战。主要有以下几个方面。

Serverless 函数是分布式的,我们不知道也无需知道函数是部署或运行在哪台机器上,所以我们需要对每个函数进行单元测试。Serverless 应用是由一组函数组成的,函数内部可能依赖了一些别的后端服务(BaaS),所以我们也需要对 Serverless 应用进行集成测试。

运行函数的 FaaS 和 BaaS 在本地也难以模拟。除此之外,不同平台提供的 FaaS 环境可能不一致,不平台提供的 BaaS 服务的 SDK 或接口也可能不一致,这不仅给我们的测试带来了一些问题,也增加了应用迁移成本。

函数的执行是由事件驱动的,驱动函数执行的事件,在本地也难以模拟。

那么如何解决这些问题呢?

根据 Mike Cohn 提出的测试金字塔,单元测试的成本最低,效率最高;UI 测试(集成)测试的成本最高,效率最低,所以我们要尽可能多的进行单元测试,从而减少集成测试。这对 Serverless 的函数测试同样适用。
12.jpg

为了能更简单对函数进行单元测试,我们需要做的就是将业务逻辑和函数依赖的 FaaS(如函数计算) 和 BaaS (如云数据库)分离。当 FaaS 和 BaaS 分离出去之后,我们就可以像编写传统的单元测试一样,对函数的业务逻辑进行测试。然后再编写集成测试,验证函数和其他服务的集成是否正常工作。
##一个糟糕的例子
下面是一个使用 Node.js 实现的函数的例子。该函数做的事情就是,首先将用户信息存储到数据库中,然后给用户发送邮件。
const db = require('db').connect();
const mailer = require('mailer');

module.exports.saveUser = (event, context, callback) => {
const user = {
email: event.email,
created_at: Date.now()
}

db.saveUser(user, function (err) {
if (err) {
callback(err);
} else {
mailer.sendWelcomeEmail(event.email);
callback();
}
});
};

这个例子主要存在两个问题:

  1. 业务逻辑和 FaaS 耦合在一起。主要就是业务逻辑都在 saveUser 这个函数里,而 saveUser 参数的 event 和 conent 对象,是 FaaS 平台提供的。
  2. 业务逻辑和 BaaS 耦合在一起。具体来说,就是函数内使用了 db 和 mailer 这两个后端服务,测试函数必须依赖于 db 和 mailer。

##编写可测试的函数
基于将业务逻辑和函数依赖的 FaaS 和 BaaS 分离的原则,对上面的代码进行重构。
class Users {
constructor(db, mailer) {
this.db = db;
this.mailer = mailer;
}

save(email, callback) {
const user = {
email: email,
created_at: Date.now()
}

this.db.saveUser(user, function (err) {
if (err) {
callback(err);
} else {
this.mailer.sendWelcomeEmail(email);
callback();
}
});
}
}

module.exports = Users;
const db = require('db').connect();
const mailer = require('mailer');
const Users = require('users');

let users = new Users(db, mailer);

module.exports.saveUser = (event, context, callback) => {
users.save(event.email, callback);
};

在重构后的代码中,我们将业务逻辑全都放在了 Users 这个类里面,Users 不依赖任何外部服务。测试的时候,我们也可以不传入真实的 db 或 mailer,而是传入模拟的服务。

下面是一个模拟 mailer 的例子。
// 模拟 mailer
const mailer = {
sendWelcomeEmail: (email) => {
console.log(`Send email to ${email} success!`);
},
};

这样只要对 Users 进行充分的单元测试,就能确保业务代码如期运行。

然后再传入真实的 db 和 mailer,进行简单的集成测试,就能知道整个函数是否能够正常工作。

重构后的代码还有一个好处是方便函数的迁移。当我们想要把函数从一个平台迁移到另一个平台的时候,只需要根据不同平台提供的参数,修改一下 Users 的调用方式就可以了,而不用再去修改业务逻辑。
##小结
综上所述,对函数进行测试,就需要牢记金字塔原则,并遵循以下原则:

  1. 将业务逻辑和函数依赖的 FaaS 和 BaaS 分离
  2. 对业务逻辑进行充分的单元测试
  3. 将函数进行集成测试验证代码是否正常工作

##函数的性能
使用 Serverless 进行开发,还有一个大家都关心的问题就是函数的性能怎么样。

对于传统的应用,我们的程序启动起来之后,就常驻在内存中;而 Serverless 函数则不是这样。

当驱动函数执行的事件到来的时候,首先需要下载代码,然后启动一个容器,在容器里面再启动一个运行环境,最后才是执行代码。前几步统称为冷启动(Cold Start)。传统的应用没有冷启动的过程。

下面是函数生命周期的示意图:
13.jpg

冷启动时间的长短,就是函数性能的关键因素。优化函数的性能,也就需要从函数生命周期的各个阶段去优化。
##不同编程语言对冷启动时间的影响
在此之前,已经有很多人测试过不同编程语言对冷启动时间的影响,比如:

* Compare coldstart time with different languages, memory and code sizes -by Yan Cui
* Cold start / Warm start with AWS Lambda - by Erwan Alliaume
* Serverless: Cold Start War - by Mikhail Shilkov

14.jpg

从这些测试中能够得到一些统一的结论:

* 增加函数的内存可以减少冷启动时间
* C#、Java 等编程语言的能启动时间大约是 Node.js、Python 的 100 倍

基于上述结论,如果想要 Java 的冷启动时间达到 Node.js 那么小,可以为 Java 分配更大的内存。但更大的内存意味着更多的成本。
##函数冷启动的时机
刚开始接触 Serverless 的开发者可能有一个误区,就是每次函数执行,都需要冷启动。其实并不是这样。

当第一次请求(驱动函数执行的事件)来临,成功启动运行环境并执行函数之后,运行环境会保留一段时间,以便用于下一次函数执行。这样就能减少冷启动的次数,从而缩短函数运行时间。当请求达到一个运行环境的限制时,FaaS 平台会自动扩展下一个运行环境。
15.jpg

以 AWS Lambda 为例,在执行函数之后,Lambda 会保持执行上下文一段时间,预期用于另一次 Lambda 函数调用。其效果是,服务在 Lambda 函数完成后冻结执行上下文,如果再次调用 Lambda 函数时 AWS Lambda 选择重用上下文,则解冻上下文供重用。

下面以两个小测试来说明上述内容。

我使用阿里云的函数计算实现了一个 Serverless 函数,并通过 HTTP 事件来驱动。然后使用不同并发数向函数发起 100 个请求。

首先是一个并发的情况:
16.jpg

可以看到第一个请求时间为 302ms,其他请求时间基本都在 50ms 左右。基本就能确定,第一个请求对应的函数是冷启动,剩余 99 个请求,都是热启动,直接重复利用了第一个请求的运行环境。

接下来是并发数为 10 的情况:
17.jpg

可以发现,前 10 个请求,耗时基本在 200ms-300ms,其余请求耗时在 50ms 左右。于是可以得出结论,前 10 个并发请求都是冷启动,同时启动了 10 个运行环境;后面 90 个请求都是热启动。

这也就印证了之前的结论,函数不是每次都冷启动,而是会在一定时间内复用之前的运行环境。
##执行上下文重用
上面的结论对我们提高函数性能有什么帮助呢?当然是有的。既然运行环境能够保留,那就意味着我们能对运行环境中的执行上下文进行重复利用。

来看一个例子:
const mysql = require('mysql');

module.exports.saveUser = (event, context, callback) => {

// 初始化数据库连接
const connection = mysql.createConnection({ /[i] ... [/i]/ });
connection.connect();

connection.query('...');

};

上面例子实现的功能就是在 saveUser 函数中初始化一个数据库连接。这样的问题就是,每次函数执行的时候,都会重新初始化数据库连接,而连接数据库又是一个比较耗时的操作。显然这样对函数的性能是没有好处的。

既然在短时间内,函数的执行上下文可以重复利用,那么我们就可以将数据库连接放在函数之外:
const mysql = require('mysql');

// 初始化数据库连接
const connection = mysql.createConnection({ /[i] ... [/i]/ });
connection.connect();


module.exports.saveUser = (event, context, callback) => {

connection.query('...');

};

这样就只有第一次运行环境启动的时候,才会初始化数据库连接。后续请求来临、执行函数的时候,就可以直接利用执行上下文中的 connection,从而提后续高函数的性能。

大部分情况下,通过牺牲一个请求的性能,换取大部分请求的性能,是完全可以够接受的。
##给函数预热
既然函数的运行环境会保留一段时间,那么我们也可以通过主动调用函数的方式,隔一段时间就冷启动一个运行环境,这样就能使得其他正常的请求都是热启动,从而避免冷启动时间对函数性能的影响。

这是目前比较有效的方式,但也需要有一些注意的地方:

  1. 不要过于频繁调用函数,至少频率要大于 5 分钟
  2. 直接调用函数,而不是通过网关等间接调用
  3. 创建专门处理这种预热调用的函数,而不是正常业务函数

这种方案只是目前行之有效且比较黑科技的方案,可以使用,但如果你的业务允许“牺牲第一个请求的性能换取大部分性能”,那也完全不必使用该方案。

##小结
总体而言,优化函数的性能就是优化冷启动时间。上述方案都是开发者方面的优化,当然还一方面主要是 FaaS 平台的性能优化。

总结一下上述方案,主要是以下几点:

  1. 选用 Node.js / Python 等冷启动时间短的编程语言
  2. 为函数分配合适的运行内存
  3. 执行上下文重用
  4. 为函数预热

#总结
作为前端工程师,我们一直在探讨前端的边界是什么。现在的前端开发早已不是以往的前端开发,前端不仅可以做网页,还可以做小程序,做 APP,做桌面程序,甚至做服务端。而前端之所以在不断拓展自己的边界、不断探索更多的领域,则是希望自己能够产生更大的价值。最好是用我们熟悉的工具、熟悉的方式来创造价值。

而 Serverless 架构的诞生,则可以最大程度帮助前端工程师实现自己的理想。使用 Serverless,我们不需要再过多关注服务端的运维,不需要关心我们不熟悉的领域,我们只需要专注于业务的开发、专注于产品的实现。我们需要关心的事情变少了,但我们能做的事情更多了。

Serverless 也必将对前端的开发模式产生巨大的变革,前端工程师的职能也将再度回归到应用工程师的职能。

如果要用一句话来总结 Serverless,那就是 Less is More。

原文链接:https://zhuanlan.zhihu.com/p/65914436

一个未打补丁的漏洞影响所有版本的 Docker !

齐达内 发表了文章 • 0 个评论 • 236 次浏览 • 2019-05-31 15:49 • 来自相关话题

所有版本的Docker目前都容易受到一种竞态条件(race condition)的攻击,这种竞态条件可能使攻击者对主机系统上的任何文件拥有读取权限和写入权限。概念验证代码已发布。 该漏洞类似CVE-2018-15664,它为黑客修改资 ...查看全部
所有版本的Docker目前都容易受到一种竞态条件(race condition)的攻击,这种竞态条件可能使攻击者对主机系统上的任何文件拥有读取权限和写入权限。概念验证代码已发布。

该漏洞类似CVE-2018-15664,它为黑客修改资源路径提供了一个机会窗口,这个机会窗口出现在路径被解析之后,但被分配的程序开始对资源进行操作之前的时间点。这被称为检查时间/使用时间(TOCTOU)类型的漏洞。
##访问主机上的文件
究其核心,该漏洞源于FollowSymlinkInScope函数,该函数容易受到基本的TOCTOU攻击的影响。这个函数的目的是如同进程在Docker容器的内部那样对待进程,以一种安全的方式来解析指定的路径。

并不立即针对解析的路径进行操作,而是“稍微过一段时间进行操作”。攻击者可以推测这个时间差,添加一条符号链接(symlink)路径,该路径可能最终解析拥有root权限的主机。

这可以通过“docker cp”实用程序来实现,该实用程序允许在容器和本地文件系统之间复制内容。早在2014年就出现过类似的漏洞。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

Suse的高级软件工程师Aleksa Sarai在安全公告中写道:“据我所知,对这种攻击没有任何有意义的防护(除了不允许对运行中的容器执行docker cp,但这只有助于应对通过FollowSymlinkInScope实现的特定攻击。)除非你通过AppArmor限制了Docker守护进程,否则它会影响主机文件系统。”
##应对方法和漏洞脚本
在Hacker News网站上,一则讨论帖子提供了潜在的应对方法,不过它们有赖于实际环境的背景和目的。

Sarai提议的一种应对办法是修改“chrootarchive”,以便归档操作在安全的环境中运行,其中root是容器“rootfs”。这需要更改Docker的核心部分,因此这不可行。

其次的办法是在使用文件系统时暂停容器。这无法阻止所有攻击,但可以防御较基本的攻击。补丁已向上游提交,目前仍在审核中。

这位工程师还编写了两个漏洞脚本:一个脚本用于读取访问,另一个用于写入访问,其中二进制代码通过运行“a RENAME_EXCHANGE of a symlink to "/" and an empty directory in a loop”,试图达到竞态条件。Sarai称,这两个脚本的目的是将文件复制到含有修改后的符号链接的路径,或从该路径复制文件。

从处于竞态条件下的主机系统读取任意内容的攻击代码其成功率不到1%。这个成功率可能看起来很低,但实际上相当于等待10秒钟攻击就能得逞。

有了将资源写入到主机上的脚本,就有可能“仅用极少的迭代就可以覆盖主机文件系统”。

在公开宣布这个漏洞之前,Sarai与Docker安全团队讨论了这个问题,最后得出了披露是合理的这一结论。

原文链接:https://mp.weixin.qq.com/s/1d18fBAKJj5LYymSzpFECA

CoDo开源一站式DevOps平台

尼古拉斯 发表了文章 • 0 个评论 • 177 次浏览 • 2019-05-31 15:37 • 来自相关话题

有幸参与到CoDo项目的开发,这是一个非常棒的一站式开源运维平台,分享给大家。 #平台介绍 CODO是一款为用户提供企业多混合云、自动化运维、完全开源的云管理平台。 CODO前端基于Vue iview开 ...查看全部
有幸参与到CoDo项目的开发,这是一个非常棒的一站式开源运维平台,分享给大家。
#平台介绍

CODO是一款为用户提供企业多混合云、自动化运维、完全开源的云管理平台。

CODO前端基于Vue iview开发、为用户提供友好的操作界面,增强用户体验。

CODO后端基于Python Tornado开发,其优势为轻量、简洁清晰、异步非阻塞。

CODO开源多云管理平台将为用户提供多功能:ITSM、基于RBAC权限系统、Web Terminnal登陆日志审计、录像回放、强大的作业调度系统、CMDB、监控报警系统、DNS管理、配置中心等。如果你想和更多DevOps技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
##产品架构

1.jpg

##产品功能

2.jpg

##模块说明


* 项目前端:基于Vue + Iview-Admin实现的一套后台管理系统
* 管理后端:基于Tornado实现,提供Restful风格的API,提供基于RBAC的完善权限管理,可对所有用户的操作进行审计
* 定时任务:基于Tornado实现,定时任务系统,完全兼容Linux Crontab语法,且支持到秒级
* 任务调度:基于Tornado实现,系统核心调度,可分布式扩展,自由编排任务,自由定义流程,支持多种触发,支持审批审核,支持操作干预
* 资产管理:基于Tornado实现,资产管理系统,支持手动添加资产,同时也支持从AWS/阿里云/腾讯云自动获取资产信息
* 配置中心:基于Tornado实现,可基于不同项目、环境管理配置,支持语法高亮、历史版本差异对比、快速回滚,并提供Restful风格的API
* 域名管理:基于Tornado实现,支持多区域智能解析、可视化Bind操作、操作日志记录
* 运维工具:基于Tornado实现,运维场景中常用的加密解密、事件、故障、项目记录、提醒、报警等

##在线体验

CoDo提供了在线Demo供使用者体验,Demo账号只有部分权限

- 地址:http://demo.opendevops.cn
- 用户:demo
- 密码:2ZbFYNv9WibWcR7GB6kcEY

3.jpg

##推荐理由

团队出品: GitHub上有很多开源的DevOps工具,几乎全部都由个人发布维护,代码质量、版本进度以及可持续性都无法保障,陷入不能用或不敢用的尴尬境地,CoDo非个人项目,由一个团队负责开发维护,有幸我也是团队中一员,参与贡献了部分代码,所以在稳定性和持续性方面更有保证。

生产实践: CoDo核心代码贡献者全部来自于一线运维团队,团队成员从运维需求出发,致力于解决运维痛点,更了解运维的需求,且核心代码经过了多年生产实践,并非实验产品,运行稳定

功能齐全: CoDo采用微服务的理念构建,模块化开发,目前已有资产管理、定时任务、任务调度、配置中心、域名管理、运维工具几大模块,支持持续集成、持续部署、代码审查、数据库审核与优化建议等众多功能,覆盖大部分的运维场景,让你不再费心劳神在多个系统间奔波,一个平台全搞定

完善支持: CoDo除了提供专业的文档支持外,还同时开始录制一些基础的部署使用视频帮助初学者快速上手

开源免费: 这是一个开源项目,所有功能均可免费使用,源码托管在GitHub
##项目地址


* 官网:http://www.opendevops.cn
* GitHub:https://github.com/opendevops-cn
* 文档地址:http://docs.opendevops.cn/zh/latest
* 安装视频:https://www.bilibili.com/video/av53446517

原文链接:https://mp.weixin.qq.com/s/eZX5qoJzuCSuLI98gSCtpw

Docker环境的持续部署优化实践

JetLee 发表了文章 • 0 个评论 • 213 次浏览 • 2019-05-31 14:48 • 来自相关话题

#背景介绍 那年公司快速成长,频繁上线新项目,每上线一个项目,就需要新申请一批机器,初始化,部署依赖的服务环境,一个脚本行天下。 那年项目发展如火如荼,A项目流量暴增马上给A扩机器,B项目上线新功能又要扩容B,上线新 ...查看全部
#背景介绍

那年公司快速成长,频繁上线新项目,每上线一个项目,就需要新申请一批机器,初始化,部署依赖的服务环境,一个脚本行天下。

那年项目发展如火如荼,A项目流量暴增马上给A扩机器,B项目上线新功能又要扩容B,上线新项目没资源了,就先下线处于流量低峰的C项目主机。

每天日夜加班,疲于奔命。

那年得知了Docker能拯救我于水火,遂决定为了荣誉(发际线)而战。

为了快速落地以及尽量降低引入Docker对整个CICD流程的影响,用最小的改动把Docker加入到了我们上线的流程中,流程变化参考下图:

1.png


那年容器编排江湖混战,K8S还不流行,加之时间精力有限,技术实力也跟不上,生产环境没敢贸然上线编排,单纯在之前的主机上跑了Docker,主要解决环境部署和扩容缩容的问题,Docker上线后也确实解决了这两块的问题,还带来了诸如保证开发线上环境一致性等额外惊喜。

但Docker的运用也并不是百利而无一害,将同步代码的方式转变成打包镜像、更新容器也带来了上线时间的增长,同时由于各个环境配置文件的不同也没能完全做到一次打包多环境共用,本文主要介绍我们是如何对这两个问题进行优化的。
#Python多线程使用

分析了部署日志,发现在整个部署过程中造成时间增长的主要原因是下载镜像、重启容器时间较长。

整个部署程序由Python开发,核心思想是用paramiko模块来远程执行ssh命令,在还没有引入Docker的时候,发布是rsyslog同步代码,单线程滚动重启服务,上线Docker后整个部署程序逻辑没有大改,只是把同步代码重启服务给换成了下载镜像重启容器,代码大致如下:
import os
import paramiko

# paramiko.util.log_to_file("/tmp/paramiko.log")
filepath = os.path.split(os.path.realpath(__file__))[0]


class Conn:
def __init__(self, ip, port=22, username='ops'):
self.ip = ip
self.port = int(port)
self.username = username

self.pkey = paramiko.RSAKey.from_private_key_file(
filepath + '/ssh_private.key'
)

def cmd(self, cmd):
ssh = paramiko.SSHClient()

try:
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.ip, self.port, self.username, pkey=self.pkey, timeout=5)
except Exception as err:
data = {"state": 0, "message": str(err)}
else:
try:
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=180)
_err_list = stderr.readlines()

if len(_err_list) > 0:
data = {"state": 0, "message": _err_list}
else:
data = {"state": 1, "message": stdout.readlines()}
except Exception as err:
data = {"state": 0, "message": '%s: %s' % (self.ip, str(err))}
finally:
ssh.close()

return data


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

hostlist = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

for i in hostlist:
print(Conn(i).cmd('docker pull %s' % image_url))
# 在镜像下载完成后进行更新容器的操作,代码类似省略了

全部都是单线程操作,可想效率就不会很高,为什么不用多线程?主要还是考虑到服务的可用性,一台服务器更新完成再更新下一台服务器直到所有服务器更新完成,单线程滚动更新最大程度保证服务可用,如果同时所有服务器进行更新,那么服务重启过程中无法对外提供服务,系统会有宕机的风险,且当时项目规模都很小,忽略掉了这个时间的增加,随着项目越来越多,规模越来越大,不得不重新思考这块的优化。

引入多线程势在必行,那么多线程该如何应用呢?从服务整体可用性考虑,把下载镜像跟重启容器两个操作拆分,下载镜像不影响服务正常提供,完全可以采用多线程,这样整个下载镜像的时间将大大缩短,优化后代码如下:
import threading
# 再导入上一个示例里边的Conn类

class DownloadThread(threading.Thread):

def __init__(self, host, image_url):
threading.Thread.__init__(self)
self.host = host
self.image_url = image_url

def run(self):
Conn(self.host).cmd('docker login -u ops -p coffee hub.ops-coffee.cn')
r2 = Conn(self.host).cmd('docker pull %s' % self.image_url)
if r2.get('state'):
self.alive_host = self.host
print('---->%s镜像下载完成' % self.host)
else:
self.alive_host = None
print('---->%s镜像下载失败,details:%s' % (self.host, r2.get('message')))

def get_result(self):
return self.alive_host


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

hostlist = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

threads = []
for host in hostlist:
t = DownloadThread(host, image_url)
threads.append(t)

for t in threads:
t.start()

for t in threads:
t.join()

alive_host = []
for t in threads:
alive_host.append(t.get_result())
[size=16] 多线程下载镜像结束
[/size]

print('---->本项目共有主机%d台,%d台主机下载镜像成功' % (len(hostlist), len(alive_host)))

重启容器就不能这么简单粗暴的多线程同时重启了,上边也说了,同时重启就会有服务宕机的风险。线上服务器都有一定的冗余,不能同时重启那么可以分批重启嘛,每次重启多少?分析了流量情况,我们想到了一个算法,如果项目主机少于8台,那么就单线程滚动重启,也用不了太长时间,如果项目主机大于8台,那么用项目主机数/8向上取整,作为多线程重启的线程数多线程重启,这样差不多能保证项目里边有80%左右的主机一直对外提供服务,降低服务不可用的风险,优化后的代码如下:
import threading
from math import ceil
# 再导入上一个示例里边的Conn类

class DeployThread(threading.Thread):
def __init__(self, thread_max_num, host, project_name, environment_name, image_url):
threading.Thread.__init__(self)
self.thread_max_num = thread_max_num
self.host = host
self.project_name = project_name
self.environment_name = environment_name
self.image_url = image_url

def run(self):
self.smile_host = []
with self.thread_max_num:
Conn(self.host).cmd('docker stop %s && docker rm %s' % (self.project_name, self.project_name))

r5 = Conn(self.host).cmd(
'docker run -d --env ENVT=%s --env PROJ=%s --restart=always --name=%s -p 80:80 %s' % (
self.environment_name, self.project_name, self.project_name, self.image_url)
)

if r5.get('state'):
self.smile_host.append(self.host)
print('---->%s镜像更新完成' % (self.host))
else:
print('---->%s服务器执行docker run命令失败,details:%s' % (self.host, r5.get('message')))

# check镜像重启状态 and 重启失败需要回滚代码省略

def get_result(self):
return self.smile_host


if __name__ == '__main__':
# 演示代码简化了很多,整体逻辑不变

alive_host = ['10.82.9.47', '10.82.9.48']
image_url = 'ops-coffee:latest'

project_name = 'coffee'
environment_name = 'prod'

# alive_host / 8 向上取整作为最大线程数
thread_max_num = threading.Semaphore(ceil(len(alive_host) / 8))

threads = []
for host in alive_host:
t = DeployThread(thread_max_num, host, project_name, environment_name, image_url)
threads.append(t)

for t in threads:
t.start()

for t in threads:
t.join()

smile_host = []
for t in threads:
smile_host.append(t.get_result())

print('---->%d台主机更新成功' % (len(smile_host)))

经过以上优化我们实测后发现,一个28台主机的项目在优化前上线要花10分钟左右的时间,优化后只要2分钟左右,效率提高80%。
#多环境下配置文件的处理

我们采用了项目代码打包进镜像的镜像管理方案,开发、测试、预发布、生产环境配置文件都不同,所以即便是同一个项目不同的环境都会单独走一遍部署发布流程打包镜像,把不同环境的配置打包到不同的镜像中,这个操作太过繁琐且没有必要,还大大增加了我们的上线时间。

用过Kubernetes的都知道,Kubernetes中有专门管理配置文件的ConfigMap,每个容器可以定义要挂载的配置,在容器启动时自动挂载,以解决打包一次镜像不同环境都能使用的问题,对于没有用到Kubernetes的要如何处理呢?配置中心还是必不可少的。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

我们处理不同配置的整体思路是,在Docker启动时传入两个环境变量ENVT和PROJ,这两个环境变量用来定义这个容器是属于哪个项目的哪个环境,Docker的启动脚本拿到这两个环境变量后利用confd服务自动去配置中心获取对应的配置,然后更新到本地对应的位置,这样就不需要把配置文件打包进镜像了。

以一个纯静态只需要Nginx服务的项目为例。

Dockerfile如下:
FROM nginx:base

COPY conf/run.sh /run.sh
COPY webapp /home/project/webapp

CMD ["/run.sh"]

run.sh脚本如下:
#!/bin/bash
/etc/init.d/nginx start && \
sed -i "s|/project/env/|/${PROJ}/${ENVT}/|g" /etc/confd/conf.d/conf.toml && \
sed -i "s|/project/env/|/${PROJ}/${ENVT}/|g" /etc/confd/templates/conf.tmpl && \
confd -watch -backend etcd -node=http://192.168.107.101:2379 -node=http://192.168.107.102:2379 || \
exit 1

Docker启动命令:
'docker run -d --env ENVT=%s --env PROJ=%s --restart=always --name=%s -p 80:80 %s' % (
self.environment_name, self.project_name, self.project_name, self.image_url)

做到了一次镜像打包多环境共用,上线时也无需再走一次编译打包的流程,只需更新镜像重启容器即可,效率明显提高。
#写在最后


* 缺少编排的容器是没有灵魂的,继续推进编排工具的运用将会是2019年工作的重点。
* 实际上我们在Docker改造稳定后,内网开发测试环境部署了一套k8s集群用到现在已经一年多的时间比较稳定。
* 线上用到了多云环境,一部分线上项目已经使用了基于Kubernetes的容器编排,当然还有一部分是我上边介绍的纯Docker环境。

原文链接:https://mp.weixin.qq.com/s/xnBehfSlZ3J02xb0GFuGDw