云原生时代下的12-factor应用与实践

黄庆兵

网易云 • 基础服务 • 技术布道师

1. 简介

个人简介

黄庆兵,网易云技术布道师,浙大硕士毕业,后加入网易至今,从事容器云平台相关技术(Kubernetes、Docker)的开发架构以及布道工作,热衷于推动微服务和云原生的实践与落地;喜欢开源,乐于分享,勤于布道,折腾过开源工具,制作过Docker课程(《动手玩Docker》、《玩转Docker镜像》),分享过Gopher Meetup、大学创客等。

内容简介

在云的时代,应用会更多的迁移到云端,基于云的架构设计和开发模式需要一套全新的理念去承载,于是云原生思想应运而生,而针对云原生应用开发的最佳实践原则,12-Factor脱颖而出,同时也带来了新的解读。本文将介绍在Cloud Native时代下,结合Docker等技术,如何一一实践12-Factor原则。

2. 云原生

云原生(Cloud Native)是由 Pivotal 的Matt Stine在2013年提出的一个概念,是他多年的架构和咨询总结出来的一个思想的集合。

那怎么去理解云原生应用?我觉得可以从三个角度来说明,这和云计算平台的三个层次不谋而合,如下图:

云原生应用,正好契合了云、平台和服务,一层层构建,所以我通常就把它理解为面向云(平台)来设计我们的应用。网易三拾众筹的架构师陈晓辉,还为它起了一个小清新的名字——向云而生,我觉得非常贴切,再通俗一点讲,也可以叫做云平台应用。

3. 12-Factor

12-Factor,是由Heroku创始人Adam Wiggins首次提出并开源,并由众多经验丰富的开发者共同完善,这综合了他们关于SaaS应用几乎所有的经验和智慧,是开发此类应用的理想实践标准。

12-Factor 全称叫 The Twelve-Factor App,它定义了一个优雅的互联网应用在设计过程中,需要遵循的一些基本原则,和 Cloud-Native 有异曲同工之处。其中文翻译不少,我觉得“十二要素”或“十二原则”比较贴切,

那具体有哪十二原则了,见下图:

在接下来的 应用和实践 当中,我们会一一实践每条原则。

注:虽然 12-Factor 的原文书籍都是发布在其官网上,但因为网络问题和格式问题,不是很方便阅读,我将其转化了为 GitBook 格式,并架设在网易蜂巢平台上,同时开源在 GitHub 上,方便大家阅读和下载:

4. 应用与实践

既然12-factor作为SaaS开发的最佳实践原则,当然脱离不了实践,接下来我们就来设计一款云原生应用,并依照12-factor,一步步验证和升级我们的应用。从中,我们将讲解每个Factor的要点,以及如何在我们的应用中实践Factor。

4.1. 0. 应用准备

这是一个面向云平台设计的简单Web应用,它将暴露一个HTTP REST风格的接口,可以实现对 user 的增删改查功能,将用到以下技术栈:

1. 基于 Node.js,用 Node.js 写Web应用非常方便,而且是当今最火的编程平台之一

- 注:Node 版本只要 v4.4 以上就够用,当前最新的稳定版是v6.9.5, 我本地的版本是v5.12.0

2. 基于 Sails,类似 Rails 框架,用于快速开发 Node.js 应用:http://sailsjs.com/

npm install sails -g

3. 基于 mongo 3.2 :https://docs.mongodb.org/manual/installation/

4. 基于 Docker,非常契合12-Factor理念,作为我们打包、发布、运行的工具

5. 以上环境安装好之后,就开始初始化我们的应用并运行,应用的名称就叫:12factor-app

$ sails new 12factor-app
  info: Created a new Sails app `12factor-app`!
$ cd 12factor-app
$ sails generate api user
  info: Created a new api!
$ npm start

注:本文源代码放在GitHub上,请参考文后参考链接

仅需4条命令就搞定了应用的框架代码,并自动生成了基于user的CRUD接口,我们已经将应用启动起来,可以通过以下方式本地调试接口:

http://localhost:1337

1. 增加一个新用户

$ curl -XPOST http://localhost:1337/user?name=bingo
{
  "name": "bingo",
  "createdAt": "2017-02-13T06:13:53.791Z",
  "updatedAt": "2017-02-13T06:13:53.791Z",
  "id": 58a41d952f53291200b9e065
}

2. 获取用户列表

$ curl http://localhost:1337/user
[
  {
    "name": "bingo",
    "createdAt": "2017-02-13T06:13:53.791Z",
    "updatedAt": "2017-02-13T06:13:53.791Z",
    "id": 58a41d952f53291200b9e065
  }
]

3. 修改一个用户

curl -XPUT http://localhost:1337/user/58a41d952f53291200b9e065?name=bingohuang
{
  "name": "bingohuang",
  "createdAt": "2017-02-13T06:13:53.791Z",
  "updatedAt": "2017-02-13T06:14:13.460Z",
  "id": 58a41d952f53291200b9e065
}

4. 删除一个用户

