用 OpenWRT 設定 VLAN 進行網路隔離 – 以 ASUS RT-AC58U 為例

在當今的數位時代,設定一個安全且專屬的家用網路變得越來越重要。透過使用開源軟體 OpenWRT,我們可以輕鬆地為家中的網路設備建立專屬的虛擬局域網(VLAN),以提供更高程度的安全性與隔離性。

在本篇文章中,我們將透過一個實作範例,詳細介紹如何設定 OpenWRT,將一台路由器切割成兩個 VLAN 區域,並讓這兩個區域互不影響。每個 VLAN 區域都會分到不同的 IP 網段,而且皆有對應的 Wifi 無線網路(2.4G 和 5G)可供使用。最後,我們也會確保每一個 VLAN 區域都能夠對外上網。

這篇文章適合具有基礎網路知識,且對於設定 VLAN 有興趣的讀者。我們將會帶領你逐步了解 VLAN 的基本概念,並實際操作如何在 OpenWRT 中進行設定。

我們用一些筆者自身有用過的機種來做設定範例,詳細探討其中設定值的差異,供大家學習。希望透過本篇文章,你可以瞭解如何透過 OpenWRT 來打造一個安全、專屬的家用網路環境。

那麼,就讓我們開始吧!


設定 VLAN 進行網路隔離 系列文:


範例環境說明

假設你的對外網路有一個光世代的盒子(小烏龜),
你透過 DHCP 配發 IP 的方式來上網

我們把一台路由器分成二個 VLAN 區域:

  • VLAN100 網段 192.168.10.0/24
  • VLAN200 網段 192.168.20.0/24

接孔對應:

  • 指定第一埠為 VLAN100
  • 指定第二埠為 VLAN200

示意圖如下

等效網路拓墣如下

等於是你在小烏龜這邊裝了一台 Switch,
底下各裝一台 Wifi Router 路由器,各自設定獨立的 IP 位址與網段,
實現完全隔離的網路。

而我們全部都在一台路由器就完成了這件事情,
一般的家用路由器沒辦法做到這樣的功能,
但 OpenWRT 可以,這也是它厲害的地方。

我們把範例情境做複雜一點,
假設我們家用區域很大,是個三層樓透天厝,ADSL 主線在一樓,
想要二樓與三樓都延伸以上雙 VLAN 的情境。要怎麼做?

  • 指定第四埠為 VLAN100VLAN200 的 trunk port
  • 多新增一台設定類似的路由器,我們暫稱它為子機
  • 多拉一條網路線,來連接母機與子機

示意圖變成如下:

等效網路拓墣如下

原本我們應該需要二台路由器、二條網路線、二台 Switch hub 來延伸這個完全隔離的網路,歸功於 VLAN 與 trunk port 的功能,我們使用二台路由器,一條網路線就可以做到一樣的事情了。


我們用華碩 ASUS RT-AC58U 來實作這個範例,
有因為硬體特性與原廠驅動設計問題,而步驟有一點點不同,
尤其這台較特別,需要另外說說

這次使用的版本為 OpenWrt 22.03.5 20134-515225c1e 供參考。

設定步驟

Step0. 觀察原有設定值

在開始設定之前,我們先觀察一下原有路由器的設定值,
據經驗,這部分不同機器還是會有些微差異,也記錄一下。

RT-AC58U 原有的設定值是這樣子

Network > Interface 頁面,Interfaces 頁籤

Network > Interface 頁面,Devices 頁籤

Network > Switch 的頁面

Network > Wireless 頁面

快速整理一下:

  • 有 Network > Switch 的頁面
  • 只有 eth1, eth2 二個 eth
  • 有一個 Bridge 介面
    • 預設綁定 VLAN 1 給 LAN 使用
    • 預設綁定 VLAN 2 給 WAN 使用
      (這段網頁管理介面沒有顯示,預設是隱藏的,這也是這台特別之處)

Step1. 修改原有 bridge 介面

到 Network > Switch 頁面

設定 VLAN ID,剛剛有提到這台 VLAN 2 是保留給 wan 使用的,而且它 預設在網頁管理介面是隱藏的
這部分我們等一下再來處理

VLAN ID CPU (eth0) LAN 1 LAN 2 LAN 3 LAN 4
100 t u off u t
200 t off u off t

到這邊只能按下「Save」存檔,不可按 Save & Apply,不然 會直接斷線!
然後到 Network > Interfaces 頁面,選擇預設的 lan 裝置

General Settings 部分

  • Protocol: Static address
  • IPv4 address: 192.168.10.1 (Edit)
  • IPv4 netmask: 255.255.255.0
  • Device: Switch VLAN: "eth0.100"

記得將 Device 調整為 Software VLAN: "eth0.100"
不然會整個連管理介面都連不上。

按 Save and Apply 套用設定

因為有修改路由器 IP,這個時候要更換瀏覽的網址。
http://192.168.1.1/ 要換成 http://192.168.10.1/ 然後重新登入。

Step 3. 設定第二個 VLAN

我們在 Network > Interfaces 的頁面中

按「Add New Interface…」按鈕,新增第二個介面

  • Name: lan200
  • Protocol: Static address
  • Device: Software VLAN: "eth0.200"

然後接續設定設定值

General Settings 區域

  • Protocol: Static address
  • IPv4 address: 192.168.20.1
  • IPv4 netmask: 255.255.255.0
  • Device: Switch VLAN: "eth0.200"

到 DHCP Server 頁籤,設定 DHCP 伺服器
(只有主要的那台路由器需要開,其他台必須關掉此設定值)

原本會顯示 No DHCP Server configured for this interface

按「Set up DHCP Server」按鈕啟動 DHCP 伺服器

DHCP Server > General Setup

  • Start: 2
  • Limit: 254

這邊設定的是 DHCP 伺服器發放 IP 位址的開始與結束區段,依需求修改
預設給 100 – 150,它可以是 2 – 254

Step4. 防火牆設定

(主要那台路由器需要設定,子機可以不用設定)

到 Network > Firewall 頁面

來到 Firewall – Zone Settings 頁面, General Settings 頁籤

依樣畫葫蘆,按 Add 新增一個區域

  • Name: lan200 (名字可以自己取)

  • Input: accept

  • Output: accept

  • Forward: accept

  • Covered networks: lan200 (Switch VLAN: "eth0.200")

Covered networks 為應用網路區域,這邊選擇前面設定的 lan200

  • Allow forward to destination zones:
    wanwan (Ethernet Adapter: "eth1") wan6 (Ethernet Adapter: "eth1")

轉送 (forward) 區域,這邊選擇 wan, wan6 即可。

第二個 VLAN 能不能正確上網就看這個設定了。

設定 Wi-Fi

這邊設定 Wi-Fi 步驟就相對簡單

這台它有二個頻段 2.4GHz 與 5GHz

分別設定為

  • SSID: OpenWRT_lan100

  • 設定您的密碼

  • 對應 VLAN100

  • SSID: OpenWRT_lan200

  • 設定您的密碼

  • 對應 VLAN200

  • SSID: OpenWRT_lan100_5G

  • 設定您的密碼

  • 對應 VLAN100

  • SSID: OpenWRT_lan200_5G

  • 設定您的密碼

  • 對應 VLAN200

這邊應該很直覺,注意不要選錯即可。


Wi-Fi 設定步驟如下:

到 Network > Wireless 頁面

在 radio0.network1 區域,修改原有的 2.4GHz Wi-Fi

General Setup

  • Operating frequency
  • Mode: N
  • Channel: 1 (2412 Mhz)
  • Width: 20 MHz

Interface Configuration
General Setup

  • Mode: Access Point
  • ESSID: OpenWRT_lan100
  • Network: lan (Switch VLAN: "eth0.100")

Interface Configuration
Wireless Security

  • Encryption: WPA2-PSK/WPA3-SAE Mixed Mode (strong security)
  • Key: (Your password)

這邊記得設定你的 Wi-Fi SSID 與密碼,注意不要選錯 vlan 介面


在 radio0.network1 區域,新增一個 2.4GHz 的 Wi-Fi

General Setup

  • Operating frequency
  • Mode: N
  • Channel: 1 (2412 Mhz)
  • Width: 20 MHz

Interface Configuration
General Setup

  • Mode: Access Point
  • ESSID: OpenWRT_lan200
  • Network: lan200 (Switch VLAN: "eth0.200")

Interface Configuration
Wireless Security

  • Encryption: WPA2-PSK/WPA3-SAE Mixed Mode (strong security)
  • Key: (Your password)

這邊記得設定你的 Wi-Fi SSID 與密碼,注意不要選錯 vlan 介面


在 radio1.network1 區域,新增一個 5GHz 的 Wi-Fi

General Setup

  • Operating frequency
  • Mode: AC
  • Channel: 36 (5180 Mhz)
  • Width: 80 MHz

Interface Configuration
General Setup

  • Mode: Access Point
  • ESSID: OpenWRT_lan100_5G
  • Network: lan (Switch VLAN: "eth0.100")

Interface Configuration
Wireless Security

  • Encryption: WPA2-PSK/WPA3-SAE Mixed Mode (strong security)
  • Key: (Your password)

這邊記得設定你的 Wi-Fi SSID 與密碼,注意不要選錯 vlan 介面


在 radio1.network1 區域,新增一個 5GHz 的 Wi-Fi

General Setup

  • Operating frequency
  • Mode: AC
  • Channel: 36 (5180 Mhz)
  • Width: 80 MHz

Interface Configuration
General Setup

  • Mode: Access Point
  • ESSID: OpenWRT_lan200_5G
  • Network: lan200 (Switch VLAN: "eth0.200")

Interface Configuration
Wireless Security

  • Encryption: WPA2-PSK/WPA3-SAE Mixed Mode (strong security)
  • Key: (Your password)

這邊記得設定你的 Wi-Fi SSID 與密碼,注意不要選錯 vlan 介面

Step 5. 修正 WAN 上網介面

這步驟算是 RT-AC58U 特有的步驟,
你會發現整個設定完畢卻無法辦法對外上網,這是正常的
因為剛剛有提到
這台 VLAN 2 是保留給 wan 使用的,而且它 預設在網頁管理介面是隱藏的
所以我們現在要來修正這件事情

使用 ssh 指令連線到路由器:

$ ssh [email protected]

(註:ssh 指令在 Windows powershell 有內建,或者用 putty 都可以)

我們需要直接修改 /etc/config/network 這個檔案

vi /etc/config/network

要比對一下設定檔,如果沒有這個段落的話,要手動加上這段

# this is the VLAN mapping for the internet port
config switch_vlan
    option device 'switch0'
    option vlan '1'
    option vid '2'
    option ports '0t 5'

