Serverless 掀起新的前端技术变革

大卫 发表了文章 • 0 个评论 • 167 次浏览 • 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 个评论 • 192 次浏览 • 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 个评论 • 145 次浏览 • 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 个评论 • 165 次浏览 • 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

云服务器反黑客入侵攻防实录(二)

wise2c 发表了文章 • 0 个评论 • 116 次浏览 • 2019-05-31 11:12 • 来自相关话题

4、探秘黑客踪 4.1 寓言公寓楼 城市一角有一栋单身公寓楼,公寓有门有窗。到了晚上,主人下班回来,拿钥匙打开门进屋,关门熄灯休息, 平时窗户半开着,通风透气,保持空气清新。 一台云 ...查看全部
4、探秘黑客踪

4.1 寓言公寓楼

城市一角有一栋单身公寓楼,公寓有门有窗。到了晚上,主人下班回来,拿钥匙打开门进屋,关门熄灯休息, 平时窗户半开着,通风透气,保持空气清新。

一台云服务器好比作一间单身公寓。黑客好比作不速之客,不请自来,黑客进屋有两条路可走,一条路是开门大摇大摆进来,另一条路是翻窗进屋,多少需要一些飞檐走壁的功夫。

公寓门比较先进,使用了电子密码锁,需要输入正确密码才能开门进屋。黑客是不知道密码的,不能进屋。

单身公寓的1605室在16楼,小明在这间公寓里住了小一年了,最近新交了一位朋友小芳,这段时间留宿在公寓。小明和新朋友上下班有早有晚,所以小明给她开了一个新账号,设了新密码。小明有点懒,把账号和密码设成一样的了,心想16楼很少有人来,楼道有24小时监控,谅他也没人敢私闯民宅。

过了一个月,两人闹别扭,小芳就搬出去住了。小明心情不好,独自郁闷,忘记收回给小芳的账号密码。

社区附近来了一个外国流浪汉。流浪汉是一名高智商的人士,喜欢无拘无束,过着狂野不羁的生活。有一天,流浪汉在公寓楼内闲逛,走着走着就到了1605房间,看见门上有一位姑娘头像剪影,还纹上了几个字母“fang”,那是小明和小芳秀恩爱时留下的。流浪汉灵机一动,不是很多人拿亲友名字做密码吗?且试一试密码锁。这一试不要紧,公寓门居然打开了。

于是,流浪汉住进了这间单身公寓,和小明过起了错峰式“同居”生活。小明白天去上班,晚上回家住。流浪汉白天过来住公寓,自个儿做饭吃,吃饱了就睡,天黑前离开。后来,流浪汉不满足了,晚上也来公寓,怕被主人小明发现,偷偷在小明喝水的杯子里放了一粒安眠药,溶解了无影无踪,小明喝了下了安眠药的白开水,晚上会睡得很香、很沉。于是,晚上也是流浪汉的天下了。

如此“同居”过了一个月,小明发现了一些异常,冰箱里的鸡蛋莫名其妙少了几个,茶几上的面巾纸也用得很快,早上带走了垃圾,晚上回来垃圾桶里又冒出啃剩下的鸡腿骨。

小明把这些异常情况跟他的好朋友小清说了,小清是一位知名的私家侦探,破获很多疑难案件。接到小明的消息,小清立刻来到小明的公寓,里里外外勘察了一遍,然后小明就去上班了。小清的目光留在公寓门上的姑娘剪影和“fang”上,似乎明白了什么。输入密码,公寓门自动打开了。小清又到大楼的视频监控室,调阅了小明公寓外的监控视频,看到那名流浪汉每天来来往往。小清修改了公寓门密码,又报了警。警方调查以后,发出了通缉令。

晚上,小明回来,小清把发生的一切一五一十地告诉了小明,小明大吃一惊,然后就开心地笑了,很高兴能有小清这样的侦探朋友。

4.2 查询访客志

前面说到,一台云服务器好比一间单身公寓,而黑客就好比闯入公寓的流浪汉。进入公寓可以从门进,也可翻窗进来,从高层公寓的窗户进屋,难度大一些,窗户还拉了钢丝网。

从门进屋,对一台云服务器就好比远程SSH登录进来,云服务器主人是管理员,也是通过SSH登录进来的。云服务器对外暴露的服务端口,可以接受访问请求返回响应,但不欢并迎不速之客从端口潜入并接管服务器。翻窗进屋,就好比攻击云服务器的服务端口,有漏洞的端口才能进去,所以翻窗难度很大

对于陌生人来说,从门进屋也不容易,除非知道门禁密码或者门禁遭到破坏。

有没有陌生人闯入云服务器,翻一翻系统登录日志就清楚了。就像调阅单身公寓的视频监控,有谁到访一目了然。

Linux的last命令能查询云服务器最近的SSH登录日志。
# last

图_Linux服务器的系统登录日志.png

图 Linux服务器的系统登录日志


4.3 孜孜找不同

