The Twelve-Factor App

三十天很快要到了尾声了,今天要来介绍 The Twelve-Factor App(下称 12 Factor),它是开发 SaaS 的方法论,适用於 Web 或网路相关服务等软件开发。怎麽突然讨论起如何开发软件呢?这跟 Docker 好像没有什麽关系?可能有点奇怪,不过这个开发软件的方法论,确实跟 Docker 有很大的关系。

笔者是先接触 Docker 之後,再接触 12 Factor 的。当时笔者还处在把 Docker 当轻量 Vagrant 用的时期,当时只感到阻碍重重。比较经典的例子像是:搞不懂为什麽 Apache container 一定要在 running Apache 的时候,才能透过 docker exec 进入 container。

後来在看 12 Factor 的时候,才发现错把 container 当 VM 使用了。当时的感受是,原来 Docker 是实践 12 Factor 的好例子;而反过来说,我们撰写程序遵守了 12 Factor 方法,会让使用 Docker 更顺利。

今天带读者简单了解 12 Factor 与 Docker 相关联的地方,有兴趣的读者可以阅读原文。

I. Codebase

One codebase tracked in revision control, many deploys

一份原始码,可以创造出多份部署。下图是官方提供的示意图:

来源:12 Factor

Docker 也有一样的概念,若把 Dockerfile 作为原始码,则相同的 Dockerfile 可以在不同环境建置并使用 docker run 执行;若把 image 作为原始码又会更精简--不同的环境都能直接使用 docker run 执行。

II. Dependencies

Explicitly declare and isolate dependencies

明确地声明与隔离依赖。什麽是不明确地声明?比方说,直接在环境安装了全域都可以直接使用的套件,这就很容易踩到不明确声明。

Dockerfile 的流程中,必须明确写出安装依赖的过程,才有办法正常执行应用程序,如 Laravel image 的范例 Dockerfile:

# 安装 bcmath 与 redis
RUN docker-php-ext-install bcmath
RUN pecl install redis
RUN docker-php-ext-enable redis

# 安装程序依赖套件
COPY composer.* ./
RUN composer install --no-dev --no-scripts && composer clear-cache

有了明确声明依赖後,任何人都可以依照这个流程打造出一模一样的环境--执行 docker build

III. Config

Store config in the environment

把设定存放在环境里。设定包含下面几种:

  • IV. Backing services 的设定,如 DB / Redis 等。
  • 第三方服务的 credentials,如 AWS。
  • Application 在不同环境下,会有特别的设定,如域名。

不同环境的设定可能差异非常大,但它们都可以在同一份原始码上正常运作。这样的做法有几个好处:

  1. 安全,像 credentials 就不会随着原始码外流
  2. 若设定放在原始码,则调整设定就势必得从修改原始码开始;若设定放在环境,则直接修改环境上的设定即可。

MySQL environment 的范例正是综合了 I. Codebase 方法与 III. Config 来达成同一份原始码,不同设定的部署实例。

IV. Backing services

Treat backing services as attached resources

後端服务作为附加资源。後端服务包括了 MySQL、Redis 等常见的第三方服务,当然也包括了我们依本篇文章设计的 12 Factor App。

来源:12 Factor

这样设计的好处即容易替换服务,比方说想把 MySQL 换成 MariaDB,或是 Redis 3.0 升级成 Redis 5,只要把连线设定调整即可。

若使用 Docker Compose 会更方便,只要更新完定义档,重新执行启动指令就可以立即替换了:

docker-composer up -d

V. Build, release, run

Strictly separate build and run stages

严格区分 build 和 run。build 阶段才能调整程序与整合依赖成 artifacts;run 阶段则非常单纯,把 artifacts 拿来执行就行了。

来源:12 Factor

在 Docker container 修改程序码,其实是非常麻烦的,要做非常多前置准备。但如果遵守 build / run 分离的方法,就会变得非常简单。

VI. Processes

Execute the app as one or more stateless processes

使用一个以上的无状态 process 来执行应用程序。这里的关键在於要设计成无状态,如果有状态或资料要保存,可以透过 IV. Backing services 的资料库保存。

Docker 每次启动 container 都是全新的没有过去状态的,之前提到的一次性。换句话说,设计一个能在 Docker 上运作良好的 container,就代表有符合此方法--Container 应用正是活动此特性的范例参考。

VII. Port binding

Export services via port binding

透过 port 绑定来提供服务。

Docker 可以用 port forwarding 来对外提供服务,Dockerfile 则可以使用 EXPOSE 指令让 container 之间也可以互相使用 port 存取服务

EXPOSE 80

CMD ["php", "artisan", "serve", "--port", "80"]

VIII. Concurrency

Scale out via the process model

