Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

Prima chì una funzione entra in produzzione, in questi ghjorni di orchestratori cumplessi è CI / CD, ci hè una longa strada per andà da l'impegnu à e teste è a consegna. Nanzu, pudete caricate novi fugliali via FTP (nimu ùn face più, nò?), è u prucessu di "spiegamentu" hà pigliatu seconde. Avà avete bisognu di creà una dumanda di fusione è aspittà assai tempu per a funzione per ghjunghje à l'utilizatori.

Una parte di sta strada hè di custruisce una maghjina Docker. Calchì volta l'assemblea dura minuti, qualchì volta decine di minuti, chì ùn pò micca esse chjamatu normale. In questu articulu, avemu da piglià una applicazione simplice chì avemu da imballà in una maghjina, applicà parechji metudi per accelerà a custruzzione, è fighjemu i sfumaturi di cumu si travaglianu sti metudi.

Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

Avemu una bona sperienza in a creazione è supportu di siti web media: TASS, U campanile di, "New Newspaper", Ripùbbrica... Pocu pocu fà avemu allargatu a nostra cartera liberando un situ web di produttu Reminder. E mentre chì e funzioni novi sò state aghjunte rapidamente è i vechji bug sò stati fissi, a implementazione lenta hè diventata un grande prublema.

Implementemu in GitLab. Raccogliamu l'imaghjini, li spingemu à u Registru di GitLab è li stendemu à a produzzione. A cosa più longa in questa lista hè l'assemblea di l'imaghjini. Per esempiu: senza ottimisazione, ogni custruzzione di backend hà pigliatu 14 minuti.

Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

À a fine, hè diventatu chjaru chì ùn pudemu micca più campà cusì, è avemu pusatu per capisce perchè l'imaghjini piglianu tantu tempu per cullà. In u risultatu, avemu riisciutu à riduce u tempu di assemblea à 30 seconde!

Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

Per questu articulu, per ùn esse ligatu à l'ambiente di Reminder, fighjemu un esempiu di assemblea una applicazione Angular viota. Allora, creemu a nostra applicazione:

ng n app

Aghjunghjite PWA (semu prugressivi):

ng add @angular/pwa --project app

Mentre chì un milione di pacchetti npm sò scaricati, scopremu cumu funziona l'imaghjini docker. Docker furnisce l'abilità di imballà applicazioni è eseguisce in un ambiente isolatu chjamatu cuntainer. Grazie à l'isolamentu, pudete eseguisce parechji cuntenituri simultaneamente in un servitore. I cuntenituri sò assai più ligeri cà i machini virtuali perchè correnu direttamente nantu à u kernel di u sistema. Per eseguisce un cuntinuu cù a nostra applicazione, avemu prima bisognu di creà una maghjina in quale impaccheremu tuttu ciò chì hè necessariu per a nostra applicazione per eseguisce. Essenzialmente, una maghjina hè una copia di u sistema di fugliale. Per esempiu, pigliate u Dockerfile:

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

Un Dockerfile hè un inseme di struzzioni; Facendu ognuna di elli, Docker salvarà i cambiamenti in u sistema di schedari è li superpone à i precedenti. Ogni squadra crea a so propria capa. È l'imaghjini finiti sò strati cumminati inseme.

Ciò chì hè impurtante di sapè: ogni capa Docker pò cache. Se nunda hà cambiatu da l'ultima custruzzione, invece di eseguisce u cumandamentu, u docker hà da piglià una capa pronta. Siccomu l'aumentu principale di a velocità di custruzzione serà dovutu à l'usu di a cache, quandu si misurà a velocità di custruzzione, prestaremu attente specificamente à custruisce una maghjina cù una cache pronta. Allora, passu à passu:

  1. Sguassemu l'imaghjini in u locu in modu chì e corse precedenti ùn affettanu micca a prova.
    docker rmi $(docker images -q)
  2. Lanciamu a custruzione per a prima volta.
    time docker build -t app .
  3. Cambiamu u schedariu src/index.html - imitemu u travagliu di un programatore.
  4. Eseguimu a custruzione una seconda volta.
    time docker build -t app .

Se l'ambiente per a custruzzione di l'imaghjini hè cunfiguratu currettamente (più nantu à quì sottu), allora quandu a custruzione principia, Docker hà digià un munzeddu di cache à bordu. U nostru compitu hè d'amparà cumu utilizà a cache in modu chì a custruzione và u più prestu pussibule. Siccomu assumemu chì l'esecuzione di una custruzzione senza cache succede solu una volta - a prima volta - pudemu dunque ignurà quantu lento era quella prima volta. In i testi, a seconda corsa di a custruzzione hè impurtante per noi, quandu i cache sò digià riscaldati è simu pronti per coce a nostra torta. Tuttavia, certi cunsiglii affettanu ancu a prima custruzione.

