当前位置: 首页 > 科技观察

如何使用Distroless使您的容器更安全

时间:2023-03-14 10:25:09 科技观察

PhotobyMichaelDziedziconUnsplash使用Distroless图像来保护Kubernetes上的容器。容器改变了我们看待技术基础设施的方式。这是我们运行应用程序方式的巨大飞跃。容器编排和云服务一起为我们提供了几乎无限规模的无缝可扩展性。根据定义,容器应该包含一个“应用程序”及其“运行时依赖项”。然而,实际上,它们包含的远不止这些。标准容器基础映像包括包管理器、shell和标准Linux发行版中的其他程序。虽然这些是构建容器镜像所必需的,但它们不应成为最终镜像的一部分。例如,一旦安装了包,就不再需要在容器中使用apt等包管理工具。这不仅会使您的容器充满不必要的包和程序,而且还为网络罪犯提供了利用特定程序漏洞的机会。您应该始终了解容器运行时中存在的内容,并且您应该将其精确限制为您的应用程序所需的依赖项。除了那些必要的,你不应该安装任何东西。一些领先的科技巨头,如谷歌,拥有多年在生产环境中运行容器的经验,并采用了这种方法。谷歌现在通过提供Distroless镜像向全世界开放这种能力。这些由Google构建的图像旨在仅包含您的应用程序及其依赖项,而它们不会具有常规Linux发行版的所有功能,包括shell。“这意味着虽然你可以像以前一样运行应用程序的容器,但是你不能在容器运行时进入容器。”这是一项重大的安全改进,因为您现在已经关闭了黑客进入您的容器的大门。Distroless基础镜像Google为大多数流行的编程语言和平台提供了Distroless基础镜像。以下基础镜像是官方发布的:gcr.io/distroless/static-debian10gcr.io/distroless/base-debian10gcr.io/distroless/java-debian10gcr.io/distroless/cc-debian10gcr.io/distroless/nodejs-debian10下的基础镜像仍然是实验性的,不推荐用于生产环境:gcr.io/distroless/python2.7-debian10gcr.io/distroless/python3-debian10gcr.io/distroless/java/jetty-debian10gcr。io/distroless/dotnet构建Distroless镜像Google在内部使用Bazel构建容器镜像,但我们可以使用Docker来做同样的事情。关于使用Distroless镜像的一个有争议的问题是:当我们有一个Distroless镜像时,我们如何使用Dockerfile来构建我们的应用程序?通常,Dockerfile以标准操作系统基础映像开始,然后创建适当的运行时。构建时需要执行几个步骤。这包括安装包,为此需要像apt或yum这样的包管理器。有两种方法:在Docker外部构建应用程序,然后使用Dockerfile中的ADD或COPY指令将二进制包复制到容器中。使用多阶段Docker构建。这是Docker17.05及更高版本中的一项新功能,它允许您将构建分成不同的阶段。第一阶段可以从帮助您构建应用程序的标准操作系统基础映像开始;第二阶段可以简单地从第一阶段获取构建文件并使用Distroless作为基础镜像。要了解它的工作原理,让我们使用多阶段构建过程进行动手练习。先决条件您将需要以下内容:用于构建映像的Docker17.05或更高版本可选的Kubernetes集群用于实践练习的第二部分。如果你想在Docker中运行你的容器,你可以使用等效的docker命令。GitHub代码库作为一个实践练习,将这个代码库fork到你的GitHub帐户,然后克隆GitHub代码库并使用cd进入项目目录。这个存储库包含一个PythonFlask应用程序,它响应HelloWorld!当您调用API时。app.py文件如下所示:fromflaskimportFlaskapp=Flask(__name__)@app.route("/")defhello():return"HelloWorld!"if__name__=='__main__':app.run(host='0.0.0.0',debug=True)Dockerfile包含两个阶段:FROMpython:2.7-slimASbuildADD./appWORKDIR/appRUNpipinstall--upgradepipRUNpipinstall-r./requirements.txtFROMgcr.io/distroless/python2.7COPY--from=build/app/appCOPY--from=build/usr/local/lib/python2.7/site-packages/usr/local/lib/python2.7/site-packagesWORKDIR/appENVPYTHONPATH=/usr/local/lib/python2.7/site-packagesEXPOSE5000CMD["app.py"]构建阶段:从python:2.7-slim的基础镜像开始复制应用到/app目录升级pip并安装依赖Distroless阶段:从gcr.io/distroless/python2.7的基础镜像开始将应用程序从build阶段的/app目录复制到当前阶段的/app目录将python的site-packages从build阶段复制到当前阶段的site-packagesdirectory设置工作目录为/app,设置pythonPATH为site-packages目录,暴露端口5000。使用CMD命令运行app.py。由于Disroless镜像不包含shell,所以最后应该使用CMD命令。如果你不这样做,Docker会认为它是一个shellCMD并尝试这样执行它,但这是行不通的。构建镜像:$dockerbuild-t/flask-hello-world-distroless.SendingbuildcontexttoDockerdaemon95.74kBStep1/12:FROMpython:2.7-slimASbuild--->eeb27ee6b893Step2/12:ADD./app--->a01dc81df193Step3/12WORKDIR/app--->Runningin48ccf6b990e4Removingintermediatecontainer48ccf6b990e4--->2e5e335be678Step4/12:RUNpipinstall--upgradepip--->Runningin583be3d0b8ccCollectingpipDownloadingpip-20.1.1-py2.py3-none-any.whl(1.5MB)Installingcollectedpackages:pipAttemptinguninstall:pipFoundexistinginstallation:pip20.0.2Uninstallingpip-20.0.2:Successfullyuninstalledpip-20.0.2Successfullyinstalledpip-20.1.1Removingintermediatecontainer583be3d0b8cc..................................SuccessfullyinstalledJinja2-2.11.2MarkupSafe-0.23click-7.1.2flask-1.1.2itsdangerous-0.24werkzeug-1.0.1Removingintermediatecontainerc4d00b1abf4a--->01cbadcc531fStep6/12:FROMgcr.io/distroless/python2.4326-2->7192-9-2步骤->COPY--from=build/app/app--->92657682cdccStep8/12:COPY--from=build/usr/local/lib/python2.7/site-packages/usr/local/lib/python2.7/site-packages--->faafd06edeacStep9/12:WORKDIR/app--->Runningin0cf545aa0e62Removingintermediatecontainer0cf545aa0e62--->4c4af4333209Step10/12:ENVPYTHONPATH=/usr/local/lib/python2.7/site-packages--->Runningin681ae3cd51ccRemovingintermediatecontainer681ae3cd51cc--->564f48eff90aStep11/12:EXPOSE5000--->Runningin7ff5c073d568Removingintermediatecontainer7ff5c073d568--->ccc3d211d295Step12/12:CMD["app.py"]--->Runningin2b2c2f111423Removingintermediatecontainer2b2c2f111423--->76d13d2f61cdSuccessfullybuilt76d13d2f61cdSuccessfullytagged/flask-hello-world-distroless:latest登录到DockerHub并推送镜像:dockerlogindockerpush/flask-hello-world-distroless:latest登录到DockerHub(或您的私有存储库),您应该会看到可用的容器映像:distroless-2如果您查看压缩后的大小,如果您使用slim发行版作为基础,它只有23.36MB图像,它无线将占用56MB。distroless-2您已经将容器占用空间减少了一半以上。太棒了!在Kubernetes中运行容器为了测试构建是否有效,让我们在Kubernetes集群中运行容器。如果您没有Kubernetes,您可以运行等效的Docker命令来执行相同的活动,因为Kubectl和Docker命令是相似的。我在代码存储库中创建了一个kubernetes.yaml文件,其中包含使用我们构建的图像的部署和负载均衡服务。---apiVersion:apps/v1kind:Deploymentmetadata:name:flask-deploymentspec:selector:matchLabels:app:flaskreplicas:2template:metadata:labels:app:flaskspec:containers:-name:flaskimage:bharamicrosystems/flask-hello-world-distrolessports:-containerPort:5000---apiVersion:v1kind:Servicemetadata:name:flask-servicespec:selector:app:flaskports:-port:80targetPort:5000type:LoadBalancer这是一个非常简单的设置。负载均衡器侦听端口80并映射到目标端口5000。这些Pod在默认端口5000上侦听Flask应用程序。Application:$kubectlapply-fkubernetes.yamldeployment.apps/flask-deploymentcreatedservice/flask-servicecreatedLet'scheckallresourcestoseewhatwehavecreated:$kubectlgetallNAMEREADYSTATUSRESTARTSAGEpod/flask-deployment-576496558b-hnbxt1/1Running047spod/flask-deployment576496558b-hszpq1/1Running073sNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGEservice/flask-serviceLoadBalancer10.8.9.16335.184.113.12080:31357/TCP86sservice/kubernetesClusterIP10.8.0.1443/TCP26mNAMEREADYUP-TO-DATEAVAILABLEAGEdeployment.apps/flask-deployment2/22288sNAMEDESIREDCURRENTREADYAGEreplicaset.apps/flask-deployment-576496558b22289s我们看到有两个Pod,一个Deployment,一个具有外部IP的LoadBalancer服务和一个ReplicaSet。让我们访问应用程序:$curlhttp://35.184.113.120HelloWorld!我们得到HelloWorld!。这表明Flask应用程序运行正常。使用shell访问应用程序正如我在介绍中所描述的,Disroless容器中没有shell,因此无法侵入容器。但是,让我们尝试在容器中执行:$kubectlexec-itflask-deployment-576496558b-hnbxt/bin/bashOCIruntimeexecfailed:execfailed:container_linux.go:349:startingcontainerprocesscaused"exec:\"/bin/bash\":stat/bin/bash:nosuchfileordirectory":unknowncommandterminatedwithexitcode126我们无法连接到容器。容器日志呢?如果无法访问容器日志,我们将无法调试应用程序。让我们尝试获取日志:$kubectllogsflask-deployment-576496558b-hnbxt*Runningonhttp://0.0.0.0:5000/*Restartingwithreloader10.128.0.4--[31/May/202013:40:27]"GET/HTTP/1.1"200-10.128.0.3--[31/May/202013:42:01]"GET/HTTP/1.1"200-这样可以获取容器日志!结论使用Distroless作为基础镜像是一种命令人们对保持容器安全的方式感到兴奋。由于镜像很小并且只包含应用程序和依赖项,它为应用程序提供了最小的攻击面。它在更大程度上提高了应用程序的安全性,因此它是保护容器的好方法。