„Docker” konteinerių naudojimas web projektuose

Kodėl verta susipažinti su Docker technologija

Prisimenu, kaip prieš keletą metų kolega pasakojo apie savo košmarą – projektas puikiai veikė jo kompiuteryje, bet production serveryje viskas byrėjo. „Works on my machine” tapo mūsų komandos vidinių pokštų dalimi, kol neaptikome Docker. Šiandien ši technologija tapo tokia įprasta, kad naujų projektų kūrimas be konteinerizacijos atrodo keistai.

Docker iš esmės išsprendžia amžiną problemą – kaip užtikrinti, kad aplikacija veiks vienodai visur: kūrėjo nešiojamame kompiuteryje, testuotojo aplinkoje, staging serveryje ir galutinėje production sistemoje. Konteineriai įpakuoja ne tik jūsų kodą, bet ir visas priklausomybes, bibliotekas, konfigūracijas – viską, ko reikia programai veikti.

Praktiškai tai reiškia, kad galite užmiršti situacijas, kai vienas komandos narys dirba su PHP 7.4, kitas su 8.1, o serveris turi 7.2. Visi naudoja tą patį konteinerį su ta pačia versija. Skamba paprasta, bet tai kardinaliai keičia darbo eigą.

Kaip Docker veikia ir kuo skiriasi nuo virtualių mašinų

Daugelis pradedančiųjų klausia – ar Docker nėra tas pats kas VirtualBox ar VMware? Trumpas atsakymas: ne, ir tai yra gera žinia. Virtualios mašinos emuliuoja visą operacinę sistemą su branduoliu, draiversiais ir viskuo kitu. Tai reiškia, kad jei turite 3 VM, jūs faktiškai paleidžiate 3 pilnas OS kopijas. Resursų ėdikai, tiesą sakant.

Docker konteineriai veikia kitaip – jie dalijasi host sistemos branduoliu, bet turi izoliuotą failų sistemą, procesus ir tinklo sąsają. Vienas konteineris gali užimti vos keliasdešimt megabaitų, kai VM paprastai reikia kelių gigabaitų. Paleidimas užtrunka sekundes, ne minutes.

Techniškai Docker naudoja Linux kernel funkcijas kaip namespaces ir cgroups. Namespaces užtikrina izoliaciją – kiekvienas konteineris mato tik savo procesus, failų sistemą, tinklo interfeisus. Cgroups kontroliuoja resursų paskirstymą – kiek CPU, RAM ar disko I/O gali naudoti konteineris.

Praktinis pavyzdys: jūsų web projektas gali turėti atskirą konteinerį PHP aplikacijai, atskirą MySQL duomenų bazei, dar vieną Redis cache, ir viskas veiks jūsų laptope naudodamas mažiau resursų nei viena VM su visa šita krūva.

Pirmieji žingsniai: Dockerfile ir image kūrimas

Dockerfile – tai receptas, kaip sukurti jūsų aplikacijos image. Panagrinėkime realų pavyzdį PHP/Laravel projektui:


FROM php:8.2-fpm

WORKDIR /var/www/html

RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip

RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

COPY . .

RUN composer install --no-dev --optimize-autoloader

RUN chown -R www-data:www-data /var/www/html

EXPOSE 9000

CMD ["php-fpm"]

Kas čia vyksta? Pradedame nuo bazinio PHP 8.2 FPM image (FROM). Nustatome darbo direktoriją (WORKDIR). Įdiegiame sisteminius paketus, kurių reikia mūsų aplikacijai. Įjungiame reikalingas PHP ekstensijas. Nukopijuojame Composer ir mūsų kodą. Įdiegiame PHP priklausomybes. Nustatome teises ir atidarome portą.

Svarbus patarimas: nenaudokite COPY . . komandos be .dockerignore failo. Sukurkite .dockerignore ir įtraukite node_modules, vendor, .git ir kitus nereikalingus katalogus. Kitaip jūsų image bus milžiniškas ir build procesas lėtas.

Kai Dockerfile paruoštas, sukuriate image komanda:

docker build -t mano-projektas:latest .

Taškas gale reiškia, kad Dockerfile yra dabartiniame kataloge. Parametras -t suteikia pavadinimą ir tag’ą jūsų image.

Docker Compose: orkestravimas vietiniam developmentui

Vienas konteineris – gerai, bet realūs projektai retai apsiriboja vienu servisu. Čia į pagalbą ateina Docker Compose. Tai įrankis, leidžiantis aprašyti ir paleisti kelių konteinerių aplikacijas.

