Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Preden funkcija pride v proizvodnjo, je v teh dneh kompleksnih orkestratorjev in CI/CD dolga pot od izdaje do preizkusov in dostave. Prej ste lahko naložili nove datoteke prek FTP (tega ne počne nihče več, kajne?), postopek "razmestitve" pa je trajal nekaj sekund. Zdaj morate ustvariti zahtevo za združitev in počakati dolgo časa, da funkcija doseže uporabnike.

Del te poti je izgradnja slike Docker. Včasih sestava traja nekaj minut, včasih več deset minut, kar težko imenujemo normalno. V tem članku bomo vzeli preprosto aplikacijo, ki jo bomo zapakirali v sliko, uporabili več metod za pospešitev gradnje in si ogledali nianse delovanja teh metod.

Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Imamo dobre izkušnje pri izdelavi in ​​podpori medijskih spletnih mest: TASS, Bell, "Novi časopis", Republika… Pred kratkim smo razširili naš portfelj z izdajo spletne strani o izdelkih Opomnik. Medtem ko so bile hitro dodane nove funkcije in odpravljene stare napake, je počasna uvedba postala velika težava.

Uvajamo v GitLab. Zbiramo slike, jih potiskamo v register GitLab in uvajamo v proizvodnjo. Najdaljša stvar na tem seznamu je sestavljanje slik. Na primer: brez optimizacije je vsaka izgradnja zaledja trajala 14 minut.

Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Na koncu je postalo jasno, da tako ne moremo več živeti, in usedli smo se, da bi ugotovili, zakaj se slike tako dolgo zbirajo. Posledično nam je uspelo skrajšati čas sestavljanja na 30 sekund!

Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Da ne bomo vezani na Reminderjevo okolje, si za ta članek oglejmo primer sestavljanja prazne aplikacije Angular. Torej, ustvarimo našo aplikacijo:

ng n app

Dodajte mu PWA (smo napredni):

ng add @angular/pwa --project app

Medtem ko se prenaša milijon paketov npm, poglejmo, kako deluje slika dockerja. Docker omogoča pakiranje aplikacij in njihovo izvajanje v izoliranem okolju, imenovanem vsebnik. Zahvaljujoč izolaciji lahko na enem strežniku izvajate več vsebnikov hkrati. Vsebniki so veliko lažji od virtualnih strojev, ker delujejo neposredno v jedru sistema. Za zagon vsebnika z našo aplikacijo moramo najprej ustvariti sliko, v katero bomo zapakirali vse, kar je potrebno za delovanje naše aplikacije. V bistvu je slika kopija datotečnega sistema. Na primer, vzemite Dockerfile:

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

Dockerfile je nabor navodil; Z vsako od njih bo Docker shranil spremembe v datotečni sistem in jih prekril na prejšnje. Vsaka ekipa ustvari svojo plast. In končna slika so plasti, združene skupaj.

Kaj je pomembno vedeti: vsak sloj Docker lahko predpomni. Če se od zadnje gradnje ni nič spremenilo, bo priklopna postaja namesto izvedbe ukaza prevzela že pripravljeno plast. Ker bo glavno povečanje hitrosti gradnje posledica uporabe predpomnilnika, bomo pri merjenju hitrosti gradnje pozorni predvsem na gradnjo slike z že pripravljenim predpomnilnikom. Torej, korak za korakom:

  1. Slike izbrišemo lokalno, tako da prejšnje izvedbe ne vplivajo na test.
    docker rmi $(docker images -q)
  2. Gradnjo zaženemo prvič.
    time docker build -t app .
  3. Spremenimo datoteko src/index.html - posnemamo delo programerja.
  4. Gradnjo izvajamo drugič.
    time docker build -t app .

Če je okolje za gradnjo slik pravilno konfigurirano (več o tem spodaj), bo Docker ob začetku gradnje že imel na krovu kup predpomnilnikov. Naša naloga je, da se naučimo uporabljati predpomnilnik, da bo gradnja potekala čim hitreje. Ker predpostavljamo, da se zagon gradnje brez predpomnilnika zgodi samo enkrat – prvič –, lahko zanemarimo, kako počasen je bil ta prvi čas. Pri testih je za nas pomemben drugi zagon gradnje, ko so predpomnilniki že ogreti in smo pripravljeni na peko naše torte. Vendar pa bodo nekateri nasveti vplivali tudi na prvo gradnjo.

