[DevOps] 手把手帶您輕鬆管理 Windows 服務 (Windows Service) — 從建立到授權完整指南

在 Windows 環境下運行應用程式時,將其註冊為系統服務能大幅提升管理效率。
這篇文章將介紹 NSSM(Non-Sucking Service Manager)這個強大工具,
手把手教你如何快速建立 Windows 服務。
除了介紹 NSSM 與基礎的服務建立步驟,我們還會帶你瞭解 Windows 服務背後的權限原理,
探討如何安全地授予一般權限使用者開啟或關閉指定服務的權限,讓團隊協作更靈活,DevOps 流程更順暢。無論你是初學者或經驗豐富的系統管理員,這篇文章都能幫助你提升 Windows 服務管理的效率,讓你對 Windows 服務有更近一步的瞭解。

TL;DR

使用 NSSM (Non-Sucking Service Manager) 註冊服務
nssm 為了以後設定方便,使用指令來操作

假設我有一個 Windows 服務,名叫 MyService 你可以這麼做
(執行以下指令需使用 cmd 系統管理員權限)

nssm install "MyService" "C:\Java\bin\java.exe" "-jar C:\MyService\app.jar"
nssm set MyService AppDirectory "C:\MyService\"
nssm set MyService Description "This is my service"

每行指令說明:

  • 使用 nssm 註冊服務,用雙引號把所需要的參數括弧包起來
  • 設定程式起始路徑
  • 設定服務的說明

然後是最關鍵的調整權限:
(使用系統管理員的 Powershell 來執行)

Adjust-ServicePermissions.ps1 -Username myuser -ServiceName MyService

程式片段在此:
https://gist.github.com/j796160836/72346b43a315055caeebb69d7c3db76f

用法很簡單,就二個參數:

  • Username 帶入指定的使用者(一般使用者)的帳號名稱
  • ServiceName 帶入指定的 Windows 服務名稱

會給你對應的提示,按下 y 開始執行套用。

這邊已經把程式用 Script 包裝好了,帶入所需的參數即可。

接下來,我們來細講這些東西


nssm 服務管理器介紹與使用

nssm 當初的取名很有趣:Non-Sucking Service Manager
因為原作者覺得Windows 內建的 Windows 服務註冊工具實在都太難用了,
太爛、太 Suck 了!所以原作者想寫一個不難用的 (Non-Sucking) 服務管理器,故得名。

大部分的一般應用程式,都可以用 nssm 來註冊 Windows 服務。
nssm 他有 GUI 圖形介面,但為了以後設定方便,甚至做成 init scripts,建議還是使用指令來操作,以下也都是介紹指令。

你可以用以下的操作:

nssm 註冊安裝服務

假設你要單獨執行的指令 (測試指令) 如下:

C:\Java\bin\java.exe -jar C:\MyService\app.jar

註冊服務

欲把上述指令註冊成 Windows 服務,名字叫做 MyService
(這邊要用 cmd 系統管理員執行)

nssm install MyService "C:\Java\bin\java.exe" "-jar C:\MyService\app.jar"

要用雙引號把所需要的參數括弧包起來

設定服務起始路徑 (Startup Path)

設定程式起始路徑
(這邊要用 cmd 系統管理員執行)

nssm set MyService AppDirectory "C:\MyService\"

設定服務說明

設定 MyService 服務的說明
(這邊要用 cmd 系統管理員執行)

nssm set MyService Description "This is my service"

這二個指令都很直覺,就不細講了

移除服務

如果不小心弄錯了,可以用這個指令解除註冊
(這邊要用 cmd 系統管理員執行)

nssm remove MyService

實測只限於使用 nssm 註冊的服務


授予一般使用者開關指定服務的權限(手動步驟)

通常來說,Windows 服務只能管理員帳號 (Administrators)
才能做開關,但這樣權限實在太大了

基於「最小化權限原則」,我們能不能讓一般使用者,針對特定服務授予開啟與關閉的權限呢?

答案是可以的!但實在有點複雜…
我們先列出手動操作的步驟。

Step 1. 列出使用者的 sid

在 cmd 執行該指令,取得 sid

wmic useraccount where name='Tony' get sid

(假設你建立的一般使用者叫做 Tony)

記錄一下過程

wmic useraccount where name='Tony' get sid

SID
S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07

你就會得到類似這樣的 SID 先記下來備用

Step 2. 列出預設權限

先記下預設權限,這很重要 (以下呈現的結果供參考,以你實際的為主)

在 cmd 執行該指令,列出指定服務的權限

sc sdshow myService

D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)

在 cmd 執行該指令,列出 Service Control Manager (SCM) 的權限

sc sdshow SCMANAGER

D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)

Step 3. 手工調整權限

主要觀念就是:
把預設的權限都留下,這些是系統管理員 (Administrator) 使用的。
我們手動再加上我們需要的權限

權限分為 D: 開頭的區域與 S: 開頭的區域,在拼組字串時要注意

我們把預設權限

D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)

D: 開頭的區域,加上這段

(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)

這邊的 S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07 就是剛剛查的 sid
記得要保留 S: 開頭的區域

變成這樣

D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

串上 sc sdset 指令,變成這樣
(這段需要系統管理員的命令提示字元 (cmd) 才能執行)

sc sdset myService D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

SCMANAGER 的部分也是如此

預設權限

D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)

D: 開頭的區域,加上這段

(A;;CCLCSWRPWPDTLOCRRC;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)

變成

D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)(A;;CCLCSWRPWPDTLOCRRC;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)

最後串上 sc sdset 指令,並執行
(這段需要系統管理員的命令提示字元 (cmd) 才能執行)

sc sdset SCMANAGER D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CCLCSWRPWPDTLOCRRC;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)

Step 4. 驗證

使用 cmd 指令執行開啟服務,關閉服務

net start MyService
net stop MyService

或使用 PowerShell:

Start-Service -Name MyService
Stop-Service -Name MyService

應該都要能夠執行

再來使用 Ansible 來測試

playbook.yml

- name: Windows Service testing
  hosts: jenkins
  gather_facts: no
  tasks:
    - name: Stop service
      ansible.windows.win_service:
        name: MyService
        state: stopped
    - name: Start service
      ansible.windows.win_service:
        name: MyService
        start_mode: delayed
        state: started

inventory

[jenkins]
192.168.1.3 ansible_user=MY_USERNAME ansible_password='MY_PASSWORD' ansible_connection=winrm ansible_winrm_transport=basic ansible_winrm_server_cert_validation=ignore ansible_port=5985

執行 ansible playbook

export ANSIBLE_HOST_KEY_CHECKING=False && ansible-playbook -v -i inventory playbook.yml

授予一般使用者開關指定服務的權限(程式步驟)

剛剛以上很複雜的步驟,我已經幫你包成 Adjust-ServicePermissions.ps1 程式了
(使用系統管理員的 Powershell 來執行)

Adjust-ServicePermissions.ps1 -Username myuser -ServiceName MyService

程式片段在此:
https://gist.github.com/j796160836/72346b43a315055caeebb69d7c3db76f

用法二個參數:

  • Username 帶入指定的使用者(一般使用者)的帳號名稱
  • ServiceName 帶入指定的 Windows 服務名稱

會給你對應的提示,按下 y 開始執行套用。

如果有成功的話,恭喜你!與自動化更近一步!

Troubleshooting

補充一下,若執行 Powershell 遇到權限問題

PS C:\Users\user\Downloads> .\Adjust-ServicePermissions.ps1
.\Adjust-ServicePermissions.ps1 : C:\Users\user\Downloads\Adjust-ServicePermissions.ps1 檔案無法載入。檔案 C:\Users
\user\Downloads\Adjust-ServicePermissions.ps1 未經數位簽署。您無法在目前的系統上執行此指令碼。如需有關執行指令碼及
設定執行原則的詳細資訊,請參閱 about_Execution_Policies (網址為 http://go.microsoft.com/fwlink/?LinkID=135170)。
位於 線路:1 字元:1
+ .\Adjust-ServicePermissions.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

那你就在 Powershell 設定這個
(使用系統管理員的 Powershell 來執行)

Set-ExecutionPolicy RemoteSigned

SDDL (安全性描述元定義語言) 學習

這邊我們就深入探討一下 SDDL (Security Descriptor Definition Language, 安全性描述元定義語言) 與剛剛講的這些東西,若沒有要深入調整也沒關係,瞭解原理總是好的。
安全描述符 (Security Descriptor) 定義了服務的權限,包括了誰可以存取服務以及可以執行哪些操作。

SDDL 結構

SDDL 字串由兩個主要部分組成:

  1. DACL (Discretionary Access Control List):以 D: 開頭,定義了對象的存取控制條目 (ACE)。
  2. SACL (System Access Control List):以 S: 開頭,定義了審核條目。

DACL 部分

D: 開頭的字串表示 DAC (Discretionary Access Control List),後面是一組 ACE (Access Control Entries, 對象的存取控制條目),每個 ACE 定義了誰擁有什麼權限。

ACE 結構

每個 ACE 的結構如下:

(A;;<Permissions>;;;<SID>)
  • A:表示這是一個允許 (Allow) 的 ACE。
  • <Permissions>:定義授予的權限。
  • <SID>:定義授權的安全主體 (Security Identifier)。

分析自行建立的服務 (myService) 的權限

這是剛剛 sc sdshow myService 所出現的字串

D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

分析 DACL (myService)

我們把 D: 開頭的部分拿出來

D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)
  (A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)
  (A;;CCLCSWLOCRRC;;;IU)
  (A;;CCLCSWLOCRRC;;;SU)
  (A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)
  1. (A;;CCLCSWRPWPDTLOCRRC;;;SY)

    • SY:表示 LocalSystem 帳戶。
    • CCLCSWRPWPDTLOCRRC:這是一組權限,分別代表:
      • CC:建立子項目(CREATE_CHILD)。
      • LC:列出子項目(LIST_CHILDREN)。
      • SW:寫入(SELF_WRITE)。
      • RP:讀取參數(READ_PROPERTY)。
      • WP:寫入參數(WRITE_PROPERTY)。
      • DT:刪除樹(DELETE_TREE)。
      • LO:列出項目(LIST_OBJECT)。
      • CR:控制存取(CONTROL_ACCESS)。
      • RC:讀取安全描述元(READ_CONTROL)。
    • 簡而言之:LocalSystem 帳戶擁有完全控制權限。
  2. (A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)

    • BA:表示 Administrators 群組。
    • CCDCLCSWRPWPDTLOCRSDRCWDWO:這是一組更高級的權限,包含:
      • CC:建立子項目(CREATE_CHILD)。
      • DC:刪除子項目(DELETE_CHILD)。
      • LC:列出子項目(LIST_CHILDREN)。
      • SW:寫入(SELF_WRITE)。
      • RP:讀取參數(READ_PROPERTY)。
      • WP:寫入參數(WRITE_PROPERTY)。
      • DT:刪除樹(DELETE_TREE)。
      • LO:列出項目(LIST_OBJECT)。
      • CR:控制存取(CONTROL_ACCESS)。
      • SD:刪除(STANDARD_DELETE)。
      • RC:讀取安全描述元(READ_CONTROL)。
      • WD:修改存取控制清單(WRITE_DAC)。
      • WO:修改擁有者(WRITE_OWNER)。
    • 簡而言之:Administrators 群組擁有完全控制權限。
  3. (A;;CCLCSWLOCRRC;;;IU)

    • IU:表示互動使用者 (Interactive Users)。
    • CCLCSWLOCRRC:這是一組有限的權限,允許讀取和列舉操作。
      • CC:建立子項目(CREATE_CHILD)。
      • LC:列出子項目(LIST_CHILDREN)。
      • SW:寫入(SELF_WRITE)。
      • LO:列出項目(LIST_OBJECT)。
      • CR:控制存取(CONTROL_ACCESS)。
      • RC:讀取安全描述元(READ_CONTROL)。
    • 簡而言之:互動使用者擁有基本的讀取和列舉權限。
  4. (A;;CCLCSWLOCRRC;;;SU)

    • SU:表示服務使用者 (Service Users)。
    • CCLCSWLOCRRC:同上,與互動使用者相同的權限。
    • 簡而言之:服務使用者擁有基本的讀取和列舉權限。
  5. (A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)

    • 這組是我們新增的權限
    • S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07:就是剛剛查詢的 sid。
    • CCDCLCSWRPWPDTLOCRSDRCWDWO:同 Administrators,就不贅述了。
    • 簡而言之:新增這個一般使用者,擁有完全控制權限,也是我們要達到的效果。

SACL 部分 (myService)

S: 表示 SACL (System Access Control List),定義了審核條目。

我們把 S: 開頭的部分拿出來

S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)

SACL 結構

(AU;FA;<Permissions>;;;<SID>)
  • AU:表示審核條目 (Audit Entry)。
  • FA:表示完全瀏覽 (Full Access)。
  • <Permissions>:定義需要審核的權限。
  • <SID>:定義審核的對象。

分析 SACL

SACL 就相對沒那麼重要,不過還是帶一下

S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)
  • AU:表示審核條目。
  • FA:表示完全瀏覽。
  • CCDCLCSWRPWPDTLOCRSDRCWDWO:定義了需要審核的權限,與 Administrators 的權限相同。
  • WD:表示 Everyone 群組。
  • 簡而言之:對 Everyone 群組的所有操作進行審核。

分析 SCMANAGER 的權限

接下來繼續看 sc sdshow SCMANAGER 命令顯示的安全描述符定義語言 (SDDL) 字串。

SCMANAGER 是服務控制管理器 (Service Control Manager)

D:(A;;CC;;;AU)(A;;CCLCRPRC;;;IU)(A;;CCLCRPRC;;;SU)(A;;CCLCRPWPRC;;;SY)(A;;KA;;;BA)(A;;CC;;;AC)S:(AU;FA;KA;;;
WD)(AU;OIIOFA;GA;;;WD)

讓我們逐段分析這些字串。

DACL 部分 (SCMANAGER)

DACL 包含了多個瀏覽控制項 (ACE),每個項目用括號包圍:

D:(A;;CC;;;AU)
  (A;;CCLCRPRC;;;IU)
  (A;;CCLCRPRC;;;SU)
  (A;;CCLCRPWPRC;;;SY)
  (A;;KA;;;BA)
  (A;;CC;;;AC)
  (A;;CCLCSWRPWPRC;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)
  1. (A;;CC;;;AU)

    • A:允許 (Allow)
    • CC:建立子項目(CREATE_CHILD)權限
    • AU:已驗證的用戶 (Authenticated Users)
  2. (A;;CCLCRPRC;;;IU)

    • A:允許 (Allow)
    • CCLCRPRC:多個權限的組合:
      • CC:建立子項目(CREATE_CHILD)。
      • LC:列出子項目(LIST_CHILDREN)。
      • RP:讀取參數(READ_PROPERTY)。
      • RC:讀取安全描述元(READ_CONTROL)。
    • IU:互動式用戶 (Interactive Users)
  3. (A;;CCLCRPRC;;;SU)

    • A:允許 (Allow)
    • CCLCRPRC:同上
    • 適用於 SU:服務用戶 (Service Users)
  4. (A;;CCLCRPWPRC;;;SY)

    • A:允許 (Allow)
    • CCLCRPWPRC 同上並加上
      • WP:寫入參數(WRITE_PROPERTY)。
    • SY:系統 (System)
  5. (A;;KA;;;BA)

    • A:允許 (Allow)
    • KA:所有權限 (KEY_ALL_ACCESS)。
    • BA:內建管理員 (Built-in Administrators)
  6. (A;;CC;;;AC)

    • A:允許 (Allow)
    • CC:建立子項目(CREATE_CHILD)。
    • AC:所有應用程序包 (All Application Packages)
  7. (A;;CCLCSWRPWPRC;;;S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07)

    • 這組是我們新增的權限
    • A:允許 (Allow)
    • CCLCSWRPWPRC:這組權限為:
      • CC:建立子項目(CREATE_CHILD)。
      • LC:列出子項目(LIST_CHILDREN)。
      • SW:寫入(SELF_WRITE)。
      • RP:讀取參數(READ_PROPERTY)。
      • WP:寫入參數(WRITE_PROPERTY)。
      • RC:讀取安全描述元(READ_CONTROL)。
    • S-1-5-xx-xxxxxxxx-xxxxxxxxx-xxxxxxxxxx-xx07:就是剛剛查詢的 sid。

SACL 部分解析 (SCMANAGER)

S:(AU;FA;KA;;;WD)
  (AU;OIIOFA;GA;;;WD)

SACL 部分定義了審計規則:

  1. (AU;FA;KA;;;WD)

    • AU:審計 (Audit)
    • FA:AUDIT_FAILURE(失敗審計)
    • KA:KEY_ALL_ACCESS (所有權限)
    • WD:所有人 (Everyone)
  2. (AU;OIIOFA;GA;;;WD)

    • AU:審計
    • OIIOFA
      • OI:物件繼承 (OBJECT_INHERIT)。
      • IO:僅繼承(INHERIT_ONLY)。
      • FA:失敗審計 (AUDIT_FAILURE)。
    • GA:一般通用存取 (GENERIC_ALL)。
    • WD:所有人 (Everyone)

說明

這個安全描述符表明:

  • 管理員擁有完全控制權
  • 系統有廣泛的讀取、寫入和控制權限
  • 已認證用戶、交互式用戶和服務用戶有有限的瀏覽權限
  • 所有應用程式包可以建立子項目
  • 任何人嘗試完全瀏覽並失敗時會被記錄審計訊息

總結

這段 SDDL 字串的含義是:

  1. DACL
    • LocalSystem 帳戶擁有完全控制權限。
    • Administrators 群組擁有完全控制權限。
    • 互動使用者和服務使用者擁有基本的讀取和列舉權限。
  2. SACL
    • 對 Everyone 群組的所有操作進行審核。

這些設定用於控制服務的安全性,確保只有授權的使用者或群組可以執行特定操作,並記錄未經授權的瀏覽嘗試。

權限設定小結

myService 服務

  • 關鍵權限:
    • LocalSystem 和 Administrators:完全控制
    • 互動使用者和服務使用者:僅讀取和列舉
    • 特定使用者 (指定SID):完全控制
      -審核:追蹤所有使用者的操作嘗試

SCMANAGER 服務

  • 權限分層:
    • 管理員和系統:高級存取權限
    • 已驗證/互動/服務使用者:有限權限
    • 所有應用程式包:僅建立子項目
    • 特定使用者:自定義權限集
  • 審核:記錄失敗操作,含繼承規則

這種分層設計確保服務安全性,將完全控制權限限制在管理員和系統中,同時能為特定使用者提供自定義權限。

參考資料

後記:因為 SDDL 部份的參考資料有一點少,
有部分概念部分使用 AI 輔助,如有錯誤,還煩請不吝指正。

[教學] x64 PC 工控主機裸機直裝 OpenWRT (Bare Metal Install OpenWRT)

緣由

一切的本來是可以不用這麼複雜的。

之前入手了一台 4 Port Ethernet 的工業電腦,
本來安裝 Proxmox VE (這過程很曲折,可以另外寫一篇)然後做網卡直通 (NIC Passthrough),

但苦惱目前這顆 CPU: Intel Celeron J1900
支援 Intel® Virtualization Technology (VT-x),可以做虛擬化(可以開虛擬機 VM)
但不支援 Intel® Virtualization Technology for Directed I/O (VT-d)

對 Intel Celeron J1900 有興趣的話可以看 這裡

file

但這個在網卡直通是必要項,故心一橫,直接裸機 (Bare metal) 安裝 OpenWRT 吧!

這中間也遇到有的沒的坑,就寫了一篇文章來分享。

備註:Directed I/O (VT-d) 是一種虛擬化技術,讓虛擬機 (VM) 直接控制實體網卡,
繞過 Hypervisor 層以實現極致的網路性能、低延遲和低 CPU 佔用率。

那我們就開始吧!


🖥️ 前置準備

