使用 Distroless
鏡像來保護 Kubernetes
上的容器。python
容器改變了咱們看待技術基礎設施的方式。這是咱們運行應用程序方式的一次巨大飛躍。容器編排和雲服務一塊兒爲咱們提供了一種近乎無限規模的無縫擴展能力。linux
根據定義,容器應該包含 應用程序 及其 運行時依賴項。然而,在現實中,它們包含的遠不止這些。標準容器基礎映像包含標準 Linux
發行版中能夠找到的包管理器、shell
和其餘程序。git
雖然這些都是構建容器鏡像所必需的,但它們不該該成爲最終鏡像的一部分。例如,一旦你把包安裝好了,就再也不須要在容器中使用 apt
等包管理工具了。github
這不只使你的容器裏充滿了沒必要要的軟件包和程序,並且還爲網絡罪犯提供了攻擊特定程序漏洞的機會。docker
你應該始終了解容器運行時中存在什麼,而且應該精確地限制其只包含應用程序所需的依賴項。shell
除了那些必要的,你不該該安裝任何東西。一些領先的科技巨頭,如谷歌,有多年在生產中運行容器的經驗,已經採用了這種方法。編程
谷歌如今經過提供 Distroless
鏡像向全世界開放這種能力。谷歌構建的這些鏡像的目標是隻包含你的應用程序及其依賴項,同時它們將沒有常規 Linux
發行版的全部特性,包括 shell
。flask
這意味着雖然能夠想之前同樣運行應用程序的容器,但不能在容器運行的時候進入容器內。這是一個重大的安全改進,由於你如今已經爲黑客經過 shell
進入你的容器關上了大門。api
谷歌爲大多數流行的編程語言和平臺提供了 Distroless
的基礎鏡像。安全
如下基礎鏡像是正式發佈的版本:
Docker
來作一樣的事情。關於使用 Distroless
鏡像的一個有爭議的問題是:當咱們有一個 Distroless
鏡像時,咱們如何使用 Dockerfile
來構建咱們的應用程序呢?一般,Dockerfile
以一個標準的 OS 基礎鏡像開始,而後是建立適當的運行時構建所需執行的多個步驟。這包括包的安裝,爲此須要像 apt
或 yum
這樣的包管理器。
有兩種方法:
Docker
外部構建好你的應用程序,而後使用 Dockerfile
中的 ADD 或 COPY 指令將二進制包複製到容器中。Docker
構建。這是 Docker 17.05 及之後版本的一個新特性,它容許你將構建分爲不一樣的階段。第一階段能夠從標準的 OS 基礎鏡像開始,能夠幫助你構建應用程序;第二階段能夠簡單地從第一階段獲取構建的文件並使用 Distroless
做爲基礎鏡像。爲了理解它是如何工做的,讓咱們使用多階段構建流程進行一個實際操做練習。
你須要具有如下內容:
Kubernetes
集羣用於實踐練習的第二部分。若是你想在 Docker
中運行你的容器,你可使用等價的 docker
命令。做爲實踐練習,將 此代碼倉 Fork 到你的 GitHub 賬號下,而後克隆 GitHub 代碼倉並使用 cd
進入到項目目錄下。
該代碼倉包含一個 Python
的 Flask
應用程序,當你調用API時,該應用程序會響應 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 包含兩個階段:
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"]
構建階段:
pip
並安裝依賴Distroless 階段:
site-packages
從構建階段複製到當前階段的 site-packages
目錄site-packages
目錄,並暴露 5000 端口CMD
指令運行 app.py
因爲 Disroless
鏡像不包含 shell
,因此應該在最後使用 CMD
指令。若是不這樣作,Docker 將認爲它是一個 shell CMD,並試圖這樣執行它,但這是不工做的。
構建鏡像:
$ 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(或者你的私有鏡像倉),你應該會看到容器鏡像可使用:
若是你看一下壓縮後的大小,它只有 23.36 MB。若是你使用 slim
發行版做爲基礎鏡像,它將佔用 56 MB。
你已經減小了超過一半的容器佔用空間。That’s amazing!
爲了測試構建是否有效,讓咱們在 Kubernetes 集羣中運行容器。若是你沒有 Kubernetes,你能夠運行等價的 Docker 命令來作相同的活動,由於 Kubectl 和 Docker 命令是類似的。
我在代碼倉中建立了一個 kubernetes.yaml 文件,該文件包含使用咱們構建的鏡像的 Deployment
和 負載均衡的 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
這是一個很是簡單的設置。負載均衡器監聽端口 80 並映射到目標端口 5000。這些 Pods 在默認的 5000 端口上監聽 Flask 應用程序。
應用:
$ kubectl apply -f kubernetes.yaml deployment.apps/flask-deployment created service/flask-service created
咱們查看一下全部的資源,看看咱們已經建立了什麼:
$ 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
咱們看到存在兩個 Pods
、一個 Deployment
、一個帶有外部 IP 的 LoadBalancer
服務和一個 ReplicaSet
。
讓咱們訪問應用程序:
$ curl http://35.184.113.120 Hello World!
咱們獲得了 Hello World!
。這代表 Flask 應用程序在正常工做。
正如我在引言中所描述的,Disroless
容器中沒有 shell
,所以不可能進入到容器內。然而,讓咱們試着在容器中執行 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
咱們沒法鏈接到容器上。
容器日誌呢?若是拿不到容器日誌,咱們就失去了調試應用程序的方法。
讓咱們試着去拿日誌:
$ 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 -
因此容器日誌是能夠被獲取到的!
使用 Distroless
做爲基礎鏡像是一種使人興奮的保護容器安全的方式。因爲鏡像小而且僅包含應用程序和依賴項,所以它爲應用程序提供了最小的攻擊面。它在更大程度上提升了應用程序的安全性,因此它是保護容器安全的好方法。
謝謝閱讀!我但願你喜歡這篇文章。
本文翻譯自 How to Harden Your Containers With Distroless Docker Images