自由學習的風

幽夢影 張潮 少年讀書,如隙中窺月;中年讀書,如庭中望月;老年讀書,如臺上玩月。皆以閱歷之淺深,為所得之淺深耳。

[AIgen] 機器掛了,只有Linux映像檔,如何找回定時執行的設定?

2026年8月10日 星期一

當伺服器突然崩潰、無法正常開機,而你手邊只剩下一份 Linux 系統映像檔(Image),或者是好不容易將硬碟掛載(Mount)到臨時系統的 /mnt/lvm 目錄時,身為維運或開發人員的你,該如何拯救那些珍貴的 Crontab 定時排程設定?

在正常的 Linux 環境下,我們習慣直接輸入 crontab -l 來查看排程。然而,在目前只有掛載目錄、沒有開機的狀態下,因為背景服務(Cron Daemon)根本沒有執行,這個指令是完全無法運作的。

這時候,我們必須跳過指令,直接深入映像檔的結構。本文將帶你手把手透過三個方向,直接去 /mnt/lvm 底下把所有遺失的定時設定挖出來!


方法一:直擊使用者排程(User Crontabs)

在 Linux 系統中,所有個別使用者(包含 root 帳號)自行透過 crontab -e 建立的排程,本質上都是一個獨立的文字檔,並以「使用者名稱」作為檔名儲存在特定的緩衝目錄中。

既然映像檔已經掛載在 /mnt/lvm,我們可以直接查看底下的對應路徑:

🔍 檢索步驟:

1. 列出所有擁有排程的使用者:

ls -la /mnt/lvm/var/spool/cron/crontabs/

2. 直接查看該使用者的排程內容:

cat /mnt/lvm/var/spool/cron/crontabs/[使用者名稱]

*(例如:欲查看 root 的排程,請輸入 cat /mnt/lvm/var/spool/cron/crontabs/root)*


方法二:地毯式搜尋系統級排程(System-wide Crontabs)

如果你的排程當初是交由系統全域執行,或是由特定軟體套件自動建立的,它們通常不會出現在上述的使用者目錄中,而是散落在系統設定的 /etc 目錄內。

請依序檢查 /mnt/lvm 底下的這四個關鍵位置:

  • 📁 主要核心設定檔: 這是系統最基礎的排程表。
    cat /mnt/lvm/etc/crontab
  • 📁 獨立套件排程目錄: 許多第三方軟體(如自動備份工具、安全防護套件)會將排程獨立寫成檔案放在這裡。
    ls -la /mnt/lvm/etc/cron.d/
  • 📁 週期性執行腳本目錄: 檢查系統預設的週期性資料夾,裡面通常放著直接被定時觸發的 Shell Script(腳本檔)。
    • /mnt/lvm/etc/cron.hourly/ (每小時執行)
    • /mnt/lvm/etc/cron.daily/ (每天執行)
    • /mnt/lvm/etc/cron.weekly/ (每週執行)
    • /mnt/lvm/etc/cron.monthly/ (每月執行)

方法三:終極大絕!從系統日誌(Logs)逆向重建

如果最糟糕的情況發生了——定時排程的檔案已經損毀,或者你在上述目錄完全找不到紀錄,我們還有一招隱藏版技巧:從舊系統過去的執行紀錄中逆向推敲。

只要該排程在機器掛掉前曾經執行過,Linux 的日誌系統就會留下足跡。我們可以透過分析舊系統的日誌,查出過去「什麼時間點」執行了「什麼指令」,進而重新拼湊出原本的 Crontab 設定。

🔍 逆向追蹤指令:

grep -i cron /mnt/lvm/var/log/syslog

*(註:視舊系統的 Linux 版本與環境不同,部分舊系統或特定發行版的紀錄可能會儲存在 /mnt/lvm/var/log/cron 檔案中。)*


💡 進階技巧:直接切換環境執行

如果你覺得路徑一直加 /mnt/lvm/ 很麻煩,或者你想直接在該映像檔環境下執行其他需要環境變數的救援指令,你可以利用 chroot 工具,把目前終端機的根目錄直接「切換」進去:

sudo chroot /mnt/lvm

切換成功後,你就可以直接輸入 crontab -l(此時讀取的就是映像檔內的排程),或是直接用正常的 /etc/crontab 路徑進行操作囉!

希望這篇文章能幫各位在伺服器災難復原時,快速找回遺失的自動化排程!

[AIgen] MariaDB 生產環境怎麼選?從 10.11、11.4 到 12.3 LTS 的升級風險與最佳路徑指南