Štai docker-compose.yml pavyzdys tipiniam LEMP (Linux, Nginx, MySQL, PHP) stackui:


version: '3.8'

services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./:/var/www/html
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
- mysql

php:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/var/www/html
environment:
- DB_HOST=mysql
- DB_DATABASE=laravel
- DB_USERNAME=root
- DB_PASSWORD=secret

mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel
volumes:
- mysql-data:/var/lib/mysql
ports:
- "3306:3306"

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

volumes:
mysql-data:

Paleidžiate viską viena komanda: docker-compose up -d. Parametras -d reiškia detached režimą – konteineriai veiks fone.

Patogumas akivaizdus: naujas komandos narys klonuoja repo, paleidžia docker-compose up, ir po minutės turi veikiančią development aplinką. Jokio „įdiek MySQL, sukonfigūruok PHP, įjunk extensijas” maratono.

Svarbu suprasti volumes koncepciją. Eilutė ./:/var/www/html reiškia, kad jūsų lokalus kodas yra „įmontuotas” į konteinerį. Keičiate failą savo IDE – pakeitimai iš karto matomi konteineryje. Duomenų bazės volume (mysql-data) užtikrina, kad duomenys išliks net perkrovus konteinerius.

Realūs scenarijai ir sprendimų būdai

Kaip dirbti su duomenų baze

Dažna problema: kaip importuoti duomenų bazės dump’ą į MySQL konteinerį? Keletas būdų:

Pirmas – tiesiogiai per docker exec:
docker exec -i mysql-container mysql -uroot -psecret laravel < dump.sql

Antras – per docker-compose:
docker-compose exec mysql mysql -uroot -psecret laravel < dump.sql

Trečias – automatizuotas per docker-entrypoint-initdb.d. MySQL image automatiškai vykdo .sql failus iš šio katalogo pirmą kartą paleidžiant konteinerį:


mysql:
image: mysql:8.0
volumes:
- ./docker/mysql/init:/docker-entrypoint-initdb.d
- mysql-data:/var/lib/mysql

Debugging ir logai

Kai kažkas neveikia, pirmiausia žiūrite logus:
docker-compose logs -f php

Parametras -f (follow) rodo logus realiu laiku. Norite pamatyti paskutines 100 eilučių?
docker-compose logs --tail=100 php

Kartais reikia „įlįsti" į konteinerį ir pažiūrėti, kas viduje:
docker-compose exec php bash

Dabar esate konteineryje ir galite vykdyti bet kokias komandas, tikrinti failus, testuoti konfigūraciją.

Performance optimizavimas Mac ir Windows sistemose

Jei dirbate Mac ar Windows, pastebėsite, kad volumes gali būti lėti. Tai žinoma problema, nes Docker šiose sistemose veikia per virtualizacijos sluoksnį. Sprendimai:

Naudokite cached ar delegated režimus:

volumes:
- ./:/var/www/html:cached

Arba išvis nenaudokite volumes development metu – kopijuokite kodą į image ir naudokite hot reload įrankius. Tai greitesnis, bet mažiau patogus variantas.

Dar vienas triukas – naudokite named volumes vendor ir node_modules katalogams:

volumes:
- ./:/var/www/html:cached
- vendor:/var/www/html/vendor
- node_modules:/var/www/html/node_modules

Taip šie katalogai lieka konteineryje ir nėra sinchronizuojami su host sistema, kas gerokai pagreitina darbą.

Multi-stage builds ir production optimizavimas

Development ir production aplinkos reikalavimai skiriasi. Development reikia debug įrankių, hot reload, source maps. Production reikia minimalaus image dydžio, saugumo, greičio.

Multi-stage builds leidžia turėti vieną Dockerfile, kuris kuria skirtingus image variantus:


# Build stage
FROM node:18 AS frontend-builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM php:8.2-fpm-alpine
WORKDIR /var/www/html

RUN apk add --no-cache \
mysql-client \
&& docker-php-ext-install pdo_mysql opcache

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY composer*.json ./
RUN composer install --no-dev --no-scripts --optimize-autoloader

COPY . .
COPY --from=frontend-builder /app/public/build ./public/build

RUN chown -R www-data:www-data /var/www/html

USER www-data
EXPOSE 9000
CMD ["php-fpm"]

Pastebėkite alpine variantą – tai minimalus Linux distribucija, dėl kurios image gali būti 5-10 kartų mažesnis. Taip pat naudojame USER direktyvą – konteineris veiks ne root teisėmis, kas saugiau.