几年前玩过一款小游戏,名叫找不同:两张图画满了形状各异的物体或几何图形,两张图绝大部分相同,只有几个形状不同而且隐藏在两张图画里。从登录日志里找蛛丝马迹,有点像玩找不同的游戏。很快找不同见效了,找到不同之处,有了新发现。

绝大多数登录记录的源地址是192.168.x.x,是云数据中心局域网地址,登录用户是root,与管理员的日常管理习惯一致。

有一条陌生IP地址记录引起了我的注意,登录账号是jira,最后退出时间是4月8日04:09到04:19,与木马文件sd-pam的最后修改时间“Apr 8 04:19”完全吻合。
吻合.png

可以推断,黑客用jira用户登录进系统,在10分钟内植入木马程序,然后扬长而去,静静等待“肉鸡”主机传回挖矿数据(数字货币)。


4.4 惊现无秘码

黑客是怎么知道登录账号和密码的?

我们知道,SSH是应用层通信加密协议,所有通信信息都是经过高强度加密的,很难破译,所以截获密文再破译的可能性几乎为零。猜测暴力破解密码的可能性也为零。猜测暴力破解密码的可能性也为零。

黑客登录账号jira并不是管理员常用的root账号,进一步证明账号密码不是破译而来的。

还有另一种可能,密码丢失是因为管理员疏忽大意,使用了容易猜测的简单密码,例如:123,123456,abc,或者密码干脆等于账号。

我们来验证一下,弱密码的猜测是否成立。到目前为止,我还不知道账号jira的密码。

通过跳板机用root登录到JIRA云服务器。 然后,切换到另一个普通用户jira1(该账号登录受限,不可远程登录),root用户切换到普通用户不用输入密码。接着,从普通用户jira1切换到用户jira,此时必须输入jira用户的密码。输入密码jira,居然切换成功了,证明jira账号的密码也是jira !!!这里我表示无语。
# ssh root@192.168.1.x
# su – jira1
$ su - jira

无语.png

云服务器有一对泄了密、形同明文的账号和密码,就好比公寓泄密了密码,任由他人自由进出、来来往往。公寓被攻陷,进了流浪汉;服务器安全防线被攻破,“挖矿”的木马程序被植入,二十四小时不停地挖数字虚拟货币。


4.5 紧锁失密门

查询系统内没有正常进程在使用jira用户,确认jira用户暂时闲置无用。立即修改jira用户密码,并锁定账号。
# passwd jira
# usermod -L jira

账号.png

又巡视了一遍操作系统,/etc/passwd、

/etc/shadow都没有可登陆的用户。从SSH端口攻入云服务器的入口都被封堵。

做完这些事情之后,初步判断系统已经安全后,我的心情才逐渐舒缓下来。

周一上班后立即与云服务器管理员取得联系,系统管理团队对云服务器集群作了漏洞扫描和安全加固。


4.6 探寻黑客踪

心情放松下来,才有空闲思考黑客从哪里来,怎么进来的,还做了什么事!
什么事.png

查询百度,4月8日的黑客入侵源地址来自挪威。但是,黑客并不一定藏身挪威,甚至跟挪威一点关系都没有。因为入侵源地址可能只是一个跳板,一台受操控的“肉鸡”主机而已。
而已.png

此后的5月1日,又有一波黑客来袭,大白天(下午14:34到14:41)大摇大摆地在侵入的云服务器待了6分钟,但并没有修改上一波黑客留下的木马程序。5月的黑客源地址来自瑞士,他会是谁呢?是上一波黑客,回来巡视取胜的旧战场。还是新来的“有道德的”黑客,看到有人捷足先登,悄然离去。不得而知,但一切皆有可能。
可能.png

再往前看,1月10日到18日,先后有四次登录记录,地址来源各不相同,185.112.82.230、185.112.82.235、41.231.5.34和188.166.171.252,分别来自俄罗斯、俄罗斯、突尼斯和荷兰。

源地址的多样化,似乎表明黑客们可以随意出入这台云服务器。是什么原因吸引黑客们蜂拥而至呢?为什么是jira用户,而不是别的用户呢?

答案是端口扫描,并通过扫描获知公开信息,进而攻击服务器漏洞。


4.7 解读黑客术

黑客程序扫描公网IP地址或者扫描域名的HTTP端口(80端口、8080端口),分析返回的HTML文本,经过过滤、提取候选词,再与词库中的关键字对比。如果关键词匹配,从关键词关联的应用库提取对应的应用程序,推断该地址的HTTP端口提供该应用服务。例如:提取到jira关键词,推断该服务器提供JIRA服务;提取到sonarqube关键字,推断该服务器提供SonarQube服务,等等。
图_HTTP服务的默认页面暴露了具体服务.png

图 HTTP服务的默认页面暴露了具体服务
图_HTTP服务返回的默认响应暴露了具体服务.png

