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

Prije nego što funkcija uđe u proizvodnju, u ovim danima složenih orkestratora i CI/CD-a, dug je put od urezivanja do testiranja i isporuke. Ranije ste mogli da otpremate nove fajlove preko FTP-a (to više niko ne radi, zar ne?), a proces „raspoređivanja“ je trajao nekoliko sekundi. Sada morate kreirati zahtjev za spajanje i čekati dugo da funkcija dođe do korisnika.

Dio ovog puta je izgradnja Docker slike. Nekada montaža traje nekoliko minuta, nekad desetina minuta, što se teško može nazvati normalnim. U ovom članku ćemo uzeti jednostavnu aplikaciju koju ćemo upakirati u sliku, primijeniti nekoliko metoda za ubrzavanje izrade 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 kreiranju i podršci medijskih web stranica: TASS, The Bell, "Nove novine", republika… Nedavno smo proširili naš portfolio izdavanjem web stranice proizvoda podsjetnik. I dok su nove funkcije brzo dodavane i stare greške popravljene, spora implementacija je postala veliki problem.

Mi postavljamo na GitLab. Prikupljamo slike, guramo ih u GitLab registar i stavljamo ih u proizvodnju. Najduža stvar na ovoj listi je sastavljanje slika. Na primjer: bez optimizacije, svaka pozadinska izrada 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 da živimo, i sjeli smo da shvatimo zašto je trebalo toliko dugo da se skupljaju slike. Kao rezultat toga, uspjeli smo smanjiti vrijeme montaže na 30 sekundi!

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

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

ng n app

Dodajte PWA (mi smo progresivni):

ng add @angular/pwa --project app

Dok se preuzima milion npm paketa, hajde da shvatimo kako funkcioniše docker slika. Docker pruža mogućnost pakovanja aplikacija i njihovog pokretanja u izolovanom okruženju koje se zove kontejner. Zahvaljujući izolaciji, možete pokrenuti više kontejnera istovremeno na jednom serveru. Kontejneri su mnogo lakši od virtuelnih mašina jer rade direktno na kernelu sistema. Da bismo pokrenuli kontejner sa našom aplikacijom, prvo trebamo kreirati sliku u koju ćemo upakovati sve što je potrebno za pokretanje naše aplikacije. U suštini, slika je kopija sistema datoteka. Na primjer, uzmite Dockerfile:

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

Dockerfile je skup instrukcija; Radeći svaki od njih, Docker će sačuvati promjene u sistemu datoteka i preklopiti ih na prethodne. Svaki tim kreira svoj vlastiti sloj. A gotova slika su slojevi kombinovani zajedno.

Ono što je važno znati: svaki Docker sloj može keširati. Ako se ništa nije promijenilo od posljednje izrade, onda će umjesto izvršenja naredbe, docker uzeti gotov sloj. Budući da će glavni porast brzine gradnje biti uzrokovan korištenjem keša, prilikom mjerenja brzine gradnje obratit ćemo pažnju posebno na izgradnju slike sa gotovim kešom. 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. Pokrećemo izgradnju po prvi put.
    time docker build -t app .
  3. Mijenjamo datoteku src/index.html - imitiramo rad programera.
  4. Pokrećemo izgradnju drugi put.
    time docker build -t app .

Ako je okruženje za pravljenje slika ispravno konfigurisano (više o tome u nastavku), onda kada gradnja počne, Docker će već imati gomilu keš memorije. Naš zadatak je da naučimo kako koristiti keš memoriju kako bi se izgradnja odvijala što je brže moguće. Pošto pretpostavljamo da se pokretanje build-a bez keš memorije dešava samo jednom – prvi put – stoga možemo zanemariti koliko je taj prvi put bio spor. U testovima nam je važna druga runda build-a, kada su kešovi već zagrijani i spremni smo za pečenje kolača. Međutim, neki savjeti će također utjecati na prvu izradu.

Stavimo gore opisani Dockerfile u direktorij projekta i započnemo build. Svi oglasi 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 vidite 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 je specificiran kao posljednji 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

Da biste smanjili kontekst, postoje dvije opcije. Ili stavite sve datoteke potrebne za sklapanje u poseban folder i usmjerite kontekst docker-a na ovu mapu. Ovo možda nije uvijek zgodno, pa je moguće specificirati izuzetke: ono što ne bi trebalo uvlačiti u kontekst. Da biste to učinili, stavite .dockerignore datoteku u projekat i naznačite šta nije potrebno za izgradnju:

.git
/node_modules

i ponovo pokrenite build:

$ 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 mnogo bolje od 409 MB. Također smo smanjili veličinu slike sa 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 da uštedite na veličini slike je korištenje male nadređene slike. Roditeljska slika je slika na osnovu koje se priprema naša slika. Donji sloj je specificiran naredbom FROM u Dockerfile. U našem slučaju koristimo sliku zasnovanu na Ubuntuu koja već ima instalirane 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 jačinu zvuka koristeći sliku zasnovanu na Alpine Linuxu. Alpine je vrlo mali Linux. Docker slika za nodejs baziranu na alpineu teži samo 88.5 MB. Pa zamenimo 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 neophodne za izradu aplikacije. Da, Angular se ne gradi bez Pythona ¯(°_o)/¯