然後重啟網路服務

$ /etc/init.d/network restart

然後測試,應該就可以連上網路了。

Step6. 連接第二台路由器(子機)

我們來實作示意圖上的第二台路由器

第二台(甚至以上)的路由器,設定跟前面非常類似,
唯有幾個需要確認:

  • DHCP Server 為關
  • 有線網路的對應網段與 VLAN 號碼,都要正確
  • Wi-Fi 帳號、密碼、對應網段與 VLAN 號碼,都要正確

然後將 trunk port 與 trunk port 用網路線,連接在一起,這樣就完成了。


文字介面設定參考

我保留了當初右上角系統透過介面自動產生的指令,供大家參考。
如果需要其他文章的話,才知道大概在講什麼。

基本上它就是用 uci 指令來做操作而已,所屬的設定檔案都已經列在註解上了,

# /etc/config/dhcp
uci del dhcp.lan.ra_slaac
uci set dhcp.lan.start='2'
uci set dhcp.lan.limit='254'
uci set dhcp.lan200=dhcp
uci set dhcp.lan200.interface='lan200'
uci set dhcp.lan200.start='100'
uci set dhcp.lan200.limit='150'
uci set dhcp.lan200.leasetime='12h'
uci set dhcp.lan200.start='2'
uci set dhcp.lan200.limit='254'

# /etc/config/firewall
uci add firewall zone # =cfg0edc81
uci set firewall.@zone[-1].name='lan200'
uci set firewall.@zone[-1].input='ACCEPT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='ACCEPT'
uci add_list firewall.@zone[-1].network='lan200'
uci add firewall forwarding # =cfg0fad58
uci set firewall.@forwarding[-1].src='lan200'
uci set firewall.@forwarding[-1].dest='wan'

# /etc/config/network
uci add network switch_vlan # =cfg0b1ec7
uci set network.@switch_vlan[-1].device='switch0'
uci set network.@switch_vlan[-1].vlan='2'
uci set network.cfg0a1ec7.ports='0t 4 2 1t'
uci set network.cfg0a1ec7.vid='100'
uci set network.@switch_vlan[-1].ports='0t 3 1t'
uci set network.@switch_vlan[-1].vid='200'
uci set network.lan.device='eth0.100'
uci set network.lan.ipaddr='192.168.10.1'
uci set network.lan200=interface
uci set network.lan200.proto='static'
uci set network.lan200.device='eth0.200'
uci set network.lan200.ipaddr='192.168.20.1'
uci set network.lan200.netmask='255.255.255.0'
uci set network.lan200.type='bridge'

# /etc/config/wireless
uci set wireless.radio0.cell_density='0'
uci set wireless.default_radio0.ssid='OpenWrt_lan100'
uci set wireless.default_radio0.encryption='sae-mixed'
uci set wireless.default_radio0.key='YOUR_PASSWD'
uci del wireless.radio0.disabled
uci set wireless.wifinet2=wifi-iface
uci set wireless.wifinet2.device='radio0'
uci set wireless.wifinet2.mode='ap'
uci set wireless.wifinet2.ssid='OpenWrt_lan200'
uci set wireless.wifinet2.encryption='sae-mixed'
uci set wireless.wifinet2.key='YOUR_PASSWD'
uci set wireless.wifinet2.network='lan200'
uci set wireless.radio1.cell_density='0'
uci set wireless.default_radio1.ssid='OpenWrt_lan100_5G'
uci set wireless.default_radio1.encryption='sae-mixed'
uci set wireless.default_radio1.key='YOUR_PASSWD'
uci del wireless.radio1.disabled
uci set wireless.wifinet3=wifi-iface
uci set wireless.wifinet3.device='radio1'
uci set wireless.wifinet3.mode='ap'
uci set wireless.wifinet3.ssid='OpenWrt_lan200_5G'
uci set wireless.wifinet3.encryption='sae-mixed'
uci set wireless.wifinet3.key='YOUR_PASSWD'
uci set wireless.wifinet3.network='lan200'

用 OpenWRT 設定 VLAN 進行網路隔離 – 以 TOTOLINK-X5000R 為例

在當今的數位時代,設定一個安全且專屬的家用網路變得越來越重要。透過使用開源軟體 OpenWRT,我們可以輕鬆地為家中的網路設備建立專屬的虛擬局域網(VLAN),以提供更高程度的安全性與隔離性。

在本篇文章中,我們將透過一個實作範例,詳細介紹如何設定 OpenWRT,將一台路由器切割成兩個 VLAN 區域,並讓這兩個區域互不影響。每個 VLAN 區域都會分到不同的 IP 網段,而且皆有對應的 Wifi 無線網路(2.4G 和 5G)可供使用。最後,我們也會確保每一個 VLAN 區域都能夠對外上網。

這篇文章適合具有基礎網路知識,且對於設定 VLAN 有興趣的讀者。我們將會帶領你逐步了解 VLAN 的基本概念,並實際操作如何在 OpenWRT 中進行設定。

我們用一些筆者自身有用過的機種來做設定範例,詳細探討其中設定值的差異,供大家學習。希望透過本篇文章,你可以瞭解如何透過 OpenWRT 來打造一個安全、專屬的家用網路環境。

那麼,就讓我們開始吧!


設定 VLAN 進行網路隔離 系列文:


範例環境說明

假設你的對外網路有一個光世代的盒子(小烏龜),
你透過 DHCP 配發 IP 的方式來上網

我們把一台路由器分成二個 VLAN 區域:

  • VLAN100 網段 192.168.10.0/24
  • VLAN200 網段 192.168.20.0/24

接孔對應:

  • 指定第一埠為 VLAN100
  • 指定第二埠為 VLAN200

示意圖如下:

等效網路拓墣如下:

等於是你在小烏龜這邊裝了一台 Switch,
底下各裝一台 Wifi Router 路由器,各自設定獨立的 IP 位址與網段,
實現完全隔離的網路。

而我們全部都在一台路由器就完成了這件事情,
一般的家用路由器沒辦法做到這樣的功能,
但 OpenWRT 可以,這也是它厲害的地方。

我們把範例情境做複雜一點,
假設我們家用區域很大,是個三層樓透天厝,ADSL 主線在一樓,
想要二樓與三樓都延伸以上雙 VLAN 的情境。要怎麼做?

  • 指定第四埠為 VLAN100VLAN200 的 trunk port
  • 多新增一台設定類似的路由器,我們暫稱它為子機
  • 多拉一條網路線,來連接母機與子機

示意圖變成如下:

等效網路拓墣如下:

看到這邊大家可能開始霧煞煞,可以先上下對照等效網路拓墣來看,
原本我們應該需要二台路由器、二條網路線、二台 Switch hub 來延伸這個完全隔離的網路,歸功於 VLAN 與 trunk port 的功能,我們使用二台路由器,一條網路線就可以做到一樣的事情了。


以下為設定步驟,這邊使用 TOTOLINK-X5000R 刷入 OpenWRT 來實作以下的步驟,
有可能會因為硬體特性與原廠驅動設計問題,而步驟有一點點不同,
如果有比較特別的部份,可能另外寫一篇文章來解釋。

使用的版本為 OpenWRT 22.03.3 r20028-43d71ad93e 供參考。

設定步驟

Step0. 觀察原有設定值

在開始設定之前,我們先觀察一下原有路由器的設定值,
據經驗,這部分不同機器還是會有些微差異,也記錄一下。

TOTOLINK-X5000R 原有的設定值是這樣子

Network > Interface 頁面,Interfaces 頁籤

Network > Interface 頁面,Devices 頁籤

br-lan 按下 「Configure…」按鈕的內容,

Bridge device: br-lan 視窗,General device options 頁籤

Bridge device: br-lan 視窗,Bridge VLAN filtering 頁籤

也附上 X5000R 的產品背面圖

我們快速整理一下:

  • 沒有 Network > Switch 的頁面
  • eth1, eth2, eth3, eth4, eth5 ,對應到 5 個 Port
  • 有一個 Bridge 介面,
    • 綁定 eth1, eth2, eth3, eth4 為 LAN 的 Interface (介面)
  • 綁定 wan 為 WAN 的 Interface (介面)

接下來我們開始一步一步調整成我們要的範例情境。

Step1. 將原有 bridge 介面改為 VLAN 模式

先到 Network > Interface 頁面,選到 Devices 頁籤,

選到 br-lan 按下 「Configure…」按鈕,

直接到 Bridge VLAN filtering 頁籤,來做 VLAN 設定調整

勾選 Enable VLAN filtering

按下 Add 打入 VLAN ID,(例如:VLAN ID: 100 )
勾選 untagged 就是要應用上去的介面,畫面上會顯示一個 u
如果要走 trunk,再選擇 tagged

如有 CPU 的 Port 一律都選 trunk,這樣介面才看得到

複習一下範例情境:

  • VLAN ID: 100 應用在 Port 1, Port 3
  • VLAN ID: 200 應用在 Port 2
  • Port 4 走 VLAN ID: 100 與 VLAN ID: 200 的 trunk

所以變成這樣

VLAN ID lan1 lan2 lan3 lan4
100 u u t
200 u t

按了存檔 Save 了之後 不要馬上 Apply,不然 會直接斷線!
很重要講三次

到 Network > Interface 頁面 Interfaces 頁籤,

選擇預設的 LAN 按「Edit」按鈕

Interfaces » lan 的 General Settings 頁籤,

將 Device 調整為 Software VLAN: "br-lan.100"

再存檔 Apply

解釋:因為原本的單純的 bridge 介面,改跑成 VLAN 模式了,
以範例來說,原有的介面變成二個軟體 VLAN 介面
但路由器 IP 設定與 DHCP 設定沒有對應綁過去

原本的 bridge 介面不應該掛任何東西才正確

這邊的設定較重要,也是有沒有設定成功的關鍵。

註:先從本身連接的 VLAN 網段開始設定會比較順,
如果做錯了就整台重置吧。

Step2. 更換路由器 IP

為了符合範例的需求,我們更換一下目前連接路由器的 IP,

到 Network > Interface 頁面

General Settings 部分

  • IPv4 address: 192.168.10.1
  • IPv4 netmask: 255.255.255.0

將原本的 192.168.1.1 修改成 192.168.10.1

按 Save and Apply 套用設定,這個時候要更換瀏覽的網址。

http://192.168.1.1/ 要換成 http://192.168.10.1/ 然後重新登入。
這步應該對大家來說不困難。

Step3. 設定第二個 VLAN

我們依樣畫葫蘆在 Network > Interfaces 頁籤中,

按「Add New Interface…」按鈕,新增第二個介面

  • Name: LAN200
  • Protocol: Static address
  • Device: Software VLAN: "br-lan.200"