需準備的硬體

  • CPU:x86_64 的 CPU (Intel 或 AMD 皆可)
  • RAM:建議 ≥ 1 GB
  • Disk:建議 ≥ 8 GB SSD/eMMC (屆時會 整顆格式化掉 要注意)
  • Ethernet:64-bit 映像檔支援 Intel 與 Realtek Ethernet 晶片組(工業電腦常見的 Intel i210/i225/i350 均有良好支援)

範例使用的硬體

  • CPU: Intel Celeron J1900 (x86_64)
  • RAM: 4GB
  • HDD: 64GB SSD
  • Ethernet: Intel(R) PRO/1000 Network Connection x 4


(圖片來源)

工具準備

  • 一台 Windows/Linux/macOS 電腦,開啟 SMB (網路上的芳鄰) 分享
  • 一支做好 Ubuntu Live USB 隨身碟(≥ 2 GB)

Step 0: 製作 Ubuntu Live USB

file
(圖片來源)

  1. 下載 Ubuntu ISO 檔案

前往 Ubuntu 官網
下載所需版本的 ISO 檔案,建議下載 LTS (Long-Term Support) 版本,
選擇 Intel or AMD 64-bit architecture 的版本

file

  1. 使用 Balena Etcher 製作 Live USB

下載 Balena Etcher
安裝並開啟 Balena Etcher
點擊「Flash from file」選擇下載的 Ubuntu ISO
點擊「Select Target」選擇 USB 隨身碟

插入至少 8GB 大小的 USB 隨身碟

點擊「Flash」開始寫入(需要輸入系統密碼)
等待完成即可

file

Step 1: 全機備份

如果原本硬碟有資料,可以做磁碟備份,
以免到時候反悔要處理有得處理,
如果是全新安裝可以跳過這段

備份的步驟

使用 Ubuntu Live CD (Live USB) 開機

掛載 SMB (網路上的芳鄰的共享資料夾)

sudo apt update -y
# 安裝 SMB/CIFS 協議
sudo apt install -y cifs-utils

# 新增一個 smb_share 資料夾(名稱可以自己取)
sudo mkdir /mnt/smb_share
# 掛載你的 SMB 目錄
sudo mount -t cifs -o username=YOUR-USERNAME //192.168.1.10/ /mnt/smb_share

確認磁碟資訊

bashlsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,LABEL
# 或
fdisk -l

使用 dd 全磁碟複製(最完整,含 MBR/GPT/EFI)

# 備份
sudo dd if=/dev/sda of=/mnt/smb_share/disk_backup.img bs=4M status=progress conv=fsync

ddrescue(更安全、有錯誤處理)

# 安裝
sudo apt-get install gddrescue

# 備份到映像檔(含 log 檔方便中斷續傳)
sudo ddrescue -d -r3 /dev/sda /mnt/smb_share/disk_backup.img /mnt/smb_share/disk_backup.log

參數說明:

  • -d:直接讀取(bypass cache)
  • -r3:遇到壞軌重試 3 次
  • log 檔讓你可以中斷後繼續

備註: dd 會備份整顆磁碟(包含空白空間),映像檔大小 = 磁碟總容量
要準備足夠的空間

還原的步驟

還原方式

使用 dd 還原

# 從映像檔還原到磁碟
sudo dd if=/mnt/smb_share/disk_backup.img of=/dev/sda bs=4M status=progress conv=fsync

使用 ddrescue 還原

# ddrescue 還原
sudo ddrescue -d /mnt/smb_share/disk_backup.img /dev/sda /mnt/smb_share/disk_backup.log

Step 2:下載正確的 OpenWRT 映像檔

前往官方下載頁面:https://downloads.openwrt.org/releases/

選擇最新穩定版(目前為 25.12.x),路徑為:

releases → [版本號] → targets → x86 → 64

file

UEFI 模式必須下載帶 -efi 的檔案:

檔名 說明
generic-ext4-combined-efi.img.gz 推薦,可擴展分區
generic-squashfs-combined-efi.img.gz 唯讀 rootfs,類似嵌入式路由器

UEFI 系統必須使用 64-bit EFI OpenWRT 映像,大多數新板子需要 UEFI,Legacy BIOS 支援已較罕見。

這次下載的檔案檔名是

openwrt-25.12.2-x86-64-generic-ext4-combined-efi.img.gz

僅供參考

驗證映像完整性 (Optional)

下載完成後,在終端機執行 sha256 驗證(與官網 sha256sums 核對):

# Linux / macOS
sha256sum openwrt-25.12.2-x86-64-generic-ext4-combined-efi.img

# Windows (PowerShell)
Get-FileHash openwrt-25.12.2-x86-64-generic-ext4-combined-efi.img -Algorithm SHA256

Step 3:確認並設定 BIOS

進入 BIOS 設定(通常按 Delete / F2 / F12):

這邊只是列個大概,每家 BIOS 設定選項有些許不同

  • 關閉 Secure Boot(Security → Secure Boot → Disabled)
  • 確認開機模式為 UEFI(Boot → Boot Mode → UEFI only)
  • 調整開機順序:將 USB 設為第一優先
  • 儲存並重啟(Save & Exit)

若 OpenWRT 無法載入,請確認已停用 Secure Boot,並確認 USB 裝置已設為優先開機裝置。

Step 4:將 OpenWRT 映像寫入磁碟

使用 Live CD (Live USB) 開機

確認磁碟資訊

bashlsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,LABEL
# 或
fdisk -l

找出你的 SSD(通常是 /dev/sda,NVMe 則為 /dev/nvme0n1
先確定是哪顆磁碟,這邊假設是 /dev/sda

先解壓縮

gunzip openwrt-25.12.2-x86-64-generic-ext4-combined-efi.img.gz

會得到 openwrt-25.12.2-x86-64-generic-ext4-combined-efi.img 檔案

然後使用 dd 全磁碟複製 開始安裝
(你沒看錯,就是用 dd 指令)

sudo dd if=/mnt/smb_share/openwrt-25.12.2-x86-64-generic-ext4-combined-efi.img of=/dev/sda bs=4M status=progress conv=fsync
sync

範例輸出:

126123520 bytes copied, 3.25 s, 38.8 MB/s

⚠️ /dev/sdX 請替換為你的 SSD,切勿寫錯裝置 (寫錯會悲劇)
這邊做的是直接「整顆」硬碟做複製,故 /dev/sda (範例值) 後面不需要帶數字

Step 5:擴展 OpenWRT 根目錄分區(強烈建議,一定要做)

OpenWRT 預設 img 映像檔只有約 100~270 MB 的 root 分區 (Partition),
以我的例子來說,我 SSD 有 64GB,但預設只有 29.5M 可以使用
需擴展以便利用完整磁碟

我們再次使用 Live CD (Live USB) 開機

然後使用 parted 指令來查看磁碟區

parted /dev/sda

然後打

print

中間會叫你 Fix 有問題的磁區,你就 Fix
這邊會自動修正 GPT 問題

這邊紀錄一下執行結果

root@OpenWrt:~# parted /dev/sda
GNU Parted 3.6
Using /dev/sda
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) p
Model: ATA Kston 64GB (scsi)
Disk /dev/sda: 64.0GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system  Name  Flags
128     17.4kB  262kB   245kB                      bios_grub
 1      262kB   17.0MB  16.8MB  fat16              legacy_boot
 2      17.0MB  64.0GB  64.0GB  ext4

然後擴展磁碟分區

# 擴展分區(以 /dev/sda2 為例,為 ext4 rootfs)
parted /dev/sda resizepart 2 100%

做完可能要再 重開機,然後再次進入 LiveCD (LiveUSB)

先做檔案系統檢查(必要步驟)

e2fsck -f /dev/sda2

刷新分區表

partprobe /dev/sda

擴展 ext4 檔案系統到分區最大

resize2fs /dev/sda2

可以使用 df 再次查看

df -h

記錄一下執行結果

# df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/root                58.8G    372.6M     58.5G   1% /
tmpfs                     1.9G      1.8M      1.9G   0% /tmp
/dev/sda1                16.0M      6.2M      9.8M  39% /boot
/dev/sda1                16.0M      6.2M      9.8M  39% /boot
tmpfs                   512.0K         0    512.0K   0% /dev

你就會發現 ext4 磁區的可用空間變大了,
如果沒有做 resize2fs 擴展磁區,分區已經撐滿了,但磁區仍然在很小的狀態


Step 6:移除 USB,從 SSD 開機

  1. 重啟機器,移除 USB 隨身碟
  2. UEFI 應自動偵測到 EFI 分區並從 SSD 開機
  3. 開機後看到 OpenWrt 的 GRUB 選單及登入提示即成功

Step 7:初始網路設定(4 Port 主機設定的關鍵步驟)

這邊會看到一堆開機 Log 文字,熟悉的 Linux 登入字樣
OpenWRT 預設會幫你設定

  • eth0LAN(br-lan,IP: 192.168.1.1)
  • eth1WAN(DHCP client)

帳號為 admin 預設沒有密碼,
ssh 有開啟,預設綁到 eth0,
很有可能沒有 LuCI (Web UI) 介面

接下來我們一步一步著手設定你的 OpenWRT

識別網路介面


(圖片來源)

可以用以下步驟來確認網路介面

ip link show
# 或
ls /sys/class/net/

紀錄執行結果

# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br-lan state UP qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br-lan state DOWN qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br-lan state DOWN qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
5: eth3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state DOWN qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
6: br-lan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff

你會看到類似 eth0 eth1 eth2 eth3(或 enp* 名稱)

確認哪個 port 對應哪條實體網路線:

# 逐一測試,觀察 Link 狀態
ip link set eth0 up
cat /sys/class/net/eth0/carrier   # 1 = 有連線, 0 = 無連線

OpenWRT x86 預設:

  • eth0LAN(br-lan,IP: 192.168.1.1)
  • eth1WAN(DHCP client)

預設就有設定好 NAT 連線。

以我的例子為例,

  • LAN1 port 是 eth0
  • LAN2 port 是 eth1
  • LAN3 port 是 eth2
  • WAN port 是 eth3

就依序設定好對應的 WAN 與 LAN,可能跟你的情況不同。
這邊你用 Web UI 設定也可以。

這邊還是提供指令版的修改網路設定(1 WAN + 3 LAN)

編輯 /etc/config/network

vi /etc/config/network

內容為

config interface 'loopback'
    option device 'lo'
    option proto 'static'
    option ipaddr '127.0.0.1'
    option netmask '255.0.0.0'

config interface 'lan'
    option type 'bridge'
    option proto 'static'
    option ipaddr '192.168.1.1'
    option netmask '255.255.255.0'
    option ip6assign '60'
    list device 'eth0'
    list device 'eth1'
    list device 'eth2'   # 將 eth1, eth2 也加入 LAN bridge

config interface 'wan'
    option device 'eth3'   # 指定第4個 port 為 WAN
    option proto 'dhcp'    # 或 pppoe

在 OpenWRT 中,你需要明確指定哪個實體 NIC 網卡用於 WAN 或 LAN,
x86 硬體與一般路由器不同,端口角色必須手動設定。

套用設定

/etc/init.d/network restart

Step 8:安裝 LuCI (Web UI) 圖形介面

如果是穩定版 (stable) 預設有安裝 LuCI,
如果你裝的是 SNAPSHOT 版本的話,要手動安裝

因為 OpenWRT 在一個版本之後,改了套件管理程式
我就新舊版指令都列出來

新版指令 (使用 apk 套件管理程式)

apk update
apk add luci

舊版指令 (使用 opkg 套件管理程式)

opkg update
opkg install luci

然後啟動 httpd 伺服器

/etc/init.d/uhttpd enable
/etc/init.d/uhttpd start

之後從 LAN 側的電腦瀏覽器開啟 http://192.168.1.1 即可使用圖形介面。


Step 9:後續基本安全設定

這邊就依照你的需求,安裝所需的軟體
這邊跟一般 Linux 操作類似,就不多敘述了

# 設定 root 密碼
passwd

# 安裝常用工具
opkg update
opkg install curl wget-ssl btop irqbalance

Step 10:(加碼)安裝 Docker

這台範例機器 CPU / RAM / Disk 應該綽綽有餘,
聰明的你,可能會想到要來裝 Docker,沒問題,安排!

新版指令 (使用 apk 套件管理程式)

# 套件更新
apk update
# 安裝 docker
apk add dockerd docker docker-compose luci-app-dockerman

舊版指令 (使用 opkg 套件管理程式)

# 套件更新
opkg update 

# 安裝 docker 套件
opkg install dockerd docker docker-compose luci-app-dockerman

記得啟動服務

/etc/init.d/dockerd enable
/etc/init.d/dockerd start

以上指令就是安裝 Docker 相關套件,說明如下:

  • dockerd:執行容器所需的主要 Docker 守護程式/引擎。
  • docker:用來與守護程式互動的命令列介面 (CLI)。
  • docker-compose:用於定義和執行多容器應用程式的工具。
  • luci-app-dockerman:一個受歡迎的網頁式介面(LuCI 應用),可直接從 OpenWRT 瀏覽器儀表板管理 Docker 容器。


常見問題排除

問題 解決方法
UEFI 開機失敗 確認下載的是 -efi 版本;關閉 Secure Boot
只看到 UEFI Shell 進 BIOS 手動加入開機項:EFI\boot\bootx64.efi
網路 port 認不到 確認晶片是 Intel / Realtek;可能需安裝 kmod-* 驅動
磁碟空間不足 擴展 rootfs 分區
WAN 無法上網 確認 eth 編號對應正確,用 ip linkcarrier 確認

完成以上步驟後,你的工業電腦就會是一台以 UEFI 模式運行的 OpenWrt x86_64 路由器,4 個網路口可以靈活設定為任意的 WAN/LAN 組合。

參考資料

OpenWrt on x86 hardware (PC / VM / server)
https://openwrt.org/docs/guide-user/installation/openwrt_x86openwrt 教學

[教學] 用 OpenWRT 做「真」 Wi-Fi 漫遊 (Wi-Fi Roaming)

這個功能屬於偏進階功能,OpenWRT 有一定的專業性,
如果實在不知道怎麼做,請不要貿然嘗試

得利於 OpenWRT 第三方韌體,
可以在家用設備上有做出商用等級才有的功能,
由衷的讚嘆開源的偉大

什麼是 WiFi 漫遊 (Wi-Fi Roaming)?

問題痛點

你可能會遇到類似的情境,
當你有二顆以上的 Wi-Fi AP 路由器,來覆蓋不同的區域,
但你只要一組 SSID 就好(先討論 2.4GHz 與 5GHz 分開的情況)

你可能會先下意識的把主要路由器與子機路由器,設定了同一組 Wi-Fi SSID 與密碼。
主要 Wi-Fi AP 路由器(下稱主機)連上 ADSL,
其他顆 Wi-Fi AP 路由器(下稱子機)插上 LAN 網路線,關閉 DHCP Server,二台路由器變成一個內網。

但,這樣雖然可以用,但不完美。

假設裝置連接上第一顆 Wi-Fi,
如果裝置移動了,
離開第一顆 Wi-Fi AP 的範圍,進入第二顆 AP 的範圍,
裝置會斷線 AP 再重連接入第二顆 AP 路由器,
雖然接上也是同一個網路,但這會造成有點惱人的小問題。

這個時候你就需要 802.11r802.11k/v
Wi-Fi Roaming 技術來「無縫」在不同 Wi-Fi AP 間切換。

802.11r(快速漫遊)

802.11r 又稱為「快速 BSS 轉換」(Fast BSS Transition, FT),主要解決無線裝置在不同存取點(AP)間切換時的延遲問題。傳統漫遊需重新進行完整的 802.1X 認證,可能耗時數百毫秒,導致語音或視訊通話中斷。802.11r 透過預先快取金鑰並簡化握手程序,將漫遊時間縮短至 50 毫秒以下,確保即時應用不受影響。此標準對企業環境中需要無縫移動的 VoIP 電話、視訊會議設備特別重要。

802.11k/v(智慧漫遊輔助)

802.11k(無線資源管理)讓 AP 向用戶端提供鄰近 AP 的資訊清單,使裝置能更快找到最佳切換目標,減少掃描所有頻道的時間與電力消耗。

802.11v(無線網路管理)則允許網路主動建議用戶端切換至負載較輕或訊號更佳的 AP,實現負載平衡並優化整體網路效能。

兩者常搭配 802.11r 共同運作,形成完整的智慧漫遊解決方案,提升大型無線網路的使用體驗。

配置

假設你的 Wi-Fi 路由器配置是這樣

  • 主要 Wi-Fi AP 路由器主機 (192.168.1.1): WAN 孔連接著 ADSL 網路,DHCP Server 開啟
  • 第二顆 Wi-Fi AP 路由器子機 (Dumb AP) (192.168.1.2): 主要延伸 主機 Wi-Fi 的訊號與範圍,
    DHCP Server 關閉(DHCP 純靠主要 AP 路由器)

操作步驟

第一步:更換驅動套件 (一定要做)

這邊我推薦用 ssh 操作,不然在更換套件可能會遇到斷線問題比較麻煩

  • 注意:移除時 WiFi 會斷線,建議使用電腦接網路線操作,或者操作完後重啟路由器。

ssh 的做法

OpenWrt 預設安裝的 wpad-basicwpad-mini
不支援 完整的 802.11r/k/v 功能或 LuCI 選項。
必須先更換套件,換成全功能的版本。

請在「主機」與「子機」上都執行:

電腦分別用 ssh 連接路由器

ssh [email protected]
ssh [email protected]

更新套件清單

opkg update 

查詢套件

opkg list-installed | grep wpad

記錄一下細節

# opkg list-installed | grep wpad
wpad-basic-mbedtls - 2025.08.26~ca266cc2-r1

我們主要目標是要找到 wpad-basicwpad-basic-wolfsslwpad-mini
我這邊是找到 wpad-basic-wolfssl 這個套件,
那我們用 wpad-wolfssl 套件取代它,
二個套件會衝突,不可同時安裝,所以在 ssh 指令列裡面做事比較方便。

我們移除 wpad-basic-mbedtls 套件,改安裝 wpad-wolfssl 套件

opkg remove wpad-basic-mbedtls && opkg install wpad-wolfssl

記錄一下細節

# opkg remove wpad-basic-mbedtls && opkg install wpad-wolfssl
Removing package wpad-basic-mbedtls from root...
Installing wpad-wolfssl (2024.09.15~5ace39b0-r2) to root...
Downloading https://downloads.openwrt.org/releases/24.10.4/packages/mipsel_24kc/base/wpad-wolfssl_2024.09.15~5ace39b0-r2_mipsel_24kc.ipk
Installing libwolfssl5.7.6.e624513f (5.7.6-r1) to root...
Downloading https://downloads.openwrt.org/releases/24.10.4/packages/mipsel_24kc/base/libwolfssl5.7.6.e624513f_5.7.6-r1_mipsel_24kc.ipk
Configuring libwolfssl5.7.6.e624513f.
Configuring wpad-wolfssl.

再查詢一次,就是我們需要的套件了

# opkg list-installed | grep wpad
wpad-wolfssl - 2024.09.15~5ace39b0-r2

備註:新版 24.10 之後與 SNAPSHOTS 版本,
opkg 套件管理器改成用 apk 套件管理器了

查詢套件

apk info | grep wpad

記錄一下細節

# apk info | grep wpad
WARNING: opening from cache https://downloads.openwrt.org/snapshots/targets/mediatek/filogic/packages/packages.adb: No such file or directory
WARNING: opening from cache https://downloads.openwrt.org/snapshots/packages/aarch64_cortex-a53/base/packages.adb: No such file or directory
WARNING: opening from cache https://downloads.openwrt.org/snapshots/targets/mediatek/filogic/kmods/6.12.67-1-aa4948ece684816486d1fa5040ce0bb3/packages.adb: No such file or directory
WARNING: opening from cache https://downloads.openwrt.org/snapshots/packages/aarch64_cortex-a53/luci/packages.adb: No such file or directory
WARNING: opening from cache https://downloads.openwrt.org/snapshots/packages/aarch64_cortex-a53/packages/packages.adb: No such file or directory
WARNING: opening from cache https://downloads.openwrt.org/snapshots/packages/aarch64_cortex-a53/routing/packages.adb: No such file or directory
WARNING: opening from cache https://downloads.openwrt.org/snapshots/packages/aarch64_cortex-a53/telephony/packages.adb: No such file or directory
WARNING: opening from cache https://downloads.openwrt.org/snapshots/packages/aarch64_cortex-a53/video/packages.adb: No such file or directory
wpad-basic-mbedtls

