Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Før en feature kommer i produktion, i disse dage med komplekse orkestratorer og CI/CD, er der lang vej fra forpligtelse til test og levering. Tidligere kunne du uploade nye filer via FTP (ingen gør det længere, vel?), og "deployment"-processen tog sekunder. Nu skal du oprette en fusionsanmodning og vente længe på, at funktionen når ud til brugerne.

En del af denne sti er at bygge et Docker-billede. Nogle gange varer samlingen minutter, nogle gange ti minutter, hvilket næppe kan kaldes normalt. I denne artikel tager vi en simpel applikation, som vi pakker ind i et billede, anvender flere metoder til at fremskynde opbygningen og ser på nuancerne af, hvordan disse metoder fungerer.

Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Vi har god erfaring med at skabe og understøtte mediehjemmesider: TASS, The Bell, "Ny avis", Republikken… For ikke længe siden udvidede vi vores portefølje ved at frigive en produktwebside Påmindelse. Og mens nye funktioner hurtigt blev tilføjet og gamle fejl blev rettet, blev langsom implementering et stort problem.

Vi implementerer til GitLab. Vi samler billeder, skubber dem til GitLab Registry og ruller dem ud til produktion. Den længste ting på denne liste er at samle billeder. For eksempel: uden optimering tog hver backend-build 14 minutter.

Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Til sidst blev det klart, at vi ikke længere kunne leve sådan her, og vi satte os ned for at finde ud af, hvorfor billederne tog så lang tid at indsamle. Som et resultat lykkedes det at reducere monteringstiden til 30 sekunder!

Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Til denne artikel, for ikke at være bundet til Reminders miljø, lad os se på et eksempel på at samle en tom Angular-applikation. Så lad os oprette vores applikation:

ng n app

Tilføj PWA til det (vi er progressive):

ng add @angular/pwa --project app

Mens en million npm-pakker downloades, lad os finde ud af, hvordan docker-billedet fungerer. Docker giver mulighed for at pakke applikationer og køre dem i et isoleret miljø kaldet en container. Takket være isolation kan du køre mange containere samtidigt på én server. Containere er meget lettere end virtuelle maskiner, fordi de kører direkte på systemkernen. For at køre en container med vores applikation skal vi først oprette et billede, hvori vi pakker alt, hvad der er nødvendigt for, at vores applikation kan køre. Grundlæggende er et billede en kopi af filsystemet. Tag for eksempel Dockerfilen:

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

En Dockerfile er et sæt instruktioner; Ved at gøre hver af dem vil Docker gemme ændringerne til filsystemet og overlejre dem på de foregående. Hvert hold opretter sit eget lag. Og det færdige billede er lag kombineret.

Hvad er vigtigt at vide: hvert Docker-lag kan cache. Hvis intet er ændret siden sidste build, vil dockeren i stedet for at udføre kommandoen tage et færdiglavet lag. Da den største stigning i byggehastigheden vil være på grund af brugen af ​​cachen, vil vi ved måling af byggehastighed være særligt opmærksomme på at bygge et billede med en færdiglavet cache. Så trin for trin:

  1. Vi sletter billederne lokalt, så tidligere kørsler ikke påvirker testen.
    docker rmi $(docker images -q)
  2. Vi lancerer byggeriet for første gang.
    time docker build -t app .
  3. Vi ændrer filen src/index.html - vi efterligner en programmørs arbejde.
  4. Vi kører byggeriet for anden gang.
    time docker build -t app .

Hvis miljøet til at bygge billeder er konfigureret korrekt (mere om det nedenfor), så når buildet starter, vil Docker allerede have en masse caches ombord. Vores opgave er at lære at bruge cachen, så opbygningen går så hurtigt som muligt. Da vi antager, at kørsel af en build uden en cache kun sker én gang – allerførste gang – kan vi derfor ignorere, hvor langsom den første gang var. I test er den anden kørsel af bygningen vigtig for os, når gemmerne allerede er varmet op, og vi er klar til at bage vores kage. Nogle tips vil dog også påvirke den første build.

Lad os lægge Dockerfilen beskrevet ovenfor i projektmappen og starte opbygningen. Alle lister er blevet komprimeret for at lette læsningen.

$ 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

Vi ændrer indholdet af src/index.html og kører det en anden gang.

$ 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

For at se, om vi har billedet, skal du køre kommandoen docker images:

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

Før du bygger, tager docker alle filerne i den aktuelle kontekst og sender dem til sin dæmon Sending build context to Docker daemon 409MB. Byg-konteksten er angivet som det sidste argument til build-kommandoen. I vores tilfælde er dette den aktuelle mappe - ".", - og Docker trækker alt, hvad vi har i denne mappe. 409 MB er meget: lad os tænke over, hvordan vi løser det.

Reducer konteksten

For at reducere konteksten er der to muligheder. Eller læg alle de filer, der er nødvendige til samling, i en separat mappe, og peg docker-konteksten til denne mappe. Dette er måske ikke altid praktisk, så det er muligt at specificere undtagelser: hvad skal ikke trækkes ind i konteksten. For at gøre dette skal du lægge .dockerignore-filen i projektet og angive, hvad der ikke er nødvendigt til build:

.git
/node_modules

og kør bygningen igen:

$ 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 er meget bedre end 409 MB. Vi reducerede også billedstørrelsen fra 1.74 til 1.38 GB:

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

Lad os prøve at reducere størrelsen af ​​billedet yderligere.

Vi bruger Alpine

En anden måde at spare på billedstørrelsen er at bruge et lille overordnet billede. Forældrebilledet er det billede, som vores billede er udarbejdet på grundlag af. Det nederste lag er angivet af kommandoen FROM i Dockerfile. I vores tilfælde bruger vi et Ubuntu-baseret billede, der allerede har nodejs installeret. Og den vejer...

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

... næsten en gigabyte. Du kan reducere lydstyrken betydeligt ved at bruge et billede baseret på Alpine Linux. Alpine er en meget lille Linux. Docker-billedet til nodejs baseret på alpine vejer kun 88.5 MB. Så lad os erstatte vores livlige image i husene:

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

Vi skulle installere nogle ting, der er nødvendige for at bygge applikationen. Ja, Angular bygger ikke uden Python ¯(°_o)/¯

Men billedstørrelsen faldt til 150 MB:

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

Lad os gå endnu længere.

Flertrins montage

Ikke alt, der er på billedet, er det, vi har brug for i produktionen.

$ 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

Med docker run app ls -lah vi lancerede en container baseret på vores image app og udførte kommandoen i den ls -lah, hvorefter containeren afsluttede sit arbejde.

I produktionen mangler vi kun en mappe dist. I dette tilfælde skal filerne på en eller anden måde gives udenfor. Du kan køre nogle HTTP-servere på nodejs. Men vi gør det nemmere. Gæt et russisk ord, der har fire bogstaver "y". Højre! Ynzhynyksy. Lad os tage et billede med nginx, lægge en mappe i det dist og en lille konfiguration:

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

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

Multi-stage build vil hjælpe os med alt dette. Lad os ændre vores 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 .

Nu har vi to instruktioner FROM i Dockerfilen kører hver af dem et andet byggetrin. Vi ringede til den første builder, men fra den sidste FROM vil vores endelige billede blive forberedt. Det sidste trin er at kopiere artefakten af ​​vores samling i det foregående trin til det endelige billede med nginx. Billedets størrelse er faldet markant:

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

Lad os køre containeren med vores billede og sørge for, at alt fungerer:

docker run -p8080:80 app

Ved at bruge -p8080:80-indstillingen videresendte vi port 8080 på vores værtsmaskine til port 80 inde i containeren, hvor nginx kører. Åbn i browser http://localhost:8080/ og vi ser vores ansøgning. Alt virker!

Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Reduktion af billedstørrelsen fra 1.74 GB til 36 MB reducerer den tid, det tager at levere din applikation til produktion markant. Men lad os gå tilbage til samlingstiden.

$ 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

Ændring af rækkefølgen af ​​lag

Vores første tre trin blev cachelagret (tip Using cache). På det fjerde trin kopieres alle projektfiler, og på det femte trin installeres afhængigheder RUN npm ci - så meget som 47.338s. Hvorfor geninstallere afhængigheder hver gang, hvis de ændrer sig meget sjældent? Lad os finde ud af, hvorfor de ikke blev gemt. Pointen er, at Docker vil tjekke lag for lag for at se, om kommandoen og de filer, der er knyttet til den, er ændret. På det fjerde trin kopierer vi alle filerne i vores projekt, og blandt dem er der selvfølgelig ændringer, så Docker ikke kun tager dette lag fra cachen, men også alle efterfølgende! Lad os lave nogle små ændringer i Dockerfilen.

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 .

Først kopieres package.json og package-lock.json, derefter installeres afhængigheder, og først derefter kopieres hele projektet. Som resultat:

$ 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 sekunder i stedet for 3 minutter - meget bedre! Den korrekte rækkefølge af lag er vigtig: Først kopierer vi det, der ikke ændrer sig, derefter det, der sjældent ændres, og til sidst det, der ændres ofte.

Dernæst et par ord om at samle billeder i CI/CD-systemer.

Brug af tidligere billeder til cache

Hvis vi bruger en form for SaaS-løsning til bygningen, kan den lokale Docker-cache være ren og frisk. For at give havnearbejderen et sted at få de bagte lag, giv ham det tidligere byggede billede.

Lad os tage et eksempel på at bygge vores applikation i GitHub Actions. Vi bruger denne konfiguration

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

Billedet samles og skubbes til GitHub-pakker på to minutter og 20 sekunder:

Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Lad os nu ændre opbygningen, så en cache bruges baseret på tidligere byggede billeder:

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

Først skal vi fortælle dig, hvorfor to kommandoer startes build. Faktum er, at i en flertrinssamling vil det resulterende billede være et sæt lag fra den sidste fase. I dette tilfælde vil lag fra tidligere lag ikke blive inkluderet i billedet. Derfor, når du bruger det endelige billede fra en tidligere build, vil Docker ikke være i stand til at finde klare lag til at bygge billedet med nodejs (builder-stadiet). For at løse dette problem oprettes et mellembillede $IMAGE_NAME-builder-stage og skubbes til GitHub-pakker, så det kan bruges i en efterfølgende build som en cachekilde.

Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Den samlede montagetid blev reduceret til halvandet minut. Der bruges et halvt minut på at trække tidligere billeder op.

Præbilledbehandling

En anden måde at løse problemet med en ren Docker-cache på er at flytte nogle af lagene ind i en anden Dockerfil, bygge den separat, skubbe den ind i Container Registry og bruge den som forælder.

Vi opretter vores eget nodejs-billede for at bygge en Angular-applikation. Opret Dockerfile.node i projektet

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

Vi indsamler og skubber et offentligt billede i Docker Hub:

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

Nu i vores hoved Dockerfile bruger vi det færdige billede:

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

I vores eksempel faldt byggetiden ikke, men forudbyggede billeder kan være nyttige, hvis du har mange projekter og skal installere de samme afhængigheder i hver af dem.

Nogle tips til, hvordan man fremskynder opbygningen af ​​Docker-billeder. For eksempel op til 30 sekunder

Vi så på flere metoder til at fremskynde opbygningen af ​​docker-billeder. Hvis du ønsker, at implementeringen skal gå hurtigt, kan du prøve at bruge dette i dit projekt:

  • reducere kontekst;
  • brug af små forældrebilleder;
  • flertrins samling;
  • at ændre rækkefølgen af ​​instruktionerne i Dockerfilen for at gøre effektiv brug af cachen;
  • opsætning af en cache i CI/CD-systemer;
  • foreløbig fremstilling af billeder.

Jeg håber, at eksemplet vil gøre det tydeligere, hvordan Docker fungerer, og du vil være i stand til at konfigurere din implementering optimalt. For at lege med eksemplerne fra artiklen er der oprettet et depot https://github.com/devopsprodigy/test-docker-build.

Kilde: www.habr.com

Tilføj en kommentar