在維護企業級或個人專案的資料庫時,選擇一個穩定、支援週期長的 LTS(長期支援) 版本是至關重要的決定。隨著 MariaDB 官方近年架構更動頻繁,許多仍停留在經典版本(如 10.11 LTS)的維運人員,正面臨是否該升級、該跳到哪一個世代的交叉路口。

本文將為你梳理目前主流的 MariaDB LTS 版本特點,並深度解析跨代升級(10 LTS 升級到 11 或 12 LTS)的潛在風險與安全路徑,幫你的資料庫無痛續命!


📌 一張表看懂現行 MariaDB LTS 版本現況

MariaDB 官方針對生產環境提供了不同的長期支援版本。以下是目前市面上最主要的四大 LTS 選擇:

  • MariaDB 10.11 LTS (經典穩健):廣泛使用的舊世代穩定版本,支援至 2028 年 2 月
  • MariaDB 11.4 LTS (目前主力推薦):穩定可靠的長期維護版本,也是目前的黃金升級目標,支援至 2029 年 5 月
  • MariaDB 11.8 LTS (過渡型 LTS):包含向量搜尋與複製效能增強的年度 LTS 版本,支援至 2028 年 6 月
  • MariaDB 12.3 LTS (最新世代):最新推出的長期支援版本,提供優化的向量搜尋與極佳的寫入效能。

🛑 大跨代升級:從 10 LTS 直升 12 LTS 有哪些風險?

如果你的資料庫目前處於 10.11 LTS,想要一步到位直接升級到 12.3 LTS官方非常不建議這樣做,因為這屬於跨越兩個大版本的重大變更,具備相當高的風險!

直接跨代硬幹,最容易遇到以下三大核心問題:

  1. 隔離級別預設值變更(Behavior Change)
    在 12.3 LTS 中,innodb_snapshot_isolation 的預設值改成了 ON(旨在讓 REPEATABLE READ 的行為更精確)。這可能會改變你既有應用程式的交易(Transaction)鎖定行為,必須實測確認。
  2. 查詢優化器(Optimizer)行為劇變
    11.x 世代對優化器进行了大量重構,全面調整了成本估算模型。部分在舊版 10.x 跑得很快的複雜 SQL 語法,在 12.x 可能因為成本估算模型改變而突然變慢,產生效能退化(Regressions)。
  3. 廢棄參數與套件依賴衝突
    10.11 的許多舊 my.cnf 參數在 12.x 已被移除,直接沿用可能導致資料庫無法啟動。另外,若有使用 Galera Cluster,12.3 起已移除自動依賴,必須手動補裝 mariadb-server-galera,否則叢集會直接無法同步。
⚠️ 官方推薦的安全路徑:必須採兩階段升級。先從 10.11 LTS 升級至 11.4 LTS 並執行結構更新,確認穩定後,再從 11.4 LTS 升級至 12.3 LTS。

💡 折衷方案:如果只升級到 11.4 LTS 呢?

相較於直奔 12 LTS,選擇升級到 11.4 LTS 是目前風險顯著降低、且最為穩健的策略

從 10.11 到 11.4 是一條官方標準支援的直升路徑,不需要多階段過渡。不過,11.4 畢竟也是重大升級(Major Upgrade),你仍需留意以下轉變:

  • 優化器成本模型調整:雖然 11.x 的新優化器對大部分查詢都有利,但極少數複雜的 JOIN 或特定索引查詢仍可能誤判。
  • 徹底移除 InnoDB Change Buffer:為了簡化代碼並提升現代 SSD 的寫入穩定性,11.0 起已移除此快取。如果應用程式有極大量的「隨機非聚集索引寫入」,可能會觀察到磁碟 I/O 壓力些微上升。
  • 一樣不可逆向降級:11.4 使用了全新的 InnoDB 檔案格式與 Redo Log 架構。一旦升級並更新系統表,資料檔案將永久無法退回 10.11 讀取

📊 升級終極對決:11.4 LTS vs 12.3 LTS

評估維度 升級至 11.4 LTS 升級至 12.3 LTS
路徑複雜度 🟢 (可直接從 10.11 覆蓋升級) 🔴 (需先過渡到 11.4,進行兩次升級)
隔離級別行為 🟢 無變更(維持既有鎖定邏輯) 🟡 有變更innodb_snapshot_isolation 預設開啟)
Galera 套件結構 🟢 維持原樣 🟡 結構拆分(需手動補裝 Galera 套件)
生產環境建議度 極高(目前最推薦的穩定長效版本) 🔵 (適合需要最新向量搜尋特性的團隊)