图 HTTP服务返回的默认响应暴露了具体服务
这些通用公共应用的安装指南,大多数会提示用户新建同名的操作系统账户,例如jira、mysql和redis等,以此用户来安装公共应用。一些粗心的管理员,会给系统账户设置简单密码。这正是这一类黑客程序攻击能得逞的关键原因之一。

互联网上IP地址浩如烟海,为什么偏偏是这台云服务器呢?

比较笨的办法是按地址段逐个IP扫描,发现IP地址上有开放的端口,再实施攻击、破解,攻破端口后,取得系统权限,植入木马程序。

一个国家所分配的IP地址号段是固定不变,除非有新增的IP地址号段。国家IP管理机构再给其国内的云服务提供商分配IP地址号段,这也是固定的,甚至可以公开查询任意一个云服务商的IP地址号段。同一个云服务提供商的云安全基础设施是共用的,有相同的护盾,也有相同的漏洞,只要在一台云服务器上发现了漏洞,很可能其他云服务器也有类似的漏洞。

向云服务提供商租用虚拟服务器的内容提供商,由于安全管理制度、安全基础软件和安全管理团队的共性,其管理的云服务器集群也就有了相似的安全性和相似的漏洞。举一反三,照猫画虎,攻击者攻陷一台云服务器后,很容易扩大战果攻击更多云服务器。

前文说到,黑客植入的木马程序是“挖矿”程序。在下一集将对这个“挖矿”程序动手术,肢解程序为几大块,分析它内部运行机制,对外通信联络模式等等。

作者:清如许
原文链接:

https://mp.weixin.qq.com/s/CBakZJ-ROMt2MzkJg3k1Iw


关于睿云智合

深圳睿云智合科技有限公司成立于2012年,总部位于深圳,并分别在成都、深圳设立了研发中心,北京、上海设立了分支机构,核心骨干人员全部为来自金融、科技行业知名企业资深业务专家、技术专家。早期专注于为中国金融保险等大型企业提供创新技术、电子商务、CRM等领域专业咨询服务。

自2016年始,在率先将容器技术引进到中国保险行业客户后,公司组建了专业的容器技术产品研发和实施服务团队,旨在帮助中国金融行业客户将容器创新技术应用于企业信息技术支持业务发展的基础能力改善与提升,成为中国金融保险行业容器技术服务领导品牌。

此外,凭借多年来在呼叫中心领域的业务经验与技术积累,睿云智合率先在业界推出基于开源软交换平台FreeSwitch的微服务架构多媒体数字化业务平台,将语音、视频、webchat、微信、微博等多种客户接触渠道集成,实现客户统一接入、精准识别、智能路由的CRM策略,并以容器化治理来支持平台的全应用生命周期管理,显著提升了数字化业务处理的灵活、高效、弹性、稳定等特性,为帮助传统企业向“以客户为中心”的数字化业务转型提供完美的一站式整体解决方案。

用 Docker 快速配置前端开发环境

翔宇 发表了文章 • 0 个评论 • 251 次浏览 • 2019-05-30 21:28 • 来自相关话题

学好Docker,你应该了解这些

老马 发表了文章 • 0 个评论 • 224 次浏览 • 2019-05-30 20:07 • 来自相关话题

Docker公司推出的三剑客:Machine、Compose和Swarm。这三件利器的出现,让Docker不仅支持单机的虚拟化,而且能支持更广泛的集群平台,提供更强大灵活的功能。本文通过简短的介绍,让对Docker三剑客有个初步了解。今后遇到Docker相关问 ...查看全部
Docker公司推出的三剑客:Machine、Compose和Swarm。这三件利器的出现,让Docker不仅支持单机的虚拟化,而且能支持更广泛的集群平台,提供更强大灵活的功能。本文通过简短的介绍,让对Docker三剑客有个初步了解。今后遇到Docker相关问题,知道用什么技术去解决。

docker-machine:解决Docker运行环境问题

Docker技术是基于Linux内核的cgroup技术实现的,那么问题来了,如果在非Linux平台上使用Docker技术需要依赖安装Linux系统的虚拟机。docker-machine就是Docker公司官方提出的,用于在各种平台上快速创建具有Docker服务的虚拟机的技术。可以把它理解为virtualbox或者VMware,最开始在Win7上用得比较多,但是Win10开始自带了hyper-v虚拟机,已经不再需要docker-machine了,Docker可以直接运行在安装了Linux系统得hyper-v上。

dcoker-compose:解决本地Docker容器编排问题

一般是通过yaml配置文件来使用它,这个docker-compose.yml文件里能记录多个容器启动的配置信息(镜像、启动命令、端口映射等),最后只需要执行docker-compose对应的命令,例如docker-compose up就会像执行脚本一样地批量创建和销毁容器。

docker-swarm:解决多主机多个容器调度部署得问题

Swarm是基于Docker平台实现的集群技术,他可以通过几条简单的指令快速的创建一个Docker集群,接着在集群的共享网络上部署应用,最终实现分布式的服务。Swarm技术不是很成熟,很多配置功能都无法实现,只能说是个半成品,目前更多的是使用Kubernetes来管理集群和调度容器。如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