你就可以接續設定

General Settings

  • IPv4 address: 192.168.20.1
  • IPv4 netmask: 255.255.255.0

到 DHCP Server 頁籤,設定 DHCP 伺服器
(只有主要的那台路由器需要開,其他台必須關掉此設定值)

原本會顯示 No DHCP Server configured for this interface

按「Set up DHCP Server」按鈕啟動 DHCP 伺服器

DHCP Server > General Setup

  • Start: 2
  • Limit: 254

這邊設定的是 DHCP 伺服器發放 IP 位址的開始與結束區段,依需求修改
預設給 100 – 150,它可以是 2 – 254

還是重申:只有主要的那台路由器需要開,其他台必須關掉此設定值

Step4. 防火牆設定

(主要那台路由器需要設定,子機可以不用設定)

到 Network > Firewall 頁面

來到 Firewall – Zone Settings 頁面, General Settings 頁籤

依樣畫葫蘆,按 Add 新增一個區域

Firewall – Zone Settings 頁面, General Settings 頁籤

  • Name: lan200 (名字可以自己取)

  • Input: accept

  • Output: accept

  • Forward: accept

  • Covered networks: lan200 (Software VLAN: "br-lan.200")

Covered networks 為應用網路區域,這邊選擇前面設定的 lan200

  • Allow forward to destination zones: wan, wan6

轉送 (forward) 區域,這邊選擇 wan, wan6 即可。

第二個 VLAN 能不能正確上網就看這個設定了。

設定 Wi-Fi

這邊設定 Wi-Fi 步驟就相對簡單

這台它有二個頻段 2.4GHz 與 5GHz

分別設定為

  • SSID: OpenWRT_lan100

  • 設定您的密碼

  • 對應 VLAN100

  • SSID: OpenWRT_lan200

  • 設定您的密碼

  • 對應 VLAN200

  • SSID: OpenWRT_lan100_5G

  • 設定您的密碼

  • 對應 VLAN100

  • SSID: OpenWRT_lan200_5G

  • 設定您的密碼

  • 對應 VLAN200

這邊應該很直覺,注意不要選錯即可。

Step5. 連接第二台路由器(子機)

我們來實作示意圖上的第二台路由器

第二台(甚至以上)的路由器,設定跟前面非常類似,
唯有幾個需要確認:

  • DHCP Server 為關
  • 有線網路的對應網段與 VLAN 號碼,都要正確
  • Wi-Fi 帳號、密碼、對應網段與 VLAN 號碼,都要正確

然後將 trunk port 與 trunk port 用網路線,連接在一起,這樣就完成了。


文字介面設定參考

我保留了當初右上角系統透過介面自動產生的指令,供大家參考。
如果需要其他文章的話,才知道大概在講什麼。

基本上它就是用 uci 指令來做操作而已,所屬的設定檔案都已經列在註解上了,
這邊就不多做解釋,圖形介面都設定好了。

# /etc/config/dhcp
uci del dhcp.lan.ra_slaac
# /etc/config/network
uci add network bridge-vlan # =cfg07a1b0
uci set network.@bridge-vlan[-1].device='br-lan'
uci set network.@bridge-vlan[-1].vlan='100'
uci add_list network.@bridge-vlan[-1].ports='lan1'
uci add_list network.@bridge-vlan[-1].ports='lan3'
uci add_list network.@bridge-vlan[-1].ports='lan4:t'
uci add network bridge-vlan # =cfg08a1b0
uci set network.@bridge-vlan[-1].device='br-lan'
uci set network.@bridge-vlan[-1].vlan='200'
uci add_list network.@bridge-vlan[-1].ports='lan2'
uci add_list network.@bridge-vlan[-1].ports='lan4:t'
uci set network.lan.device='br-lan.100'

# /etc/config/dhcp
uci set dhcp.lan.start='2'
uci set dhcp.lan.limit='254'
uci set dhcp.lan200=dhcp
uci set dhcp.lan200.interface='lan200'
uci set dhcp.lan200.start='100'
uci set dhcp.lan200.limit='150'
uci set dhcp.lan200.leasetime='12h'
uci set dhcp.lan200.start='2'
uci set dhcp.lan200.limit='254'
# /etc/config/network
uci set network.lan.ipaddr='192.168.10.1'
uci set network.lan200=interface
uci set network.lan200.proto='static'
uci set network.lan200.device='br-lan.200'
uci set network.lan200.ipaddr='192.168.20.1'
uci set network.lan200.netmask='255.255.255.0'

# /etc/config/firewall
uci add firewall zone # =cfg0edc81
uci set firewall.@zone[-1].name='lan200'
uci set firewall.@zone[-1].input='ACCEPT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='REJECT'
uci add_list firewall.@zone[-1].network='lan200'
uci add firewall forwarding # =cfg0fad58
uci set firewall.@forwarding[-1].src='lan200'
uci set firewall.@forwarding[-1].dest='wan'

# /etc/config/wireless
uci del wireless.radio0.disabled
uci set wireless.wifinet2=wifi-iface
uci set wireless.wifinet2.device='radio0'
uci set wireless.wifinet2.mode='ap'
uci set wireless.wifinet2.ssid='OpenWrt_lan200'
uci set wireless.wifinet2.encryption='sae-mixed'
uci set wireless.wifinet2.key='YOUR_PASSWD'
uci set wireless.wifinet2.network='lan200'
uci set wireless.radio0.cell_density='0'
uci set wireless.default_radio0.ssid='OpenWrt_lan100'
uci set wireless.default_radio0.encryption='sae-mixed'
uci set wireless.default_radio0.key='YOUR_PASSWD'
uci del wireless.radio1.disabled
uci set wireless.wifinet3=wifi-iface
uci set wireless.wifinet3.device='radio1'
uci set wireless.wifinet3.mode='ap'
uci set wireless.wifinet3.ssid='OpenWrt_lan200_5G'
uci set wireless.wifinet3.encryption='sae-mixed'
uci set wireless.wifinet3.key='YOUR_PASSWD'
uci set wireless.wifinet3.network='lan200'
uci set wireless.radio1.cell_density='0'
uci set wireless.default_radio1.ssid='OpenWrt_lan100_5G'
uci set wireless.default_radio1.encryption='sae-mixed'
uci set wireless.default_radio1.key='YOUR_PASSWD'

以上就是這次的內容,希望對大家有幫助。

刷 OpenWRT 韌體刷機記錄 – 華碩 ASUS RT-N16 路由器

查查部落格文章,原來我曾經有過 Asus RT-N10+Asus RT-N16 …等路由器。
這幾台其實可以相容 DD-WRT 與 Tomato 第三方韌體,
但現今,我還是會推薦使用 OpenWRT,功能多太多了。

因為華碩原廠開始用各種方式阻擋置換第三方韌體,熟悉的手感不見了,故記錄一下。

注意:刷機有風險,刷機前請詳閱說明書,刷壞了屬於個人行為,小弟也不負責。
刷機後很有可能失去保固,如果害怕弄壞就按左上角關閉本文吧!

開始之前,記得看文件

OpenWRT 的刷機指南
https://openwrt.org/toh/asus/rt-n16
找到對應的型號就開始吧,很多招都從這來的。

硬體概述

  • CPU: Broadcom BCM4718 480MHz
  • Flash: 32MB
  • RAM: 128MB
  • 2.4GHz Wi-Fi (b/g/n): 300Mbps
  • 5 ports Gigabit ethernet 10/100/1000 Mbps
  • USB 2.0 x 2

材料準備

  1. 去華碩 ASUS 官網下載 Firmware Restoration 韌體更新程式
    ASUS Firmware Restoration 版本 2.1.0.2

https://www.asus.com/tw/supportonly/rtn16/helpdesk_bios/

並在 Windows 電腦安裝起來。

  1. 下載該型號最初版的韌體 (Firmware)

ASUS RT-N16韌體版本 3.0.0.4.374.979 (2013/10/09)
https://www.asus.com/tw/supportonly/rtn16/helpdesk_bios/

這是該型號最初版的韌體。

  1. DD-WRT 的 INITIAL 中繼過渡韌體 (Firmware)

依據 DD-WRT 官方文件所說需要下載這一份 22118 K2.6_mini_RT-N16.trx 過渡韌體
https://download1.dd-wrt.com/dd-wrtv2/downloads/betas/2013/07-24-2013-r22118/broadcom_K26/dd-wrt.v24-22118_NEWD-2_K2.6_mini_RT-N16.trx

(過渡韌體檔名為 dd-wrt.v24-22118_NEWD-2_K2.6_mini_RT-N16.trx 供參考)

  1. OpenWRT 該型號的最新版韌體 (Firmware)

https://downloads.openwrt.org/releases/22.03.5/targets/bcm47xx/mips74k/openwrt-22.03.5-bcm47xx-mips74k-asus_rt-n16-squashfs.trx

(截稿至今,檔名為: openwrt-22.03.5-bcm47xx-mips74k-asus_rt-n16-squashfs.trx 僅供參考。)

安裝步驟

相關步驟筆記如下,可能寫得有點雜,請見諒。

因為新版韌體「 3.0.0.4.380.7378 (2017/04/26) 」的網頁介面有鎖非原廠韌體更新,故要先退版。

  1. 設定好電腦本機 IP: 192.168.1.2 子網路遮罩: 255.255.255.0
  2. 進入「救援模式 (Rescue mode)」(等下會敘述)
  3. 使用剛剛下載的 ASUS Firmware Restoration 韌體更新程式 刷機

這裡有二種選擇,

刷入最初版的韌體 (Firmware):3.0.0.4.374.979 (2013/10/09)

完成後會進入 原廠韌體介面,確認版號之後,直接從路由器管理介面,直接更新韌體,刷最新版 OpenWRT 韌體即可。

(截稿至今,檔名為: openwrt-22.03.5-bcm47xx-mips74k-asus_rt-n16-squashfs.trx 僅供參考。)

或者

刷入 DD-WRT 的 INITIAL 過渡韌體
dd-wrt.v24-22118_NEWD-2_K2.6_mini_RT-N16.trx 供參考)

完成後會進入 DD-WRT,但這個版本不能直接使用(所以才稱做中繼過渡韌體)
使用電腦 Firefox,透過這個過渡版本的路由器網頁管理介面
,刷最新版 OpenWRT 韌體即可。

(截稿至今,檔名為: openwrt-22.03.5-bcm47xx-mips74k-asus_rt-n16-squashfs.trx 僅供參考。)

救援模式 (Rescue mode)

新版韌體「 3.0.0.4.380.7378 (2017/04/26) 」的救援模式進入方式有改版,但如果你刷入了舊版韌體「3.0.0.4.374.979 (2013/10/09)」,救援模式進入方式會變成舊方式。

