Perl on Docker - Multi-stage Builds zur Verkürzung von Build-Laufzeiten

Veröffentlicht von Thomas Fahle am (Permalink)

Two, baby and adult, brown camels. Dasht-e Kavir also known as Kavir-e Namak and the Great Salt Desert, is a large desert lying in the middle of the Iranian plateau.

 

Einführung

Beim Bau eines Docker-Images werden oft (mehrere) Artifakte erzeugt, deren Erstellung recht zeit- und ressourceaufwendig sein kann. Ein typisches Bespiel dafür ist das Kompilieren von umfangreichen C-Programmen.

Ohne multi-stage builds muss das Image auch bei noch so kleinen Änderungen am Dockerfile komplett neu erstellt werden.

Mit Docker Multi-Stage Builds kann man hingegen innerhalb eines Dockerfiles mehrere unabhängige Zwischencontainer/Images erstellen, die jeweils ein Ergebnis (Artefakt) produzieren. Dieses intermediäre Ergebnis wird von Docker zwischengespeichert (Cache) und kann von anderen Containern/Images gezogen werden. Es wird also nur der geänderte Teil des Images neu erzeugt, alle anderen Teile werden wiederverwendet.

In diesem Beitrag wird ein C-Programm (hier ta-lib) aus den Sourcen in ein intermediäres Image installiert. Dieses Artefakt wird in einem weiterem Build Stage zur Installation eines CPAN-Moduls (hier Finance::TA) wiederverwendet. Wiederholte Docker Builds laufen Dank dieser Vorgehensweise erheblich schneller.

 

Beispiel: ta-lib in Zwischencontainer kompilieren (Stage 1) und Finance::TA installieren (Stage 2)

Das nachfolgende Beispiel nutzt Official Docker Perl Images, die bereits in Perl on Docker - Offizielle Perl Docker Images als Grundlage eigener Images beschrieben wurden, als Basis.

Im ersten Schritt wird ta-lib wie breits in TA-LIB und Finance::TA auf Ubuntu installieren beschrieben aus den C-Sourcen in einen Zwischencontainer installiert.

Im zweiten Schritt wird das CPAN Modul Finance::TA mittels cpanm installiert. Die fertigen Binaries, Bibliotheken und Header aus dem ersten Schritt/Zwischencontainer werden in den zweiten Container (Layer) kopiert.

