AI & Data Dreamers練功房
寶箱片場規則
正片・課程手冊

第 13 章:完整系統整合 — 一鍵啟動、七步驟驗證、看見全貌

最後一章不學新東西,而是把你學過的全部東西一次跑起來:一個 compose 啟動 13 個容器,然後用一套七步驟的驗證流程證明整條 pipeline 是通的。這套驗證方法本身,就是你帶得走的資產。


做完這一章,你會做到

  1. 用 docker-compose-all.yml 一個指令啟動全部服務(13 個容器)。
  2. 跑完七步驟端到端驗證:服務 → Web → logs → 發任務 → 執行 → 資料 → 編排。
  3. 對照全架構圖,說得出每個服務在系統裡的角色。
  4. 建立「驗證一個多服務系統」的標準流程——不是「容器有起來」就叫成功。

全架構圖(先看再動手)

                    ┌─────────────┐
                    │   Airflow   │ :8081  排程觸發(第 10-12 章)
                    │ (Scheduler) │
                    └──────┬──────┘
                           │ 觸發 Producer / 直接呼叫
                           ▼
┌──────────┐    ┌──────────────────┐    ┌──────────────┐
│ Producer │───▶│    RabbitMQ      │───▶│ Celery Worker│
│ (發任務) │    │ :5672  :15672    │    │ (twse/tpex)  │
└──────────┘    └──────────────────┘    └──────┬───────┘
(第 1-3 章)        (第 1、4、7 章)           │ 爬 FinMind(第 2 章)
                    ┌──────────┐               │ 寫入 MySQL(第 5-6 章)
                    │  Flower  │ :5555         ▼
                    │ (監控)   │        ┌──────────────┐
                    └──────────┘        │    MySQL     │ :3306
                                        │   (mydb)     │
                ┌──────────┐            └──────┬───────┘
                │Metabase  │ :3000             │
                │ (第 8 章)│◀───查詢────────────┘
                └──────────┘            ┌──────────────┐
                                        │ phpMyAdmin   │ :8080
                (BigQuery 第 15 章)     │ (DB 管理)    │
                                        └──────────────┘

每一個框,你都在前面某一章跑過。這一章只是讓它們同時運行。

這張圖也有 draw.io 版本,可以自己編輯:課程手冊/drawio/本地端架構圖.drawio。想知道它是怎麼一步步畫出來的,看補充A。


這一章會用到的檔案

檔案 角色 說明
docker-compose-all.yml 全服務整合 13 個容器一次定義
airflow/Dockerfile Airflow image 第 10 章 build 過的 stock-airflow:latest

docker-compose-all.yml 裡的 13 個容器(下表 11 個主線容器,另有補充D 的 mongodb 與 mongo-express 兩個,跟主線互不相干、跟著一起起動即可):

容器 章節 說明
rabbitmq 1 訊息佇列(Broker),所有任務訊息都經過它
flower 1、7 Celery 的監控介面(第 1 章初次使用,第 7 章 Web 管理介面詳解)
mysql 5 業務資料庫,儲存爬到的股價資料
phpmyadmin 5、7 MySQL 的 Web 管理介面(第 5 章初次使用,第 7 章詳解)
crawler_twse 3 Celery Worker,只聽 twse 佇列、負責上市股票的任務
crawler_tpex 3 Celery Worker,只聽 tpex 佇列、負責上櫃股票的任務
airflow-postgres 10 Airflow 自己的 metadata 資料庫,存 DAG 狀態與執行紀錄
airflow-init 10 一次性的初始化容器,跑完會自動退出(Exit 0 是正常的)
airflow-webserver 10 Airflow 的 UI(走 8081,避開 phpMyAdmin 佔用的 8080)
airflow-scheduler 10 Airflow 的排程器,負責掃描 DAG 和觸發任務
metabase 8 BI 視覺化工具(設定庫放在 MySQL 的 metabasedb,volume 初始化時自動建立)

💡 注意兩個設計:Airflow UI 改走 8081(跟 phpMyAdmin 的 8080 不撞了);所有服務在同一個 compose 網路裡,容器互連一律用服務名。


一步一步跟著做

準備

先確認 image 存在、並把其他 compose 關掉,避免 port 衝突。「Step 1~7」這組編號留給下面的驗證流程專用,準備和啟動不佔編號:

# 確認 stock-airflow image 在(第 10 章 build 過)
docker images | grep stock-airflow
# 沒有就 build:docker build -f airflow/Dockerfile -t stock-airflow:latest .

# 其他 compose 全部先關掉,避免 port 衝突
docker compose -f docker-compose-local.yml down 2>/dev/null
docker compose -f airflow/docker-compose-airflow.yml down 2>/dev/null