透过 process model 做水平扩展。12 Factor 是以工作类型来分类 process,如 HTTP 请求给 web process 处理,背景则是使用 worker process。

来源:12 Factor

配合 VI. Processes 提到的无状态特性,可以让应用程序非常容易做水平扩展,甚至是跨 VM、跨实体机器的扩展。

Container 即 process,因此 Docker 要做水平扩展是非常简单的--多跑几次 docker run 就行了,甚至 Docker Compose 还提供专用的 scale 指令:

docker-compose scale web=2 worker=3

IX. Disposability

Maximize robustness with fast startup and graceful shutdown

快速启动,优雅终止,最大化系统的强健性。

将应用程序设计成可以快速启动并开放服务,好处就在於扩展和上线变得非常容易。收到终止信号 SIGTERM 并优雅终止,则要求 process 停止接收任务,并把最的任务完成後,才真正结束 process。

Docker 在管理启动或终止都做的很完整,主要还是程序设计要得当。

X. Dev/prod parity

Keep development, staging, and production as similar as possible

尽可能保持环境一致,环境一致最大的好处还是在於开发除错的效率。「我的电脑上就没问题」这句话正是这个方法的反指标,正因个人环境上有做了特别安装,才让程序有办法正常运作,这个安装过程就得考虑是否要同步到其他人或测试环境上。

Dockerfile 是一个 IoC 很好的实践,因此非常容易做到环境一致。

XI. Logs

Treat logs as event streams

使用 event stream 输出 log。正如 IV. Backing servicesVI. Processes 所提到的,12 Factor App 不应该保存状态,类似的,它也不应该保存 log,而是要把 log 作为 event stream 输出。

Docker 可以截取 process 的标准输出(STDOUT),并透过内部机制转到 log driver,因此程序只要处理好标准输出即可。

XII. Admin processes

Run admin/management tasks as one-off processes

管理与维护任务作为一次性的 process 执行,像 migration 正是属於这一类的任务。

对 Docker 而言,要在已启动的 container 上执行 process 太简单了,使用 docker exec 即可达成任务。

docker exec -it web php artisan migrate

12 Factor 的目标

最後回头来看 12 Factor 当初设计的目标:

这段原文很长,但因为是必要的,所以还是无断复制过来

  • Use declarative formats for setup automation, to minimize time and cost for new developers joining the project;
  • Have a clean contract with the underlying operating system, offering maximum portability between execution environments;
  • Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration;
  • Minimize divergence between development and production, enabling continuous deployment for maximum agility;
  • And can scale up without significant changes to tooling, architecture, or development practices.

简单整理如下:

  1. 使用描述的格式设定自动化流程,让新人能用更小的成本加入专案。在为各种框架 build image 的时候有提到「只要有程序和 Dockerfile,读者就可以建得出跟笔者一样的环境与 server」。不仅如此,因为 Dockerfile 正是描述如何建置环境,因此对任何理解描述的开发者,都有办法调整里面的流程,并同步给其他开发者。
  2. 与 OS 之间有更清楚的介面,这样就能具备更高的移植性,因此 12 Factor App 不仅能在很多机器上执行,甚至是云端服务上也能运作良好。
  3. 追求环境一致性,因此能有更快的交付速度,在实现持续整合与持续部署会更加容易
  4. Process model 设计,让扩展服务变得非常容易,甚至不需要动到任何工具架构,或开发流程。

今日自我回顾

曾有人问笔者:身为一个开发者,学完 run container,学完 build image,之後要学什麽呢?

学习写出符合 12 Factor 的程序。

能在 Docker 上运作良好的,正是符合 12 Factor 的应用程序。


<<:  Day 29 |> Elixir 并行性 (三)

>>:  [Day29]Flutter Netflix UI 底部导航栏上的通知数量

Day 26:「按钮博物馆」- 轻松变化各种按钮元件

哈罗大家好~ 不知道昨天的进度条做的怎麽样? 想要交作业的人可以贴在昨天的留言区给我呦! 那我们今...

Day 19 - WooCommerce: 初始化付款外挂

虽然在 WordPress 中扩充功能有很多方式,例如在布景主题上使用子布景主题 (child th...

【从实作学习ASP.NET Core】Day17 | 後台 | 角色的 CRUD 页面

昨天在处理角色的时候已经把新增的页面处理好了 今天就用之前的方法把角色的其他页面也建立起来吧! 宣告...

(DAY 29) MS Teams另类应用:视讯切换器

每星期及每个月都会有一次固定的会议,加上其他大大小小的会议,一个月至少有6次以上的会议,每次会议看到...

【Day 18】混合云 x AWS Outposts EC2 设置

tags: 铁人赛 AWS Outposts network 前情提要 昨天将 AWS 的网路配置好...