总结

如果你是在非Linux环境下考虑使用docker-machine,当然我更推荐使用hyper-v或者virtualbox。如果你需要同时操作多个容器,或者希望使用配置文件记录容器启动命令参数,那么推荐使用docker-compose。如果你需要在多台主机上部署Docker容器,并对其进行调度,那么Swarm是一种选择,当然更推荐Kubernetes。

原文链接:https://www.jianshu.com/p/b293d076b34b

Git 自救指南

Andy_Lee 发表了文章 • 0 个评论 • 128 次浏览 • 2019-05-30 19:57 • 来自相关话题

Git 虽然因其分布式管理方式,不完全依赖网络,良好的分支策略,容易部署等优点,已经成为最受欢迎的源代码管理方式。但是一分耕耘一分收获,如果想更好地掌握 Git,需要付出大量的学习成本。即使在各种 GUI 的加持下,也不得不说 Git 真的很难,在 V2EX ...查看全部
Git 虽然因其分布式管理方式,不完全依赖网络,良好的分支策略,容易部署等优点,已经成为最受欢迎的源代码管理方式。但是一分耕耘一分收获,如果想更好地掌握 Git,需要付出大量的学习成本。即使在各种 GUI 的加持下,也不得不说 Git 真的很难,在 V2EX 上也常有如何正确使用 Git 的讨论,同时在 Stackoverflow 上超过 10w+ 的 Git 相关问题也证明了 Git 的复杂性。再加上 Git 的官方文档也一直存在着 “先有鸡还是先有蛋” 的问题,虽然文档非常全面,但如果你不知道你遇到的问题叫什么,那么根本就无从查起。

本文节选自 Katie Sylor-Miller 在日常工作中所遇到过的让他很头疼的 Git 相关问题,并整理了相应的应对措施,在这里分享给正在学习如何使用 Git 的同学们。当然这些应对措施并不是唯一的,可能你会有其他更好的应对方法,这也恰恰是 Git 这套版本控制系统强大的地方。如果你想和更多 Git 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

我刚刚好像搞错了一个很重要的东西,但是 Git 有个神奇的时间机器能帮我复原!
git reflog
# you will see a list of every thing you've done in git, across all branches!
# each one has an index HEAD@{index}
# find the one before you broke everything
git reset HEAD@{index}
# magic time machine

reflog 是一个非常实用的命令,你可以使用这个命令去找回无意间删除的代码,或者去掉一些刚刚添加的却把仓库里的代码弄坏的内容。同时也可以拯救一下失败的 merge,或者仅仅是为了回退到之前的版本。

我 commit 完才想起来还有一处小地方要修改!
# make your change
git add . # or add individual files
git commit --amend
# follow prompts to change or keep the commit message
# now your last commit contains that change!

当我 commit 完然后跑测试的时候,经常突然发现忘了在等于号前面加空格。虽然可以把修改过的代码再重新 commit 一下,然后 rebase -i 将两次揉在一起,不过上面的方法会比较快。

我要改一下上一个 commit message!
git commit --amend
# follow prompts to change the commit message

当你们组对 commit message 有格式要求时,或者当你忘了中英文间要加空格,这个命令能救你狗命。

我不小心把本应在新分支上的内容 commit 到 master 了!
# create a new branch from the current state of master
git branch some-new-branch-name
# remove the commit from the master branch
git reset HEAD~ --hard
git checkout some-new-branch-name
# your commit lives in this branch now :)

注意:这个指令必须在错误的 commit 后直接执行,如果你已经试了其他的方式,你可能就需要用 git reset HEAD@{number} 来代替 HEAD~ 了。

我不小心 commit 到错误的分支上了!
# undo the last commit, but leave the changes available
git reset HEAD~ --soft
git stash
# move to the correct branch
git checkout name-of-the-correct-branch
git stash pop
git add . # or add individual files
git commit -m "your message here"
# now your changes are on the correct branch

也有很多人推荐了 cherry-pick 的解决方案,所以选哪个就看你心情了。
git checkout name-of-the-correct-branch
# grab the last commit to master
git cherry-pick master
# delete it from master
git checkout master
git reset HEAD~ --hard

我执行了 diff 但是啥也没出现
git diff --staged

Git 不会给通过 add 加入到 staging 区域里面的文件做 diff ,除非你加了 --staged 的标签,别怀疑了这是一个 feature 不是一个 bug,当然对于第一次碰到这个问题的人来说还是有些不好理解的。

Git 从入门到放弃
cd ..
sudo rm -r fucking-git-repo-dir
git clone https://some.github.url/fucking-git-repo-dir.git
cd fucking-git-repo-dir

为了维护最后的尊严 XD

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

如何限制Kubernetes本地临时存储的容量

aoxiang 发表了文章 • 0 个评论 • 229 次浏览 • 2019-05-30 19:02 • 来自相关话题

