学习
实践
活动
专区
工具
TVP
写文章
专栏首页翻译计划如何使用 Distroless 让你的容器更加安全

如何使用 Distroless 让你的容器更加安全

使用 Distroless 镜像来保护 Kubernetes 上的容器。

容器改变了我们看待技术基础设施的方式。这是我们运行应用程序方式的一次巨大飞跃。容器编排和云服务一起为我们提供了一种近乎无限规模的无缝扩展能力。

根据定义,容器应该包含 应用程序 及其 运行时依赖项。然而,在现实中,它们包含的远不止这些。标准容器基础映像包含标准 Linux 发行版中可以找到的包管理器、shell 和其他程序。

虽然这些都是构建容器镜像所必需的,但它们不应该成为最终镜像的一部分。例如,一旦你把包安装好了,就不再需要在容器中使用 apt 等包管理工具了。

这不仅使你的容器里充满了不必要的软件包和程序,而且还为网络罪犯提供了攻击特定程序漏洞的机会。

你应该始终了解容器运行时中存在什么,并且应该精确地限制其只包含应用程序所需的依赖项。

除了那些必要的,你不应该安装任何东西。一些领先的科技巨头,如谷歌,有多年在生产中运行容器的经验,已经采用了这种方法。

谷歌现在通过提供 Distroless 镜像向全世界开放这种能力。谷歌构建的这些镜像的目标是只包含你的应用程序及其依赖项,同时它们将没有常规 Linux 发行版的所有特性,包括 shell

这意味着虽然可以想以前一样运行应用程序的容器,但不能在容器运行的时候进入容器内。这是一个重大的安全改进,因为你现在已经为黑客通过 shell 进入你的容器关上了大门。

Distroless 基础镜像

谷歌为大多数流行的编程语言和平台提供了 Distroless 的基础镜像。

以下基础镜像是正式发布的版本:

下面的基础镜像仍在实验阶段,不推荐用于生产环境:

构建 Distroless 镜像

谷歌在内部使用 Bazel 来构建容器映像,但是我们可以使用 Docker 来做同样的事情。关于使用 Distroless 镜像的一个有争议的问题是:当我们有一个 Distroless 镜像时,我们如何使用 Dockerfile 来构建我们的应用程序呢?

通常,Dockerfile 以一个标准的 OS 基础镜像开始,然后是创建适当的运行时构建所需执行的多个步骤。这包括包的安装,为此需要像 aptyum 这样的包管理器。

有两种方法:

  1. 先在 Docker 外部构建好你的应用程序,然后使用 Dockerfile 中的 ADDCOPY 指令将二进制包复制到容器中。
  2. 使用多阶段 Docker 构建。这是 Docker 17.05 及以后版本的一个新特性,它允许你将构建分为不同的阶段。第一阶段可以从标准的 OS 基础镜像开始,可以帮助你构建应用程序;第二阶段可以简单地从第一阶段获取构建的文件并使用 Distroless 作为基础镜像。

为了理解它是如何工作的,让我们使用多阶段构建流程进行一个实际操作练习。

必要条件

你需要具备以下内容:

  • Docker 版本大于等于 17.05,用于构建镜像
  • 可选的 Kubernetes 集群用于实践练习的第二部分。如果你想在 Docker 中运行你的容器,你可以使用等价的 docker 命令。

GitHub 代码仓

作为实践练习,将 此代码仓 Fork 到你的 GitHub 帐号下,然后克隆 GitHub 代码仓并使用 cd 进入到项目目录下。

该代码仓包含一个 PythonFlask 应用程序,当你调用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"]

构建阶段:

  • 从 python:2.7-slim 的基础镜像开始
  • 将应用程序复制到 /app 目录下
  • 升级 pip 并安装依赖

Distroless 阶段:

  • 从 gcr.io/distroless/python2.7 的基础镜像开始
  • 将应用程序从构建阶段的 /app 目录复制到当前阶段的 /app 目录
  • 将 python 的 site-packages 从构建阶段复制到当前阶段的 site-packages 目录
  • 设置工作目录到 /app,将 python PATH 设置为 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(或者你的私有镜像仓),你应该会看到容器镜像可以使用:

distroless-2

如果你看一下压缩后的大小,它只有 23.36 MB。如果你使用 slim 发行版作为基础镜像,它将占用 56 MB。

distroless-2

你已经减少了超过一半的容器占用空间。That’s amazing!

在 Kubernetes 中运行容器

为了测试构建是否有效,让我们在 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 应用程序在正常工作。

使用 Shell 对应用程序进行访问

正如我在引言中所描述的,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

原文链接:https://betterprogramming.pub/how-to-harden-your-containers-with-distroless-docker-images-c2abd7c71fdb

原文作者:Gaurav Agarwal

登录 后参与评论
0 条评论

