Мазмұнға өту

Аз болса да — күн сайын.

Nx affected-пен нөлден CI: «пайплайн жоқтан» барлық staging-қолданбаларға 9 минутқа дейін

5 мин оқу

Осы бетте
Қысқаша
Ондаған қолданбасы бар монорепозиторийде әр коммитке бәрін құрастыру — ұзақ әрі мағынасыз: қолданбалардың көбін өзгеріс қозғамаған. Nx нені қозғағанын санай алады — nx affected. Қиындық командада емес, «неге қатысты қозғалған?» деген сұрақта. Төменде — скриптермен қолмен деплой жасаудан MR-ды тексеретін және тек керегін құрастыратын пайплайнға дейінгі жол, және жолда басқан төрт тырма.

Нөлдік нүкте: қолмен деплой

Алғашында пайплайн болған жоқ. Shell-скрипт болды: қолданбаны жергілікті құрастырып, файлдарды серверге көшіру. Мәселелер айқын:

  • репозиторийдегі емес, сенің дискіңде жатқан нәрсе құрастырылады;
  • деплойға дейін ешқандай тексеріс жоқ;
  • деплойды тек қолжетімділігі мен скрипті бар адам ғана жасай алады.

Алғашқы пайплайн скриптті қайталады: тәуелділіктерді орнату → барлық қолданбаларды құрастыру → көшіру. Жақсырақ, бірақ әр пушқа барлық қолданбалар құрастырылатын.

Affected идеясы, қарапайым тілмен

Nx жобаның графын біледі: қай қолданба қай кітапханаларға тәуелді. Егер users кітапханасындағы файлды өзгертсең, Nx граф бойымен «жоғары» жүріп, осы кітапхананы қолданатын барлық қолданбаларды табады. Қалғандары қозғалмаған, оларды құрастырмауға және тестілемеуге болады.

npx nx affected -t build --base=<коммит> --head=HEAD

Nx base мен head арасындағы өзгерген файлдарды алады (шын мәнінде, git diff), олардың жобаларын табады, оларға тәуелдінің бәрін қосады және build-ні тек осы жиынға іске қосады.

Бұл қарапайым графты аралау. Кітапханалар — төбелер, ал импорттар — қабырғалар болса, «қозғалғандар» — қабырғалар бойымен өзгергенге жетуге болатын барлық төбелер.

Басты сұрақ: base дегеніміз не?

Команда қарапайым, ал base-мен қателесу — оңай. Әртүрлі міндеттерге жауап әртүрлі.

MR тексеру үшін: тармақталу нүктесі

Merge request үшін база — тармақ мақсатты тармақтан бөлінген коммит (merge-base). GitLab-та бұл CI_MERGE_REQUEST_DIFF_BASE_SHA айнымалысы.

Маңызды бөлшек: әдепкі бойынша GitLab репозиторийді тарихтың шектеулі тереңдігімен клондайды. Толық тарихсыз git merge-base-ті таба алмайды, ал affected дұрыс санамайды. Сондықтан:

variables:
  GIT_DEPTH: "0"   # полная история

Staging құрастыру үшін: соңғы деплой жасалған коммит

Бірінші ой — тармақтың алдыңғы коммитімен (CI_COMMIT_BEFORE_SHA) салыстыру. Бұл қате, себебі мынада.

Бізде staging пайплайндары үзілетін (interruptible: true): жаңа пуш келсе, ескі құрастыру тоқтатылады — оны бәрібір жаңасы басып кетеді. Елестетіп көр:

  1. A коммиті X қолданбасын өзгертеді. A пайплайны басталады.
  2. Бір минуттан кейін B коммиті Y қолданбасын өзгертеді. A пайплайны тоқтатылды, B басталады.
  3. B өзін A-мен салыстырып, тек Y-ты құрастырады.

X өзгерістері бірде-бір құрастыруға кірмеді. Серверде X-тің ескі нұсқасы тұр, ал неге екенін ешкім білмейді.

Дұрыс база — серверге шынымен жеткен соңғы коммит. Оны сәтті деплойдан кейін бөлек job есте сақтайды:

# учебный пример
mark-deployed:
  stage: mark_deployed
  needs: [deploy]
  cache:
    key: deployed-sha
    paths: [.ci-cache/]
    policy: push
  script:
    - mkdir -p .ci-cache
    - echo "$CI_COMMIT_SHA" > .ci-cache/deployed-sha

Құрастыру осы файлды оқып, affected-ті содан бастап санайды. Ол тек сәтті деплойдан кейін ғана жазылады, сондықтан құлаған және тоқтатылған пайплайндардың өзгерістері келесі құрастыруға автоматты түрде кіреді.

Және міндетті түрде — қосалқы шығу жолы: бәрін құрастыру, егер:

  • база жоқ болса (алғашқы іске қосу, кэш тазаланған);
  • тарихта мұндай коммит жоқ болса (мысалы, force-push-тан кейін);
  • CI-дың өзі немесе қолданбалар тізімі өзгерсе;
  • біреу пайплайнды FULL_BUILD=true-мен іске қосса.