Pudemu u Dockerfile descrittu sopra in u cartulare di u prughjettu è cuminciamu a custruisce. Tutti i listi sò stati condensati per facilità di lettura.

$ 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

Cambiamu u cuntenutu di src/index.html è eseguite una seconda volta.

$ 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

Per vede s'ellu avemu l'imaghjini, eseguite u cumandamentu docker images:

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

Prima di custruisce, docker piglia tutti i schedari in u cuntestu attuale è li manda à u so daemon Sending build context to Docker daemon 409MB. U cuntestu di custruzzione hè specificatu cum'è l'ultimu argumentu à u cumandamentu di custruzzione. In u nostru casu, questu hè u cartulare attuale - ".", - è Docker trascina tuttu ciò chì avemu in questu cartulare. 409 MB hè assai: pensemu à cumu risolve.

Reduce u cuntestu

Per riduce u cuntestu, ci sò duie opzioni. O mette tutti i fugliali necessarii per l'assemblea in un cartulare separatu è puntate u cuntestu docker à questu cartulare. Questu ùn hè micca sempre cunvene, cusì hè pussibule di specificà eccezzioni: ciò chì ùn deve esse trascinatu in u cuntestu. Per fà questu, mette u schedariu .dockerignore in u prugettu è indica ciò chì ùn hè micca necessariu per a custruzzione:

.git
/node_modules

è eseguite a custruzione di novu:

$ 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 hè assai megliu cà 409 MB. Avemu ancu riduciutu a dimensione di l'imagine da 1.74 à 1.38 GB:

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

Pruvemu di riduce a dimensione di l'imaghjini più.

Avemu aduprà Alpine

Un altru modu per salvà a dimensione di l'imaghjini hè di utilizà una piccula maghjina parent. L'imaghjini parentali hè l'imaghjini nantu à a basa di quale a nostra imagina hè preparata. A capa di fondu hè specificatu da u cumandimu FROM in Dockerfile. In u nostru casu, usemu una maghjina basata in Ubuntu chì hà digià installatu nodejs. È pesa...

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

... quasi un gigabyte. Pudete riduce significativamente u voluminu usendu una maghjina basata in Alpine Linux. Alpine hè un Linux assai chjucu. L'imaghjini docker per nodejs basati in alpine pesa solu 88.5 MB. Allora rimpiazzemu a nostra viva maghjina in e case:

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

Avemu avutu à stallà alcune cose chì sò necessarii per custruisce l'applicazione. Iè, Angular ùn hè micca custruitu senza Python ¯(°_o)/¯

Ma a dimensione di l'imagine hè cascata à 150 MB:

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

Andemu ancu più luntanu.

Assemblage multistage

Micca tuttu ciò chì hè in l'imaghjini hè ciò chì avemu bisognu in a produzzione.

$ 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

Cù l'aiutu di docker run app ls -lah avemu lanciatu un containeru basatu annantu à a nostra maghjina app è eseguitu u cumandamentu in questu ls -lah, dopu chì u cuntinuu hà finitu u so travagliu.

In a pruduzzione avemu solu bisognu di un cartulare dist. In questu casu, i fugliali anu da esse datu fora. Pudete eseguisce qualchì servitore HTTP in nodejs. Ma l'averemu più faciule. Indovinà una parolla russa chì hà quattru lettere "y". Diritta! Ynzhynyksy. Pigliemu una maghjina cù nginx, mette un cartulare in questu dist è una piccula cunfigurazione:

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

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

A custruzzione in più tappe ci aiuterà à fà tuttu questu. Cambiamu u nostru Dockerfile:

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 .

Avà avemu dui struzzioni FROM in u Dockerfile, ognuna di elli corre un passu di custruzzione diversu. Avemu chjamatu u primu builder, ma partendu da l'ultimu FROM, a nostra maghjina finali serà preparata. L'ultimu passu hè di copià l'artefattu di a nostra assemblea in u passu precedente in l'imaghjini finali cù nginx. A dimensione di l'imaghjini hè diminuitu significativamente:

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

Eseguimu u cuntinuu cù a nostra maghjina è assicuratevi chì tuttu funziona:

docker run -p8080:80 app

Utilizendu l'opzione -p8080: 80, avemu trasmessu u portu 8080 nantu à a nostra macchina host à u portu 80 in u cuntainer induve nginx corre. Apertura in u navigatore http://localhost:8080/ è vedemu a nostra applicazione. Tuttu travaglia!

Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

A riduzione di a dimensione di l'imaghjini da 1.74 GB à 36 MB riduce significativamente u tempu chì ci vole à furnisce a vostra applicazione à a produzzione. Ma tornemu à u tempu di assemblea.

