Pradžia / Programavimas / Docker konteinerių pagrindai

Docker konteinerių pagrindai

Kas tas Docker ir kodėl visi apie jį kalba?

Jei bent kiek laiko praleidai IT pasaulyje, tikriausiai girdėjai žodį „Docker” – kartais su entuziazmu, kartais su lengvu nusivylimu, kai kažkas vėl negali suprasti, kodėl konteineris nepaleidžiamas. Docker tapo vienu iš tų įrankių, kurie iš esmės pakeitė tai, kaip programinė įranga kuriama, testuojama ir diegiama. Bet kas tai iš tikrųjų?

Paprasčiausiai tariant, Docker – tai platforma, leidžianti supakuoti aplikaciją kartu su visomis jos priklausomybėmis į vieną izoliuotą vienetą, vadinamą konteineriu. Tas konteineris veikia vienodai tiek tavo nešiojamajame kompiuteryje, tiek serveryje Vokietijoje, tiek debesų infrastruktūroje. Skamba paprastai, bet šios idėjos pasekmės yra milžiniškos.

Prieš Docker erą buvo klasikinė situacija: kūrėjas parašo kodą, viskas veikia jo mašinoje, o kai perduoda administratoriui – niekas neveikia. „Works on my machine” – šis sakinys tapo tikru IT memeliu. Docker tą problemą išsprendė elegantiškai: jei veikia konteineryje, veiks visur, kur yra Docker.

Konteineriai prieš virtualias mašinas – kuo skiriasi?

Čia dažnai kyla painiava, tad verta išsiaiškinti iš karto. Virtualios mašinos (VM) ir konteineriai sprendžia panašias problemas, bet visiškai skirtingais būdais.

Virtuali mašina emuliuoja visą kompiuterį – turi savo branduolį (kernel), savo operacinę sistemą, savo virtualią aparatinę įrangą. Tai reiškia, kad kiekviena VM gali sverti kelis gigabaitus ir užtrukti minutes, kol paleidžiama. Hipervizoriai kaip VMware ar VirtualBox sėdi tarp fizinės mašinos ir VM, tvarkydami resursų paskirstymą.

Konteineriai veikia kitaip. Jie dalinasi su pagrindine sistema (host) operacinės sistemos branduoliu, bet turi savo izoliuotą failų sistemą, procesų erdvę ir tinklo sąsają. Dėl to konteineris gali sverti tik kelis megabaitus ir paleisti per sekundes ar net milisekundes. Tai ne emuliacija – tai izoliacija.

Praktinis pavyzdys: jei nori paleisti 10 mikroservisų VM pagrindu, reikia 10 atskirų OS kopijų, 10 kartų daugiau RAM, 10 kartų ilgesnio paleidimo laiko. Su Docker – 10 konteinerių, vienas branduolys, minimalūs resursai. Skirtumas yra dramatiškas.

Tai nereiškia, kad VM yra pasenusios. Jos vis dar reikalingos, kai reikia pilnos izoliacijos saugumo sumetimais arba kai reikia paleisti skirtingas OS. Bet daugeliui programinės įrangos kūrimo scenarijų Docker yra greitesnis, lengvesnis ir praktiškesnis pasirinkimas.

Docker architektūra – kaip viskas veikia po gaubtu

Docker nėra vienas monolitinis įrankis – tai ekosistema iš kelių komponentų, kurie dirba kartu.

Docker daemon (dockerd) – tai pagrindinis procesas, veikiantis fone. Jis valdo konteinerius, vaizdus (images), tinklus ir saugyklą. Su juo bendraujama per Docker API.

Docker klientas (docker CLI) – tai komandų eilutės įrankis, kurį naudoji kasdien. Kai rašai docker run ar docker build, klientas siunčia užklausas daemonui per REST API. Klientas ir demonas gali veikti toje pačioje mašinoje arba skirtingose – tai leidžia valdyti nutolusias Docker aplinkas.

