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 的應用程式。