Kodėl CI/CD automatizavimas tapo būtinybe, o ne prabanga
Prisimenu laikus, kai deployment’as reiškė penktadienio vakarą praleistą biure, nervingai spaudant mygtukus ir tikintis, kad niekas nesugrius. Dabar, kai dirbu su GitLab CI/CD, tas laikotarpis atrodo kaip tolima praeitis. Automatizavimas nėra tiesiog madinga sąvoka – tai realus būdas išsaugoti savo protinį sveikatą ir pristatyti kodą greičiau bei patikimiau.
GitLab CI/CD sistema leidžia automatizuoti viską nuo kodo testavimo iki production aplinkos deployment’o. Skirtingai nei kai kurios kitos platformos, GitLab siūlo visą ekosistemą vienoje vietoje – nereikia jongliavimu tarp skirtingų įrankių ir integracijų. Tai ypač patogu mažesnėms komandoms, kurios neturi resursų prižiūrėti sudėtingos infrastruktūros.
Pagrindinė GitLab CI/CD filosofija – „everything as code”. Jūsų pipeline’ai aprašomi YAML faile, kuris gyvena kartu su projekto kodu. Tai reiškia versijų kontrolę, code review galimybes ir lengvą atkūrimą, jei kas nors sugenda.
Pirmieji žingsniai su .gitlab-ci.yml
Viskas prasideda nuo .gitlab-ci.yml failo projekto šakniniame kataloge. Šis failas – jūsų pipeline’o širdis. Pradžioje jis gali atrodyti bauginantis, bet iš tikrųjų bazinė struktūra yra gana intuityvi.
Štai paprasčiausias pavyzdys:
stages:
- test
- build
- deploy
test_job:
stage: test
script:
– npm install
– npm test
build_job:
stage: build
script:
– npm run build
artifacts:
paths:
– dist/
Šiame pavyzdyje apibrėžiame tris etapus (stages). Kiekvienas job’as priklauso vienam etapui, ir GitLab automatiškai vykdo juos nuosekliai. Jei test_job nepavyksta, build_job net neprasideda – tai logiška ir saugi elgsena.
Praktinis patarimas: pradėkite paprastai. Nesistenkite iš karto sukurti super sudėtingo pipeline’o su visomis įmanomomis funkcijomis. Geriau pradėti nuo bazinio testavimo ir deployment’o, o vėliau laipsniškai pridėti daugiau funkcionalumo.
Runners ir jų konfigūravimas: kas vykdo jūsų kodą
GitLab Runner – tai programa, kuri faktiškai vykdo jūsų CI/CD job’us. Galite naudoti GitLab.com shared runner’ius (patogiausia pradedantiesiems) arba sukonfigūruoti savo.
Savo runner’io turėjimas duoda daugiau kontrolės ir gali būti pigiau, jei turite daug job’ų. Aš asmeniškai naudoju dedicated runner’ius production projektams – taip galiu užtikrinti, kad jokios kitos komandos job’ai nekonkuruoja dėl resursų kritiniais momentais.
Runner’io registravimas yra gana tiesmukas procesas:
gitlab-runner register \
--url https://gitlab.com/ \
--registration-token YOUR_TOKEN \
--executor docker \
--description "My Docker Runner" \
--docker-image "alpine:latest"
Executor’ių tipai – tai svarbi tema. Docker executor’ius yra populiariausias pasirinkimas, nes suteikia izoliuotą aplinką kiekvienam job’ui. Shell executor’ius paprastesnis, bet mažiau saugus. Kubernetes executor’ius puikiai tinka, jei jau naudojate K8s infrastruktūrą.
Vienas dalykas, kurį išmokau sunkiuoju būdu: visada nustatykite concurrent limitus savo runner’iams. Kartą palikau default nustatymus, ir vienas klaidingai sukonfigūruotas pipeline’as sukėlė 50 paralelių job’ų, kurie užkrovė serverį taip, kad nieko kito nebegalėjo veikti.
Cache ir artifacts: kaip sutaupyti laiko ir nervų
Viena didžiausių pradedančiųjų klaidų – ignoruoti cache ir artifacts skirtumus. Aš pats pradžioje tai painiojau, ir mano pipeline’ai buvo nepagrįstai lėti.
Cache naudojamas dependency’ams, kurie nekinta tarp pipeline’ų. Pavyzdžiui, node_modules arba composer vendor katalogas. Cache’as nėra garantuotas – jis gali būti išvalytas, todėl jūsų job’as turi sugebėti veikti ir be jo.
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
Artifacts – tai failai, kuriuos norite perduoti tarp job’ų arba išsaugoti po pipeline’o pabaigos. Build rezultatai, test reportai, compiled binary’ai – visa tai turėtų būti artifacts.
artifacts:
paths:
- dist/
expire_in: 1 week
reports:
junit: test-results.xml
Praktinis patarimas: naudokite expire_in parametrą artifacts’ams. GitLab saugo juos neribotą laiką pagal nutylėjimą, ir jūsų storage gali greitai užsipildyti. Savaitės ar dviejų pakanka daugumai atvejų.
Dar vienas trikis – cache key strategija. Aš naudoju kombinaciją iš branch pavadinimo ir lock file hash’o:
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
Taip cache automatiškai atsinaujina, kai keičiasi dependency’ai, bet išlieka stabilus tarp commit’ų.
Variables ir secrets valdymas: saugumas pirmiausia
Slaptažodžiai ir API raktai kode – tai katastrofa, laukianti savo valandos. GitLab siūlo kelias saugumo lygmenis variables valdymui.
Project level variables yra paprasčiausias būdas. Einate į Settings → CI/CD → Variables ir pridedate. Bet štai keletas niuansų, kuriuos verta žinoti:
Visada pažymėkite „Protected” flag’ą jautriems kintamiesiems. Taip jie bus prieinami tik protected branch’uose ir tag’uose. Tai reiškia, kad kas nors negali tiesiog sukurti feature branch’o ir ištraukti production database slaptažodžio.
„Masked” flag’as užtikrina, kad reikšmė nebus rodoma job log’uose. Bet atsargiai – masked variables turi atitikti tam tikrą regex pattern’ą (tik base64 simboliai), todėl ne visi slaptažodžiai gali būti masked.
deploy_production:
stage: deploy
script:
- echo "Deploying to production..."
- ./deploy.sh $PROD_SERVER $PROD_API_KEY
only:
- main
environment:
name: production
Grupės lygio variables puikiai tinka, kai turite kelis projektus, kurie dalijasi tais pačiais credentials. Pavyzdžiui, visi projektai gali naudoti tą patį Docker registry prisijungimą.
Aš asmeniškai dar naudoju external secrets management (HashiCorp Vault) kritiniams production secrets. GitLab turi integraciją, kuri leidžia dinamiškai fetch’inti secrets pipeline’o metu:
secrets:
DATABASE_PASSWORD:
vault: production/db/password@secret
file: false
Pipeline strategijos skirtingiems projektams
Nėra vieno „teisingojo” būdo struktūrizuoti pipeline’us. Tai, kas veikia mikroservisų architektūroje, gali būti overkill paprastam WordPress projektui.
Monolith projektams aš rekomenduoju linear pipeline’ą su aiškiais gate’ais:
stages:
- lint
- test
- security
- build
- deploy_staging
- deploy_production
Kiekvienas etapas turi praeiti, kad galėtų tęstis toliau. Security scanning’as (SAST, dependency scanning) vyksta prieš build’ą – jei randama kritinių pažeidžiamumų, deployment’as nesivykdo.
Mikroservisų projektams dažnai naudoju dynamic child pipeline’us. Parent pipeline’as nustato, kurie servisai pasikeitė, ir sukuria child pipeline’us tik jiems:
generate_pipelines:
stage: prepare
script:
- python generate_pipeline.py > generated-config.yml
artifacts:
paths:
- generated-config.yml
trigger_children:
stage: execute
trigger:
include:
– artifact: generated-config.yml
job: generate_pipelines
strategy: depend
Frontend projektams su preview deployment’ais naudoju merge request pipeline’us, kurie automatiškai deploy’ina į temporary environment’ą:
deploy_preview:
stage: deploy
script:
- netlify deploy --dir=dist --alias=mr-${CI_MERGE_REQUEST_IID}
environment:
name: review/$CI_MERGE_REQUEST_IID
url: https://mr-${CI_MERGE_REQUEST_IID}--myapp.netlify.app
on_stop: cleanup_preview
only:
- merge_requests
Tai neįtikėtinai patogu code review metu – reviewers gali iš karto pamatyti pakeitimus veikiančioje aplikacijoje.
Optimization: kaip paspartinti lėtus pipeline’us
Lėti pipeline’ai – tai produktyvumo žudikas. Jei jūsų CI/CD užtrunka 30 minučių, developeriai tiesiog nustos juo naudotis arba merge’ins be laukimo rezultatų.
Pirmas dalykas – paralelizacija. Jei turite nepriklausomus testus, vykdykite juos lygiagrečiai:
test:
stage: test
script:
- npm run test -- --shard=${CI_NODE_INDEX}/${CI_NODE_TOTAL}
parallel: 5
Tai sukuria 5 job’us, kurie vykdomi vienu metu, kiekvienas testuodamas skirtingą kodo dalį.
Docker layer caching – kitas didelis laiko taupytojas. Vietoj to, kad kiekvieną kartą build’intumėte image’ą nuo nulio:
build:
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker pull $CI_REGISTRY_IMAGE:latest || true
script:
- docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Selective job execution – nevykdykite visų job’ų kiekvienam commit’ui. Naudokite rules arba only/except:
deploy_docs:
stage: deploy
script:
- ./deploy-docs.sh
rules:
- changes:
- docs/**/*
- README.md
Dar vienas trikis, kurį retai kas naudoja – interruptible jobs. Jei push’inate naują commit’ą, senasis pipeline’as gali būti nutrauktas:
test:
interruptible: true
script:
- npm test
Tai sutaupo runner’ių resursų ir pagreitina feedback loop’ą.
Monitoring ir debugging: kai kas nors eina ne taip
Pipeline’ai sugenda. Tai neišvengiama. Svarbu greitai suprasti, kas nutiko ir kaip pataisyti.
GitLab CI/CD job log’ai yra jūsų pirmasis šaltinis. Bet kartais jų nepakanka. Aš visada pridedame debug informaciją svarbiuose job’uose:
deploy:
script:
- echo "=== Environment Info ==="
- echo "Runner: $CI_RUNNER_DESCRIPTION"
- echo "Commit: $CI_COMMIT_SHORT_SHA"
- echo "Branch: $CI_COMMIT_REF_NAME"
- env | grep CI_ | sort
- echo "=== Starting Deployment ==="
- ./deploy.sh
Failed job artifacts – kai job’as failina, dažnai norite pamatyti, kas liko. Naudokite artifacts:when: on_failure:
test:
script:
- npm test
artifacts:
when: on_failure
paths:
- screenshots/
- logs/
expire_in: 1 week
Pipeline schedules puikiai tinka nightly builds arba periodic testams. Bet atsargiai su timezone’ais – GitLab naudoja UTC. Aš kartą nustatydamas 2AM schedule’ą savo laiko juostoje, netyčia sukonfigūravau jį 2PM UTC, ir pipeline’ai pradėjo vykti viduryje darbo dienos.
Dar vienas naudingas dalykas – pipeline email notifications. Galite konfigūruoti, kad gautumėte email’ą tik kai failina pipeline’as main branch’e:
notify_failure:
stage: .post
script:
- curl -X POST $SLACK_WEBHOOK -d '{"text":"Pipeline failed on main!"}'
when: on_failure
only:
- main
Aš naudoju Slack integraciją vietoj email’ų – taip visa komanda iš karto mato problemas.
Kas toliau: praktiniai patarimai ir realybės patikrinimas
Po kelių metų darbo su GitLab CI/CD, turiu keletą patarimų, kurie nėra dokumentacijoje, bet išmokti praktikoje.
Pirmiausia – dokumentuokite savo pipeline’us. Komentarai .gitlab-ci.yml faile ir README su pipeline’o architektūros aprašymu sutaupo begalę laiko naujiems komandos nariams. Aš sukuriu atskirą docs/ci-cd.md failą su pipeline’o diagrama ir paaiškinimais.
Antra – testuokite pipeline’o pakeitimus. GitLab turi CI Lint įrankį, bet jis tik patikrina sintaksę. Sukurkite test branch’ą ir išbandykite pakeitimus ten prieš merge’indami į main. Kartą aš sugedau production deployment’ą, nes nepatikrinau, kaip veikia nauja rules logika.
Trečia – naudokite extends ir anchors DRY principui. Vietoj to, kad kartotumėte tą patį konfigą:
.deploy_template: &deploy_template
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | ssh-add -
deploy_staging:
<<: *deploy_template stage: deploy script: – ssh user@staging „cd /app && git pull && restart” deploy_production: <<: *deploy_template stage: deploy script: – ssh user@prod „cd /app && git pull && restart” when: manual
Ketvirta – manual gates kritiniams deployment’ams. Production deployment’ai turėtų reikalauti rankinio patvirtinimo. Tai apsaugo nuo atsitiktinių deploy’ų ir duoda galimybę dar kartą viską patikrinti.
Penkta – monitoring po deployment’o. Jūsų pipeline’as neturėtų baigtis deployment’u. Pridėkite smoke testus, kurie patikrina, ar aplikacija tikrai veikia:
verify_deployment:
stage: .post
script:
- sleep 30 # laukiam, kol aplikacija pilnai pasileidžia
- curl -f https://myapp.com/health || exit 1
- curl -f https://myapp.com/api/status || exit 1
only:
- main
Galiausiai – reguliariai review’inkite ir refactorinkite savo pipeline’us. Tai, kas veikė prieš metus, gali būti nebeoptimalu dabar. Aš kas ketvirtį skiriu dieną CI/CD peržiūrai ir optimizavimui.
GitLab CI/CD nėra tobulas – kartais runner’iai pakimba, kartais cache’as neveikia kaip tikėtasi, kartais dokumentacija yra pasenusi. Bet tai vis tiek vienas geriausių įrankių rinkoje, ypač jei vertinate all-in-one sprendimą be išorinių dependency’ų.
Svarbiausia – pradėkite paprastai ir tobulinkite laipsniškai. Nebandykite iš karto implementuoti visų best practices. Geriau turėti veikiantį paprastą pipeline’ą nei idealų, bet nesukonfigūruotą. Automatizavimas turi padėti, o ne tapti dar viena problema, kurią reikia spręsti.