Docker registras (registry) – tai saugykla Docker vaizdams. Docker Hub yra viešiausias iš jų, bet galima naudoti ir privačius registrus – AWS ECR, Google Container Registry, GitLab Container Registry arba savo self-hosted sprendimą kaip Harbor.

Docker vaizdas (image) – tai sluoksniuota, nekeičiama failų sistemos momentinė nuotrauka. Kiekvienas vaizdas sudarytas iš sluoksnių (layers), kurie kaupiasi vienas ant kito. Tai labai protinga architektūra – jei du vaizdai dalinasi baziniais sluoksniais, jie saugomi tik vieną kartą.

Konteineris – tai paleistas vaizdo egzempliorius. Vaizdas yra kaip klasė programavime, o konteineris – kaip objektas. Iš vieno vaizdo gali paleisti dešimtis konteinerių.

Dockerfile – receptas tavo aplikacijai

Dockerfile yra tekstinis failas, kuriame aprašai, kaip sukurti Docker vaizdą. Tai tarsi instrukcijų rinkinys – Docker seka jas eilės tvarka ir kuria vaizdą sluoksnis po sluoksnio.

Štai paprastas Node.js aplikacijos Dockerfile pavyzdys:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Pereikime per kiekvieną eilutę:

  • FROM – nurodo bazinį vaizdą. node:18-alpine reiškia Node.js 18 versiją ant Alpine Linux – labai mažos Linux distribucijos, kuri sumažina galutinio vaizdo dydį.
  • WORKDIR – nustato darbo direktoriją konteineryje. Visi tolesni komandų keliai bus santykiniai šios direktorijos atžvilgiu.
  • COPY package*.json ./ – kopijuoja tik package failus pirmiausia. Kodėl? Dėl sluoksnių kešavimo – jei package.json nepasikeitė, Docker nereikės iš naujo paleisti npm install.
  • RUN npm install – įvykdo komandą vaizdo kūrimo metu.
  • EXPOSE – dokumentuoja, kurį portą aplikacija naudoja (bet faktiškai neatidarinėja porto).
  • CMD – nurodo komandą, kuri bus vykdoma paleidus konteinerį.

Vienas praktinis patarimas: visada naudok konkrečias versijas baziniuose vaizduose, ne latest. node:latest šiandien ir po mėnesio gali būti visiškai skirtingi vaizdai, kas sukels netikėtų problemų.

Kitas svarbus dalykas – .dockerignore failas. Jis veikia kaip .gitignore, bet Docker build kontekstui. Visada įtraukk node_modules, .git, .env failus ir kitus nereikalingus dalykus. Tai pagreitins build procesą ir sumažins vaizdo dydį.

Pagrindinės Docker komandos – nuo teorijos prie praktikos

Teorija yra gerai, bet Docker išmokstamas per praktiką. Štai komandos, kurias naudosi kasdien:

Vaizdų valdymas:

# Parsisiųsti vaizdą iš registro
docker pull nginx:latest

# Sukurti vaizdą iš Dockerfile
docker build -t mano-app:1.0 .

# Peržiūrėti visus lokalius vaizdus
docker images

# Ištrinti vaizdą
docker rmi mano-app:1.0

# Išvalyti nenaudojamus vaizdus
docker image prune

Konteinerių valdymas:

# Paleisti konteinerį
docker run -d -p 8080:3000 --name mano-konteineris mano-app:1.0

# Peržiūrėti veikiančius konteinerius
docker ps

# Peržiūrėti visus konteinerius (ir sustabdytus)
docker ps -a

# Sustabdyti konteinerį
docker stop mano-konteineris

# Prisijungti prie veikiančio konteinerio
docker exec -it mano-konteineris sh

# Peržiūrėti konteinerio žurnalus
docker logs -f mano-konteineris

Atkreipk dėmesį į docker run parametrus: -d paleidžia konteinerį fone (detached mode), -p 8080:3000 susieja host porto 8080 su konteinerio portu 3000, o --name suteikia konteineriui vardą, kad vėliau būtų lengviau jį rasti.