Zu Demonstrationszwecken werden noch einige weitere CPAN-Module und ein kleines Testprogramm (finance-ta-sma.pl) installiert. Das Programm wurde bereits in Finance::TA - Perl Bibliothek zur Nutzung der Technical Analysis Library (http://ta-lib.org) beschrieben.

Randnotiz: Hier wird alles – auch alte Beiträge – wiederverwendet.

 

Dockerfile

Datei Dockerfile.

# Stage 1: Build TA-LIB
FROM perl:5.34.0 as ta-lib
# https://github.com/perl/docker-perl/blob/311f05366d91427d289740dd15fb9401dc4347ef/5.034.000-main-buster/Dockerfile

RUN set -ex; \
       wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \
       tar -xvzf ta-lib-0.4.0-src.tar.gz && \
       cd ta-lib/ && \
       ./configure --prefix=/usr/local && \
       make && \
       make install; \
       cd .. ; \
       rm -rf ta-lib ta-lib-0.4.0-src.tar.gz ;

# Stage 2: Install Finance::TA
FROM perl:5.34.0

LABEL maintainer="Thomas Fahle <perlhowto.github.io@gmail.com>"
LABEL version="0.10"
LABEL description="Perl on Docker - Finance::TA/ta-lib Container - based on perl:5.34.0 buildpacks-deps"

# Copy compiled (docker cached) ta-lib from stage 1 into stage 2
COPY --from=ta-lib /usr/local/lib/libta_lib.* /usr/local/lib/
COPY --from=ta-lib /usr/local/bin/ta-lib-config /usr/local/bin/ta-lib-config
COPY --from=ta-lib /usr/local/include/ta-lib/ /usr/local/include/ta-lib/


# Upgrade already installed packages in noninteractive mode
# Install SQLite3
# Install tree
# Install vim
# Autoremove unused packages
# Clean local repos
#
# cpanm
# Install DBD::SQLite
# Install Finance::TA
# Install Text::CSV Text::CSV_XS Text::CSV_PP Text::CSV::Slurp
#
# Free disk space to decrease image size

ARG DEBIAN_FRONTEND=noninteractive
# Set TALIB_CFLAGS because
# ta-lib-config --cflags returns incorrect data
ARG TALIB_CFLAGS='-I/usr/local/include/ta-lib -DHAVE_CONFIG_H'
RUN set -ex; \
    apt-get update && \
    apt-get dist-upgrade -y && \
    apt-get -y -qq --no-install-recommends install sqlite3 tree vim && \
    apt-get -y autoremove && \
    apt-get clean \
    ; \
    /usr/local/bin/cpanm \
     DBD::SQLite \
     Finance::TA \
     Text::CSV Text::CSV_XS Text::CSV_PP Text::CSV::Slurp \
    ; \
    rm -rf /var/lib/apt/lists/* ; \
    rm -rf /tmp/* ; \
    rm -rf /root/.cpanm/

WORKDIR /app

# Copy simple demo program into container to check Finance::TA
COPY ./finance-ta-sma.pl /app/finance-ta-sma.pl

CMD ["/bin/bash"]

Das Image kann jetzt gebaut,

sudo DOCKER_BUILDKIT=1 docker build -t ta-lib-image .

gestartet

sudo docker run -it --rm --name ta-lib ta-lib-image

und getestet werden.

root@c97043f36ab8:/app# /app/finance-ta-sma.pl
i |      value |   slow_sma |             fast_sma |
0 |  91.500000 |        N/A |                  N/A |
1 |  94.815000 |        N/A |                  N/A |
2 |  94.375000 |        N/A |     93.5633333333333 |
3 |  95.095000 |   93.94625 |     94.7616666666667 |
4 |  93.780000 |   94.51625 |     94.4166666666667 |
5 |  94.625000 |   94.46875 |                 94.5 |
6 |  92.530000 |    94.0075 |               93.645 |
7 |  92.750000 |   93.42125 |     93.3016666666667 |
8 |  90.315000 |     92.555 |               91.865 |
9 |  92.470000 |   92.01625 |               91.845 |

 

Keine erneute Kompilation von ta-lib (Stage 1) bei Änderungen in Stage 2

Falls das Image nach einer Anpassung, z.B. Installation weiterer CPAN-Module, im Abschnitt 2 des Dockerfiles neu gebaut werden muss, so wird nur der zweite Zwischencontainer neu erstellt. Der Zwischencontainer aus Schritt 1 wird nicht neu erzeugt, sondern aus dem Docker-Image-Cache gezogen. Dies beschleunigt den erneuten Build-Vorgang erheblich.

Bei Änderungen in Schritt 1 muss natürlich das Zwischenimage neu gebaut werden, da hilft leider nichts.

 

Beispielprogramm finance-ta-sma.pl

Hier noch das Beispielprogramm finance-ta-sma.pl aus Finance::TA - Perl Bibliothek zur Nutzung der Technical Analysis Library (http://ta-lib.org):

#!/usr/bin/env perl
use strict;
use warnings;

use Finance::TA;

# Data straight from https://metacpan.org/dist/Finance-TA/view/TA.pod
my @series = (
    '91.500000', '94.815000', '94.375000', '95.095000', '93.780000', '94.625000',
    '92.530000', '92.750000', '90.315000', '92.470000'
);

# https://metacpan.org/dist/Finance-TA/view/TA.pod#TA_SMA-(Simple-Moving-Average)
# TA_SMA (Simple Moving Average)
# ($retCode, $begIdx, $outReal) = TA_SMA($startIdx, $endIdx, \@inReal, $optInTimePeriod);
#
my $startIdx = 0;
my $endIdx   = $#series;

# Fast SMA
my $optInTimePeriod_fast_sma = 3;
my ( $retCode_fast_sma, $begIdx_fast_sma, $result_fast_sma ) =
  TA_SMA( $startIdx, $endIdx, \@series, $optInTimePeriod_fast_sma );
# Die on TA errors
die "Error fast TA_SMA $retCode_fast_sma" unless $retCode_fast_sma == $TA_SUCCESS;

# Slow SMA
my $optInTimePeriod_slow_sma = 4;
my ( $retCode_slow_sma, $begIdx_slow_sma, $result_slow_sma ) =
  TA_SMA( $startIdx, $endIdx, \@series, $optInTimePeriod_slow_sma );
# Die on TA errors
die "Error slow TA_SMA $retCode_slow_sma" unless $retCode_slow_sma == $TA_SUCCESS;

my $format = "%s | %10s | %10s | %20s |\n";
printf( $format, 'i', 'value', 'slow_sma', 'fast_sma' );
for ( my $i = 0 ; $i <= $#series ; $i++ ) {
    my $value    = $series[$i];
    my $fast_sma = 'N/A';
    my $slow_sma = 'N/A';
    if ( $i >= $begIdx_fast_sma ) {
        $fast_sma = $result_fast_sma->[ $i - $begIdx_fast_sma ];
    }
    if ( $i >= $begIdx_slow_sma ) {
        $slow_sma = $result_slow_sma->[ $i - $begIdx_slow_sma ];
    }
    printf( $format, $i, $value, $slow_sma, $fast_sma );
}

 

That's it.

 

Siehe auch

 

Source-Code der Beispiele im Github Repo perl-howto-code.

 

Bildnachweis

Photo by mostafa meraji on Unsplash

 

Weitere Posts