我二種方式都列出來供大家參考。

  • 舊版:路由器斷電後,按住 restore 鍵不放並插電,持續 5 秒,電源燈會變成慢閃 (2 秒亮一下)
  • 新版:路由器斷電後,然後按住紅色 WPS 按紐,然後插電,等到看到電源燈亮了 (大約 1 秒) 之後再按住 restore 直到電源燈慢閃 (2 秒亮一下)

如何判斷是否有進入救援模式?

如何判斷是否有進入救援模式?
我覺得電腦連接網路線,用 ping 最準。

設定好電腦本機 IP: 192.168.1.2 子網路遮罩: 255.255.255.0

用 ping 加上 -t 參數,變成連續 ping 模式

ping 192.168.1.1 -t

你會發現 TTL=64 (正常模式) 變為 TTL=100 (救援模式)

Reply from 192.168.1.1: bytes=32 time<1ms TTL=64
Reply from 192.168.1.1: bytes=32 time<1ms TTL=100

有變成 TTL=100 就代表成功進入救援模式了,有時候可能要多試幾次才會成功進去,就算進入救援模式了

幾個須注意的重點 (雷點)

  • 如果你的電腦處於複雜的網路環境,例如有裝 Hyper-V 虛擬機、 VMWare 虛擬機
    又有 WiFi 等等多個網路裝置
    把這些網路裝置先停用,把網路環境單純化

  • 救援模式的方式有偷偷改過,但如果刷舊版韌體會被改回來
    總而言之,可以先試試看舊版,如果不行再試試看新版方式

  • 過渡版 DD-WRT 網頁介面刷機的時候,Google Chrome 會出現 ERROR_EMPTY_RESPONSE 的離奇問題
    實測後用火狐 Firefox 就不會出現此問題,在這裡使用 Firefox。

希望整個流程對你有幫助。

參考資料

刷 OpenWRT 韌體刷機記錄 – 華碩 ASUS RT-AC58U 路由器

剛好拿到一台華碩 ASUS RT-AC58U AC1300 路由器,華碩硬體做得還不錯,很耐用。
雖然不是 WiFi 6,但 WiFi 5 AC1300 (400 + 867 Mbps) 可以勇一陣子,再戰十年。

這算是我第五台刷機的機器了,該有步驟的還是會講一下。

我個人很愛開源第三方韌體,從 DD-WRT 玩到 Tomato,直到 OpenWRT (LEDE) 橫空出世,可玩性就在這裡了。
OpenWRT 可以突破原廠限制,很多商用進階功能,直接下放到家用機種,穩定性也比原廠韌體高,這就是我刷機的原因。

注意:刷機有風險,刷機前請詳閱說明書,刷壞了屬於個人行為,小弟也不負責。
刷機後很有可能失去保固,如果害怕弄壞就按左上角關閉本文吧!

開始之前,記得看文件

OpenWRT 的刷機指南
https://openwrt.org/toh/asus/rt-ac58u
很多沒提到的細節都在這裡

硬體概述

  • CPU: Qualcomm Atheros IPQ4018 或 IPQ4019 717Mhz
  • Flash: 128MB
  • RAM: 128MB
  • 2.4GHz Wi-Fi (b/g/n): 400Mbps
  • 5GHz Wi-Fi (a/n/ac): 867Mbps
  • 5 ports Gigabit ethernet
  • USB 3.0 x 1

安裝方式

注意:刷機有風險,刷機前請詳閱說明書,刷壞了屬於個人行為,小弟也不負責。
刷機後很有可能失去保固,如果害怕弄壞就按左上角關閉本文吧!

刷機前請再三確認型號、硬體版號、國際版還是地區版,刷錯型號會直接變磚。

參考步驟為 OpenWRT 的刷機步驟

  1. 到 Zyxmon’s AC58U 網頁,下載我們的中繼韌體 (Firmware) XXX-squashfs-flash-factory.trx 檔案。

