Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Prije nego značajka uđe u proizvodnju, u današnje vrijeme složenih orkestratora i CI/CD-a, dug je put od predaje do testiranja i isporuke. Prije ste mogli uploadati nove datoteke putem FTP-a (to više nitko ne radi, zar ne?), a proces "deploymenta" trajao je nekoliko sekundi. Sada trebate izraditi zahtjev za spajanje i dugo čekati da značajka dođe do korisnika.

Dio ovog puta je izgradnja Docker slike. Ponekad montaža traje minutama, ponekad desecima minuta, što se teško može nazvati normalnim. U ovom ćemo članku uzeti jednostavnu aplikaciju koju ćemo zapakirati u sliku, primijeniti nekoliko metoda za ubrzavanje izgradnje i pogledati nijanse kako te metode funkcioniraju.

Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Imamo dobro iskustvo u izradi i podršci medijskih web stranica: TASS, Bell, "Nove novine", Republika… Nedavno smo proširili naš portfelj izdavanjem web stranice proizvoda Podsjetnik. I dok su nove značajke brzo dodane, a stare greške popravljene, spora implementacija postala je veliki problem.

Implementiramo u GitLab. Prikupljamo slike, guramo ih u GitLab registar i stavljamo ih u proizvodnju. Najduža stvar na ovom popisu je sastavljanje slika. Na primjer: bez optimizacije, svaka pozadinska izgradnja trajala je 14 minuta.

Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Na kraju je postalo jasno da više ne možemo ovako živjeti i sjeli smo da shvatimo zašto se slike tako dugo prikupljaju. Kao rezultat, uspjeli smo smanjiti vrijeme sastavljanja na 30 sekundi!

Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Za ovaj članak, kako ne bismo bili vezani uz Reminderovo okruženje, pogledajmo primjer sastavljanja prazne Angular aplikacije. Dakle, kreirajmo našu aplikaciju:

ng n app

Dodajte mu PWA (mi smo progresivni):

ng add @angular/pwa --project app

Dok se milijun npm paketa preuzima, shvatimo kako radi docker slika. Docker pruža mogućnost pakiranja aplikacija i njihovog pokretanja u izoliranom okruženju koje se zove spremnik. Zahvaljujući izolaciji, možete pokrenuti više spremnika istovremeno na jednom poslužitelju. Kontejneri su puno lakši od virtualnih strojeva jer se pokreću izravno na jezgri sustava. Da bismo pokrenuli spremnik s našom aplikacijom, prvo moramo napraviti image u koji ćemo upakirati sve što je potrebno za rad naše aplikacije. U biti, slika je kopija datotečnog sustava. Na primjer, uzmite Dockerfile:

FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

Dockerfile je skup uputa; Radeći svaku od njih, Docker će spremiti promjene u datotečni sustav i preklopiti ih na prethodne. Svaki tim stvara svoj sloj. A gotova slika sastoji se od slojeva kombiniranih zajedno.

Ono što je važno znati: svaki Docker sloj može predmemorirati. Ako se ništa nije promijenilo od posljednjeg builda, umjesto izvršenja naredbe, docker će uzeti gotov sloj. Budući da će glavno povećanje brzine izrade biti posljedica upotrebe predmemorije, pri mjerenju brzine izrade posebno ćemo obratiti pozornost na izgradnju slike s već pripremljenom predmemorijom. Dakle, korak po korak:

  1. Slike brišemo lokalno tako da prethodna izvođenja ne utječu na test.
    docker rmi $(docker images -q)
  2. Po prvi put pokrećemo izgradnju.
    time docker build -t app .
  3. Mijenjamo datoteku src/index.html - oponašamo rad programera.
  4. Pokrećemo izgradnju drugi put.
    time docker build -t app .

Ako je okruženje za izradu slika ispravno konfigurirano (više o tome u nastavku), tada će, kada izgradnja počne, Docker već imati hrpu predmemorija na brodu. Naš je zadatak naučiti koristiti predmemoriju kako bi izgradnja išla što je brže moguće. Budući da pretpostavljamo da se izvođenje izgradnje bez predmemorije događa samo jednom - prvi put - stoga možemo zanemariti koliko je taj prvi put bio spor. U testovima nam je važno drugo pokretanje izgradnje, kada su predmemorije već zagrijane i spremni smo ispeći kolač. Međutim, neki savjeti također će utjecati na prvu izgradnju.

Stavimo gore opisani Dockerfile u mapu projekta i započnimo izgradnju. Svi popisi su sažeti radi lakšeg čitanja.

