← 返回上一頁
Kubernetes

深入理解容器環境下的 Kernel 參數調整:從 Docker 到 Kubernetes 的 sysctl 實戰

本頁目錄
Kubernetes 容器核心參數設定
Kubernetes 容器核心參數設定

在日常維運容器化服務的過程中,我們有時候會遇到需要調整 Linux 核心參數的需求,例如調高最大連線數、修改 TCP 相關設定等。但容器畢竟不是一台完整的虛擬機,到底哪些核心參數可以在容器裡面改?又該怎麼改?這篇文章就來好好梳理一下。

核心觀念速覽

先把幾個重要觀念列出來:

  1. Linux 系統參數可分為全域參數Namespaced 參數兩大類
  2. 全域參數在主機上修改後,會影響所有容器
  3. 全域參數在主機和容器之間是互相連動的
  4. 全域參數不能透過 Kubernetes 的 SecurityContext 修改
  5. 全域參數中,有些在容器內「看得到」,有些則「看不到」,但都可以從主機端修改
  6. 看得到的全域參數,可以在特權容器中修改,且修改會影響主機與全部容器
  7. 看不到的全域參數,只能從主機端修改

什麼是 Namespaced 系統參數?

Namespaced 參數是指主機與各容器可以各自獨立設定的核心參數。目前 Linux Kernel 只有以下幾類支援 Namespace 隔離,其餘都歸類為全域參數:

參數類別說明
kernel.shm*共享記憶體相關
kernel.msg*Kernel 訊息佇列 / IPC 相關
kernel.sem信號量相關
fs.mqueue.*POSIX 訊息佇列相關
net.*網路子系統相關

要特別注意的是,net.* 底下仍有例外,像 net.netfilter.nf_conntrack_max(Kernel 3.10 時期)就屬於全域參數。

Kubernetes 的安全與非安全 sysctl 分類

Kubernetes 進一步將 Namespaced 參數區分為安全(safe)非安全(unsafe)兩種。安全參數必須滿足:

  • 不會影響同節點上的其他 Pod
  • 不會損害節點健康狀態
  • 不會讓 Pod 使用超出資源限制的 CPU 或記憶體

以下是目前 Kubernetes 預設允許的安全 sysctl 清單:

參數名稱用途備註
kernel.shm_rmid_forced強制回收不再使用的共享記憶體段,防止記憶體洩漏
net.ipv4.ip_local_port_range定義 TCP/UDP 選擇本機連接埠的範圍
net.ipv4.tcp_syncookies啟用 SYN cookies 機制防範 SYN flood 攻擊
net.ipv4.ping_group_range定義允許開啟 ICMP echo socket 的群組 ID 範圍
net.ipv4.ip_unprivileged_port_start指定非特權連接埠的起始埠號
net.ipv4.ip_local_reserved_ports保留特定連接埠給指定服務使用需 Kernel 3.16+

如果要使用非安全的 sysctl 參數,需要在 kubelet 啟動時加上白名單:

kubelet --allowed-unsafe-sysctls 'kernel.msg*,net.core.somaxconn' ...

Docker 容器中如何修改核心參數?

Dockerfile 本身無法直接修改核心參數,但可以在啟動容器時透過 --sysctl 參數傳入:

docker run -it --sysctl 'net.ipv4.ip_default_ttl=63' ubuntu sysctl net.ipv4.ip_default_ttl
# 輸出: net.ipv4.ip_default_ttl = 63

這邊有幾個要注意的地方:

  • 只有 Namespaced 參數才能這樣設定,否則會報 "invalid argument" 錯誤
  • 這個方式只在容器初始化階段生效,啟動後 /proc/sys 仍是唯讀掛載
  • 想在執行中的容器內用指令修改,必須使用 --privileged 模式

我實際測試過,在 Dockerfile 裡面透過 /etc/sysfs.conf 來設定也是行不通的:

FROM ubuntu:latest
RUN apt-get update && apt-get install -y sysfsutils
RUN echo "net.ipv4.ip_default_ttl = 63" >> /etc/sysfs.conf
CMD ["bash"]

啟動後進容器確認:

$ docker run -it --rm ubuntu:kernal /bin/bash
root@45c2e6c156d9:/# sysctl net.ipv4.ip_default_ttl
net.ipv4.ip_default_ttl = 64

root@45c2e6c156d9:/# sysctl net.ipv4.ip_default_ttl=63
sysctl: setting key "net.ipv4.ip_default_ttl", ignoring: Read-only file system

結果很明確,寫進 sysfs.conf 的設定並沒有生效,而且手動修改也因為唯讀檔案系統而失敗。

Kubernetes Pod sysctl 設定架構

在 Kubernetes Pod 中設定 Sysctl

在 Kubernetes 裡,可以透過 Pod 的 securityContext 來設定 Namespaced sysctl 參數,設定會套用到同一 Pod 中的所有容器:

apiVersion: v1
kind: Pod
metadata:
  name: sysctl-example
spec:
  securityContext:
    sysctls:
    - name: kernel.shm_rmid_forced
      value: "0"
    - name: net.core.somaxconn
      value: "1024"
    - name: kernel.msgmax
      value: "65536"

在這個範例中,安全參數和非安全參數在 YAML 裡的寫法完全一樣,Kubernetes 不會在語法層面做區分。

如果需要使用 unsafe sysctl 並搭配特權模式:

apiVersion: v1
kind: Pod
metadata:
  name: sysctl-example
  annotations:
    security.alpha.kubernetes.io/unsafe-sysctls: net.core.somaxconn=65535
spec:
  securityContext:
    privileged: true
  ...

但請務必注意,非安全 sysctl 參數本身具有不穩定性,使用不當可能導致容器異常、節點資源耗盡,甚至節點完全損壞。

那些無法 Namespace 隔離的參數怎麼辦?

對於沒有命名空間的 sysctl 參數(也就是節點層級的參數),如果需要修改,有幾種做法:

  1. 直接在每個節點的作業系統上手動設定
  2. 透過 DaemonSet 部署特權容器來批次修改
  3. 使用特權 InitContainer 在 Pod 啟動前修改(好處是不需要讓應用容器本身擁有特權)

重點回顧

情境做法
容器內 sysctl -a 看得到的 Namespaced 參數加入 kubelet 白名單後,透過 securityContext 修改
容器內 sysctl -a 看不到的全域參數只能從宿主機端修改
可見參數的範圍會隨 Kernel 版本而有差異

參考資料

分享這篇
X LinkedIn Facebook Hacker News Reddit

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料