# Podman Pod 設定 App Containers 啟動順序
## 理解 OCI hooks
`Container hooks` 是 `OCI` 規範中的一項功能,它允許使用者在 App Container 生命週期 (lifecycle) 的不同時間點,定義並執行特定的動作。
這些 `hook` 是指一些腳本 (`scripts`) 或指令 (`commands`),它們會在特定的事件發生時被執行,例如在 container 被建立 (`created`)、啟動 (`started`)、停止 (`stopped`) 或刪除 (`deleted`) 的前後。`Container hooks` 提供了一種擴充 container 行為的方法,可用於多種目的,包括設定 (`setup`)、配置 (`configuration`) 和清理 (`cleanup`) 等任務。
`OCI container hooks` 包含以下幾種類型:
* **`precreate`**:這個 `hook` 會在 container 的設定檔產生之後,但在 container 真正啟動之前執行。
* **`prestart`**:這個 `hook` 會在 container 的主要程序 (`primary process`) 啟動**前**執行。它可以用來執行像是設定環境變數、配置網路設定或準備 container 的檔案系統 (`filesystem`) 等任務。
* **`poststart`**:這個 `hook` 會在 container 的主要程序 (`primary process`) 啟動**後**執行。它可用於執行那些需要在 container 開始運行後才進行的任務,例如將 container 註冊到服務發現系統 (`service discovery system`) 或監控工具。
* **`poststop`**:這個 `hook` 會在 container 的主要程序 (`primary process`) 退出或 container 被停止**後**執行。它可用於清理任務 (`cleanup tasks`)、記錄日誌 (`logging`) 或任何在 container 停止運行後應採取的行動。
`Container hooks` 可以在 container 的 `OCI` 設定檔中定義,該設定檔通常儲存為一個 `JSON` 檔案。此設定檔會指明 `hook` 腳本或指令的路徑,以及任何需要的參數 (`arguments`) 或環境變數 (`environment variables`)。
## 快速動手實作
### Step1: 設定 Podman `hook` 定義檔的存放位置
執行以下命令,編輯 `/etc/containers/containers.conf` 檔案:
```
sudo nano /etc/containers/containers.conf
```
修改以下內容 :
```
# Path to OCI hooks directories for automatically executed hooks.
# 將以下三行取消註解
hooks_dir = [
"/usr/share/containers/oci/hooks.d",
]
```
### Step2: 以 Json 格式設定 Hook 定義檔
```
sudo mkdir -p /usr/share/containers/oci/hooks.d/
sudo nano /usr/share/containers/oci/hooks.d/prestart-delay.json
```
檔案內容如下 :
```json
{
"version": "1.0.0",
"hook": {
"path": "/usr/local/bin/delay.sh",
},
"when": {
"annotations": {
"^delay$": ".+"
}
},
"stages": ["prestart"]
}
```
### Step3: 編輯 Hook 定義檔中宣告的 shell script,並賦予執行權限
```
sudo nano /usr/local/bin/delay.sh
```
檔案內容如下 :
```
#!/bin/bash
set -x
# 將 stderr 重新導向到一個日誌檔案以便除錯,同時也輸出到原始的 stderr
exec 2> >(tee -a /var/log/oci-hooks.log >&2)
# 從標準輸入 (stdin) 讀取容器狀態 JSON
# OCI 執行階段會在 prestart 階段將此 JSON 傳遞給 Hook
input="-"
CONTAINER_CONFIG=$(cat "$input")
# 檢查 jq 是否安裝
if ! command -v jq &> /dev/null; then
echo "Error: jq is not installed. Cannot parse container state." >&2
exit 1
fi
# 使用 jq 從 JSON 中解析我們自訂的註解值。
# 註解鍵為 "delay"。
# -r 選項移除引號。
# // "0" 提供一個預設值,如果註解不存在或為 null,則回傳 "0"。
delay_seconds=$(echo "$CONTAINER_CONFIG" | jq -r '.annotations["delay"] // "0"')
# 輸入驗證:確保我們得到的是一個非負整數。
# 這可以防止錯誤或潛在的命令注入。
if ! [[ "$delay_seconds" =~ ^[0-9]+$ ]]; then
echo "Warning: Invalid value for annotation delay: '$delay_seconds'. Must be a non-negative integer. Defaulting to 0." >&2
delay_seconds=0
fi
# 如果延遲時間大於 0,則執行 sleep 並記錄日誌。
if [ "$delay_seconds" -gt 0 ]; then
echo "Dynamic delay hook triggered. Sleeping for $delay_seconds seconds..." >&2
sleep "$delay_seconds"
echo "Sleep complete. Container will now start." >&2
else
# 如果延遲為 0 或無效,也記錄一條資訊,以便追蹤 Hook 是否被觸發。
echo "Dynamic delay hook triggered, but delay is 0. Starting container immediately." >&2
fi
```
賦予程式執行權限 :
```
sudo chmod +x /usr/local/bin/delay.sh
```
### Step4: 執行與驗證
#### 1. 先用 app container 驗證
```
date; sudo podman run \
--rm \
-it \
--annotation delay=5 \
docker.io/library/alpine date
```
執行結果 :
```
Sat Jul 26 16:41:05 CST 2025
Sat Jul 26 08:41:10 UTC 2025
```
> 可以看到只差 5 秒
#### 2. 使用 podman pod 驗證
```
# 建立 pod
sudo podman pod create --name mypod
# 建立 container c1,指定 annotation
sudo podman create --name c1 --pod mypod \
--annotation delay=5 \
docker.io/library/alpine \
sh -c "date; sleep infinity"
# 建立 container c2,指定 annotation
sudo podman create --name c2 --pod mypod \
--annotation delay=10 \
docker.io/library/alpine \
sh -c "date; sleep infinity"
# 建立 container c3,指定 annotation
sudo podman create --name c3 --pod mypod \
--annotation delay=15 \
docker.io/library/alpine \
sh -c "date; sleep infinity"
# 啟動所有 container
sudo podman start c1 c2 c3
```
檢視 podman pod logs
```
$ sudo podman pod logs mypod
```
執行結果 :
```
9c922e6a8c15 Sat Jul 26 15:24:33 UTC 2025
caca4df50c11 Sat Jul 26 15:24:58 UTC 2025
5531bc974469 Sat Jul 26 15:24:43 UTC 2025
```