$ 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

Mijenjamo sadržaj src/index.html i pokrećemo ga drugi put.

$ 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

Da biste vidjeli imamo li sliku, pokrenite naredbu docker images:

REPOSITORY   TAG      IMAGE ID       CREATED              SIZE
app          latest   79f335df92d3   About a minute ago   1.74GB

Prije izgradnje, docker uzima sve datoteke u trenutnom kontekstu i šalje ih svom demonu Sending build context to Docker daemon 409MB. Kontekst izgradnje naveden je kao zadnji argument naredbe izgradnje. U našem slučaju, ovo je trenutni direktorij - “.”, - i Docker povlači sve što imamo u ovoj mapi. 409 MB je puno: razmislimo kako to popraviti.

Smanjenje konteksta

Za smanjenje konteksta postoje dvije mogućnosti. Ili stavite sve datoteke potrebne za sastavljanje u zasebnu mapu i usmjerite docker kontekst na ovu mapu. Ovo možda nije uvijek zgodno, pa je moguće odrediti iznimke: što se ne smije povlačiti u kontekst. Da biste to učinili, stavite .dockerignore datoteku u projekt i označite što nije potrebno za izgradnju:

.git
/node_modules

i ponovno pokrenite izgradnju:

$ 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 je puno bolje od 409 MB. Također smo smanjili veličinu slike s 1.74 na 1.38 GB:

REPOSITORY   TAG      IMAGE ID       CREATED         SIZE
app          latest   4942f010792a   3 minutes ago   1.38GB

Pokušajmo dodatno smanjiti veličinu slike.

Koristimo Alpine

Drugi način uštede na veličini slike je korištenje male nadređene slike. Roditeljska slika je slika na temelju koje se priprema naša slika. Donji sloj je određen naredbom FROM u Dockerfileu. U našem slučaju koristimo sliku temeljenu na Ubuntuu koja već ima instaliran nodejs. I teži...

$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB

... skoro gigabajt. Možete značajno smanjiti glasnoću pomoću slike temeljene na Alpine Linuxu. Alpine je vrlo mali Linux. Docker slika za nodejs temeljena na alpineu teži samo 88.5 MB. Pa zamijenimo našu živu sliku u kućama:

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

Morali smo instalirati neke stvari koje su potrebne za izgradnju aplikacije. Da, Angular se ne gradi bez Pythona ¯(°_o)/¯

Ali veličina slike pala je na 150 MB:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   aa031edc315a   22 minutes ago   761MB

Idemo još dalje.

Višestupanjska montaža

Nije sve što je na slici ono što nam treba u proizvodnji.

$ 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

S docker run app ls -lah pokrenuli smo spremnik na temelju naše slike app i izvršio naredbu u njemu ls -lah, nakon čega je kontejner završio s radom.

U proizvodnji nam je potrebna samo mapa dist. U ovom slučaju, datoteke se nekako moraju dati van. Možete pokrenuti neki HTTP poslužitelj na nodejs. Ali mi ćemo to olakšati. Pogodite rusku riječ koja ima četiri slova "y". Pravo! Ynzhynyksy. Uzmimo sliku s nginxom, stavimo mapu u nju dist i mala konfiguracija:

server {
    listen 80 default_server;
    server_name localhost;
    charset utf-8;
    root /app/dist;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Višestupanjska izgradnja pomoći će nam u svemu tome. Promijenimo naš 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 .

Sada imamo dvije upute FROM u Dockerfileu, svaki od njih pokreće drugačiji korak izgradnje. Pozvali smo prvog builder, ali počevši od posljednjeg FROM, bit će pripremljena naša konačna slika. Posljednji korak je kopiranje artefakta našeg sklopa iz prethodnog koraka u konačnu sliku pomoću nginxa. Veličina slike značajno se smanjila:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   2c6c5da07802   29 minutes ago   36MB

Pokrenimo spremnik s našom slikom i uvjerimo se da sve radi:

docker run -p8080:80 app

Koristeći opciju -p8080:80, proslijedili smo port 8080 na našem glavnom računalu na port 80 unutar spremnika gdje se pokreće nginx. Otvori u pretraživaču http://localhost:8080/ i vidimo svoju aplikaciju. Sve radi!

Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Smanjenje veličine slike s 1.74 GB na 36 MB značajno smanjuje vrijeme potrebno za isporuku vaše aplikacije u proizvodnju. No, vratimo se na vrijeme montaže.

$ 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

Promjena redoslijeda slojeva

Naša prva tri koraka bila su predmemorirana (savjet Using cache). U četvrtom koraku kopiraju se sve projektne datoteke, a u petom koraku instaliraju se ovisnosti RUN npm ci - čak 47.338s. Zašto svaki put ponovno instalirati ovisnosti ako se vrlo rijetko mijenjaju? Hajdemo otkriti zašto nisu spremljeni u predmemoriju. Poanta je da će Docker provjeriti sloj po sloj da vidi jesu li se naredba i datoteke povezane s njom promijenile. U četvrtom koraku kopiramo sve datoteke našeg projekta, a među njima, naravno, postoje promjene, tako da Docker ne samo da ne uzima ovaj sloj iz predmemorije, već i sve sljedeće! Napravimo male promjene u Dockerfileu.

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 .

Prvo se kopiraju package.json i package-lock.json, zatim se instaliraju zavisnosti, a tek nakon toga se kopira cijeli projekt. Kao rezultat:

$ 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 sekundi umjesto 3 minute - puno bolje! Važan je pravilan redoslijed slojeva: prvo kopiramo ono što se ne mijenja, zatim ono što se rijetko mijenja i na kraju ono što se često mijenja.

Zatim nekoliko riječi o sastavljanju slika u CI/CD sustavima.

Korištenje prethodnih slika za predmemoriju

Ako koristimo neku vrstu SaaS rješenja za izgradnju, onda lokalna Docker predmemorija može biti čista i svježa. Da biste dockeru dali mjesto za dobivanje pečenih slojeva, dajte mu prethodnu izgrađenu sliku.

Uzmimo primjer izrade naše aplikacije u GitHub Actions. Koristimo ovu konfiguraciju

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

Slika se sastavlja i šalje u GitHub pakete za dvije minute i 20 sekundi:

Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Sada promijenimo izgradnju tako da se koristi predmemorija na temelju prethodnih izgrađenih slika:

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

Prvo vam moramo reći zašto se pokreću dvije naredbe build. Činjenica je da će u višestupanjskom sklopu rezultirajuća slika biti skup slojeva iz posljednje faze. U tom slučaju slojevi iz prethodnih slojeva neće biti uključeni u sliku. Stoga, kada koristite konačnu sliku iz prethodne izgradnje, Docker neće moći pronaći spremne slojeve za izgradnju slike pomoću nodejs (faza izgradnje). Kako bi se riješio ovaj problem, stvara se međuslika $IMAGE_NAME-builder-stage i gura se u GitHub pakete tako da se može koristiti u sljedećoj izgradnji kao izvor predmemorije.

Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Ukupno vrijeme sastavljanja smanjeno je na jednu i pol minutu. Pola minute potrošeno je na izvlačenje prethodnih slika.

Predslikavanje

Još jedan način da se riješi problem čiste predmemorije Dockera je premjestiti neke od slojeva u drugu Dockerfile, izgraditi je zasebno, gurnuti je u Registar spremnika i koristiti je kao nadređenu.

Stvaramo vlastitu nodejs sliku za izradu Angular aplikacije. Stvorite Dockerfile.node u projektu

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add 
    python 
    make 
    g++

Prikupljamo i šaljemo javnu sliku u Docker Hub:

docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest

Sada u našoj glavnoj Docker datoteci koristimo gotovu sliku:

FROM exsmund/node-for-angular:latest as builder
...

U našem primjeru, vrijeme izgradnje nije se smanjilo, ali unaprijed izgrađene slike mogu biti korisne ako imate mnogo projekata i morate instalirati iste ovisnosti u svakom od njih.

Nekoliko savjeta o tome kako ubrzati izgradnju Docker slika. Na primjer, do 30 sekundi

Pregledali smo nekoliko metoda za ubrzavanje izgradnje docker slika. Ako želite da implementacija ide brzo, pokušajte upotrijebiti ovo u svom projektu:

  • smanjenje konteksta;
  • korištenje malih roditeljskih slika;
  • višestupanjska montaža;
  • mijenjanje redoslijeda uputa u Dockerfileu radi učinkovite upotrebe predmemorije;
  • postavljanje predmemorije u CI/CD sustavima;
  • preliminarno stvaranje slika.

Nadam se da će vam primjer pojasniti kako Docker radi i da ćete moći optimalno konfigurirati svoju implementaciju. Kako bismo se poigrali s primjerima iz članka, stvoreno je spremište https://github.com/devopsprodigy/test-docker-build.

Izvor: www.habr.com

Dodajte komentar