Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Áður en þáttur fer í framleiðslu, á þessum tímum flókinna hljómsveitarstjóra og CI/CD, er langt í land frá skuldbindingu til prófana og afhendingu. Áður gat þú hlaðið upp nýjum skrám í gegnum FTP (enginn gerir það lengur, ekki satt?), og „dreifing“ ferlið tók nokkrar sekúndur. Nú þarftu að búa til sameiningarbeiðni og bíða lengi eftir að eiginleikinn nái til notenda.

Hluti af þessari leið er að byggja Docker mynd. Stundum tekur samkoman mínútur, stundum tugir mínútna, sem varla er hægt að kalla eðlilegt. Í þessari grein munum við taka einfalt forrit sem við munum pakka inn í mynd, beita nokkrum aðferðum til að flýta fyrir byggingu og skoða blæbrigði þess hvernig þessar aðferðir virka.

Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Við höfum góða reynslu af því að búa til og styðja við fjölmiðlavef: TASS, The Bell, "Nýtt dagblað", Lýðveldið… Ekki er langt síðan við stækkuðum vörusafnið okkar með því að gefa út vöruvefsíðu Áminning. Og á meðan nýjum eiginleikum var fljótt bætt við og gamlar villur voru lagfærðar, varð hæg uppsetning stórt vandamál.

Við sendum til GitLab. Við söfnum myndum, ýtum þeim í GitLab Registry og rúllum þeim út í framleiðslu. Það lengsta á þessum lista er að setja saman myndir. Til dæmis: án hagræðingar tók hver bakendabygging 14 mínútur.

Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Á endanum varð ljóst að við gætum ekki lifað svona lengur og við settumst niður til að átta okkur á því hvers vegna það tók svona langan tíma að safna myndunum. Fyrir vikið tókst okkur að stytta samsetningartímann í 30 sekúndur!

Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Fyrir þessa grein, til að vera ekki bundin við umhverfi Reminder, skulum við skoða dæmi um að setja saman tómt Angular forrit. Svo, við skulum búa til umsókn okkar:

ng n app

Bættu PWA við það (við erum framsækin):

ng add @angular/pwa --project app

Á meðan verið er að hlaða niður milljón npm pakka, skulum við reikna út hvernig docker myndin virkar. Docker veitir möguleika á að pakka forritum og keyra þau í einangruðu umhverfi sem kallast gámur. Þökk sé einangrun geturðu keyrt marga gáma samtímis á einum netþjóni. Gámar eru miklu léttari en sýndarvélar vegna þess að þeir keyra beint á kerfiskjarnanum. Til að keyra gám með forritinu okkar þurfum við fyrst að búa til mynd þar sem við munum pakka öllu sem er nauðsynlegt til að forritið okkar geti keyrt. Í meginatriðum er mynd afrit af skráarkerfinu. Taktu til dæmis Dockerfile:

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

Dockerfile er sett af leiðbeiningum; Með því að gera hverja þeirra mun Docker vista breytingarnar á skráarkerfinu og leggja þær yfir þær fyrri. Hvert lið býr til sitt eigið lag. Og fullunna myndin er lög sameinuð saman.

Það sem er mikilvægt að vita: hvert Docker lag getur vistað skyndiminni. Ef ekkert hefur breyst frá síðustu smíði, þá mun hafnarstjórinn taka tilbúið lag í stað þess að framkvæma skipunina. Þar sem helsta aukningin á byggingarhraða verður vegna notkunar skyndiminni, þegar byggingarhraði er mældur munum við huga sérstaklega að því að byggja upp mynd með tilbúnu skyndiminni. Svo, skref fyrir skref:

  1. Við eyðum myndunum á staðnum þannig að fyrri keyrslur hafi ekki áhrif á prófið.
    docker rmi $(docker images -q)
  2. Við ræsum bygginguna í fyrsta skipti.
    time docker build -t app .
  3. Við breytum src/index.html skránni - við líkjum eftir verkum forritara.
  4. Við keyrum bygginguna í annað sinn.
    time docker build -t app .

Ef umhverfið til að byggja upp myndir er rétt stillt (meira um það hér að neðan), þegar byggingin hefst mun Docker þegar hafa fullt af skyndiminni um borð. Verkefni okkar er að læra hvernig á að nota skyndiminni svo að smíðin gangi eins fljótt og hægt er. Þar sem við gerum ráð fyrir að keyra smíði án skyndiminni gerist aðeins einu sinni - í fyrsta skiptið - getum við því hunsað hversu hægt það fyrsta skipti var. Í prófunum er önnur keyrsla smíðinnar mikilvæg fyrir okkur, þegar skyndiminni er þegar hituð og við erum tilbúin að baka kökuna okkar. Hins vegar munu nokkrar ábendingar einnig hafa áhrif á fyrstu byggingu.

