5
头图

Use the Distroless image to protect the container on Kubernetes

Containers have changed the way we look at technical infrastructure. This is a huge leap forward in the way we run applications. Together, container orchestration and cloud services provide us with a near-infinite scale of seamless scalability.

By definition, the container should contain the application and its runtime dependency . However, in reality, they contain far more than these. The standard container base image contains the package manager, shell and other programs Linux

Although these are necessary to build the container image, they should not be part of the final image. For example, once you install the package, you no longer need to use package management tools such as apt

This not only makes your container full of unnecessary software packages and programs, but also provides cybercriminals with the opportunity to attack specific program vulnerabilities.

You should always understand what is in the container runtime, and you should precisely limit it to only the dependencies required by the application.

You should not install anything except those necessary. Some leading technology giants, such as Google, have years of experience in running containers in production and have already adopted this approach.

Google is now opening up this capability to the world Distroless The goal of these images built by Google is to only contain your application and its dependencies, and they will not have all the features of the Linux shell .

This means that although you can run the container of the application as before, you cannot enter the container while the container is running. This is a major security improvement, because you have now closed the door shell

Distroless base image

Google provides a base image Distroless for most popular programming languages and platforms.

The following basic images are officially released versions:

The following basic images are still in the experimental stage and are not recommended for production environments:

Build Distroless image

Google uses Bazel internally to build container images, but we can use Docker to do the same thing. A controversial question about using the Distroless image is: when we have a Distroless image, how do we use Dockerfile to build our application?

Usually, Dockerfile with a standard OS base image, followed by the multiple steps required to create a proper runtime build. This includes the installation of packages, for which a package manager apt or yum

There are two ways:

  1. First in Docker external build up your application, and then use Dockerfile in the ADD or COPY binary package to the container instructions.
  2. Use multi-stage Docker build. This is a new feature of Docker 17.05 and later, which allows you to divide the build into different stages. The first stage can start from the standard OS base image, which can help you build applications; the second stage can simply get the built files from the first stage and use Distroless as the base image.

In order to understand how it works, let's use a multi-stage build process for a hands-on exercise.

Necessary condition

You need to have the following:

  • Docker version is greater than or equal to 17.05, used to build the image
  • The optional Kubernetes cluster is used for the second part of the practice exercise. If you want Docker , you can use the equivalent docker command.

GitHub code warehouse

As a practical exercise, code warehouse to your GitHub account, then clone the GitHub code warehouse and use cd enter the project directory.

The code warehouse contains a Python application of Flask . When you call the API, the application will respond to Hello World! .

app.py file is as follows:

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)

The Dockerfile contains two stages:

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"]

Construction phase:

  • Start from the base image of python:2.7-slim
  • Copy the application to the /app directory
  • Upgrade pip and install dependencies

Distroless stage:

  • Start from the base image of gcr.io/distroless/python2.7
  • Copy the application from the /app directory in the build phase to the /app directory in the current phase
  • Copy python's site-packages from the build stage to the site-packages directory of the current stage
  • Set the working directory to /app, set the python PATH to the site-packages directory, and expose port 5000
  • Use CMD instruction to run app.py

Since the Disroless image does not contain shell CMD instruction should be used at the end. If you don't do this, Docker will think it is a shell CMD and try to execute it like this, but this will not work.

Build the image:

$ 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

Log in to DockerHub and push the image:

docker login
docker push <your_docker_repo>/flask-hello-world-distroless:latest

Log in to DockerHub (or your private mirror warehouse), you should see that the container image is available:

If you look at the compressed size, it is only 23.36 MB. If you use the slim release as the base image, it will take up 56 MB.

You have reduced the container footprint by more than half. That's amazing!

Run containers in Kubernetes

To test whether the build works, let's run the container in the Kubernetes cluster. If you don't have Kubernetes, you can run equivalent Docker commands to do the same activities, because Kubectl and Docker commands are similar.

I created a file in the code kubernetes.yaml warehouse, which included the use of our built mirrored Deployment and load balancing 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

This is a very simple setup. The load balancer listens on port 80 and maps to the target port 5000. These Pods listen to the Flask application on the default port 5000.

application:

$ kubectl apply -f kubernetes.yaml
deployment.apps/flask-deployment created
service/flask-service created

Let's take a look at all the resources and see what we have 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

We see that there are two Pods , one Deployment LoadBalancer service with an external IP, and one ReplicaSet .

Let's access the application:

$ curl http://35.184.113.120
Hello World!

We got Hello World! . This indicates that the Flask application is working properly.

Use Shell to access applications

As I described in the introduction, Disroless is no shell 060dbc83eb63dc container, so it is impossible to enter the container. However, let's try to execute exec in the container:

$ 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

We cannot connect to the container.

What about container logs? If we can't get the container log, we lose the way to debug the application.

Let's try to get the log:

$ 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 -

So container logs can be obtained!

in conclusion

Using Distroless as the base image is an exciting way to protect container security. Since the image is small and contains only the application and dependencies, it provides the smallest attack surface for the application. It improves the security of the application to a greater extent, so it is a good way to protect the security of the container.

thanks for reading! I hope you enjoy this article.

Original link

This article is translated from How to Harden Your Containers With Distroless Docker Images


K8sCat
270 声望737 粉丝