Kol funkcija pradedama gaminti, šiais sudėtingų orkestrantų ir CI/CD laikais nuo įsipareigojimo iki bandymų ir pristatymo reikia nueiti ilgą kelią. Anksčiau naujus failus galėjai įkelti per FTP (niekas to nebedaro, tiesa?), o „diegimo“ procesas užtruko kelias sekundes. Dabar reikia sukurti sujungimo užklausą ir ilgai laukti, kol funkcija pasieks vartotojus.
Dalis šio kelio yra „Docker“ vaizdo kūrimas. Kartais surinkimas trunka kelias minutes, kartais keliasdešimt minučių, o tai vargu ar galima pavadinti normaliu. Šiame straipsnyje paimsime paprastą programą, kurią supakuosime į vaizdą, pritaikysime kelis metodus, kad paspartintume kūrimą, ir pažvelgsime į šių metodų veikimo niuansus.
Turime gerą patirtį kuriant ir palaikant žiniasklaidos svetaines:
Mes diegiame į GitLab. Mes renkame vaizdus, perkeliame juos į „GitLab“ registrą ir išleidžiame į gamybą. Ilgiausias dalykas šiame sąraše yra vaizdų surinkimas. Pavyzdžiui: be optimizavimo kiekviena užpakalinės sistemos kūrimas užtruko 14 minučių.
Galiausiai tapo aišku, kad taip gyventi nebegalime, ir susėdome aiškintis, kodėl vaizdų rinkimas užtrunka taip ilgai. Dėl to surinkimo laiką pavyko sutrumpinti iki 30 sekundžių!
Šiame straipsnyje, kad nebūtų susietas su priminimo aplinka, pažvelkime į tuščios Angular programos surinkimo pavyzdį. Taigi, sukurkime savo programą:
ng n app
Pridėkite prie jo PWA (mes esame progresyvūs):
ng add @angular/pwa --project app
Kol atsisiunčiama milijonas npm paketų, išsiaiškinkime, kaip veikia „Docker“ vaizdas. „Docker“ suteikia galimybę supakuoti programas ir paleisti jas izoliuotoje aplinkoje, vadinamoje konteineriu. Dėl izoliacijos galite paleisti daug konteinerių vienu metu viename serveryje. Konteineriai yra daug lengvesni nei virtualios mašinos, nes jie veikia tiesiogiai sistemos branduolyje. Norėdami paleisti konteinerį su mūsų programa, pirmiausia turime sukurti vaizdą, kuriame supakuosime viską, ko reikia, kad programa veiktų. Iš esmės vaizdas yra failų sistemos kopija. Pavyzdžiui, paimkite Dockerfile:
FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod
Dockerfile yra instrukcijų rinkinys; Atlikdama kiekvieną iš jų, „Docker“ išsaugos failų sistemos pakeitimus ir perdengs juos ant ankstesnių. Kiekviena komanda susikuria savo sluoksnį. Ir baigtas vaizdas yra sluoksniai, sujungti kartu.
Ką svarbu žinoti: kiekvienas Docker sluoksnis gali išsaugoti talpyklą. Jei niekas nepasikeitė nuo paskutinio kūrimo, tada, užuot vykdydamas komandą, dokeris imsis paruošto sluoksnio. Kadangi pagrindinis kūrimo greičio padidėjimas bus dėl talpyklos naudojimo, matuodami kūrimo greitį atkreipsime dėmesį į vaizdo sukūrimą su paruošta talpykla. Taigi, žingsnis po žingsnio:
- Ištriname vaizdus vietoje, kad ankstesni paleidimai neturėtų įtakos bandymui.
docker rmi $(docker images -q)
- Mes pradedame statyti pirmą kartą.
time docker build -t app .
- Keičiame src/index.html failą – imituojame programuotojo darbą.
- Mes statome antrą kartą.
time docker build -t app .
Jei vaizdų kūrimo aplinka sukonfigūruota teisingai (daugiau apie tai žemiau), tada, kai prasidės kūrimas, „Docker“ jau turės krūvą talpyklų. Mūsų užduotis yra išmokti naudoti talpyklą, kad kūrimas vyktų kuo greičiau. Kadangi darome prielaidą, kad kūrimas be talpyklos paleidžiamas tik vieną kartą – patį pirmą kartą, todėl galime nepaisyti, koks lėtas buvo pirmasis kartas. Atliekant testus, mums svarbus antrasis statymo paleidimas, kai talpyklos jau įkaista ir esame pasiruošę kepti tortą. Tačiau kai kurie patarimai taip pat turės įtakos pirmajai konstrukcijai.
Įdėkite aukščiau aprašytą Dockerfile į projekto aplanką ir pradėkite kurti. Visi sąrašai buvo sutrumpinti, kad būtų lengviau skaityti.
$ 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
Pakeičiame src/index.html turinį ir paleidžiame jį antrą kartą.
$ 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
Norėdami pamatyti, ar turime vaizdą, paleiskite komandą docker images
:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 79f335df92d3 About a minute ago 1.74GB
Prieš kurdama, docker paima visus failus dabartiniame kontekste ir siunčia juos į savo demoną Sending build context to Docker daemon 409MB
. Sukūrimo kontekstas nurodomas kaip paskutinis kūrimo komandos argumentas. Mūsų atveju tai yra dabartinis katalogas - „.“, - ir „Docker“ nuvelka viską, ką turime šiame aplanke. 409 MB yra daug: pagalvokime, kaip tai pataisyti.
Sumažinti kontekstą
Norint sumažinti kontekstą, yra dvi galimybės. Arba įdėkite visus surinkimui reikalingus failus į atskirą aplanką ir nukreipkite docker kontekstą į šį aplanką. Tai ne visada gali būti patogu, todėl galima nurodyti išimtis: ko nereikėtų tempti į kontekstą. Norėdami tai padaryti, įdėkite .dockerignore failą į projektą ir nurodykite, ko nereikia kurti:
.git
/node_modules
ir paleiskite kūrimą dar kartą:
$ 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 yra daug geriau nei 409 MB. Taip pat sumažinome vaizdo dydį nuo 1.74 iki 1.38 GB:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 4942f010792a 3 minutes ago 1.38GB
Pabandykime dar labiau sumažinti vaizdo dydį.
Naudojame Alpine
Kitas būdas sutaupyti vaizdo dydžio yra naudoti mažą pirminį vaizdą. Tėvų įvaizdis yra įvaizdis, kurio pagrindu rengiamas mūsų įvaizdis. Apatinis sluoksnis nurodomas komanda FROM
„Dockerfile“. Mūsų atveju mes naudojame Ubuntu pagrįstą vaizdą, kuriame jau yra įdiegti nodejs. Ir sveria...
$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB
... beveik gigabaitas. Galite žymiai sumažinti garsumą naudodami vaizdą, pagrįstą Alpine Linux. Alpine yra labai mažas Linux. „Docker“ vaizdas, skirtas mazgams, pagrįstas Alpine, sveria tik 88.5 MB. Taigi pakeiskime savo gyvą įvaizdį namuose:
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
Turėjome įdiegti kai kuriuos dalykus, kurių reikia norint sukurti programą. Taip, Angular nekuriamas be Python ¯(°_o)/¯
Tačiau vaizdo dydis sumažėjo iki 150 MB:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest aa031edc315a 22 minutes ago 761MB
Eikime dar toliau.
Daugiapakopis surinkimas
Ne viskas, kas yra paveikslėlyje, yra tai, ko mums reikia gamyboje.
$ 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
naudojant docker run app ls -lah
paleidome konteinerį pagal savo įvaizdį app
ir įvykdė jame esančią komandą ls -lah
, po kurio konteineris baigė savo darbą.
Gamyboje mums reikia tik aplanko dist
. Tokiu atveju failus kažkaip reikia atiduoti į lauką. Galite paleisti tam tikrą HTTP serverį nodejs. Bet mes palengvinsime. Atspėk rusišką žodį, kurį sudaro keturios raidės „y“. Teisingai! Ynzhynyksy. Nufotografuokime su nginx, įdėkime į jį aplanką dist
ir maža konfigūracija:
server {
listen 80 default_server;
server_name localhost;
charset utf-8;
root /app/dist;
location / {
try_files $uri $uri/ /index.html;
}
}
Visa tai mums padės kelių etapų kūrimas. Pakeiskime Docker failą:
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 .
Dabar turime dvi instrukcijas FROM
„Dockerfile“ kiekvienas iš jų vykdo skirtingą kūrimo veiksmą. Paskambinome pirmajam builder
, bet pradedant nuo paskutinio FROM, bus paruoštas mūsų galutinis vaizdas. Paskutinis veiksmas yra nukopijuoti ankstesniame žingsnyje atliktą mūsų surinkimo artefaktą į galutinį vaizdą su nginx. Vaizdo dydis žymiai sumažėjo:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 2c6c5da07802 29 minutes ago 36MB
Paleiskite konteinerį su savo atvaizdu ir įsitikinkime, kad viskas veikia:
docker run -p8080:80 app
Naudodami parinktį -p8080:80, savo pagrindinio kompiuterio 8080 prievadą persiuntėme į 80 prievadą konteineryje, kuriame veikia nginx. Atidaryti naršyklėje
Sumažinus vaizdo dydį nuo 1.74 GB iki 36 MB, žymiai sutrumpėja laikas, per kurį programa pristatoma į gamybą. Bet grįžkime prie surinkimo laiko.
$ 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
Sluoksnių tvarkos keitimas
Pirmieji trys žingsniai buvo išsaugoti talpykloje (užuomina Using cache
). Ketvirtajame žingsnyje visi projekto failai nukopijuojami, o penktame žingsnyje įdiegiamos priklausomybės RUN npm ci
- net 47.338 sek. Kam kaskart iš naujo įdiegti priklausomybes, jei jos keičiasi labai retai? Išsiaiškinkime, kodėl jie nebuvo išsaugoti talpykloje. Esmė ta, kad „Docker“ tikrins sluoksnis po sluoksnio, kad pamatytų, ar komanda ir su ja susiję failai pasikeitė. Ketvirtajame žingsnyje nukopijuojame visus savo projekto failus, o tarp jų, žinoma, yra pakeitimų, todėl Docker ne tik nepaima šio sluoksnio iš talpyklos, bet ir visų vėlesnių! Atlikime keletą nedidelių „Dockerfile“ pakeitimų.
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 .
Pirmiausia nukopijuojami package.json ir package-lock.json, tada įdiegiamos priklausomybės ir tik po to nukopijuojamas visas projektas. Kaip rezultatas:
$ 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 sekundės vietoj 3 minučių – daug geriau! Svarbu teisinga sluoksnių tvarka: pirmiausia nukopijuojame tai, kas nesikeičia, tada tai, kas keičiasi retai, ir galiausiai tai, kas keičiasi dažnai.
Toliau keli žodžiai apie vaizdų surinkimą CI/CD sistemose.
Ankstesnių vaizdų naudojimas talpyklai
Jei kūrimui naudosime kokį nors SaaS sprendimą, vietinė „Docker“ talpykla gali būti švari ir nauja. Kad dokeriui būtų suteikta vieta, kur gauti iškeptus sluoksnius, suteikite jam ankstesnį pastatytą vaizdą.
Paimkime pavyzdį, kaip sukurti programą „GitHub Actions“. Mes naudojame šią konfigūraciją
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
Vaizdas surenkamas ir perkeliamas į „GitHub“ paketus per dvi minutes ir 20 sekundžių:
Dabar pakeiskime kūrimą, kad talpykla būtų naudojama pagal ankstesnius sukurtus vaizdus:
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
Pirmiausia turime pasakyti, kodėl paleidžiamos dvi komandos build
. Faktas yra tas, kad daugiapakopiame surinkime gautas vaizdas bus paskutinio etapo sluoksnių rinkinys. Tokiu atveju ankstesnių sluoksnių sluoksniai nebus įtraukti į vaizdą. Todėl naudojant galutinį vaizdą iš ankstesnio kūrimo, „Docker“ negalės rasti paruoštų sluoksnių, kad būtų galima sukurti vaizdą su mazgais (kūrimo stadija). Siekiant išspręsti šią problemą, sukuriamas tarpinis vaizdas $IMAGE_NAME-builder-stage
ir perkeliamas į „GitHub“ paketus, kad jį būtų galima naudoti vėlesnėje versijoje kaip talpyklos šaltinis.
Bendras surinkimo laikas sutrumpėjo iki pusantros minutės. Pusė minutės praleidžiama traukiant ankstesnius vaizdus.
Išankstinis vaizdas
Kitas būdas išspręsti švarios „Docker“ talpyklos problemą yra perkelti kai kuriuos sluoksnius į kitą „Dockerfile“, sukurti jį atskirai, perkelti į konteinerių registrą ir naudoti kaip pagrindinį.
Mes kuriame savo nodejs vaizdą, kad sukurtume Angular programą. Projekte sukurkite Dockerfile.node
FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++
Renkame ir pateikiame viešą vaizdą „Docker Hub“:
docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest
Dabar pagrindiniame Docker faile naudojame gatavą vaizdą:
FROM exsmund/node-for-angular:latest as builder
...
Mūsų pavyzdyje kūrimo laikas nesumažėjo, tačiau iš anksto sukurti vaizdai gali būti naudingi, jei turite daug projektų ir kiekviename iš jų turite įdiegti tas pačias priklausomybes.
Išnagrinėjome kelis metodus, kaip pagreitinti „Docker“ vaizdų kūrimą. Jei norite, kad diegimas vyktų greitai, pabandykite naudoti tai savo projekte:
- konteksto mažinimas;
- mažų tėvų vaizdų naudojimas;
- daugiapakopis surinkimas;
- pakeisti instrukcijų tvarką Dockerfile, kad būtų galima efektyviai naudoti talpyklą;
- talpyklos nustatymas CI/CD sistemose;
- preliminarus vaizdų kūrimas.
Tikiuosi, kad pavyzdyje bus aiškiau, kaip veikia Docker, ir galėsite optimaliai sukonfigūruoti savo diegimą. Norint žaisti su pavyzdžiais iš straipsnio, buvo sukurta saugykla
Šaltinis: www.habr.com