Við skulum setja Dockerfile sem lýst er hér að ofan í verkefnamöppuna og hefja bygginguna. Allar skráningar hafa verið þéttar til að auðvelda lestur.

$ 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

Við breytum innihaldi src/index.html og keyrum það í annað sinn.

$ 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

Til að sjá hvort við höfum myndina skaltu keyra skipunina docker images:

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

Áður en hann byggir tekur docker allar skrárnar í núverandi samhengi og sendir þær til púkans Sending build context to Docker daemon 409MB. Byggingarsamhengið er tilgreint sem síðasta rökin fyrir byggingu skipuninni. Í okkar tilviki er þetta núverandi möppu - “.”, - og Docker dregur allt sem við höfum í þessari möppu. 409 MB er mikið: við skulum hugsa um hvernig á að laga það.

Að draga úr samhenginu

Til að draga úr samhenginu eru tveir kostir í boði. Eða settu allar skrár sem þarf til samsetningar í sérstakri möppu og vísaðu tengikvíarsamhenginu á þessa möppu. Þetta er kannski ekki alltaf þægilegt, svo það er hægt að tilgreina undantekningar: hvað ætti ekki að draga inn í samhengið. Til að gera þetta skaltu setja .dockerignore skrána í verkefnið og gefa til kynna hvað er ekki nauðsynlegt fyrir bygginguna:

.git
/node_modules

og keyrðu bygginguna aftur:

$ 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 er miklu betra en 409 MB. Við minnkuðum líka myndstærðina úr 1.74 í 1.38 GB:

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

Við skulum reyna að minnka stærð myndarinnar enn frekar.

Við notum Alpine

Önnur leið til að spara myndstærð er að nota litla foreldramynd. Foreldraímyndin er sú mynd sem mynd okkar er útbúin á. Neðsta lagið er tilgreint með skipuninni FROM í Dockerfile. Í okkar tilviki erum við að nota Ubuntu-undirstaða mynd sem þegar er með nodejs uppsett. Og það vegur...

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

... næstum gígabæt. Þú getur dregið verulega úr hljóðstyrknum með því að nota mynd byggða á Alpine Linux. Alpine er mjög lítið Linux. Docker myndin fyrir nodejs byggð á alpine vegur aðeins 88.5 MB. Svo skulum við skipta um líflega mynd okkar í húsunum:

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

Við þurftum að setja upp nokkra hluti sem eru nauðsynlegir til að byggja upp forritið. Já, Angular byggir ekki án Python ¯(°_o)/¯

En myndstærðin fór niður í 150 MB:

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

Við skulum ganga enn lengra.

Fjölþrepa samsetning

Ekki er allt sem er á myndinni það sem við þurfum í framleiðslu.

$ 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

Með docker run app ls -lah við settum af stað gám sem byggir á myndinni okkar app og framkvæmdi skipunina í henni ls -lah, en eftir það lauk gámurinn verki sínu.

Í framleiðslu þurfum við aðeins möppu dist. Í þessu tilviki þarf einhvern veginn að gefa skrárnar utan. Þú getur keyrt einhvern HTTP netþjón á nodejs. En við munum gera það auðveldara. Giska á rússneskt orð sem hefur fjóra stafi „y“. Rétt! Ynzhynyksy. Tökum mynd með nginx, setjum möppu í hana dist og lítil stilling:

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

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

Fjölþrepa bygging mun hjálpa okkur að gera þetta allt. Við skulum breyta Dockerfile okkar:

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ú höfum við tvær leiðbeiningar FROM í Dockerfile keyrir hver þeirra mismunandi byggingarskref. Við hringdum í þann fyrsta builder, en frá og með síðasta FROM verður lokamyndin okkar undirbúin. Síðasta skrefið er að afrita gripinn af samsetningunni okkar á fyrra stigi yfir í lokamyndina með nginx. Stærð myndarinnar hefur minnkað verulega:

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

Við skulum keyra ílátið með myndinni okkar og ganga úr skugga um að allt virki:

docker run -p8080:80 app

Með því að nota -p8080:80 valmöguleikann sendum við höfn 8080 á hýsingarvélinni okkar yfir á höfn 80 inni í gámnum þar sem nginx keyrir. Opna í vafra http://localhost:8080/ og við sjáum umsókn okkar. Allt er að virka!

Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Með því að minnka myndastærð úr 1.74 GB í 36 MB dregur það verulega úr þeim tíma sem það tekur að koma forritinu þínu til framleiðslu. En snúum okkur aftur að samkomutímanum.