Postavimo zgoraj opisano Dockerfile v mapo projekta in začnimo gradnjo. Vsi seznami so bili zgoščeni zaradi lažjega branja.

$ 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

Spremenimo vsebino src/index.html in jo zaženemo drugič.

$ 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

Če želite videti, ali imamo sliko, zaženite ukaz docker images:

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

Pred gradnjo docker vzame vse datoteke v trenutnem kontekstu in jih pošlje svojemu demonu Sending build context to Docker daemon 409MB. Kontekst gradnje je podan kot zadnji argument ukaza gradnje. V našem primeru je to trenutni imenik - “.”, - in Docker povleče vse, kar imamo v tej mapi. 409 MB je veliko: razmislimo, kako to popraviti.

Zmanjšanje konteksta

Za zmanjšanje konteksta sta na voljo dve možnosti. Ali pa shranite vse datoteke, potrebne za sestavljanje, v ločeno mapo in usmerite kontekst dockerja v to mapo. To morda ni vedno priročno, zato je mogoče določiti izjeme: česa ne smete vleči v kontekst. Če želite to narediti, vstavite datoteko .dockerignore v projekt in označite, kaj ni potrebno za gradnjo:

.git
/node_modules

in znova zaženi gradnjo:

$ 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

607.2 KB je veliko boljše od 409 MB. Zmanjšali smo tudi velikost slike z 1.74 na 1.38 GB:

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

Poskusimo še zmanjšati velikost slike.

Uporabljamo Alpine

Drug način za prihranek pri velikosti slike je uporaba majhne nadrejene slike. Starševska podoba je podoba, na podlagi katere je pripravljena naša podoba. Spodnjo plast določi ukaz FROM v Dockerfile. V našem primeru uporabljamo sliko, ki temelji na Ubuntuju in ima že nameščen nodejs. In tehta ...

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

... skoraj gigabajt. Glasnost lahko bistveno zmanjšate z uporabo slike, ki temelji na Alpine Linux. Alpine je zelo majhen Linux. Slika dockerja za nodejs, ki temelji na alpine, tehta le 88.5 MB. Zamenjajmo torej našo živahno podobo v hišah:

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

Namestiti smo morali nekaj stvari, ki so potrebne za izdelavo aplikacije. Da, Angular ne gradi brez Pythona ¯(°_o)/¯

Toda velikost slike je padla na 150 MB:

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

Pojdimo še dlje.

Večstopenjska montaža

Ni vse, kar je na sliki, tisto, kar potrebujemo v proizvodnji.

$ 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

Z docker run app ls -lah zagnali smo posodo, ki temelji na naši podobi app in v njem izvršil ukaz ls -lah, nakar je kontejner zaključil svoje delo.

V proizvodnji potrebujemo samo mapo dist. V tem primeru je treba datoteke nekako dati zunaj. Na nodejs lahko zaženete kakšen strežnik HTTP. Ampak mi bomo olajšali. Ugani rusko besedo, ki ima štiri črke "y". Prav! Ynzhynyksy. Posnemimo sliko z nginxom, vanjo postavimo mapo dist in majhna konfiguracija:

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

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

Pri vsem tem nam bo pomagala večstopenjska gradnja. Spremenimo našo datoteko Docker:

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 .

Zdaj imamo dve navodili FROM v datoteki Dockerfile vsak od njih izvaja drugačen korak gradnje. Poklicali smo prvega builder, a od zadnjega FROM bo pripravljena naša končna slika. Zadnji korak je kopiranje artefakta našega sklopa v prejšnji fazi v končno sliko z nginxom. Velikost slike se je znatno zmanjšala:

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

Zaženimo vsebnik z našo sliko in poskrbimo, da vse deluje:

docker run -p8080:80 app

Z možnostjo -p8080:80 smo posredovali vrata 8080 na našem gostiteljskem računalniku na vrata 80 znotraj vsebnika, kjer se izvaja nginx. Odpri v brskalniku http://localhost:8080/ in vidimo našo aplikacijo. Vse deluje!

Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Zmanjšanje velikosti slike z 1.74 GB na 36 MB znatno skrajša čas, potreben za dostavo vaše aplikacije v produkcijo. Toda vrnimo se k času montaže.

$ 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

Spreminjanje vrstnega reda plasti

