Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

Mielőtt egy funkció gyártásba kerülne, manapság, amikor az összetett hangszerelők és CI/CD-k vannak, hosszú utat kell megtenni az elkötelezettségtől a tesztekig és a szállításig. Korábban FTP-n keresztül lehetett új fájlokat feltölteni (ezt már senki sem csinálta, igaz?), és a „telepítési” folyamat másodpercekig tartott. Most létre kell hoznia egy egyesítési kérelmet, és sokáig kell várnia, amíg a funkció eléri a felhasználókat.

Ennek az útvonalnak egy része a Docker-kép létrehozása. Néha percekig tart az összeszerelés, néha több tíz percig, amit aligha nevezhetünk normálisnak. Ebben a cikkben bemutatunk egy egyszerű alkalmazást, amelyet képbe csomagolunk, számos módszert alkalmazunk a felépítés felgyorsítására, és megvizsgáljuk e módszerek működésének árnyalatait.

Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

Jó tapasztalattal rendelkezünk a médiawebhelyek létrehozásában és támogatásában: TASS, The Bell, "Új Újság", Köztársaság… Nem sokkal ezelőtt bővítettük portfóliónkat egy termékwebhely megjelenésével emlékeztető. És bár gyorsan hozzáadtak új funkciókat, és kijavították a régi hibákat, a lassú üzembe helyezés nagy problémát jelentett.

A GitLab-ba telepítjük. Összegyűjtjük a képeket, továbbítjuk a GitLab Registry-be, és kiterjesítjük a termelésbe. A leghosszabb dolog ezen a listán a képek összeállítása. Például: optimalizálás nélkül minden backend build 14 percig tartott.

Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

A végén kiderült, hogy nem élhetünk tovább így, és leültünk kitalálni, miért tart ilyen sokáig a képek összegyűjtése. Ennek eredményeként sikerült 30 másodpercre csökkenteni az összeszerelési időt!

Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

Ebben a cikkben, hogy ne kötődjünk a Reminder környezetéhez, nézzünk egy példát egy üres Angular alkalmazás összeállítására. Tehát készítsük el az alkalmazásunkat:

ng n app

Add hozzá a PWA-t (progresszívek vagyunk):

ng add @angular/pwa --project app

Miközben egymillió npm-csomag letöltése zajlik, nézzük meg, hogyan működik a docker image. A Docker lehetőséget biztosít az alkalmazások csomagolására és futtatására egy elszigetelt környezetben, amelyet konténernek neveznek. Az elszigeteltségnek köszönhetően több konténer futtatható egyszerre egy szerveren. A tárolók sokkal könnyebbek, mint a virtuális gépek, mivel közvetlenül a rendszermagon futnak. Ahhoz, hogy alkalmazásunkkal konténert futtathassunk, először létre kell hoznunk egy képet, amelybe mindent becsomagolunk, ami az alkalmazásunk futtatásához szükséges. Lényegében a kép a fájlrendszer másolata. Vegyük például a Dockerfile-t:

FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

A Dockerfile egy utasításkészlet; Mindegyik végrehajtásával a Docker elmenti a fájlrendszer módosításait, és átfed az előzőekre. Minden csapat létrehozza a saját rétegét. Az elkészült kép pedig rétegek kombinálása.

Amit fontos tudni: minden Docker réteg képes gyorsítótárat tárolni. Ha semmi sem változott az utolsó build óta, akkor a parancs végrehajtása helyett a docker egy kész réteget vesz fel. Mivel az építési sebesség fő növekedése a gyorsítótár használatának köszönhető, ezért az építési sebesség mérésénél kifejezetten figyelni fogunk a kép elkészítésére kész gyorsítótárral. Tehát lépésről lépésre:

  1. A képeket helyben töröljük, hogy a korábbi futtatások ne befolyásolják a tesztet.
    docker rmi $(docker images -q)
  2. Először indítjuk el a buildet.
    time docker build -t app .
  3. Megváltoztatjuk az src/index.html fájlt - programozó munkáját utánozzuk.
  4. Másodszor futtatjuk az építést.
    time docker build -t app .