curl -XDELETE http://localhost:1337/user/58a41d952f53291200b9e065

我已经将该应用部署到了网易蜂巢在线平台,如果您对这个应用感兴趣,直接将localhost替换为59.111.110.95,一样可以体验CRUD操作,如下所示,只要把yourname换成您的名字即可(建议在PC端操作):

# 注册你自己
curl -XPOST http://59.111.110.95:1337/user?name=yourname
# 查看所有注册过的用户
curl http://59.111.110.95:1337/user
# 或者PC浏览器直接访问 http://59.111.110.95:1337/user

接下来开始就让我们开始一一实践12-Factor中的每条原则吧,每个原则中我们将分为Factor解说和Factor实践两块。

4.2. I. 基准代码

Factor解说

- 一旦有多个基准代码,就不能称为一个应用,而是一个分布式系统。分布式系统中的每一个组件都是一个应用,每一个应用可以分别使用 12-Factor 进行开发。
- 多个应用共享一份基准代码是有悖于 12-Factor 原则的。解决方案是将共享的代码拆分为独立的类库,然后使用 依赖管理(第二个原则) 策略去加载它们。
- 多份部署相当于是运行了该应用的多个实例,比如开发环境一个实例,测试环境、生产环境都有一个实例
- 一个代码仓库,确保了单一的信任源,从而保证了更少的配置错误和更强的容错和复原能力

Factor实践

$ echo "# 12factor-app" >> README.md
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin git@github.com:bingohuang/12factor-app.git
$ git push -u origin master

4.3. II. 依赖

Factor解说

Factor实践

{
"name": "12factor-app",
"private": true,
"version": "0.0.0",
"description": "a Sails application",
"keywords": [],
"dependencies": {
  "ejs": "2.3.4",
  "grunt": "1.0.1",
  "grunt-contrib-clean": "1.0.0",
  "grunt-contrib-coffee": "1.0.0",
  "grunt-contrib-concat": "1.0.1",
  "grunt-contrib-copy": "1.0.0",
  "grunt-contrib-cssmin": "1.0.1",
  "grunt-contrib-jst": "1.0.0",
  "grunt-contrib-less": "1.3.0",
  "grunt-contrib-uglify": "1.0.1",
  "grunt-contrib-watch": "1.0.0",
  "grunt-sails-linker": "~0.10.1",
  "grunt-sync": "0.5.2",
  "include-all": "^1.0.0",
  "rc": "1.0.1",
  "sails": "~0.12.11",
  "sails-disk": "~0.10.9"
},
"scripts": {
  "debug": "node debug app.js",
  "start": "node app.js"
},
"main": "app.js",
"repository": {
  "type": "git",
  "url": "git://github.com/bingo/12factor-app.git"
},
"author": "bingo",
"license": ""
}
# 接下来我们加入 mongodb 的库依赖(后续会用到),只需要执行:
 npm install sails-mongo --save

# 同时 package.json 中会有相应的变更
 {
   ...
   "dependencies": {
     ...
     "sails-mongo": "^0.12.2"  //最新加入的依赖
   }
   ...
 }

4.4. III. 配置

Factor解说

- 环境变量是一种清楚、容易理解和标准化的配置方法
- 环境变量可以非常方便地在不同的部署间做修改,却不动一行代码
- 与配置文件不同,不小心把它们签入代码库的概率微乎其微
- 与一些传统的解决配置问题的机制(比如 Java 的属性配置文件)相比,环境变量与语言和系统无关
- 存储在环境变量中的另一个好处是,方便和Docker配合使用

Factor实践

module.exports.connections = {
  mongo: {
     adapter: 'sails-mongo',
     url: process.env.MONGO_URL
  }
};
module.exports.models = {
  connection: mongo,
  migrate: 'safe'
};
MONGO_URL=mongodb://localhost:27017/12factor-app npm start

4.5. IV. 后端服务

Factor解说

- 所谓后端服务是指程序运行所需要的通过网络调用的各种服务,如数据库(MySQL),消息/队列系统(RabbitMQ),SMTP 邮件发送服务( Postfix),以及缓存系统(Memcached)等
- 除了本地服务之外,应用程序有可能使用了第三方发布和管理的服务,如 SMTP(例如 Postmark),数据收集服务,数据存储服务(如 Amazon S3),以及使用 API 访问的服务(例如 Twitter)等

Factor实践

4.6. V. 构建,发布,运行

Factor解说

- Cloud Native应用的构建流程把大部分发布配置挪到开发阶段,包括实际的代码构建和运行应用所需的生产环境配置
- 举例来说,直接修改处于运行状态的代码是非常不可取的做法,因为这些修改很难再同步回构建步骤
- 发布的版本就像一本只能追加的账本,一旦发布就不可修改,任何的变动都应该产生一个新的发布版本

Factor实践

构建