Naši prvi trije koraki so bili predpomnjeni (namig Using cache). V četrtem koraku se kopirajo vse projektne datoteke, v petem koraku pa se namestijo odvisnosti RUN npm ci - kar 47.338s. Zakaj bi vsakič znova nameščali odvisnosti, če se spreminjajo zelo redko? Ugotovimo, zakaj niso bili predpomnjeni. Bistvo je, da bo Docker plast za plastjo preveril, ali so se ukaz in z njim povezane datoteke spremenile. V četrtem koraku kopiramo vse datoteke našega projekta in med njimi so seveda spremembe, tako da Docker ne samo, da ne vzame te plasti iz predpomnilnika, ampak tudi vse naslednje! Naredimo nekaj majhnih sprememb v datoteki Docker.

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 .

Najprej se prekopirata package.json in package-lock.json, nato se namestijo odvisnosti in šele nato se prekopira celoten projekt. Kot rezultat:

$ 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 sekund namesto 3 minut - veliko bolje! Pomemben je pravilen vrstni red slojev: najprej kopiramo tisto, kar se ne spreminja, nato tisto, kar se redko spreminja, in nazadnje tisto, kar se pogosto spreminja.

Nato nekaj besed o sestavljanju slik v sistemih CI/CD.

Uporaba prejšnjih slik za predpomnilnik

Če za gradnjo uporabimo nekakšno rešitev SaaS, je lahko lokalni predpomnilnik Docker čist in svež. Če želite dockerju omogočiti prostor za pridobivanje pečenih plasti, mu dajte prejšnjo zgrajeno sliko.

Vzemimo primer gradnje naše aplikacije v GitHub Actions. Uporabljamo to konfiguracijo

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

Slika je sestavljena in potisnjena v pakete GitHub v dveh minutah in 20 sekundah:

Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Zdaj pa spremenimo gradnjo tako, da bo uporabljen predpomnilnik na podlagi prejšnjih zgrajenih slik:

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

Najprej vam moramo povedati, zakaj se zaženeta dva ukaza build. Dejstvo je, da bo v večstopenjskem sestavu nastala slika niz plasti iz zadnje stopnje. V tem primeru plasti iz prejšnjih plasti ne bodo vključene v sliko. Zato pri uporabi končne slike iz prejšnje gradnje Docker ne bo mogel najti pripravljenih plasti za gradnjo slike z vozlišči (faza graditelja). Za rešitev tega problema se ustvari vmesna slika $IMAGE_NAME-builder-stage in je potisnjen v pakete GitHub, tako da ga je mogoče uporabiti v naslednji gradnji kot vir predpomnilnika.

Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Skupni čas sestavljanja se je zmanjšal na minuto in pol. Pol minute porabimo za vlečenje prejšnjih slik.

Predslikanje

Drug način za rešitev težave s čistim predpomnilnikom Docker je, da premaknete nekatere plasti v drugo datoteko Docker, jo zgradite ločeno, jo potisnete v register vsebnikov in jo uporabite kot nadrejenega.

Ustvarimo lastno sliko nodejs za izdelavo aplikacije Angular. Ustvarite Dockerfile.node v projektu

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

Zbiramo in pošiljamo javno sliko v Docker Hub:

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

Zdaj v naši glavni datoteki Docker uporabljamo končano sliko:

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

V našem primeru se čas gradnje ni zmanjšal, vendar so lahko vnaprej izdelane slike uporabne, če imate veliko projektov in morate v vsakega od njih namestiti iste odvisnosti.

Nekaj ​​nasvetov o tem, kako pospešiti gradnjo slik Docker. Na primer do 30 sekund

Ogledali smo si več metod za pospešitev gradnje slik dockerja. Če želite, da uvajanje poteka hitro, poskusite uporabiti to v svojem projektu:

  • zmanjšanje konteksta;
  • uporaba majhnih nadrejenih slik;
  • večstopenjska montaža;
  • spreminjanje vrstnega reda navodil v datoteki Dockerfile za učinkovito uporabo predpomnilnika;
  • nastavitev predpomnilnika v sistemih CI/CD;
  • predhodno ustvarjanje slik.

Upam, da bo s primerom bolj jasno, kako deluje Docker, in da boste lahko optimalno konfigurirali svojo uvedbo. Za poigravanje s primeri iz članka je bilo ustvarjeno skladišče https://github.com/devopsprodigy/test-docker-build.

Vir: www.habr.com

Dodaj komentar