$ 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

Breyting á röð laga

Fyrstu þrjú skrefin okkar voru í skyndiminni (vísbending Using cache). Í fjórða þrepi eru allar verkefnaskrár afritaðar og í fimmta þrepi eru ósjálfstæðir settir upp RUN npm ci - allt að 47.338s. Af hverju að setja upp ósjálfstæði aftur í hvert skipti ef þær breytast mjög sjaldan? Við skulum reikna út hvers vegna þeir voru ekki í skyndiminni. Málið er að Docker mun athuga lag fyrir lag til að sjá hvort skipunin og skrárnar sem tengjast henni hafi breyst. Í fjórða skrefi afritum við allar skrár verkefnisins okkar, og meðal þeirra eru auðvitað breytingar, svo Docker tekur ekki aðeins þetta lag úr skyndiminni, heldur einnig öll síðari! Við skulum gera smá breytingar á 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 .

Fyrst eru package.json og package-lock.json afrituð, síðan eru ósjálfstæði sett upp og aðeins eftir það er allt verkefnið afritað. Þar af leiðandi:

$ 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 sekúndur í stað 3 mínútna - miklu betra! Rétt röð laga er mikilvæg: fyrst afritum við það sem breytist ekki, síðan það sem breytist sjaldan og loks það sem breytist oft.

Næst, nokkur orð um samsetningu mynda í CI/CD kerfum.

Notar fyrri myndir fyrir skyndiminni

Ef við notum einhvers konar SaaS lausn fyrir bygginguna, þá gæti staðbundið Docker skyndiminni verið hreint og ferskt. Til að gefa hafnarmanninum stað til að fá bökuðu lögin, gefðu honum fyrri byggða mynd.

Við skulum taka dæmi um að byggja upp forritið okkar í GitHub Actions. Við notum þessa stillingu

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

Myndin er sett saman og ýtt í GitHub pakka á tveimur mínútum og 20 sekúndum:

Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Nú skulum við breyta byggingunni þannig að skyndiminni sé notað byggt á fyrri myndum:

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

Fyrst þurfum við að segja þér hvers vegna tvær skipanir eru ræstar build. Staðreyndin er sú að í fjölþrepa samsetningu mun myndin sem myndast vera sett af lögum frá síðasta stigi. Í þessu tilviki verða lög úr fyrri lögum ekki með í myndinni. Þess vegna, þegar lokamyndin úr fyrri byggingu er notuð, mun Docker ekki geta fundið tilbúin lög til að byggja myndina með nodejs (byggjarstigi). Til að leysa þetta vandamál er búið til millimynd $IMAGE_NAME-builder-stage og er ýtt í GitHub pakka þannig að hægt sé að nota það í síðari byggingu sem skyndiminni.

Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Heildarsamsetningartíminn var styttur í eina og hálfa mínútu. Hálf mínúta fer í að draga upp fyrri myndir.

Formyndataka

Önnur leið til að leysa vandamálið með hreinu Docker skyndiminni er að færa sum laganna inn í aðra Dockerfile, byggja það sérstaklega, ýta því inn í Container Registry og nota það sem foreldri.

Við búum til okkar eigin nodejs mynd til að byggja upp Angular forrit. Búðu til Dockerfile.node í verkefninu

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

Við söfnum og ýtum á opinbera mynd í Docker Hub:

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

Nú í aðal Dockerfile okkar notum við fullunna myndina:

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

Í dæminu okkar minnkaði byggingartíminn ekki, en forsmíðaðar myndir geta verið gagnlegar ef þú ert með mörg verkefni og þarft að setja upp sömu ósjálfstæði í hverju þeirra.

Nokkur ráð um hvernig á að flýta fyrir byggingu Docker-mynda. Til dæmis allt að 30 sekúndur

Við skoðuðum nokkrar aðferðir til að flýta fyrir smíði bryggjumynda. Ef þú vilt að uppsetningin gangi hratt fyrir sig, reyndu að nota þetta í verkefninu þínu:

  • draga úr samhengi;
  • notkun lítilla foreldramynda;
  • fjölþrepa samsetning;
  • breyta röð leiðbeininga í Dockerfile til að nýta skyndiminni á skilvirkan hátt;
  • setja upp skyndiminni í CI/CD kerfum;
  • bráðabirgðagerð mynda.

Ég vona að dæmið muni gera það skýrara hvernig Docker virkar og þú munt geta stillt uppsetningu þína sem best. Til þess að leika sér með dæmin úr greininni hefur verið búið til geymsla https://github.com/devopsprodigy/test-docker-build.

Heimild: www.habr.com

Bæta við athugasemd