Prima chì una funzione entra in produzzione, in questi ghjorni di orchestratori cumplessi è CI / CD, ci hè una longa strada per andà da l'impegnu à e teste è a consegna. Nanzu, pudete caricate novi fugliali via FTP (nimu ùn face più, nò?), è u prucessu di "spiegamentu" hà pigliatu seconde. Avà avete bisognu di creà una dumanda di fusione è aspittà assai tempu per a funzione per ghjunghje à l'utilizatori.
Una parte di sta strada hè di custruisce una maghjina Docker. Calchì volta l'assemblea dura minuti, qualchì volta decine di minuti, chì ùn pò micca esse chjamatu normale. In questu articulu, avemu da piglià una applicazione simplice chì avemu da imballà in una maghjina, applicà parechji metudi per accelerà a custruzzione, è fighjemu i sfumaturi di cumu si travaglianu sti metudi.
Avemu una bona sperienza in a creazione è supportu di siti web media:
Implementemu in GitLab. Raccogliamu l'imaghjini, li spingemu à u Registru di GitLab è li stendemu à a produzzione. A cosa più longa in questa lista hè l'assemblea di l'imaghjini. Per esempiu: senza ottimisazione, ogni custruzzione di backend hà pigliatu 14 minuti.
À a fine, hè diventatu chjaru chì ùn pudemu micca più campà cusì, è avemu pusatu per capisce perchè l'imaghjini piglianu tantu tempu per cullà. In u risultatu, avemu riisciutu à riduce u tempu di assemblea à 30 seconde!
Per questu articulu, per ùn esse ligatu à l'ambiente di Reminder, fighjemu un esempiu di assemblea una applicazione Angular viota. Allora, creemu a nostra applicazione:
ng n app
Aghjunghjite PWA (semu prugressivi):
ng add @angular/pwa --project app
Mentre chì un milione di pacchetti npm sò scaricati, scopremu cumu funziona l'imaghjini docker. Docker furnisce l'abilità di imballà applicazioni è eseguisce in un ambiente isolatu chjamatu cuntainer. Grazie à l'isolamentu, pudete eseguisce parechji cuntenituri simultaneamente in un servitore. I cuntenituri sò assai più ligeri cà i machini virtuali perchè correnu direttamente nantu à u kernel di u sistema. Per eseguisce un cuntinuu cù a nostra applicazione, avemu prima bisognu di creà una maghjina in quale impaccheremu tuttu ciò chì hè necessariu per a nostra applicazione per eseguisce. Essenzialmente, una maghjina hè una copia di u sistema di fugliale. Per esempiu, pigliate u Dockerfile:
FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod
Un Dockerfile hè un inseme di struzzioni; Facendu ognuna di elli, Docker salvarà i cambiamenti in u sistema di schedari è li superpone à i precedenti. Ogni squadra crea a so propria capa. È l'imaghjini finiti sò strati cumminati inseme.
Ciò chì hè impurtante di sapè: ogni capa Docker pò cache. Se nunda hà cambiatu da l'ultima custruzzione, invece di eseguisce u cumandamentu, u docker hà da piglià una capa pronta. Siccomu l'aumentu principale di a velocità di custruzzione serà dovutu à l'usu di a cache, quandu si misurà a velocità di custruzzione, prestaremu attente specificamente à custruisce una maghjina cù una cache pronta. Allora, passu à passu:
- Sguassemu l'imaghjini in u locu in modu chì e corse precedenti ùn affettanu micca a prova.
docker rmi $(docker images -q)
- Lanciamu a custruzione per a prima volta.
time docker build -t app .
- Cambiamu u schedariu src/index.html - imitemu u travagliu di un programatore.
- Eseguimu a custruzione una seconda volta.
time docker build -t app .
Se l'ambiente per a custruzzione di l'imaghjini hè cunfiguratu currettamente (più nantu à quì sottu), allora quandu a custruzione principia, Docker hà digià un munzeddu di cache à bordu. U nostru compitu hè d'amparà cumu utilizà a cache in modu chì a custruzione và u più prestu pussibule. Siccomu assumemu chì l'esecuzione di una custruzzione senza cache succede solu una volta - a prima volta - pudemu dunque ignurà quantu lento era quella prima volta. In i testi, a seconda corsa di a custruzzione hè impurtante per noi, quandu i cache sò digià riscaldati è simu pronti per coce a nostra torta. Tuttavia, certi cunsiglii affettanu ancu a prima custruzione.
Pudemu u Dockerfile descrittu sopra in u cartulare di u prughjettu è cuminciamu a custruisce. Tutti i listi sò stati condensati per facilità di lettura.
$ 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
Cambiamu u cuntenutu di src/index.html è eseguite una seconda volta.
$ 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
Per vede s'ellu avemu l'imaghjini, eseguite u cumandamentu docker images
:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 79f335df92d3 About a minute ago 1.74GB
Prima di custruisce, docker piglia tutti i schedari in u cuntestu attuale è li manda à u so daemon Sending build context to Docker daemon 409MB
. U cuntestu di custruzzione hè specificatu cum'è l'ultimu argumentu à u cumandamentu di custruzzione. In u nostru casu, questu hè u cartulare attuale - ".", - è Docker trascina tuttu ciò chì avemu in questu cartulare. 409 MB hè assai: pensemu à cumu risolve.
Reduce u cuntestu
Per riduce u cuntestu, ci sò duie opzioni. O mette tutti i fugliali necessarii per l'assemblea in un cartulare separatu è puntate u cuntestu docker à questu cartulare. Questu ùn hè micca sempre cunvene, cusì hè pussibule di specificà eccezzioni: ciò chì ùn deve esse trascinatu in u cuntestu. Per fà questu, mette u schedariu .dockerignore in u prugettu è indica ciò chì ùn hè micca necessariu per a custruzzione:
.git
/node_modules
è eseguite a custruzione di novu:
$ 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 hè assai megliu cà 409 MB. Avemu ancu riduciutu a dimensione di l'imagine da 1.74 à 1.38 GB:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 4942f010792a 3 minutes ago 1.38GB
Pruvemu di riduce a dimensione di l'imaghjini più.
Avemu aduprà Alpine
Un altru modu per salvà a dimensione di l'imaghjini hè di utilizà una piccula maghjina parent. L'imaghjini parentali hè l'imaghjini nantu à a basa di quale a nostra imagina hè preparata. A capa di fondu hè specificatu da u cumandimu FROM
in Dockerfile. In u nostru casu, usemu una maghjina basata in Ubuntu chì hà digià installatu nodejs. È pesa...
$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB
... quasi un gigabyte. Pudete riduce significativamente u voluminu usendu una maghjina basata in Alpine Linux. Alpine hè un Linux assai chjucu. L'imaghjini docker per nodejs basati in alpine pesa solu 88.5 MB. Allora rimpiazzemu a nostra viva maghjina in e case:
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
Avemu avutu à stallà alcune cose chì sò necessarii per custruisce l'applicazione. Iè, Angular ùn hè micca custruitu senza Python ¯(°_o)/¯
Ma a dimensione di l'imagine hè cascata à 150 MB:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest aa031edc315a 22 minutes ago 761MB
Andemu ancu più luntanu.
Assemblage multistage
Micca tuttu ciò chì hè in l'imaghjini hè ciò chì avemu bisognu in a produzzione.
$ 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
Cù l'aiutu di docker run app ls -lah
avemu lanciatu un containeru basatu annantu à a nostra maghjina app
è eseguitu u cumandamentu in questu ls -lah
, dopu chì u cuntinuu hà finitu u so travagliu.
In a pruduzzione avemu solu bisognu di un cartulare dist
. In questu casu, i fugliali anu da esse datu fora. Pudete eseguisce qualchì servitore HTTP in nodejs. Ma l'averemu più faciule. Indovinà una parolla russa chì hà quattru lettere "y". Diritta! Ynzhynyksy. Pigliemu una maghjina cù nginx, mette un cartulare in questu dist
è una piccula cunfigurazione:
server {
listen 80 default_server;
server_name localhost;
charset utf-8;
root /app/dist;
location / {
try_files $uri $uri/ /index.html;
}
}
A custruzzione in più tappe ci aiuterà à fà tuttu questu. Cambiamu u nostru 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 .
Avà avemu dui struzzioni FROM
in u Dockerfile, ognuna di elli corre un passu di custruzzione diversu. Avemu chjamatu u primu builder
, ma partendu da l'ultimu FROM, a nostra maghjina finali serà preparata. L'ultimu passu hè di copià l'artefattu di a nostra assemblea in u passu precedente in l'imaghjini finali cù nginx. A dimensione di l'imaghjini hè diminuitu significativamente:
REPOSITORY TAG IMAGE ID CREATED SIZE
app latest 2c6c5da07802 29 minutes ago 36MB
Eseguimu u cuntinuu cù a nostra maghjina è assicuratevi chì tuttu funziona:
docker run -p8080:80 app
Utilizendu l'opzione -p8080: 80, avemu trasmessu u portu 8080 nantu à a nostra macchina host à u portu 80 in u cuntainer induve nginx corre. Apertura in u navigatore
A riduzione di a dimensione di l'imaghjini da 1.74 GB à 36 MB riduce significativamente u tempu chì ci vole à furnisce a vostra applicazione à a produzzione. Ma tornemu à u tempu di assemblea.
$ 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
Cambia l'ordine di i strati
I nostri primi trè passi sò stati salvati in cache (suggerimentu Using cache
). À u quartu passu, tutti i schedarii di u prughjettu sò copiati è à u quintu passu dependenzii sò stallati RUN npm ci
- quant'è 47.338s. Perchè reinstallà dipendenze ogni volta si cambianu assai raramente? Scupritemu perchè ùn sò micca stati in cache. U puntu hè chì Docker verificarà strata per capa per vede s'ellu u cumandamentu è i schedarii assuciati sò cambiati. À u quartu passu, copiemu tutti i schedarii di u nostru prughjettu, è trà elli, sicuru, ci sò cambiamenti, cusì Docker ùn solu ùn piglia micca sta capa da a cache, ma ancu tutti i successivi! Facemu alcuni picculi cambiamenti à u 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 .
Prima, package.json è package-lock.json sò copiati, dopu i dependenzii sò stallati, è solu dopu chì tuttu u prughjettu hè copiatu. Di cunsiguenza:
$ 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 seconde invece di 3 minuti - assai megliu! L'ordine currettu di strati hè impurtante: prima copiamu ciò chì ùn cambia micca, dopu ciò chì cambia raramente, è infine ciò chì cambia spessu.
In seguitu, uni pochi di parolle nantu à l'assemblea di l'imaghjini in sistemi CI / CD.
Utilizà l'imaghjini precedenti per a cache
Se usemu un tipu di suluzione SaaS per a custruzione, allora a cache Docker locale pò esse pulita è fresca. Per dà à u docker un locu per uttene i strati cotti, dà l'imaghjini custruiti precedente.
Pigliemu un esempiu di custruisce a nostra applicazione in GitHub Actions. Avemu aduprà sta cunfigurazione
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
L'imaghjina hè assemblata è imbuttata à i Pacchetti GitHub in dui minuti è 20 seconde:
Avà cambiate a custruzzione in modu chì una cache hè aduprata basatu annantu à l'imaghjini custruiti precedenti:
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
Prima ci vole à dì perchè dui cumandamenti sò lanciati build
. U fattu hè chì in una assemblea multistage l'imaghjini resultanti serà un inseme di strati da l'ultima tappa. In questu casu, i strati di strati precedenti ùn saranu micca inclusi in l'imaghjini. Dunque, quandu si usa l'imaghjini finali da una custruzzione precedente, Docker ùn serà micca capaci di truvà strati pronti per custruisce l'imaghjini cù nodejs (stadiu di custruttore). Per risolve stu prublema, hè creatu una maghjina intermedia $IMAGE_NAME-builder-stage
è hè imbuttatu à i Pacchetti GitHub in modu chì pò esse usatu in una custruzzione successiva cum'è fonte di cache.
U tempu tutale di l'assemblea hè stata ridutta à un minutu è mezu. A mità di minutu si spende per tirare l'imaghjini precedenti.
Preimaging
Un'altra manera di risolve u prublema di una cache Docker pulita hè di trasfurmà alcune di e strati in un altru Dockerfile, custruite separatamente, spinghjelu in u Registru di Container è l'utilizanu cum'è parent.
Creemu a nostra propria imagina nodejs per custruisce una applicazione Angular. Crea Dockerfile.node in u prugettu
FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add
python
make
g++
Cullemu è spingemu una maghjina publica in Docker Hub:
docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest
Avà in u nostru Dockerfile principale usemu l'imagine finita:
FROM exsmund/node-for-angular:latest as builder
...
In u nostru esempiu, u tempu di custruzzione ùn hè micca diminuitu, ma l'imaghjini pre-custruiti ponu esse utili si avete assai prughjetti è avete da installà e stesse dependenze in ognuna di elli.
Avemu guardatu parechji metudi per accelerà a custruzione di l'imaghjini docker. Se vulete chì l'implementazione vada rapidamente, pruvate aduprà questu in u vostru prughjettu:
- riducendu u cuntestu;
- usu di picculi imagine parenti;
- assemblea multistage;
- cambià l'ordine di l'urdinamentu in u Dockerfile per fà un usu efficace di a cache;
- crià una cache in sistemi CI/CD;
- creazione preliminare di imagine.
Speru chì l'esempiu farà più chjaru cumu funziona Docker, è puderete cunfigurà in modu ottimale a vostra implementazione. Per ghjucà cù l'esempii di l'articulu, hè statu creatu un repository
Source: www.habr.com