angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Het verkleinen van de grootte van een Python applicatieafbeelding Docker met behulp van Python wheels

Met Docker multi-stage builds en Python wheels we compileren de afhankelijkheden in de eerste fase en we installeren ze in de tweede fase

10 maart 2019
post main image

Bij het gebruik van de docker willen we dat het formaat van de dockerafbeelding minimaal is. Waarom? Vele redenen. Geheugenvoetafdruk is er een vooral bij het draaien van veel Python Flask websites op een ISPConfig3 server. Gelukkig kunnen we vanaf docker 17.05 gebruik maken van builds multistage . Hiermee verkleinen we onze beeldgrootte van 376MB naar 211MB!

Hieronder staan twee afbeeldingen die we kunnen gebruiken:

> Dockers beelden

python 3.6-alpine 1837080c5e87 5 weeks ago 74.4MB
python 3.6.7 1ec4d11819ad 2 months ago 918MB

74 MB versus 918 MB is een groot verschil. Natuurlijk gaat de kleine omvang gepaard met een prijs. Veel programma's/scripts zijn verwijderd uit de alpine afbeelding, dus soms kunnen we problemen krijgen.

Met Alpine behulp van het debuggen kunnen we programma's zoals telnet en netstat op een zeer eenvoudige manier toevoegen:

> apk: busybox-extra's toevoegen

Voor deze blog Flask app is de requirements.txt van toepassing:

alembic==1.0.0
asn1crypto==0.24.0
Babel==2.6.0
beautifulsoup4==4.6.3
cffi==1.11.5
Click==7.0
cryptography==2.3.1
Flask==1.0.2
Flask-Babel==0.11.2
Flask-Login==0.4.1
Flask-Session==0.3.1
Flask-WTF==0.14.2
gunicorn==19.9.0
html2text==2018.1.9
idna==2.7
itsdangerous==0.24
Jinja2==2.10
Mako==1.0.7
MarkupSafe==1.0
Pillow==5.3.0
pycparser==2.19
PyMySQL==0.9.2
python-dateutil==2.7.3
python-editor==1.0.3
python-magic==0.4.15
python-slugify==1.2.6
pytz==2018.5
six==1.11.0
SQLAlchemy==1.2.12
Unidecode==1.0.22
Werkzeug==0.14.1
WTForms==2.2.1

Bij het gebruik van python:3.6-alpine komen we fouten tegen bij het opbouwen van ons imago. In dit geval, voor cffi en kussen:

  • Geen werkende compiler gevonden' fout.
  • De headers of bibliotheekbestanden konden niet gevonden worden voor jpeg, een vereiste afhankelijkheid bij het compileren van Pillow from source.

Oplossingen zijn er en er zijn meer programma's/code aan het beeld toegevoegd:

  • CFFI dependencies bloats Docker image #458
    h
    ttps://github.com/gliderlabs/docker-alpine/issues/458 Voeg toe aan Dockerbestand: RUN apk toevoegen --geen cache krul python3 pkgconfig python3-dev openssl-dev libffi-dev libffi-dev musl-dev maken gcc
  • Hoe te om hoofdkussen, psycopg, pylibmc pakketten in python te installeren: alpine beeld
    h
    ttps://blog.sneawo.com/blog/2017/09/07/how-to-install-pillow-psycopg-pylibmc-packages-in-pythonalpine-image/ Voeg aan Dockerdossier toe: RUN apk toevoegen --geen cache jpeg-dev zlib-dev

Helaas is ons imago nu gegroeid naar 376MB!

Herschrijf Dockerbestand voor meerfasige toepassingen

Vanaf docker 17.05 kunnen we gebruik maken van meertraps opbouw. Het idee is dat we een aantal hulpprogramma's bouwen en vervolgens het resultaat kopiëren naar de uiteindelijke containerafbeelding.

Herschrijven van het Dockerbestand van:

FROM python:3.6-alpine
MAINTAINER Peter Mooring peterpm@xs4all.nl peter@petermooring.com

# create and set working directory
RUN mkdir -p /home/flask/app/web
WORKDIR /home/flask/app/web

