在上一篇《容器安全實踐(一):概念篇》中,我們深入探討了容器安全的底層原理,并糾正了“容器天生安全”的誤解。我們了解了 root
用戶的雙重身份,以及特權容器的危險性。
然而,僅僅了解這些概念是不夠的。真正的安全,需要從源頭開始,貫穿容器的整個生命周期。本文將深入我們討論的幾個核心議題:runAsNonRoot
的真實意義、鏡像構建與運行時權限的契約,以及如何構建一個從 Dockerfile
到 Kubernetes Pod 的完整安全防線。
一、runAsNonRoot
:不只是一個開關
許多人認為 runAsNonRoot: true
只是一個簡單的安全開關,用來阻止 root
用戶運行容器。但這個配置背后,隱藏著一個更重要的安全理念:徹底放棄 root
身份。
當我們為 Pod 配置 runAsUser: 1001
和 runAsNonRoot: true
時,Kubernetes 并不會先以 root
身份啟動容器再切換用戶。它會從最開始就強制容器進程以 UID 1001
的身份運行。這意味著:
- 身份的根本性轉變:容器內的進程從誕生之初,就不是
root
。我們之前討論的“假root
”身份,在這個場景下根本不存在。 - 從源頭規避風險:你的應用無法利用任何需要
root
權限的漏洞,因為它沒有這些權限。這包括了綁定特權端口、修改系統文件或執行某些高危內核操作。
因此,runAsNonRoot: true
不僅僅是一個簡單的“拒絕”配置,它是一個聲明,聲明你的容器將完全放棄 root
的身份,從而進入一個更安全、權限更受限的運行環境。
二、權限的起點:構建鏡像的藝術
你無法在 Pod 運行階段憑空創造權限,所有的權限都必須在容器鏡像構建時就得到妥善處理。這就像是在出廠前就給產品貼上正確的標簽。
1. 黃金法則:構建時用 root
,運行時用非 root
這是一個被廣泛認可的最佳實踐。其核心思想是,利用 root
用戶的便利性來完成所有必須的構建任務,然后將運行時環境鎖定在最小權限。
2. Dockerfile
的實踐步驟
下面,我們將把這個黃金法則分解為具體的 Dockerfile
實踐步驟,確保你的鏡像既安全又功能完善。
步驟 1:從一個精簡的基礎鏡像開始
選擇一個輕量且安全的父鏡像,這能從一開始就減少不必要的系統組件和潛在的漏洞。像 alpine
、distroless
或一些語言官方提供的 slim 版本都是很好的選擇。
# 這是一個基于 Alpine 的示例
FROM alpine:3.18
步驟 2:在構建時創建非 root
用戶
在鏡像構建階段,使用 root
權限創建你的應用用戶。為了和 Kubernetes runAsUser
的配置保持一致,最好為其指定一個固定的 UID,比如 1001
。
# 使用 root 權限創建 myuser,并指定 UID 為 1001
RUN adduser -D -u 1001 myuser
步驟 3:處理文件權限
這是最關鍵的一步。當你在 Dockerfile
中復制應用文件時,它們默認都屬于 root
。你必須在切換用戶前,將文件的所有權轉移給你的非 root
用戶,否則應用將無法訪問或執行這些文件。
你可以選擇以下兩種方式:
-
方式一(推薦):
COPY --chown
這是最簡潔的方法,它在復制文件的同時直接指定所有者。# 復制 package.json,并立即將其所有權轉移給 myuser COPY --chown=myuser:myuser package*.json ./# 運行 npm install,這里依然是 root 權限 RUN npm install# 復制所有應用代碼,并指定所有者 COPY --chown=myuser:myuser . .
-
方式二:
RUN chown
如果你的 Docker 版本較舊,不支持chown
參數,可以使用RUN
命令來完成。# 復制所有文件 COPY . . # 使用 root 權限,將整個應用目錄的所有權轉移給 myuser RUN chown -R myuser:myuser ./
步驟 4:在末尾切換用戶
這是 Dockerfile
的最后一步,也是最重要的一步。USER
指令告訴 Docker,從這里開始,所有后續的命令(包括 CMD
和 ENTRYPOINT
)都將以這個非 root
用戶身份執行。
# 切換到非 root 用戶
USER myuser# 啟動你的應用程序
CMD ["node", "app.js"]
三、Pod 部署:在 Kubernetes 中建立“契約”
在 Kubernetes 中,securityContext
是你與鏡像構建者(通常是團隊的另一位成員,甚至是自己)之間建立的“權限契約”。這個契約的核心是一致性。
為了確保你的 Pod 安全地運行,你的 Pod YAML 應該強制執行與 Dockerfile
中約定的權限。
securityContext
的終極組合拳
一個健壯且安全的 Pod 部署 YAML,應該包含以下關鍵配置:
runAsUser: 1001
:強制容器以 UID1001
運行。這是與Dockerfile
的UID 契約。runAsNonRoot: true
:一個額外的安全檢查,確保容器不會以root
身份啟動。fsGroup: 1001
:當 Pod 掛載數據卷時,確保其所有權屬于1001
組,從而解決非root
用戶寫入權限的問題。readOnlyRootFilesystem: true
:將容器的根文件系統設置為只讀,防止任何運行時篡改。capabilities
:精準地添加或移除內核能力,遵循最小權限原則。
一個遵循這些原則的 YAML 模板如下:
apiVersion: v1
kind: Pod
metadata:name: secure-app
spec:securityContext:runAsUser: 1001runAsNonRoot: truefsGroup: 1001readOnlyRootFilesystem: truecontainers:- name: app-containerimage: my-secure-image:latestsecurityContext:capabilities:# 移除所有不必要的默認特權drop:- ALL# 僅添加應用必須的特權,例如綁定特權端口# 注意:如果你的應用不需要,這里應該為空add:- NET_BIND_SERVICEvolumeMounts:- name: data-volumemountPath: /datavolumes:- name: data-volumeemptyDir: {}
總結:容器安全是一場接力賽
容器安全不是一個單一的工具或配置,它是一場從鏡像構建到 Pod 運行的“接力賽”。
- 第一棒:在
Dockerfile
中,你負責權限的初始化和準備。 - 第二棒:在 Kubernetes 中,你負責權限的強制執行和加固。
通過理解和實踐這種分層防御,你將能夠構建一個真正健壯、可靠且難以被攻破的容器化應用環境。