Бір рет керегін құрастырмағаннан гөрі, кейде артығын құрастырған жақсы.

MR тексеру: тек өзгергенін

Құрастыру — жартысы ғана. Екінші жартысы — қателерді staging-ке жібермеу. Әр MR-ға verify кезеңі, бәрі affected бойынша:

JobНе істейді
lintтек өзгерген файлдар бойынша ESLint + стильдерді тексеру
typecheckшығарусыз Angular компиляторы (ngc --noEmit): типтер мен шаблондар
testқозғалған жобалардың unit-тесттері, MR виджетіне JUnit есебі мен қамту

Үш job қатар жүреді. Линтердің ескі бұзушылықтары базлайн-файлға жазылған және кедергі келтірмейді; тек жаңасы құлатады.

Дәл осыны жергілікті бір командамен де (pnpm verify). Тексеріс тек CI-да болса, адам қате туралы он минуттан кейін біледі. Жергілікті де болса — пушқа дейін.

1-тырма. Қатар екі құрастыру — OOM

Жылдамдатқым келді: бір уақытта екі қолданбадан құрастыру (--parallel=2). Құрастырулар esbuild-тің оғаш қатесімен құлай бастады: all goroutines are asleep - deadlock!.

Қате адастырады. Шын себебі — жад. Angular-дың қатар екі құрастыруы 15,5 ГБ хостта шамамен 13 ГБ алатын. Linux ядросы (OOM killer) құрастыру процесін өлтіретін, ал ата-анасыз қалған esbuild еншілес процесі осы хабарламамен құлайтын.

Сабақ: CI-да оғаш қате болса, алдымен ресурстарды қара — жад, диск, уақыт. Шешімі — --parallel=1. Бір құрастыруға баяуырақ, бірақ affected олардың санын онсыз да азайтты.

2-тырма. Тесттерді жасырған кэш

Nx нәтижелерді кэштей алады: тапсырманың кірістері өзгермесе, ол оны іске қоспай, нәтижені кэштен (соның ішінде қашықтағы Nx Cloud-тан) қалпына келтіреді.

MR есебінде кенет 944 тесттің 237-сі ғана қалды. Тесттерді ешкім өшірмеген. Себебі: кэштен тапсырманың тек жарияланған шығыстары (outputs) қалпына келтіріледі. Есепке арналған junit.xml файлы оларға кірмеген. Кэштен келген жоба — «жасыл», бірақ есепсіз, ал GitLab оны жай көрсетпейді.

MR-дағы тесттер үшін шешім — --skip-nx-cache. Есеп үшін адал жүргізу керек. Баламасы — тапсырманың outputs ішіне junit.xml қосу.

Сабақ: кэш жылдамдатады, бірақ кейін файлдардан оқитын нәрсенің бәрі outputs ішінде болуы керек. Әйтпесе ол үнсіз жоғалады.

3-тырма. Ешкім қолданбайтынды құрастыру

Staging-тегі 17 қолданбаның 7-еуі ғана шынымен тестіленетін. Қалғандары тізімде болғандықтан ғана құрастырылатын. Деплой картасында артықтарын өшірдік — бір жол ақылды кодсыз жұмыстың жартысынан көбі азайды.

Ең жылдам құрастыру — жоқ құрастыру.

4-тырма. CI-ды веб-редактор арқылы түзеу

Пайплайнның алғашқы нұсқасын тікелей GitLab веб-редакторында түзедім: бір аптада тарихта «Update .gitlab-ci.yml file» деген жиырма бестей коммит қалды. Әрқайсысы — іске қос, күт, қара, түзе.

Басқаша не істеу керек:

  • CI-ды тармақта жазып, MR-да тексеру;
  • YAML-ды пушқа дейін GitLab-тың кірістірілген линтерімен (CI Lint) тексеру;
  • логиканы YAML-дан жергілікті іске қосып, жөндеуге болатын скрипттерге (node tools/scripts/...) шығару.

Минуттарды үнемдейтін ұсақ-түйек

  • pnpm-lock.yaml кілті бойынша pnpm сторының кэші: тәуелділіктер әр жолы қайтадан жүктелмейді.
  • Бір job кэшті жазады, қалғандары тек оқиды (policy: pull): әйтпесе қатар үш job бір нәрсені кезекпен архивтеп, жүктейді.
  • Source maps қателерді бақылау жүйесіне құрастырумен бір job-та жүктеледі және деплойдың алдында жойылады: серверге олар керек емес, артефакт кішірейеді.

Қорытынды

БұрынҚазір
скриптпен қолмен деплойәр пушқа пайплайн
тексерістер жоқәр MR-ға lint + typecheck + тесттер
барлық қолданбаларды құрастырутек соңғы деплойдан бері қозғалғандары
? минутбарлық staging-қолданбаларға ~9 минут

Түйгенім: nx affected — командаға емес, «неге қатысты?» деген сұраққа дұрыс жауапқа қатысты. Және жауап жоқ кезде «бәрін құрастыр» деген қосалқы шығу жолына.

Байланысты жазбалар: nx-monorepo-from-copies, tailwind-v3-to-v4-monorepo, custom-eslint-rules.