使用Distroless提高容器安全
使用 Distroless 鏡像來(lái)保護(hù) Kubernetes 上的容器
容器改變了我們看待技術(shù)基礎(chǔ)設(shè)施的方式。這是我們運(yùn)行應(yīng)用程序方式的一次巨大飛躍。容器編排和云服務(wù)一起為我們提供了一種近乎無(wú)限規(guī)模的無(wú)縫擴(kuò)展能力。
根據(jù)定義,容器應(yīng)該包含 應(yīng)用程序 及其 運(yùn)行時(shí)依賴(lài)項(xiàng)。然而,在現(xiàn)實(shí)中,它們包含的遠(yuǎn)不止這些。標(biāo)準(zhǔn)容器基礎(chǔ)映像包含標(biāo)準(zhǔn) Linux
發(fā)行版中可以找到的包管理器、shell
和其他程序。
雖然這些都是構(gòu)建容器鏡像所必需的,但它們不應(yīng)該成為最終鏡像的一部分。例如,一旦你把包安裝好了,就不再需要在容器中使用 apt
等包管理工具了。
這不僅使你的容器里充滿(mǎn)了不必要的軟件包和程序,而且還為網(wǎng)絡(luò)罪犯提供了攻擊特定程序漏洞的機(jī)會(huì)。
你應(yīng)該始終了解容器運(yùn)行時(shí)中存在什么,并且應(yīng)該精確地限制其只包含應(yīng)用程序所需的依賴(lài)項(xiàng)。
除了那些必要的,你不應(yīng)該安裝任何東西。一些領(lǐng)先的科技巨頭,如谷歌,有多年在生產(chǎn)中運(yùn)行容器的經(jīng)驗(yàn),已經(jīng)采用了這種方法。
谷歌現(xiàn)在通過(guò)提供 Distroless
鏡像向全世界開(kāi)放這種能力。谷歌構(gòu)建的這些鏡像的目標(biāo)是只包含你的應(yīng)用程序及其依賴(lài)項(xiàng),同時(shí)它們將沒(méi)有常規(guī) Linux
發(fā)行版的所有特性,包括 shell
。
這意味著雖然可以想以前一樣運(yùn)行應(yīng)用程序的容器,但不能在容器運(yùn)行的時(shí)候進(jìn)入容器內(nèi)。這是一個(gè)重大的安全改進(jìn),因?yàn)槟悻F(xiàn)在已經(jīng)為黑客通過(guò) shell
進(jìn)入你的容器關(guān)上了大門(mén)。
Distroless 基礎(chǔ)鏡像
谷歌為大多數(shù)流行的編程語(yǔ)言和平臺(tái)提供了 Distroless
的基礎(chǔ)鏡像。
以下基礎(chǔ)鏡像是正式發(fā)布的版本:
- gcr.io/distroless/static-debian10
- gcr.io/distroless/base-debian10
- gcr.io/distroless/java-debian10
- gcr.io/distroless/cc-debian10
- gcr.io/distroless/nodejs-debian10
下面的基礎(chǔ)鏡像仍在實(shí)驗(yàn)階段,不推薦用于生產(chǎn)環(huán)境:
- gcr.io/distroless/python2.7-debian10
- gcr.io/distroless/python3-debian10
- gcr.io/distroless/java/jetty-debian10
- gcr.io/distroless/dotnet
構(gòu)建 Distroless 鏡像
谷歌在內(nèi)部使用 Bazel 來(lái)構(gòu)建容器映像,但是我們可以使用 Docker
來(lái)做同樣的事情。關(guān)于使用 Distroless
鏡像的一個(gè)有爭(zhēng)議的問(wèn)題是:當(dāng)我們有一個(gè) Distroless
鏡像時(shí),我們?nèi)绾问褂?nbsp;Dockerfile
來(lái)構(gòu)建我們的應(yīng)用程序呢?
通常,Dockerfile
以一個(gè)標(biāo)準(zhǔn)的 OS 基礎(chǔ)鏡像開(kāi)始,然后是創(chuàng)建適當(dāng)?shù)倪\(yùn)行時(shí)構(gòu)建所需執(zhí)行的多個(gè)步驟。這包括包的安裝,為此需要像 apt
或 yum
這樣的包管理器。
有兩種方法:
- 先在
Docker
外部構(gòu)建好你的應(yīng)用程序,然后使用Dockerfile
中的 ADD 或 COPY 指令將二進(jìn)制包復(fù)制到容器中。 - 使用多階段
Docker
構(gòu)建。這是 Docker 17.05 及以后版本的一個(gè)新特性,它允許你將構(gòu)建分為不同的階段。第一階段可以從標(biāo)準(zhǔn)的 OS 基礎(chǔ)鏡像開(kāi)始,可以幫助你構(gòu)建應(yīng)用程序;第二階段可以簡(jiǎn)單地從第一階段獲取構(gòu)建的文件并使用Distroless
作為基礎(chǔ)鏡像。
為了理解它是如何工作的,讓我們使用多階段構(gòu)建流程進(jìn)行一個(gè)實(shí)際操作練習(xí)。
必要條件
你需要具備以下內(nèi)容:
- Docker 版本大于等于 17.05,用于構(gòu)建鏡像
- 可選的
Kubernetes
集群用于實(shí)踐練習(xí)的第二部分。如果你想在Docker
中運(yùn)行你的容器,你可以使用等價(jià)的docker
命令。
GitHub 代碼倉(cāng)
作為實(shí)踐練習(xí),將 此代碼倉(cāng) Fork 到你的 GitHub 帳號(hào)下,然后克隆 GitHub 代碼倉(cāng)并使用 cd
進(jìn)入到項(xiàng)目目錄下。
該代碼倉(cāng)包含一個(gè) Python
的 Flask
應(yīng)用程序,當(dāng)你調(diào)用API時(shí),該應(yīng)用程序會(huì)響應(yīng) Hello World!
。
app.py
文件如下所示:
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == '__main__': app.run(host='0.0.0.0', debug=True)
Dockerfile 包含兩個(gè)階段:
FROM python:2.7-slim AS build ADD . /app WORKDIR /app RUN pip install --upgrade pip RUN pip install -r ./requirements.txt FROM gcr.io/distroless/python2.7 COPY --from=build /app /app COPY --from=build /usr/local/lib/python2.7/site-packages /usr/local/lib/python2.7/site-packages WORKDIR /app ENV PYTHONPATH=/usr/local/lib/python2.7/site-packages EXPOSE 5000 CMD ["app.py"]
構(gòu)建階段:
- 從 python:2.7-slim 的基礎(chǔ)鏡像開(kāi)始
- 將應(yīng)用程序復(fù)制到 /app 目錄下
- 升級(jí)
pip
并安裝依賴(lài)
Distroless 階段:
- 從 gcr.io/distroless/python2.7 的基礎(chǔ)鏡像開(kāi)始
- 將應(yīng)用程序從構(gòu)建階段的 /app 目錄復(fù)制到當(dāng)前階段的 /app 目錄
- 將 python 的
site-packages
從構(gòu)建階段復(fù)制到當(dāng)前階段的site-packages
目錄 - 設(shè)置工作目錄到 /app,將 python PATH 設(shè)置為
site-packages
目錄,并暴露 5000 端口 - 使用
CMD
指令運(yùn)行app.py
由于 Disroless
鏡像不包含 shell
,所以應(yīng)該在最后使用 CMD
指令。如果不這樣做,Docker 將認(rèn)為它是一個(gè) shell CMD,并試圖這樣執(zhí)行它,但這是不工作的。
構(gòu)建鏡像:
$ docker build -t <your_docker_repo>/flask-hello-world-distroless . Sending build context to Docker daemon 95.74kB Step 1/12 : FROM python:2.7-slim AS build ---> eeb27ee6b893 Step 2/12 : ADD . /app ---> a01dc81df193 Step 3/12 : WORKDIR /app ---> Running in 48ccf6b990e4 Removing intermediate container 48ccf6b990e4 ---> 2e5e335be678 Step 4/12 : RUN pip install --upgrade pip ---> Running in 583be3d0b8cc Collecting pip Downloading pip-20.1.1-py2.py3-none-any.whl (1.5 MB) Installing collected packages: pip Attempting uninstall: pip Found existing installation: pip 20.0.2 Uninstalling pip-20.0.2: Successfully uninstalled pip-20.0.2 Successfully installed pip-20.1.1 Removing intermediate container 583be3d0b8cc ................................... Successfully installed Jinja2-2.11.2 MarkupSafe-0.23 click-7.1.2 flask-1.1.2 itsdangerous-0.24 werkzeug-1.0.1 Removing intermediate container c4d00b1abf4a ---> 01cbadcc531f Step 6/12 : FROM gcr.io/distroless/python2.7 ---> 796952c43cc4 Step 7/12 : COPY --from=build /app /app ---> 92657682cdcc Step 8/12 : COPY --from=build /usr/local/lib/python2.7/site-packages /usr/local/lib/python2.7/site-packages ---> faafd06edeac Step 9/12 : WORKDIR /app ---> Running in 0cf545aa0e62 Removing intermediate container 0cf545aa0e62 ---> 4c4af4333209 Step 10/12 : ENV PYTHONPATH=/usr/local/lib/python2.7/site-packages ---> Running in 681ae3cd51cc Removing intermediate container 681ae3cd51cc ---> 564f48eff90a Step 11/12 : EXPOSE 5000 ---> Running in 7ff5c073d568 Removing intermediate container 7ff5c073d568 ---> ccc3d211d295 Step 12/12 : CMD ["app.py"] ---> Running in 2b2c2f111423 Removing intermediate container 2b2c2f111423 ---> 76d13d2f61cd Successfully built 76d13d2f61cd Successfully tagged <your_docker_repo>/flask-hello-world-distroless:latest
登錄到 DockerHub 并推送鏡像:
docker login docker push <your_docker_repo>/flask-hello-world-distroless:latest
登錄到 DockerHub(或者你的私有鏡像倉(cāng)),你應(yīng)該會(huì)看到容器鏡像可以使用
如果你看一下壓縮后的大小,它只有 23.36 MB。如果你使用 slim
發(fā)行版作為基礎(chǔ)鏡像,它將占用 56 MB。
你已經(jīng)減少了超過(guò)一半的容器占用空間。That’s amazing!
在 Kubernetes 中運(yùn)行容器
為了測(cè)試構(gòu)建是否有效,讓我們?cè)?Kubernetes 集群中運(yùn)行容器。如果你沒(méi)有 Kubernetes,你可以運(yùn)行等價(jià)的 Docker 命令來(lái)做相同的活動(dòng),因?yàn)?Kubectl 和 Docker 命令是相似的。
我在代碼倉(cāng)中創(chuàng)建了一個(gè) kubernetes.yaml 文件,該文件包含使用我們構(gòu)建的鏡像的 Deployment
和 負(fù)載均衡的 Service
。
--- apiVersion: apps/v1 kind: Deployment metadata: name: flask-deployment spec: selector: matchLabels: app: flask replicas: 2 template: metadata: labels: app: flask spec: containers: - name: flask image: bharamicrosystems/flask-hello-world-distroless ports: - containerPort: 5000 --- apiVersion: v1 kind: Service metadata: name: flask-service spec: selector: app: flask ports: - port: 80 targetPort: 5000 type: LoadBalancer
這是一個(gè)非常簡(jiǎn)單的設(shè)置。負(fù)載均衡器監(jiān)聽(tīng)端口 80 并映射到目標(biāo)端口 5000。這些 Pods 在默認(rèn)的 5000 端口上監(jiān)聽(tīng) Flask 應(yīng)用程序。
應(yīng)用:
$ kubectl apply -f kubernetes.yaml deployment.apps/flask-deployment created service/flask-service created
我們查看一下所有的資源,看看我們已經(jīng)創(chuàng)建了什么:
$ kubectl get all NAME READY STATUS RESTARTS AGE pod/flask-deployment-576496558b-hnbxt 1/1 Running 0 47s pod/flask-deployment-576496558b-hszpq 1/1 Running 0 73s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/flask-service LoadBalancer 10.8.9.163 35.184.113.120 80:31357/TCP 86s service/kubernetes ClusterIP 10.8.0.1 <none> 443/TCP 26m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/flask-deployment 2/2 2 2 88s NAME DESIRED CURRENT READY AGE replicaset.apps/flask-deployment-576496558b 2 2 2 89s
我們看到存在兩個(gè) Pods
、一個(gè) Deployment
、一個(gè)帶有外部 IP 的 LoadBalancer
服務(wù)和一個(gè) ReplicaSet
。
讓我們?cè)L問(wèn)應(yīng)用程序:
$ curl http://35.184.113.120 Hello World!
我們得到了 Hello World!
。這表明 Flask 應(yīng)用程序在正常工作。
使用 Shell 對(duì)應(yīng)用程序進(jìn)行訪問(wèn)
正如我在引言中所描述的,Disroless
容器中沒(méi)有 shell
,因此不可能進(jìn)入到容器內(nèi)。然而,讓我們?cè)囍谌萜髦袌?zhí)行 exec:
$ kubectl exec -it flask-deployment-576496558b-hnbxt /bin/bash OCI runtime exec failed: exec failed: container_linux.go:349: starting container process caused "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unknown command terminated with exit code 126
我們無(wú)法連接到容器上。
容器日志呢?如果拿不到容器日志,我們就失去了調(diào)試應(yīng)用程序的方法。
讓我們?cè)囍ツ萌罩荆?/p>
$ kubectl logs flask-deployment-576496558b-hnbxt * Running on http://0.0.0.0:5000/ * Restarting with reloader 10.128.0.4 - - [31/May/2020 13:40:27] "GET / HTTP/1.1" 200 - 10.128.0.3 - - [31/May/2020 13:42:01] "GET / HTTP/1.1" 200 -
所以容器日志是可以被獲取到的!
結(jié)論
使用 Distroless
作為基礎(chǔ)鏡像是一種令人興奮的保護(hù)容器安全的方式。由于鏡像小并且僅包含應(yīng)用程序和依賴(lài)項(xiàng),因此它為應(yīng)用程序提供了最小的攻擊面。它在更大程度上提高了應(yīng)用程序的安全性,所以它是保護(hù)容器安全的好方法。
翻譯自 How to Harden Your Containers With Distroless Docker Images
以上就是使用Distroless提高容器安全的詳細(xì)內(nèi)容,更多關(guān)于Distroless容器安全的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Docker 制作鏡像Dockerfile和commit操作
這篇文章主要介紹了Docker 制作鏡像Dockerfile和commit操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11Docker跨主機(jī)網(wǎng)絡(luò)(manual)的實(shí)現(xiàn)
這篇文章主要介紹了Docker跨主機(jī)網(wǎng)絡(luò)(manual)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Springboot使用docker-compose實(shí)現(xiàn)動(dòng)態(tài)配置過(guò)程
這篇文章主要介紹了Springboot使用docker-compose實(shí)現(xiàn)動(dòng)態(tài)配置全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03maven構(gòu)建docker鏡像push到鏡像倉(cāng)庫(kù)方式
這篇文章主要介紹了maven構(gòu)建docker鏡像push到鏡像倉(cāng)庫(kù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Dockerfile中multi-stage(多階段構(gòu)建)詳解
在2017年5月3日即將發(fā)行的 Docker 17.05.0-ce 中,Docker 官方提供了簡(jiǎn)便的多階段構(gòu)建 (multi-stage build) 方案,下面這篇文章主要給大家介紹了關(guān)于Dockerfile中multi-stage(多階段構(gòu)建)的相關(guān)資料,需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03Linux系統(tǒng)通過(guò)Docker安裝SQL?Server數(shù)據(jù)庫(kù)
這篇文章介紹了Linux系統(tǒng)通過(guò)Docker安裝SQL?Server數(shù)據(jù)庫(kù)的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03