cp .env.example .env    # 沒有 .env 的話

啟動:一個指令起全部服務

docker compose -f docker-compose-all.yml up -d

等 60~90 秒讓所有服務就緒(Airflow init、MySQL 初始化、Metabase JVM 都要暖機)。


七步驟端到端驗證

為什麼要七步? 因為「容器都是 Up」離「系統真的能動」還很遠。服務起來了但連不上、任務發了但沒人做、worker 跑了但資料沒進庫——每一種都是真實會發生的故障。七步驟就是逐層把「看起來會動」變成「證明會動」。

Step 1:服務狀態

docker ps -a --format 'table {{.Names}}\t{{.Status}}' | sort

✅ 全部 Up;airflow-init 是 Exited (0) —— 一次性初始化容器,跑完退出是正常的。

Step 2:Web 介面

curl -s -o /dev/null -w "RabbitMQ:  %{http_code}\n" http://localhost:15672
curl -s -o /dev/null -w "Flower:    %{http_code}\n" http://localhost:5555
curl -s -o /dev/null -w "phpMyAdmin:%{http_code}\n" http://localhost:8080
curl -s -o /dev/null -w "Airflow:   %{http_code}\n" http://localhost:8081/health
curl -s -o /dev/null -w "Metabase:  %{http_code}\n" http://localhost:3000/api/health

✅ 五個都 200。Metabase 最慢(JVM),503 就再等 30 秒。

Step 3:Logs 判讀

docker logs crawler_twse 2>&1 | tail -3     # 要看到 ready
docker logs crawler_tpex 2>&1 | tail -3     # 要看到 ready
docker logs airflow-init 2>&1 | tail -3     # 要看到 User "admin" created

✅ worker ready、admin 建好。

💡 判讀重點:啟動初期的 Connection refused 是啟動順序造成的暫時現象(worker 比 RabbitMQ 先跑),會自動重連——這不算錯誤。要區分「暫時的連線重試」和「真正的 error」。

Step 4:發任務

uv run crawler/producer_multi_queue.py

✅ send task_2330 task、send task_00679b task。

Step 5:Worker 執行

sleep 5
docker logs crawler_twse 2>&1 | grep succeeded | tail -3
docker logs crawler_tpex 2>&1 | grep succeeded | tail -3

✅ 兩個 worker 各自 succeeded——twse 做 2330、tpex 做 00679B(第 3 章的分流仍在運作)。

Step 6:DB 驗證

docker exec mysql mysql -uroot -p1234 mydb -e \
  "SHOW TABLES; SELECT stock_id, COUNT(*) AS cnt FROM TaiwanStockPrice GROUP BY stock_id;"

✅ TaiwanStockPrice 存在、兩支股票各有數百筆。任務 succeeded 還不夠,資料在庫裡才算數。

Step 7:Airflow 編排層

docker exec airflow-webserver airflow dags list 2>&1 | grep stock

✅ 列出 stock_crawler_dag、stock_crawler_producer_dag、stock_crawler_etl_dag 等。

開 http://localhost:8081(admin / admin),想跑就 unpause + trigger stock_crawler_dag,回 Step 6 再驗一次資料增長——那就是完整閉環。


服務總覽(貼在牆上的那張表)

Port 服務 URL 帳密
5672 RabbitMQ(AMQP) — worker / worker
15672 RabbitMQ 管理介面 http://localhost:15672 worker / worker
5555 Flower http://localhost:5555 —
3306 MySQL — root / 1234
8080 phpMyAdmin http://localhost:8080 root / 1234
8081 Airflow http://localhost:8081 admin / admin
3000 Metabase http://localhost:3000 首次自設

卡住了?常見錯誤這樣排

你遇到的狀況 原因 怎麼解
worker 的 log 出現 Connection refused worker 比 RabbitMQ 先啟動,RabbitMQ 還沒準備好 worker 會自動重連,等幾秒就恢復,不需要處理
Airflow UI 打不開 init 的初始化還沒有跑完 先用 docker logs airflow-init 確認初始化完成,再執行 docker restart airflow-webserver
Metabase 回 503 Metabase 是 JVM 應用,啟動比其他服務慢 等 30~60 秒再試一次
port 被佔用 其他的 compose 還開著,跟這一套搶同一個 port 回到「準備」段,先把其他 compose down 掉
報錯說 stock-airflow image 不存在 還沒有 build 過這顆 image 執行 docker build -f airflow/Dockerfile -t stock-airflow:latest .
MySQL 裡沒有資料表 還沒有發過任何任務,worker 沒寫過資料 跑驗證的 Step 4 發一批任務;或載入 mock 資料:docker exec -i mysql mysql -uroot -p1234 mydb < example/mock_stock_price_data.sql