(這些 WARNING 可以先不用理會,關注在搜尋結果即可)

移除wpad-basic-mbedtls 套件,改安裝 wpad-wolfssl 套件

apk del wpad-basic-mbedtls && apk add wpad-wolfssl

記錄一下細節

# apk del wpad-basic-mbedtls && apk add wpad-basic-wolfssl
(1/1) Purging wpad-basic-mbedtls (2025.08.26~ca266cc2-r1)
  Executing wpad-basic-mbedtls-2025.08.26~ca266cc2-r1.pre-deinstall
OK: 22.4 MiB in 159 packages
(1/2) Installing libwolfssl5.8.4.e624513f (5.8.4-r1)
  Executing libwolfssl5.8.4.e624513f-5.8.4-r1.post-install
(2/2) Installing wpad-wolfssl (2025.08.26~ca266cc2-r1)
  Executing wpad-wolfssl-2025.08.26~ca266cc2-r1.post-install
OK: 25.3 MiB in 161 packages

安裝完成後,記得要 重啟路由器
不然新的選單不會出現。

LuCI 網頁管理做法

  1. 登入 LuCI,進入 System (系統) -> Software (軟體)
  2. 點擊 Update lists (更新清單)
  3. 在 Filter 搜尋 wpad
  4. 移除 wpad-basicwpad-basic-wolfsslwpad-mini (看你原本裝哪個)。
    • 注意:移除時 WiFi 會斷線,建議使用電腦接網路線操作,或者操作完後重啟路由器。
  5. 安裝 wpad (通常是指向 wpad-wolfsslwpad-openssl 的完整版)。
    • 推薦安裝 wpad-openssl (或 wpad-wolfssl)。
  6. 安裝完成後,記得要 重啟路由器
    不然新的選單不會出現。

第二步:設定 802.11r (Fast Transition)

現在進入 LuCI 設定漫遊參數。請在「主機」和「子機」的 2.4GHz5GHz 介面都要做一樣的設定。

  1. 進入 Network (網路) -> Wireless (無線)
  2. 對你的 SSID 點擊 Edit (編輯)
  3. 切換到 Wireless Security (無線安全) 分頁。
  4. 確認 Encryption (加密模式)WPA2-PSK (相容性最好) 或 WPA2/WPA3 Mixed(這邊要每台都一致)。

WLAN roaming 頁籤裡

設定以下參數:

  • 勾選 802.11r Fast Transition (啟用快速切換)。
    (Enables fast roaming among access points that belong to the same Mobility Domain)

  • Mobility Domain (行動網域): 輸入一個 4 位數的十六進位碼,例如 4f57
    (4-character hexadecimal ID)
    關鍵點: 主機和子機必須設定 完全一樣 的代碼。

  • Reassociation Deadline: 保持預設值 (通常是 1000) 即可。
    (time units (TUs / 1.024 ms) [1000-65535])

  • FT Protocol (FT 協議): 選擇 FT over the Air (相容性較佳)。

  • 勾選 Generate PMK locally (本地生成 PMK)
    (When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.)
    說明:勾選後路由器會自動處理金鑰,不需要手動輸入繁瑣的 R0KH/R1KH 列表。

點擊 Save (儲存)


第三步:啟用 802.11k 與 802.11v (輔助漫遊)

這兩個標準能幫助手機「提早」知道何時該切換,以及該切換到哪裡,避免手機死抓著遠處的訊號不放。

file

在同一個 在 WLAN roaming 頁籤裡 頁面中:
(需安裝完整版 wpad 才會出現這些選項)

802.11k (RRM) 的部分

  • 勾選 802.11k RRM
    (Radio Resource Measurement – Sends beacons to assist roaming. Not all clients support this.)
  • 勾選 Neighbour Report (預設會勾)
    (802.11k: Enable neighbor report via radio measurements.)
  • 勾選 Beacon Report (預設會勾)
    (802.11k: Enable beacon report via radio measurements.)

802.11v (WNM/BSS Transition) 的部分

  • (選填)勾選 WNM Sleep Mode (省電功能)。
    (802.11v: Wireless Network Management (WNM) Sleep Mode (extended sleep mode for stations).)

  • 勾選 BSS Transition
    (802.11v: Basic Service Set (BSS) transition management.)

最後按 Save & Apply (儲存並套用)


第四步:檢查頻道與功率 (最佳化)

為了避免干擾並讓漫遊更順暢:

  1. 頻道 (Channel):

    • 主機與子機的 SSID、密碼、加密方式、Mobility Domain 都要一樣
    • 但是 頻道 (Channel) 建議錯開
    • 例如:主機 2.4G 設 Ch 1,子機 2.4G 設 Ch 6。主機 5G 設 Ch 36,子機 5G 設 Ch 44。
    • 原因:若頻道相同,重疊區域的訊號會互相干擾,反而影響漫遊體驗。
  2. 發射功率 (Transmit Power):

    • 不要盲目調到最大 (Max)。
    • 適度降低功率(例如設為 20dBm 或 auto),讓兩個 AP 的訊號覆蓋範圍有適當的重疊(約 15-20%),但不要重疊太多,這樣手機才會願意切換。

總結檢查清單

設定項目 主機 (Master) 子機 (Slave) 備註
軟體套件 wpad-wolfssl wpad-wolfssl 必須移除 basic 版,改安裝 full 版
SSID MyWiFi (範例) MyWiFi (範例) 必須相同
密碼 xxxxxxxx xxxxxxxx 必須相同
802.11r 啟用 啟用
Mobility Domain 4f57 (範例) 4f57 (範例) 必須相同
FT Protocol FT over the Air FT over the Air
頻道 (Channel) 1 (範例) 6 (範例) 建議不同

設定完成後,當你在兩個 AP 之間移動時,手機應該會顯示 WiFi 訊號滿格但不會斷線重連(不會出現 4G/5G 圖示),這就代表漫遊成功了。

若不換套件,只有 wpad-basic 套件能做到什麼程度?

簡單來說:只能做到「同名 WiFi」,無法做到「漫遊」。

如果你只保留預設的 wpad-basic,因為它閹割了 802.11r/k/v 協議的支援,你的網路狀況會變成這樣:

  1. 黏滯效應 (Sticky Client): 手機連著客廳的主機,當你走到臥室(子機旁)時,即使客廳訊號只剩一格、臥室訊號滿格,手機依然會死抓著客廳的訊號不放,直到完全斷線才會切換。
  2. 切換會斷線: 當手機終於決定切換時,因為沒有 802.11r 的快速握手協議,手機必須重新執行完整的 4 次握手驗證。這會導致 3~5 秒的網路中斷(Line 通話會斷掉、遊戲會掉線、影片會轉圈圈)。
  3. 若不換 wpad 完整版,你設定相同的 SSID 意義不大,體驗會很差。強烈建議一定要更換。

Troubleshooting:如果路由器無法上網更新套件

如果你的 AP 路由器的症狀是「下面連接的電腦能上網(因為封包透過 Switch 直接轉發給主機),但路由器自己不能上網」。
這是因為你的 缺了「閘道器 (Gateway)」和「DNS」的設定

因為你是用 LAN 接 LAN,OpenWrt 預設認為 LAN 是內部網路,通常不會去設定 Gateway。
你必須手動告訴 AP 路由器:「如果要下載軟體,請找主機幫忙。」

假設你的 主機 (Main Router) 的 LAN IP 是 192.168.1.1

修改子機的 LAN 設定

  1. 登入子機的 LuCI。

  2. 進入 Network (網路) -> Interfaces (介面)

  3. 找到 LAN,點擊 Edit (編輯)

  4. General Settings (一般設定) 頁面中,確認以下設定:

    • IPv4 address (IPv4 位址): 應該已經設為固定 IP (例如 192.168.1.2),這是正確的,不要動它
    • IPv4 gateway (IPv4 閘道器): 這裡目前應該是空的,請填入主機 IP (例如 192.168.1.1)。
    • IPv4 broadcast (IPv4 廣播): 留空即可。
    • Use custom DNS servers (使用自訂 DNS 伺服器): 這裡也很重要。請填入主機 IP (192.168.1.1) 或者 Google DNS (8.8.8.8)。
    • 如果不填 DNS,路由器雖然連得到網路,但無法解析 downloads.openwrt.org 的網址,一樣無法安裝軟體。
  5. (選用檢查) 往下滑到 DHCP Server,確認它是勾選 Ignore interface (忽略介面/停用)

  6. 點擊 Save (儲存)

套用並測試

  1. 點擊右上角的 Save & Apply (儲存並套用)
    • 注意:如果你的子機 IP 和電腦不在同網段,改完可能會連不上管理頁面,請確保電腦 IP 是自動取得或手動設為同網段。
  2. 進入 Network -> Diagnostics (診斷)
  3. 點擊 Ping (預設是 openwrt.org)。
  4. 如果有看到類似 64 bytes from ... 的回應,代表子機已經可以自己上網了!

Troubleshooting:如果路由器跳出 SSL verify error

如果你的 AP 路由器跳出這個錯誤:

SSL verify error: unknown error

這個錯誤在 OpenWrt 的子機(AP 模式)上非常常見。

這通常不是網路不通,而是因為路由器的 「系統時間」跑掉了

原因說明

OpenWrt 路由器通常沒有內建電池,斷電重啟後時間會重置(例如回到 2018 年或 1970 年)。
當你執行 opkg update 時,系統會檢查軟體源伺服器的 SSL 安全憑證。但憑證都有「有效期限」,
如果你的路由器認為現在是 1970 年,它會判定伺服器的憑證「尚未生效」,因此報錯並拒絕連線。

解決方式:透過 LuCI 同步瀏覽器時間

這是最快讓時間恢復正常的方法,不需要打指令。

  1. 登入子機的 LuCI 介面。
  2. 進入 System (系統) -> System (系統)
  3. 找到 System Properties (系統屬性) 區塊。
  4. 你會看到 Local Time (本地時間) 可能顯示一個舊的日期。
  5. 點擊旁邊的 Sync with browser (與瀏覽器同步) 按鈕。
  6. 確認時間變正確後,點擊 Save & Apply
  7. 再次嘗試執行 opkg update,應該就正常了。

解決方式:修正 NTP 設定

為了避免下次重開機又發生一樣的問題,你需要讓子機知道去哪裡自動對時。

  1. 進入 System (系統) -> System (系統) -> Time Synchronization (時間同步)
  2. 勾選 Enable NTP client (啟用 NTP 客戶端)
  3. NTP server candidates (NTP 伺服器候選):
    • 因為 DNS 可能還沒完全穩,建議加入你的 主機 IP (例如 192.168.1.1) 作為第一順位 (前提是主機有開 NTP 服務,OpenWrt 主機預設都有開)。
    • 或者加入 Google 的 NTP IP:216.239.35.0

參考資料

https://openwrt.org/docs/guide-user/network/wifi/roaming

[教學] TOTOLINK X6000R 路由器刷機 OpenWRT 完整指南

之前有寫過一篇 TOTOLINK X5000R 的刷機介紹,
X5000R 刷機幾乎沒有難度可言,原廠網頁管理介面的韌體更新直接上傳 OpenWRT 就可以了。

本來以為一切會跟之前一樣很順利的,
結果還是大意了。
把曲折的過程記錄一下,
這種久久做一次的東西容易忘記。

還是要提醒:

刷機有風險,而且有一定的專業性,而且會有破壞保固的可能,
本文不承擔任何操作的風險。


硬體規格與晶片組資訊

TOTOLINK X6000R 採用 MediaTek 的 Filogic 820 平台,
這是 Wi-Fi 6 AX3000 級別路由器設計的整合型晶片方案。
MT7981B 使用雙核心 ARM Cortex-A53 處理器,運行頻率 1.3GHz
採用 12nm 製程,整合了乙太網路和 WiFi 功能。

組件 規格
SoC/CPU MediaTek MT7981B (Filogic 820),雙核 ARM Cortex-A53 @ 1.3GHz
記憶體 256MB DDR3
快閃記憶體 16MB SPI NOR (EON EN25QX128A)
2.4GHz WiFi MT7981BA + MT7976CN,802.11ax,2×2 MIMO,574Mbps
5GHz WiFi MT7981BA + MT7976CN,802.11ax,2×3 MIMO,2402Mbps
乙太網路交換器 MediaTek MT7531AE,5 埠 Gigabit
網路埠 1 WAN + 4 LAN (全 Gigabit)
電源 12V DC,1A

WiFi 功能方面,X6000R 支援 160MHz 頻寬1024-QAM 調變、MU-MIMO、OFDMA、Beamforming 和 TWT 節能技術。四根外接天線(1 雙頻 + 1 2.4GHz + 2 5GHz)提供 TOTOLINK 所謂的「Xtra Range Technology」延伸覆蓋範圍。


OpenWRT 官方支援狀態

支援狀態:已正式支援

TOTOLINK X6000R 的 OpenWRT 支援已於 2025 年 9 月 21 日 透過 Pull Request #20035 合併到主線程式碼庫。

https://github.com/openwrt/openwrt/pull/20035

https://openwrt.org/toh/hwdata/totolink/totolink_x6000r
https://openwrt.org/toh/totolink/x6000r

記錄一下原出廠的韌體版本號: V9.4.0cu.1429 (2025-4-08 10:11:18)
這個版本可能在原廠韌體下載網頁被拿掉了,但這個版本可以進入救援模式,
其他版本不確定


原廠網頁介面升級(無法使用)

這是最簡單的刷機方式,適用於路由器正常運作且能進入原廠管理介面的情況。
以前可以(這也是我為何這麼推薦的原因),但這步已經被封掉了。

使用 TOTOLINK 網頁救援模式

事前準備:

  • 下載 OpenWRT sysupgrade 映像檔(安裝用)或 OEM 原廠韌體(還原用)
  • 一條乙太網路線

詳細步驟:

  1. 從 OpenWRT 韌體下載 *-sysupgrade.bin 映像檔

撰寫文的時候的正式版是 24.10.5

https://downloads.openwrt.org/releases/24.10.5/targets/mediatek/filogic/openwrt-24.10.5-mediatek-filogic-totolink_x6000r-squashfs-sysupgrade.bin

檔名為 openwrt-24.10.5-mediatek-filogic-totolink_x6000r-squashfs-sysupgrade.bin

這個版本在 TOTOLINK X6000R 實測 Wi-Fi 不正常,等待新版本修復。

請改用 SNAPSHOT 版本

https://downloads.openwrt.org/snapshots//targets/mediatek/filogic/openwrt-mediatek-filogic-totolink_x6000r-squashfs-sysupgrade.bin

檔名為 openwrt-mediatek-filogic-totolink_x6000r-squashfs-sysupgrade.bin

注意是要下載 -sysupgrade.bin 而不是另外一個

  1. 設定電腦網路

    • IP 位址:192.168.1.10(或任意 192.168.1.2-254)
    • 子網路遮罩:255.255.255.0
    • 閘道:192.168.1.1
  2. 單接網路線到該路由器任意 LAN 孔

  3. 持續的 ping 查看路由器存活

Windows 可以用 命令提示字元 (cmd) 打上 ping

ping -t 192.168.1.1

Mac/Linux 的 Terminal

ping 192.168.1.1
  1. 進入恢復模式 (Recovery Mode)

    • 設定電腦 IP: 192.168.1.100
    • 子網路遮罩 NetMask: 255.255.255.0

打開 ping 持續 ping 192.168.1.1

Windows 使用 ping -t,Mac/Linux 使用 ping

ping -t 192.168.1.1
ping 192.168.1.1

步驟如下:

  1. 拔除電源(斷電),
  2. 用迴紋針按住 Reset 鍵不放並插電,
  3. 看到 LED 藍紅閃爍後,整個 LAN 綠色 LED 燈亮起(大約 6 秒後)
  4. 釋放 RESET 按鈕

如果成功的話,會進入恢復模式 (Recovery Mode)
ping 原本不通的,變成會通

  1. 瀏覽網頁 http://192.168.1.1
    只有一個醜醜的網頁介面(甚至是簡體字的…但毋須在意,之後就刷機刷掉了)

  1. 上傳 OpenWRT sysupgrade 映像檔

  1. 等待約 5 分鐘左右,路由器將自動重啟

  1. 重啟後以 http://192.168.1.1 存取 OpenWRT(使用者為 root,無密碼)

使用 SNAPSHOT 韌體注意事項

SNAPSHOT 韌體他預設預覽版是沒有安裝 luci 網頁管理介面的,
(只開了一個 ssh port 而已)
所以瀏覽 http://192.168.1.1完全沒有反應
我們用以下步驟來安裝 luci 網頁管理介面,來修正這件事情

記得 WAN 插入現有網路,讓路由器可以上網。

使用 ssh 登入

ssh [email protected]

預設沒有密碼,會直接登入

記錄一下

BusyBox v1.37.0 (2026-01-02 17:07:02 UTC) built-in shell (ash)

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 OpenWrt SNAPSHOT, r32804-ac8b5526bc
 -----------------------------------------------------

 OpenWrt recently switched to the "apk" package manager!

 OPKG Command           APK Equivalent      Description
 ------------------------------------------------------------------
 opkg install <pkg>     apk add <pkg>       Install a package
 opkg remove <pkg>      apk del <pkg>       Remove a package
 opkg upgrade           apk upgrade         Upgrade all packages
 opkg files <pkg>       apk info -L <pkg>   List package contents
 opkg list-installed    apk info            List installed packages
 opkg update            apk update          Update package lists
 opkg search <pkg>      apk search <pkg>    Search for packages
 ------------------------------------------------------------------

For more information visit:
https://openwrt.org/docs/guide-user/additional-software/opkg-to-apk-cheatsheet

可以看到我使用了 SNAPSHOT, r32804-ac8b5526bc 版本的韌體。
他有提示到,這個版本已經從 opkg 套件管理程式,改成 apk 指令了

我們用以下指令安裝 luci 網路管理介面

apk update && \
apk add luci

如果是之前 opkg 版本的話,可以這樣

opkg update && \
opkg install luci

意思是相同的。

裝完就能使用瀏覽器瀏覽 http://192.168.1.1 存取 OpenWRT(使用者為 root,無密碼)摟!

參考資料

當 Kubernetes (K8s) 遇到 GPU 詳細裝機筆記 – Redhat 篇

會寫這一個主題,大概就是這步驟實在又多又複雜,
細節很多、然後軟體更新很快,步驟文章很容易失效

想必很有可能這篇沒多久就失效了,
至少可以給初學者一個概念,
就算其中步驟有變化,但一定只會變得更方便、更直覺

這種從頭開始、包含 GPU 的安裝,應該這個經驗不會太多人有。

不囉唆,我們開始吧

架構圖

關閉 Nouveau 驅動

在安裝 GPU 之前,需關閉 Nouveau 驅動,不然會安裝失敗

新增一個 /etc/modprobe.d/blacklist-nouveau.conf 檔案
(這檔案預設系統沒有,需要自行創立)

sudo vi /etc/modprobe.d/blacklist-nouveau.conf

內容為

blacklist nouveau
options nouveau modeset=0

:wq 存檔

然後輸入以下指令,讓核心 Kernel 以重新載入 initramfs

sudo dracut --force

查看 Nouveau 狀態

lsmod | grep -i nouveau

安裝 NVIDIA 驅動

這邊使用 Redhat 系列做範例, Rocky Linux, Fedora 是同家族的,
理論上都可以使用