(截稿至今,檔名為: openwrt-r1834-0f04829-ipq806x-asus_rt-ac58u-squashfs-flash-factory.trx 僅供參考。)

  1. 瀏覽器鍵入 http://192.168.1.1/ 登入華碩的路由器管理介面

  2. 在 系統管理 Administration → 韌體更新 Firmware Upgrade
    (可能的頁面網址是 http://192.168.1.1/Advanced_FirmwareUpgrade_Content.asp
    上傳 XXX-squashfs-flash-factory.trx 檔案,重開機後會進入 OpenWRT (LEDE),不過這不要太高興,這個中繼版本有一些 Bug,沒辦法使用。

  3. 在終端機用指令打開 SSH
    (以下是用 Mac 做為連線,理論上 Windows 的 Powershell 也適用,或者使用 putty ,SSH 的連線方式就不贅述了。)

ssh [email protected]

依序鍵入以下指令:

# umount /mnt/ubi0_5
# ubirmvol /dev/ubi0 -N jffs2

意思大致為:刪除 jffs2 分割區,解除其保護機制。

  1. OpenWRT 網站 下載最新版更新版 OpenWRT 韌體,
    檔名 XXX-squashfs-sysupgrade.bin

(截稿至今,檔名為: openwrt-22.03.2-ipq40xx-generic-asus_rt-ac58u-squashfs-sysupgrade.bin 僅供參考。)

  1. 瀏覽器鍵入 http://192.168.1.1/ 到這個半殘中繼韌體的路由器管理介面,
    上傳韌體(可能的頁面網址是 http://192.168.1.1/cgi-bin/luci/admin/system/flashops),記得 Keep settings 的勾勾要拿掉,不要保留設定。

  2. 重開機就可以使用完整版 OpenWRT 了。

希望這篇文章對大家有幫助。

參考資料

關於 Kubernetes (K8s) 二個 Pod 與 Service 連線的一些細節

在 Kubernetes (K8s) 的微服務架構中,了解 Pod 之間的連線細節是至關重要的一環。透過這篇文章的例子,深度探討 Kubernetes 的工作原理,讓讀者不僅理解 Pod 與 Service 之間的基本關聯,還能掌握其背後的機制與細節。我們將從 Pod 的概念和建立開始,進一步講解 Service 的角色和功能,並討論如何實現兩者間的連線。希望通過這篇文章,讀者能夠更具信心地運用 Kubernetes,無論是管理現有的微服務,還是設計新的應用架構。我們將嘗試將這些概念以最簡潔明了的方式呈現,使初學者和專業人士都能從中獲益。本文章帶你深入淺出,一窺 Kubernetes 的核心,理解與掌握 Pod 連線的關鍵知識。

範例需求

  • 建置二個 Deployment 而讓他們能夠內網互相溝通
  • 用一個 LoadBalancer 對應到其中一個 Deployment

配置範例

以下是一個 Kubernetes 配置範例,建立兩個 Deployment 並讓它們能夠內網互相溝通,以及一個 LoadBalancer 服務對應到其中一個 Deployment:

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-1
  template:
    metadata:
      labels:
        app: app-1
    spec:
      containers:
      - name: container-1
        image: j796160836/simple-test-http:latest
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-2
  template:
    metadata:
      labels:
        app: app-2
    spec:
      containers:
      - name: container-2
        image: j796160836/simple-test-http:latest
        ports:
        - containerPort: 80

service.yml

apiVersion: v1
kind: Service
metadata:
  name: service-1
spec:
  selector:
    app: app-1
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  name: service-2
spec:
  selector:
    app: app-2
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

這個範例中:

  1. 建立了兩個 Deployment,分別名為 deployment-1deployment-2。每個 Deployment 都有 2 個副本,分別使用標籤 app: app-1app: app-2

  2. deployment-1deployment-2 建立了兩個對應的 ClusterIP 服務,分別名為 service-1service-2。這兩個 ClusterIP 服務會將流量轉發到標籤為 app: app-1app: app-2 的 Pod。

  3. deployment-2 建立了一個名為 service-2 的 LoadBalancer 服務,將外部流量轉發到標籤為 app: app-2 的 Pod。

通過這個配置,兩個 Deployment 的 Pod 可以通過 ClusterIP 服務在內網進行通信,而 LoadBalancer 服務則允許外部流量來存取其中一個 Deployment 的 Pod。

備註:type: LoadBalancer 這個設定值只會在雲端服務
(例如:GCP (Google Cloud Platform) 裡面的 Google Kubernetes Engine (GKE) 、
AWS (Amazon Web Services) 的 Amazon Elastic Kubernetes Service (EKS)、
Microsoft 的 Azure Kubernetes Service (AKS))才會生效,
自行架設 on-premise 的 Kubernetes 叢集不會有動作,除非有另外做一些設定。

準備就緒,我們把它部署上來

$ kubectl apply -f deployment.yaml 

然後可以用 kubectl get pods 來查看 Pod 運作情形

$ kubectl get pods -n default
NAME                            READY   STATUS    RESTARTS   AGE
deployment-1-79c659f4ff-kkvgx   1/1     Running   0          112s
deployment-1-79c659f4ff-wf4kk   1/1     Running   0          112s
deployment-2-76d567869f-cgts7   1/1     Running   0          112s
deployment-2-76d567869f-fpqsm   1/1     Running   0          111s

別忘記部署 service

$ kubectl apply -f service.yaml 

我們使用 kubectl get services 來查看 services 的運作情況

$ kubectl get services -n default
NAME         TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)          AGE
service-1    ClusterIP      10.54.3.115   <none>          80/TCP           48s
service-2    LoadBalancer   10.54.3.33    34.xxx.xxx.123   80:32103/TCP     48s

筆記備註:
deployment 為一個部署計劃,裡面定義了 Pod spec,意指這個 Pod 樣板裡面有什麼 container。
replicas 標籤來定義這個樣板要跑幾個副本

通常一個 Pod 裡面只會有一個 container,在其他情況(例如需要 sidecar 的時候)才會一個 Pod 裡面有不只一個 container。


我的 app-2 要怎麼 ping 到 app-1 ?

想必你也跟我有一樣的問題, app-2 要怎麼 ping 到 app-1?讓我們一一解釋。

在 Kubernetes 集群中,你可以使用內部的 Service DNS 名稱來讓 app-2 的 Pod 連接到 app-1 的 Pod。在本例中,app-2 可以透過 service-1 服務名稱來存取 app-1

假設你的應用程式支持從環境變數讀取目標服務的 DNS 名稱,你可以在 deployment-2 的 Pod 模板中添加一個環境變數,指向 service-1 的 DNS 名稱,例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-2
  template:
    metadata:
      labels:
        app: app-2
    spec:
      containers:
      - name: container-2
        image: your-image-repo/image-2:latest
        ports:
        - containerPort: 80
        env:
        - name: APP_1_SERVICE_URL
          value: "http://service-1.default.svc.cluster.local:80"

這裡,我們為 container-2 添加了一個環境變數 APP_1_SERVICE_URL,其值為 http://service-1.default.svc.cluster.local:80。這個環境變數將被傳遞給 app-2 的應用程式,讓它可以連接到 app-1

在你的 app-2 應用程式中,你需要使用這個環境變數(例如,APP_1_SERVICE_URL)作為 app-1 服務的基礎 URL 進行連接。根據你的應用程式語言和框架,讀取環境變數的方法可能會有所不同。

例如,如果你的應用程式是用 Python 編寫的,你可以使用以下方式讀取環境變數:

import os

app_1_service_url = os.environ['APP_1_SERVICE_URL']

之後,你可以使用 app_1_service_url 作為 app-1 服務的基礎 URL 進行連接。

我們來做一個測試,嘗試把 container 裡面 console 掛進去看看

先列出 Pod,找到你要的 Pod

$ kubectl get pods -n default
NAME                            READY   STATUS    RESTARTS   AGE
deployment-1-79c659f4ff-kkvgx   1/1     Running   0          112s
deployment-1-79c659f4ff-wf4kk   1/1     Running   0          112s
deployment-2-76d567869f-cgts7   1/1     Running   0          112s
deployment-2-76d567869f-fpqsm   1/1     Running   0          111s

將 console 掛進去

$ kubectl exec -it -n my-namespace deployment-1-79c659f4ff-kkvgx -- /bin/bash

然後做 curl 瀏覽看看

root@deployment-1-79c659f4ff-kkvgx:/# curl service-2.my-namespace.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
....(後略)

可以成功連線!

關於內部 DNS 名稱

你或許會問: service-1.default.svc.cluster.local 是固定值嗎? 每次 deploy 會不會變更呢?

service-1.default.svc.cluster.local 是一個 Kubernetes 服務的內部 DNS 名稱。這個名稱是根據你的服務名稱和命名空間生成的。在本例中,服務名稱是 service-1,命名空間是 default

DNS 名稱的規則為 <service-name>.<namespace>.svc.cluster.local

這個 DNS 名稱在 Kubernetes 集群中是固定的,只要你不更改相應的服務名稱和命名空間。每次部署時,只要保持相同的服務名稱和命名空間,這個 DNS 名稱就不會變更。

在本例中,每次部署時,只要你保持服務名稱為 service-1 和命名空間為 default
那這個 service-1.default.svc.cluster.local 的 DNS 名稱就不會變更。

當然,如果你將服務名稱或命名空間更改為其他值,則對應的 DNS 名稱也會相應更改。在這種情況下,你需要在應用程式配置或部署文件中更新相應的 DNS 名稱。


Cleanup

做完實驗了,我們把剛剛建的這些東西都清掉(刪除),避免在雲端服務產生不必要的費用,這一步是很重要的。

$ kubectl delete deployments -n my-namespace deployment-1
deployment.apps "deployment-1" deleted

$ kubectl delete deployments -n my-namespace deployment-2
deployment.apps "deployment-2" deleted

$ kubectl delete services -n my-namespace service-1
service "service-1" deleted

$ kubectl delete services -n my-namespace service-2
service "service-2" deleted

依序把建立出來的 deployment、 service 給刪除

kubectl delete deployments <deployment>
kubectl delete services <services>
kubectl delete pods <pods>
kubectl delete daemonset <daemonset>

參考資料

https://sharegpt.com/c/feBhUAr

Kubernetes (K8s) 自建地端伺服器 (on-premise) 建置實錄 – Ubuntu 篇

在當今的雲端時代,Kubernetes(簡稱 K8s)作為 Open source 的 container (容器) 編排平台,已經成為許多企業和開發者的首選。它為應用程式的部署、擴展和管理提供了一個強大且靈活的解決方案。
本篇文章將詳細介紹如何在地端 (On-premise, self-host) 伺服器上搭建 Kubernetes 環境,我們將介紹所有必要的步驟,包括環境設置、安裝必要的套件、建立節點與部署應用程式。這將是一個完整的實錄,讓讀者能夠透過這篇文章深入瞭解 K8s 的建置與運作。


若是測試環境,請使用 虛擬機 (Virtual machine, VM) 來建置,
你可以用你喜歡的虛擬機程式來架設,例如 VMWare Workstation, VirtualBox 都可以,我是使用 Promox VE 裡面的 VM 功能來完成。
測試穩定再架設實體機也不遲。

順帶一提,以下這個方式安裝方式為 Bare-metal (裸金屬、裸機)的安裝方式,
這個也叫做 Vanilla Kubernetes (翻譯:單純的 Kubernetes 安裝),
如果在其他教學有看到這樣的詞彙的話,可以意識過來。

安裝地圖

Docker 跟 Kubernetes (K8s) 發展至今,百家爭鳴,門派也很多,
安裝部署方式也不盡相同,為了避免初學者混肴,
先幫你預先選好各種所需要的元件:

作業系統

  • Ubuntu Linux 22.04.2 LTS (Jammy Jellyfish)

服務們

  • kubelet
  • Container 運行環境 (Container Runtime):docker
  • cgroup drivers: 確認為 systemd (cgroup drivers v2)
  • CRI (Container Runtime Interface):使用 cri-dockerd
  • CNI (Container Network Interface):使用 Flannel

指令們

  • kubectl
  • kubeadm

這篇主要關注在如何架設 Kubernetes 叢集,
除此之外,你還需要一個配合的共用儲存空間,叢集都可以存取到的儲存空間(檔案伺服器)
可以用 TrueNAS 架設一個。

虛擬機硬體建置

這邊是我 虛擬機 (VM) 的硬體部分建置設定

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

註:經過測試,不要用 Promox VE 裡的 LXC Container 功能架設,
會有非常多的問題,包含權限切不乾淨等。

到時候要建立三台 VM,一台 Control Node 跟二台 Worker Node ,這是最小叢集的配置。
可以先安裝一個母版,到時候用複製 VM 的方式來達成。

虛擬機作業系統 – Ubuntu

作業系統可以是不同的發行版,我這邊用 Ubuntu 來做三台虛擬機的作業系統。

Ubuntu 可從官網下載
https://releases.ubuntu.com/

截稿時的最新 LTS 版本為 Ubuntu 22.04.2 LTS (Jammy Jellyfish)

使用 Server install image (檔名:ubuntu-22.04.2-live-server-amd64.iso) 來安裝

只安裝 SSH Server 就好,其他都先不要。
然後選項裡面有個 docker 實測發現在後續的步驟會有一些問題,請不要偷懶勾上。

<每台都做> 關掉 swap

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

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

根據 kubeadm 的安裝文件,他有特別指示

MUST disable swap in order for the kubelet to work properly.

必須要關掉 swap 才能正確運作。

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

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

暫時關閉 swap 可以用 swapoff 指令

sudo swapoff -a

⭐️ 後記:調整 vm.swappiness 的值為零只能降低 swap 使用優先權,並不能完全關閉 swap 故移除該指令

sudo sysctl -w vm.swappiness=0

確認 swap

sysctl 的方式來列出目前 swppiness 設定值

sysctl vm.swappiness

或者用列檔案方式列出

cat /proc/sys/vm/swappiness

<每台都做> 安裝 Docker

Docker 不分角色,三台都要裝

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

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

sudo apt update -y && \
sudo apt install -y ca-certificates curl gnupg && \
sudo install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
sudo chmod a+r /etc/apt/keyrings/docker.gpg && \
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt update -y && \
sudo apt 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"
}

預設開機啟動

sudo systemctl enable --now docker

驗證 Docker

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

sudo systemctl status docker

看看是否有 Running

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

docker ps

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

Docker 版本

留下當時截稿的 Docker 版本給大家參考

# docker version
Client: Docker Engine - Community
 Version:           23.0.5
 API version:       1.42
 Go version:        go1.19.8
 Git commit:        bc4487a
 Built:             Wed Apr 26 16:21:07 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          23.0.5
  API version:      1.42 (minimum version 1.12)
  Go version:       go1.19.8
  Git commit:       94d3ad6
  Built:            Wed Apr 26 16:21:07 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.20
  GitCommit:        2806fc1057397dbaeefbea0e4e17bddfbd388f38
 runc:
  Version:          1.1.5
  GitCommit:        v1.1.5-0-gf19387a
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

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

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

小弟整理的安裝指令

sudo apt update -y && \
sudo apt-get install -y apt-transport-https ca-certificates curl && \
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/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.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list && \
sudo apt-get update -y && \
sudo apt-get install -y kubelet kubeadm kubectl && \
sudo apt-mark hold kubelet kubeadm kubectl

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

目前安裝的版本是 kubelet 1.28.2

<每台都做> 安裝 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

release 頁面找到最新版,並符合您的版本的執行檔,下載並安裝

用 deb 檔案安裝

以筆者為例,筆者用的是 Ubuntu 22.04.1 LTS (Jammy Jellyfish)
代號為 Jammy 當時最新版為 v0.3.0

所以找到了 cri-dockerd_0.3.0.3-0.ubuntu-jammy_amd64.deb
這個檔案