相关文章

  • 只需简单 2 步,让你的 SSH 更加安全

    从 OpenSSH 6.2 开始已经支持 SSH 多因素认证,本文就来讲讲如何在 OpenSSH 下启用该特性。

    iMike
  • 如何让你的lint检查更加高效?

    导语:在自定义lint规则的实践过程中,我们发现lint扫描的效率非常低,比如在项目中进行一次lint全量扫描,平均需要5分钟左右,而且这是在仅扫描自定义规则的...

    腾讯技术工程官方号
  • 【Tomcat优化篇】如何让你的Tomcat性能更加优越

      我们可以打开Tomcat的管理页面,这块需要先配置下,在 tomcat-users.xml中添加相关的用户和角色信息

    用户4919348
  • 使用with关键字让你的Python代码更加Pythonic

    首先解释一下上一篇文章Python科学计算扩展库numpy中的广播运算中最后的小题目,该题目答案是一个元组(True, 5),原因在于Python中的等号=虽然...

    Python小屋屋主
  • 让你的代码更加简练,springboot使用JSR303校验

    相信不少人写过这样都代码,对方法入参进行了各种校验。上面还是比较少的校验。如果遇到什么邮箱、手机号更复杂,对格式也需要进行校验。可能洋洋洒洒几百行代码就过去了。...

    码农小胖哥
  • 如何让你的 WordPress 网站更安全

    现在写博客已经成为很多人的爱好,而 WordPress 是最受青睐的平台。默认情况下,wordpress 博客采用最低级别的安全性,而且它的文件和/或插件可能经...

    海拥
  • Linux/Mac如何配置ZSH并使用Oh-my-zsh?让你的终端更加实用、美观

    现在,越来越多的人趋向使用ZSH取代(Linux)原本的Bash作为自己的终端Shell。的确,ZSH才是适用于现代的Shell:

    Mintimate
  • 使用以语言为中心的容器基础镜像 distroless

    关于容器技术,我之前分享不少文章和技巧,包括如何优化镜像,如何更优雅的进行构建封装,以及大量的容器应用实践、使用案例以及维护方式。

    soulteary
  • 使用以语言为中心的容器基础镜像 distroless

    关于容器技术,我之前分享不少文章和技巧,包括如何优化镜像,如何更优雅的进行构建封装,以及大量的容器应用实践、使用案例以及维护方式。

    soulteary
  • 涨姿势:如何让你的Google账户更安全

    如果你使用Gmail作为你主要的电子邮件,或者长期依赖于谷歌提供的服务,再或者你是“Google脑残粉”……那么这篇文章就值得你来读读。本文将指导你重新审视并重...

    FB客服
  • 如何使用Gorsair保护Docker容器的安全性

    Gorsair是一款功能强大的针对Docker容器的安全分析和渗透测试工具,可以帮助广大研究人员检查目标Docker容器中潜在的安全问题,并发现和访问目标Doc...

    FB客服
  • 优化 Docker 镜像大小常见方法

    平时我们构建的 Docker 镜像通常比较大,占用大量的磁盘空间,随着容器的大规模部署,同样也会浪费宝贵的带宽资源。本文将介绍几种常用的方法来优化 Docker...

    我是阳明
  • 如何使用 golang 的反射机制让你事半功倍

    上一篇文章中,我们详细了解了 golang 中反射机制的实现原理。 golang 中的反射(上) — 反射的原理与实现

    用户3147702
  • 三个技巧,将Docker镜像体积减小90%【面试+工作】

    在构建Docker容器时,应该尽量想办法获得体积更小的镜像,因为传输和部署体积较小的镜像速度更快。

    Java帮帮
  • 如何使用WWWGrep检查你的网站元素安全

    WWWGrep是一款针对HTML安全的工具,该工具基于快速搜索“grepping”机制实现其功能,并且可以按照类型检查HTML元素,并允许执行单个、多个或递归搜...

    FB客服
  • 三个技巧,将Docker镜像体积减小90%

    在构建 Docker 容器时,应该尽量想办法获得体积更小的镜像,因为传输和部署体积较小的镜像速度更快。

    菲宇
  • 三个技巧 大幅减少 Docker 镜像体积

    在构建 Docker 容器时,应该尽量想办法获得体积更小的镜像,因为传输和部署体积较小的镜像速度更快。但 RUN 语句总是会创建一个新层,而且在生成镜像之前还需...

    Debian中国
  • 还在用Alpine做Docker镜像?看看大牛怎么说

    大家都喜欢Alpine的镜像,因为它们很轻,攻击面较小,但也许它们不再是最佳选择。现在又到了谈论distroless版镜像的时候了。 在 SumUp我们经常使...

    IT大咖说
  • 推荐一个极其轻量级的 Docker 基础镜像,大小 2MB

    你好,我是征哥,提到轻量级的 Docker 镜像,很多人都知道 Alpine,因为它确实很轻,只有 5.57MB,使用它作为基础镜像的 Python 也就 44...

    somenzz

扫码关注腾讯云开发者

领取腾讯云代金券