Production docker-compose.yml paprastai naudoja pre-built images iš registry:


services:
php:
image: registry.example.com/mano-projektas:${VERSION}
restart: unless-stopped
environment:
- APP_ENV=production

CI/CD integracija ir automatizavimas

Docker puikiai dera su CI/CD pipeline'ais. GitLab CI pavyzdys:


stages:
- build
- test
- deploy

build:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
stage: test
script:
- docker-compose -f docker-compose.test.yml up -d
- docker-compose -f docker-compose.test.yml exec -T php vendor/bin/phpunit
- docker-compose -f docker-compose.test.yml down

deploy:
stage: deploy
script:
- ssh user@server "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
- ssh user@server "docker-compose up -d"
only:
- main

Kiekvienas commit sukuria naują image su unikaliu tag'u (commit SHA). Testai vykdomi atskirame docker-compose.test.yml aplinkoje. Jei testai praeina ir tai main branch – automatiškai deplojiname.

Svarbus patarimas: naudokite image registry (Docker Hub, GitLab Container Registry, AWS ECR). Nekurkite image tiesiogiai production serveryje – tai lėta ir nesaugu. Sukurkite CI/CD sistemoje, pushinkite į registry, production serveryje tik pullinkite ir paleiskite.

Saugumas ir best practices

Docker saugumas – plati tema, bet keletas esminių dalykų:

Nenaudokite latest tag production. Jis nuolat keičiasi, ir nežinosite, kokią versiją tiksliai paleidote. Naudokite konkretų tag'ą ar commit SHA.

Reguliariai atnaujinkite base images. Seni image gali turėti saugumo spragų. Įrankiai kaip Trivy ar Snyk gali skenuoti jūsų images ir perspėti apie pažeidžiamumus:


docker run aquasec/trivy image mano-projektas:latest

Nenaudokite root user konteineryje. Visada pridėkite:

RUN addgroup -g 1000 appuser && adduser -D -u 1000 -G appuser appuser
USER appuser

Nekopijuokite sensitive informacijos į image. .env failai, API raktai, slaptažodžiai – visa tai turėtų būti perduodama per environment variables arba secrets management sistemas (Docker secrets, Kubernetes secrets, AWS Secrets Manager).

Naudokite .dockerignore:

.git
.env
node_modules
vendor
*.log
.idea
.vscode

Ribokite konteinerių resursus docker-compose.yml:

services:
php:
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
memory: 512M

Ką daryti, kai viskas sudėta ir veikia

Dabar, kai suprantate Docker pagrindus, keletas patarimų kaip išlaikyti viską tvarkingoje būsenoje. Pirmiausia – reguliariai valykite nebenaudojamus resursus. Docker turi tendenciją kaupti senus images, sustabdytus konteinerius, neprisijungusias networks:


docker system prune -a --volumes

Ši komanda išvalys viską, kas nebenaudojama. Būkite atsargūs su --volumes – tai ištrins ir duomenų volumes, kurie nėra prijungti prie veikiančių konteinerių.

Dokumentuokite savo Docker setup. README.md turėtų turėti aiškias instrukcijas, kaip paleisti projektą. Naujas komandos narys neturėtų spėlioti, kokias komandas vykdyti. Pavyzdys:


# Projekto paleidimas
1. Klonuokite repo
2. Nukopijuokite .env.example į .env
3. Paleiskite: docker-compose up -d
4. Įvykdykite migrations: docker-compose exec php php artisan migrate
5. Atidarykite: http://localhost

Monitorinkite konteinerių būseną production. Įrankiai kaip Portainer, cAdvisor ar Prometheus + Grafana padės sekti resursų naudojimą, logus, konteinerių health status.

Svarbiausia – Docker nėra sidabrinė kulka. Tai įrankis, kuris išsprendžia konkrečias problemas: aplinkos konsistenciją, deployment paprastumą, resursų efektyvumą. Bet jis nepakeis blogo kodo, neišspręs architektūrinių problemų, nepadarys lėtos aplikacijos greitos.

Pradėkite mažai – vienas projektas, paprastas setup. Eksperimentuokite, darykite klaidas, mokykitės. Docker dokumentacija yra puiki, community aktyvus, Stack Overflow pilnas atsakymų. Po kelių savaičių pastebėsite, kad jau nebegalvojate apie „works on my machine" problemas, o fokusatės į tai, kas iš tiesų svarbu – kurti gerą produktą.

Parašykite komentarą

El. pašto adresas nebus skelbiamas. Būtini laukeliai pažymėti *