Tarkibga o'tish

Linux CI/CD pipeline’larida

Har bir CI/CD pipeline — GitHub Actions, GitLab CI, Jenkins — oxir-oqibat bitta narsaga borib taqaladi: kodni checkout qiladigan, shell buyruqlari ketma-ketligini ishga tushiradigan va ular 0 status bilan tugaganmi yoki yo'qmi, xabar beradigan Linux mashinasi. Pipeline’ni belgilaydigan YAML — yupqa orkestratsiya qatlami. Haqiqiy ish — bog'liqliklarni o'rnatish, testlarni ishga tushirish, image qurish, artefaktni deploy qilish — xuddi siz SSH orqali qo'lda kiradigan serverda qanday bajarilsa, shunday bajariladi. CI/CD’ni "klaviatura oldida inson yo'q Linux administratsiyasi" deb tushunish noaniq pipeline xatosini debug qilishni taxmin qilishdan Troubleshooting jarayoni maqolasida ko'rib chiqilgan o'sha tizimli jarayonga aylantiradi — chunki u aynan shunday.

Nimalarni o'rganasiz

Bu maqola oxirida siz quyidagilarni qila olasiz:

  • CI/CD runner operatsion tizim darajasida aslida nima ekanini tushuntirish;
  • pipeline’ning shell qadamlarini oddiy Bash sifatida, shu kursda oldinroq ko'rib chiqilgan exit kod intizomi bilan o'qish;
  • pipeline ichidan container image qurish va uni push qilish;
  • secret’lar va bir martalik (ephemeral) runner’larning xavfsizlik oqibatlarini tushuntirish;
  • muvaffaqiyatsiz pipeline qadamini istalgan boshqa Linux jarayoni uchun ishlatiladigan o'sha vositalar bilan diagnostika qilish.

Runner — shunchaki Linux host

CI/CD runner — GitHub Actions uni "runner" deb ataydi, GitLab ham "runner" deydi, Jenkins esa "agent" deb ataydi — Git repozitoriyni checkout qiladigan va konfiguratsiya faylida belgilangan shell buyruqlari ketma-ketligini bajaradigan mashina (container, VM yoki ba’zan bare metal). Bajarilish muhitida hech qanday sehr yo'q:

Trigger (push, PR, schedule)
   │
   ▼
Scheduler assigns a runner (a Linux VM or container)
   │
   ▼
Runner: git clone/checkout
   │
   ▼
Runner: execute each pipeline step as an ordinary shell command
   │
   ▼
Runner: report exit code of each step; non-zero halts the pipeline (by default)

Shuning uchun Exit kodlar va xatolarni qayta ishlash CI/CD uchun bevosita muhim. Buyruqning noldan farqli exit status’ini bosib qo'yadigan pipeline qadami (adashib qolgan || true yoki pipefail siz pipe dagi faqat oxirgi buyruqni tekshiradigan pipeline) asosiy buyruq muvaffaqiyatsiz tugagan bo'lsa ham, GitHub Actions yoki GitLab’ga muvaffaqiyat haqida xabar beradi. CI tizimida qadam "haqiqatan" muvaffaqiyatsiz tugaganini o'zi kuzatadigan exit koddan tashqari bilishning mustaqil usuli yo'q.

# .github/workflows/ci.yml — a minimal example
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: |
          set -euo pipefail
          pip install -r requirements.txt
          pytest

run bloki boshidagi set -euo pipefail CI’ga xos kelishuv emas. Bu Exit kodlar va xatolarni qayta ishlash maqolasidagi o'sha himoyalovchi Bash shabloni. U bu yerda muvaffaqiyatsiz pip install ishni darhol to'xtatishi uchun qo'llanilgan. Aks holda testlar yarim o'rnatilgan bog'liqliklarga qarshi ishga tushib, chalkash, aloqasiz xatoni keltirib chiqarardi.

Runner’lar (odatda) container’lar va bu muhim