Ha a képek összeállításának környezete megfelelően van konfigurálva (erről bővebben lentebb), akkor a felépítés megkezdésekor a Docker már egy csomó gyorsítótárral rendelkezik a fedélzeten. A mi feladatunk az, hogy megtanuljuk a gyorsítótár használatát, hogy a build a lehető leggyorsabban menjen. Mivel azt feltételezzük, hogy egy build gyorsítótár nélküli futtatása csak egyszer – a legelső alkalommal – történik meg, ezért figyelmen kívül hagyhatjuk, hogy az első alkalommal milyen lassú volt. A teszteknél fontos számunkra a build második menete, amikor a gyorsítótárak már felmelegedtek, és készen állunk a sütemény sütésére. Néhány tipp azonban hatással lesz az első összeállításra is.

Tegyük a fent leírt Dockerfile-t a projekt mappájába, és kezdjük el a buildet. A könnyebb olvashatóság érdekében minden listát tömörítettünk.

$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Status: Downloaded newer image for node:12.16.2
Step 2/5 : WORKDIR /app
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:20:09.664Z - Hash: fffa0fddaa3425c55dd3 - Time: 37581ms
Successfully built c8c279335f46
Successfully tagged app:latest

real 5m4.541s
user 0m0.000s
sys 0m0.000s

Megváltoztatjuk az src/index.html tartalmát, és másodszor is futtatjuk.

$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
 ---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:26:26.587Z - Hash: fffa0fddaa3425c55dd3 - Time: 37902ms
Successfully built 79f335df92d3
Successfully tagged app:latest

real 3m33.262s
user 0m0.000s
sys 0m0.000s

Ha meg szeretné nézni, hogy megvan-e a kép, futtassa a parancsot docker images:

REPOSITORY   TAG      IMAGE ID       CREATED              SIZE
app          latest   79f335df92d3   About a minute ago   1.74GB

A felépítés előtt a docker az összes fájlt az aktuális környezetben elküldi a démonjának Sending build context to Docker daemon 409MB. A build kontextus a build parancs utolsó argumentumaként van megadva. Esetünkben ez az aktuális könyvtár - „.”, és a Docker mindent áthúz, ami ebben a mappában van. A 409 MB sok: gondoljuk át, hogyan lehet megjavítani.

A kontextus csökkentése

A kontextus csökkentésére két lehetőség van. Vagy helyezze az összeállításhoz szükséges összes fájlt egy külön mappába, és irányítsa a docker környezetet ebbe a mappába. Ez nem mindig kényelmes, ezért lehet kivételeket megadni: mit nem szabad belerángatni a kontextusba. Ehhez helyezze be a .dockerignore fájlt a projektbe, és adja meg, hogy mi nem szükséges a buildhez:

.git
/node_modules

és futtasd újra a buildet:

$ time docker build -t app .
Sending build context to Docker daemon 607.2kB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
 ---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:33:54.338Z - Hash: fffa0fddaa3425c55dd3 - Time: 37313ms
Successfully built 4942f010792a
Successfully tagged app:latest

real 1m47.763s
user 0m0.000s
sys 0m0.000s

A 607.2 KB sokkal jobb, mint a 409 MB. A képméretet is csökkentettük 1.74-ről 1.38 GB-ra:

REPOSITORY   TAG      IMAGE ID       CREATED         SIZE
app          latest   4942f010792a   3 minutes ago   1.38GB

Próbáljuk meg tovább csökkenteni a kép méretét.

Alpine-t használunk

A képméret megtakarításának másik módja egy kis szülőkép használata. A szülőkép az a kép, amely alapján képünk készül. Az alsó réteget a parancs határozza meg FROM a Dockerfile-ban. Esetünkben egy Ubuntu-alapú képet használunk, amelyen már telepítve van a nodejs. És súlya van...

$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB

... majdnem egy gigabájt. Jelentősen csökkentheti a hangerőt az Alpine Linux alapú lemezkép használatával. Az Alpine egy nagyon kicsi Linux. Az alpesi alapú csomópontok docker-képe mindössze 88.5 MB-ot nyom. Cseréljük le élénk arculatunkat a házakban:

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

Telepítenünk kellett néhány dolgot, ami az alkalmazás elkészítéséhez szükséges. Igen, az Angular nem épül fel Python nélkül ¯(°_o)/¯