$ 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

Cambia l'ordine di i strati

I nostri primi trè passi sò stati salvati in cache (suggerimentu Using cache). À u quartu passu, tutti i schedarii di u prughjettu sò copiati è à u quintu passu dependenzii sò stallati RUN npm ci - quant'è 47.338s. Perchè reinstallà dipendenze ogni volta si cambianu assai raramente? Scupritemu perchè ùn sò micca stati in cache. U puntu hè chì Docker verificarà strata per capa per vede s'ellu u cumandamentu è i schedarii assuciati sò cambiati. À u quartu passu, copiemu tutti i schedarii di u nostru prughjettu, è trà elli, sicuru, ci sò cambiamenti, cusì Docker ùn solu ùn piglia micca sta capa da a cache, ma ancu tutti i successivi! Facemu alcuni picculi cambiamenti à u Dockerfile.

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 .

Prima, package.json è package-lock.json sò copiati, dopu i dependenzii sò stallati, è solu dopu chì tuttu u prughjettu hè copiatu. Di cunsiguenza:

$ 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 seconde invece di 3 minuti - assai megliu! L'ordine currettu di strati hè impurtante: prima copiamu ciò chì ùn cambia micca, dopu ciò chì cambia raramente, è infine ciò chì cambia spessu.

In seguitu, uni pochi di parolle nantu à l'assemblea di l'imaghjini in sistemi CI / CD.

Utilizà l'imaghjini precedenti per a cache

Se usemu un tipu di suluzione SaaS per a custruzione, allora a cache Docker locale pò esse pulita è fresca. Per dà à u docker un locu per uttene i strati cotti, dà l'imaghjini custruiti precedente.

Pigliemu un esempiu di custruisce a nostra applicazione in GitHub Actions. Avemu aduprà sta cunfigurazione

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

L'imaghjina hè assemblata è imbuttata à i Pacchetti GitHub in dui minuti è 20 seconde:

Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

Avà cambiate a custruzzione in modu chì una cache hè aduprata basatu annantu à l'imaghjini custruiti precedenti:

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

Prima ci vole à dì perchè dui cumandamenti sò lanciati build. U fattu hè chì in una assemblea multistage l'imaghjini resultanti serà un inseme di strati da l'ultima tappa. In questu casu, i strati di strati precedenti ùn saranu micca inclusi in l'imaghjini. Dunque, quandu si usa l'imaghjini finali da una custruzzione precedente, Docker ùn serà micca capaci di truvà strati pronti per custruisce l'imaghjini cù nodejs (stadiu di custruttore). Per risolve stu prublema, hè creatu una maghjina intermedia $IMAGE_NAME-builder-stage è hè imbuttatu à i Pacchetti GitHub in modu chì pò esse usatu in una custruzzione successiva cum'è fonte di cache.

Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

U tempu tutale di l'assemblea hè stata ridutta à un minutu è mezu. A mità di minutu si spende per tirare l'imaghjini precedenti.

Preimaging

Un'altra manera di risolve u prublema di una cache Docker pulita hè di trasfurmà alcune di e strati in un altru Dockerfile, custruite separatamente, spinghjelu in u Registru di Container è l'utilizanu cum'è parent.

Creemu a nostra propria imagina nodejs per custruisce una applicazione Angular. Crea Dockerfile.node in u prugettu

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

Cullemu è spingemu una maghjina publica in Docker Hub:

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

Avà in u nostru Dockerfile principale usemu l'imagine finita:

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

In u nostru esempiu, u tempu di custruzzione ùn hè micca diminuitu, ma l'imaghjini pre-custruiti ponu esse utili si avete assai prughjetti è avete da installà e stesse dependenze in ognuna di elli.

Certi cunsiglii per accelerà a custruzzione di l'imaghjini Docker. Per esempiu, finu à 30 seconde

Avemu guardatu parechji metudi per accelerà a custruzione di l'imaghjini docker. Se vulete chì l'implementazione vada rapidamente, pruvate aduprà questu in u vostru prughjettu:

  • riducendu u cuntestu;
  • usu di picculi imagine parenti;
  • assemblea multistage;
  • cambià l'ordine di l'urdinamentu in u Dockerfile per fà un usu efficace di a cache;
  • crià una cache in sistemi CI/CD;
  • creazione preliminare di imagine.

Speru chì l'esempiu farà più chjaru cumu funziona Docker, è puderete cunfigurà in modu ottimale a vostra implementazione. Per ghjucà cù l'esempii di l'articulu, hè statu creatu un repository https://github.com/devopsprodigy/test-docker-build.

Source: www.habr.com

Add a comment