Labai naudingas triukas – docker exec -it konteineris sh (arba bash, jei yra). Tai leidžia „įlipti” į veikiantį konteinerį ir tiesiogiai tyrinėti jo failų sistemą, tikrinti konfigūracijas ar debuginti problemas. Tai vienas iš pirmų dalykų, kuriuos išmoksti, kai kažkas neveikia.

Docker Compose – kai vieno konteinerio neužtenka

Reali aplikacija retai susideda iš vieno komponento. Paprastai yra backend, frontend, duomenų bazė, cache, galbūt eilių sistema. Valdyti visus šiuos konteinerius atskiromis docker run komandomis – tikras košmaras. Čia ateina Docker Compose.

Docker Compose leidžia aprašyti visą aplikacijos infrastruktūrą viename YAML faile ir paleisti viena komanda. Štai tipiškas docker-compose.yml pavyzdys web aplikacijai su PostgreSQL ir Redis:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "8080:3000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
    volumes:
      - ./src:/app/src

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

Kelios svarbios detalės šiame faile:

depends_on nurodo paleidimo tvarką – web servisas paleidžiamas tik po db ir cache. Tačiau svarbu žinoti: depends_on tik laukia, kol konteineris paleidžiamas, bet ne kol servisas jame tampa paruoštas. PostgreSQL gali užtrukti kelias sekundes, kol priims jungtis. Dėl to reikia naudoti health checks arba retry logiką aplikacijos kode.

volumes su ./src:/app/src – tai bind mount, kuris sinchronizuoja lokalų katalogą su konteineriu. Puiku kūrimo aplinkoje – keiti kodą, ir pakeitimai matomi konteineryje iš karto.

Named volumes kaip postgres_data – tai Docker valdomos saugyklos, kurios išlieka net po konteinerio ištrynimo. Duomenų bazės duomenys nedingtų.

Pagrindinės Compose komandos:

# Paleisti visus servisus fone
docker compose up -d

# Sustabdyti ir ištrinti konteinerius
docker compose down

# Sustabdyti ir ištrinti su volumes
docker compose down -v

# Peržiūrėti žurnalus
docker compose logs -f web

# Perkurti vaizdus
docker compose build

Tinklai, saugumas ir dažniausios klaidos

Docker tinklai – tema, kuri dažnai ignoruojama, kol kažkas neveikia. Kai sukuri Docker Compose failą, Docker automatiškai sukuria tinklą visiems servisams. Tai reiškia, kad konteineriai gali pasiekti vienas kitą pagal servisų pavadinimus – db, cache, web. Tai labai patogu ir veikia iš karto.

Bet yra niuansų. Jei nori, kad konteineris būtų pasiekiamas iš išorės, reikia eksplicitiškai nurodyti portų susiejimą. Jei nori, kad konteineris nebūtų pasiekiamas iš išorės (pvz., duomenų bazė), tiesiog nenurodyk ports – ji bus pasiekiama tik iš to paties Docker tinklo.

Saugumo klausimai Docker aplinkoje dažnai nuvertinami. Keletas principų, kurių verta laikytis:

  • Niekada nevykdyk procesų kaip root konteineryje, jei to nereikia. Dockerfile pridėk USER instrukciją su neprivilegijuotu vartotoju.
  • Nenaudok :latest produkcijoje – visada fiksuok versijas.
  • Nesaugok paslapčių (secrets) vaizduose – slaptažodžiai, API raktai neturi būti Dockerfile ar pačiame vaizde. Naudok aplinkos kintamuosius, Docker secrets arba išorinį sprendimą kaip Vault.
  • Reguliariai skenuok vaizdus dėl pažeidžiamumų. Įrankiai kaip Trivy, Snyk ar Docker Scout padeda automatizuoti šį procesą.
  • Naudok minimal base images – Alpine, Distroless ar scratch. Kuo mažiau komponentų, tuo mažiau atakos paviršiaus.