#介绍 作为Kubernetes平台的提供方,必须要对某些“流氓”应用做出一些限制,防止它们滥用平台的CPU、内存、磁盘、网络等资源。 例如,Kubernetes提供了对CPU,内存的限制,可以防止应用无限制的使用系统的资 ...查看全部
#介绍
作为Kubernetes平台的提供方,必须要对某些“流氓”应用做出一些限制,防止它们滥用平台的CPU、内存、磁盘、网络等资源。

例如,Kubernetes提供了对CPU,内存的限制,可以防止应用无限制的使用系统的资源;Kubernetes提供的PVC,如CephFS、RBD,也支持容量的限制。

但是,早期Kubernetes版本并没有限制container的rootfs的容量,由于默认容器使用的log存储空间是在 /var/lib/kubelet/ 下,rootfs在/var/lib/docker下,而这两个目录默认就在宿主机Node的根分区,如果应用恶意攻击,可以通过在容器内大量dd从而迅速造成宿主机Node根分区文件系统满。我们知道,当Linux根分区使用达到100%的时候,通常会很危险。

Kubernetes在1.8版本引入了一种新的resource:local ephemeral storage(临时存储),用来管理本地临时存储,对应特性 LocalStorageCapacityIsolation。从1.10开始该特性转为beta状态,默认开启。如果你想和更多 Kubernetes 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

临时存储,如emptyDir volumes, container logs,image layers and container writable layers,默认它们使用的是 /var/lib/kubelet,通过限制临时存储容量,也就可以保护Node的root分区了。

本地临时存储管理只对root分区有效,如果你定制了相关的参数,例如 --root-dir,则不会生效。
#配置
我的集群版本是1.14,默认开启了 local ephemeral storage 的特性,只需要配置Pod即可。

Pod的每个container都可以配置:

* spec.containers[].resources.limits.ephemeral-storage
* spec.containers[].resources.requests.ephemeral-storage

单位是byte,可以直接配置,也可以按E/P/T/G/M/K或者Ei, Pi, Ti, Gi, Mi, Ki.为单位来配置,例如 128974848, 129e6, 129M, 123Mi 表示的是同一个容量。

下面创建一个Deployment,设置其使用的临时存储最大为2Gi。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
selector:
matchLabels:
run: nginx
template:
metadata:
labels:
run: nginx
spec:
containers:
- image: nginx
name: nginx
resources:
limits:
ephemeral-storage: 2Gi
requests:
ephemeral-storage: 2Gi

Pod启动后,进入容器,执行dd if=/dev/zero of=/test bs=4096 count=1024000 ,尝试创建一个4Gi的文件,可以发现在执行一段时间后,Pod被 Evict,controller重新创建了新的Pod。
nginx-75bf8666b8-89xqm                    1/1     Running             0          1h
nginx-75bf8666b8-pm687 0/1 Evicted 0 2h

#实现
Evict Pod动作是由kubelet完成的。每个节点上的kubelet会启动一个evict manager,每10秒种(evictionMonitoringPeriod)进行一次检查,ephemeral storage的检查也是在这个阶段完成的。

evict manager可以对pod和container来检查超额应用。
func (m [i]managerImpl) localStorageEviction(summary [/i]statsapi.Summary, pods [][i]v1.Pod) [][/i]v1.Pod {
statsFunc := cachedStatsFunc(summary.Pods)
evicted := []*v1.Pod{}
for _, pod := range pods {
podStats, ok := statsFunc(pod)
if !ok {
continue
}

if m.emptyDirLimitEviction(podStats, pod) {
evicted = append(evicted, pod)
continue
}

if m.podEphemeralStorageLimitEviction(podStats, pod) {
evicted = append(evicted, pod)
continue
}

if m.containerEphemeralStorageLimitEviction(podStats, pod) {
evicted = append(evicted, pod)
}
}

return evicted
}

其中Pod为GetActivePods获取的本节点所有非Terminated状态的Pod。

kubelet会依此检查Pod的emptyDir、Pod级临时存储、container级临时存储,若Pod需要被evict,则加到evicted数组,之后会将evicted的Pod挤出。

contaier级检查比较简单,因为ephemeral storage设置的就是在container上,依次检查container的使用情况和设置的limits,如果超过了limits,则要加入到evicted pods列表中。

相关代码在 containerEphemeralStorageLimitEviction 中。

而Pod级别的检查会复杂一点。

首先是限制值的计算。

kubelet会统计Pod所有container(但不包括init container)的ephemeral storage limits之和。init container指定的是Pod的配额最低需求(有点像最低工资标准,用于生活保障),当所有container指定的配额,超过init container指定的配额时,将忽略init container指定的配额。数学描述如下。
max(sum(containers), initContainer1, initContainer2, ...)

而实际临时存储用量的计算,除了会计算指定过ephemeral storage的container的使用量,还会统计未指定过ephemeral storage的container,以及emptyDir的使用量。

当实际临时存储用量,超过了限制值时,kubelet会将该Pod Evict,然后等待controller重新创建新的Pod并重新调度。