下載安裝檔並解開安裝
(抱歉,這套件目前沒收錄在 apt-get 套件管理程式裡,沒辦法直接 apt install

wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.4/cri-dockerd_0.3.4.3-0.ubuntu-jammy_amd64.deb && \
sudo dpkg -i cri-dockerd_0.3.4.3-0.ubuntu-jammy_amd64.deb

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

systemctl daemon-reload && \
systemctl enable --now cri-docker.service

從官網手動安裝 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


這邊補充,其實有網友發了 Pull request,但一直沒過
https://github.com/Mirantis/cri-dockerd/pull/394
也有網友詢問 RHEL 9.4 與 Ubuntu 24.04 的做法
RHEL 9.4
https://github.com/Mirantis/cri-dockerd/issues/368
Ubuntu 24.04
https://github.com/Mirantis/cri-dockerd/issues/361

複製虛擬機 (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

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

叢集的三台機器做出來,還不知道彼此,
這邊用 /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 分隔。

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

確認 cgroup drivers 為 systemd

https://stackoverflow.com/questions/45708175/kubelet-failed-with-kubelet-cgroup-driver-cgroupfs-is-different-from-docker-c

直接講結論,目前最新使用的是 systemd (cgroup Version: 2)

查看 docker 的 cgroup

docker info | grep -i cgroup

執行結果

# docker info | grep -i cgroup

 Cgroup Driver: systemd
 Cgroup Version: 2
  cgroupns

查看 kubelet 的 cgroup

sudo cat /var/lib/kubelet/config.yaml | grep cgroup

執行結果

$ sudo cat /var/lib/kubelet/config.yaml | grep cgroup

cgroupDriver: systemd

可以確認是否為 systemd (cgroup Version: 2)

如果 docker 不為 systemd

可以在 daemon.json手動加上

sudo vi /etc/docker/daemon.json

這個段落

 "exec-opts": [
    "native.cgroupdriver=systemd"
  ],

重啟 docker

sudo systemctl restart docker

如果 kubelet 不為 systemd 就手動修改之

sudo vi /var/lib/kubelet/config.yaml

重啟 kubelet

sudo systemctl restart kubelet

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

根據文件:
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

設定 Control plane node(控制平台) (舊名 Master node)

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

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

sudo kubeadm init \
    --kubernetes-version 1.28.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 這設定值 請不要修改

記錄一下運作的樣子

# kubeadm init --pod-network-cidr=10.244.0.0/16 --cri-socket unix:///var/run/cri-dockerd.sock

[init] Using Kubernetes version: v1.28.2
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
W0503 18:35:29.693213    1321 images.go:80] could not find officially supported version of etcd for Kubernetes v1.28.2, falling back to the nearest etcd version (3.5.7-0)
W0503 18:35:46.627127    1321 checks.go:835] detected that the sandbox image "registry.k8s.io/pause:3.6" of the container runtime is inconsistent with that used by kubeadm. It is recommended that using "registry.k8s.io/pause:3.9" as the CRI sandbox image.
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-ctrl kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.1.100]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-ctrl localhost] and IPs [192.168.1.100 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-ctrl localhost] and IPs [192.168.1.100 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
W0503 18:36:08.496065    1321 images.go:80] could not find officially supported version of etcd for Kubernetes v1.28.2, falling back to the nearest etcd version (3.5.7-0)
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 8.502958 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node k8s-ctrl as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node k8s-ctrl as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: c1hnqs.c4imcnzqxqry62d0
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6

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

Your Kubernetes control-plane has initialized successfully!

別太高興,設定還沒完,先把 kubeadm join 語句先存起來備用

然後依照步驟,
若是 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 套件管理程式

Helm 是 Kubernetes (K8s) 所使用的套件管理程式,
類似 apt-get 可以方便我們安裝元件,免去一點設定的雷

Helm 只要裝在 Control plane (舊名 Master node) 就可以了

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

從 APT 安裝(推薦),可用小弟整理之一鍵安裝指令

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null && \
sudo apt install apt-transport-https --yes && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list && \
sudo apt update -y && \
sudo apt install -y 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

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

\<Control plane 做> 安裝 Flannel CNI

https://github.com/flannel-io/flannel

使用 Helm 安裝 Flannel,將之安裝在 kube-flannel 的 namespace,可用小弟整理之一鍵安裝指令

Flannel 只要在 Control plane (舊名 Master node) 上面下指令,就會部署到整個叢集。

可以使用以下整理之指令一鍵安裝

kubectl create ns kube-flannel && \
kubectl label --overwrite ns kube-flannel pod-security.kubernetes.io/enforce=privileged && \
helm repo add flannel https://flannel-io.github.io/flannel/ && \
helm install flannel --set podCidr="10.244.0.0/16" --namespace kube-flannel flannel/flannel

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

指令意思大致為:

  1. 建立一個 namespace (命名空間)名叫 kube-flannel
  2. 給定 kube-flannel 特權的權限
  3. 加入 repo 網址
  4. 用 helm 安裝 Flannel

設定 Worker node

這下終於可以設定 Worker node 了

還記得剛剛留下來的指令

kubeadm join 192.168.1.100:6443 --token cxxxxs.c4xxxxxxxxxxxxd0 \
    --discovery-token-ca-cert-hash sha256:103d7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5b1b6

什麼?忘記了?

可以用指令重新生成加入指令

kubeadm token create --print-join-command

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

變成這樣

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

記錄一下運作情形

$ 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

[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

這樣就加入叢集了

查看 kubelet Log

另外這二個指令,對於 kubelet 的啟動不了的問題,也會有一些方向

查看 kubelet 狀態

systemctl status kubelet

查看 kubelet 的 Log

journalctl -xeu kubelet

最後,一個小小經驗談,
--control-plane-endpoint--apiserver-advertise-address 的 IP 可以再次確認是否有打錯字,這也會造成錯誤

重設整個叢集

如果整個叢集有其他問題,做爛了,可以用以下方法重新設定

進到每一台 node 裡面,利用 kubeadm reset 重置,記得代入 cri-socket

如下:

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

記錄一下運作情形

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

[preflight] Running pre-flight checks
W0507 02:43:32.160215    1264 removeetcdmember.go:106] [reset] No kubeadm config, using etcd pod spec to get data directory
[reset] Deleted contents of the etcd data directory: /var/lib/etcd
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
W0507 02:43:32.169757    1264 cleanupnode.go:134] [reset] Failed to evaluate the "/var/lib/kubelet" directory. Skipping its unmount and cleanup: lstat /var/lib/kubelet: no such file or directory
[reset] Deleting contents of directories: [/etc/kubernetes/manifests /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]

The reset process does not clean CNI configuration. To do so, you must remove /etc/cni/net.d

The reset process does not reset or clean up iptables rules or IPVS tables.
If you wish to reset iptables, you must do so manually by using the "iptables" command.

If your cluster was setup to utilize IPVS, run ipvsadm --clear (or similar)
to reset your system's IPVS tables.

The reset process does not clean your kubeconfig files and you must remove them manually.
Please, check the contents of the $HOME/.kube/config file.

它會提示你,有些防火牆規則並不會完全刪掉

可以刪掉 cni 資料夾來重置

rm -rf /etc/cni/net.d

對應文件:
https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-reset/

\<Control plane 做> 測試檢查叢集

測試 Kubernetes 是否正常運作,
在 Control plane (控制平台) 裡可以用二個指令觀察一下:

取得所有的 Pods

kubectl get pods 指令取得 Pod,加上 -A 代表包含所有 namespace (命名空間)

以下指令就是取得所有的 Pods

kubectl get pods -A

取得所有的 pods

$ kubectl get pods -A

NAMESPACE      NAME                               READY   STATUS    RESTARTS   AGE
kube-flannel   kube-flannel-ds-8rtvc              1/1     Running   0          30s
kube-flannel   kube-flannel-ds-9w2vw              1/1     Running   0          30s
kube-flannel   kube-flannel-ds-jdndp              1/1     Running   0          30s
kube-system    coredns-5d78c9869d-df989           1/1     Running   0          4m20s
kube-system    coredns-5d78c9869d-s8ftg           1/1     Running   0          4m19s
kube-system    etcd-k8s-ctrl                      1/1     Running   0          4m35s
kube-system    kube-apiserver-k8s-ctrl            1/1     Running   0          4m33s
kube-system    kube-controller-manager-k8s-ctrl   1/1     Running   0          4m35s
kube-system    kube-proxy-2qrjj                   1/1     Running   0          4m19s
kube-system    kube-proxy-bpk94                   1/1     Running   0          3m51s
kube-system    kube-proxy-mgrjn                   1/1     Running   0          3m57s
kube-system    kube-scheduler-k8s-ctrl            1/1     Running   0          4m36s

你應該要看到:

  • kube-flannel 的若干個 Pod 為 Running
    (若是 Pending 或者 CrashLoopBackOff 可能要除錯)
  • kube-system (K8s 核心元件) 的二個 coredns 的 Pod 為 Running
    (若是 Pending 或者 CrashLoopBackOff 可能要除錯)
  • kube-system (K8s 核心元件) 的 etcdRunning
  • kube-system (K8s 核心元件) 的 kube-controller-manager 的 Pod 為 Running
  • kube-system (K8s 核心元件) 的 kube-apiserver 的 Pod 為 Running
  • kube-system (K8s 核心元件) 的 kube-scheduler 的 Pod 為 Running
  • kube-system (K8s 核心元件) 的若干個 kube-proxy 的 Pod 為 Running

當然,放在 kube-system 裡面的 Pod 屬於系統保留的,請勿更動修改。

取得所有 nodes (主機節點)

你可以用 kubectl get nodes -A 指令來取得所有運作的 nodes

kubectl get nodes -A

NAME        STATUS   ROLES           AGE     VERSION
k8s-ctrl    Ready    control-plane   4m40s   v1.28.2
k8s-node1   Ready    <none>          3m59s   v1.28.2
k8s-node2   Ready    <none>          3m53s   v1.28.2

你應該要看到你的叢集,三台都是 Ready

Google 介面語言切換的小技巧

像我個人常使用英文介面,理當所有網頁都會給英文的結果,
但有時候會遇到一種情況,例如查看 Google Map 地圖,
地圖都是中文路名,翻譯成英文都沒辦法一眼看懂,要試著唸一下才知道再講什麼。
但 Google 家的產品,語言切換都藏的比較深,
這時候網址切換就比較方便。

以下教學只適用於 Google 家的產品,不同公司定義不一樣。
而 Google 隨時會改,
如果已經失效了的話再麻煩跟我說。

在 https:// …… /xxx?aaa=bbb&ccc=ddd

後面加上 &hl=zh-Hant 可切繁體中文
後面加上 &hl=en 可切英文

例如:

https://www.google.com/maps?hl=zh-Hant
就切成中文

https://www.google.com/maps?hl=en
就切成英文

需注意 URL Query string 的格式,
? (問號)開頭,中間用 & 符號分隔,
如果最開頭有了 ? (問號),那後面只能接 & 符號。

[教學] Google 表單 製作多選複選選項 與限制總量數量(團購表單好用)

因為 Choice Eliminator 2 這個第三方外掛已經不能用了(第三方外掛的廠商已經下線了)
這陣子就在找替代方案,發現這一招還是可用,分享給大家。

本教學會帶入部分 Google 表單與 Google 試算表的使用,
看不懂也不用擔心,會一步一步帶大家建立。

教學步驟


(這是範例成品)

我們在這舉一個例子,假設是一個蛋糕團購表單,有六個選項,
在六個選項中,選擇二個:

  • 原味
  • 抹茶
  • 起司
  • 草莓
  • 芋頭
  • 巧克力

在這團購表單很常見。

Step1. 建立表單

首先先建立一個 Google 表單,

網址:
https://docs.google.com/forms/

按新增

這邊用打入標題、 等聯絡資訊。
用簡答功能打入 姓名、電話、地址 等聯絡資訊。
旁邊有加號可以新增題目,這功能就不贅述。

再來就是重頭戲,新增一個「核取方塊」,
打入 原味、抹茶、起司、草莓、芋頭、巧克力 六個選項