安裝 kernel-devel 套件

sudo yum install -y gcc kernel-devel-$(uname -r)

到 NVIDIA 官網來下載驅動程式

https://www.nvidia.com/en-us/drivers/

打入你型號的 GPU 卡,就可以搜尋到 Linux 版本的驅動程式
筆者當時寫文的時候,拿到的檔名是 NVIDIA-Linux-x86_64-550.142.run

我們就執行該程式

chmod +x NVIDIA-Linux-x86_64-550.142.run
./NVIDIA-Linux-x86_64-550.142.run

他會跑一連串互動式安裝

  1. Nouveau 停用選項
Nouveau can usually be disabled by adding files to the modprobe configuration
  directories and rebuilding the initramfs.

  Would you like nvidia-installer to attempt to create these modprobe configuration
  files for you?

Nouveau 通常可以透過在 modprobe 設定目錄中新增檔案並重建 initramfs 來停用。
您希望 NVIDIA 安裝程式嘗試為您建立這些 modprobe 設定檔嗎?

這邊選擇 YES

  1. Nouveau 停用檔已建立
 One or more modprobe configuration files to disable Nouveau have been written.
  You will need to reboot your system and possibly rebuild the initramfs before
  these changes can take effect.  Note if you later wish to reenable Nouveau, you
  will need to delete these files:
  /usr/lib/modprobe.d/nvidia-installer-disable-nouveau.conf,
  /etc/modprobe.d/nvidia-installer-disable-nouveau.conf

NVIDIA 安裝程式已建立一個或多個 modprobe 設定檔來停用 Nouveau。
您需要重新啟動系統,並可能需要重建 initramfs,這些變更才會生效。
請注意,如果您之後希望重新啟用 Nouveau,需要刪除以下檔案:

  • /usr/lib/modprobe.d/nvidia-installer-disable-nouveau.conf
  • /etc/modprobe.d/nvidia-installer-disable-nouveau.conf

OK 繼續

  1. 警告:NVIDIA 安裝程式無法確定 X 函式庫路徑
WARNING: nvidia-installer was forced to guess the X library path '/usr/lib64' and X module path '/usr/lib64/xorg/modules'; these paths were not queryable from the system.  If X fails to find the NVIDIA X driver module,
please install the `pkg-config` utility and the X.Org SDK/development package for your distribution and reinstall the driver.

警告: NVIDIA 安裝程式猜測 X 函式庫路徑為 /usr/lib64 且 X 模組路徑為 /usr/lib64/xorg/modules;這些路徑無法從系統中查詢到。
如果 X 無法找到 NVIDIA X 驅動程式模組,請安裝 pkg-config 工具以及適用於您發行版的 X.Org SDK/開發套件,然後重新安裝驅動程式。

這個警告可以忽略

OK 繼續

  1. 安裝 NVIDIA 32 位元相容性函式庫?
Install NVIDIA's 32-bit compatibility libraries?

您要安裝 NVIDIA 的 32 位元相容性函式庫嗎?

這邊選擇 NO

  1. 警告:未偵測到 Vulkan ICD 載入器
WARNING: This NVIDIA driver package includes Vulkan components, but no Vulkan ICD loader was detected on this system. The NVIDIA Vulkan ICD will not function without the loader. Most distributions package the Vulkan loader;
try installing the "vulkan-loader", "vulkan-icd-loader", or "libvulkan1" package.

這個 NVIDIA 驅動程式套件雖然包含了 Vulkan 元件,但系統並未偵測到 Vulkan ICD 載入器。如果沒有這個載入器,NVIDIA Vulkan ICD 將無法正常運作。

這邊選擇 OK

  1. 自動執行 nvidia-xconfig 更新 X 設定檔
Would you like to run the nvidia-xconfig utility to automatically update your X configuration file so that the NVIDIA X driver will be used when you restart X?  Any pre-existing X configuration file will be backed up.

您希望執行 nvidia-xconfig 工具來自動更新您的 X 設定檔嗎?這樣,當您重新啟動 X 時,就會使用 NVIDIA X 驅動程式。任何現有的 X 設定檔都會被備份。

這邊選擇 YES

  1. 完成
Installation of the NVIDIA Accelerated Graphics Driver for Linux-x86_64 (version: 550.142) is now complete.  Please update your xorg.conf file as appropriate; see the file /usr/share/doc/NVIDIA_GLX-1.0/README.txt for
details.

NVIDIA Linux-x86_64 加速顯示驅動程式(版本:550.142)已經安裝完成。
請您根據需求更新 xorg.conf 檔案;詳細資訊請參考 /usr/share/doc/NVIDIA_GLX-1.0/README.txt 這個檔案。

最後按 OK 完成安裝

顯示目前使用的顯示器

lshw -c video

鎖定 Kernel 核心 (Optional)

因為 NVIDIA 驅動程式跟 Linux Kernel (核心,內核) 有強相關,
為避免 Linux Kernel 不小心被更新而導致 NVIDIA 驅動程式壞掉
導致一直要反覆安裝 NVIDIA 驅動程式修復環境問題

可以用 yum versionlock 鎖定 Kernel 版本,讓它不被自動更新
若無安裝 yum versionlock 可以用這指令安裝

sudo yum install python3-dnf-plugin-versionlock

yum versionlock 鎖定 Kernel 版本

sudo yum versionlock kernel kernel-devel kernel-core kernel-modules kernel-modules-core kernel-headers kernel-tools kernel-tools-libs

安裝 nvidia-container-toolkit (nvidia-ctk)

註:nvidia-docker 已經 Deprecated 了,它已經用 nvidia-container-toolkit 取代了
有些舊文就不要參考了

nvidia-container-toolkit 文件
https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html

加入 yum repo 路徑

curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo | \
sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo

安裝 nvidia-container-toolkit

sudo yum install -y nvidia-container-toolkit

裝完會得到 nvidia-ctk 指令

使用 nvidia-ctk 指令來設定 docker

sudo nvidia-ctk runtime configure --runtime=docker

它會直接修改 /etc/docker/daemon.json 檔案,加上 NVIDIA Container Runtime 支援

重開 Docker daemon

sudo systemctl restart docker

使用 nvidia-ctk 指令來設定 containerd (用於 K8s)

sudo nvidia-ctk runtime configure --runtime=containerd

重開 containerd daemon

sudo systemctl restart containerd

執行測試程式

這邊範例會開一個 ubuntu image 然後把 gpu 掛進去容器 (所有的 GPU)
並在容器裡面嘗試呼叫 nvidia-smi 指令

sudo docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi

成功的話會看到 GPU 顯卡的資訊

安裝 CUDA Toolkit

文件
https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=RHEL&target_version=9&target_type=rpm_local

CUDA Toolkit Installer
Installation Instructions:

wget https://developer.download.nvidia.com/compute/cuda/12.6.2/local_installers/cuda-repo-rhel9-12-6-local-12.6.2_560.35.03-1.x86_64.rpm
sudo rpm -i cuda-repo-rhel9-12-6-local-12.6.2_560.35.03-1.x86_64.rpm
sudo dnf clean all
sudo dnf -y install cuda-toolkit-12-6

到目前為止,docker 就已經可以取用 GPU 了

docker 測試

測試一:docker 跑一個測試容器

這邊「隨意的」跑一個一個容器,甚至沒有 nvidia 相關指令都沒關係
像是最原始標準乾淨的 alpine image

docker run --rm --runtime=nvidia --gpus all alpine:3.22.0 nvidia-smi

這邊範例直接跑一個 nvidia-smi 指令,
這很明顯標準 alpine image 是沒有這個指令的

重點在於這二個參數 --runtime=nvidia --gpus all
說明如下:

  • --runtime=nvidia 把 NVIDIA 相關基礎驅動程式放進去容器
    包含 nvidia-smi 等相關指令
  • --gpus all 是使用全部的

若正常執行的話,會得到 GPU 卡的資料
若無法正常執行,可能依序排查:

主機上是否可以執行 nvidia-smi

若不行,請檢查 Kernel 與 NVIDIA 驅動程式是否有正確安裝

註:NVIDIA 驅動程式 跟 Linux Kernel (核心) 有強關聯,安裝時要注意。
建議要鎖定 Kernel 版本,避免 Kernel 不小心被更新,然後 NVIDIA 驅動程式壞掉,
導致一直要反覆安裝 NVIDIA 驅動程式修復環境問題
方法詳見上方 [鎖定 Kernel 核心]

若主機上可以執行 nvidia-smi,但 container 不能執行 nvidia-smi
理應是 nvidia-container-toolkit 的問題

測試二:docker 跑 vectoradd 測試容器

如果覺得這個測試太無聊,可以跑一個 VectorAdd image
他會用 GPU 反覆的開始跑向量加總,真的讓 GPU 有負載,
你可以藉由此來確定 GPU 是否運作正常

sudo docker run --rm --runtime=nvidia --gpus all nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda11.6.0-ubi8

執行紀錄

# sudo docker run --rm --runtime=nvidia --gpus all nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda11.6.0-ubi8
[Vector addition of 50000 elements]
Copy input data from the host memory to the CUDA device
CUDA kernel launch with 196 blocks of 256 threads
Copy output data from the CUDA device to the host memory
Test PASSED
Done

安裝 Kubernetes (K8s)

這段的步驟就跟標準 Kubernetes (K8s) 很接近,
完整可參考這裡
這邊快速節錄

關閉 Swap

sed 指令找尋 swap 片段,並加上註解

sudo sed -i '/ swap /s/^/#/g' /etc/fstab

暫時關閉 Swap

sudo swapoff -a

使用 grubby 指令確認開機參數是否還有 Swap

sudo grubby --info DEFAULT

可能會得到類似的結果(這邊以 RockyLinux 9.5 為例)

index=0
kernel="/boot/vmlinuz-5.14.0-503.14.1.el9_5.x86_64"
args="ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rl_rk8--ctrl-swap 
rd.lvm.lv=rl_rk8-ctrl/root rd.lvm.lv=rl_rk8-ctrl/swap"
root="/dev/mapper/rl_rk8--ctrl-root"
initrd="/boot/initramfs-5.14.0-503.14.1.el9_5.x86_64.img"
title="Rocky Linux (5.14.0-503.14.1.el9_5.x86_64) 9.5 (Blue Onyx)"
id="11732e333bc94575b1636210b0a72f03-5.14.0-503.14.1.el9_5.x86_64"

這邊看到 resume=/dev/mapper/rl_rk8--ctrl-swaprd.lvm.lv=rl_rk8-ctrl/swap 就是殘留的 swap 參數,
(Swap 磁區名稱有可能跟我的不同,請依照實際情況調整)

一樣使用 grubby 指令移除

sudo grubby --update-kernel=ALL --remove-args="resume=/dev/mapper/rl_rk8--ctrl-swap rd.lvm.lv=rl_rk8-ctrl/swap"

雖然要移除前後這二個 swap 指令,但 
rd.lvm.lv=rl_rk8-ctrl/root 這個參數是要保留的,
如果誤刪除會「無法開機」要注意。

安裝 kubeletkubeadmkubectl 三兄弟

安裝文件:
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

小弟整理的安裝指令

sudo setenforce 0 && \
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config && \
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF && \
sudo yum install -y yum-plugin-versionlock && \
sudo yum install -y kubelet-1.28.2 kubeadm-1.34.2 kubectl-1.34.2 --disableexcludes=kubernetes && \
sudo yum versionlock kubectl kubeadm kubelet && \
sudo systemctl enable --now kubelet

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

<每台都做> 手動編譯安裝 Container Runtime Interface (CRI) – cri-dockerd

這步驟不分角色,三台都要裝

https://kubernetes.io/docs/setup/production-environment/container-runtimes/

我們用 Docker Engine 推薦的 cri-dockerd

說明文件:
https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/migrate-dockershim-dockerd/#what-is-cri-dockerd

從官網手動安裝 Golang

若是 RHEL 9.4 (RockyLinux 9.4) 一樣沒有對應的 rpm 可以裝
然後新版 cri-dockerd 又要求新版 Golang(1.22.0 以上)才能編譯
但 RHEL 9.4 的 golang 套件沒這麼新,才到 go1.21.13 而已,但官網最新版是 1.23.2
所以我們需要岔題一下手動安裝 Golang

到 Golang 的官網下載最新版本的 Golang 例如 1.23.2

wget https://go.dev/dl/go1.23.2.linux-amd64.tar.gz

解壓縮 go1.23.2.linux-amd64.tar.gz 檔案,會得到 go 資料夾,把他搬到對應位置

tar zxvf go1.23.2.linux-amd64.tar.gz
sudo mv go /usr/lib/golang

然後建立捷徑

sudo ln -s /usr/lib/golang/bin/go /usr/bin/go

使用 go version 來確認版本

go version

執行紀錄

$ go version
go version go1.23.2 linux/amd64

手動編譯安裝 cri-dockerd

若是 RHEL 9.4 (RockyLinux 9.4) 沒有對應的 rpm 可以裝
所以用手動編譯的方式進行

以下是官方文件提供的步驟
https://github.com/mirantis/cri-dockerd#build-and-install

先安裝必要套件

sudo yum install -y make go

如果 yum 給的 golang 版本不夠新,需要手動安裝 golang,步驟在上方

git clone 最新的版本

git clone https://github.com/Mirantis/cri-dockerd.git

編譯它 (compile)

cd cri-dockerd && \
make cri-dockerd

安裝