Ali veličina slike je pala na 150 MB:

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

Idemo još dalje.

Višestepena 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

Uz pomoć docker run app ls -lah pokrenuli smo kontejner na osnovu naše slike app i izvršio naredbu u njemu ls -lah, nakon čega je kontejner završio svoj posao.

U proizvodnji nam je potreban samo folder dist. U ovom slučaju, fajlove nekako treba dati van. Možete pokrenuti neki HTTP server na nodejs. Ali mi ćemo to olakšati. Pogodi rusku riječ koja ima četiri slova "y". Tačno! Ynzhynyksy. Uzmimo sliku sa nginxom, stavimo folder 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šestepena izgradnja će nam pomoći da uradimo sve ovo. 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 dva uputstva FROM u Dockerfile-u, svaki od njih izvodi drugačiji korak izgradnje. Pozvali smo prvog builder, ali počevši od posljednjeg FROM, naša konačna slika će biti pripremljena. Poslednji korak je kopiranje artefakta našeg sklopa u prethodnoj fazi u finalnu sliku pomoću nginxa. Veličina slike se značajno smanjila:

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

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

docker run -p8080:80 app

Koristeći opciju -p8080:80, prosledili smo port 8080 na našem host računaru na port 80 unutar kontejnera u kojem radi nginx. Otvorite u pretraživaču http://localhost:8080/ i vidimo našu aplikaciju. Sve radi!

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

Smanjenje veličine slike sa 1.74 GB na 36 MB značajno smanjuje vrijeme potrebno za isporuku vaše aplikacije u produkciju. No, vratimo se na vrijeme sastavljanja.

$ 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 su keširana (nagoveštaj Using cache). U četvrtom koraku kopiraju se svi projektni fajlovi, a u petom koraku se instaliraju zavisnosti RUN npm ci - čak 47.338s. Zašto svaki put ponovo instalirati ovisnosti ako se mijenjaju vrlo rijetko? Hajde da shvatimo zašto nisu keširani. Poenta je da će Docker provjeravati sloj po sloj da vidi da li su se komanda i datoteke povezane s njom promijenile. U četvrtom koraku kopiramo sve datoteke našeg projekta, a među njima, naravno, ima promjena, tako da Docker ne samo da ne preuzima ovaj sloj iz keša, već i sve naredne! Učinimo 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 i tek nakon toga se kopira cijeli projekat. 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 - mnogo bolje! Važan je ispravan redoslijed slojeva: prvo kopiramo ono što se ne mijenja, zatim ono što se mijenja rijetko i na kraju ono što se često mijenja.

Zatim nekoliko riječi o sklapanju slika u CI/CD sistemima.

Korištenje prethodnih slika za keš memoriju

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

Uzmimo primjer izgradnje 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 sklapa i prenosi u GitHub pakete za dva minuta i 20 sekundi:

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

Sada promijenimo build tako da se koristi keš na osnovu prethodno 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 komande build. Činjenica je da će u višestepenoj montaži rezultirajuća slika biti skup slojeva iz posljednje faze. U ovom slučaju, slojevi iz prethodnih slojeva neće biti uključeni u sliku. Stoga, kada koristite konačnu sliku iz prethodne gradnje, Docker neće moći pronaći spremne slojeve za izgradnju slike sa nodejs-om (faza graditelja). Da bi se riješio ovaj problem, kreira se srednja slika $IMAGE_NAME-builder-stage i gura se u GitHub pakete tako da se može koristiti u narednoj gradnji kao izvor keša.

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

Ukupno vrijeme montaže smanjeno je na minut i po. Pola minute se troši na izvlačenje prethodnih slika.

Preimaging

Drugi način da se riješi problem čiste Docker keš memorije je da premjestite neke od slojeva u drugi Dockerfile, napravite ga zasebno, gurnete u Registar kontejnera i koristite ga kao nadređeni.

Kreiramo vlastitu nodejs sliku za izgradnju Angular aplikacije. Kreirajte 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 guramo 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šem glavnom Dockerfile-u koristimo gotovu sliku:

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

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

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

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

  • smanjenje konteksta;
  • korištenje malih slika roditelja;
  • višestepena montaža;
  • menjanje redosleda instrukcija u Dockerfile-u kako bi se efikasno koristila keš memorija;
  • postavljanje keš memorije u CI/CD sistemima;
  • preliminarno kreiranje slika.

Nadam se da će primjer učiniti jasnijim kako Docker funkcionira i da ćete moći optimalno konfigurirati svoju implementaciju. Da bismo se poigrali s primjerima iz članka, napravljeno je spremište https://github.com/devopsprodigy/test-docker-build.

izvor: www.habr.com

Dodajte komentar