Dažniausios klaidos, kurias daro pradedantieji:

Pirmoji – kopijuoti visą projektą be .dockerignore. Tai ne tik lėtina build procesą, bet gali netyčia į vaizdą patekti .env failai su slaptažodžiais.

Antroji – neoptimizuoti sluoksnių tvarkos Dockerfile. Instrukcijos, kurios keičiasi dažnai (kaip kodo kopijavimas), turi būti failo pabaigoje. Instrukcijos, kurios keičiasi retai (kaip priklausomybių diegimas), – pradžioje. Taip Docker kešas veikia efektyviau.

Trečioji – pamiršti, kad konteinerio failų sistema yra laikina. Jei rašai failus į konteinerį be volume, jie dingsta sustabdžius konteinerį. Tai ypač aktualu duomenų bazėms ir upload’intiems failams.

Nuo vieno konteinerio iki realaus pasaulio – kur eiti toliau

Docker pagrindai – tai tik pradžia. Kai supranti konteinerių idėją ir moki naudoti pagrindinius įrankius, natūraliai atsiranda klausimas: o kaip tai veikia produkcijoje, kai reikia paleisti šimtus konteinerių?

Čia ateina konteinerių orkestravimas. Kubernetes yra de facto standartas šioje srityje – jis automatiškai valdo konteinerių paleidimą, perkrovimą, skalėjimą, tinklo konfigūraciją ir daug daugiau. Docker Swarm yra paprastesnis alternatyvas, integruotas į patį Docker, bet pramonėje dominuoja Kubernetes.

Multi-stage builds – tai technika, kuri verta išmokti anksčiau nei vėliau. Idėja paprasta: naudoji vieną vaizdą kodui kompiliuoti (su visais build įrankiais), o į galutinį vaizdą kopijuoji tik sukompiliuotus artefaktus. Rezultatas – drastiškai mažesni produkcijos vaizdai. Node.js aplikacijai tai gali reikšti skirtumą tarp 1GB ir 100MB vaizdo.

CI/CD integravimas su Docker yra beveik privalomas šiuolaikiniame kūrime. GitHub Actions, GitLab CI, Jenkins – visi šie įrankiai turi puikią Docker palaikymą. Tipiškas pipeline atrodo taip: kodas pushinamas į repozitoriją, CI sistema automatiškai sukuria Docker vaizdą, jį testuoja, skenuoja dėl pažeidžiamumų ir, jei viskas gerai, publikuoja į registrą ir diegia į aplinką.

Monitoringas konteinerizuotoje aplinkoje taip pat turi savo specifiką. Prometheus ir Grafana yra populiari kombinacija metrikoms rinkti ir vizualizuoti. Centralizuotas žurnalų rinkimas su ELK stack (Elasticsearch, Logstash, Kibana) arba Loki padeda valdyti žurnalus iš daugybės konteinerių.

Docker nėra sidabrinė kulka – yra situacijų, kai jis nėra tinkamas sprendimas. Labai senos aplikacijos su sudėtingomis licencijomis, sistemos reikalaujančios tiesioginio aparatinės įrangos prieigos, arba labai paprasti scenarijai, kur pridedama kompleksiškumas nepagrįstas – visais šiais atvejais verta pagalvoti, ar Docker iš tikrųjų reikalingas.

Bet jei kuri modernią web aplikaciją, mikroservisus, ar tiesiog nori, kad „veikia mano mašinoje” problema išnyktų amžiams – Docker yra vienas geriausių investicijų į savo įrankių arsenalą. Pradėk nuo paprasto Dockerfile, pereik prie Docker Compose, ir natūraliai atrasi, kad konteineriai tampa neatsiejama tavo darbo proceso dalimi. Tai ne mada – tai fundamentalus pokytis, kaip programinė įranga kuriama ir pristatoma.