相关代码在 podEphemeralStorageLimitEviction 中。
#requests
注意,设置的local ephemeralstorage requests在evict manager处理过程中没有用到。但是它不是没用的。

创建Pod后,scheduler会将该Pod调度到集群中某个node上。由于每个node所能承载的local ephemeral storage是有上限的,所以scheduler会保证该node上所有Pod的 local ephemeralstorage requests 总和不会超过node的根分区容量。
#inode 保护
有的时候,我们会发现磁盘写入时会报磁盘满,但是df查看容量并没有100%使用,此时可能只是因为inode耗尽造成的。因此,对平台来说,inode的保护也是需要的。

其中,podLocalEphemeralStorageUsage 也统计了container或者pods使用的inode的数量。

但是当前Kubernetes并不支持对Pod的临时存储设置inode的limits/requests。

当然了,如果node进入了inode紧缺的状态,kubelet会将node设置为 under pressure,不再接收新的Pod请求。
#emptyDir
emptyDir也是一种临时存储,因此也需要限制使用。

在Pod级别检查临时存储使用量时,也会将emptyDir的使用量计算在内,因此如果对emptyDir使用过量后,也会导致该Pod被kubelet Evict。

另外,emptyDir本身也可以设置容量上限。如下所摘录编排文件片段,我指定了emptyDir使用内存作为存储介质,这样用户可以获得极好的读写性能,但是由于内存比较珍贵,我只提供了64Mi的空间,当用户在 /cache 目录下使用超过64Mi后,该Pod会被kubelet evict。
        volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- emptyDir:
medium: Memory
sizeLimit: 64Mi
name: cache-volume

相关代码在 emptyDirLimitEviction 中。

原文链接:https://ieevee.com/tech/2019/05/23/ephemeral-storage.html

火热的云原生到底是什么?一文了解云原生四要素!

阿娇 发表了文章 • 0 个评论 • 201 次浏览 • 2019-05-30 18:33 • 来自相关话题

所谓云原生,它不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。 随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应 ...查看全部
所谓云原生,它不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。

随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应用的云平台被越来越多的提及。IaaS、PaaS和SaaS是云计算的3种基本服务类型,它们是关注硬件基础设施的基础设施即服务、关注软件和中间件平台的平台即服务以及关注业务应用的软件即服务。

在容器技术、可持续交付、编排系统等开源社区的推动下,以及微服务等开发理念的带动下,应用上云已经是不可逆转的趋势。随着云化技术的不断进展,云原生的概念也应运而生。
#云原生概念的诞生

云原生(Cloud Native)的概念,由来自Pivotal的MattStine于2013年首次提出,被一直延续使用至今。这个概念是Matt Stine根据其多年的架构和咨询经验总结出来的一个思想集合,并得到了社区的不断完善,内容非常多,包括DevOps、持续交付(Continuous Delivery)、微服务(MicroServices)、敏捷基础设施(Agile Infrastructure)和12要素(The Twelve-Factor App)等几大主题,不但包括根据业务能力对公司进行文化、组织架构的重组与建设,也包括方法论与原则,还有具体的操作工具。采用基于云原生的技术和管理方法,可以更好地把业务生于“云”或迁移到云平台,从而享受“云”的高效和持续的服务能力。
1.jpg

The Twelve-Factor App

顾名思义,云原生是面向“云”而设计的应用,因此技术部分依赖于传统云计算的3层概念,基础设施即服务(IaaS)、平台即服务(PaaS)和软件即服务(SaaS),例如,敏捷的不可变基础设施交付类似于IaaS,用来提供计算网络存储等基础资源,这些资源是可编程且不可变的,直接通过API可以对外提供服务;有些应用通过PaaS服务本来就能组合成不同的业务能力,不一定需要从头开始建设;还有一些软件只需要“云”的资源就能直接运行起来为云用户提供服务,即SaaS能力,用户直接面对的就是原生的应用。
##云原生并不是一个产品

最近讨论云原生应用越来越多。关于云原生应用,简单地说,就是大多数传统的应用,不做任何改动,都是可以在云平台运行起来,只要云平台支持这个传统应用所运行的计算机架构和操作系统。只不过这种运行模式,仅仅是把虚拟机当物理机一样使用,不能够真正利用起来云平台的能力。

云并非把原先在物理服务器上跑的东西放到虚拟机里跑,真正的云化不仅是基础设施和平台的事情,应用也要做出改变,改变传统的做法,实现云化的应用——应用的架构、应用的开发方式、应用部署和维护技术都要做出改变,真正的发挥云的弹性、动态调度、自动伸缩……一些传统IT所不具备的能力。这里说的“云化的应用”也就是“云原生应用”。云原生架构和云原生应用所涉及的技术很多,如容器技术、微服务、可持续交付、DevOps等。
2.jpg

而云原生应用最大的特点就是可以迅速部署新业务。在企业里,提供新的应用程序环境及部署软件新版本通常所需时间以日、周甚至以月计算。这种速度严重限制了软件发布所能承受的风险,因为犯错及改错也需要花费同样的时间成本,竞争优势就会由此产生。