De a kép mérete 150 MB-ra csökkent:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   aa031edc315a   22 minutes ago   761MB

Menjünk még tovább.

Többlépcsős összeszerelés

Nem minden, ami a képen van, az, amire szükségünk van a gyártás során.

$ docker run app ls -lah
total 576K
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 .
drwxr-xr-x 1 root root 4.0K Apr 16 20:00 ..
-rwxr-xr-x 1 root root 19 Apr 17 2020 .dockerignore
-rwxr-xr-x 1 root root 246 Apr 17 2020 .editorconfig
-rwxr-xr-x 1 root root 631 Apr 17 2020 .gitignore
-rwxr-xr-x 1 root root 181 Apr 17 2020 Dockerfile
-rwxr-xr-x 1 root root 1020 Apr 17 2020 README.md
-rwxr-xr-x 1 root root 3.6K Apr 17 2020 angular.json
-rwxr-xr-x 1 root root 429 Apr 17 2020 browserslist
drwxr-xr-x 3 root root 4.0K Apr 16 19:54 dist
drwxr-xr-x 3 root root 4.0K Apr 17 2020 e2e
-rwxr-xr-x 1 root root 1015 Apr 17 2020 karma.conf.js
-rwxr-xr-x 1 root root 620 Apr 17 2020 ngsw-config.json
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 node_modules
-rwxr-xr-x 1 root root 494.9K Apr 17 2020 package-lock.json
-rwxr-xr-x 1 root root 1.3K Apr 17 2020 package.json
drwxr-xr-x 5 root root 4.0K Apr 17 2020 src
-rwxr-xr-x 1 root root 210 Apr 17 2020 tsconfig.app.json
-rwxr-xr-x 1 root root 489 Apr 17 2020 tsconfig.json
-rwxr-xr-x 1 root root 270 Apr 17 2020 tsconfig.spec.json
-rwxr-xr-x 1 root root 1.9K Apr 17 2020 tslint.json

-Val docker run app ls -lah képünk alapján konténert indítottunk app és végrehajtotta a benne lévő parancsot ls -lah, ami után a konténer befejezte munkáját.

A termelésben csak egy mappára van szükségünk dist. Ebben az esetben a fájlokat valahogy kívül kell adni. Néhány HTTP-kiszolgálót futtathat nodejs-en. De megkönnyítjük. Találj ki egy orosz szót, amely négy „y” betűből áll. Jobb! Ynzhynyksy. Készítsünk képet az nginx-szel, tegyünk bele egy mappát dist és egy kis konfig:

server {
    listen 80 default_server;
    server_name localhost;
    charset utf-8;
    root /app/dist;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Mindehhez a többlépcsős összeállítás segít bennünket. Változtassuk meg a Docker-fájlunkat:

FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

Most két utasításunk van FROM a Dockerfile-ban mindegyik más-más összeállítási lépést futtat. Felhívtuk az elsőt builder, de az utolsó FROM-tól kezdve elkészül a végső képünk. Az utolsó lépés az, hogy az előző lépésben szereplő összeállításunk műtermékét bemásoljuk a végső képbe az nginx segítségével. A kép mérete jelentősen csökkent:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   2c6c5da07802   29 minutes ago   36MB

Futtassuk a tárolót a képünkkel, és győződjön meg arról, hogy minden működik:

docker run -p8080:80 app

A -p8080:80 opció használatával a gazdagépünk 8080-as portját továbbítottuk a 80-as portra azon a tárolón belül, ahol az nginx fut. Megnyitás böngészőben http://localhost:8080/ és látjuk az alkalmazásunkat. Minden működik!

Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

A képméret 1.74 GB-ról 36 MB-ra való csökkentése jelentősen csökkenti az alkalmazás éles üzembe helyezési idejét. De térjünk vissza az összeszerelés idejére.

$ time docker build -t app .
Sending build context to Docker daemon 608.8kB
Step 1/11 : FROM node:12.16.2-alpine3.11 as builder
Step 2/11 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
 ---> Using cache
Step 3/11 : WORKDIR /app
 ---> Using cache
Step 4/11 : COPY . .
Step 5/11 : RUN npm ci
added 1357 packages in 47.338s
Step 6/11 : RUN npm run build --prod
Date: 2020-04-16T21:16:03.899Z - Hash: fffa0fddaa3425c55dd3 - Time: 39948ms
 ---> 27f1479221e4
Step 7/11 : FROM nginx:stable-alpine
Step 8/11 : WORKDIR /app
 ---> Using cache
Step 9/11 : RUN rm /etc/nginx/conf.d/default.conf
 ---> Using cache
Step 10/11 : COPY nginx/static.conf /etc/nginx/conf.d
 ---> Using cache
Step 11/11 : COPY --from=builder /app/dist/app .
Successfully built d201471c91ad
Successfully tagged app:latest

real 2m17.700s
user 0m0.000s
sys 0m0.000s

A rétegek sorrendjének megváltoztatása

Az első három lépésünket a gyorsítótárban tároltuk (tipp Using cache). A negyedik lépésben az összes projektfájl másolásra kerül, az ötödik lépésben pedig a függőségek telepítése RUN npm ci - akár 47.338s. Miért telepítsd újra a függőségeket minden alkalommal, ha nagyon ritkán változnak? Nézzük meg, miért nem lettek gyorsítótárban. A lényeg az, hogy a Docker rétegről rétegre ellenőrzi, hogy a parancs és a hozzá tartozó fájlok változtak-e. A negyedik lépésben a projektünk összes fájlját átmásoljuk, és ezek között természetesen vannak változások, így a Docker nem csak ezt a réteget nem veszi ki a gyorsítótárból, hanem az összes következőt is! Vegyünk néhány apró változtatást a Dockerfile-on.

FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

Először a package.json és a package-lock.json fájlokat másolják át, majd telepítik a függőségeket, és csak ezt követően másolják át a teljes projektet. Ennek eredményeként:

$ time docker build -t app .
Sending build context to Docker daemon 608.8kB
Step 1/12 : FROM node:12.16.2-alpine3.11 as builder
Step 2/12 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
 ---> Using cache
Step 3/12 : WORKDIR /app
 ---> Using cache
Step 4/12 : COPY package*.json ./
 ---> Using cache
Step 5/12 : RUN npm ci
 ---> Using cache
Step 6/12 : COPY . .
Step 7/12 : RUN npm run build --prod
Date: 2020-04-16T21:29:44.770Z - Hash: fffa0fddaa3425c55dd3 - Time: 38287ms
 ---> 1b9448c73558
Step 8/12 : FROM nginx:stable-alpine
Step 9/12 : WORKDIR /app
 ---> Using cache
Step 10/12 : RUN rm /etc/nginx/conf.d/default.conf
 ---> Using cache
Step 11/12 : COPY nginx/static.conf /etc/nginx/conf.d
 ---> Using cache
Step 12/12 : COPY --from=builder /app/dist/app .
Successfully built a44dd7c217c3
Successfully tagged app:latest

real 0m46.497s
user 0m0.000s
sys 0m0.000s

46 perc helyett 3 másodperc – sokkal jobb! Fontos a rétegek helyes sorrendje: először azt másoljuk le, ami nem változik, majd azt, ami ritkán, végül pedig azt, ami gyakran változik.

Ezután néhány szó a képek összeállításáról CI/CD rendszerekben.

Korábbi képek használata a gyorsítótárhoz

Ha valamilyen SaaS megoldást használunk a buildhez, akkor a helyi Docker gyorsítótár tiszta és friss lehet. Ahhoz, hogy a dokkoló megkapja a sült rétegeket, adja meg neki az előző épített képet.

Vegyünk egy példát az alkalmazásunk felépítésére a GitHub Actionsben. Ezt a konfigurációt használjuk

on:
  push:
    branches:
      - master

name: Test docker build

jobs:
  deploy:
    name: Build
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
      IMAGE_TAG: ${{ github.sha }}

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Login to GitHub Packages
      env:
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

    - name: Build
      run: |
        docker build 
          -t $IMAGE_NAME:$IMAGE_TAG 
          -t $IMAGE_NAME:latest 
          .

    - name: Push image to GitHub Packages
      run: |
        docker push $IMAGE_NAME:latest
        docker push $IMAGE_NAME:$IMAGE_TAG

    - name: Logout
      run: |
        docker logout docker.pkg.github.com

A kép összeállítása és a GitHub-csomagokba kerülése két perc és 20 másodperc alatt történik:

Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

Most változtassuk meg az összeállítást úgy, hogy a gyorsítótár a korábbi beépített képek alapján kerüljön felhasználásra:

on:
  push:
    branches:
      - master

name: Test docker build

jobs:
  deploy:
    name: Build
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
      IMAGE_TAG: ${{ github.sha }}

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Login to GitHub Packages
      env:
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

    - name: Pull latest images
      run: |
        docker pull $IMAGE_NAME:latest || true
        docker pull $IMAGE_NAME-builder-stage:latest || true

    - name: Images list
      run: |
        docker images

    - name: Build
      run: |
        docker build 
          --target builder 
          --cache-from $IMAGE_NAME-builder-stage:latest 
          -t $IMAGE_NAME-builder-stage 
          .
        docker build 
          --cache-from $IMAGE_NAME-builder-stage:latest 
          --cache-from $IMAGE_NAME:latest 
          -t $IMAGE_NAME:$IMAGE_TAG 
          -t $IMAGE_NAME:latest 
          .

    - name: Push image to GitHub Packages
      run: |
        docker push $IMAGE_NAME-builder-stage:latest
        docker push $IMAGE_NAME:latest
        docker push $IMAGE_NAME:$IMAGE_TAG

    - name: Logout
      run: |
        docker logout docker.pkg.github.com

Először is el kell mondanunk, hogy miért indul el két parancs build. A helyzet az, hogy egy többlépcsős összeállításban az eredményül kapott kép az utolsó szakaszból származó rétegek halmaza lesz. Ebben az esetben az előző rétegek rétegei nem fognak szerepelni a képen. Emiatt egy korábbi összeállításból származó végső kép használatakor a Docker nem fog tudni kész rétegeket találni a kép csomópontokkal történő felépítéséhez (építési szakasz). A probléma megoldása érdekében egy köztes kép készül $IMAGE_NAME-builder-stage és a GitHub-csomagokba kerül, hogy egy következő összeállításban gyorsítótár-forrásként használható legyen.

Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

A teljes összeszerelési idő másfél percre csökkent. Fél percet tölt a korábbi képek előhúzása.

Előképezés

A tiszta Docker-gyorsítótár probléma megoldásának másik módja az, hogy a rétegek egy részét áthelyezi egy másik Docker-fájlba, külön építi fel, betolja a tárolóregiszterbe, és szülőként használja.

Saját nodejs image-t készítünk egy Angular alkalmazás létrehozásához. Hozzon létre Dockerfile.node-ot a projektben

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++

Nyilvános képet gyűjtünk és továbbítunk a Docker Hubban:

docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest

Most a fő Docker-fájlunkban a kész képet használjuk:

FROM exsmund/node-for-angular:latest as builder
...

Példánkban a felépítési idő nem csökkent, de az előre elkészített képek hasznosak lehetnek, ha sok projektje van, és mindegyikbe ugyanazokat a függőségeket kell telepítenie.

Néhány tipp a Docker-képek felgyorsítására. Például legfeljebb 30 másodpercig

Több módszert is megvizsgáltunk a docker képek felépítésének felgyorsítására. Ha azt szeretné, hogy a telepítés gyorsan menjen, próbálja meg ezt használni a projektben:

  • kontextus csökkentése;
  • kisméretű szülőképek használata;
  • többlépcsős összeszerelés;
  • az utasítások sorrendjének megváltoztatása a Dockerfile-ban a gyorsítótár hatékony használata érdekében;
  • gyorsítótár beállítása CI/CD rendszerekben;
  • előzetes képek elkészítése.

Remélem, a példa világosabbá teszi a Docker működését, és optimálisan konfigurálhatja a telepítést. A cikkben szereplő példákkal való eljátszás érdekében létrehoztunk egy tárat https://github.com/devopsprodigy/test-docker-build.

Forrás: will.com

Hozzászólás