Innan en funktion gÄr live, mÄste den i vÄr tid av komplexa orkestratorer och CI/CD genomgÄ en lÄng vÀg frÄn commit till tester och leverans. Tidigare kunde man bara ladda upp nya filer via FTP (det Àr inte sÄ nÄgon gör lÀngre, eller hur?), och processen för "deployment" tog sekunder. Nu mÄste man skapa en merge request och vÀnta en betydande tid pÄ att funktionen ska nÄ anvÀndarna.
En del av denna vĂ€g Ă€r att bygga Docker-bilder. Ibland tar byggandet minuter, ibland â tiotals minuter, vilket Ă€r svĂ„rt att kalla normalt. I den hĂ€r artikeln kommer vi att ta ett enkelt program, packa det i en bild, tillĂ€mpa nĂ„gra metoder för att snabba upp byggnaden och gĂ„ igenom detaljerna för dessa metoder.

Vi har en ganska bra erfarenhet av att skapa och underhÄlla nyhetssajter: , , , ⊠För inte sÄ lÀnge sedan utökade vi vÄr portfölj genom att lansera sajten . Och medan vi snabbt finjusterade nya funktioner och fixade gamla buggar blev den lÄngsamma deploymenten ett stort problem.
Vi gör deployment pÄ GitLab. Vi bygger bilder, pushar till GitLab Registry och distribuerar till produktion. Det som tar lÀngst tid i den hÀr listan Àr byggandet av bilder. Som exempel: utan optimering tog varje backend-bygge 14 minuter.
![]()
Till slut blev det uppenbart att vi inte kunde fortsÀtta sÄ hÀr, och vi satte oss ned för att förstÄ varför bilderna tog sÄ lÄng tid att bygga. Vi lyckades i slutÀndan minska byggtiden till 30 sekunder!
![]()
För den hÀr artikeln, för att inte knyta oss till Reminder-miljön, kommer vi att titta pÄ ett exempel pÄ hur man bygger en tom applikation pÄ Angular. SÄ, lÄt oss skapa vÄr applikation:
ng n appVi lÀgger till PWA (vi Àr ju progressiva):
ng add @angular/pwa --project appMedan en miljon npm-paket laddas ner, lÄt oss ta en titt pÄ hur en docker-bild Àr uppbyggd. Docker ger möjlighet att paketera applikationer och köra dem i en isolerad miljö som kallas container. Tack vare isoleringen kan mÄnga containrar köras samtidigt pÄ en och samma server. Containrar Àr betydligt lÀttare Àn virtuella maskiner eftersom de körs direkt pÄ systemets kÀrna. För att köra en container med vÄr applikation behöver vi först skapa en bild dÀr vi packar allt som behövs för att vÄr applikation ska fungera. I princip Àr en bild en avbildning av filsystemet. LÄt oss ta ett exempel pÄ Dockerfile:
FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prodDockerfile Àr en uppsÀttning instruktioner; nÀr varje instruktion utförs kommer Docker att spara Àndringarna i filsystemet och applicera dem pÄ föregÄende lager. Varje kommando skapar sitt eget lager. Den fÀrdiga bilden Àr en sammanstÀllning av dessa lager.
Det Àr viktigt att veta: varje lager kan cachas av Docker. Om ingenting har Àndrats sedan den senaste byggprocessen, kommer Docker att ta det redan existerande lagret istÀllet för att utföra kommandot. Eftersom den största hastighetsvinsten i byggprocessen kommer att ske genom anvÀndning av cache, kommer vi att fokusera pÄ byggtiden för att mÀta detta med caching. SÄ, steg för steg:
- Vi tar bort bilder lokalt sÄ att tidigare körningar inte pÄverkar testet.
docker rmi $(docker images -q) - Vi kör bygget första gÄngen.
time docker build -t app . - Vi Ă€ndrar filen src/index.html â simulerar programmerarens arbete.
- Vi kör bygget en andra gÄng.
time docker build -t app .
Om miljön för byggandet av bilder Ă€r korrekt instĂ€lld (vilket beskrivs lite lĂ€ngre ner), kommer Docker vid byggstart att ha en mĂ€ngd cachelagrade objekt. VĂ„rt mĂ„l Ă€r att lĂ€ra oss att anvĂ€nda cachen sĂ„ att byggprocessen gĂ„r sĂ„ snabbt som möjligt. Eftersom vi antar att byggprocessen utan cache bara körs en gĂ„ng â den allra första â kan vi bortse frĂ„n hur lĂ„ngsam den första körningen var. I testerna Ă€r den andra körningen den mest relevanta, nĂ€r cacherna redan Ă€r uppvĂ€rmda och vi Ă€r redo att baka vĂ„r kaka. Trots detta kommer vissa tips ocksĂ„ pĂ„verka den första byggprocessen.
Vi lÀgger Dockerfile som beskrivs ovan i projektmappen och kör bygget. Alla angivna listingar Àr förkortade för att underlÀtta 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.000sVi Àndrar innehÄllet i src/index.html och kör det en andra gÄng.
$ 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.000sFör att se om vi har fÄtt en bild, kör vi följande kommando docker images:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 79f335df92d3 För ungefĂ€r en minut sedan 1.74GBInnan bygget tar Docker alla filer i den aktuella kontexten och skickar dem till sin daemon. Skickar byggets kontext till Docker-demonen 409MB.. Kontexten för bygget anges som det sista argumentet i build-kommandot. I vĂ„rt fall Ă€r det den aktuella katalogen â «.», â och Docker tar med allt vi har i den hĂ€r mappen. 409 megabyte Ă€r mycket: lĂ„t oss tĂ€nka pĂ„ hur vi kan Ă„tgĂ€rda detta.
Minska kontexten.
För att minska kontexten finns det tvÄ alternativ. Antingen lÀgger vi alla filer som behövs för bygget i en separat mapp och anger för Docker att kontexten Àr just den mappen. Detta kanske inte alltid Àr bekvÀmt, sÄ det finns möjlighet att ange undantag: vad som inte ska tas med i kontexten. För detta lÀgger vi till en .dockerignore-fil i projektet och anger vad som inte behövs för bygget:
.git
/node_modulesoch kör bygget igen:
$ time docker build -t app .
Skickar byggets kontext till Docker-demonen 607.2kB
Steg 1/5 : FRĂ
N node:12.16.2
Steg 2/5 : WORKDIR /app
---> AnvÀnder cache
Steg 3/5 : COPY . .
Steg 4/5 : RUN npm ci
lade till 1357 paket pÄ 22.47s
Steg 5/5 : RUN npm run build --prod
Datum: 2020-04-16T19:33:54.338Z - Hash: fffa0fddaa3425c55dd3 - Tid: 37313ms
Byggd framgÄngsrikt 4942f010792a
MĂ€rkt app:latest
reell 1m47.763s
anvÀndare 0m0.000s
system 0m0.000s607.2 kB â mycket bĂ€ttre Ă€n 409 MB. Och dessutom har vi minskat bildens storlek frĂ„n 1.74 till 1.38 GB:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 4942f010792a 3 minuter sedan 1.38GBLÄt oss försöka minska bildens storlek Ànnu mer.
AnvÀnd Alpine.
Ănnu ett sĂ€tt att spara pĂ„ bildens storlek Ă€r att anvĂ€nda en liten förĂ€ldrabild. FörĂ€ldrabilden Ă€r bilden som vĂ„r bild byggs pĂ„. Det undre lagret anges med kommandot FRĂ
N i Dockerfile. I vĂ„rt fall anvĂ€nder vi en bild baserad pĂ„ Ubuntu, som redan har nodejs installerat. Och den vĂ€ger âŠ
$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minuter sedan 916MB⊠nÀstan en gigabyte. Vi kan kraftigt minska storleken genom att anvÀnda en bild baserad pÄ Alpine Linux. Alpine Àr en mycket liten Linux. Docker-bilden för nodejs baserat pÄ Alpine vÀger endast 88.5 MB. SÄ lÄt oss ersÀtta vÄr feta förÀldrabild:
FRĂ
N 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 --prodVi var tvungna att installera nÄgra verktyg som behövs för att bygga applikationen. Ja, Angular byggs inte utan Python ¯(°_o)/¯
Men storleken pÄ bilden minskade med 150 MB:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest aa031edc315a 22 minuter sedan 761MBLÄt oss gÄ Ànnu lÀngre.
Multi-stage build.
Inte allt som finns i bilden behöver vi 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.jsonMed hjÀlp av docker run app ls -lah Vi har startat en container baserad pÄ vÄr bild app och körde ett kommando i den ls -lah, efter vilket containern avslutade sin drift.
I produktion behöver vi bara mappen dist. Samtidigt mĂ„ste vi pĂ„ nĂ„got sĂ€tt ge ut filerna. Vi kan starta en HTTP-server med nodejs. Men vi gör det enklare. Gissa ett ryskt ord som innehĂ„ller fyra bokstĂ€ver âŃâ. RĂ€tt! Đ«ĐœĐ¶ŃĐœŃĐșŃŃ. Vi tar en bild med nginx, lĂ€gger in mappen dist och en liten konfiguration:
server {
listen 80 default_server;
server_name localhost;
charset utf-8;
root /app/dist;
location / {
try_files $uri $uri/ /index.html;
}
}Detta kommer att hjÀlpa oss att genomföra en multi-stage build. Vi Àndrar vÄr 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 tvĂ„ instruktioner FRĂ
N i Dockerfile, var och en av dem kör sin egen byggfas. Den första har vi kallat För Raspberry Pi 3 med un-def-kÀrnor, lts, mp, mÄste en av ljudkortarna (hörlurar eller HDMI) stÀngas av nÀr pulseaudio anvÀnds, annars upplevs lÄngsam avstÀngning av datorn och ljud kan försvinna pÄ grund av fryst pulseaudio., men frÄn den sista FROM kommer vÄrt slutgiltiga bild att förberedas. Som sista steg kopierar vi artefakten frÄn vÄr tidigare byggfas till den slutgiltiga bilden med nginx. Storleken pÄ bilden har minskat betydligt:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 2c6c5da07802 29 minuter sedan 36MBLÄt oss köra en container med vÄr bild och se till att allt fungerar:
docker run -p8080:80 appMed alternativet -p8080:80 har vi vidarebefordrat port 8080 pĂ„ vĂ„r vĂ€rdmaskin till port 80 inuti containern, dĂ€r nginx körs. Ăppna i webblĂ€saren och se vĂ„r applikation. Allt fungerar!

Att minska bildens storlek frÄn 1,74 GB till 36 MB minskar avsevÀrt tiden för att leverera din applikation till produktion. Men lÄt oss ÄtervÀnda till byggtiden.
$ 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.000sVi Àndrar ordningen pÄ lagren
De första tre stegen har cache-lagts (tips Using cache). I det fjĂ€rde steget kopieras alla projektfiler och i det femte steget installeras beroenden RUN npm ci â hela 47.338s. Varför installera beroenden varje gĂ„ng, om de Ă€ndras sĂ„ sĂ€llan? LĂ„t oss gĂ„ igenom varför de inte cachades. Saken Ă€r den att Docker kontrollerar lager för lager, om kommandot och filer kopplade till det har Ă€ndrats. I det fjĂ€rde steget kopierar vi alla filer frĂ„n vĂ„rt projekt, och bland dem finns det givetvis Ă€ndringar, dĂ€rför att Docker inte bara inte tar det hĂ€r lagret frĂ„n cachen, utan Ă€ven alla efterföljande! LĂ„t oss göra nĂ„gra smĂ„ Ă€ndringar i 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 .Först kopieras package.json och package-lock.json, sedan installeras beroenden, och först efter det kopieras hela projektet. Resultatet:
$ 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.000s46 sekunder istĂ€llet för 3 minuter â mycket bĂ€ttre! Viktigt Ă€r den korrekta ordningen pĂ„ lagren: först kopierar vi det som inte Ă€ndras, sedan det som sĂ€llan Ă€ndras, och till sist â det som ofta Ă€ndras.
Sedan nÄgra ord om att bygga bilder i CI/CD-system.
AnvÀndning av tidigare bilder för caching
Om vi anvÀnder en SaaS-lösning för att bygga, kan den lokala cachén av Docker vara ren och frÀsch. För att Docker ska ha nÄgonstans att hÀmta bakade lager frÄn, ge den den tidigare byggda bilden.
LÄt oss ta exemplet med att bygga vÄr applikation i GitHub Actions. Vi anvÀnder en sÄdan 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.comAvbilden samlas och pushas till GitHub Packages pÄ tvÄ minuter och 20 sekunder:

Nu förÀndrar vi bygget sÄ att vi anvÀnder cache baserat pÄ tidigare byggda bilder:
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.comFörst mÄste vi förklara varför tvÄ kommandon körs build. Det handlar om att i en multi-stage-build kommer den resulterande bilden att vara en uppsÀttning lager frÄn det senaste steget. Lager frÄn tidigare steg kommer inte att ingÄ i bilden. DÀrför, nÀr man anvÀnder den slutgiltiga bilden frÄn tidigare byggning, kommer Docker inte att kunna hitta de fÀrdiga lagren för att bygga bilden med nodejs (builder-steget). För att lösa detta problem skapas en mellanliggande bild $IMAGE_NAME-builder-stage och skickas till GitHub Packages sÄ att den kan anvÀndas i den efterföljande byggningen som en cachekÀlla.

Den totala byggetiden har minskat till en och en halv minut. En halv minut gÄr Ät till att hÀmta tidigare bilder.
Förhandsbygge av bilder
Ett annat sÀtt att lösa problemet med ren Docker-cache Àr att extrahera delar av lagren till en annan Dockerfile, bygga den separat, pusha den till Container Registry och anvÀnda den som förÀlder.
Vi skapar vÄr egen Node.js-bild för att bygga Angular-applikationen. Skapa Dockerfile.node i projektet.
FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++Bygg och pusha den offentliga bilden till Docker Hub:
docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latestNu anvÀnder vi den fÀrdiga bilden i vÄr huvud-Dockerfile:
FROM exsmund/node-for-angular:latest as builder
...I vÄrt exempel har byggtiden inte minskat, men förhandsbyggda bilder kan vara anvÀndbara om du har mÄnga projekt som krÀver samma beroenden.

Vi har gÄtt igenom flera metoder för att pÄskynda byggandet av Docker-bilder. Om du vill att distributionen ska gÄ snabbt, prova att implementera följande i ditt projekt:
- reducera kontexten;
- anvÀnda smÄ förÀlderbilder;
- multistage-build;
- Àndra ordningen pÄ instruktionerna i Dockerfile för att effektivt anvÀnda cache;
- konfigurera cache i CI/CD-system;
- förhandsbygga bilder.
Jag hoppas att exemplet gör det tydligare hur Docker fungerar, och att du kan optimera din distribution. För att experimentera med exemplen i artikeln har ett repo skapats. .
KĂ€lla: habr.com
