یک Pipeline سبز فقط وقتی ارزش دارد که بدانیم دقیقاً چه چیزی را، روی کدام Commit و Toolchain، با چه Testهایی سنجیده و کدام Artifact را آماده تحویل کرده است. سه مرحلهی checkout → npm install → npm test ممکن است Demo را اجرا کند، اما هنوز lockfile، Test Report، timeout، runner trust، Artifact identity، cancellation و Deployment gate ندارد. در این آموزش، یک Contract واحد را هم با Jenkinsfile و هم با GitLab CI پیاده میکنیم تا تفاوت ابزار، معنی Quality Gate را عوض نکند.
نمونه برای یک پروژه Node.js نوشته شده، ولی الگو به Java، Python و .NET قابل انتقال است: نصب deterministic، فرمانهای CLI غیرتعاملی، JUnit، Artifact immutable، Evidence و exit code. برای معماری Fast/Slow/Event-driven در مقیاس بزرگتر، مقاله تست مداوم در CI/CD مکمل این آموزش قدمبهقدم است.
پاسخ کوتاه: Pipeline پایه باید روی Merge Request و شاخه اصلی اجرا شود، SHA و runtime را ثبت کند، dependency را از lockfile نصب کند، Lint/Unit/Build/Integration را با exit code واقعی اجرا کند، JUnit و Artifact را حتی در Failure قابل مشاهده سازد، Build را فقط یکبار بسازد و همان checksum را به Stage بعد بدهد. Jenkins Controller یا GitLab Runner نباید Secret پرارزش را به کد Pull Request غیرقابل اعتماد بدهد.
CI، Continuous Delivery و Continuous Deployment چه تفاوتی دارند؟
Continuous Integration یا CI
CI یک عمل تیمی است: تغییرهای کوچک و مکرر در خط اصلی ادغام میشوند و هر تغییر با Build/Test خودکار و بازخورد قابل اقدام سنجیده میشود. ابزار CI فقط اجرای Job نیست؛ قرارداد ادغام، branch policy، ownership شکست و زمان تعمیر نیز بخشی از سیستماند.
Continuous Delivery
در Continuous Delivery، هر تغییرِ عبورکرده از Gateها در وضعیت قابل انتشار نگه داشته میشود. Artifact، configuration، migration و روش rollback آمادهاند؛ تصمیم Production میتواند دستی و وابسته به Risk/Business باشد.
Continuous Deployment
در Continuous Deployment، تغییر واجد شرایط بدون approval انسانی تا Production پیش میرود. این انتخاب به test evidence، observability، progressive delivery، rollback و تحمل ریسک نیاز دارد. حذف دکمه approval بهتنهایی Continuous Deployment بالغ نمیسازد.
این آموزش تا کجا میرود؟
Pipeline این مقاله CI کامل و یک Gate دستی Staging برای نشاندادن مرز Delivery دارد. Production deployment عمداً نسخه عمومی ندارد؛ credential، topology، change policy و rollback هر سازمان متفاوت است. هدف این است که Artifact نامزد انتشار قابل شناسایی و تستشده باشد.
Contract مشترک Jenkins و GitLab CI
پیش از نوشتن YAML یا Groovy، خروجی هر Stage را تعریف کنید:
| Stage | ورودی | Gate موفقیت | Evidence/خروجی |
|---|---|---|---|
| Checkout | SCM event | SHA دقیق و workspace تمیز | Commit/branch/source |
| Runtime | Runner + Image | Node/npm و platform مورد انتظار | Version و Image digest |
| Dependencies | package-lock.json |
npm ci موفق |
Install log؛ cache فقط شتابدهنده |
| Lint | Source | صفر violation در Rule profile مصوب | Exit code/report |
| Unit | Source + dependency | Test command غیرصفر نباشد | JUnit و در صورت نیاز coverage |
| Build | همان SHA | Build reproducible و موفق | dist/ و Artifact checksum |
| Integration | Build output + محیط کنترلشده | Contract/Journey منتخب Pass | JUnit، log و environment identity |
| Staging | همان Artifact | شاخه/approval/credential مجاز | Deployment record و environment URL |
Result state فقط Pass/Fail نیست
- Passed: فرمان و Oracleهای تعریفشده موفقاند؛
- Failed: Evidence به نقض رفتار محصول یا Quality Gate اشاره دارد؛
- Blocked: پیششرط مانند dependency یا environment مهیا نیست؛
- Infrastructure failure: Runner، network، registry یا CI service شکست خورده؛
- Cancelled/Superseded: Run قدیمی با Commit جدید بیارزش شده؛
- Unstable/Quarantined: سیگنال تست نامطمئن است و owner/expiry دارد.
مدیریت attempt و status را با چرخه اجرای تست هماهنگ کنید؛ Pipeline UI نباید Failure تست و Failure زیرساخت را در یک سطل مبهم بریزد.
Jenkins یا GitLab CI؛ انتخاب براساس Operating Model
| معیار | Jenkins | GitLab CI/CD |
|---|---|---|
| Control plane | Controller مستقل با Plugin/Agent که تیم مدیریت میکند | یکپارچه با GitLab؛ SaaS یا Self-Managed |
| Pipeline as Code | Jenkinsfile، Declarative یا Scripted |
.gitlab-ci.yml |
| Runner model | Static/Cloud Agent با Label | Instance/Group/Project Runner با Tag |
| Extension | Plugin ecosystem بزرگ؛ upgrade/test سازگاری لازم | Keywordها و integration پلتفرم؛ upgrade نسخه GitLab/Runner لازم |
| SCM/MR UX | به Branch Source/Plugin و پیکربندی بستگی دارد | Pipeline/MR/Report یکپارچه |
| Isolation ownership | تیم Jenkins مسئول Controller/Agent/Plugin است | در Self-Managed تیم؛ در SaaS مسئولیت مشترک با provider |
| مناسبتر وقتی… | زیرساخت متنوع، legacy و integration سفارشی دارید | کد و workflow از قبل در GitLab متمرکز است |
«Jenkins انعطافپذیرتر» یا «GitLab سادهتر» بدون context تصمیم کافی نیست. با یک Pipeline یکسان، زمان Setup، patch ownership، runner isolation، secret model، artifact retention، queue time، export و هزینه را PoC کنید. چارچوب Pilot و Scale در استراتژی اتوماسیون تست قابل استفاده است.
پیشنیازهای Repository
فرمانها باید محلی و در CI یکسان باشند
CI نباید منطق Test را داخل دهها خط YAML/Groovy پنهان کند. Repository یک interface CLI کوچک داشته باشد:
{
"scripts": {
"lint": "eslint .",
"test:unit": "jest --selectProjects unit",
"build": "tsc -p tsconfig.build.json",
"test:integration": "jest --selectProjects integration"
}
}
نام Projectهای Jest نمونه است و باید با config پروژه شما تطبیق داده شود. Reporter jest-junit نیز باید در devDependencies و lockfile باشد؛ نصب ad-hoc آن داخل Pipeline، نسخه اجرا را نامعلوم میکند.
Lockfile باید authoritative باشد
package-lock.jsonهمراه تغییر dependency Commit شود؛npm ciدر صورت اختلافpackage.jsonو lockfile Fail میکند و lockfile را بازنویسی نمیکند؛node_modulesمیان Runnerها Artifact نشود؛ OS/architecture/native module و symlink میتواند فرق کند؛- cache فقط package download را شتاب دهد و correctness به آن وابسته نباشد؛
- dependency update یک Pull Request با Test و review باشد.
Runtime پشتیبانیشده و pinشده
در زمان بازبینی این مقاله، صفحه رسمی انتشارهای Node.js نسخه ۲۴ را LTS و Node ۱۶ نمونه قدیمی مقاله را EOL نشان میدهد. قبل از استفاده، وضعیت فعلی را دوباره بررسی کنید. در Pipeline، tag خوانا را همراه digest مورد تأیید registry سازمان pin کنید؛ placeholder زیر باید با manifest digest واقعی platform Runner جایگزین شود.
ساختار فایل پیشنهادی
.
├── Jenkinsfile
├── .gitlab-ci.yml
├── package.json
├── package-lock.json
├── src/
├── tests/
│ ├── unit/
│ └── integration/
├── ci/
│ └── deploy-staging.sh
└── reports/ # generated; ignored by Git
reports/، artifacts/، dist/ و node_modules/ معمولاً generated هستند و در Git Commit نمیشوند.
راهاندازی Jenkins برای Pipeline تست خودکار
Controller و Agent
Jenkins Controller باید Job scheduling/configuration را مدیریت کند، نه اجرای Build غیرقابل اعتماد را. مستندات Controller Isolation توصیه میکند executor مربوط به built-in node صفر باشد و Build روی Agent جدا اجرا شود. برای نمونه ما Agent با Label linux-container، Docker runtime و workspace کوتاهعمر لازم است.
Pluginهای حداقلی
- Pipeline و Declarative Pipeline؛
- SCM/Branch Source متناسب با Git provider؛
- Docker Pipeline برای Agent کانتینری؛
- JUnit برای Test Report؛
- Credentials Binding فقط در Stage نیازمند Secret.
Plugin «پیشنهادی» را کورکورانه روی Production Controller نصب نکنید. inventory، version pin/upgrade window، backup و تست سازگاری داشته باشید.
Multibranch Pipeline و Webhook
- یک Multibranch Pipeline یا Organization Folder بسازید؛
- SCM credential فقط برای discovery/checkout با کمترین scope تعریف کنید؛
- Script path را
Jenkinsfileبگذارید؛ - Webhook provider را فعال و polling دورهای را fallback محدود نگه دارید؛
- Branch/PR discovery و trust policy forkها را صریح تنظیم کنید.
URL repository را در Jenkinsfile hard-code نکنید؛ checkout scm همان revision انتخابشده توسط Multibranch job و credential تنظیمشده را میگیرد.
Jenkinsfile اجرایی برای Node.js
pipeline {
agent {
docker {
image 'node:24.18.0-bookworm@sha256:<approved-node-digest>'
label 'linux-container'
args '--user 1000:1000'
}
}
options {
skipDefaultCheckout(true)
timestamps()
timeout(time: 20, unit: 'MINUTES')
disableConcurrentBuilds(abortPrevious: true)
buildDiscarder(
logRotator(
numToKeepStr: '30',
artifactNumToKeepStr: '10'
)
)
}
environment {
CI = 'true'
NPM_CONFIG_CACHE = '.npm'
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git rev-parse HEAD'
sh 'test -f package-lock.json'
}
}
stage('Runtime') {
steps {
sh 'node --version'
sh 'npm --version'
sh 'uname -a'
}
}
stage('Dependencies') {
steps {
sh 'npm ci --cache .npm --prefer-offline'
}
}
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Unit') {
environment {
JEST_JUNIT_OUTPUT_DIR = 'reports/unit'
JEST_JUNIT_OUTPUT_NAME = 'junit.xml'
}
steps {
sh 'mkdir -p reports/unit'
sh '''
npm run test:unit -- \
--ci \
--runInBand \
--reporters=default \
--reporters=jest-junit
'''
}
post {
always {
junit(
allowEmptyResults: false,
testResults: 'reports/unit/junit.xml'
)
}
}
}
stage('Build') {
steps {
sh 'npm run build'
sh '''
mkdir -p artifacts
tar -czf artifacts/app.tgz \
dist package.json package-lock.json
sha256sum artifacts/app.tgz \
> artifacts/app.tgz.sha256
'''
}
}
stage('Integration') {
environment {
JEST_JUNIT_OUTPUT_DIR = 'reports/integration'
JEST_JUNIT_OUTPUT_NAME = 'junit.xml'
}
steps {
sh 'mkdir -p reports/integration'
sh '''
npm run test:integration -- \
--ci \
--runInBand \
--reporters=default \
--reporters=jest-junit
'''
}
post {
always {
junit(
allowEmptyResults: false,
testResults: 'reports/integration/junit.xml'
)
}
}
}
}
post {
always {
archiveArtifacts(
allowEmptyArchive: true,
artifacts: 'artifacts/**/*,reports/**/*',
fingerprint: true
)
}
cleanup {
deleteDir()
}
}
}
این Jenkinsfile چه چیزهایی را اصلاح میکند؟
- محیط اجرای شناور
agent anyبا Label و Image مشخص جایگزین شده است؛ - Checkout همان SCM revision است و Repository URL دوباره تعریف نمیشود؛
npm cilockfile را enforce میکند؛- Unit و Integration JUnit مستقل دارند؛
- Build فقط یک بار تولید و checksum آن ثبت میشود؛
- timeout و superseded run از مصرف بیپایان Agent جلوگیری میکند؛
- retention Artifact/Build صریح است؛
- Artifact نامزد Release archive و fingerprint میشود؛
- workspace در پایان پاک میشود.
چرا Deploy در همین Jenkinsfile نیست؟ این نمونه یک Docker Agent سراسری دارد. قرار دادن input در انتهای آن، Agent را تا زمان تصمیم انسانی اشغال و timeout بیستدقیقهای CI را با زمان approval مخلوط میکند. برای Staging یک Promotion Pipeline جدا بسازید که Artifact همین Build را با build ID و checksum دریافت کند، input را بدون نگهداشتن Build Agent منتظر بماند، سپس روی Agent محدود staging-deployer همان Artifact را Deploy کند؛ هرگز آن را دوباره Build نکند.
JUnit در Jenkins
مرحله post { always { ... } } حتی پس از Failure تست تلاش میکند Report را publish کند. مرجع رسمی JUnit Plugin میگوید Jenkins از XML برای trend و نمایش Failure استفاده میکند. allowEmptyResults: false عمدی است: نبود فایل Report وقتی Test باید اجرا شده باشد، نقض Contract است؛ آن را Pass نکنید.
Pipeline syntax را با نسخه Jenkins خود Validate کنید
Declarative Directive و optionها با Plugin/Core نسخهدارند. مرجع Pipeline Syntax و Snippet Generator همان Controller معیار نهاییاند. Jenkinsfile را در Pull Request review و در Controller غیرProduction یا validation job آزمایش کنید.
راهاندازی GitLab Runner
Runner باید با Job match و با Trust هماهنگ باشد
- Project/Group Runner کوتاهعمر یا isolate انتخاب کنید؛
- Tag مانند
linux-containerرا روی Runner و Job یکسان بگذارید؛ - برای Stageهای دارای Secret/Deploy از Protected Runner استفاده کنید؛
- Runner را از production network و credential غیرلازم جدا کنید؛
- concurrency، CPU/memory/disk، cache و cleanup را محدود کنید.
مستندات پیکربندی GitLab Runner نقش Tag و Protected Runner را توضیح میدهد. Protected بودن Runner بهتنهایی کافی نیست؛ Branch/Tag protection، variable scope و ruleهای Job نیز باید همسو باشند.
Repository variable و Secret
نمونه CI تست به Secret نیاز ندارد. برای Deploy، token/SSH key در GitLab CI/CD Variable یا Secret provider تعریف، masked/protected و environment-scoped شود. هیچ value واقعی داخل .gitlab-ci.yml یا command echo نشود.
.gitlab-ci.yml اجرایی و معادل Jenkinsfile
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: '$CI_COMMIT_TAG'
- when: never
default:
image: "node:24.18.0-bookworm@sha256:<approved-node-digest>"
tags:
- linux-container
interruptible: true
retry:
max: 1
when:
- runner_system_failure
- stuck_or_timeout_failure
before_script:
- node --version
- npm --version
- test -f package-lock.json
- npm ci --cache .npm --prefer-offline
variables:
CI: "true"
NPM_CONFIG_CACHE: ".npm"
cache:
key:
files:
- package-lock.json
paths:
- .npm/
policy: pull-push
stages:
- verify
- test
- build
- integration
- deploy
lint:
stage: verify
timeout: 10m
script:
- npm run lint
unit:
stage: test
timeout: 15m
variables:
JEST_JUNIT_OUTPUT_DIR: "reports/unit"
JEST_JUNIT_OUTPUT_NAME: "junit.xml"
script:
- mkdir -p reports/unit
- >-
npm run test:unit --
--ci
--runInBand
--reporters=default
--reporters=jest-junit
artifacts:
when: always
expire_in: 7 days
paths:
- reports/unit/
reports:
junit: reports/unit/junit.xml
build:
stage: build
timeout: 15m
script:
- npm run build
- mkdir -p artifacts
- tar -czf artifacts/app.tgz dist package.json package-lock.json
- sha256sum artifacts/app.tgz > artifacts/app.tgz.sha256
artifacts:
name: "app-$CI_COMMIT_SHA"
expire_in: 7 days
paths:
- dist/
- artifacts/app.tgz
- artifacts/app.tgz.sha256
integration:
stage: integration
timeout: 20m
needs:
- job: build
artifacts: true
variables:
JEST_JUNIT_OUTPUT_DIR: "reports/integration"
JEST_JUNIT_OUTPUT_NAME: "junit.xml"
script:
- mkdir -p reports/integration
- >-
npm run test:integration --
--ci
--runInBand
--reporters=default
--reporters=jest-junit
artifacts:
when: always
expire_in: 7 days
paths:
- reports/integration/
reports:
junit: reports/integration/junit.xml
deploy_staging:
stage: deploy
timeout: 20m
interruptible: false
resource_group: staging
needs:
- job: build
artifacts: true
- job: integration
artifacts: false
before_script: []
script:
- ./ci/deploy-staging.sh artifacts/app.tgz
environment:
name: staging
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
- when: never
allow_failure: false
نکات مهم YAML
workflow: rulesPipelineهای MR، شاخه اصلی و Tag را میسازد و بقیه را حذف میکند؛ با workflow واقعی تیم تطبیق دهید.interruptible: trueValidation قدیمی را قابل لغو میکند؛ Deploy عمداً false است.- Retry فقط failureهای Runner/timeout انتخابی را پوشش میدهد، نه
script_failureیا Test Failure. - Cache با hash lockfile package tarballها را شتاب میدهد؛ correctness به cache وابسته نیست.
- Artifact Build از
dist/و tar/checksum تشکیل میشود؛node_modulesArtifact نیست. needsIntegration را به Build Artifact دقیق متصل و Deploy را تا عبور Integration متوقف میکند.resource_group: stagingدو Deployment همزمان به یک Environment را serialize میکند.
قواعد فعلی GitLab را از مرجع بخوانید
Keywordهای workflow، rules، needs، interruptible، retry و resource_group در مرجع رسمی GitLab CI/CD YAML تعریف شدهاند. فایل را با CI Lint همان GitLab instance اعتبارسنجی کنید؛ feature availability و syntax ممکن است با نسخه Self-Managed فرق داشته باشد.
نگاشت دقیق Jenkins و GitLab CI
| Contract | Jenkins | GitLab |
|---|---|---|
| Revision | checkout scm |
checkout خودکار Runner برای SHA Job |
| Runtime | Docker Agent image + label | image + runner tag |
| Timeout | options timeout |
timeout |
| Superseded run | disableConcurrentBuilds(abortPrevious) |
interruptible + auto-cancel policy |
| Test report | junit |
artifacts:reports:junit |
| Build artifact | archiveArtifacts + fingerprint |
artifacts:paths + checksum |
| Dependency edge | Stage ordering | needs |
| Manual approval | input |
when: manual |
| Deploy serialization | Lock/Job policy در setup سازمان | resource_group |
| Retention | buildDiscarder |
expire_in و instance policy |
نام Directiveها متفاوت است، اما Risk contract نباید متفاوت شود. مهاجرت ابزار وقتی ساده است که Stageها به commandهای Repository و Artifact استاندارد متکی باشند.
JUnit، exit code و Artifact؛ سه مفهوم متفاوت
JUnit Report نتیجه Job را تعیین نمیکند
GitLab در مستندات Unit Test Reports توضیح میدهد که JUnit برای نمایش Result است و بهتنهایی Job را Fail نمیکند؛ command تست باید exit code غیرصفر بدهد. در Jenkins نیز Report publication نباید با || true Test Failure را سبز کند مگر عمداً Result را بعداً مدیریت کنید.
Report همیشه، Artifact بهاندازه نیاز
- JUnit حتی در Failure upload شود؛
- Screenshot/log/trace شکست با redaction و retention محدود نگهداری شود؛
- Build Artifact فقط پس از Build موفق ایجاد شود؛
- Secret،
.env، core dump یا workspace کامل archive نشود؛ - نام Artifact شامل SHA یا build identity باشد؛
- checksum و در مسیر بالغتر provenance/SBOM ثبت شود.
Cache Artifact نیست
مرجع GitLab CI Cache Cache را برای dependencyهای قابل دانلود و Artifact را برای خروجی Job و انتقال میان Stageها جدا میکند. Cache ممکن است missing، stale یا evicted شود؛ Pipeline درست باید بدون Cache هم نتیجه یکسان، فقط کندتر، داشته باشد.
ساخت یکباره و Promotion
Build once
اگر Staging یا Production دوباره npm run build بزند، Artifact منتشرشده دقیقاً همان Artifact تستشده نیست. Pipeline نمونه dist را یکبار میسازد، Integration را پس از Build اجرا میکند و tar/checksum را به Deploy میدهد.
چه چیزی هنوز کم است؟
- برای Container deployment، Image digest جای tar را میگیرد؛
- برای package registry، version immutable و checksum/provenance لازم است؛
- Migration باید forward/backward compatibility و owner داشته باشد؛
- Staging health/smoke و Deployment record پس از deploy لازم است؛
- rollback باید با Artifact قبلی تمرین شود.
محیط Integration میتواند با Docker Compose کوتاهعمر ساخته شود؛ الگوی health/migration/evidence/teardown در داکر برای محیط تست آمده است.
طراحی Test Lane در Pipeline
Pre-merge سریع
- format/lint/type/static؛
- Unit و Component؛
- Integrationهای باریک و deterministic؛
- Contractهای Consumer/Provider ضروری؛
- Build و package candidate.
Post-merge
- Integration matrix گستردهتر؛
- migration و backward compatibility؛
- Security/SCA/Image/IaC scan؛
- System Journeyهای منتخب؛
- environment smoke.
Scheduled/Release
- Performance/Soak/Resilience با محیط و stop rule؛
- device/browser/locale matrix؛
- dependency واقعی یا Sandbox پرهزینه؛
- Recovery و rollback rehearsal.
همه Testها را در Merge Request نریزید. Quality Gate باید Risk class، duration budget و ownership داشته باشد. Static/SAST/SCA/Secret/IaC boundaries را در تحلیل استاتیک برای QA و Contract gate را در تست قراردادی Pact ببینید.
Security و Trust؛ کد Pull Request فرمان اجرا میکند
Threat model ساده
هر نویسندهای که بتواند Test، build script، dependency یا Pipeline file را تغییر دهد، میتواند روی Runner command اجرا کند. Mask کردن Secret در Log مانع exfiltration عمدی نیست. پس Untrusted MR باید بدون production credential و روی Runner isolate اجرا شود.
Jenkins credential boundary
- Credential در پایینترین Folder/Item scope تعریف شود؛
- Controller و Agent filesystem جدا باشند؛
- PR از fork به credential Deploy دسترسی نداشته باشد؛
withCredentialsفقط اطراف command لازم و دور از code غیرقابل اعتماد استفاده شود؛- Groovy interpolation و shell tracing Secret را افشا نکند؛
- Controller/Plugin patch و backup/restore آزمایش شود.
راهنمای رسمی Using a Jenkinsfile هم Pipeline-as-Code، post handling و credential binding را پوشش میدهد و هشدار میدهد code دارای دسترسی میتواند Secret را افشا کند.
GitLab boundary
- Protected Variable فقط به protected ref/job مجاز برسد؛
- Deploy روی Protected Runner و environment-scoped credential اجرا شود؛
- MR fork بدون Secret validate شود؛
- Runner shared برای network داخلی/Production route استفاده نشود؛
- Image، include و template خارجی با ref immutable pin شوند؛
- job token permission و retention Artifact حداقلی باشد.
Webhook، Rule و Duplicate Pipeline
Webhook بهجای Poll مداوم
Webhook برای Push/MR تغییر را فوری اعلام میکند و polling بیدلیل را کم میکند. با این حال delivery failure، signature validation و retry provider را monitor کنید. Polling محدود میتواند fallback باشد، نه تنها trigger.
MR Pipeline و Branch Pipeline را دوبار نسازید
Ruleهای GitLab نمونه فقط MR، default branch و Tag را میپذیرد. در Jenkins، Multibranch discovery باید PR و Branch strategy را طوری تنظیم کند که یک Commit دو Job هممعنا نسازد. Duplicate Pipeline هزینه، queue و statusهای متناقض میسازد.
Tag قابل اعتماد نیست مگر protected
وجود Git tag بهتنهایی مجوز Deploy نیست. Protected tag/branch، signer/review policy و Artifact checksum لازم است. Pipeline source و actor را در Deployment record ثبت کنید.
Flaky Test، Retry و Quarantine
Retry فقط Infra failure محدود
GitLab نمونه فقط runner_system_failure و timeout زیرساختی منتخب را یک بار Retry میکند. Test Failure با script_failure نباید خودکار سبز شود. در Jenkins نیز Retry باید دور operation واقعاً idempotent و با حفظ attempt نخست باشد.
Passed-on-retry Pass عادی نیست
- Failure اول و Artifact آن نگهداری شود؛
- Test به Flaky candidate با owner تبدیل شود؛
- علت Product/Test/Data/Environment/Dependency تفکیک شود؛
- Quarantine scope، expiry و Risk impact داشته باشد؛
- Quarantined Test از denominator پوشش ادعایی پنهان نشود.
Fail-fast با Evidence
Fail-fast feedback را کوتاه میکند، اما Report Stageهای اجراشده باید publish شود. برای Parallel Test sharding، همه shardها JUnit یکتا و merge بدون duplicate name داشته باشند؛ یک shard گمشده نباید بهعنوان صفر Failure تفسیر شود.
Environment و Test Data در CI
Manifest حداقلی Run
run_id: pipeline-1842
pipeline_source: merge_request
commit_sha: a1b2c3d
runner_id: linux-container-07
runner_platform: linux/amd64
runtime_image: node@sha256:<digest>
node_version: "24.x"
lockfile_sha256: "<sha256>"
artifact_sha256: "<sha256>"
environment: integration
data_seed: checkout-v4
dependencies:
payment: fake-v3
sms: stub-v2
Secret value در Manifest قرار نمیگیرد. Environment health، booking/TTL و dependency fidelity را با مدیریت محیط تست هماهنگ کنید.
Data namespace
هر Pipeline/Worker باید user/order/topic/schema prefix یکتا داشته باشد. Cleanup idempotent و TTL fallback لازم است. حساب مشترک و داده Production واقعی هم Parallel را خراب میکند و هم privacy risk دارد.
نکات ویژه تیمهای ایرانی
Registry و Package دسترسپذیر
اختلال npm، Docker registry، Jenkins update center یا GitLab dependency نباید Failure محصول گزارش شود. package cache/registry سازمانی مجاز، lockfile، Image digest و Artifact retention داشته باشید؛ hash و provenance را verify کنید. fallback باید قانونی، امنیتی و مستند باشد.
Runner location و Data residency
- Runner SaaS را به Production network یا داده مشتری وصل نکنید بدون risk review؛
- Self-hosted Runner patch، isolation، disk cleanup و access logging میخواهد؛
- Artifact شامل log فارسی، شماره موبایل، آدرس یا token را redact و زماندار کنید؛
- timezone را
Asia/Tehranو clock source را ثبت کنید؛ - payment/SMS/Push را با Fake/Sandbox نسخهدار اجرا کنید.
هزینه را فقط دقیقه Runner نبینید
هزینه شامل queue، نگهداری Controller/Runner، Plugin/upgrade، cache/storage/egress، debugging، secret management و downtime است. PoC Jenkins/GitLab را با workload واقعی و TCO مقایسه کنید.
عیبیابی Pipeline
| نشانه | علت محتمل | بررسی بعدی |
|---|---|---|
| Local سبز، CI قرمز | Node/OS/arch/env/data متفاوت | Manifest، Image digest، lockfile و command دقیق را diff کنید. |
npm ci Fail |
lockfile با package.json همگام نیست | Dependency change را محلی resolve و هر دو فایل را Commit کنید. |
| JUnit نمایش داده نمیشود | Reporter نصب/مسیر/نام XML غلط | وجود فایل، syntax XML و Artifact path را قبل از upload بررسی کنید. |
| Job سبز ولی Test Fail نشان میدهد | command exit code صفر یا || true |
Test runner و wrapper script را اصلاح کنید. |
| Job قرمز و Report نیست | Test پیش از ساخت Report crash کرده | stdout/stderr و setup را حفظ؛ نبود Report را Contract failure بدانید. |
| GitLab Job در Stuck است | Runner Tag/Protected/online mismatch | همه Tagهای Job و Runner و ref protection را تطبیق دهید. |
| Jenkins روی Controller اجرا میکند | built-in executor یا Label اشتباه | executor صفر، Agent label و scheduling log را بررسی کنید. |
| دو Pipeline برای یک SHA | Push و MR rule/discovery همپوشان | workflow/discovery را بازطراحی و source را ثبت کنید. |
| Artifact Deploy با Test یکی نیست | Rebuild در Deploy یا Artifact overwrite | checksum/digest را میان Build/Test/Deploy مقایسه کنید. |
| فقط در Parallel Fail | Data/port/account/cache/workspace مشترک | namespace Worker و resource ownership را audit کنید. |
| Secret در Log | echo، debug، dump env یا code مخرب | rotate فوری، revoke، redact و trust boundary را اصلاح کنید. |
| Pipeline بهتدریج کند شده | queue، cache miss، test growth یا flaky retry | queue و stage p50/p95 و critical path را جدا تحلیل کنید. |
اولین خطای معنادار را پیدا کنید
آخرین پیام معمولاً «job failed» است. به نخستین Stage و command غیرصفر، JUnit failure، Runner system message و تغییر Manifest نگاه کنید. Console log را با timestamp و Stage boundary نگه دارید؛ log بیانتها evidence بهتر نیست.
Anti-patternهای CI/CD برای تست
agent anyیا Runner ناشناخته: Toolchain و trust تصادفی میشود.- Node EOL: runtime بدون patch امنیتی و سازگاری package شکننده است.
npm installدر CI: lockfile contract میتواند جابهجا شود.- Artifact کردن
node_modules: cache/dependency با build output قاطی میشود. - فقط Console log: Test history و MR summary وجود ندارد.
|| trueروی Test: Failure به Green تبدیل میشود.- Retry همه Failureها: Flake و defect پنهان میشود.
- Secret در همه Jobها: code غیرقابل اعتماد سطح دسترسی غیرضروری میگیرد.
- Rebuild در Deploy: Artifact تستشده منتشر نمیشود.
- همه Testها در MR: feedback کند و queue طولانی میشود.
- Polling تهاجمی: latency و بار بیدلیل به SCM/Controller میدهد.
- Latest image: تغییر محیط بدون Commit رخ میدهد.
- Artifact بدون retention/redaction: storage و privacy risk رشد میکند.
- Quality Gate با درصدهای جهانی: context ریسک و uncertainty حذف میشود.
متریکهایی که Pipeline را قابل مدیریت میکنند
Flow
- Queue time و duration p50/p95 هر Stage؛
- Commit-to-signal و Commit-to-deploy؛
- Critical path و زمان هدررفته Superseded run؛
- Cache hit در کنار correctness مستقل از Cache؛
- Runner utilization و saturation.
Reliability
- Pipeline success به تفکیک Product/Test/Data/Environment/Dependency/Infra؛
- Flaky rate و Passed-on-retry؛
- Missing/invalid JUnit rate؛
- Artifact checksum/provenance completeness؛
- زمان تعمیر pipeline/test شکسته؛
- Quarantine age و owner.
Outcome
- Change failure و rollback مرتبط با release cohort؛
- escaped defect براساس Risk/Journey؛
- Lead time بدون قربانیکردن guardrail؛
- درصد Deployهایی که همان Artifact تستشده را promote کردهاند.
تعداد Job، تعداد Test و Green rate خام بهتنهایی قابل بازیاند. طراحی سبد متریک و denominator را با متریکهای تست نرمافزار انجام دهید.
برنامه ۱۴روزه راهاندازی
روز ۱ تا ۳: Baseline و CLI
- زمان دستی Build/Test و failureهای فعلی را ثبت کنید؛
- scriptهای lint/unit/build/integration را محلی deterministic کنید؛
- lockfile و JUnit reporter را اضافه کنید؛
- یک runtime LTS و Image digest تصویب کنید.
روز ۴ تا ۶: Pipeline CI
- Jenkinsfile یا GitLab YAML را با Checkout/Install/Lint/Unit/Build بسازید؛
- JUnit و Artifact/checksum را publish کنید؛
- timeout، retention و cancellation را تنظیم کنید؛
- MR و default branch trigger را آزمایش کنید.
روز ۷ تا ۱۰: Integration و Security
- Environment/data identity و Integration JUnit؛
- Runner isolation و Controller/Protected Runner؛
- Fork/MR بدون Secret؛
- Failure taxonomy، infra-only retry و Quarantine policy.
روز ۱۱ تا ۱۴: Delivery و تصمیم
- Deploy دستی Staging با همان Artifact؛
- checksum و Deployment record؛
- rollback rehearsal؛
- p50/p95 queue/duration، Flake و maintenance را با baseline مقایسه کنید؛
- Stage بعدی را براساس Risk اضافه کنید، نه فهرست ابزار.
چکلیست نهایی Pipeline پایه
- Pipeline as Code در همان Repository و زیر review است.
- Jenkins Controller Build اجرا نمیکند یا GitLab Runner trust مشخص دارد.
- SCM event به SHA دقیق نگاشت میشود و Duplicate Pipeline نداریم.
- Runtime LTS، Image digest، OS/arch و lockfile ثبت شدهاند.
npm ciاستفاده میشود؛node_modulesArtifact نیست.- Lint، Unit، Build و Integration command محلی یکسان دارند.
- JUnit حتی در Failure upload و نبود آن Fail محسوب میشود.
- Test command exit code واقعی میدهد و
|| trueندارد. - Build فقط یک بار تولید و checksum/digest آن ثبت میشود.
- Staging همان Artifact تستشده را مصرف میکند.
- timeout، cancellation، retention و cleanup صریحاند.
- Retry فقط Failure زیرساختی محدود را پوشش میدهد.
- Flaky/Quarantine owner و expiry دارد.
- MR/Fork غیرقابل اعتماد به Deploy Secret یا network دسترسی ندارد.
- Secret masked/protected/scoped است و در Artifact/Log نیست.
- Environment/data/dependency Manifest و Failure taxonomy داریم.
- Queue/Stage p95، time-to-signal و Artifact identity اندازهگیری میشوند.
سوالات متداول درباره Jenkins و GitLab CI
برای شروع CI/CD، Jenkins بهتر است یا GitLab CI؟
ابزار برنده عمومی وجود ندارد. اگر GitLab مرکز SCM و workflow شماست، integration داخلی مزیت دارد؛ اگر Agent/Plugin/integration متنوع و زیرساخت legacy دارید، Jenkins ممکن است مناسبتر باشد. یک Pipeline هممعنا را با TCO، patch ownership، isolation، queue و exit test مقایسه کنید.
چرا در CI از npm ci استفاده کنیم؟
npm ci برای نصب خودکار از lockfile طراحی شده، در اختلاف package/lock Fail میکند و dependency tree را در CI بازنویسی نمیکند. این رفتار reproducibility را بهتر میکند؛ ولی همچنان runtime/Image، registry، platform و install scriptها باید کنترل شوند.
آیا JUnit XML بهتنهایی Job را Fail میکند؟
در GitLab نه؛ command تست باید exit code غیرصفر بدهد و JUnit برای نمایش/مقایسه است. در Jenkins نیز publisher میتواند Build را Unstable کند، اما نباید command واقعی را با || true بیاثر کنید مگر Result policy را آگاهانه مدیریت کنید.
آیا Docker برای CI/CD اجباری است؟
خیر. VM، shell agent، Kubernetes executor و راههای دیگر وجود دارند. Docker runtime را بستهبندی میکند، اما daemon/security، Image supply chain، UID/workspace و environment fidelity را باید مدیریت کرد. Tool را از نیاز isolation و reproducibility انتخاب کنید.
اولین Quality Gate چه باشد؟
با کمترین مجموعه قابل اعتماد شروع کنید: lockfile install، lint/type، Unitهای پرارزش، Build و یک Integration باریک. Gate باید owner، timeout، Evidence و failure policy داشته باشد. Coverage درصدی یا تعداد Test را بدون Risk context threshold جهانی نکنید.
جمعبندی: Pipeline خوب مجموعهای از لوگوها نیست؛ یک زنجیره Evidence از Commit تا Artifact است. Jenkinsfile و GitLab YAML این مقاله عمداً Contract یکسان دارند: runtime pinشده، نصب lockfile، Test Report، Build یکباره، Integration، checksum و Deploy کنترلشده. از همین نسخه پایه شروع کنید، Secret را از MR جدا نگه دارید، Failure زیرساخت را از Failure محصول تفکیک کنید و تنها وقتی Stage تازه اضافه کنید که تصمیم Release به Evidence آن نیاز دارد.