🛠️ 維運人員的「防踩雷」升級準備

不論你最後決定升級到 11.4 還是 12.3,在動手更新 production 環境前,請務必嚴格執行以下三件事:

  1. 乾淨關閉(Clean Shutdown):升級前關閉服務時,務必確認 innodb_fast_shutdown 不可設定為 2(不可強制崩潰關閉),以確保 Redo Log 完全清空、資料全面落盤,否則新版二進位檔案可能無法讀取舊日誌。
  2. 完整的物理與邏輯備份:升級失敗是無法直接降級的!請務必使用 MariaDB Backup 進行完整物理備份,並針對 mysql 系統資料庫進行 mariadb-dump 備份。
  3. 搭建 Staging 環境盲測:強烈建議匯入一份真實資料到測試機,模擬升級流程,並將後端應用的壓測腳本導過去跑一次,觀察有沒有噴任何 SQL 語法錯誤或效能嚴重下滑的現象。

結語

如果你的專案沒有迫切需要 12.x 的新功能(例如最新版的向量搜尋優化),現階段將舊環境升級到 11.4 LTS 是投資報酬率最高、也最安全的選擇。它不僅能避開 12.x 許多行為改變的風險,還能穩定地將你的資料庫安全生命週期一口氣延長到 2029 年。

Laravel 排程在 Docker 中執行

2026年7月28日 星期二

Laravel 專案有排程要執行,但是 php-fpm container 裡沒有 crontab 指令,這是很常見的情況。

這時候不要急著把 cron 裝進 php-fpm。php-fpm 本來是處理網站 request 的,排程可以用別的方式做。

需求:

要定時執行:

php artisan forum:sync

例如每天 4 點和 15 點各執行一次。

方法一:由 Linux 主機 crontab 執行

可以直接在 host 的 crontab 裡呼叫 container 裡的 artisan:

docker exec -w /var/www/default php-fpm php artisan forum:sync

先手動測一次:

docker exec -w /var/www/default php-fpm php artisan forum:sync

如果可以正常執行,再加入 crontab:

crontab -e

每天 04:00 和 15:00 執行:

0 4,15 * * * /usr/bin/flock -n /tmp/forum-sync.lock /usr/bin/docker exec -w /var/www/default php-fpm php artisan forum:sync >> /var/log/forum-sync.log 2>&1

指令說明:

  1. 0 4,15 * * *

每天 4 點和 15 點執行。

  1. /usr/bin/flock -n /tmp/forum-sync.lock

避免上一輪還沒跑完,下一輪又啟動。

  1. /usr/bin/docker exec

從 host 執行 container 裡的指令。

  1. -w /var/www/default

指定 Laravel 專案所在目錄。

  1. >> /var/log/forum-sync.log 2>&1

把執行結果和錯誤訊息寫入 log。

方法二:建立 scheduler container

如果想比較 Docker 化,也可以另外建立一個 scheduler service:

laravel-scheduler:
  image: "${PHP_VERSION}"
  container_name: laravel-scheduler
  working_dir: /var/www/default
  volumes:
    - ./wwwroot:/var/www
    - ./session:/var/lib/php/session
  environment:
    TZ: ${TZ}
  command: sh -c "while true; do php artisan schedule:run --verbose --no-interaction; sleep 60; done"
  restart: unless-stopped
  depends_on:
    - mysql

啟動:

docker compose up -d laravel-scheduler

這種方式適合有使用 Laravel scheduler,也就是在 app/Console/Kernel.php 裡集中管理排程的專案。

常見錯誤:

如果出現:

Could not open input file: artisan

代表 -w /var/www/default 不是 Laravel 專案所在目錄,要改成實際路徑。

如果實際使用 PHP 8.3 container,就要把 container 名稱改成:

php83-fpm

結論:

php-fpm 裡沒有 crontab 不算問題。

簡單需求可以用 host crontab 呼叫:

docker exec ...

比較完整的 Laravel 排程,則可以用獨立 scheduler container。

不要把所有功能都塞進 php-fpm,之後比較好維護。

PHP-FPM Docker image 選擇與重建


最近整理 PHP-FPM 的 Dockerfile,順便把 PHP 8.4、PHP 8.2、PHP 8.3、PHP 7.4 的 image 選擇記錄一下。

PHP 8.4 要選哪一個?

如果是一般 Laravel、Symfony、WordPress 或自己寫的 PHP 網站,建議先用:

FROM php:8.4-fpm

如果想更明確,可以寫:

FROM php:8.4-fpm-trixie

