Voordat 'n funksie in produksie kom, in hierdie dae van komplekse orkestreerders en CI/CD, is daar 'n lang pad om te gaan van toewyding tot toetse en aflewering. Voorheen kon u nuwe lêers via FTP oplaai (niemand doen dit meer nie, reg?), en die "ontplooiing"-proses het sekondes geneem. Nou moet jy 'n samesmeltingsversoek skep en lank wag vir die kenmerk om gebruikers te bereik.
Deel van hierdie pad is om 'n Docker-beeld te bou. Soms duur die samekoms minute, soms tien minute, wat moeilik normaal genoem kan word. In hierdie artikel sal ons 'n eenvoudige toepassing neem wat ons in 'n prent sal verpak, verskeie metodes toepas om die bou te bespoedig, en kyk na die nuanses van hoe hierdie metodes werk.
Ons het goeie ondervinding in die skep en ondersteuning van mediawebwerwe:
Ons ontplooi na GitLab. Ons versamel beelde, stoot dit na GitLab-register en rol dit uit na produksie. Die langste ding op hierdie lys is om beelde saam te stel. Byvoorbeeld: sonder optimalisering het elke backend-bou 14 minute geneem.
Op die ou end het dit duidelik geword dat ons nie meer so kan lewe nie, en ons het gaan sit om uit te vind hoekom die beelde so lank neem om te versamel. As gevolg hiervan het ons daarin geslaag om die monteertyd tot 30 sekondes te verminder!
Kom ons kyk vir hierdie artikel na 'n voorbeeld van die samestelling van 'n leë Angular-toepassing om nie aan die Reminder-omgewing gekoppel te wees nie. Dus, kom ons skep ons toepassing:
ng n app
Voeg PWA daarby (ons is progressief):
ng add @angular/pwa --project app
Terwyl 'n miljoen npm-pakkette afgelaai word, laat ons uitvind hoe die docker-beeld werk. Docker bied die vermoë om toepassings te verpak en uit te voer in 'n geïsoleerde omgewing wat 'n houer genoem word. Danksy isolasie kan u baie houers gelyktydig op een bediener laat loop. Houers is baie ligter as virtuele masjiene omdat hulle direk op die stelselkern loop. Om 'n houer met ons toepassing te laat loop, moet ons eers 'n prent skep waarin ons alles sal verpak wat nodig is om ons toepassing te laat loop. In wese is 'n prent 'n kopie van die lêerstelsel. Neem byvoorbeeld die Dockerfile:
FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod
'n Dockerfile is 'n stel instruksies; Deur elkeen van hulle te doen, sal Docker die veranderinge aan die lêerstelsel stoor en dit op die voriges oorlê. Elke span skep sy eie laag. En die voltooide beeld is lae wat saam gekombineer is.
Wat belangrik is om te weet: elke Docker-laag kan kas. As niks verander het sedert die laaste bou nie, sal die docker 'n klaargemaakte laag neem, in plaas daarvan om die opdrag uit te voer. Aangesien die grootste toename in bouspoed te wyte sal wees aan die gebruik van die kas, sal ons, wanneer bouspoed gemeet word, spesifiek aandag gee aan die bou van 'n beeld met 'n klaargemaakte kas. Dus, stap vir stap:
- Ons vee die beelde plaaslik uit sodat vorige lopies nie die toets beïnvloed nie.
docker rmi $(docker images -q)
- Ons begin die bou vir die eerste keer.
time docker build -t app .
- Ons verander die src/index.html lêer - ons boots die werk van 'n programmeerder na.
- Ons voer die bouwerk 'n tweede keer uit.
time docker build -t app .
As die omgewing vir die bou van beelde korrek opgestel is (meer hieroor hieronder), sal Docker reeds 'n klomp kas aan boord hê wanneer die bou begin. Ons taak is om te leer hoe om die kas te gebruik sodat die bou so vinnig as moontlik gaan. Aangesien ons aanneem dat die bestuur van 'n bou sonder 'n kas slegs een keer gebeur - die heel eerste keer - kan ons dus ignoreer hoe stadig daardie eerste keer was. In toetse is die tweede lopie van die bou vir ons belangrik, wanneer die kas reeds opgewarm is en ons gereed is om ons koek te bak. Sommige wenke sal egter ook die eerste bouwerk beïnvloed.
Kom ons plaas die Dockerfile wat hierbo beskryf word in die projekgids en begin die bou. Alle lyste is saamgevat vir maklike lees.
$ 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
Ons verander die inhoud van src/index.html en voer dit 'n tweede keer uit.
$ 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
Om te sien of ons die prent het, voer die opdrag uit docker images
:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 79f335df92d3 About a minute ago 1.74GB
Voordat jy bou, neem docker al die lêers in die huidige konteks en stuur dit na sy daemon Sending build context to Docker daemon 409MB
. Die bou-konteks word gespesifiseer as die laaste argument vir die bou-opdrag. In ons geval is dit die huidige gids - ".", - en Docker sleep alles wat ons in hierdie gids het. 409 MB is baie: kom ons dink oor hoe om dit reg te stel.
Verminder die konteks
Om die konteks te verminder, is daar twee opsies. Of plaas al die lêers wat nodig is vir samestelling in 'n aparte vouer en wys die docker-konteks na hierdie vouer. Dit is dalk nie altyd gerieflik nie, so dit is moontlik om uitsonderings te spesifiseer: wat nie in die konteks ingesleep moet word nie. Om dit te doen, plaas die .dockerignore-lêer in die projek en dui aan wat nie nodig is vir die bou nie:
.git
/node_modules
en voer die bou weer uit:
$ 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 is baie beter as 409 MB. Ons het ook die beeldgrootte van 1.74 tot 1.38 GB verminder:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 4942f010792a 3 minutes ago 1.38GB
Kom ons probeer om die grootte van die prent verder te verklein.
Ons gebruik Alpine
Nog 'n manier om prentgrootte te bespaar, is om 'n klein ouerprent te gebruik. Die ouerbeeld is die beeld op grond waarvan ons beeld voorberei word. Die onderste laag word deur die opdrag gespesifiseer FROM
in Dockerfile. In ons geval gebruik ons 'n Ubuntu-gebaseerde beeld wat reeds nodejs geïnstalleer het. En dit weeg...
$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB
... amper 'n gigagreep. U kan die volume aansienlik verminder deur 'n prent te gebruik wat op Alpine Linux gebaseer is. Alpine is 'n baie klein Linux. Die docker-beeld vir nodejs gebaseer op alpine weeg slegs 88.5 MB. Laat ons dus ons lewendige beeld in die huise vervang:
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
Ons moes 'n paar dinge installeer wat nodig is om die toepassing te bou. Ja, Angular bou nie sonder Python ¯(°_o)/¯ nie
Maar die beeldgrootte het tot 150 MB gedaal:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest aa031edc315a 22 minutes ago 761MB
Kom ons gaan nog verder.
Multistadium samestelling
Nie alles wat in die beeld is, is wat ons nodig het in produksie nie.
$ 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
Met docker run app ls -lah
ons het 'n houer op grond van ons beeld bekendgestel app
en die opdrag daarin uitgevoer ls -lah
, waarna die houer sy werk voltooi het.
In produksie het ons net 'n gids nodig dist
. In hierdie geval moet die lêers op een of ander manier buite gegee word. U kan een of ander HTTP-bediener op nodejs laat loop. Maar ons sal dit makliker maak. Raai 'n Russiese woord wat vier letters "y" het. Reg! Ynzhynyksy. Kom ons neem 'n prent met nginx, plaas 'n gids daarin dist
en 'n klein konfigurasie:
server {
listen 80 default_server;
server_name localhost;
charset utf-8;
root /app/dist;
location / {
try_files $uri $uri/ /index.html;
}
}
Multi-stadium bou sal ons help om dit alles te doen. Kom ons verander ons 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 .
Nou het ons twee instruksies FROM
in die Dockerfile loop elkeen van hulle 'n ander boustap. Ons het die eerste een gebel builder
, maar vanaf die laaste FROM, sal ons finale beeld voorberei word. Die laaste stap is om die artefak van ons samestelling in die vorige stap na die finale prent met nginx te kopieer. Die grootte van die beeld het aansienlik verminder:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 2c6c5da07802 29 minutes ago 36MB
Kom ons hardloop die houer met ons beeld en maak seker alles werk:
docker run -p8080:80 app
Met die -p8080:80-opsie het ons poort 8080 op ons gasheermasjien aangestuur na poort 80 binne die houer waar nginx loop. Maak oop in blaaier
Deur die beeldgrootte van 1.74 GB tot 36 MB te verklein, verminder dit die tyd wat dit neem om jou toepassing tot produksie te lewer aansienlik. Maar kom ons gaan terug na samekomstyd.
$ 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
Verander die volgorde van lae
Ons eerste drie stappe is gekas (wenk Using cache
). By die vierde stap word alle projeklêers gekopieer en in die vyfde stap word afhanklikhede geïnstalleer RUN npm ci
- soveel as 47.338s. Waarom afhanklikes elke keer weer installeer as dit baie selde verander? Kom ons vind uit hoekom hulle nie gekas is nie. Die punt is dat Docker laag vir laag sal kyk om te sien of die opdrag en die lêers wat daarmee geassosieer word, verander het. By die vierde stap kopieer ons al die lêers van ons projek, en onder hulle is daar natuurlik veranderinge, so Docker neem nie net hierdie laag uit die kas nie, maar ook al die daaropvolgendes! Kom ons maak 'n paar klein veranderinge aan die 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 .
Eerstens word package.json en package-lock.json gekopieer, dan word afhanklikhede geïnstalleer, en eers daarna word die hele projek gekopieer. As gevolg daarvan:
$ 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 sekondes in plaas van 3 minute - baie beter! Die korrekte volgorde van lae is belangrik: eers kopieer ons wat nie verander nie, dan wat selde verander, en laastens wat dikwels verander.
Volgende, 'n paar woorde oor die samestelling van beelde in CI/CD-stelsels.
Gebruik vorige beelde vir kas
As ons 'n soort SaaS-oplossing vir die bou gebruik, kan die plaaslike Docker-kas skoon en vars wees. Om die koppelaar 'n plek te gee om die gebakte lae te kry, gee hom die vorige geboude beeld.
Kom ons neem 'n voorbeeld van die bou van ons toepassing in GitHub Actions. Ons gebruik hierdie konfigurasie
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
Die prent word in twee minute en 20 sekondes saamgestel en na GitHub-pakkette gestoot:
Kom ons verander nou die bou sodat 'n kas gebruik word gebaseer op vorige geboude beelde:
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
Eerstens moet ons jou vertel hoekom twee opdragte geloods word build
. Die feit is dat in 'n meerfase-samestelling die resulterende beeld 'n stel lae van die laaste stadium sal wees. In hierdie geval sal lae van vorige lae nie by die prent ingesluit word nie. Daarom, wanneer die finale prent van 'n vorige bou gebruik word, sal Docker nie gereed lae kan vind om die prent met nodejs (bouer stadium) te bou nie. Om hierdie probleem op te los, word 'n tussenbeeld geskep $IMAGE_NAME-builder-stage
en word na GitHub-pakkette gedruk sodat dit in 'n daaropvolgende bou as 'n kasbron gebruik kan word.
Die totale monteringstyd is tot een en 'n half minuut verminder. 'n Halfminuut word spandeer om vorige beelde op te trek.
Voorafbeelding
Nog 'n manier om die probleem van 'n skoon Docker-kas op te los, is om sommige van die lae na 'n ander Docker-lêer te skuif, dit apart te bou, dit in die Container Registry te druk en dit as 'n ouer te gebruik.
Ons skep ons eie nodejs-beeld om 'n Angular-toepassing te bou. Skep Dockerfile.node in die projek
FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++
Ons versamel en stoot 'n publieke beeld in Docker Hub:
docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest
Nou in ons hoof Dockerfile gebruik ons die voltooide prent:
FROM exsmund/node-for-angular:latest as builder
...
In ons voorbeeld het die boutyd nie afgeneem nie, maar voorafgeboude beelde kan nuttig wees as jy baie projekte het en dieselfde afhanklikhede in elk van hulle moet installeer.
Ons het na verskeie metodes gekyk om die bou van docker-beelde te bespoedig. As jy wil hê dat die ontplooiing vinnig moet gaan, probeer dit in jou projek gebruik:
- vermindering van konteks;
- gebruik van klein ouerbeelde;
- meerfase-samestelling;
- die volgorde van instruksies in die Dockerfile te verander om die kas doeltreffend te gebruik;
- die opstel van 'n kas in CI/CD-stelsels;
- voorlopige skepping van beelde.
Ek hoop dat die voorbeeld dit duideliker sal maak hoe Docker werk, en jy sal jou ontplooiing optimaal kan konfigureer. Om met die voorbeelde uit die artikel te speel, is 'n bewaarplek geskep
Bron: will.com