在右下角的點點點選項,選擇「回應驗證」

就會多出一行選項

選擇「選取剛好」,數量打入 2,最後輸入提示訊息

再來點到「回覆」頁籤,右上角一個小按鈕「建立試算表」

選取回應目的地的視窗中
選建立新試算表,輸入名字,然後按「確定」

動畫就像這樣

你就會看到打開了一個 Google 試算表。

Step2. 製作回應表單的試算表

在試算表下方按下「 + 」,建立試算表

我們建立二個新的試算表,名字分別叫做:

  • 選項拆解
  • 統計

像這樣

Step3.「選項拆解」的試算表設計

在「選項拆解」的試算表中,

A1 打入「選項」
B2 ~ G2 依序打入「原味、抹茶、起司、草莓、芋頭、巧克力」等選項

接下來這個騷操作,要一步完成
先選擇 A2 這格,先打一個「 = 」(等號)
然後直接選擇「表單回應 1」,的第一格選項欄位

你會看到公式跳出來

='表單回應 1'!E2

動畫在這:

未來填單有資料的時候就會這樣

意思是說,單引號括起來的是試算表的名字,
意指「表單回應 1」試算表裡的 E2 欄位。

如果你是直接複製公式的話,
要注意單引號與雙引號,還有要注意欄位格子是否正確。

按下 Enter 之後,他會顯示空白的是正常現象,選取上去就會該格子就會有公式出現。

Step3-1.「選項拆解」試算表套用搜尋公式

選到 B2 這格,打上公式:

=IF(IFERROR(FIND(B$1, $A2), 0) > 0, 1, 0)

然後選上 B2 這格,直接拖拉到 G2,讓這列的選項都填滿了公式。

動畫:


如果有興趣的朋友,這邊小小解釋一下這個公式的原理:

FIND() 輸入三個參數:

  • 要找的文字
  • 原始文字
  • 從第幾個字開始找(可忽略不填)

白話文就是在第二個參數中,找第一個參數的文字,在哪個位置,
從第三個參數開始找,第三個參數可忽略,就是從第一個字開始找。

輸出就是在第幾個字出現的位置。

FIND() 的說明文件:
https://support.google.com/docs/answer/3094126?hl=zh-Hant

IFERROR() 輸入二個參數:

  • 要執行的公式
  • 出錯時要顯示的字

第一個參數是執行公式,如果它出錯了,就顯示第二個參數值。

IFERROR() 的說明文件:
https://support.google.com/docs/answer/3093304?hl=zh-Hant

IF() 就是判斷,輸入三個參數:

  • 判斷的公式
  • true 時顯示文字
  • false 時顯示文字

算蠻好理解的。

IF() 的說明文件:
https://support.google.com/docs/answer/3093364?hl=zh-Hant

整段白話文就是:我們把選項拆解,有搜尋到字的欄位標成 1 否則是 0


選取 要估算一下會填單的人數,整列選起來往下拉,讓每列都填上公式。
至於要拉幾列,當然 越多越好

這表格意旨在拆解選項並填到各個對應的格子裡。

Step4.「統計」的試算表設計

切到「統計」的試算表

A1 ~ E1 欄位分別打上「顯示、選項、額滿提示、已達數量、數量上限」
然後 B2 ~ B7 依序分別打上總共有的「原味、抹茶、起司、草莓、芋頭、巧克力」選項,
額滿提示可以打上你想打的額滿提示,範例是在最後加上(完售)字樣。

如果你想隱藏該選項,在額滿提示直接留空白。

然後在 E2 ~ E7 打入你要的數量上限。

Step4-1.「統計」試算表套加總公式

選擇 D2 套入加總公式

=SUM('選項拆解'!B:B)

這句 SUM() 的意思是:做 「選項拆解」試算表裡的 B 欄所有列的加總,

然後如法炮製,依序在 D3 套入公式

=SUM('選項拆解'!C:C)

在 D4 套入公式

=SUM('選項拆解'!D:D)

以此類推,套到 D7

=SUM('選項拆解'!G:G)

這邊重點要注意統計欄位是不是正確

Step4-2.「統計」試算表套顯示判斷公式

選擇 A2 這格,套入公式

=IF(D2<E2,B2,C2)

這個應該很好理解,就是數量統計超過上限時,顯示額滿提示,否則顯示原本的選項名

然後往下拉,填滿到 A7

為何要這樣設計呢?為了配合等下要提到的 Form Ranger 外掛

Step5. 安裝 Form Ranger 表單外掛

在右上方點點點點開「外掛程式」,搜尋「Form Ranger」

也就是這個外掛:
https://workspace.google.com/u/5/marketplace/app/form_ranger/387838027286?hl=zh&pann=forms_addon_widget

安裝,

選擇帳號,

瀏覽授權權限,按下「允許」,

允許這個第三方外掛。

Step6. 使用 Form Ranger 表單外掛

然後回到剛剛做的表單,在右上角多一個小積木
選擇「formRanger — PROD」

點「Start」

會看到你的多選問題

勾選「Populate from range」,然後旁邊按「 + 」(New Range)

在 Select sheet 的頁籤中,

選擇剛剛的回應試算表「蛋糕訂購表單 (回覆)」,按「Select」。

在 Select Ranage 的頁籤中,

Sheet name 選「統計」
Column header 選「顯示」

左側會出現預覽,按下「Next」

最後在 Name range 頁籤中,

Range name 打入一個名稱(名稱辨識用,可隨意填)
然後按下「Save and populate question」

最後的最後,

在 Auto-repopulate questions (自動重新產生問題) 這邊,
記得要把 On form submit (每次表單送出時) 選項給勾起來,
它會在表單送出的時候根據欄位值重新產生表單。

如果資料不同步時,可以手動按下「Update questions list」手動更新表單。

Step7. 發佈連結

表單右上角按下「傳送」,

看到傳送表單的視窗,

選擇「連結」圖示,可以勾「縮短網址」,然後按複製,
這個就是你的表單前台網址。

Step8. 測試!

最後當然是當個客人,來測試一下運作情況啦!

拿剛剛產生的這個網址去瀏覽器真的填看看,也驗證一下會不會有問題,
還是哪裡有做錯的地方。

最後附上筆者隨意填三筆資料的觀察各表單結果。

表單回應

選項拆解

統計

前台表單的樣子

補充

最後的最後,如果你想隱藏額滿的選項,
在額滿提示直接留空白就可以了,選項他會自動消失。

記得時不時注意一下,「選項拆解」試算表套公式的列數,不然會出錯。
這功能不管單選多選都可以適用,選項在對應的地方修改即可,變通一下就有超萬用的表單了。

希望這邊教學對你有幫助。

參考資料

[iOS教學] 使用 PromiseKit 來管理你的 callback!

請參考 PromiseKit 的文件:

Promise 基本概念

在非同步執行的流程處理上,傳統作法一直是個麻煩點,
而 Promise 透過一些函式可以很直覺的管理非同步的流程。

傳統的做法

以 iOS 而言,你可能要透過 NSOperationQueue 或者
GCD (Grand Central Dispatch) 這些方法來做非同步的流程。

如果要接續觸發( A 事情做完做 B )的情境,A 事情的 callback 做完之後緊接著 B 事情,你可能會得到一個很深的縮排。
如果是互相等待完成( A 跟 B 事情 )的情境,你可能要透過一些 boolean 來把狀態記住,然後 A 事情跟 B 事情的 callback 同時要檢查對方是否做完,才接續另外一個 callback 。 以上並不是說這樣寫不能用,只是你會有更好的解法,讓程式碼變得更乾淨好閱讀。

Promise 的做法

主要關鍵字有 firstly then catch 還有 always
要字面上來看就是 「首先」、「然後」最後是有錯誤時用 catch 抓取錯誤。

這個範例還用了一個很實用的 when() 來綜合二件非同步的事情,當二件事情都結束時才會回傳到下一個 Callback。

這是一個綜合各種基本關鍵字的範例

firstly {
    // Show Loading status bar
    return when(myAsnycTaskA(), myAsnycTaskB())
}.then { (resultA:MyObjectA, resultB:MyObjectB) -> Void in
    // Show results
}.always {
    // Hide Loading status bar
}.catch { error in
    print(error)
}

最簡單的範例

這是一個最基本的範例
基本句型有 thencatch 就可以了

myAsnycTaskA().then { 
    (resultA:MyObjectA) -> Void in
    // Show results

}.catch { error in
    print(error)
}

宣告

至於你想要宣告一個事情也不難

func myAsnycTaskA()-> Promise<MyObjectA> {
    return Promise { fulfill, reject in
        // Done
        fulfill(result)

        // Fail
        reject(error)
    }
}

回傳一個 Promise,裡面有 fulfill()reject() 二個 method。
當資料回來的時候呼叫 fulfill()
當出現錯誤的時候呼叫 reject() 並帶入一個 Error 物件。

複雜一點的範例

這時回過頭來看第一個範例,我又把它改複雜了一點,是不是比較不難了呢?

firstly {
    // Show Loading status bar
    return when(myAsnycTaskA(), myAsnycTaskB())
}.then { (resultA:MyObjectA, resultB:MyObjectB) -> Promise<MyObjectC> in
    // So some processing
    return myProcessingTaskC()
}.then { (resultC:MyObjectC) -> Void in
    // Show results
}.always {
    // Hide Loading status bar
}.catch { error in
    print(error)
}

首先,同時做 myAsnycTaskA()myAsnycTaskB()
等二者結果回傳了之後,做 myProcessingTaskC() 最後回傳結果。

在 callback 的處理上是不是變得比較開心愉快了一點呢?
以上就是 Promise 的快速介紹。

有興趣的話,可以查看官方文件裡面有更進階的寫法。
這個概念不只在 iOS (Swift) 可以用,在 JavaScript (ES6) 也有類似的語法,有機會再專文介紹。

[教學] Mac OSX 內建防火牆 PF 使用筆記

<br />

你知道 Mac 本身也有內建防火牆嗎?
我指的不是 System Preferences 裡面有的防火牆(那個屬於 Application Firewall 指定程式進出的),而是可以像 linux iptables 可以設定規則的防火牆。

它叫做 Packet filter 簡稱 PF,因為 Mac OSX 屬於 BSD 系列的系統
自從 10.7 (Lion) 之後就有內建 PF(在這之前是 IPFW),只是預設是關閉的。

官方文件寫的非常少,大部分要參考 FreeBSD 的文件或者 OpenBSD 的文件,
大部分這二者看到的文件跟語法大多都支援。
介面只能用指令列,跟手動編輯文字檔的方式,沒有美美的 GUI 介面 😀