# install package dependencies
COPY requirements.txt ./
# Solve 'No working compiler found' error, 
# see: https://github.com/gliderlabs/docker-alpine/issues/458
RUN apk add --no-cache curl python3 pkgconfig python3-dev openssl-dev libffi-dev musl-dev make gcc \
# Solve 'The headers or library files could not be found for jpeg, a required dependency when compiling Pillow from source.', 
# see https://blog.sneawo.com/blog/2017/09/07/how-to-install-pillow-psycopg-pylibmc-packages-in-pythonalpine-image/
    jpeg-dev zlib-dev \
    libmagic \
  && pip install --no-cache-dir -r ./requirements.txt \
  && rm -rf /var/cache/apk/*

# copy app code into container
COPY . ./

# create group and user used in this container
RUN addgroup flaskgroup && adduser -D flaskuser -G flaskgroup
RUN chown -R flaskuser:flaskgroup /home/flask

USER flaskuser

aan:

FROM python:3.6-alpine as base
MAINTAINER Peter Mooring peterpm@xs4all.nl peter@petermooring.com

RUN mkdir /svc
WORKDIR /svc
COPY requirements.txt .

# install package dependencies
# COPY requirements.txt /requirements.txt, requirements.txt already copied 
# Solve 'No working compiler found' error, 
# see: https://github.com/gliderlabs/docker-alpine/issues/458
# Solve 'The headers or library files could not be found for jpeg, a required dependency when compiling Pillow from source.', 
# see https://blog.sneawo.com/blog/2017/09/07/how-to-install-pillow-psycopg-pylibmc-packages-in-pythonalpine-image/


RUN rm -rf /var/cache/apk/* && \
    rm -rf /tmp/*

RUN apk update

# Instead, I run python setup.py bdist_wheel first, then run pip wheel -r requirements.txt for pypi packages.

RUN apk add --update \
    curl \
    python3 \ 
    pkgconfig \ 
    python3-dev \
    openssl-dev \ 
    libffi-dev \ 
    musl-dev \
    make \ 
    gcc \
    jpeg-dev zlib-dev \
    libmagic \
    && rm -rf /var/cache/apk/* \
    && pip wheel -r requirements.txt --wheel-dir=/svc/wheels

# the wheels are now here: /svc/wheels

FROM python:3.6-alpine

RUN apk add --no-cache \
    jpeg-dev zlib-dev \
    libmagic

COPY --from=base /svc /svc

WORKDIR /svc
RUN pip install --no-index --find-links=/svc/wheels -r requirements.txt

# after installation, remove wheels, does not free up space, probably because we are in new layer, too bad is some 20MB
#RUN rm -R *

# create and set working directory
RUN mkdir -p /home/flask/app/web
WORKDIR /home/flask/app/web

# copy app code into container
COPY . ./

# create group and user used in this container
RUN addgroup flaskgroup && adduser -D flaskuser -G flaskgroup && chown -R flaskuser:flaskgroup /home/flask

USER flaskuser

De Python wheels directory /svc/wheels:

-rw-r--r-- 1 root root 8098645 Feb 15 12:56 Babel-2.6.0-py2.py3-none-any.whl
-rw-r--r-- 1 root root 81299 Feb 15 12:56 Click-7.0-py2.py3-none-any.whl
-rw-r--r-- 1 root root 91364 Feb 15 12:56 Flask-1.0.2-py2.py3-none-any.whl
-rw-r--r-- 1 root root 9267 Feb 15 12:56 Flask_Babel-0.11.2-py2.py3-none-any.whl
-rw-r--r-- 1 root root 4936158 Feb 15 12:56 Flask_CKEditor-0.4.2-py2.py3-none-any.whl
-rw-r--r-- 1 root root 15935 Feb 15 12:57 Flask_Login-0.4.1-py2.py3-none-any.whl
-rw-r--r-- 1 root root 7535 Feb 15 12:56 Flask_Session-0.3.1-py2.py3-none-any.whl
-rw-r--r-- 1 root root 14903 Feb 15 12:56 Flask_WTF-0.14.2-py2.py3-none-any.whl
-rw-r--r-- 1 root root 126381 Feb 15 12:56 Jinja2-2.10-py2.py3-none-any.whl
-rw-r--r-- 1 root root 76583 Feb 15 12:57 Mako-1.0.7-py3-none-any.whl
-rw-r--r-- 1 root root 29273 Feb 15 12:57 MarkupSafe-1.0-cp36-cp36m-linux_x86_64.whl
-rw-r--r-- 1 root root 1101554 Feb 15 12:57 Pillow-5.3.0-cp36-cp36m-linux_x86_64.whl
-rw-r--r-- 1 root root 47758 Feb 15 12:56 PyMySQL-0.9.2-py2.py3-none-any.whl
-rw-r--r-- 1 root root 1144841 Feb 15 12:57 SQLAlchemy-1.2.12-cp36-cp36m-linux_x86_64.whl
-rw-r--r-- 1 root root 235421 Feb 15 12:56 Unidecode-1.0.22-py2.py3-none-any.whl
-rw-r--r-- 1 root root 166353 Feb 15 12:56 WTForms-2.2.1-py2.py3-none-any.whl
-rw-r--r-- 1 root root 322863 Feb 15 12:56 Werkzeug-0.14.1-py2.py3-none-any.whl
-rw-r--r-- 1 root root 158276 Feb 15 12:56 alembic-1.0.0-py2.py3-none-any.whl
-rw-r--r-- 1 root root 101571 Feb 15 12:56 asn1crypto-0.24.0-py2.py3-none-any.whl
-rw-r--r-- 1 root root 90375 Feb 15 12:56 beautifulsoup4-4.6.3-py3-none-any.whl
-rw-r--r-- 1 root root 385610 Feb 15 12:56 cffi-1.11.5-cp36-cp36m-linux_x86_64.whl
-rw-r--r-- 1 root root 813672 Feb 15 12:57 cryptography-2.3.1-cp36-cp36m-linux_x86_64.whl
-rw-r--r-- 1 root root 112930 Feb 15 12:56 gunicorn-19.9.0-py2.py3-none-any.whl
-rw-r--r-- 1 root root 21118 Feb 15 12:56 html2text-2018.1.9-py3-none-any.whl
-rw-r--r-- 1 root root 58213 Feb 15 12:56 idna-2.7-py2.py3-none-any.whl
-rw-r--r-- 1 root root 10622 Feb 15 12:57 itsdangerous-0.24-py3-none-any.whl
-rw-r--r-- 1 root root 111031 Feb 15 12:57 pycparser-2.19-py2.py3-none-any.whl
-rw-r--r-- 1 root root 211414 Feb 15 12:56 python_dateutil-2.7.3-py2.py3-none-any.whl
-rw-r--r-- 1 root root 6686 Feb 15 12:57 python_editor-1.0.3-py3-none-any.whl
-rw-r--r-- 1 root root 5543 Feb 15 12:56 python_magic-0.4.15-py2.py3-none-any.whl
-rw-r--r-- 1 root root 4595 Feb 15 12:57 python_slugify-1.2.6-py2.py3-none-any.whl
-rw-r--r-- 1 root root 510974 Feb 15 12:56 pytz-2018.5-py2.py3-none-any.whl
-rw-r--r-- 1 root root 10702 Feb 15 12:56 six-1.11.0-py2.py3-none-any.whl

Samenvatting

Voor: 376M, erna: 211MB.

We hebben dit gedaan met behulp van meerfasen:

Stadium 1:

  • Gebouwd Python wheels voor requirements.txt

Stadium 2:

  • Kopie Python wheels van stadium 1
  • Installeer afhankelijkheden met behulp van Python wheels
  • Andere dingen doen zoals het aanmaken van een gebruiker, het kopiëren van code, enz.

Helaas kunnen we niet draaien met de directory /svc/wheels van stage1 of deze directory uit het resultaat verwijderen. Dat zou nog eens 25M besparen!

Links / credits

Building Minimal Docker Containers for Python Applications
https://blog.realkinetic.com/building-minimal-docker-containers-for-python-applications-37d0272c52f3

How do I reduce a python (docker) image size using a multi-stage build? (**python specific**)
https://stackoverflow.com/questions/48543834/how-do-i-reduce-a-python-docker-image-size-using-a-multi-stage-build-pytho

Leveraging Docker multi-stage builds in Python development
https://www.merixstudio.com/blog/docker-multi-stage-builds-python-development/

Lighter Python images using multi-stage Dockerfile
https://lekum.org/post/multistage-dockerfile/

Smaller Python Docker Containers with Multi-Stage Builds and Python Wheels
https://softwarejourneyman.com/docker-python-install-wheels.html

Use multi-stage builds
https://docs.docker.com/develop/develop-images/multistage-build/

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen (1)

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.

avatar

You can use
RUN pip install --no-cache /wheels/* \
&& rm -rf /wheels/*
to delete wheels