Debian-based image 比較適合一般網站環境,extension 和系統套件比較不容易踩到奇怪的相容性問題。

Alpine base 呢?

Alpine image 比較小,例如:

FROM php:8.4-fpm-alpine

但 Alpine 使用 musl libc,有時 PHP extension、Composer package 或外部 binary 會比較麻煩。

除非真的很在意 image 大小,不然一般網站我會先選 Debian 版。

不建議用 buster

buster 是比較舊的 Debian 系列,不建議再拿來當新的 PHP 8.4 image 基礎。

如果要固定 Debian 版本,可以考慮:

FROM php:8.4-fpm-trixie

或依需求使用 bookworm 系列。

官方 image 更新後要重新 build

如果官方 php:8.4-fpm 有更新,本機自建 image 不會自己變新。

要重新 build,建議加上 --pull:

docker build --pull -f Dockerfile_php84-fpm -t tiebob/php:8.4-fpm .

如果用 Compose:

docker compose build --pull
docker compose up -d

如果想完全不用 cache:

docker compose build --pull --no-cache

Dockerfile 整理重點:

這次整理 Dockerfile 時,主要做了幾件事:

  1. 明確指定 base image
  2. apt-get update、apt-get install、清 cache 放在同一層
  3. 加上 --no-install-recommends
  4. 移除 production image 不必要的 vim
  5. Composer 改成從官方 image 複製

Composer 可以這樣裝:

COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer

這樣比在 Dockerfile 裡 curl installer 清楚一些。

PHP 7.4:

後來也建立了 PHP 7.4 的 Dockerfile:

FROM php:7.4-fpm-bullseye

不過 PHP 7.4 已經是 EOL,只建議給舊系統短期使用,不適合新的正式服務。

所以如果專案裡還有 PHP 7.4,最好在檔案裡加註解,提醒自己這是 legacy 用途。

結論:

我的選擇大概是:

一般網站:php:8.4-fpm 或 php:8.4-fpm-trixie
想省 image 大小:php:8.4-fpm-alpine
舊系統相容:php:7.4-fpm-bullseye,但要標示風險

Image 不一定要追最新,但版本和用途要寫清楚,之後維護才不會搞混。

Docker container 的 UID/GID 權限整理

示意圖:Docker UID/GID 權限關係


這次整理 Docker 多網站環境時,遇到一個很常見的問題:不同 container 裡的使用者 UID/GID 不一樣,結果 bind mount 到 host 的檔案權限就容易亂掉。

問題:

目前有這些服務:

  1. nginx
  2. php-fpm
  3. php82-fpm
  4. php83-fpm
  5. mysql

PHP-FPM 大多已經是:

uid=1000(www) gid=1000(www)

但是 Nginx worker 可能是:

uid=101(nginx) gid=101(nginx)

MySQL 則是自己的:

uid=27(mysql) gid=27(mysql)

這時候如果 PHP 建立檔案、Nginx 要讀檔,或者 host 要直接修改檔案,就可能遇到權限問題。

原則:

不要把所有 container 都硬改成同一個 UID。

應該分成兩類:

網站檔案:
nginx、php-fpm、php82-fpm、php83-fpm

資料庫資料:
mysql

也就是網站相關服務統一,MySQL 保持自己的使用者。

建議目標:

nginx worker -> 1000:1000
php-fpm      -> 1000:1000
php82-fpm    -> 1000:1000
php83-fpm    -> 1000:1000
mysql        -> 保持 mysql 預設 UID/GID

網站目錄可以整理成:

sudo chown -R 1000:1000 ./wwwroot
sudo chown -R 1000:1000 ./session
sudo chown -R 1000:1000 ./log/nginx

MySQL 資料目錄不要一起改。

如果 MySQL container 裡查到是 27:27:

sudo chown -R 27:27 ./data/mysql/data

Nginx 注意事項:

原本以為可以直接在 nginx.conf 寫:

user 1000 1000;

結果啟動時出現:

getpwnam("1000") failed

原因是 Nginx 的 user 指令會去找使用者名稱,不是單純用數字 UID。

比較好的方式是自建 Nginx image,在 image 裡建立一個 www 使用者,UID/GID 是 1000:1000,然後 nginx.conf 寫:

user www www;

驗證:

先讓 PHP 建立測試檔:

docker compose exec php-fpm sh -c 'echo ok > /var/www/permission-test.txt'

再讓 Nginx 讀取:

docker compose exec nginx sh -c 'cat /var/www/permission-test.txt'

回 host 看 owner:

ls -lan ./wwwroot/permission-test.txt