Ko'pchilik hosted CI provayderlari har bir ishni (job) yangi container yoki VM ichida ishga tushiradi va ish tugagach, uni tashlab yuboradi. Buning Linux containerlari maqolasiga bevosita borib taqaladigan ikkita aniq, amaliy oqibati bor:

  • Ishga tushirishlar orasida hech qanday holat saqlanib qolmaydi, agar u aniq keshlanmasa yoki tiklanmasa. Bir ishga tushirishda qo'lda o'rnatilgan paket keyingisida yo'q bo'ladi. Shuning uchun pipeline’lar runner’ning oldindan mavjud holatiga tayanish o'rniga o'z bog'liqliklarini aniq e’lon qiladi (requirements.txt, package.json).
  • Har bir ishga tushirish bir xil ma’lum image’dan boshlanadi. CI natijalarini takrorlanadigan qiladigan narsa shu. CI’da muvaffaqiyatsiz bo'lib, "mening kompyuterimda ishlaydigan" test ko'pincha aynan container’lar boshidanoq hal qilish uchun yaratilgan bog'liqliklar siljishi (dependency drift) muammosi bilan izohlanadi.
# A typical hosted runner is already a container itself
cat /proc/1/cgroup
0::/

Hosted runner’da PID 1 dagi bo'sh yoki minimal cgroup yo'li sizga hozirgina berilgan "mashina"ning o'zi alohida VM emas, container ekanining kuchli belgisi. Pipeline qadami resurs cheklovlari atrofida kutilmagan xatti-harakat qilganda buni tekshirishga arziydi. Chunki umumiy, hajmi cheklangan runner container’i o'zida to'liq hostning CPU va xotirasi bor deb hisoblaydigan build’ni jimgina sekinlashtirishi mumkin.

Intervyuda: "runner bir martalik, unda har safar bog'liqliklarni qayta yuklab olishdan qanday qochasiz?"

Bu yangi runner modelining bevosita amaliy oqibati va javob — keshlash (caching). CI provayderi nomlangan katalogni (yoki kalitlangan arxivni) bir martalik runner’dan tashqarida saqlaydi va har bir ishga tushirish boshida, bog'liqliklarni o'rnatish qadamidan oldin uni tiklaydi. Kesh kaliti — eng muhim tafsilot. U odatda lockfile’ning (requirements.txt, package-lock.json, go.sum) hash’idan olinadi. Shunda kesh faqat bog'liqliklar o'zgarmagan paytda qayta ishlatiladi va lockfile o'zgargan zahoti avtomatik ravishda qayta quriladi:

  - name: Cache pip packages
    uses: actions/cache@v4
    with:
      path: ~/.cache/pip
      key: pip-${{ hashFiles('requirements.txt') }}

Aniq aytib o'tish kerak bo'lgan tuzoq: hech qachon o'zgarmaydigan narsaga (lockfile hash’i o'rniga statik satrga) kalitlangan kesh eskirgan bog'liqliklarni cheksiz taqdim etaveradi. Bu "CI’da ishlaydi, production’da buzilgan" turidagi xatolarni keltirib chiqaradi va ularni kuzatib borish haqiqatan qiyin, chunki pipeline yashil ko'rinadi. Kesh kaliti u keshlaydigan narsa o'zgarganda aynan o'zgarishi kerak — na ko'proq, na kamroq.

Pipeline’dan image qurish va push qilish

Keng tarqalgan pipeline bosqichi Docker va Podman: amaliy ishlash maqolasida ko'rib chiqilgan o'sha turdagi image’ni quradi, keyin uni deploy qilish uchun registry’ga push qiladi:

  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - name: Log in to registry
        run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login myregistry.example.com -u ci --password-stdin
      - name: Build and push
        run: |
          docker build -t myregistry.example.com/myapp:${{ github.sha }} .
          docker push myregistry.example.com/myapp:${{ github.sha }}

Image’ni latest kabi o'zgaruvchan teg o'rniga ${{ github.sha }} — aniq commit hash’i — bilan teglash ataylab qilingan. Bu har bir deploy qilingan image’ni u qurilgan aniq manba kodigacha kuzatib borish imkonini beradi. Production hodisasi birinchi marta "hozir aslida qaysi commit ishlayapti?" degan savolga javob berishni talab qilganda bu juda muhim bo'ladi.

docker login ... --password-stdin token’ni buyruq qatori argumenti sifatida emas, stdin’dan o'qiydi. Bu token’ning jarayonlar ro'yxatida (ps aux) yoki shell tarixida paydo bo'lishidan qochadi — SSH kalit autentifikatsiyasi maqolasida ko'rib chiqilgan parolni hech qachon ochiq CLI argumenti sifatida uzatmaslik tamoyili ham shunga asoslanadi.

