第 13 章:完整系統整合 — 一鍵啟動、七步驟驗證、看見全貌
最後一章不學新東西,而是把你學過的全部東西一次跑起來:一個 compose 啟動 13 個容器,然後用一套七步驟的驗證流程證明整條 pipeline 是通的。這套驗證方法本身,就是你帶得走的資產。
做完這一章,你會做到
- 用
docker-compose-all.yml一個指令啟動全部服務(13 個容器)。 - 跑完七步驟端到端驗證:服務 → Web → logs → 發任務 → 執行 → 資料 → 編排。
- 對照全架構圖,說得出每個服務在系統裡的角色。
- 建立「驗證一個多服務系統」的標準流程——不是「容器有起來」就叫成功。
全架構圖(先看再動手)
┌─────────────┐
│ 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 帳號,屬進階選修內容)

送件鴿飛出去了