所以云原生不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。意义在于让云成为云化战略成功的基石,而不是障碍。它可以根据商业能力对公司进行重组的能力,既包含技术、也包含管理,可以说是一系列云技术和企业管理方法的集合,通过实践及与其他工具相结合更好地帮助用户实现数字化转型。
##云原生计算基金会(CNCF)

CNCF,即云原生计算基金会,2015年由谷歌牵头成立,基金会成员目前已有一百多企业与机构,包括亚马逊、微软、思科等巨头。

目前CNCF所托管的应用已达14个,下图为其公布的Cloud Native Landscape,给出了云原生生态的参考体系。
3.jpg

Cloud Native Landscape新版

CNCF(云原生计算基金会)认为云原生系统需包含的属性:

* 容器化封装:以容器为基础,提高整体开发水平,形成代码和组件重用,简化云原生应用程序的维护。在容器中运行应用程序和进程,并作为应用程序部署的独立单元,实现高水平资源隔离。
* 自动化管理:统一调度和管理中心,从根本上提高系统和资源利用率,同时降低运维成本。
* 面向微服务:通过松耦合方式,提升应用程序的整体敏捷性和可维护性。

正因为如此,你可以专注于创新,解决业务问题,而不是把时间花在“静态、不灵活的传统架构”存在的许多技术问题。
#云原生的四要素:持续交付、DevOps、微服务、容器

从云原生的概念中,我们总是能看到持续交付、DevOps、微服务、容器等技术的出现,那么它们到底是什么,这里引用Pivotal台湾云计算资深架构师的部分观点,为大家逐一揭开他们的神秘面纱!
4.jpg

##持续交付——缩小开发者认知,灵活开发方向

首先是持续交付,什么样的时候客户要求持续交付?敏捷开发要求持续交付,因为敏捷开发要求随时有一个版本可以上到大群环境,所以要持续交付。

而换句话说,持续交付就是不误时开发。举一个例子,有些公司非常喜欢谈需求,谈很久,可是开发只剩1/3时间就开发完成,然后交付,再上线运营。这就会碰到一个问题,就是你开始谈需求到最后交付产品的时间,短则三月,长则半年,这中间市场已经变化了,需求也随之变化了。因此市场上出现了新的想法,即是不是能够小步快跑,把交付的周期缩短一点,我可以实现快速交付,每次交付都可以重新确认方向,这样尽量避免与未来期待的落差。
5.jpg

用小步快跑的方式,打破瀑布式开发流程

那么问题来了,持续交付对于开发的人谈的需求、开发的方式有改变,那它对于开发有影响吗?如果说公司的开发团队一天可以交付五次,那研发团队要帮忙部署一次吗?现在公司大部分部署都是研发团队帮忙部署应用的,研发团队部署五次,要改版五次就需要部署一次,这是无法实现的。而且每次部署的时候都要面对停机,而实际公司的应用经不起一天停机五次部署,在互联网的思维之下,零宕机时间已经是现在企业的基本要求。于是“蓝绿部署”的概念营运而生。即在一个环境里面,第一版还在线上服务,第二版先做封测,封测完成后,让外面的流量进来一些,看log是不是开发人员要的,确认后再把全部的流量导到新的版本上。
6.jpg

蓝绿(Blue-Green)部署

但“蓝绿部署”在系统过多过复杂的情况下,在传统架构上实现非常困难,所以企业要做到zero down time的持续交付就需要有良好的平台與工具协助。因此,持续交付的优势在于,它可以缩小开发者认知,重新确认开发方向。
##微服务——内聚更强,更加敏捷

第二部分是微服务。微服务是什么?有客户表示,提供商出产品,客户把应用全部放上去,结果就是一个微服务。这种认知是错误的,因为微服务是一个架构的改变。那么微服务是怎么做的呢?它所面临的最大挑战是什么?

是切割。那么如何切割呢?其实这件事情早在1968年康威就提出了——康威定律,系统的服务划分应该是根据组织架构的功能来划分。1968年康威就提出了这个想法,我认为拿来做微服务的切割非常适用。
7.jpg

Going Agile - Breaking the monolith Conway's Law and Microservices

这样按照组织架构划分的优势在于:

  1. 内聚更强,所有遵循同一种业务准则的人内聚在一起,就容易解决问题。
  2. 服务解耦,变更容易,更加敏捷。当做到解耦合的时候,要变更就容易。所以微服务应该是切分成这个样子,由上而下来切,根据Function来切。

另外一个划分微服务的技巧,可以运用领域驱动设计(Domain Driven Design)的理论,而领域驱动设计亦可算是面向物件的一种设计思维;聚合可以让微服务划分更有依据,也让未來的系統变更具有弹性。值得一提的是领域驱动设计,也提供微服务中的事物问题。因为过去巨石应用进行两个报数的阶段,相当容易也常见,但在微服务架构中,如何在分散的服务中进行事物就显得相当困难。利用领域驱动设计的Event Souring进行设计,是目前最好的解決办法。