Secret’lar: har qanday pipeline’dagi eng qimmatli nishon

CI pipeline odatda haqiqiy production kirish huquqiga ega credential’larni saqlaydi — registry token’lari, bulut provayderi API kalitlari, deployment SSH kalitlari. Bu esa faqat u deploy qiladigan serverlarni emas, pipeline konfiguratsiyasining o'zini ham xavfsizlik nuqtai nazaridan sezgir yuzaga aylantiradi.

Danger

Hech qachon secret’ni bevosita pipeline YAML’iga, Dockerfile ARG iga yoki repozitoriyga commit qilinadigan skriptga yozmang. Bularning har biri Git tarixida cheksiz saqlanib qoladi. Oshkor bo'lgan secret’ni oxirgi commit’dan olib tashlash uni tarixdan o'chirmaydi va oshkor bo'lgan credential shunchaki fayldan o'chirilmasdan, darhol almashtirilishi (rotate) kerak. Buning o'rniga CI provayderining maxsus secret’lar omboridan foydalaning (GitHub Actions secrets, "masked" va "protected" deb belgilangan GitLab CI/CD o'zgaruvchilari).

# Wrong: secret visible in the workflow file and in every log line that echoes $TOKEN
run: curl -H "Authorization: Bearer sk_live_abc123..." https://api.example.com

# Right: secret injected from the provider's secret store at run time, never echoed
run: curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" https://api.example.com

Ko'pchilik CI provayderlari ro'yxatdan o'tkazilgan secret qiymatini log chiqishida avtomatik ravishda yashiradi (mask qiladi) — lekin faqat aynan o'sha satrni. Chiqarilishidan oldin base64 bilan kodlangan, boshqa satrga qo'shilgan yoki boshqacha o'zgartirilgan secret maskadan baribir sizib chiqishi mumkin. Bu haqiqiy va takrorlanib turadigan nosozlik bo'lib, platformaning maskalashi xatosiz deb o'ylash o'rniga uni ataylab sinab ko'rishga arziydi.

Intervyuda: xavfsizlik uchun sezgir pipeline’larda nega bir martalik runner’lar?