cd cri-dockerd && \
mkdir -p /usr/local/bin && \
install -o root -g root -m 0755 cri-dockerd /usr/local/bin/cri-dockerd && \
install packaging/systemd/* /etc/systemd/system && \
sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service

然後請 systemctl 重新載入 daemon
最後啟動服務

sudo systemctl daemon-reload && \
sudo systemctl enable --now cri-docker

裝完就會有 unix:///var/run/cri-dockerd.sock

複製虛擬機 (VM)

這邊步驟就是將單純的將 虛擬機 (VM) 複製二份成三台,並全部啟動。
以下分別闡述複製完要做的事情

重新產生 Machine-id

用以下指令重新產生 Machine-id

sudo rm /etc/machine-id && \
sudo systemd-machine-id-setup

修改 Hostname (主機名稱)

sudo vi /etc/hostname

分別改成對應的主機名稱

重新設定 ssh,產生全新的 known-host

sudo rm -f /etc/ssh/ssh_host_* && sudo ssh-keygen -A

(這個部分的指令跟 Ubuntu 不一樣)

<每台都做> 設定主機對應

叢集的三台機器做出來,還不知道彼此,
這邊用 /etc/hosts 檔案來讓主機們各自找到彼此

sudo vi /etc/hosts

根據每台主機的 IP 位址與主機名稱

192.168.1.100   k8s-ctrl
192.168.1.101   k8s-node1
192.168.1.102   k8s-node2

IP 位址在前,主機名稱在後,用 tab 分隔。

先整理好內容,再各自寫在每一台上面,每一台主機都會看到同一份資料。

<每台都做> 設定網路雜項值

根據文件:
https://kubernetes.io/docs/setup/production-environment/container-runtimes/#forwarding-ipv4-and-letting-iptables-see-bridged-traffic

這邊設定網路連線轉發 IPv4 位址並讓 iptables 查看橋接器的流量

用文件提供的指令操作,等等一句一句解釋:

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

請 Kubernetes (K8s) 引用載入 br_netfilteroverlay 二個核心模組

sudo modprobe overlay && \
sudo modprobe br_netfilter

啟用 br_netfilteroverlay 二個核心模組

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

設定轉發 IPv4 位址,讓 iptables 查看橋接器的流量

sudo sysctl --system

再不起重新啟動電腦情況下,套用設定值

設定 Control plane node(控制平台)

利用 kubeadm init 指令來初始化,並代入這些參數:

sudo kubeadm init \
    --kubernetes-version 1.34.2 \
    --control-plane-endpoint=192.168.1.100 \
    --apiserver-advertise-address=192.168.1.100 \
    --node-name k8s-ctrl \
    --apiserver-bind-port=6443 \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket unix:///var/run/cri-dockerd.sock

如果沒意外的話,完成之後會看到

Your Kubernetes control-plane has initialized successfully!

然後依照步驟,
若是 root 使用者,

.bash_profile 或者 .zsh_profile 設定環境變數

export KUBECONFIG=/etc/kubernetes/admin.conf

若是一般使用者,請依照指令依序設定

mkdir -p $HOME/.kube && \
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && \
sudo chown $(id -u):$(id -g) $HOME/.kube/config

註:加入 token 是有期限的,如果隔太久沒有整個步驟做完,
或者忘記了、被洗掉了,可以用指令重新生成加入指令

kubeadm token create --print-join-command

\<Control plane 做> 安裝 Helm 套件管理程式

安裝文件
https://helm.sh/docs/intro/install/

從執行檔直接複製

wget https://get.helm.sh/helm-v3.13.1-linux-amd64.tar.gz
tar zxvf helm-v3.13.1-linux-amd64.tar.gz
cp linux-amd64/helm /usr/local/bin/helm

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

也可從 Script 安裝

curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && \
chmod 700 get_helm.sh && \
./get_helm.sh

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

二者效果相同,擇一安裝即可。

設定 Worker node

用指令重新生成加入指令

kubeadm token create --print-join-command

出現 kubeadm join 指令之後,加上指明 cri-socket 就可以執行了

意指加上這行

--cri-socket unix:///var/run/cri-dockerd.sock

變成這樣

sudo kubeadm join 192.168.1.100:6443 
    --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6 \
    --cri-socket unix:///var/run/cri-dockerd.sock

這樣就加入叢集了

設定 Calico CNI 網路

參考文件
https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart

註:這邊 Calico CNI 也不停的一直在更新版本,步驟會略有一些差異,這邊本就文字記錄,
請時時刻刻查詢官方文件,實際以官方文件撰寫的為主

根據文件,第一步要建立 tigera-operator.yaml 的內容

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml

要注意 calico 的版本號
另一個要注意,這指令一定要使用 kubectl create,不能使用 kubectl apply 指令替代
不然會有錯誤

第二步要建立 custom-resources.yaml 的內容
這邊我們修改一下,先把檔案抓下來

wget https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/custom-resources.yaml

然後修改 custom-resources.yaml 的內容

vi custom-resources.yaml
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  # Configures Calico networking.
  calicoNetwork:
    ipPools:
    - name: default-ipv4-ippool
      blockSize: 26
      cidr: 10.244.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()

---

apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
  name: default
spec: {}

cidr 的值,原本是 192.168.0.0/16,改成我們使用 --pod-network-cidr 參數的值:10.244.0.0/16
其實也只是因為我們外面主機已經使用 192.168.0.0/16 的網段了,所以內部 K8s 跑的網段改成跟主機不一樣的 10.244.0.0/16

然後執行建立指令

kubectl create -f custom-resources.yaml

設定 Control node 兼 Worker node (Optional)

如果你需要 Control node 兼 Worker node 校長兼撞鐘,
你可以使用這個指令移除 taint,讓 control-plane 也能跑 Pod

(如有需求再使用)

kubectl taint nodes --all node-role.kubernetes.io/control-plane:NoSchedule-

安裝 gpu-operator

這邊是 GPU 的重點了,我們要安裝 gpu-operator,
讓 GPU 支援進入每一個 K8s node

註:舊版文件會教你安裝 k8s-device-plugin 元件,現直接使用 gpu-operator 元件即可
因為 gpu-operator 裡面已經包含了 k8s-device-plugin 元件
別的文件會教你安裝 DCGM-Exporter 元件,而它也一併納入 gpu-operator 元件裡了

GPU 切割分享方式有四種:

  • Time slicing (分時多工)
  • MPS (Multi-Process Service)
  • MIG (Multi-Instance GPU)
  • vGPU

這次使用 Time slicing(分時多工) 的方式來共享 GPU

加入 helm repo

helm repo add gpu-operator https://helm.ngc.nvidia.com/nvidia && \
helm repo update

顯示 helm charts 參數

helm show values gpu-operator/gpu-operator --version 24.6.2 > gpu-values.yaml

helm 安裝 gpu-operator

helm install gpu gpu-operator/gpu-operator -n gpu-operator --version 24.6.2 -f gpu-values.yaml

gpu-values.yaml 基本上不需要改什麼東西,除非你有其他需求

其他指令

helm 更新 gpu-operator

helm upgrade gpu-operator gpu-operator/gpu-operator -n gpu --version 24.6.2 -f gpu-values.yaml

helm 刪除 gpu-operator

helm uninstall gpu-operator -n gpu-operator

helm 下載 chart (如果有離線需求的話)

helm pull gpu-operator/gpu-operator --version 24.6.2

顯示 helm charts 參數 (離線檔案)

helm show values gpu-operator-24.6.2.tgz --version 24.6.2 > gpu-values.yaml

helm 安裝 gpu-operator (離線檔案)

helm install gpu gpu-operator-24.6.2.tgz -n gpu --version 24.6.2 -f gpu-values.yaml

GPU 確認 Compute Mode (運算模式)

注意,GPU 的 Compute Mode (運算模式) 是不受上述的 nvidia-container-toolkit, gpu-operator 影響的
是在 NVIDIA GPU 裡獨立運作的模式

模式有四種:

  • O: Default (Compute shared mode)
    預設,一次可以執行多個程式
  • 1: Exclusive Thread
    (deprecated) 作用與 Exclusive Process 相同
  • 2: Prohibited
    禁止在該卡執行任何計算程式
  • 3: Exclusive Process
    獨佔模式,該卡只能一次執行一個程式

通常設定在 DEFAULT 但一些特殊情況會「被」設定成別的

例如:使用 MPS (Multi-Process Service) 模式,
Compute Mode 會被設定成 EXCLUSIVE_PROCESS
EXCLUSIVE_PROCESS 在 Time slicing 運作模式底下,
無法將多個程式掛在同一張卡上,造成問題。

設定第一張卡的 Compute Mode 為 Default
通常預設為這個模式,在 Docker、在 K8s 使用 Time slicing(分時多工)
也是依賴這個模式

nvidia-smi -i 0 -c DEFAULT

設定第一張卡的 Compute Mode 為 Exclusive Process

nvidia-smi -i 0 -c EXCLUSIVE_PROCESS

如果你在 K8s 使用 MPS (Multi-Process Service) 模式,
因為 MPS 是一個服務程式獨佔整張 GPU 再軟體切割,
gpu-operator 會幫你切成這個模式(但不會幫你切回去)

那就先這樣啦!祝安裝愉快!

標準配置 Kubernetes (K8s) 叢集安裝筆記 – Ubuntu 篇

後來做了很多研究,分享我的 Kubernetes (K8s) 標準架設方式。

因為 Kubernetes (K8s) 套件一直更新,步驟已經有一點不太一樣了,
再加上我有小小更換一些元件,感覺值得再寫一次
沒意外的話,會來個大改版,到時候可能又要再寫一次(笑)
這次一樣分二個版本 Ubuntu 版本跟 Redhat 版本

如果想要參考以前的文章可以參考這裡:

廢話不多說,我們開始

預期得到的成果

  • Ubuntu 24.04 LTS
  • Vanilla Kubernetes (via kubeadm) 1.34.2
  • docker v29.1.2 (containerd: v2.2.0)
  • cri-docker 0.3.20 (b11203a)
  • calico v3.29.2
  • 三台 Control node 與 三台 Worker Node 標準配置
  • 使用 NFS 存放 PVC 空間 (nfs-subdir-external-provisioner)
  • Metrics Server

架構圖

Kubernetes 安裝步驟

Step 0. 虛擬機硬體建置

這邊是我 虛擬機 (VM) 的硬體部分建置設定
(最小實驗性質的資源規格,正式機不建議使用這個規格)

  • 2 CPU
  • 4GB Ram
  • 10GB Disk 以上,建議 30GB 較穩定

到時候要建立六台 VM,三台 Control Node 跟三台 Worker Node ,這是標準叢集的配置。
如果你要把三台 Control Node 兼用 Worker Node 校長兼撞鐘,也可以,但不建議,後面會告訴你怎麼設定。

Step 1. <每台都做> 安裝 Docker

Docker 不分角色,三台都要裝

安裝文件:
https://docs.docker.com/engine/install/ubuntu/

小弟整理的一鍵安裝指令
(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

apt-get update -m -y && \
apt-get install -y ca-certificates curl && \
install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc && \
chmod a+r /etc/apt/keyrings/docker.asc && \
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  tee /etc/apt/sources.list.d/docker.list > /dev/null && \
apt-get update -y && \
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

修改 daemon.json 讓跳開預設網段
(如果沒有該檔案請自行新增之)

sudo vi /etc/docker/daemon.json

內容為

{
  "log-driver": "json-file",
  "log-opts": {
    "tag": "{{.Name}}",
    "max-size": "2m",
    "max-file": "2"
  },
  "default-address-pools": [
    {
      "base": "172.31.0.0/16",
      "size": 24
    }
  ],
  "bip": "172.7.0.1/16"
}

設定 docker 預設開機啟動

sudo systemctl enable --now docker

驗證 Docker

可用 systemctl 指令查看是否有正常執行

sudo systemctl status docker

看看是否有 Running

可以用 docker ps 查看目前所有運行中的 container

docker ps

是否能夠正常顯示列表,若是初次安裝,列表是空的很正常。

Step 2. <每台都做> 關掉 swap

這步驟不分角色,六台都要做,雖然最新版本有(有限度的)支援 Swap
但我還是先建議把 Swap 關掉,以確保叢集的穩定性。

我們用以下步驟永久關閉 Swap:

  1. sed 指令找尋 swap 片段,並加上註解
sudo sed -i '/ swap /s/^/#/g' /etc/fstab
  1. 然後重新載入磁區
sudo mount -a

暫時關閉 swap 可以用 swapoff 指令

sudo swapoff -a

確認 swap

我們用 free 指令就可以看到 Swap 有沒有啟用了

free

Step 3. <每台都做> 安裝 kubeletkubeadmkubectl 三兄弟

安裝文件:
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

加入 K8s 套件參考
安裝 kubelet kubeadm kubectl
(指定版本 1.34.2)

sudo apt update -y && \
sudo apt-get install -y apt-transport-https ca-certificates curl && \
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg && \
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list && \
sudo apt-get update -y && \
sudo apt-get install -y kubelet=1.34.2-1.1 kubeadm=1.34.2-1.1 kubectl=1.34.2-1.1 && \
sudo apt-mark hold kubelet kubeadm kubectl

(科技發展迅速,整理的安裝文件有可能會過時,如果有更新版,請參考官方文件)

這邊我有修改指定版本號

若你想查詢所有的版本,可以用以下指令

顯示所有版號

apt show kubelet -a | less

再修改指令上去

Step 4. <每台都做> 安裝 Container Runtime Interface (CRI) – cri-dockerd

這步驟不分角色,三台都要裝

https://kubernetes.io/docs/setup/production-environment/container-runtimes/

我們用 Docker Engine 推薦的 cri-dockerd

說明文件:
https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/migrate-dockershim-dockerd/#what-is-cri-dockerd

查看最新版本一樣沒有 24.04 (noble)

從官網手動安裝 Golang

如果你的 apt-get 套件庫的 Golang 不夠新的話
我在 Redhat 那邊有遇到這情況,我把說明文件先放在這裡

到 Golang 的官網下載最新版本的 Golang 例如 1.23.2

wget https://go.dev/dl/go1.23.2.linux-amd64.tar.gz

解壓縮 go1.23.2.linux-amd64.tar.gz 檔案,會得到 go 資料夾,把他搬到對應位置

tar zxvf go1.23.2.linux-amd64.tar.gz
sudo mv go /usr/lib/golang

然後建立捷徑

sudo ln -s /usr/lib/golang/bin/go /usr/bin/go

使用 go version 來確認版本

go version

內容如下

$ go version
go version go1.23.2 linux/amd64

手動編譯安裝 cri-dockerd

如果是 Ubuntu 24.04.1 LTS (Noble Numbat)
如果找不到你的版本,可能要手動編譯並安裝

以下是官方文件提供的步驟
https://github.com/mirantis/cri-dockerd#build-and-install
https://mirantis.github.io/cri-dockerd/usage/install-manually/

安裝 make 與 golang 套件

sudo apt install -y make golang

git clone 最新的版本

git clone https://github.com/Mirantis/cri-dockerd.git

編譯它 (compile)

cd cri-dockerd && \
make cri-dockerd

安裝

cd cri-dockerd && \
mkdir -p /usr/local/bin && \
install -o root -g root -m 0755 cri-dockerd /usr/local/bin/cri-dockerd && \
install packaging/systemd/* /etc/systemd/system && \
sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service

然後請 systemctl 重新載入 daemon
最後啟動服務

sudo systemctl daemon-reload && \
sudo systemctl enable --now cri-docker

如果是服務更新版本,需要重啟服務

sudo systemctl restart cri-docker

驗證 cri-docker

可用 systemctl 指令確認是否有正常運行

sudo systemctl status cri-docker

確認有 Running

確認版本號

cri-dockerd --version

執行紀錄

$ cri-dockerd --version
cri-dockerd 0.3.12-16-gebd9de06 (ebd9de06)

裝完就會有 unix:///var/run/cri-dockerd.sock

註:之前社群一直有人討論是否要編譯 ubuntu 24.04 (noble)
但我看下一版,應該就不使用 cri-dockerd 了
就沒繼續追蹤進度了

Step 5. 複製虛擬機 (VM)

這邊步驟就是將單純的將 虛擬機 (VM) 複製二份成三台,並全部啟動。
以下分別闡述複製完要做的事情

重新產生 Machine-id

用以下指令重新產生 Machine-id

sudo rm /etc/machine-id && \
sudo systemd-machine-id-setup

修改 Hostname (主機名稱)

sudo hostnamectl set-hostname k8s-node1

分別改成對應的主機名稱

重新設定 ssh,產生全新的 known-host

sudo ssh-keygen -A && \
sudo dpkg-reconfigure openssh-server

確認 Machine-id

sudo cat /sys/class/dmi/id/product_uuid

確認 Hostname

hostname

確認網卡 Mac address 位址

ip link

或者

ifconfig

都可以,如果沒有 ifconfig 指令要安裝 net-tools

sudo apt install -y net-tools

https://superuser.com/questions/636924/regenerate-linux-host-fingerprint

如果有需要的話,可以用 dhclient 指令重新取 DHCP 的 IP
(基本上你重新產生 Machine-id 的話,就會視為別台電腦了)

sudo dhclient -r

Step 6. <每台都做> 設定主機對應

叢集的三台機器做出來,還不知道彼此,
這邊用 /etc/hosts 檔案來讓主機們各自找到彼此

sudo vi /etc/hosts

根據每台主機的 IP 位址與主機名稱

192.168.1.100   ubuntu2404-k8s-ctrl1
192.168.1.101   ubuntu2404-k8s-ctrl2
192.168.1.102   ubuntu2404-k8s-ctrl3
192.168.1.103   ubuntu2404-k8s-worker1
192.168.1.104   ubuntu2404-k8s-worker2
192.168.1.105   ubuntu2404-k8s-worker3

IP 位址在前,主機名稱在後,用 tab 分隔。

先整理好內容,再各自寫在每一台上面,每一台主機都會看到同一份資料。

Step 7. <每台都做> 設定網路雜項值

根據文件:
https://kubernetes.io/docs/setup/production-environment/container-runtimes/#forwarding-ipv4-and-letting-iptables-see-bridged-traffic

這邊設定網路連線轉發 IPv4 位址並讓 iptables 查看橋接器的流量

用文件提供的指令操作,等等一句一句解釋:

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

請 Kubernetes (K8s) 引用載入 br_netfilteroverlay 二個核心模組

sudo modprobe overlay && \
sudo modprobe br_netfilter

啟用 br_netfilteroverlay 二個核心模組

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

設定轉發 IPv4 位址,讓 iptables 查看橋接器的流量

sudo sysctl --system

再不起重新啟動電腦情況下,套用設定值

檢查驗證

檢查 br_netfilteroverlay 二個核心模組有沒有被正確載入可以用以下二個指令

lsmod | grep br_netfilter
lsmod | grep overlay

檢查

  • net.bridge.bridge-nf-call-iptables
  • net.bridge.bridge-nf-call-ip6tables
  • net.ipv4.ip_forward 

這幾個系統變數是否有設定為 1,可以用 sysctl 指令來檢查:

sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

Step 8. 設定第一台 Control plane node(控制平台)

終於要來設定 Control plane (控制平台) 了,如果有其他教學看到 Master node 的話,
別擔心,指的是同一件事情。

利用 kubeadm init 指令來初始化,並代入這些參數:

sudo kubeadm init \
    --kubernetes-version 1.34.2 \
    --control-plane-endpoint=192.168.1.100 \
    --apiserver-advertise-address=192.168.1.100 \
    --node-name k8s-ctrl \
    --pod-network-cidr=10.244.0.0/16 \
    --cri-socket unix:///var/run/cri-dockerd.sock

參數說明

  • control-plane-endpoint
    指明 Control plane (控制平台) 是哪個網址,這邊設定好目前這台 IP 位址即可,假設為 192.168.1.100
    (這設定值可省略)
  • apiserver-advertise-address
    指明 API server 的廣播地址,預設就是 Control plane (控制平台) IP 位址,假設為 192.168.1.100
    (這設定值可省略)
  • node-name
    指明 Control plane (控制平台) 的名字,這裡跟主機名稱一致即可,注意大小寫底線,有些字元是不允許的。
  • pod-network-cidr
    指明 pod 內部網路使用的網段,這邊因為配合 Flannel CNI,請保留 10.244.0.0/16 先不要修改,除非你知道在做什麼。
  • cri-socket
    指明使用的 CRI 使用 unix:///var/run/cri-dockerd.sock 這設定值 請不要修改

會一路安裝第一台設定好為 control node

註:如果有需要,可以事先先下載 image
使用這指令

kubeadm config images pull --cri-socket unix:///var/run/cri-dockerd.sock --kubernetes-version 1.34.2

如果沒意外的話,完成之後會看到

Your Kubernetes control-plane has initialized successfully!

才成功三成而已,還沒完成!後續還要接續設定

Step 9. <在第一台 Control-node 做> 複製金鑰與證書

資料準備

在第一台 Control node 做操作

建立資料夾,假設路徑在 /tmp/k8s-certs 底下

mkdir -p /tmp/k8s-certs && \
mkdir -p /tmp/k8s-certs/etcd

我們需要複製以下檔案

.
├── ca.crt
├── ca.key
├── etcd
│   ├── ca.crt
│   └── ca.key
├── front-proxy-ca.crt
├── front-proxy-ca.key
├── sa.key
└── sa.pub

1 directory, 8 files

所以指令如下

sudo cp -r /etc/kubernetes/pki/{ca.*,sa.*,front-proxy-ca.*} /tmp/k8s-certs/ && \
sudo cp -r /etc/kubernetes/pki/etcd/ca.* /tmp/k8s-certs/etcd/

注意不要多複製其他檔案,不然到時候建立會有問題

複製到其他節點

我們就假設你都在第一台 Control node 做操作
我們把 /tmp/k8s-certs 資料夾複製到其他節點

scp -r /tmp/k8s-certs [email protected]:/tmp/
scp -r /tmp/k8s-certs [email protected]:/tmp/

然後 ssh 分別登入到另外二個節點

ssh [email protected]

在另外兩個節點中,建立資料夾,並複製檔案
把剛剛的那幾個金鑰複製到指定 K8s 位置 /etc/kubernetes/pki/

mkdir -p /etc/kubernetes/pki/ && \
cp -R /tmp/k8s-certs/* /etc/kubernetes/pki/

注意如果做錯了需要下 kubeadm reset 重來的時候, /etc/kubernetes/pki/ 金鑰也會被清空掉,所以要再複製一次

kubeadm reset -f --cri-socket unix:///var/run/cri-dockerd.sock

Step 10. <在另外二台 Control node 做> 加入成爲 Control node

這邊就比較特別,因為剛剛的金鑰複製步驟做完之後,
就可以用指令重新生成加入指令

kubeadm token create --print-join-command

然後你就會得到一串加入指令,假設長這樣

kubeadm join 192.168.1.100:6443 --token 2xxxxc.6bxxxxxxxxxxxx96 \
      --discovery-token-ca-cert-hash sha256:b84fxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc6b4

這時候你就可以在第二台與第三台 Control node 上執行類似這樣的指令

kubeadm join 192.168.1.100:6443 --token 2xxxxc.6bxxxxxxxxxxxx96 \
      --discovery-token-ca-cert-hash sha256:b84fxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc6b4 \
      --control-plane --cri-socket unix:///var/run/cri-dockerd.sock

加上 --control-plane 參數,讓這台節點成為 Control node
還有加上 --cri-socket unix:///var/run/cri-dockerd.sock 參數,讓 kubeadm 知道你使用的是 cri-dockerd

這樣就完成了

你可以用 kubectl get node 查看一下

# kubectl get node node
NAME                   STATUS     ROLES           AGE     VERSION
ubuntu2404-k8s-ctrl1   NotReady   control-plane   3m22s   v1.34.2
ubuntu2404-k8s-ctrl2   NotReady   control-plane   9s      v1.34.2
ubuntu2404-k8s-ctrl3   NotReady   control-plane   5s      v1.34.2

這邊因為還沒有設定 CNI,所以 STATUS 為 NotReady 是 正常現象
(叢集才設定一半,還沒設定網路,當然顯示 K8s 叢集不可用)

Step 11. <在 Worker node 做> 加入 Worker node

如果要加入 Worker node,可以使用 kubeadm join 指令

可以在其中一台 Control code 用指令重新生成加入指令

kubeadm token create --print-join-command

然後你就會得到一串加入指令,假設長這樣

kubeadm join 192.168.1.100:6443 --token 2xxxxc.6bxxxxxxxxxxxx96 \
      --discovery-token-ca-cert-hash sha256:b84fxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc6b4

這時候你就可以在 Worker node 上執行類似這樣的指令

kubeadm join 192.168.1.100:6443 --token 2xxxxc.6bxxxxxxxxxxxx96 \
      --discovery-token-ca-cert-hash sha256:b84fxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc6b4 \
      --cri-socket unix:///var/run/cri-dockerd.sock

沒有意外的話,就可以正常加入了
其他台 Worker node 也是一樣的操作

Step 12. 設定 Calico CNI 網路

參考文件
https://docs.tigera.io/calico/latest/getting-started/kubernetes/quickstart

註:這邊 Calico CNI 也不停的一直在更新版本,步驟會略有一些差異,這邊本就文字記錄,
請時時刻刻查詢官方文件,實際以官方文件撰寫的為主

筆者撰文的時候 calico v3.29.2

文件在此
https://docs.tigera.io/calico/3.29/getting-started/kubernetes/quickstart

Murmur: 以前 v3.14 版本之前本來只有 calico.yaml 一個檔案,
後來改成 tigera-operator.yamlcustom-resources.yaml 二個檔案了,不影響操作

根據文件,第一步要建立 tigera-operator.yaml 的內容

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml

要注意 calico 的版本號
另一個要注意,這指令一定要使用 kubectl create,不能使用 kubectl apply 指令替代
不然會有錯誤

第二步要建立 custom-resources.yaml 的內容
這邊我們修改一下,先把檔案抓下來

wget https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/custom-resources.yaml

然後修改 custom-resources.yaml 的內容

vi custom-resources.yaml
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  # Configures Calico networking.
  calicoNetwork:
    ipPools:
    - name: default-ipv4-ippool
      blockSize: 26
      cidr: 10.244.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()

---

apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
  name: default
spec: {}

cidr 的值,原本是 192.168.0.0/16,改成我們使用 --pod-network-cidr 參數的值:10.244.0.0/16
其實也只是因為我們外面主機已經使用 192.168.0.0/16 的網段了,所以內部 K8s 跑的網段改成跟主機不一樣的 10.244.0.0/16

然後執行建立指令

kubectl create -f custom-resources.yaml

我這邊也列一下 calico v3.29.2 會用到的 image,供參考
(每一個版本可能用的 image 版號也會不同)

quay.io/tigera/operator:v1.36.5
docker.io/calico/typha:v3.29.2
docker.io/calico/node-driver-registrar:v3.29.2
docker.io/calico/csi:v3.29.2
docker.io/calico/pod2daemon-flexvol:v3.29.2
docker.io/calico/node:v3.29.2
docker.io/calico/kube-controllers:v3.29.2
docker.io/calico/cni:v3.29.2
docker.io/calico/apiserver:v3.29.2

Step 13. 設定 Control node 兼 Worker node (選擇性)

如果你需要 Control node 兼 Worker node 校長兼撞鐘,
你可以使用這個指令移除 taint

(如有需求再使用)

kubectl taint nodes --all node-role.kubernetes.io/control-plane:NoSchedule-

筆記一下,舊版指令如下

kubectl taint nodes --all node-role.kubernetes.io/master-

測試驗證

驗證

kubectl get pods -A

全部都要是 Running 的狀態

像這樣

# kubectl get pods -A
NAMESPACE         NAME                                           READY   STATUS    RESTARTS   AGE
calico-system     calico-node-nzl6r                              0/1     Running   0          37s
calico-system     calico-node-xp467                              1/1     Running   0          39s
calico-system     calico-node-xt9xg                              1/1     Running   0          39s
calico-system     calico-typha-6b99cb568-d8t92                   1/1     Running   0          102s
calico-system     calico-typha-6b99cb568-xsq5f                   1/1     Running   0          101s
kube-system       coredns-66bc5c9577-556g8                       1/1     Running   0          44m
kube-system       coredns-66bc5c9577-vwh6x                       1/1     Running   0          44m
kube-system       etcd-ubuntu2404-k8s-ctrl1                      1/1     Running   0          44m
kube-system       etcd-ubuntu2404-k8s-ctrl2                      1/1     Running   0          41m
kube-system       etcd-ubuntu2404-k8s-ctrl3                      1/1     Running   0          41m
kube-system       kube-apiserver-ubuntu2404-k8s-ctrl1            1/1     Running   0          44m
kube-system       kube-apiserver-ubuntu2404-k8s-ctrl2            1/1     Running   0          41m
kube-system       kube-apiserver-ubuntu2404-k8s-ctrl3            1/1     Running   0          41m
kube-system       kube-controller-manager-ubuntu2404-k8s-ctrl1   1/1     Running   0          44m
kube-system       kube-controller-manager-ubuntu2404-k8s-ctrl2   1/1     Running   0          41m
kube-system       kube-controller-manager-ubuntu2404-k8s-ctrl3   1/1     Running   0          41m
kube-system       kube-proxy-gssk9                               1/1     Running   0          44m
kube-system       kube-proxy-shls8                               1/1     Running   0          41m
kube-system       kube-proxy-xtsfw                               1/1     Running   0          41m
kube-system       kube-scheduler-ubuntu2404-k8s-ctrl1            1/1     Running   0          44m
kube-system       kube-scheduler-ubuntu2404-k8s-ctrl2            1/1     Running   0          41m
kube-system       kube-scheduler-ubuntu2404-k8s-ctrl3            1/1     Running   0          41m
tigera-operator   tigera-operator-6dc5767955-cfshr               1/1     Running   0          2m58s
kubectl get nodes -o wide

要看到所有節點都有 Ready 的狀態

像這樣

# kubectl get nodes -o wide
NAME                   STATUS   ROLES           AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
ubuntu2404-k8s-ctrl1   Ready    control-plane   46m   v1.34.2   192.168.1.100   <none>        Ubuntu 24.04.3 LTS   6.8.0-88-generic   docker://29.1.2
ubuntu2404-k8s-ctrl2   Ready    control-plane   43m   v1.34.2   192.168.1.101   <none>        Ubuntu 24.04.3 LTS   6.8.0-88-generic   docker://29.1.2
ubuntu2404-k8s-ctrl3   Ready    control-plane   43m   v1.34.2   192.168.1.102   <none>        Ubuntu 24.04.3 LTS   6.8.0-88-generic   docker://29.1.2
ubuntu2404-k8s-worker1 Ready    None            43m   v1.34.2   192.168.1.103   <none>        Ubuntu 24.04.3 LTS   6.8.0-88-generic   docker://29.1.2
ubuntu2404-k8s-worker1 Ready    None            43m   v1.34.2   192.168.1.104   <none>        Ubuntu 24.04.3 LTS   6.8.0-88-generic   docker://29.1.2
ubuntu2404-k8s-worker1 Ready    None            43m   v1.34.2   192.168.1.105   <none>        Ubuntu 24.04.3 LTS   6.8.0-88-generic   docker://29.1.2

版本資訊

只是做個紀錄

# docker version
Client: Docker Engine - Community
 Version:           29.1.2
 API version:       1.52
 Go version:        go1.25.5
 Git commit:        890dcca
 Built:             Tue Dec  2 21:55:14 2025
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          29.1.2
  API version:      1.52 (minimum version 1.44)
  Go version:       go1.25.5
  Git commit:       de45c2a
  Built:            Tue Dec  2 21:55:14 2025
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v2.2.0
  GitCommit:        1c4457e00facac03ce1d75f7b6777a7a851e5c41
 runc:
  Version:          1.3.4
  GitCommit:        v1.3.4-0-gd6d73eb8
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
# kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"34", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.34.2", GitCommit:"8cc511e399b929453cd98ae65b419c3cc227ec79", GitTreeState:"clean", BuildDate:"2025-11-11T19:08:36Z", GoVersion:"go1.24.9", Compiler:"gc", Platform:"linux/amd64"}
# kubectl version
Client Version: v1.34.2
Kustomize Version: v5.7.1
Server Version: v1.34.2
# cri-dockerd --version
cri-dockerd 0.3.20 (b11203a)

Troubleshooting 疑難排解

如果你遇到類似的錯誤

# kubeadm join 192.168.1.100:6443 --token kkxxxx.xxxxxxxxxxxxxdl2      --discovery-token-ca-cert-hash sha256:bdfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx19c         --control-plane --cri-socket unix:///var/run/cri-dockerd.sock

[preflight] Running pre-flight checks
[preflight] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[preflight] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.
error execution phase preflight: 
One or more conditions for hosting a new control plane instance is not satisfied.

[failure loading certificate for CA: couldn't load the certificate file /etc/kubernetes/pki/ca.crt: open /etc/kubernetes/pki/ca.crt: no such file or directory, failure loading key for service account: couldn't load the private key file /etc/kubernetes/pki/sa.key: open /etc/kubernetes/pki/sa.key: no such file or directory, failure loading certificate for front-proxy CA: couldn't load the certificate file /etc/kubernetes/pki/front-proxy-ca.crt: open /etc/kubernetes/pki/front-proxy-ca.crt: no such file or directory, failure loading certificate for etcd CA: couldn't load the certificate file /etc/kubernetes/pki/etcd/ca.crt: open /etc/kubernetes/pki/etcd/ca.crt: no such file or directory]

Please ensure that:
* The cluster has a stable controlPlaneEndpoint address.
* The certificates that must be shared among control plane instances are provided.

To see the stack trace of this error execute with --v=5 or higher

遇到這段

failure loading certificate for CA: couldn't load the certificate file

應該是沒有正確複製金鑰

除錯

如果有遇到問題,可以這樣觀察

查看 kubelet 的狀態

systemctl status kubelet

查看 kubelet 的 Log

journalctl -xeu kubelet

這樣最基礎的 K8s 加上網路就完成了

Persistent Volumes (PV) 磁碟相關設定

基本上會需要一個共用空間來配置 Persistent Volumes (PV)
我們可以用 NFS 來做為該共用空間
這邊可能就比較雜項一點,但如果沒有設定好,
應用程式設定 Persistent Volume Claim (PVC) 是不會有動作的,
狀態會卡住無法正確部署

安裝 nfs-server (Optional)

剛剛有提到,我們使用 NFS 來作為存放 Persistent Volumes (PV) 的地方,
需要一個 NFS 的位置,這個可以是你的 NAS,也可以是台電腦,
也可以是 TrueNAS 或者 OpenMediaVault (OMV),總之做法很多,
這邊示範如果你什麼都沒有,只有 Ubuntu 主機,如何直接在上面裝一個 NFS 伺服器。

安裝 nfs-server

sudo apt install nfs-kernel-server nfs-common -y

假設我們要共用的資料夾路徑為 /export/k8s-space
所以我們來開 /export/k8s-space 資料夾

mkdir -p /export && \
mkdir -p /export/k8s-space

編輯 /etc/exports 設定檔

vi /etc/exports

內容為

/export/k8s-space 192.168.1.0/24(rw,subtree_check,insecure)
/export 192.168.1.0/24(rw,root_squash,no_subtree_check,hide)

這邊 IP 設定可存取的網段,假設為 192.168.1.0/24,請依需求修改

啟動 nfs 服務

sudo systemctl start --now nfs-kernel-server.service

如果 /etc/exports 設定檔有更新,記得用指令更新 nfs 檔案清單

exportfs -a

設定與安裝 nfs-subdir-external-provisioner

這塊就是 K8s 的範疇,
使用 helm 來安裝 nfs-subdir-external-provisioner
他會做一件事情,持續偵測 K8s 狀態,
當收到 PVC 請求的時候,在 nfs 開一個指定的資料夾,當成 PV
然後掛載在 PVC 上

加入 helm repo 參考

helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
helm repo update

產生 helm charts 參數

helm show values nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  --version 4.0.18 > nfs-values.yaml

它會產生一個預設的 nfs-values.yaml 供你修改

修改 nfs-values.yaml

這就是重頭戲,修改 nfs-values.yaml

vi nfs-values.yaml

修改的片段如下,請依需求修改

image:
  repository: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner
  tag: v4.0.2
  pullPolicy: IfNotPresent
#imagePullSecrets:
#- name: regcred

設定值說明

  • image.repositoryimage.tag: image 的位址,通常情況私有部署時,
    會把 image 放進私有的 Registry,所以會對應修改這些值
  • image.pullPolicy:部署時拉取的策略,常用值可以是 IfNotPresent (如果沒有的話才從遠端下載) 或 Always (總是每次都從遠端下載)
  • imagePullSecrets.name:私有 Registry 的登入資訊
nfs:
  server: 192.168.1.2
  path: /export/k8s-space
  mountOptions:
  volumeName: nfs-subdir-external-provisioner-root
  # Reclaim policy for the main nfs volume
  reclaimPolicy: Delete

設定值說明

  • nfs.server:NFS 伺服器位址,請依需求修改
  • nfs.path:NFS 的遠端路徑,請依需求修改
  • nfs.reclaimPolicy: 如果 PVC 刪除之後的該空間的預設動作處理,
    常用值為 Retain (保留) 與 Delete (刪除),
    若是 Retain 的話,要記得 定時進來手動清理空間
    因為 PVC 刪除時,不會連動被刪除,但也不會掛回同一個 PVC,
    重新部署時就會開一個新的空間,久而久之就變成莫名的占空間

修改完成之後,就可以將它安裝起來

安裝部署 nfs-subdir-external-provisioner

helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  -n nfs-subdir-external-provisioner --version 4.0.18 -f nfs-values.yaml

其他相關指令

更新 nfs-subdir-external-provisioner 部署

helm upgrade nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  -n nfs-subdir-external-provisioner --version 4.0.18 -f nfs-values.yaml

如果有參數有弄錯,可以用指令刪除部署,然後再重新部署

helm uninstall nfs-subdir-external-provisioner -n nfs-subdir-external-provisioner

如果不知道 nfs-values.yaml 合併回 yaml 會長什麼樣子,
我會用 helm templatenfs-values.yaml 合併回 template 輸出原始 yaml,
來做比對與比較。

產生 nfs-subdir-external-provisioner templeate

helm template nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  -n nfs-subdir-external-provisioner --version 4.0.18 -f nfs-values.yaml --output-dir ./nfs-yamls

正常情況會有一個 Pod 在 K8s 叢集中常駐執行

安裝 metrics-server

在自行安裝的 Vanilla Kubernetes 預設是不會安裝 metrics-server 的
換言之,你無法使用 kubectl top nodekubectl top pod 等指令,
部署 Horizontal Pod Autoscaling (HPA) 也會失效,
因為他抓不到叢集 CPU、記憶體…等資訊。

所以我們讓補上 metrics-server 讓功能完整。

安裝指令也蠻簡單的,不需什麼額外設定

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

測試 metrics-server

測試 metrics-server 的方式很簡單

打上常用的這個指令可以測試

顯示每個 node 的資源使用狀況

kubectl top node

顯示每個 Pod 的資源使用狀況

kubectl top pod -A

就先分享到這,希望有幫助到你。
祝架設愉快!

[DevOps] 如何用 Webhook API 發訊息到 Microsoft Teams (使用 Workflows)

看到標題你一定想說,這麼簡單的東西,為何要寫一篇文章,
想當然爾,這篇文章一定是伴隨著怒氣寫出來的
沒事一個簡單的事情,它一定要搞得超複雜它才開心 Orz

需求很稀鬆平常,就是我有各種系統通知要串接 Microsoft Teams
簡單來說就是讓機器發訊息到 Teams 的指定群組

前前後後花了好長時間找文件,最後終於找到了
避免大家走彎路,就分享給大家

舊方式 Incoming webhooks (準備棄用)

有全網搜尋過就知道,以前舊的方式是使用 Incoming webhooks,

在 Teams 的左側欄的左下角有個 Apps 按下去,
然後搜尋「Incoming Webhook

會看到這個 Incoming Webhook (to be retired)

按照步驟,選擇你的頻道名稱

按下 Create

然後你就會得到一個 Webhook URL

可能類似長這樣

https://xxxxxxxx.webhook.office.com/webhookb2/6dxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx@xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx3f4/IncomingWebhook/bf0xxxxxxxxxxxxxxxxxxxxxxxxxxx54/40xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx3de/V2xx-xxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxx_xxxxBF

(這個就是 API 需要打的網址,網址每個人都不一樣)

呼叫它最簡單的方式就是,打一個 POST 到這個 URL,
內容帶入這個 JSON

{ "text": "My Test Message example" }

正常會回 200 OK

習慣看 curl 的朋友,也附上 curl 的版本

curl --location 'https://xxxxxxxx.webhook.office.com/webhookb2/6dxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx@xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx3f4/IncomingWebhook/bf0xxxxxxxxxxxxxxxxxxxxxxxxxxx54/40xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx3de/V2xx-xxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxx_xxxxBF' 
--data '{ "text": "My Test Message example" }'

結案

但以上這段已經準備要 Deprecated (棄用) 了

新方式,使用 Workflows

在 Teams 的左側欄的左下角有個 Apps 按鈕,按下去,
然後搜尋「Workflows

可能會很多個都叫 Workflows,找到確定是 Microsoft Corporation 才對哦!

把它加進去 Teams 中

然後在左側標籤,按下 Workflows 的標籤

在上方找到 Create 標籤

等等會用到 Send webhook alerts to a channel 或者 Send webhook alerts to a chat 等等分開說明

Send webhook alerts to a channel(收到 webhook 要求時發佈在頻道中)

搜尋「Send webhook alerts to a channel
中文名稱「收到 webhook 要求時發佈在頻道中」

根據提示,選擇你的 Teams 與 Channel

最後會出現一個網址

Send webhook alerts to a chat(收到 webhook 要求時發佈在聊天中)

搜尋「Send webhook alerts to a chat
中文名稱「收到 webhook 要求時發佈在聊天中」

設定你想要丟訊息的群組名稱

不管這二種方式,最後會出現一個網址,像這樣

https://defaultxxxxxxxxxxxxxxxxxxxxxxxxxxxab0.xx.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/2aaxxxxxxxxxxxxxxxxxxxxxxxxxx77d/triggers/manual/paths/invoke/?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=kmcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxFBU

(這個就是 API 需要打的網址,網址每個人都不一樣)

重點來了!!!

他的介面沒有交代你怎麼使用這個 API
在微軟的網站翻找超級久,終於找到他的 API 用法

API 文件在這裡(淚~~)

https://learn.microsoft.com/en-us/connectors/teams/?tabs=text1%2Cdotnet&cf_lbyyhhwhyjj5l3rs65cb3w=j09h5ro1lgi6jiaolddgf#microsoft-teams-webhook

等等直接上範例說明

Workflows 發訊息到 Teams 簡單範例

打 POST 到你剛剛產生的網址

https://defaultxxxxxxxxxxxxxxxxxxxxxxxxxxxab0.xx.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/2aaxxxxxxxxxxxxxxxxxxxxxxxxxx77d/triggers/manual/paths/invoke/?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=kmcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxFBU

Body 的部分選 Raw (JSON)
然後打上以下內容

{
  "type": "message",
  "attachments": [
    {
      "contentType": "application/vnd.microsoft.card.adaptive",
      "contentUrl": null,
      "content": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.5",
        "body": [
          {
            "type": "TextBlock",
            "text": "Hello, World",
            "wrap": true
          }
        ]
      }
    }
  ]
}

這就是最簡單的範例了
(別問我傳個文字訊息為何要搞這麼複雜…)

回應會是 202 Accepted 內容為空白
再檢查一下你的 Channel,應該就會看到訊息了

怕只看 curl 的朋友看不懂,這邊附上 curl 的版本

curl --location 'https://defaultxxxxxxxxxxxxxxxxxxxxxxxxxxxab0.xx.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/2aaxxxxxxxxxxxxxxxxxxxxxxxxxx77d/triggers/manual/paths/invoke/?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=kmcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxFBU' 
--header 'Content-Type: application/json' 
--data '{
    "type": "message",
    "attachments": [
        {
            "contentType": "application/vnd.microsoft.card.adaptive",
            "contentUrl": null,
            "content": {
                "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
                "type": "AdaptiveCard",
                "version": "1.5",
                "body": [
                    {
                        "type": "TextBlock",
                        "text": "Hello, World",
                        "wrap": true
                    }
                ]
            }
        }
    ]
}'

訊息長這樣

這個 JSON 格式在這裡是有講究的

  • "type": "message"
  • "contentType": "application/vnd.microsoft.card.adaptive"
  • "contentUrl": null

這些值都是固定的
你能改的地方只有 body 這裡。
它是為了可以做特殊客製卡片訊息,放了很多保留參數在上面。

Workflows 發訊息到 Teams 複雜範例

放一個稍微複雜的範例

{
  "type": "message",
  "attachments": [
    {
      "contentType": "application/vnd.microsoft.card.adaptive",
      "contentUrl": null,
      "content": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.5",
        "body": [
          {
            "type": "TextBlock",
            "size": "Medium",
            "weight": "Bolder",
            "text": "This is my title"
          },
          {
            "type": "ColumnSet",
            "columns": [
              {
                "type": "Column",
                "items": [
                  {
                    "type": "TextBlock",
                    "weight": "Bolder",
                    "text": "Matt Hidinger",
                    "wrap": true
                  },
                  {
                    "type": "TextBlock",
                    "spacing": "None",
                    "text": "Created 2025-08-25 21:03",
                    "isSubtle": true,
                    "wrap": true
                  }
                ],
                "width": "stretch"
              }
            ]
          },
          {
            "type": "TextBlock",
            "text": "This is desc",
            "wrap": true
          }
        ]
      }
    }
  ]
}{
  "type": "message",
  "attachments": [
    {
      "contentType": "application/vnd.microsoft.card.adaptive",
      "contentUrl": null,
      "content": {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.5",
        "body": [
          {
            "type": "TextBlock",
            "size": "Medium",
            "weight": "Bolder",
            "text": "This is Alert Title"
          },
          {
            "type": "ColumnSet",
            "columns": [
              {
                "type": "Column",
                "items": [
                  {
                    "type": "Image",
                    "style": "person",
                    "url": "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg",
                    "size": "small"
                  }
                ],
                "width": "auto"
              },
              {
                "type": "Column",
                "items": [
                  {
                    "type": "TextBlock",
                    "weight": "Bolder",
                    "text": "Matt Hidinger",
                    "wrap": true
                  },
                  {
                    "type": "TextBlock",
                    "spacing": "None",
                    "text": "Created 2025-08-25 21:03",
                    "isSubtle": true,
                    "wrap": true
                  }
                ],
                "width": "stretch"
              }
            ]
          },
          {
            "type": "TextBlock",
            "text": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
            "wrap": true
          }
        ]
      }
    }
  ]
}

這個可以做一個類似文章的小卡片

附上 curl 的版本

curl --location 'https://defaultxxxxxxxxxxxxxxxxxxxxxxxxxxxab0.xx.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/2aaxxxxxxxxxxxxxxxxxxxxxxxxxx77d/triggers/manual/paths/invoke/?api-version=1&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=kmcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxFBU' 
--data '{
    "type": "message",
    "attachments": [
        {
            "contentType": "application/vnd.microsoft.card.adaptive",
            "contentUrl": null,
            "content": {
                "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
                "type": "AdaptiveCard",
                "version": "1.5",
                "body": [
                    {
                        "type": "TextBlock",
                        "size": "Medium",
                        "weight": "Bolder",
                        "text": "This is Alert Title"
                    },
                    {
                        "type": "ColumnSet",
                        "columns": [
                            {
                                "type": "Column",
                                "items": [
                                    {
                                        "type": "Image",
                                        "style": "person",
                                        "url": "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg",
                                        "size": "small"
                                    }
                                ],
                                "width": "auto"
                            },
                            {
                                "type": "Column",
                                "items": [
                                    {
                                        "type": "TextBlock",
                                        "weight": "Bolder",
                                        "text": "Matt Hidinger",
                                        "wrap": true
                                    },
                                    {
                                        "type": "TextBlock",
                                        "spacing": "None",
                                        "text": "Created 2025-08-25 21:03",
                                        "isSubtle": true,
                                        "wrap": true
                                    }
                                ],
                                "width": "stretch"
                            }
                        ]
                    },
                    {
                        "type": "TextBlock",
                        "text": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry'''s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
                        "wrap": true
                    }
                ]
            }
        }
    ]
}'

卡片大概就是這樣使用

它有做一個卡片編輯器可以使用
https://adaptivecards.io/designer

你可以編輯成你喜歡的樣子之後,再拿來使用

取代 attachments[0].content 的部分就好

這樣就可以發送你自訂的訊息了

根據實測,這個 JSON 架構不要亂改
他的欄位是根據他文件定義的 JSON 架構去做的
如果你改了 JSON 架構,一樣會回應 202 Accepted
只是你在設定 Workflows 的時候會選不到指定欄位,這部分我暫時無解

另外它文件有提到小限制,
封包最大大約 28 KB,超過大小會回應 Request Entity too large.

整個測試最謎的地方就在這裡,
除非 JSON 格式錯誤,或者權限不足等問題,
不然他都會回應 202 Accepted

至少,看完整篇你會避掉一些雷,
有些方向不對的文件,就不要參考了
應該…是新版最簡單發訊息的方法,分享給大家。

相關資料

卡片編輯器
https://adaptivecards.io/designer

各種卡片範例
https://adaptivecards.io/samples/

參考資料

[DevOps/RPA] 使用 pyautogui 做自動化 automation

tags: pyautogui

用了一下 pyautogui 這個自動化套件,
覺得這個套件其實還蠻容易上手的,
你可以把它想成某種程式化的「按鍵精靈」
會照著你的想法去操作鍵盤、滑鼠。

在製作 RPA (Robotic Process Automation) 節省時間,增進效率,
會是一個非常關鍵要件

這邊帶你快速上手 pyautogui,算是我的某種 Cheat Sheet 吧

PyAutoGUI 介紹

PyAutoGUI 是一個用於 桌面自動化 的 Python 套件,能模擬滑鼠移動、點擊、拖曳,以及鍵盤輸入等操作。它跨平台支援 Windows、macOS、Linux,非常適合用來撰寫腳本自動完成重複性工作,例如批次截圖、測試 UI 或自動填表。PyAutoGUI 也提供螢幕截圖與簡易畫面影像辨識功能,能根據畫面上的元素定位與操作,讓自動化更靈活易用。

快速上手 pyautogui

鍵盤滑鼠類

列出我幾個常用的 method

按組合鍵

pyautoui.hotkey('win', 'r')

鍵盤上按指定按鍵

pyautogui.press('tab')

鍵盤打字

pyautogui.write('Hello, World.')

把滑鼠移過去點擊

pyautogui.click(100, 50, duration=0.5)

螢幕相關

找圖片上的位置(做定位點)

myAncher = pyautogui.locateOnScreen('button.png', grayscale=True)

螢幕截圖

pyautogui.screenshot('screenshot/1.png')

這邊你可以先螢幕截圖到時候讓程式來便是它

如果找不到圖片會噴 ImageNotFoundException

光這樣就可以玩很多花樣了

警告視窗系列

其他的部分它有簡單的提供一些 Alert, Confirm 的視窗

打開一個 Alert 警告窗

pyautogui.alert('This displays some text with an OK button.')

有時候提示使用者需要用到

取得視窗相關

取得所有視窗物件

pyautogui.getAllWindows()

取得所有視窗標題

pyautogui.getAllTitles()

它官網範例是操作 Windows 計算機
準備各按鈕截圖好的圖片,利用 locateOnScreen() 來定位
click() 來點擊操作計算機

基礎是這樣

參考資料

https://ithelp.ithome.com.tw/articles/10277668
https://pyautogui.readthedocs.io/en/latest/

[Linux] 把玩 Android 16 beta 的原生終端機 Terminal 功能

Android 16 beta 推出了很多全新的功能,
讓我眼睛一亮的是原生終端機 (Terminal) 功能,
相對於手機有一個小小 Debian Linux 可以使用,
如果熟悉 Ubuntu 的朋友,幾乎無痛上手。

就來看看要怎麼把玩這個新功能吧!

Android 16 新功能頁面:

https://developer.android.com/about/versions/16?hl=zh-tw

支援的清單

支援的清單列表在這裡:

https://developer.android.com/about/versions/16/download-ota?hl=zh-tw

  • Pixel 6 與 6 Pro
  • Pixel 6a
  • Pixel 7 與 7 Pro
  • Pixel 7a
  • Pixel Fold
  • Pixel Tablet
  • Pixel 8 和 8 Pro
  • Pixel 8a
  • Pixel 9、Pixel 9 Pro、Pixel 9 Pro XL 和 Pixel 9 Pro Fold

有支援的清單可以直接線上 OTA 升級,不用清除資料
不用清除資料,這點真的香

這次使用的機器:Pixel 7 pro

以下是操作步驟

加入 Android Beta 版體驗方案

到這個連結:

https://www.google.com/android/beta#devices

登入你有綁定 Pixel 手機的 Google 帳號,選取你手機,
將你的手機加入 Beta 體驗方案,屆時 Android 就會幫你推送 Beta 的更新

在手機系統,點選系統更新,就會看到新版 Android 16 beta 啦!

開心的的按下更新
(註:Beta 版可能會有不穩定的情況,建議不要用主要使用的手機做這個更新)

打開 Linux 開發環境

更新完畢之後,除了 UI 介面有小改之外,感覺什麼事情都沒有發生,
這很正常,要打開秘密選項才有

到系統設定

系統 > 開發人員選項 > Linux 開發環境

[勾選] 實驗功能 在 Android 上執行 Linux 終端機

系統 > 開發人員選項 > Linux 開發環境

[勾選] 停止限制子程序

應用程式就會出現 [終端機] 程式
就可以開始玩了

等於一台 arm64 的 debian 機器

設定硬碟大小

設定 > 調整磁碟大小

預設 5GB 可以直接開到最大 16GB

不然你可能會遇到磁碟空間不足的問題

各種安裝與把玩

接下來就是各種安裝與把玩啦!
想怎麼玩都可以,不一定要照我的做

有個地方要注意,手機是 arm64 架構,
一般 x86_64 程式是不能直接運行的,
它也不是 Apple silicon (M1) 有 Rosetta 可以跑相容模式,
下載程式編譯架構要注意一下

以下是一些指令筆記:

安裝相關套件與權限調整

安裝相關套件

sudo apt update -y
sudo apt install -y tmux neofetch

建立使用者,調整權限

useradd myuser
passwd myuser
sudo usermod -a -G sudo myuser
sudo usermod -a -G docker myuser

調整磁碟大小

sudo apt install -y parted

在 parted 介面裡,找到 rootfs ext4 磁區,將它擴大

print
resizepart 1 100%
quit

SSH Server

安裝 SSH Server

sudo apt install -y openssh-server

設定開啟的 port

sudo vi /etc/ssh/sshd_config

修改

port 22

重啟服務

sudo systemctl restart sshd

會有通知提示是否允許 port 開啟,因爲安全原因,只會開放對應到本機上

ngrok

安裝 ngrok,下列指令二選一

wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-arm64.tgz
sudo tar xvzf ./ngrok-v3-stable-linux-arm64.tgz -C /usr/local/bin

參考:

https://ngrok.com/docs/guides/device-gateway/linux/

或者

curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
  | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null \
  && echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \
  | sudo tee /etc/apt/sources.list.d/ngrok.list \
  && sudo apt update \
  && sudo apt install ngrok

參考:

https://ngrok.com/downloads/linux

Docker

一鍵指令安裝 Docker

sudo apt-get update -y && \
sudo apt-get install ca-certificates curl && \
sudo install -m 0755 -d /etc/apt/keyrings && \
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
sudo chmod a+r /etc/apt/keyrings/docker.asc && \
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt-get update -y && \
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

參考:

https://docs.docker.com/engine/install/debian/

kind & K8s

安裝 kind

export BIN_PATH=/usr/local/bin

export KIND_VER=0.26.0
export CLOUD_PROVIDER_KIND_VER=0.5.0

curl -Lo ${BIN_PATH}/kind https://kind.sigs.k8s.io/dl/v${KIND_VER}/kind-linux-arm64 && chmod +x ${BIN_PATH}/kind
curl -Lo /tmp/cloud-provider-kind.tgz https://github.com/kubernetes-sigs/cloud-provider-kind/releases/download/v${CLOUD_PROVIDER_KIND_VER}/cloud-provider-kind_${CLOUD_PROVIDER_KIND_VER}_linux_arm64.tar.gz && tar zxvf /tmp/cloud-provider-kind.tgz -C ${BIN_PATH} cloud-provider-kind
curl -Lo ${BIN_PATH}/kubectl "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl" && chmod +x ${BIN_PATH}/kubectl

參考:

https://github.com/kalug/k8s-demo/blob/main/kind-starting/1-create-cluster.md

ollama 與 LLM

安裝 ollama

curl -fsSL https://ollama.com/install.sh | sh

參考:

https://ollama.com/download/linux

下載模型

ollama pull deepseek-r1:1.5b

註:gemma:2b 手機記憶體不足,跑不動,
故改用 deepseek-r1:1.5b

會有通知提示是否允許 port 11434 開啟,因爲安全原因,只會開放對應到本機上
按允許即可

你可以用相關聊天 WebUI,我這邊直接用 App 解決

下載 Maid:

https://play.google.com/store/apps/details?id=com.danemadsen.maid

進入 App 的設定,
設定來源為 ollama
網址: localhost:11434

按下確定,就可以開始聊天了

參考資料

https://ivonblog.com/posts/ollama-android-termux/
https://www.youtube.com/watch?v=UT8Q_V82boM

[DevOps] 用 Ansible 來控制 IBM AIX

在現在的 Web 基礎架構中,自動化管理是提升維運效率的不可或缺的一環。透過 Ansible,企業能夠執行自動化部署、設定檔管理及例行維護,提高作業效率,減少人為錯誤,從而達成更高的系統穩定性和靈活性。嗯,到這裡你可能都聽過,也甚至使用過,畢竟每次談論 DevOps 都是在一些比較現代的 Linux 系統中實現。
那麼 IBM AIX 呢?IBM AIX 是基於 UNIX 的高效能作業系統,專為企業伺服器和關鍵任務設計。具備卓越的穩定性、安全性與可擴展性。就是因為他只讓開發者安裝必要的套件,所以穩定。😂
就讓我們來看看 IBM AIX 要如何串接 Ansible 達成自動化維運吧!

IBM AIX 做為目標機 (Managed node) 環境準備

因為 IBM AIX 於 1986 年推出,基於 UNIX System V 發展,擁有專屬管理工具,如 PowerVM。
Linux 於 1991 年推出,稱為 Unix-Like (類 UNIX) 系統,其開源性與靈活性在各領域廣泛應用。
AIX 的歷史發展是比 Linux 還要早的大型主機,跟 Linux 有不少的差異,
例如 AIX 預設是 KornShell (ksh) 而不是 bash。
所以執行指令上也需要注意

AIX 使用的 CPU 架構也不同,是 PowerPC(非 Apple 早期使用的 PowerPC),也不是 Linux 常用的 x86 架構,故很多編譯都要另外處理。

使用 ansible 的 ssh 指令雖然可以操作,但只能使用 ansible.builtin.raw 模組,
會有大多數的元件無法使用,所以我們需要安裝 python 3 來完整支援 ansible,等下會介紹到。

另外,如果你要使用到 ansible.posix.synchronize 這個模組,
因為 ansible.posix.synchronize 背後是使用 rsync 這個指令,這個指令在 AIX 上預設也是沒有安裝的,後面一步一步詳述。

註1:ansible 目標機 (Managed node) 最低要求需要一個 python
註2:歸功於 ansible.builtin.raw 模組,ansible 可以透過 ssh 來自動化操控網通設備

在 AIX 安裝 python

因為 AIX 本身沒有預裝套件管理程式,他有一個自己的套件程式叫做 installp
我找到 aixtools 這個網站,他有搜集各種 AIX 常用的套件,
而且是原生 *.I 的檔案,而不是一堆 rpm,
因為 AIX 本身也沒有類似 yum 的程式,光用 rpm 的安裝方式,它沒辦法幫我們處理一大堆 dependency 的問題,故目前可行的做法是還是用 installp 指令。
當然另外一條思路是安裝 dnf 這種套件管理程式,安裝較耗空間,這個就留給大家測試了。

下載來源:
http://www.aixtools.net/index.php/python3
(後期測試該網站壞掉,不過截稿時檔案還能下載,有需要的朋友請趕快留檔。)

檔案下載:
http://download.aixtools.net/tools/aixtools.python.py39.3.9.10.0.I

檔名:aixtools.python.py39.3.9.10.0.I

安裝指令

installp -acXd . aixtools.python.py39

(該指令需要 root 權限)

執行大概會像這樣

+-----------------------------------------------------------------------------+
                    Pre-installation Verification...
+-----------------------------------------------------------------------------+
Verifying selections...done
Verifying requisites...done
Results...

SUCCESSES
---------
  Filesets listed in this section passed pre-installation verification
  and will be installed.

  Selected Filesets
  -----------------
  aixtools.python.py39.adt 3.9.10.0            # python py39 ADT files
  aixtools.python.py39.man.en_US 3.9.10.0      # python py39 man pages
  aixtools.python.py39.rte 3.9.10.0            # python py39 23-Feb-2022

  << End of Success Section >>

+-----------------------------------------------------------------------------+
                   BUILDDATE Verification ...
+-----------------------------------------------------------------------------+
Verifying build dates...done
FILESET STATISTICS
------------------
    3  Selected to be installed, of which:
        3  Passed pre-installation verification
  ----
    3  Total to be installed

+-----------------------------------------------------------------------------+
                         Installing Software...
+-----------------------------------------------------------------------------+

installp:  APPLYING software for:
        aixtools.python.py39.rte 3.9.10.0
        aixtools.python.py39.man.en_US 3.9.10.0
        aixtools.python.py39.adt 3.9.10.0

Finished processing all filesets.  (Total time:  32 secs).

+-----------------------------------------------------------------------------+
                                Summaries:
+-----------------------------------------------------------------------------+

Installation Summary
--------------------
Name                        Level           Part        Event       Result
-------------------------------------------------------------------------------
aixtools.python.py39.rte    3.9.10.0         USR         APPLY       SUCCESS
aixtools.python.py39.man.en 3.9.10.0         USR         APPLY       SUCCESS
aixtools.python.py39.adt    3.9.10.0         USR         APPLY       SUCCESS

有看到 SUCCESS 就代表安裝成功了
這個套件預設會裝在 /opt/bin/python3.9

測試 python

跟之前一樣,我們可以嘗試印出 python 版本

/opt/bin/python3.9 --version

如果沒問題的話,會印出 python 版本

AIX 安裝 rsync

一樣是從 aixtools 下載

下載來源:
http://www.aixtools.net/index.php/rsync
(後期測試該網站壞掉,不過截稿時檔案還能下載,有需要的朋友請趕快留檔。)

檔案下載:
http://download.aixtools.net/tools/aixtools.samba.rsync.3.1.3.0.I

檔名: aixtools.samba.rsync.3.1.3.0.I

安裝指令

installp -acXvd . aixtools.samba.rsync

(該指令需要 root 權限)

執行大概會像這樣

+-----------------------------------------------------------------------------+
                    Pre-installation Verification...
+-----------------------------------------------------------------------------+
Verifying selections...done
Verifying requisites...done
Results...

SUCCESSES
---------
  Filesets listed in this section passed pre-installation verification
  and will be installed.

  Selected Filesets
  -----------------
  aixtools.samba.rsync.man.en_US 3.1.3.0      # samba rsync man pages
  aixtools.samba.rsync.rte 3.1.3.0            # samba rsync 06-Feb-2020

  << End of Success Section >>

+-----------------------------------------------------------------------------+
                   BUILDDATE Verification ...
+-----------------------------------------------------------------------------+
Verifying build dates...done
FILESET STATISTICS
------------------
    2  Selected to be installed, of which:
        2  Passed pre-installation verification
  ----
    2  Total to be installed

+-----------------------------------------------------------------------------+
                         Installing Software...
+-----------------------------------------------------------------------------+

installp:  APPLYING software for:
        aixtools.samba.rsync.rte 3.1.3.0
        aixtools.samba.rsync.man.en_US 3.1.3.0

Finished processing all filesets.  (Total time:  1 secs).

+-----------------------------------------------------------------------------+
                                Summaries:
+-----------------------------------------------------------------------------+

Installation Summary
--------------------
Name                        Level           Part        Event       Result
-------------------------------------------------------------------------------
aixtools.samba.rsync.rte    3.1.3.0         USR         APPLY       SUCCESS
aixtools.samba.rsync.man.en 3.1.3.0         USR         APPLY       SUCCESS

有看到 SUCCESS 就代表安裝成功了

預設會安裝路徑在 /opt/bin/rsync

測試 rsync

可以直接執行 /opt/bin/rsync 來測試是否安裝成功
成功會看到使用說明

AIX 安裝 OpenSSL 與 OpenSSH

後來發現,AIX 上面的 ssh 太舊,
新版的加密演算法與 ciphers 不支援,所以著手更新 ssh。
這段特別把 OpenSSL 與 OpenSSH 寫在一起,就是因為 OpenSSH 相依 OpenSSL,
所以要裝一個相配合的版本。

安裝 OpenSSL

這裡從 IBM 官網來下載

安裝文件
https://www.ibm.com/support/pages/downloading-and-installing-or-upgrading-openssl-and-openssh

下載來源
https://www.ibm.com/resources/mrs/assets?source=aixbp&S_PKG=openssl
(該網站需要登入,但帳號可以免費註冊,註冊後登入就可下載)

請根據你對應的 AIX 版本來選擇

這邊用
OpenSSL 1.1.1 for AIX 6.1, 7.1, 7.2 & 7.3
VRMF: 1.1.2.2400 (1.1.1x with no weak ciphers support)
做為範例

檔名:openssl-1.1.2.2400.tar.Z

首先我們把它解壓縮
(註:這邊雖然也是 tar 指令,但跟 Linux 的指令用法不同)

zcat openssl-1.1.2.2400.tar.Z | tar -xvf -

執行結果

x openssl-1.1.2.2400
x openssl-1.1.2.2400/openssl.base, 70546432 bytes, 137786 media blocks.
x openssl-1.1.2.2400/openssl.license, 31744 bytes, 62 media blocks.
x openssl-1.1.2.2400/openssl.man.en_US, 5326848 bytes, 10404 media blocks.

得到 openssl-1.1.2.2400 資料夾

我們切換資料夾,用 installp 來安裝

cd openssl-1.1.2.2400
installp -qaXFY -d . openssl.base openssl.license openssl.man.en_US

執行結果

+-----------------------------------------------------------------------------+
                    Pre-installation Verification...
+-----------------------------------------------------------------------------+
Verifying selections...done
Verifying requisites...done
Results...

SUCCESSES
---------
  Filesets listed in this section passed pre-installation verification
  and will be installed.

  Selected Filesets
  -----------------
  openssl.base 1.1.2.2400                     # Open Secure Socket Layer
  openssl.license 1.1.2.2400                  # Open Secure Socket License
  openssl.man.en_US 1.1.2.2400                # Open Secure Socket Layer

  << End of Success Section >>

+-----------------------------------------------------------------------------+
                   BUILDDATE Verification ...
+-----------------------------------------------------------------------------+
Verifying build dates...done
FILESET STATISTICS
------------------
    3  Selected to be installed, of which:
        3  Passed pre-installation verification
  ----
    3  Total to be installed

+-----------------------------------------------------------------------------+
                         Installing Software...
+-----------------------------------------------------------------------------+

installp:  APPLYING software for:
        openssl.man.en_US 1.1.2.2400

. . . . . << Copyright notice for openssl.man.en_US >> . . . . . . .
 Licensed Materials - Property of IBM

 5765G6281
   Copyright International Business Machines Corp. 2011, 2024.

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.
. . . . . << End of copyright notice for openssl.man.en_US >>. . . .

Filesets processed:  1 of 3  (Total time:  32 secs).

installp:  APPLYING software for:
        openssl.license 1.1.2.2400

. . . . . << Copyright notice for openssl.license >> . . . . . . .
 Licensed Materials - Property of IBM

 5765G6281
   Copyright International Business Machines Corp. 2011, 2024.

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.
. . . . . << End of copyright notice for openssl.license >>. . . .

Filesets processed:  2 of 3  (Total time:  32 secs).

installp:  APPLYING software for:
        openssl.base 1.1.2.2400

. . . . . << Copyright notice for openssl.base >> . . . . . . .
 Licensed Materials - Property of IBM

 5765G6281
   Copyright International Business Machines Corp. 2011, 2024.
   Copyright Baltimore Technologies Ltd. 2004.
   Copyright KISA (Korea Information Security Agency), 2007.
   Copyright Ben Laurie ([email protected]), 2008.
   Copyright Richard Levitte <[email protected]), 2004.
   Copyright The OpenSSL Project. 1998-2008
   Copyright The OpenTSA Project. 2002
   Copyright Andy Polyakov <[email protected]>, 2008
   Copyright Sun Microsystems, Inc. 2002.

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.
. . . . . << End of copyright notice for openssl.base >>. . . .

Successfully updated the Kernel Authorization Table.
Successfully updated the Kernel Role Table.
Successfully updated the Kernel Command Table.
Successfully updated the Kernel Device Table.
Successfully updated the Kernel Object Domain Table.
Successfully updated the Kernel Domains Table.
Successfully updated the Kernel Authorization Table.
Successfully updated the Kernel Role Table.
Successfully updated the Kernel Command Table.
Successfully updated the Kernel Device Table.
Successfully updated the Kernel Object Domain Table.
Successfully updated the Kernel Domains Table.
Finished processing all filesets.  (Total time:  40 secs).

+-----------------------------------------------------------------------------+
                                Summaries:
+-----------------------------------------------------------------------------+

Installation Summary
--------------------
Name                        Level           Part        Event       Result
-------------------------------------------------------------------------------
openssl.man.en_US           1.1.2.2400      USR         APPLY       SUCCESS
openssl.license             1.1.2.2400      USR         APPLY       SUCCESS
openssl.base                1.1.2.2400      USR         APPLY       SUCCESS
openssl.base                1.1.2.2400      ROOT        APPLY       SUCCESS

接下來接續安裝 OpenSSH

安裝 OpenSSH

一樣是從 IBM 網站下載

下載位置
https://www.ibm.com/resources/mrs/assets?source=aixbp&S_PKG=openssh
(該網站需要登入,但帳號可以免費註冊,註冊後登入就可下載)

找到跟 OpenSSL 對應的版本
OpenSSH 9.2 (compiled with Openssl 1.1.2)
VRMF: 9.2.112.2400
來下載

檔名:OpenSSH_9.2.112.2400.tar.Z

一樣用 tar 指令解壓縮

zcat OpenSSH_9.2.112.2400.tar.Z | tar -xvf -

(註:這邊雖然也是 tar 指令,但跟 Linux 的指令用法不同)

得到 OpenSSH_9.2.112.2400 資料夾

安裝

cd OpenSSH_9.2.112.2400
installp -qaXFY -d . openssh.base openssh.license openssh.man.en_US openssh.msg.en_US

記錄一下執行結果

+-----------------------------------------------------------------------------+
                    Pre-installation Verification...
+-----------------------------------------------------------------------------+
Verifying selections...done
Verifying requisites...done
Results...

SUCCESSES
---------
  Filesets listed in this section passed pre-installation verification
  and will be installed.

  Selected Filesets
  -----------------
  openssh.base.client 9.2.112.2400            # Open Secure Shell Commands
  openssh.base.server 9.2.112.2400            # Open Secure Shell Server
  openssh.license 9.2.112.2400                # Open Secure Shell License
  openssh.man.en_US 9.2.112.2400              # Open Secure Shell Documentat...
  openssh.msg.en_US 9.2.112.2400              # Open Secure Shell Messages -...

  << End of Success Section >>

+-----------------------------------------------------------------------------+
                   BUILDDATE Verification ...
+-----------------------------------------------------------------------------+
Verifying build dates...done
FILESET STATISTICS
------------------
    5  Selected to be installed, of which:
        5  Passed pre-installation verification
  ----
    5  Total to be installed

+-----------------------------------------------------------------------------+
                         Installing Software...
+-----------------------------------------------------------------------------+

installp:  APPLYING software for:
        openssh.license 9.2.112.2400

. . . . . << Copyright notice for openssh.license >> . . . . . . .
 Licensed Materials - Property of IBM

 5765E6111
   Copyright International Business Machines Corp. 2001, 2024.

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.
. . . . . << End of copyright notice for openssh.license >>. . . .

Filesets processed:  1 of 5  (Total time:  1 secs).

installp:  APPLYING software for:
        openssh.base.server 9.2.112.2400
        openssh.base.client 9.2.112.2400

. . . . . << Copyright notice for openssh.base >> . . . . . . .
 Licensed Materials - Property of IBM

 5765E6111
   Copyright International Business Machines Corp. 2011, 2024.
   Copyright Per Allansson, 2001.
   Copyright AppGate Network Security AB, 2004-2009.
   Copyright Gary S. Brown, 1986.
   Copyright The Regents of the University of California, 1983, 1990, 1992, 1993, 1995.
   Copyright Aaron Campbell. 1999
   Copyright CORE SDI S.A., Buenos Aires, Argentina. 1998
   Copyright Gert Doering, 2001.
   Copyright Jason Downs, 1996.
   Copyright Markus Friedl. 1999, 2000, 2001, 2002
   Copyright Free Software Foundation, Inc., 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002.
   Copyright Dr Brian Gladman <[email protected]>, Worcester, UK, 2001
   Copyright g10 Code GmbH, 2006, 2007.
   Copyright Wesley Griffin, 2003.
   Copyright Andreas Jellinghaus, 2006.
   Copyright Daniel Kouril, 2002.
   Copyright Ben Lindstrom, 2000, 2001, 2003.
   Copyright Andre Lucas, 2000.
   Copyright David Mazieres <[email protected]>  1995, 1996
   Copyright Damien Miller. 1999-2003
   Copyright Massachusetts Institute of Technology, 1987 - 2001.
   Copyright The NetBSD Foundation, Inc., 1997, 1998.
   Copyright Nils Nordman, 2002.
   Copyright The OpenBSD project, 2004.
   Copyright Niels Provos. 1995
   Copyright Theo de Raadt. 1999
   Copyright Tim Rice, 2002.
   Copyright Jakob Schlyter, 2003.
   Copyright Dug Song. 1995
   Copyright Kevin Steves. 1995
   Copyright Peter Stuge <[email protected]>, 2003
   Copyright Todd C. Miller, 1998.
   Copyright Darren Tucker 2004.
   Copyright Simon Wilkinson, 2001, 2003.
   Copyright Tatu Ylonen <[email protected]>, Espoo, Finland, 1995

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.

 Licensed Materials - Property of IBM

 5765E6111
   Copyright International Business Machines Corp. 2011, 2024.
   Copyright Per Allansson, 2001.
   Copyright Gary S. Brown, 1986.
   Copyright The Regents of the University of California, 1983, 1990, 1992, 1993, 1995.
   Copyright Aaron Campbell. 1999
   Copyright CORE SDI S.A., Buenos Aires, Argentina. 1998
   Copyright Gert Doering, 2001.
   Copyright Jason Downs, 1996.
   Copyright Markus Friedl. 1999, 2000, 2001, 2002
   Copyright Free Software Foundation, Inc., 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002.
   Copyright Dr Brian Gladman <[email protected]>, Worcester, UK, 2001
   Copyright Wesley Griffin, 2003.
   Copyright Daniel Kouril, 2002.
   Copyright Ben Lindstrom, 2000, 2001, 2003.
   Copyright Andre Lucas, 2000.
   Copyright David Mazieres <[email protected]>  1995, 1996
   Copyright Damien Miller. 1999-2003
   Copyright Massachusetts Institute of Technology, 1987 - 2001.
   Copyright Nils Nordman, 2002.
   Copyright The OpenBSD project, 2004.
   Copyright Niels Provos. 1995
   Copyright Theo de Raadt. 1999
   Copyright Tim Rice, 2002.
   Copyright Jakob Schlyter, 2003.
   Copyright Dug Song. 1995
   Copyright Kevin Steves. 1995
   Copyright Peter Stuge <[email protected]>, 2003
   Copyright Todd C. Miller, 1998.
   Copyright Darren Tucker 2004.
   Copyright Simon Wilkinson, 2001, 2003.
   Copyright Tatu Ylonen <[email protected]>, Espoo, Finland, 1995

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.
. . . . . << End of copyright notice for openssh.base >>. . . .

Generating rsa key...
Generating dsa key...
Generating ecdsa key...
Generating ed25519 key...
Successfully updated the Kernel Authorization Table.
Successfully updated the Kernel Role Table.
Successfully updated the Kernel Command Table.
Successfully updated the Kernel Device Table.
Successfully updated the Kernel Object Domain Table.
Successfully updated the Kernel Domains Table.
Successfully updated the Kernel Authorization Table.
Successfully updated the Kernel Role Table.
Successfully updated the Kernel Command Table.
Successfully updated the Kernel Device Table.
Successfully updated the Kernel Object Domain Table.
Successfully updated the Kernel Domains Table.
0513-071 The sshd Subsystem has been added.
0513-004 The Subsystem or Group, ssh, is currently inoperative.
0513-059 The sshd Subsystem has been started. Subsystem PID is 22413564.
Successfully updated the Kernel Authorization Table.
Successfully updated the Kernel Role Table.
Successfully updated the Kernel Command Table.
Successfully updated the Kernel Device Table.
Successfully updated the Kernel Object Domain Table.
Successfully updated the Kernel Domains Table.
Successfully updated the Kernel Authorization Table.
Successfully updated the Kernel Role Table.
Successfully updated the Kernel Command Table.
Successfully updated the Kernel Device Table.
Successfully updated the Kernel Object Domain Table.
Successfully updated the Kernel Domains Table.
Filesets processed:  3 of 5  (Total time:  7 secs).

installp:  APPLYING software for:
        openssh.msg.en_US 9.2.112.2400

. . . . . << Copyright notice for openssh.msg.en_US >> . . . . . . .
 Licensed Materials - Property of IBM

 5765E6111
   Copyright International Business Machines Corp. 2011, 2024.

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.
. . . . . << End of copyright notice for openssh.msg.en_US >>. . . .

Filesets processed:  4 of 5  (Total time:  7 secs).

installp:  APPLYING software for:
        openssh.man.en_US 9.2.112.2400

. . . . . << Copyright notice for openssh.man.en_US >> . . . . . . .
 Licensed Materials - Property of IBM

 5765E6111
   Copyright International Business Machines Corp. 2011, 2024.
   Copyright Aaron Campbell. 1999
   Copyright Markus Friedl. 1999, 2000, 2001, 2002
   Copyright David Mazieres <[email protected]>  1995, 1996
   Copyright Damien Miller. 2001, 2002
   Copyright Theo de Raadt. 1999
   Copyright Tatu Ylonen <[email protected]>, Espoo, Finland, 1995

 All rights reserved.
 US Government Users Restricted Rights - Use, duplication or disclosure
 restricted by GSA ADP Schedule Contract with IBM Corp.
. . . . . << End of copyright notice for openssh.man.en_US >>. . . .

Finished processing all filesets.  (Total time:  8 secs).

+-----------------------------------------------------------------------------+
                                Summaries:
+-----------------------------------------------------------------------------+

Installation Summary
--------------------
Name                        Level           Part        Event       Result
-------------------------------------------------------------------------------
openssh.license             9.2.112.2400    USR         APPLY       SUCCESS
openssh.base.server         9.2.112.2400    USR         APPLY       SUCCESS
openssh.base.client         9.2.112.2400    USR         APPLY       SUCCESS
openssh.base.server         9.2.112.2400    ROOT        APPLY       SUCCESS
openssh.base.client         9.2.112.2400    ROOT        APPLY       SUCCESS
openssh.msg.en_US           9.2.112.2400    USR         APPLY       SUCCESS
openssh.man.en_US           9.2.112.2400    USR         APPLY       SUCCESS

測試 OpenSSH

我們用以下指令來測試 sshd 服務

/usr/sbin/sshd -d -e

取得 sshd 的狀態

lssrc -s sshd

如有必要,重啟 sshd 服務

stopsrc -s sshd;startsrc -s sshd

安裝 Bash

恩對,你沒看錯 bash 也是可以另外裝的
一樣從 aixtools 網站下載

下載來源
http://www.aixtools.net/index.php/bash
(後期測試該網站壞掉,不過截稿時檔案還能下載,有需要的朋友請趕快留檔。)

檔案
http://download.aixtools.net/tools/gnu/aixtools.gnu.bash.5.0.18.0.I

檔名:aixtools.gnu.bash.5.0.18.0.I

安裝指令

installp -acXvd . aixtools.gnu.bash

記錄一下執行結果

+-----------------------------------------------------------------------------+
                    Pre-installation Verification...
+-----------------------------------------------------------------------------+
Verifying selections...done
Verifying requisites...done
Results...

SUCCESSES
---------
  Filesets listed in this section passed pre-installation verification
  and will be installed.

  Selected Filesets
  -----------------
  aixtools.gnu.bash.man.en_US 5.0.18.0        # gnu bash man pages
  aixtools.gnu.bash.rte 5.0.18.0              # gnu bash 15-Oct-2020
  aixtools.gnu.bash.share 5.0.18.0            # gnu bash universal files

  << End of Success Section >>

+-----------------------------------------------------------------------------+
                   BUILDDATE Verification ...
+-----------------------------------------------------------------------------+
Verifying build dates...done
FILESET STATISTICS
------------------
    3  Selected to be installed, of which:
        3  Passed pre-installation verification
  ----
    3  Total to be installed

+-----------------------------------------------------------------------------+
                         Installing Software...
+-----------------------------------------------------------------------------+

installp:  APPLYING software for:
        aixtools.gnu.bash.share 5.0.18.0
        aixtools.gnu.bash.rte 5.0.18.0
        aixtools.gnu.bash.man.en_US 5.0.18.0

Finished processing all filesets.  (Total time:  1 secs).

+-----------------------------------------------------------------------------+
                                Summaries:
+-----------------------------------------------------------------------------+

Installation Summary
--------------------
Name                        Level           Part        Event       Result
-------------------------------------------------------------------------------
aixtools.gnu.bash.share     5.0.18.0        USR         APPLY       SUCCESS
aixtools.gnu.bash.rte       5.0.18.0        USR         APPLY       SUCCESS
aixtools.gnu.bash.man.en_US 5.0.18.0        USR         APPLY       SUCCESS

如找不到 bash 可以用以下指令尋找

find / -name bash 2>/dev/null

這樣我們就萬事具備了

撰寫 ansible playbook 腳本

設定完成後,終於可以來寫 ansible playbook 了

這邊給一個範例,印出 oslevel,查看系統版本
在 Control node 這邊建立 playbook.ymlinventory 二個檔案

playbook.yml

- name: print os version
  hosts: aix
  gather_facts: no
  tasks:
    - name: print os version
      ansible.builtin.command: "oslevel"
      register: out
    - name: output
      ansible.builtin.debug:
        msg: 
          - "{{ out.stdout_lines }}"

inventory

[aix]
192.168.1.2 ansible_user=myuser ansible_ssh_private_key_file=./server_key ansible_connection=ssh ansible_port=22 ansible_python_interpreter=/opt/bin/python3.9     

這邊假設 AIX 的主機是 192.168.1.2 你可以改成你的主機

然後執行 ansible playbook

export ANSIBLE_HOST_KEY_CHECKING=False && \
ansible-playbook -vvv -i inventory playbook.yml'

解釋一下指令,因為 python 不是預設安裝路徑,需給定 python 路徑,這是重點之一。

ansible_python_interpreter=/opt/bin/python3.9

這邊提供其他範例,這個是利用 ansible.posix.synchronize 模組來複製檔案

- name: copy files
  hosts: aix
  gather_facts: no
  tasks:
    - name: copy files
      ansible.posix.synchronize:
        src: /local/dir
        dest: /remote/dir
        recursive: true
        checksum: true
        rsync_path: /opt/bin/rsync
        rsync_opts:
        - "--no-motd"
        - "--exclude=.git"

這邊一樣因為 rsync 不是預設路徑,使用 rsync_path 參數來指定遠端 rsync 的路徑

rsync_path: /opt/bin/rsync

這邊有特別忽略 .git 資料夾,避免不該傳上去的檔案傳上去,
其他做法就跟使用 linux 差不多了

ansible 模組的差異

這邊列出一些常用的 ansible 指令

  • ansible.builtin.command
  • ansible.builtin.shell
  • ansible.builtin.copy
  • ansible.posix.synchronize

根據官方文件,
ansible.builtin.command 預設會過濾一些特殊字元(例如:大於、小於、星號…等),較安全
如果有特殊符號需求,請改用 ansible.builtin.shell

ansible.builtin.copyansible.posix.synchronize 雖然都是複製檔案,但也有點不同。
ansible.builtin.copy 是基於 scp 指令來實作的,它是一個一個檔案複製(包含 checksum)
ansible.posix.synchronize 是基於 rsync 指令來實作的,因為連線有壓縮,複製速度較快

同樣複製多層資料夾,約 500MB 的檔案,實際使用差異可以到 3 小時的差別。

祝設定愉快