2025/05/26

Homestead SSL Issue

Laravel Homestead 在使用 nginx 創建網頁 config 時是有幫他配置 ssl key 的,但為什麼我們使用 https 還是會被警告呢,因為我們的瀏覽器是不認這個憑證的,在 Windows 上可以手動安裝該憑證讓你測試的時候可以透過 https 連到你的測試機,有時候開發會遇到跨網站連結 https 的狀況,如果你是 http 的話可能會被擋下來。

配置 Homestead.yml

folders:
  - map: ./ssl
    to: /ssl
  - map: ./code
    to: /home/vagrant/code

sites:
  - map: ssl.test
    to: /home/vagrant/code/
    php: "8.2"

這是我添加的測試網站,將根目錄開一個 ssl 目錄,在 code 目錄下放一個有 echo hello world 的 PHP 檔案,修改本機的 hosts 將 ssl.test 指到該 ip。

vm 起來後,vagrant ssh 進去該 vm,執行 sudo cp /etc/ssl/certs/ca.homestead.homestead.pem /ssl,將憑證複製到 /ssl 下,exit 退出 vm。

前往該 ssl 目錄,執行以下步驟:

  1. 右鍵 點擊憑證檔,選擇 「安裝憑證」
  2. 點選 「下一步」
  3. 選擇 「將所有憑證放入以下的存放區」
  4. 點選 「瀏覽」
  5. 選擇 「受信任的根憑證授權單位」
  6. 點選 「下一步」
  7. 點選 「完成」

此時瀏覽 https://ssl.test 應該就不會有警告了,如果還是有的話可能是 nginx cache,重開 server 或瀏覽器應該就解決了,可以先用無痕驗證一下。

該憑證過期時間為十年,應該是蠻夠用的,過期的話要重產一個新的,但我想應該在過期之前就會重置測試機了。

Homestad PHP PPA Issue

最近執行 Homesteadvagrant reload --provision 會遇到錯誤。

E: Repository 'https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy InRelease' changed its 'Label' value from '***** The main PPA for supported PHP versions with many PECL extensions *****' to 'PPA for PHP'

這是因為該 PPA 的 Label 產生了變化,解決方法也挺簡單。

vagrant ssh 進去 vm 後,執行下面指令:

sudo rm -rf /var/lib/apt/lists/*
sudo apt-get clean
sudo apt-get update --allow-releaseinfo-change

記得遇到這個問題處理過後要退出 vm 執行 vagrant reload --provision,才會有變動。

2025/05/13

Tmux Auto Split Window Syntax

tmux 是很強大的視窗分割工具,他可以透過語法建立初始化的視窗結構,今天的案例是我在我的 vagrant 虛機裡面有許多 laravel 專案,每個專案都有使用 horizon queue,每次要工作的時候我先 vagrant ssh 進去虛機以後,再手動割四個視窗,進入四個專案目錄後執行 php artisan horizon 啟動 queue server,這個過程可以透過語法讓 tmux 自己幫你完成。

horizon.sh

#!/bin/bash

# API 專案目錄
PROJECTS=(
    "/project1"
    "/project2"
    "/project3"
    "/project4"
)

# 取得目前 tmux Session 名稱
SESSION=$(tmux display-message -p '#S')

# 開啟新視窗並處理第一個 pane
FIRST_DIR="${PROJECTS[0]}"
tmux new-window -n "horizon" -c "$FIRST_DIR"
tmux select-window -t "${SESSION}:horizon"

# 設定第一個 pane 並執行命令
tmux select-pane -t 0
tmux send-keys -t 0 "php artisan horizon" C-m

# 處理其餘 panes
for i in "${!PROJECTS[@]}"; do
    if [ "$i" -eq 0 ]; then continue; fi

    DIR="${PROJECTS[$i]}"

    # 分割方向
    if [ "$i" -eq 1 ]; then
        tmux split-window -h -c "$DIR"
    else
        tmux select-pane -t $((i - 1))
        tmux split-window -v -c "$DIR"
    fi

    # 執行命令
    tmux select-pane -t "$i"
    tmux send-keys -t "$i" "php artisan horizon" C-m
done

# 整理 pane 排列
tmux select-layout tiled

該 script 需要在開啟了一個 tmux window 後執行。

2025/05/02

Log Handle In Docker

Docker 現在已經是可以當 production server 用的工具了,來聊一下他對 logs 有哪些配置方式。

根據 docker 官方文件,目前有以下幾個配置方法:

Driver 說明
none 容器沒有可用的日誌,且 docker logs 不會返回任何輸出。
local 日誌以一種為了最小化開銷而設計的自定義格式儲存。
json-file 日誌格式為 JSON。Docker 的預設日誌驅動程式。
syslog 將日誌消息寫入 syslog 設施。主機上必須運行 syslog 守護程序。
journald 將日誌消息寫入 journald。主機上必須運行 journald 守護程序。
gelf 將日誌消息寫入 Graylog 擴展日誌格式 (GELF) 端點,如 Graylog 或 Logstash。
fluentd 將日誌消息寫入 fluentd(轉發輸入)。主機上必須運行 fluentd 守護程序。
awslogs 將日誌消息寫入 Amazon CloudWatch Logs。
splunk 使用 HTTP 事件收集器將日誌消息寫入 Splunk。
etwlogs 將日誌消息寫入 Windows 事件追蹤 (ETW) 事件。僅適用於 Windows 平台。
gcplogs 將日誌消息寫入 Google Cloud Platform (GCP) Logging。

如果你能擁有自己的 log server,會建議把 log 打進 log api server,擁有權限的人就可以在 web tool 上做所有的查詢以及決定資料怎麼保留跟處理,畢竟如果 log 停留在各 server,以現在很多服務走 HA 或是 k8s 情況下,遇到狀況你到底要進哪台服務查詢都會成為問題。

不過現實生活中很多人都只有一台 server,而且也沒有 api log server 這種資源,只是要有 log 即可的話,我們一般會用 json-file 這個選項,這也是 docker 預設的 log driver。

docker-compose json-file 範例

services:
    nginx:
        image: nginx
        container_name: nginx
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
        logging:
            driver: json-file
            options:
                max-size: "10m"
                max-file: "3"

這個範例會把 nginx 的 log 儲存到 /var/lib/docker/containers/<container-id>/<container-id>-json.log,當 log 檔案大小超過 10MB 時,docker 會自動將舊的 log 檔案重新命名為 <container-id>-json.log.1,並開始寫入新的 log 檔案,最多會保留 3 個 log 檔案,當你使用指令 docker logs 時,docker 會自動讀取最新的 log 檔案,並顯示在終端機上。

一般 image 如 nginx,他會把 log 設定成 stdout,這樣 docker 才能夠讀取到 log,然後寫入到你設定的 log driver 裡面,選擇 json-file 時 docker 會自動幫你把 stdout 或 stderr 的 log 轉成 json 格式,然後寫入到你設定的 log driver 裡面。

nginx 的話,他的配置會是這樣:

access_log /dev/stdout;
error_log /dev/stderr;

這樣的缺點是,access log 會跟 error log 都寫在同一個檔案裡面,會造成你在查詢 log 的時候不方便,假設我們想要使用 docker,又想要產生跟傳統 web server 一樣的 log,我們可以修改 nginx.conf,讓他把 log 寫到 /var/log/nginx/access.log/var/log/nginx/error.log

Nginx Config 範例

nginx.conf

http {
    server {
        listen       80;
        server_name  localhost;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;
    }
}

然後把 log 目錄掛載出來。

docker-compose.yml

services:
    nginx:
        image: nginx
        container_name: nginx
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
            - ./logs:/var/log/nginx

我們在 /etc/logrotate.d/nginx 新增一個檔案,然後把以下的內容貼上去:

logrotate conf

/var/log/nginx/*.log {
    daily
    missingok
    rotate 7
    compress
    notifempty
    sharedscripts
    lastaction
        docker exec nginx nginx -s reopen
    endlaction
}

docker exec nginx nginx -s reopen 的用意是 logrotate 在 rotate 之後需要呼叫 docker 重新開啟 nginx 的 log,不這樣的話 nginx in docker 會使用你 rename 後的 log 檔案繼續寫 log。

當我們使用 compress,並且會針對壓縮過後的檔案做後續處理的話,一定要有 sharedscripts 以及使用 lastaction,如果使用 postrotate 的話,他不會等所有的檔案都執行完壓縮就直接作動了。

這邊有幾個 logrotate 的指令可以使用:

指令 說明
prerotate/endscript 在進行日誌輪轉之前執行的指令,每個匹配的日誌檔案都會執行一次
postrotate/endscript 在進行日誌輪轉之後執行的指令,每個匹配的日誌檔案都會執行一次
firstaction/endscript 在所有匹配日誌檔案進行輪轉之前執行的指令,只執行一次
lastaction/endscript 在所有匹配日誌檔案進行輪轉之後執行的指令,只執行一次
preremove/endscript 在舊的日誌檔案被移除之前執行的指令

另外有一種不用重新開啟檔案的配置參數 copytruncate,這個參數會在 logrotate 開始 rotate 的時候複製原本的 log 檔案,然後 truncate 原本的 log 檔案,這樣 nginx 就不需要重新開啟檔案了,但這樣有機會漏寫紀錄,所以不建議使用這個參數。

Apache2 Config 範例

apache2 的原始 log 配置會是:

ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 common

我們要修改成:

ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined

然後把 log 目錄掛載出來。

docker-compose.yml

services:
    apache2:
        image: httpd
        container_name: httpd
        ports:
            - "80:80"
        volumes:
            - ./httpd.conf:/usr/local/apache2/conf/httpd.conf
            - ./logs:/var/log/apache2

httpd.conf

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot /var/www/html

    ErrorLog /var/log/apache2/error.log
    CustomLog /var/log/apache2/access.log combined
</VirtualHost>

logrotate conf

/var/log/apache2/*.log {
    daily
    missingok
    rotate 7
    compress
    notifempty
    sharedscripts
    lastaction
        docker exec apache2 apachectl -k graceful
    endscript
}

這個範例跟 nginx 的範例一樣,只是把 nginx -s reopen 改成 apachectl -k graceful

假設你 logrotate 後要執行其他 bash 做其他處理的話。

/var/log/apache2/*.log {
    daily
    missingok
    rotate 7
    compress
    notifempty
    sharedscripts
    lastaction
        docker exec httpd apachectl -k graceful
        /usr/bin/bash /usr/local/bin/other_script.sh
    endscript
}