如果看到:

1000 1000

而且 Nginx 也能讀到 ok,表示權限大致整理好了。

結論:

遇到 Docker 權限問題,不要先用:

chmod -R 777

這只是把問題蓋掉。

比較好的做法是先弄清楚誰要讀、誰要寫,再把共同使用同一批檔案的 container 對齊 UID/GID。

Docker 多網站部署架構整理

示意圖:Docker 多網站部署架構








圖片檔 blog-drafts/images

目前在 Linux 主機上利用 Docker 跑多個網站,這次順手把目前的建置方式整理一下,避免之後要調整時又要重新看一次。

環境:

  1. Linux 主機
  2. Docker / Docker Compose
  3. Nginx 或 Caddy 做 Web 入口
  4. 多個 PHP-FPM container
  5. MySQL container

目前架構:

大致上是這樣:

Browser
  ↓
Nginx / Caddy
  ↓
依不同網站轉給不同 PHP-FPM
  ↓
MySQL

這樣的做法有點像把傳統的 LEMP 主機,改成用 Docker 來管理。

目的:

  1. 一台主機可以放多個 PHP 網站
  2. 不同網站可以使用不同 PHP 版本
  3. 網站目錄、log、資料庫資料都放在 host 上,方便備份
  4. Web server 統一處理 80、443、SSL、virtual host
  5. 需要瀏覽器自動化時,可以用 geckodriver container

目前比較像「共享主機 Docker 化」,不是每個網站都獨立一整組服務。

建議調整:

1. 不要使用 latest

例如:

NGINX_VERSION=nginx:latest

建議改成固定版本:

NGINX_VERSION=nginx:1.29.3-alpine

不然之後重新 build 或 pull 時,可能會拉到不同版本,問題會比較不好追。

2. PHP 7.4 要列為舊系統

如果還有網站需要 PHP 7.4,可以先保留,但要知道這已經是 legacy 環境。

建議做法:

  1. 先列出哪些網站還需要 PHP 7.4
  2. 確認不能升級的原因
  3. 有計畫地改到 PHP 8.2 / 8.3 / 8.4

3. MySQL 不一定要開給外部

如果只有 container 裡的網站會連 MySQL,通常不需要:

ports:
  - 3306:3306

比較保守可以改成:

ports:
  - 127.0.0.1:3306:3306

或者乾脆不要 publish port,只讓 Docker network 內部連線。

4. volumes 和 bind mount 要整理

目前像這些目錄都會影響備份:

wwwroot/
data/mysql/data/
etc/nginx/
etc/caddy/
log/
session/

建議之後整理一份備份清單,才不會只備份 image,卻漏掉真正重要的網站資料。

結論:

這個架構是可行的,而且蠻適合維護多個 PHP 網站。

接下來優先處理的順序,我覺得可以是:

  1. 固定 image 版本
  2. 整理 Nginx / PHP-FPM 權限
  3. 收斂 MySQL 對外連線
  4. 補上 healthcheck
  5. 將 PHP 7.4 網站逐步升級
  6. 評估改用 Caddy 簡化 HTTPS

先把基礎整理好,之後要搬主機、重建服務或排除錯誤都會輕鬆很多。

VS Code 直接透過 sftp 同步到遠端伺服器

2024年9月29日 星期日

環境介紹:

  • 正式機:10.x.y.aaa,  Linux Ubuntu + Docker(Nginx + PHP + MySQL)
  • 開發機:10.x.y.bbb, Windows 11 + Laragon + VS Code

之前利用 VS Code 開發之前,總是利用 SSHFS-Win 掛載至本機後,再用比對軟體檢查哪些檔案有修改,是否需要上傳(註:freecommander 很好用)。

不過,有時只是一、二支程式需要上傳,總覺得這樣做的話有點繁瑣,最近發現有支 vscode 的 extension(擴充套件):sftp

可以 ssh 連到遠端伺服器,直接上傳,也支援多個網站,所以可以先上傳到測試伺服器,沒問題後再上傳到正式機。

安裝後要先設定遠端伺器資訊,按【F1】,選擇「SFTP: Config」,會在 .vscode 目錄中產生 sftp.json 設定範本檔,直接依個人需求填入設定值即可。


預設是只會有一組伺服器設定,若有多組伺服器的話,例如有測試機、正式機…等,那就要設定 profiles,把設定檔改成下列即可,連線時,按按【F1】,選擇「SFTP: Set Profile」,選擇要連線的伺服器即可。


這支擴充套件還提供其它蠻方便的功能,也可以去發掘喔!