FROM hub.c.163.com/library/node:5.12.0
MAINTAINER bingohuang <me@bingohuang.com>
# 拷贝依赖清单
COPY package.json /tmp/package.json
# 安装依赖包
RUN cd /tmp && npm install --registry=https://registry.npm.taobao.org
# 将依赖包拷贝到应用程序目录下
RUN mkdir /app && cp -a /tmp/node_modules /app/
# 更改工作目录
WORKDIR /app
# 拷贝应用程序代码
COPY . /app
# 设置应用启动端口
ENV PORT 1337
# 暴露应用程序端口
EXPOSE 1337
# 启动应用
CMD ["npm","start"]
$ docker build -t 12factor-app:v1.0 .
$ docker push hub.c.163.com/bingohuang/12factor-app:1.0

发布

version: '2'
services:
mongo:
  image: hub.c.163.com/library/mongo:3.2
  volumes:
    - mongo-data:/data/db
  ports:
    - "27017:27017"
app:
  image: hub.c.163.com/bingohuang/12factor-app:1.0
  ports:
    - "1337:1337"
  links:
    - mongo
  depends_on:
    - mongo
  environment:
    - MONGO_URL=mongodb://mongo/12factor-app
volumes:
  mongo-data:
MONGO_URL=mongodb://mongo/12factor-app

运行

docker-compose up -d
docker-compose logs -f

注:为了方便不熟悉docker和docker-compose命令的人快速运行程序和本地调试,我在源代码中还提供了 docker.sh 脚本,方便构建、发布和运行应用(源码请看后续资料链接)。

4.7. VI. 进程

Factor解说

任何需要持久化的数据都要存储在 后端服务内,比如数据库。
Session 中的数据应该保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中。
最简单的场景中,代码是一个独立的脚本,运行环境是开发人员自己的笔记本电脑,进程由一条命令行(例如python my_script.py)。另外一个极端情况是,复杂的应用可能会使用很多 进程类型 ,也就是零个或多个进程实例。

Factor实践

$ docker  exec 12-factor ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.2  2.0 1076204 42024 ?       Ssl  18:22   0:00 npm
root        17  0.0  0.0   4340   724 ?        S    18:22   0:00 sh -c node app.js
root        18  0.9  4.5 1253808 93808 ?       Sl   18:22   0:01 node app.js
root        27  1.1  3.7 962884 77076 ?        Sl   18:22   0:01 grunt

到此,我们已经介绍完了6个Factor,并且都成功实践在了我们的应用上,Yes!

接下来,我们继续介绍和实践后续的6个Factor。

4.8. VII. 端口绑定

Factor解说

- 意思就是说:Web应用通过端口绑定(Port binding)来提供服务 ,并监听发送至该端口的请求。
- Cloud Native应用的服务接口优先选择 HTTP API 作为通用的集成框架。

Factor实践

ports:
    - "1337:1337" // 应用容器暴露1337端口在容器中,宿主机将其映射到1337端口

4.9. VIII. 并发

Factor解说

- 这意味着依赖底层平台就能实现横向扩展,不需要技术难度高的多线程编码。

Factor实践

4.10. IX. 易处理

Factor解说

- 这有利于快速、弹性的伸缩应用,迅速部署变化的代码或配置,提高健壮性
- 进程应当追求最小启动时间,可以提供了更敏捷的发布以及扩展过程
- 进程一旦接收终止信号(SIGTERM) 就会优雅的终止

Factor实践

4.11. X. 环境等价

Factor解说

缩小时间差异:开发人员可以几小时,甚至几分钟就部署代码
缩小人员差异:开发人员不只要编写代码,更应该密切参与部署过程以及代码在线上的表现
缩小工具差异:尽量保证开发环境以及线上环境的一致性
这是因为,不同的后端服务意味着会突然出现的不兼容,从而导致测试、预发布都正常的代码在线上出现问题。

Factor实践

4.12. XI. 日志

Factor解说

Factor实践

log:
  command: '-t a80277ea-4233-7785203ae328'
  image: 'logentries/docker-logentries’
  restart: always
  tags:
    - development
  volumes:
    - '/var/run/docker.sock:/var/run/docker.sock'

4.13. XII. 管理进程

Factor解说

- 运行数据移植
- 运行一个控制台也被称为 REPL shell,来执行一些代码或是针对线上数据库做一些检查。
- 运行一些提交到代码仓库的一次性脚本。

Factor实践

docker exec -ti ADMIN_CONTAINER_ID bash

5. 总结

至此,12-Factor一一实践完毕,从中可以看出,12-Factor并非相互独立,而是一个整体,有的涉及代码和框架(Node和Rails),有的涉及工具(Docker和Compose)有的涉及架构和平台。在云原生时代,12-Factor仍然具有强大的生命力,每一条原则都是应用开发的珠玑,而且每一个原则也不是一成不变的,随着新的理念出现,原有的Factor会得到延伸和发展,也会出现新的原则,有兴趣的同学,不妨读一读《Beyond the 12 Factor App》这本书,还会有更大的收获。最后,希望此次分享对你理解云原生应用、实践12-Factor有所帮助。

6. 参考链接: