Enne funktsioon jõuab tootmisse, peab see meie keerukate orkestratorite ja CI/CD ajastul läbima pika tee commitist testide ja tarnimiseni. Varem sai faile FTP kaudu kiiresti üles laadida (aga keegi ei tee seda enam, eks?), ja „deploion“ kestis sekundeid. Nüüd tuleb luua merge request ja oodata, kuni funktsioon jõuab kasutajateni.
Osa sellest teest on Docker-pildi koostamine. Mõnikord kestab koostamine minuteid, mõnikord — kümneid minuteid, mida on raske normaalseks pidada. Selles artiklis võtame lihtsa rakenduse, pakime selle pildiks, rakendame mitmeid meetodeid koostamise kiirendamiseks ja vaatame nende meetodite nüansse.

Meil on hea kogemus meedia veebisaitide loomisel ja haldamisel: , , , … Eelmisel ajal täiendame oma portfelli, käivitades tootmisse veebisaidi . Ja samal ajal kui me kiiresti täiendame uusi funktsioone ja parandame vanu vigu, on aeglane deploion muutunud suureks probleemiks.
Deploime GitLabis. Koostame pilte, pushime need GitLabi registrisse ja levitame tootmises. Sellest nimekirjast on aeglasem osa piltide koostamine. Näiteks: ilma optimeerimiseta kestis iga backendi koostamine 14 minutit.
![]()
Lõpuks selgus, et nii ei saa enam elada, ja istusime maha, et aru saada, miks pildid nii kaua koostavad. Lõpuks õnnestus koostamisaega kärpida 30 sekundini!
![]()
Käesolevas artiklis, et mitte seonduda Reminder'i keskkonnaga, vaatame näidet tühja rakenduse koostamisest Angularis. Alustame meie rakenduse loomisega:
ng n appLisame sellele PWA (me oleme ju progressiivsed):
ng add @angular/pwa --project appKuni miljoni npm-paketi allalaadimise jooksul vaatame, kuidas docker-pilt töötab. Docker võimaldab rakendusi pakendada ja käitada neid isoleeritud keskkonnas, mida nimetatakse konteineriks. Isolatsiooni tõttu saab samaaegselt ühel serveril käitada palju konteineri. Konteinerid on oluliselt kergemad virtuaalsetest masinatest, kuna need töötavad otse süsteemi tuumast. Meie rakenduse konteineri käivitamiseks peame esmalt looma pildi, kuhu pakime kõik vajalikud komponendid meie rakenduse talitamiseks. Sisuliselt on pilt failisüsteemi koopia. Näiteks võtame Dockerfile'i:
FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prodDockerfile on komplekt juhiseid; täitmisel salvestab Docker igaühe muudatused failisüsteemis ja lisab need eelnevate peale. Iga käsk loob oma kihi. Valmis image on kokku pandud kihtide kombinatsioon.
Oluline teada: iga kiht suudab Dockeris vahemälu salvestada. Kui midagi pole muutunud eelmise ehituse ajal, siis pigem kui käsku täita, võtab docker juba olemasoleva kihi. Kuna peamine kiirusetõus ehituses toimub vahemälu kasutamise kaudu, pöörame ehituse kiirusmõõtmistesse tähelepanu just valmiskihile. Nii et, sammud on järgmised:
- Kustutame kohalikult pildid, et varasemad käivitused ei mõjutaks testi.
docker rmi $(docker images -q) - Käivitame ehituse esmakordselt.
time docker build -t app . - Muudame faili src/index.html — jäljendame programmeerija tööd.
- Käivitame ehituse teist korda.
time docker build -t app .
Kui pildiehituse keskkond on õigesti seadistatud (mille kohta räägime natuke allpool), on dockeri käivitamisel juba hulk vahemälusid valmis. Meie ülesanne on õppida vahemälu kasutama nii, et ehitus kulgeks võimalikult kiiresti. Kuna eeldame, et ilma vahemäluta ehitus käivitub ainult üks kord — kõige esimene — võime ignoreerida, kui aeglane oli see esimene kord. Testides on meie jaoks oluline teine ehitus, kui vahemälud on juba soojendatud ja oleme valmis meie pirukat küpsetama. Siiski mõnede nõuannete mõju avaldub ka esimesel ehitusel.
Paigutame eespool kirjeldatud Dockerfile projekti kausta ja käivitame ehituse. Kõik esitatud loetelud on lühendatud lugemise mugavuse huvides.
$ time docker build -t app .
Saadetakse ehituskontekst Docker daemonile 409MB
Samm 1/5 : FROM node:12.16.2
Oleku: Uus pilt node:12.16.2
Samm 2/5 : WORKDIR /app
Samm 3/5 : COPY . .
Samm 4/5 : RUN npm ci
lisatud 1357 paketid 22.47s
Samm 5/5 : RUN npm run build --prod
Kuupäev: 2020-04-16T19:20:09.664Z - Hash: fffa0fddaa3425c55dd3 - Aeg: 37581ms
Edukas ehitus c8c279335f46
Edukas märgistatud app:latest
reaalne 5m4.541s
kasutaja 0m0.000s
süsteem 0m0.000sMuudame sisu src/index.html ja käivitame teist korda.
$ time docker build -t app .
Saadetakse ehituskontekst Docker daemonile 409MB
Samm 1/5 : FROM node:12.16.2
Samm 2/5 : WORKDIR /app
---> Kasutatakse vahemälu
Samm 3/5 : COPY . .
Samm 4/5 : RUN npm ci
lisatud 1357 paketid 22.47s
Samm 5/5 : RUN npm run build --prod
Kuupäev: 2020-04-16T19:26:26.587Z - Hash: fffa0fddaa3425c55dd3 - Aeg: 37902ms
Edukas ehitus 79f335df92d3
Edukas märgistatud app:latest
reaalne 3m33.262s
kasutaja 0m0.000s
süsteem 0m0.000sKuna tahame näha, kas oleme pildi saanud, käivitame käsu docker images:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 79f335df92d3 Umbes minut tagasi 1.74GBEnne dokkeri kogumist võtab dokker kõik failid praeguses kontekstis ja saadab need oma daemoni Saadan ehituse konteksti Docker daemonile 409MB. Ehituse konteksti määratakse build käsu viimase argumendina. Meie puhul on see praegune kataloog — ".", — ja dokker toob kõik, mis meil selles kaustas on. 409 MB on palju: mõelgem, kuidas seda parandada.
Vähendame konteksti
Konteksti vähendamiseks on kaks võimalust. Kas panna kõik ehitamiseks vajalikud failid eraldi kausta ja osutada dokkerile just sellele kaustale. See ei pruugi alati mugav olla, seega on võimalus määrata erandeid: mida ei pea konteksti tooma. Selleks paneme projekti faili .dockerignore ja määrame, mida ei ole vajalik ehitamiseks:
.git
/node_modulesja käivitame ehituse uuesti:
$ time docker build -t app .
Saadan ehituse konteksti Docker daemonile 607.2kB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
---> Kasutatakse vahemälu
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
Lisatud 1357 paketti 22.47s jooksul
Step 5/5 : RUN npm run build --prod
Kuupäev: 2020-04-16T19:33:54.338Z - Hash: fffa0fddaa3425c55dd3 - Aeg: 37313ms
Eduka ehituse lõpetamine 4942f010792a
Eduka sildistamise lõpetamine app:latest
reaalne 1m47.763s
kasutaja 0m0.000s
süsteem 0m0.000s607.2 Kb — palju parem kui 409 MB. Lisaks vähendasime pildi suurust 1.74 GB-lt 1.38 GB-le:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 4942f010792a 3 minutit tagasi 1.38GBProovime veelgi pildi suurust vähendada.
Kasutame Alpine'i
Veel üks viis pildi suuruse kärpimiseks on kasutada väikest vanemat pilti. Vanem pilt on see pilt, mille põhjal meie pilt luuakse. Alumine kiht määratakse käsuga KUST Dockerfile'is. Meie puhul kasutame Ubuntu põhjal tehtud pilti, kus on juba installitud nodejs. Ja selle suurus on …
$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutit tagasi 916MB… peaaegu gigabait. Oluliselt mahtu vähendada saame, kasutades Alpine Linux põhjal tehtud pilti. Alpine on väga väike linux. Docker pilt nodejs jaoks Alpine'i põhjal kaalub vaid 88.5 MB. Seega asendame meie suure pildi:
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 --prodMe pidime installima mõned asjad, mis on vajalikud rakenduse ehitamiseks. Jah, Angular ei ehitu ilma pythonita ¯(°_o)\/¯
Aga pildi suurus langes 150 MB:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest aa031edc315a 22 minutit tagasi 761MBLiigume veelgi kaugemale.
Mitme etapi ehitus
Ei ole kõike, mis pildis on, meie jaoks tootmises vajalik.
$ 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.jsonEL-i abil docker run app ls -lah meie käivitasime konteineri meie pildist app ja täitsime selles käsu ls -lah, pärast mida konteiner lõpetas oma töö.
Prod-keskkonnas vajame ainult kausta dist. Sellegipoolest peame failid kuidagi väljastama. Saame käivitada mõne HTTP-serveri nodejs-is. Kuid teeme lihtsamalt. Arva vene sõna, milles on neli tähte «ы». Õige! Ынжыныксы. Võtame nginx-i pildi, paneme sinna kausta dist ja väikese konfi:
server {
listen 80 default_server;
server_name localhost;
charset utf-8;
root /app/dist;
location / {
try_files $uri $uri/ /index.html;
}
}Seda kõike saab teha multi-stage build abil. Muudame meie Dockerfile'i:
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 .Nüüd on meil kaks käsku KUST Dockerfile'is, millest igaühel on oma ehitusetapp. Esimese nimega Raspberry Pi 3 jaoks un-def, lts, mp kernelitega, kui pulseaudio on kasutusel, tuleb ühe helikaardi (kõrvaklapid või HDMI) välja lülitada, vastasel juhul võib arvuti välja lülitamine kesta kaua ja heli võib kaduda pulseaudio tõttu., kuid alates viimast FROM-ist valmistatakse meie lõplikku pilti. Viimasena kopeerime meie ehitusartefakti eelmisest etapist nginx-i lõplikku pilti. Pildi suurus on oluliselt vähenenud:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 2c6c5da07802 29 minutes ago 36MBKäivitame konteineri meie pildiga ja veendume, et kõik töötab:
docker run -p8080:80 appValiku -p8080:80 kaudu suunamine viib meie hostmasinalt portaali 8080 konteineri 80-le, kus nginx töötab. Avame brauseris ja näeme meie rakendust. Kõik töötab!

Pildi suuruse vähendamine 1,74 GB-lt 36 MB-le lühendab oluliselt teie rakenduse toimetamise aega. Kuid naaseme ehitusaega.
$ 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.000sMuudame kihtide järjekorda
Esimesed kolm sammu on meil vahemälul (vihje Using cache). Neljandas etapis kopeeritakse kõik projektifailid ja viiendas etapis installitakse sõltuvused RUN npm ci — kokku 47.338s. Miks installida sõltuvusi iga kord uuesti, kui need harva muutuvad? Vaatame, miks need ei ole vahemälusse salvestunud. Asi on selles, et Docker kontrollib kihtide kaupa, kas käsk ja sellega seotud failid on muutunud. Neljandas sammus kopeerime kõik meie projekti failid, ja nende seas on loomulikult muudatusi, seega Docker ei kasuta mitte ainult seda kihti vahemälust, vaid ka kõiki järgnevaid! Teeme Dockerfile'is väikeseid muudatusi.
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 .Esiteks kopeeritakse package.json ja package-lock.json, seejärel installitakse sõltuvused ja alles siis kopeeritakse terve projekt. Tulemuseks:
$ time docker build -t app .
Saatmine ehituse konteksti Docker daemonile 608.8kB
1/12 etapp: FROM node:12.16.2-alpine3.11 as builder
2/12 etapp: RUN apk --no-cache --update --virtual build-dependencies add python make g++
---> Kasutatakse vahemälu
3/12 etapp: WORKDIR /app
---> Kasutatakse vahemälu
4/12 etapp: COPY package*.json ./
---> Kasutatakse vahemälu
5/12 etapp: RUN npm ci
---> Kasutatakse vahemälu
6/12 etapp: COPY . .
7/12 etapp: RUN npm run build --prod
Kuupäev: 2020-04-16T21:29:44.770Z - Hash: fffa0fddaa3425c55dd3 - Aeg: 38287ms
---> 1b9448c73558
8/12 etapp: FROM nginx:stable-alpine
9/12 etapp: WORKDIR /app
---> Kasutatakse vahemälu
10/12 etapp: RUN rm /etc/nginx/conf.d/default.conf
---> Kasutatakse vahemälu
11/12 etapp: COPY nginx/static.conf /etc/nginx/conf.d
---> Kasutatakse vahemälu
12/12 etapp: COPY --from=builder /app/dist/app .
Ehitamine õnnestus a44dd7c217c3
Ehitamine märgitud: app:latest
reaalne 0m46.497s
kasutaja 0m0.000s
süsteem 0m0.000s46 sekundit, mitte 3 minutit — oluliselt parem! Oluline on kihtide õige järjekord: esmalt kopeerime selle, mis ei muutu, seejärel selle, mis harva muutub, ja lõpuks — selle, mis tihti.
Järgmine paar sõna piltide ehitamisest CI/CD süsteemides.
Eelnevate piltide kasutamine vahemäluna
Kui kasutame mõnda SaaS-lahendust kogumise jaoks, võib kohalik Docker vahemälu olla tühi ja värske. Et Dockeril oleks kustki võtta küpsetatud kihte, andke talle eelnevalt koostatud pilt.
Vaadakem näiteks meie rakenduse ehitamist GitHub Actions'is. Kasutame sellist konfi
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.comPildi loomine ja saatmine GitHub Packages'i kestab kaks minutit ja 20 sekundit:

Nüüd muudame ehituse nii, et kasutatakse eelmisel korral loodud piltide põhjal cache'i:
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.comEsiteks tuleb rääkida, miks käivitatakse kaks käsku build. Asi on selles, et mitmestastilise ehituse tulemusena on lõpp-produkt viimasest etapist koosnev kihistikum. Samal ajal ei pääse eelmistest kihtidest loodud kihid pildisse. Seetõttu, kui kasutada viimase ehituse lõpp-pilti, ei suuda Docker leida valmis kihte Node.js pildi (ehituse etapp) jaoks. Selle probleemi lahendamiseks luuakse vahepealne pilt $IMAGE_NAME-builder-stage ja saadetakse GitHub Packages'isse, et saaks seda järgmises ehituses kasutada cache'i allikana.

Kokkuvõttes on ehitamise aeg lühenenud ühele ja poolele minutile. Pool minutit kulub eelnevate piltide allalaadimisele.
Eelneva pildi loomine
Veel, kuidas lahendada puhta docker-kihi probleemi, on osa kihtidest viia teise Dockerfile'i, koguda see eraldi, pushida see Container Registry'sse ja kasutada seda vanemana.
Loome oma nodejs pildi Angular-rakenduse ehitamiseks. Loome projektis Dockerfile.node.
FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual ehitus-sõltuvused lisada
python
make
g++Kogume ja pushime avaliku pildi Docker Hub'i:
docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latestNüüd kasutame meie põhikanalis valmis pilti:
FROM exsmund/node-for-angular:latest as builder
...Meie näites ei vähenenud ehitusaeg, kuid eelnevalt loodud pildid võivad olla kasulikud, kui teil on palju projekte ja igas neist tuleb paigaldada sama sõltuvus.

Oleme vaadanud mitmeid meetodeid docker-piltide ehituse kiirusel. Kui soovite, et juurutamine oleks kiire, proovige oma projektis rakendada:
- konteksti vähendamine;
- väikeste vanemate piltide kasutamine;
- mitmeastmelist ehitamist;
- Dockerfile'i käskude järjekorra muutmine, et tõhusalt kasutada vahemälu;
- vahemälu seadistamine CI/CD süsteemides;
- eelnevalt loodud piltide tegemine.
Loodetavasti selgub näitest, kuidas Docker töötab, ja suudate oma juurutuse optimaalselt seadistada. Artikli näidistega mängimiseks on loodud hoidla. .
Allikas: habr.com