Ishlar orasida saqlanib qoladigan self-hosted runner yangilanmagan server kabi xavf to'playdi. Bitta ishning build qadamidagi buzib kirilgan bog'liqlik xuddi shu mashinada ishga tushadigan keyingi ish uchun backdoor, o'zgartirilgan binary yoki o'g'irlangan credential’ni keshda qoldirishi mumkin. Agar runner umumiy bo'lsa, bu boshqa, aloqasiz pipeline’ning ishlariga ham tegishli. Bir martalik runner’lar (har bir ish uchun yangi container yoki VM, darhol keyin yo'q qilinadi) bu saqlanib qolishni butunlay yo'q qiladi. Hosted GitHub Actions va GitLab.com runner’lari standart holatda bir martalik ekanining va sezgir pipeline’lar uchun self-hosted runner parklari uzoq yashovchi, qayta ishlatiladigan mashinalar sifatida qoldirilmasdan, ko'pincha xuddi shunday sozlanishining sababi aynan shu.

Muvaffaqiyatsiz pipeline’ni diagnostika qilish

Muvaffaqiyatsiz pipeline qadami har qanday Linux jarayoni nosozligi kabi debug qilinadi, chunki u aynan shunday jarayon:

  1. Faqat "failed" holatini emas, haqiqiy exit kod va chiqishni o'qing. Muvaffaqiyatsiz qadamning to'liq log'i deyarli har doim noldan farqli kod qaytargan haqiqiy asosiy buyruqni aytib turadi.
  2. Mahalliy qayta yarating. Runner ma’lum, ko'pincha ochiq hujjatlashtirilgan image bo'lgani uchun (masalan, GitHub Actions’dagi ubuntu-latest ning e’lon qilingan, versiyalangan paketlar ro'yxati bor), xuddi shu qadamlarni odatda CI navbatini kutmasdan, xatoni qayta yaratish uchun ekvivalent mahalliy container ichida ishga tushirish mumkin:
docker run --rm -it -v "$PWD:/workspace" -w /workspace ubuntu:24.04 bash
# then run the same commands the pipeline runs, one at a time
  1. Faqat koddagi farqlarni emas, muhitdagi farqlarni ham tekshiring. Mahalliy o'rnatilgan vositaga, vaqt zonasi haqidagi taxminga yoki qattiq yozilgan localhost yo'liga bog'liq test ko'pincha dasturchi mashinasida o'tadi va toza runner’da muvaffaqiyatsiz bo'ladi — aynan runner’da o'sha yashirin mahalliy holatning hech biri yo'qligi sababli.

Keng tarqalgan xatolar

  • "Pipeline yashil bo'lishi uchun" qadamning haqiqiy exit kodini || true bilan bosib qo'yish. Bu asosiy nosozlikni tuzatmaydi — uni yashiradi. Pipeline’ga keyingi tegadigan odam yashirin nosozlik boshqa joyda zarar yetkazmaguncha biror narsa noto'g'ri ekani haqida hech qanday signal olmaydi.
  • Pipeline’ni sinash uchun secret’ni "vaqtincha" commit qilish. Yuqorida ko'rib chiqilganidek, Git tarixi uni doimiy saqlab qoladi. Har qanday tasodifiy commit’dan keyin to'g'ri tuzatish — keyingi commit’da qatorni olib tashlash emas, credential’ni almashtirish.
  • Hosted runner shaxsiy mashina bilan bir xil resurs yoki holatga ega deb o'ylash. Hosted runner’lar odatda dasturchining mahalliy o'rnatilgan vositalari, keshlangan paketlari yoki muhit o'zgaruvchilaridan birortasi ham bo'lmagan, resurslari cheklangan container’lar yoki VM’lar.
  • Har bir qurilgan image’ni latest deb teglash. Bu hozir production’da ishlab turgan image’ni aynan qaysi commit hosil qilganini keyinchalik bilishni imkonsiz qiladi. O'zgarmas, commit’dan olingan teg bu muammoni bevosita hal qiladi.

Mashqlar

  1. Bu kurs uchun allaqachon yozgan Bash skriptingizni oling (backup skripti yoki healthcheck skripti yaxshi mos keladi) va uni har bir push’da ishga tushiradigan minimal GitHub Actions workflow’iga o'rang. Ataylab xato kiritib, workflow muvaffaqiyatsizlik haqida to'g'ri xabar berishini tasdiqlang.
  2. docker login --password-stdin nega xuddi shu buyruq qatorida parolni -p bilan uzatishdan xavfsizroq ekanini har bir holatda ps aux nimani ko'rsatishiga tayanib, o'z so'zlaringiz bilan tushuntiring.
  3. "Secret uch commit oldin ochiq repozitoriyga tasodifan commit qilingan" holati uchun qadamlarni ular bajarilishi kerak bo'lgan tartibda sanab o'tadigan bir paragraflik hodisaga javob berish rejasini yozing.
  4. CI provayderingiz hujjatlarda ishlatishini ko'rsatgan o'sha bazaviy image’ni (masalan, ubuntu:24.04) yuklab oling va pipeline’ingizning shell qadamlarini uning ichida docker run -it bilan qo'lda ishga tushiring. O'z mashinangizdagidan boshqacha ishlaydigan har qanday buyruqni yozib qo'ying va nega shunday ekanini tushuntiring.

Xulosa

CI/CD runner — shell buyruqlarini ketma-ket bajaradigan va har birining exit kodi haqida xabar beradigan oddiy Linux host, odatda o'zi ham container. Pipeline YAML’i esa shu kurs davomida ko'rib chiqilgan o'sha shell scripting, exit kod intizomi va container vositalari ustidagi orkestratsiya. Bir martalik, tashlab yuboriladigan runner’lar va provayder boshqaradigan secret omborlari mavjud, chunki doimiy build mashinasi va qattiq yozilgan credential yangilanmagan, haddan tashqari imtiyozli server bilan aynan bir xil xavf. Yumshatish choralari ham bir xil, faqat ular production infratuzilmasiga emas, build infratuzilmasiga qo'llaniladi. Linux bulutda (cloud) maqolasi shu yerdan davom etadi, chunki bunday pipeline hosil qilgan artefakt deyarli har doim o'sha maqolada tasvirlanganidek tayyorlangan bulut instansiyasida ishlaydi.

Manbalar