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

社区编辑2018-05-18 14:26

本文继续上文内容:云原生时代下的12-factor应用与实践(上)


Factor实践:

对我们的应用程序来说,用到的后端服务就是 MongoDB 数据库。我们正是通过 MONGO_URL 来传递 MongoDB 的资源地址,从而实现了后端服务和应用程序的解耦。

如果当前这个 MongoDB 实例出问题了,我们可以通过设置 MONGO_URL 这个环境变量,很方便的切换一个新的实例。


5 构建,发布,运行

Factor解说:

12-Factor 应用严格区分构建,发布,运行这三个步骤。

Cloud Native应用的构建流程把大部分发布配置挪到开发阶段,包括实际的代码构建和运行应用所需的生产环境配置。

举例来说,直接修改处于运行状态的代码是非常不可取的做法,因为这些修改很难再同步回构建步骤。

发布的版本就像一本只能追加的账本,一旦发布就不可修改,任何的变动都应该产生一个新的发布版本。



Factor实践:

针对这条原则,强烈推荐使用 Docker及其组件(Compose),它的核心理念正是:Build, Ship and Run,将适合在整个构建、发布和运行流程,我们也将从这三个方面进行讲解。

① 构建:

书写构建脚本:Dockerfile

FROM hub.c.163.com/library/node:5.12.0
MAINTAINER bingohuang 
# 拷贝依赖清单
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 构建

$ docker build -t 12factor-app:v1.0 .

Docker 镜像推送:可以将其 push 到指定的镜像仓库,比如网易云基础服务的镜像仓库中。

$ docker push hub.c.163.com/bingohuang/12factor-app:1.0

② 发布:

书写发布脚本:docker-compose.yml


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)通过环境变量注入进去。

MONGO_URL=mongodb://mongo/12factor-app

③ 运行:

可以通过 Docker Compose在本地运行,也可以通过云平台来在线编排(网易云基础服务即将支持服务编排功能)。

docker-compose up -d

继而查看日志

docker-compose logs -f

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


6 进程

Factor解说:

12-Factor 应用的进程必须无状态且无共享。

任何需要持久化的数据都要存储在后端服务内,比如数据库。Session 中的数据应该保存在诸如 Memcached 或 Redis 这样的带有过期时间的缓存中。

运行环境中,应用程序通常是以一个和多个进程 运行的。

最简单的场景中,代码是一个独立的脚本,运行环境是开发人员自己的笔记本电脑,进程由一条命令行(例如 python my_script.py)。另外一个极端情况是,复杂的应用可能会使用很多 进程类型 ,也就是零个或多个进程实例。

这么做是为了保证 Cloud Native基础设施的速度和效率。

Factor实践:

虽然这是一个简单的 demo应用,但查看 docker容器中的运行进程,发现也有4个进程在运行,其中 npm也就是我们的启动进程,`node app.js` 是实际运行应用的进程。

$ 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

在这里,我们的应用进程是无状态的,持久化的数据都存储在了后端服务 MongoDB 当中。


7 端口绑定

Factor解说:

12-Factor 应用通过自我加载而不依赖于任何网络服务器就可以创建一个面向网络的服务。

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

还要指出的是,端口绑定这种方式也意味着一个应用可以成为另外一个应用的 后端服务 ,调用方将服务方提供的相应 URL 当作资源存入 配置 以备将来调用。

Factor实践:

docker-compose文件为我们很好的定义了端口绑定。

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

需要注意的是,如果在一个宿主机中部署多个应用实例,就不能将一个宿主机端口映射到多个容器端口(端口冲突),解决方法是在这之上加一个负载均衡,负载宿主机的不同端口服务所对应的不同容器。



8 并发

Factor解说:

12-factor 应用通过进程模型进行扩展,把进程看作是一等公民,并且具备具备的无共享,水平分区的特性。

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

举例来说,HTTP 请求可以交给 web 进程来处理,而常驻的后台工作则交由 worker 进程负责,定时任务交由 clock 来处理,这样扩展每一类的进程就非常方便,如下图所示:



Factor实践:

如第六个原则所描述,我们的应用拥有多个进程,最主要的是 Node.js 的http server进程,进程都是无状态并无共享,所以我们可以非常容易的水平扩展应用。


9 易处理

Factor解说:

12-Factor 应用的进程是易处理(disposable)的,意思是说任何进程都可以快速启动和优雅终止,这样做的好处是:

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

如下图所示,就是一个优雅的应用启动和终止流程。



Factor实践:

Docker 先天的轻量级和隔离性,就非常适合来做快速启动和优雅终止,Docker非常适合实践这条原则,在我们的应用中,就加入了 Docker和Compose实践。

针对线上环境,推荐构建在容器云平台之上(比如网易云基础服务平台),可以更优雅的处理进程的启动和停止。


10 环境等价

Factor解说:

12-Factor 应用想要做到持续部署就必须缩小本地与线上差异,包括以下三种差异:

○ 缩小时间差异:开发人员可以几小时,甚至几分钟就部署代码;
○ 缩小人员差异:开发人员不只要编写代码,更应该密切参与部署过程以及代码在线上的表现;
○ 缩小工具差异:尽量保证开发环境以及线上环境的一致性。

12-Factor 应用的开发人员应该反对在不同环境间使用不同的后端服务。

这是因为,不同的后端服务意味着会突然出现的不兼容,从而导致测试、预发布都正常的代码在线上出现问题。

Factor实践:

我们的应用程序中,使用了 docker-compose作为我们的发布脚本,它使得应用既可以在本地运行,也可以在任何支持 Docker 的云平台上运行,应用无需变化,只需修改配置文件,很好的解除了不同环境的差异化。

从以往经验来看,传统应用和 12-Factor应用会存在如下差异:




11 日志

Factor解说:

12-factor应用本身从不考虑存储自己的输出流。相反,每一个运行的进程都会直接的标准输出(stdout)事件流。

当日志是由云平台而不是应用包含的库处理时,日志处理机制必须保持简单。

Factor实践:

许多服务都能提供日志集中管理,比如 ELK、Splunk、Logentries,而且大多数都能方便的和 Docker集成在一起。

这里以 Logentries 为例来为应用集成日志服务,需要在 docker-compose 文件中加入 log 服务,如下:

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

一个典型的 Logentries 面板界面如下:



12 管理进程

Factor解说:

开发人员经常希望执行一些管理或维护应用的一次性任务,例如:

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

12-Factor应用中,一次性管理进程应该和正常的常驻进程(应用进程)使用同样的环境,并且使用相同的代码和配置,基于某个发布版本运行,随着其他的应用程序一起发布。

在 Cloud Native中,管理任务也是一个进程,而不是特别的工具;同样重要的是,管理任务的进程不应使用秘密的 API 或者内部机制。

Factor实践:

我们可以在 docker-compose 文件中定义管理服务,和程序一起执行。

我们可以通过通过docker exec命令执行一些管理任务,比如:

docker exec -ti ADMIN_CONTAINER_ID bash

如果多个容器处在相同的网络下,可以通过一个容器来管理其它容器。


总结

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

参考链接
源代码:
https://github.com/bingohuang/12factor-app

文章地址:
http://talks.bingohuang.com/2017/cloud-native-12factor.article

12-Factor 在线书籍:
http://12.bingohuang.com/zh_cn/index.html

12-Factor 书籍开源:
https://github.com/bingohuang/12factor-gitbook