檢查你是不是真的做到了

# 你應該看到 它證明了什麼
1 13 個容器全部是 Up(init 是 Exited 0) 服務層正常
2 五個 Web 介面都回 200 入口層正常
3 logs 裡沒有真正的錯誤、worker 顯示 ready 執行層正常
4 發了任務 → worker 顯示 succeeded → 資料庫查得到資料 整條資料流是通的
5 Airflow 的 DAG 列表正常顯示 編排層正常

想一想(確認你懂了)

Q1:為什麼「容器全部 Up」不能當成「系統正常」的證明?

Up 只代表行程存在,不代表:連得上彼此(網路對不對)、任務有人做(-Q 訂對了嗎)、資料寫得進(帳密 host 對嗎)。這一章的七步驟就是把這些斷點逐層驗掉——驗證要驗到資料落地,不是驗到容器啟動。

Q2:airflow-init 顯示 Exited,該緊張嗎?

看 exit code:Exited (0) 是一次性初始化容器的正常結局(做完就走);Exited (1) 以上才是出錯。學會分辨「設計上就會退出的容器」和「掛掉的容器」。

Q3:如果 Step 5 的 worker succeeded、但 Step 6 查不到資料,你會怎麼追?

這是第 7 章排錯 SOP 的實戰:任務成功但資料不在,通常是「寫去了別的地方」——worker 容器裡的 MYSQL_HOST 指到哪?是不是連去了另一個 MySQL(例如本機那個)?查 worker 環境變數(docker exec crawler_twse env | grep MYSQL)、再查它 log 裡的連線資訊,把「寫到哪裡去了」找出來。


換你試試看

練習 1:跑完整閉環(Airflow 版)

在 Airflow UI(8081)unpause + trigger stock_crawler_dag,等它跑完,用 Step 6 的 SQL 確認 10 支股票的資料都增加了。這是「編排層驅動的端到端」——比手動 producer 又高一層。

練習 2:接上 Metabase

照第 8 章的方法在 Metabase(3000)加 MySQL 資料來源(Host 填 mysql),把剛爬的資料拉成走勢圖。至此:排程 → 爬取 → 入庫 → 視覺化,全部在同一組容器裡發生。

練習 3:故障演練

故意 docker stop crawler_twse,發一批 twse 任務,用第 7 章的 SOP 找出「症狀 → 原因」(提示:RabbitMQ 的 Ready 會堆)。然後 docker start crawler_twse,看任務被消化。這是整套系統的綜合排錯驗證。


全課程收束:整條 pipeline 的全貌

十三章走完(加上下一章的雲端延伸),整套系統長這樣:

Airflow(DAG 定時 + 依賴 + 補跑 + 監控)
   → Producer 發任務 → RabbitMQ 佇列 → Celery Worker 爬 FinMind
   → 雙寫落地:MySQL(冪等 upsert)+ BigQuery(OLAP 分析,雲端段啟用,第 15 章詳講)
   → Metabase 做成 Dashboard
外加:失敗自動 retry / requeue、Flower 監控、多佇列分流、排錯 SOP、
     以及執行引擎可以抽換——第 12 章示範過把 Airflow 的 executor
     從 LocalExecutor 換成 CeleryExecutor,一個環境變數就能水平擴充
  • Phase A(第 1~7 章):把「任務怎麼可靠地跑」徹底拆解——Celery、分流、失敗處理、落地、冪等、監控介面。
  • Phase B(第 8~9 章):資料的下游出口與自動化——視覺化與定時排程。
  • Phase C(第 10~13 章):用 Airflow 把所有東西編排成生產級工作流,最後整套整合。
  • 延伸(第 14 章起):上雲後爬蟲開啟雙寫——同一份資料同時落 MySQL 與雲端倉儲 BigQuery(第 15 章詳講),銜接後續的雲端部署。

最後,試著從第 12 章的 Airflow DAG 往回看:每一個 Operator 背後,都是你前面某一章學過的東西。這種「回頭看見全貌」的一刻,就是整套課最有價值的收束。恭喜你走完了。


收工

docker compose -f docker-compose-all.yml down        # 保留資料
docker compose -f docker-compose-all.yml down -v     # 全部清掉重來

下一章要做什麼

本地的完整系統跑通了。下一章是雲端延伸:把 MySQL 裡的股價同步進資料倉儲 BigQuery(OLTP → OLAP),讓大規模分析不拖累營運資料庫。(需要 GCP 帳號,屬進階選修內容)