官方文件: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man5/pf.conf.5.html

指令是 pfctl 設定檔主要是 /etc/pf.conf

常用指令

這裡列出我常用的 pfctl 指令

檢查預覽規則

$ sudo pfctl -vnf /etc/pf.conf

如果設定檔有錯誤會在這裡顯示,反之顯示規則

重新啟動防火牆

$ sudo pfctl -d ; sudo pfctl -ef /etc/pf.conf

分號前半段是關閉防火牆,後半段是啟動防火牆,我把它合再一起比較方便

檢視狀態

$ pfctl -s state

pf.conf 設定

打開 /etc/pf.conf (需要 root 權限,可以用 sudo 拿到)
預設只有這樣

#
# Default PF configuration file.
#
# This file contains the main ruleset, which gets automatically loaded
# at startup.  PF will not be automatically enabled, however.  Instead,
# each component which utilizes PF is responsible for enabling and disabling
# PF via -E and -X as documented in pfctl(8).  That will ensure that PF
# is disabled only when the last enable reference is released.
#
# Care must be taken to ensure that the main ruleset does not get flushed,
# as the nested anchors rely on the anchor point defined here. In addition,
# to the anchors loaded by this file, some system services would dynamically
# insert anchors into the main ruleset. These anchors will be added only when
# the system service is used and would removed on termination of the service.
#
# See pf.conf(5) for syntax.
#
#
# com.apple anchor point
#
scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"

Options (參數選項)

這個 Options 要在設定檔最開頭加入,不然會 Syntax error
不多說,直接看範例就明白

# 最佳化模式:一般模式
set optimization normal
# 阻擋的策略:直接丟棄
set block-policy drop
# 封包需要照順序
set require-order yes
# 跳過 lookback 介面不處理
set skip on lo
# log 紀錄只記最危急的等級
set debug urgent

其他細節可以參考這裡
https://www.openbsd.org/faq/pf/options.html

Anchors (規則群組)

Anchor 算是一個規則群組,你可以把你的規則分門別類。
例如這樣:

anchor "myrules"
load anchor "myrules" from "/etc/pf.anchors/myrules"

再來就是編輯你剛剛定義的 /etc/pf.anchors/myrules 檔案就可以了

當然這例子很粗淺,
你也可以使用連結裡的範例,用巢狀的方式寫出更複雜的規則
https://www.openbsd.org/faq/pf/anchors.html

Variables (變數)

變數定義就像是寫程式一樣定義字串,看範例就懂,也就不再贅述:

int_if="{ en0 en1 }"
ext_if="{ ppp0 }"
webports = "{http, https}"
int_tcp_services = "{domain, ssh, ntp, www, https}"
int_udp_services = "{domain, ntp}"
icmp_types = "echoreq"

跟 bash shell 一樣,井號 # 開頭的為註解,使用變數就是前面加錢字號 $

Tables (表格)

table <private> const { 192.168/16 }

這裡定義了表格,名叫 private,IP範圍在 192.168.x.x
這個 /16 是指 16bit 的子網路遮罩,等同於 255.255.0.0
用的是 CIDR 表示法
https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation

相關資料
https://www.openbsd.org/faq/pf/tables.html

Rules (規則)

這裡就是最複雜的地方了,建議文件先開起來看
https://www.openbsd.org/faq/pf/filter.html

文件裡有提到語法為

action [direction] [log] [quick] [on interface] [af] [proto protocol]
[from src_addr [port src_port]] [to dst_addr [port dst_port]]
[flags tcp_flags] [state]

有一個很大的重點是 規則是從上到下做比對,如果規則有衝突的話,會諄照 最後一條 符合的規則去執行。
要提一下 quick 這個關鍵字,如果有看到 quick 就代表只符合該規則,會無視後面寫(沒有 quick 關鍵字)的規則

例如文件寫的範例:

# 擋掉所有 ssh (TCP port: 22) 的連線
block in proto tcp to port ssh
# 允許所有連線
pass in all

規則第一行寫擋掉它,第二行又寫允許,假設有一個 SSH 連線, 該封包是被擋掉呢?還是被允許?
答案是被允許,因為規則如有衝突的話,諄照 最後一條 符合的規則去執行。

# 擋掉所有 ssh (TCP port: 22) 的連線
block in quick proto tcp to port ssh
# 允許所有連線
pass in all

第一行雖寫擋掉它,但因為有加 quick 所以會忽略之後所有符合的規則(忽略掉允許的規則)
所以假設有一個 SSH 連線,這次會變成 被擋掉。
這才是我們要的結果。

黑名單範例

黑名單顧名思義就是預設允許,只擋掉不要的
假設有台 個人開發機 要設定 PF 防火牆,需求如下:

  • 預設允許所有連出連入連線
  • 擋掉所有 80 (http), 443 (https) 的連入,只允許私有IP使用
  • 擋掉所有 3306 (mysql) 的連入,只允許本機連線
# === Developer machine example ===
# 指定連線介面
int_if="{ en0 en1 }"
# 指定要開放 連入 的服務
webports = "{http, https}"
# 定義私有IP範圍
table <private> const { 192.168/16 }
# 預設允許所有連出連入連線,並保持狀態
pass in all keep state
pass out all keep state
# 跳過 lookback 介面不處理
set skip on lo
# MySQL (TCP:3306) 允許本機 lookback 介面 能連線,阻擋其他的封包
pass in quick on lo proto tcp from any to any port 3306
block in quick proto tcp from any to any port 3306
# 只允許私有 IP 能連線,阻擋其他的封包
pass in quick proto tcp from <private> to any port webports
block in quick proto tcp from any to any port webports

測試

設定好了之後來測試,這裡列出幾個:

  • 是否本機能正常連線到 MySQL?
  • 找一台同網段的裝置,是否無法連接 MySQL?
  • 找一台接不同網路的裝置來測試,是否無法連接 MySQL?
  • 是否本機能正常看到自己開發中的網站?
  • 找一台同網段的裝置,是否能正常看到網站?
  • 找一台接不同網路的裝置來測試,是否能正常看到網站?

    附註:MySQL 連線指令為

    $ mysql -u root -h <YOUR_IP> -p

    測試 HTTP 伺服器

    $ docker run -d -p 80:00 j796160836/simple-test-http

    使用 docker 這個測試 image,
    對應內部 container 80 port (後者) 到主機 80 port (前者)

白名單範例

白名單顧名思義就是預設全部擋掉,再允許我要的連線,
這個設定比較複雜,要小心設定,要避免自己把自己鎖住出不去。

假設有一台 http 伺服器 要設定 PF 防火牆,需求如下:

  • 預設擋掉所有進出的連線
  • 只接受 80 (http), 443 (https) 的連入,其餘不打開
  • 基本對外上網要能用 (dns查詢、ntp網路對時、ssh、http)

這裡是簡化版,如有其他需要可以自行再加

# === HTTP server example ====
# 指定對外連線介面
ext_if="{ ppp0 }"
# 指定要開放 連入 的服務
webports = "{http, https}"
# 指定要開放的 連出 的服務
int_tcp_services = "{domain, ssh, ntp, www, https}"
int_udp_services = "{domain, ntp}"
# 跳過 lookback 介面不處理
set skip on lo
# 重新組合封包
scrub in all random-id fragment reassemble
# 擋掉所有 進入 的連線
block drop in log all
# 擋掉所有 連出 的連線
block out all
# 阻擋惡意偽造封包
antispoof quick for $ext_if
# 指定要開放 連入 的服務
pass in on $ext_if proto tcp from any to any port $webports
# 指定要開放的 連出 的服務
pass out quick on $ext_if proto tcp to any port $int_tcp_services
pass out quick on $ext_if proto udp to any port $int_udp_services
# 接受 PING 回應
pass on $ext_if inet proto icmp to $ext_if icmp-type "echoreq" keep state
# 接受使用 traceroute
pass out on $ext_if inet proto udp from any to any port 33433 >< 33626 keep state

測試

設定好了之後當然要測試,這裡列出幾個:

  • 該台伺服器是否能正確上網?是否能正常使用 ping, traceroute, nslookup 等功能?
  • 多個網路介面同時連線上網之後,對外介面 ppp0 斷線之後,是否無法上網? ping 會不會不通?
  • 找另一台接不同網路的裝置來測試,是否能夠 ping 到該台伺服器? http 是否能正確連入? 確認其他的 port 是否不能連入?

開機自啟動

PF 防火牆設定好了之後,可以這樣設定讓它開機自動啟動

$ sudo vi /System/Library/LaunchDaemons/com.apple.pfctl.plist

然後修改部分內容,以下列出全文

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Enabled</key>
<false/>
<key>Label</key>
<string>com.apple.pfctl</string>
<key>WorkingDirectory</key>
<string>/var/run</string>
<key>Program</key>
<string>/sbin/pfctl</string>
<key>ProgramArguments</key>
<array>
<string>pfctl</string>
<string>-e</string>
<string>-f</string>
<string>/etc/pf.conf</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

其他規則範例

這裡我搜集了一些網友寫的規則,也蠻有參考價值的

https://gist.github.com/kujohn/7209628
https://gist.github.com/nathwill/9703175
https://gist.github.com/rosstimson/5826279
http://daemon-notes.com/articles/network/pf

注意事項

這裡講一些我遇到的雷,

  • pf.conf 跟 Anchors 文件的最後請多放二個換行,不然有時候會莫名遇到 Syntax error (這個最雷)
  • Mac OS X系統大版本更新之後(例如:El Capitan 升級到 Sierra),請倒回去確認防火牆規則是否還在,可能會被清掉
  • 設定防火牆時請避免使用遠端連線,避免重啟防火牆時被斷線或被錯誤的規則鎖在門外
  • 防火牆啟動後(尤其是採用白名單策略時),如果該電腦不能正常上網,切勿慌張,先查查看防火牆規則,是不是自己把自己給檔在門內出不去 😄

設定防火牆規則很複雜,請多測試,有時候不是你想的那樣,多參考別人的範例規則,再慢慢調整成自己想要的,good luck!

參考資料

http://www.thedeepsky.com/howto/newbiepfguide.php
http://www.scottro.net/pf.html
http://www.openbsd.org/faq/pf/options.html
http://www.openbsd.org/faq/pf/example1.html
http://www.openbsd.org/faq/pf/logging.html
https://www.freebsd.org/doc/handbook/firewalls-pf.html
https://forums.freebsd.org/threads/38669/
https://forums.freebsd.org/threads/11511/
https://forums.freebsd.org/threads/28101/
http://www.bsdforen.de/threads/pf-icmp-wahnsinn.14694/
https://forum.pfsense.org/index.php?topic=126031.0