那么在什么情况下需要微服务?我认为有三个标准:

  1. 有HA(High Available)的需求需要微服务。
  2. 有性能调校的需求(例如:图片的呈现或者搜寻)需要微服务。
  3. 经常变更的需要微服务。

实际上,微服务需要关注的源代码范围比较小,使得各个服务解耦、变更容易,内聚更强,因为都会集中在服务里。另外,它更容易单独改版,因为微服务之间是用RESTful间接起来的,用RESTful只要API的界面不改,原则上则不会错,也更敏捷。

但微服务也会留下一些问题,例如App团队如何分工?环境怎么配合?如何实现自动化部署?
##容器技术——使资源调度、微服务更容易

再来看看容器。在机器上运行的容器只是主机操作系统上的一个进程,与任何其他进程无异。那么,为什么容器如此受欢迎呢?原因在于这个进程被隔离和限制的方式。这种方式很特殊,可简化开发和运维。

其实1979年就有容器技术,很多人会以为说Docker是不是等于容器,其实Docker不等于容器。容器的历史可追溯到Linux操作系统。容器利用了Linux的内核功能。Linux中容器的核心概念(cgroup、namespaces和filesystems)在独立的区域运行。容器的神奇之处在于将这些技术融为一体,以实现最大的便利性。

VMware之前的技术专家在2011年发展出一个技术,把这个技术贡献出来成立了一个Cloud Foundry基金会。Docker在2013年才开始有,而且它第一版是用SLC的技术去做的。后来陆续一路成长,使得为服务的实现更容易了。
8.jpg

从 Infra 角度来看技术演进

从上面这个表中可以看出,从左边开始,IaaS,虚拟化技术有了之后,刚刚提到的所谓第三代平台,这四个区块开发人员交付的内容不一样。所有的IaaS、CaaS、PaaS、FaaS一路的变化演进,对于客户的负担越到后面越小,而对于开发人员的想象力则愈发抽象。

大家一定会遇到下列这些计算,一个是所谓的单体应用,或者翻译成巨石应用。此外,你们一定会有一些批次的管理,另外就是所谓的数据库的部分,开始可能会有容器技术,像Kubernetes、Docker。

Docker是软件行业最受欢迎的软件容器项目之一。思科、谷歌和IBM等公司在其基础设施和产品中使用Docker容器。

Kubernetes是软件容器领域的另一个值得关注的项目。Kubernetes是一个允许自动化部署、管理和伸缩容器的工具。为了便于管理其容器,谷歌建立了Kubernetes。它提供了一些强大的功能,例如容器之间的负载均衡,重启失败的容器以及编排容器使用的存储。如果你想和更多 Kubernetes 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
9.jpg

容器生态图

容器为云原生应用程序增加了更多优势。使用容器,你可以将微服务及其所需的所有配置、依赖关系和环境变量移动到全新的服务器节点上,而无需重新配置环境,这样就实现了强大的可移植性。
##DevOps——以终为始,运维合一

10.png

最后让我们走向DevOps,它不是一种工具,DevOps其实要谈的是运维合一。

DevOps如果从字面上来理解只是Dev(开发人员)+Ops(运维人员),实际上,它是一组过程、方法与系统的统称,其概念从2009年首次提出发展到现在,内容也非常丰富,有理论也有实践,包括组织文化、自动化、精益、反馈和分享等不同方面。

首先,组织架构、企业文化与理念等,需要自上而下设计,用于促进开发部门、运维部门和质量保障部门之间的沟通、协作与整合,简单而言组织形式类似于系统分层设计。

其次,自动化是指所有的操作都不需要人工参与,全部依赖系统自动完成,比如上述的持续交付过程必须自动化才有可能完成快速迭代。再次,DevOps的出现是由于软件行业日益清晰地认识到,为了按时交付软件产品和服务,开发部门和运维部门必须紧密合作。

总之,DevOps强调的是高效组织团队之间如何通过自动化的工具协作和沟通来完成软件的生命周期管理,从而更快、更频繁地交付更稳定的软件。在内部沟通上,你可以想象DevOps是一个敏捷思維,是一个沟通的文化。当运营和研发有良好的沟通效率,才可以有更大的生产力。如果你的自动化程度够高,可以自主可控,工作负担降低,DevOps能够带来更好的工作文化、更高的工作效率。
#总结

综上所述,云原生的DevOps、平台、持续交付、微服务都是云原生不可或缺的一部分,需要以全局地眼光看待问题,脱离任何一个元素,对于企业来说都是“管中窥豹”、“一叶障目”,只有加以整合才能见到云原生的全局风貌。

面对业态各异的业务上云以及碎片化的物联网解决方案部署,利用云原生思维和模式,构建基于云原生的物联网平台以及解决方案,势必将加速企业,甚至整个社会的数字化转型。

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