برای Pull Request شمارهٔ ۴۲ یک URL ساختهاید؛ Product آن را باز میکند، QA مسیر اصلی را میبیند و تیم خوشحال است که دیگر منتظر Staging نمیماند. سه روز بعد همان PR پنج Commit جدید دارد، دو نسخهٔ قدیمی هنوز Public هستند، دیتابیس میان دو Preview مشترک است، Secret تولید در Namespace مانده و Job حذف پس از Close شدن PR اجرا نشده است. محیط موقت فقط وقتی سرعت میدهد که Lifecycle آن از ساخت تا اثبات حذف مهندسی شده باشد.
این راهنما روی محیط تست موقت یا Ephemeral/Preview Environment برای هر Branch، Pull Request یا Merge Request تمرکز دارد. موضوع مقالهٔ جامع مدیریت محیط تست، طراحی کل Portfolio محیطها، Drift و عملیات TEM است؛ اینجا مسئله محدودتر و عمیقتر است: چگونه یک محیط کوتاهعمر را با هویت تغییر، Isolation، داده، Secret، Health evidence، TTL، Reconciliation و Cost guardrail بهصورت سلفسرویس اداره کنیم.
Preview Environment چیست و چه مسئلهای را حل میکند؟
Preview Environment نمونهای کوتاهعمر از برنامه و بخشی از وابستگیهای آن است که به یک Revision مشخص وصل میشود. نامهای رایج آن:
- Review App در GitLab؛
- Preview Deployment یا PR Environment؛
- Ephemeral Environment؛
- On-demand Environment؛
- Namespace/Stack موقت برای Branch یا Change.
مستندات Review Apps گیتلب آنها را محیطهای موقت خودکار برای Branch یا Merge Request معرفی میکند. ارزش اصلی Preview این است که یک Artifact واقعی و قابلاشتراک را زود در اختیار توسعه، تست، طراحی، محصول و ذینفع قرار دهد؛ نه اینکه Production را دقیقاً کپی کند.
Preview، Test، Integration، Staging و Production یکسان نیستند
| محیط | تصمیم اصلی | عمر | Fidelity | مالکیت معمول |
|---|---|---|---|---|
| Local/Dev | آیا تغییر قابل توسعه و Debug است؟ | ساعت/روز | انتخابی | فرد/تیم |
| Preview/PR | آیا این Revision قابل مشاهده و آزمون محدود است؟ | ساعت/روز | Fit-for-purpose | تیم + Platform |
| Shared Integration | آیا چند جزء/نسخه با هم کار میکنند؟ | پایدار | وابستگی واقعیتر | چند تیم |
| Staging | آیا Release candidate در مسیر نزدیک به تولید آماده است؟ | پایدار | بالاتر و کنترلشده | Release/Operations |
| Production | آیا Outcome مشتری و عملیات واقعی سالم است؟ | دائمی | واقعی | Product/Engineering/Ops |
Preview جای Staging، تست قرارداد، آزمون بار یا Production canary را نمیگیرد. اگر دیتابیس، حجم داده، شبکه یا PSP مجازی است، این محدودیت بخشی از Verdict است. ادعای «Production-like» بدون فهرست تفاوتها Evidence نیست.
چه زمانی Preview Environment ارزش دارد؟
نامزدهای مناسب:
- UI/UX و محتوایی که Review دیداری و تعاملی نیاز دارد؛
- جریان چندجزئی که Local setup دشوار است؛
- Contract/API تغییرکرده که Consumer نمونه میخواهد؛
- Demo قابل تکرار برای Product/Support/Design؛
- تست اکتشافی محدود روی Revision مشخص؛
- Migration یا Feature flag که باید پیش از Merge مشاهده شود.
چه زمانی نسازیم؟
- یک Unit/Component test همان Failure mechanism را سریعتر میسنجد؛
- تغییر Documentation یا Backend داخلی هیچ مصرفکنندهٔ Preview ندارد؛
- وابستگی گران/حساس قابل مجازیسازی نیست و ارزش تصمیم پایین است؛
- اجرای کد Fork ناشناس نیازمند Secret یا دسترسی داخلی است؛
- تیم Cleanup، مالک و بودجهٔ ظرفیت ندارد؛
- Preview به یک Staging دومِ مشترک و دائمی تبدیل میشود.
واحد مالکیت را انتخاب کنید: PR، Commit یا Session؟
سه مدل رایج:
- یک محیط برای PR: هر Commit همان محیط را Update میکند؛ URL ثابت و هزینه کمتر، اما Evidence قبلی جایگزین میشود.
- یک محیط برای Commit: هر SHA یک محیط مستقل؛ Reproducibility بیشتر، ولی هزینه و Cleanup دشوارتر.
- یک محیط برای Session/Request: کاربر یا Job محیط رزرو میکند؛ مناسب آزمون خاص، با نیاز به Lease و تمدید.
برای بیشتر Reviewها، PR بهعنوان مالک و SHA بهعنوان نسخهٔ فعال تعادل خوبی است: در هر لحظه حداکثر یک Revision Active؛ نسخهٔ قبلی Drain و حذف میشود. اگر Evidence Commit قبلی لازم است، Artifact و Manifest را نگه دارید، نه الزاماً زیرساخت زنده را.
Environment Contract؛ قرارداد قابل کپی
environment_id: preview-pr-1042
owner_ref: repo=checkout; pr=1042; head_sha=9f21c7a
desired_state: active
artifact: registry.example/checkout@sha256:...
created_by: pipeline-88124
purpose: UI review + API smoke + exploratory payment flow
components: [web, api, postgres, psp-simulator]
shared_dependencies: [identity-sandbox]
fidelity_limits: [synthetic data, no real SMS, reduced topology]
isolation: namespace + DB schema + tenant/order prefix
identity: workload identity; no static production credential
network: default-deny; allow DNS, registry, approved sandbox
data: seed-v7; synthetic; reset-on-deploy
health_gate: deploy + readiness + migration + smoke + telemetry
public_access: authenticated; robots=noindex; rate-limited
ttl: 24h inactivity; max_lifetime=7d
cleanup: idempotent destroy + periodic desired-state sweep
cost_budget: 0.50 USD/hour; owner labels required
evidence: manifest, test result, diff, logs/traces, destroy receipt
قرارداد باید Machine-readable باشد و همراه Environment تغییر کند. نام URL یا Namespace بهتنهایی هویت نیست؛ Commit، Artifact digest، Config/Data version و Pipeline run لازماند.
Lifecycle کامل: از Build تا Destroy Evidence
- Classify: منبع PR، سطح Trust، هدف و نیاز Fidelity را تعیین کنید.
- Build: Artifact یکبار و با Digest immutable ساخته و Scan شود.
- Plan: Policy، Quota، Cost و Collision پیش از Apply بررسی شوند.
- Provision: Namespace/Stack، DNS، Identity، Secret reference و Storage ساخته شوند.
- Migrate/Seed: Schema سازگار و دادهٔ مصنوعی نسخهدار اعمال شود.
- Verify: Readiness، Contract، Smoke، Security و Telemetry gate بگذرند.
- Expose: URL فقط پس از Gate و با Auth/Noindex نمایش داده شود.
- Observe: Version-correlated log/trace/metric و Activity ثبت شود.
- Reconcile: Desired PR/SHA با Actual resources پیوسته تطبیق یابد.
- Expire: Close/Merge، TTL، Budget یا Policy، Desired state را Stopped کند.
- Destroy: منابع با ترتیب و Retry idempotent حذف شوند.
- Prove: Endpoint/DNS/Volume/Secret/Data/Cloud resource و Cost allocation صفر یا بسته شوند.
Artifact را Promote کنید، نه اینکه در هر محیط دوباره Build کنید
اگر Preview از Source یک Image بسازد و Staging همان Commit را دوباره Build کند، «همان کد» الزاماً همان Artifact نیست: Dependency mutable، Base image، Timestamp یا Build tool ممکن است فرق کند. الگوی امن:
source commit → trusted build → immutable artifact digest
→ preview deploy → evidence → staging/release promotion
Promotion به معنی کافیبودن Preview برای Release نیست؛ فقط هویت Binary را حفظ میکند. Environment-specific config و Secret جدا میمانند و هر مرحله Gate متناسب خود را دارد. معماری Lane و Gate در راهنمای Continuous Testing آمده است.
Desired State، نه مجموعهای از Webhookهای خوشبینانه
Webhook برای سرعت خوب است، اما شبکه، API، Runner یا Job cleanup ممکن است شکست بخورد. Desired state از Source معتبر—مثلاً PRهای باز و SHA فعال—استخراج و دورهای با منابع واقعی مقایسه میشود:
desired = open PRs × latest approved SHA × environment policy
actual = resources labeled as preview environment
missing desired → create/retry
wrong SHA → replace/drain old
closed PR → destroy
unowned/stale → quarantine then destroy
policy drift → reconcile or block
Reconciler باید Idempotent باشد: اجرای دوبارهٔ Create یا Delete نتیجه را خراب نکند. Delete موفق API بهتنهایی پایان نیست؛ Finalizer، Volume، Load Balancer، DNS و Snapshot ممکن است باقی بمانند.
آزمایش تکرارپذیر: Webhook-only چگونه Leak میسازد؟
برای این مقاله یک شبیهساز Node.js با ۱۱ رویداد Open، Sync، Close و Reopen روی سه PR ساخته شد. کنترلگر ساده در هر Commit یک Endpoint تازه میساخت، Close webhook اول و سوم را از دست میداد و در Cleanup موفق فقط آخرین SHA را حذف میکرد. مدل Desired-state یک Failure موقت Delete را نیز تحمل و در Sweep بعدی Retry کرد.
node=v24.18.0
events=11; cost_per_environment_hour=0.42
WEBHOOK_ONLY
desired_open_prs=1; remaining_endpoints=7
correct=1; stale_commit=2; orphan=4; leaked=6
hourly_cost=2.94; daily_cost=70.56
DESIRED_STATE_RECONCILED
desired_open_prs=1; remaining_endpoints=1
correct=1; stale_commit=0; orphan=0; leaked=0
hourly_cost=0.42; daily_cost=10.08
در پایان فقط PR ۱۰۱ با SHA `a3` باید زنده میماند. Webhook-only هفت Endpoint و شش Leak داشت؛ Reconciler فقط Endpoint درست را نگه داشت. اعداد هزینه، Eventها و Failure injection ساختگیاند؛ این آزمایش Benchmark GitLab/Kubernetes/Cloud یا اثبات صرفهجویی واقعی نیست. مدل همچنین DNS delay، Volume، Queue، Cluster contention و Billing lag را حذف میکند. نتیجهٔ محدود آن: Cleanup event-driven بدون Inventory و Reconciliation دورهای قابل اعتماد نیست.
Isolation؛ Namespace یک مرز امنیتی کامل نیست
Namespace نام منابع و بسیاری از Policyها را جدا میکند، اما Node، Kernel، Network، Control plane و برخی Cloud serviceها مشترک میمانند. راهنمای Multi-tenancy کوبرنتیز بر RBAC، Quota و Network isolation تأکید دارد و صریح میگوید Quota همهٔ منابع مشترک مانند ترافیک شبکه را محافظت نمیکند.
Isolation contract در هفت لایه
- Identity/RBAC: ServiceAccount اختصاصی و Least privilege؛
- Network: Default-deny ingress/egress و مقصدهای Allowlisted؛
- Compute: Request/Limit/Quota، Priority و در ریسک بالا Node/Cluster جدا؛
- Data: DB/Schema/Tenant/Key prefix اختصاصی و Reset؛
- Messaging: Topic/Queue/Consumer group و Correlation namespace؛
- Storage: Bucket/path/PVC و Retention اختصاصی؛
- External side effect: Email/SMS/Payment/Webhook به Sink یا Sandbox.
برای هر Shared dependency بررسی کنید آیا داده، Rate limit، Cache، Lock یا Global state میتواند Preview دیگری را آلوده کند. «Podها جدا هستند» پاسخ کافی نیست.
Policy و Quota؛ جلوگیری از Noisy Neighbor و انفجار هزینه
مستندات Policy کوبرنتیز NetworkPolicy، LimitRange و ResourceQuota را از سازوکارهای اصلی معرفی میکند. Baseline پیشنهادی:
- تعداد Preview همزمان برای Repo/Team/Organization؛
- CPU، Memory، Ephemeral storage، PVC و LoadBalancer quota؛
- Default requests/limits و سقف Per workload؛
- ممنوعیت Privileged، HostPath/HostNetwork و Capability اضافی؛
- Allowed registry و Image digest؛
- مالک/Cost-center/TTL labels اجباری؛
- Admission denial برای Resource بیمالک یا Public بدون Auth.
Quota رزرو ظرفیت واقعی نمیکند و ممکن است مجموع Quotaها از ظرفیت Cluster بیشتر باشد. Provisioning gate باید هم Policy و هم Available capacity را بسنجد؛ Pending شدن بینهایت، Environment موفق نیست.
Pod Security و Runtime محدود
Pod Security Admission سطحهای Privileged، Baseline و Restricted را در Namespace اعمال میکند. برای Previewهای کد غیرقابلاعتماد:
- سطح Restricted یا کنترل معادل را Enforce کنید؛
- Root filesystem ترجیحاً Read-only و اجرای Non-root؛
- Token خودکار Kubernetes را فقط در صورت نیاز Mount کنید؛
- Metadata service و شبکهٔ داخلی را مسدود کنید؛
- Runner و Runtime موقت و غیرقابل استفاده مجدد باشند؛
- Artifact و Log را Untrusted data فرض کنید.
Fork و کد غیرقابلاعتماد؛ Preview یک مرز اجرای کد است
هر Build script، dependency، migration و test در PR میتواند کد دلخواه اجرا کند. راهنمای امنیتی GitHub برای `pull_request_target` هشدار میدهد اجرای کد PR نامطمئن با Token/Secret سطح Base میتواند به pwn request منجر شود.
ماتریس Trust پیشنهادی
| منبع | Build/Test | Secret | Preview |
|---|---|---|---|
| Fork ناشناس | Runner موقت، Token read-only | بدون Secret سازمانی | Sandbox بدون شبکه داخلی؛ Manual approval برای Exposure |
| عضو سازمان | Runner محدود | OIDC کوتاهعمر و Scoped | Policy/Quota استاندارد |
| Bot/Dependency PR | مانند Untrusted تا Approval | حداقل | ترجیحاً تست بدون Public URL |
| Trusted release | Protected branch/workflow | Environment-scoped | Gate و Approval بالاتر |
هیچگاه Secret تولید را برای اینکه Demo «واقعیتر» شود وارد Preview نکنید. OIDC/Workload identity کوتاهعمر و مخاطب/Scope محدود از Credential ثابت بهتر است، اما Policy و Egress همچنان لازماند.
Secret lifecycle باید همعمر محیط باشد
- Secret reference از Repo و Log جدا باشد؛
- Credential در Provision و با TTL کوتاه صادر شود؛
- Scope فقط Environment ID و سرویس لازم را بپوشاند؛
- Rotation/Revocation در Destroy اجرا و Verify شود؛
- Masking Log را تضمین کامل ندانید؛ Derived/encoded value ممکن است نشت کند؛
- Snapshot، Artifact و Crash dump از نظر Secret/PII Scan شوند.
URL عمومی؛ Auth، Noindex و Hostname امن
Preview URL ممکن است Feature منتشرنشده، داده، Stack trace یا پنل داخلی را افشا کند. کنترلها:
- SSO یا Access proxy؛ لینک ناشناس فقط برای Use case تأییدشده؛
- TLS و Certificate lifecycle خودکار؛
- `robots: noindex,nofollow` و جلوگیری از Sitemap؛
- Hostname تولیدشده از شناسهٔ Sanitized، نه Branch text خام؛
- محافظت در برابر Host-header/Dangling DNS/Subdomain takeover؛
- Rate limit، WAF متناسب و عدم نمایش Debug endpoint؛
- Banner واضح: محیط موقت، Revision، دادهٔ مصنوعی و مالک.
دادهٔ Preview؛ واقعینما، نه دادهٔ واقعی
Data contract باید Version، سناریو، Privacy، Isolation و Reset را مشخص کند:
- Seed deterministic برای Smoke و Review؛
- Scenario pack برای Empty/Normal/Boundary/Error/Long text/RTL؛
- شناسههای یکتا با Environment prefix؛
- Clock/Timezone و Random seed قابل کنترل؛
- عدم استفاده از PII/PAN/OTP/Token واقعی؛
- Reset روی Deploy یا Test و Cleanup قابل اثبات؛
- Retention مستقل برای Backup/Snapshot/Log.
روشهای Synthetic، Masking، Subsetting و ریسک بازشناسایی در راهنمای مدیریت داده تست پوشش داده شدهاند.
Database per Preview، Schema per Preview یا Shared Tenant؟
| مدل | مزیت | ریسک | کاربرد |
|---|---|---|---|
| DB اختصاصی | Isolation و Cleanup روشن | هزینه/Provision time | تغییر Schema یا ریسک بالا |
| Schema اختصاصی | تعادل هزینه و جداسازی | Extension/role/global object مشترک | اغلب Previewها |
| Tenant/prefix مشترک | سریع و ارزان | نشت، Collision، Query بدون tenant | ریسک پایین با Guardrail قوی |
| Shared mutable DB | ساده در شروع | Flake، آلودگی و Attribution ضعیف | عموماً اجتناب شود |
Migration؛ Preview نمیتواند هر Schema ناسازگار را آزادانه اجرا کند
اگر چند Revision به دیتابیس مشترک وصلاند، Migration ناسازگار یکی دیگری را میشکند. کنترلها:
- Database اختصاصی برای PRهای Schema-changing؛
- Expand–Migrate–Contract برای Shared dependency؛
- Migration idempotent با Lock، Timeout و Failure recovery؛
- Forward/backward compatibility tests؛
- عدم اجرای Down migration مخرب روی دادهٔ مشترک؛
- Version stamp و Migration health در Manifest.
برای Legacy یا Dual-run، قرارداد داده و Cutover را با راهنمای تست سیستمهای لگسی تطبیق دهید.
وابستگیها؛ Fidelity ladder را آگاهانه انتخاب کنید
- Stub پاسخ ثابت؛
- Fake stateful؛
- Simulator با Failure/Latency؛
- Contract-verified provider؛
- Shared sandbox؛
- نسخهٔ اختصاصی dependency؛
- Production-like external system با کنترل سخت.
بالاتر همیشه بهتر نیست. برای Review UI، PSP Simulator کافی است؛ برای Timeout-after-commit شاید Sandbox یا Fake stateful دقیق لازم باشد. هر Double باید Contract calibration و محدودیت نسخه داشته باشد. طراحی مرز واقعی در تست یکپارچهسازی توضیح داده شده است.
Health gate؛ Deployment موفق مساوی Environment آماده نیست
Gate چندلایه:
- Provision: منابع موردانتظار وجود دارند و Policy pass است؛
- Runtime: Pod/VM آماده، CrashLoop و Pending ندارد؛
- Dependency: DNS/TLS/DB/Queue/Sandbox قابل استفادهاند؛
- Migration/Data: نسخه و Seed صحیحاند؛
- Application: Smoke و Critical contract pass؛
- Observability: Log/trace/metric با Environment ID دیده میشود؛
- Exposure: Auth، Noindex، Network و Secret policy pass؛
- Completeness: همهٔ اجزای Manifest دیده شدهاند؛ Missing نتیجه را Inconclusive میکند.
ENVIRONMENT_READY =
artifact_digest_matches
AND expected_resources == observed_resources
AND migrations_valid
AND dependencies_usable
AND critical_smoke_valid
AND telemetry_correlated
AND security_policy_passed
Readiness endpoint نباید فقط Process alive را نشان دهد؛ باید برای هدف Preview معنا داشته باشد. اما Health check سنگین نیز میتواند خود Dependency را تحت فشار بگذارد.
Observability؛ هر Signal باید به PR و SHA برگردد
روی Log، Trace، Metric و Event این Attributeها را ثبت کنید:
service.name
service.version / image.digest
deployment.environment.name=preview
preview.environment.id
vcs.repository
vcs.pull_request.id
vcs.commit.sha
pipeline.run.id
test.run.id
data.seed.version
Sampling و Retention Preview میتواند پایینتر باشد، ولی Error و Critical flow باید قابل تشخیص بمانند. PII در Telemetry مجاز نیست و حذف Environment باید Retention Artifact را نیز طبق Policy مدیریت کند.
Concurrency؛ آخرین SHA باید برندهٔ قابل اثبات باشد
دو Pipeline برای یک PR ممکن است همزمان Deploy شوند. بدون کنترل، Pipeline قدیمی پس از جدید تمام و Environment را Downgrade میکند. راهکارها:
- Concurrency/resource group بر اساس PR ID؛
- Cancel کردن Job قدیمی فقط اگر Interruptible است؛
- Compare desired SHA پیش از Apply و پس از Deploy؛
- Generation number یا optimistic lock؛
- Atomic route switch پس از Health gate؛
- Drain/Delete نسخهٔ قبلی پس از تأیید Route.
Slug Branch ممکن است Collision داشته باشد؛ شناسهٔ Repo+PR و Hash کوتاه را مبنا بگیرید.
GitLab Review App؛ نمونهٔ Lifecycle، نه نسخهٔ آمادهٔ Production
مستندات Environment گیتلب Dynamic environment، `on_stop` و `auto_stop_in` را پوشش میدهد. نمونهٔ حداقلی:
deploy_review:
stage: deploy
resource_group: review-$CI_MERGE_REQUEST_IID
script:
- ./preview reconcile --pr "$CI_MERGE_REQUEST_IID" --sha "$CI_COMMIT_SHA"
- ./preview verify --pr "$CI_MERGE_REQUEST_IID" --sha "$CI_COMMIT_SHA"
environment:
name: review/$CI_MERGE_REQUEST_IID
url: https://pr-$CI_MERGE_REQUEST_IID.preview.example.ir
on_stop: stop_review
auto_stop_in: 24 hours
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
stop_review:
stage: deploy
variables:
GIT_STRATEGY: none
script:
- ./preview set-desired-state --pr "$CI_MERGE_REQUEST_IID" --state absent
- ./preview reconcile --pr "$CI_MERGE_REQUEST_IID"
- ./preview verify-absent --pr "$CI_MERGE_REQUEST_IID"
environment:
name: review/$CI_MERGE_REQUEST_IID
action: stop
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual
allow_failure: true
این YAML صرفاً اسکلت است: `preview` باید Tool داخلی idempotent باشد، Policy/Secret/Auth/Quota پیاده شود و Sweep مستقل اجرا گردد. خود GitLab هشدار میدهد Stop job ممکن است بهدلیل Stage/Needs قابل اجرا نباشد و worker انقضا دقیقاً در لحظه TTL عمل نمیکند. جزئیات Pipeline امن در راهنمای تست GitLab CI آمده است.
Pull Request Generator و GitOps؛ Template یک سطح اعتماد است
مستندات Pull Request Generator در Argo CD کاربرد ساخت Environment برای PR را توضیح میدهد و دربارهٔ پیامد امنیتی Templateهای Project هشدار میدهد. کنترلها:
- ApplicationSet/Template فقط در Repo و Project مورد اعتماد؛
- PR نتواند Destination cluster/namespace/project را آزادانه تعیین کند؛
- Source repo/path و Helm/Kustomize valueها Allowlist شوند؛
- SCM token فقط Read و Scope محدود داشته باشد؛
- Deletion/Finalizer و Orphan scan مستقل مانیتور شوند؛
- Sync status بهتنهایی Health و Evidence کامل نیست.
TTL، Lease و Max lifetime را جدا کنید
- Idle TTL: اگر Activity معتبر نبود محیط Expire میشود؛
- Lease: کاربر تا زمان معین مالک استفاده است و میتواند با دلیل تمدید کند؛
- Max lifetime: حتی با Activity، پس از سقف باید Recreate یا Approval شود؛
- Close/Merge trigger: Desired state بلافاصله Absent میشود؛
- Budget trigger: عبور از Cost/Capacity محیط را Suspend یا Scale down میکند.
بازکردن URL توسط Monitor را Activity ندانید. Activity باید Source و Actor معتبر داشته باشد. Pin کردن Environment نیز مالک، دلیل، هزینه و تاریخ انقضا میخواهد.
Cleanup contract؛ «Stop» با «حذف کامل» فرق دارد
cleanup_order:
1. disable public route and revoke identity
2. stop jobs/consumers and drain in-flight work
3. delete workloads/services/ingress
4. delete DB/schema/queue/bucket/PVC/snapshot per policy
5. delete DNS/certificate/load-balancer/firewall
6. remove secrets and access bindings
7. verify cloud inventory and billing labels absent
8. retain only approved sanitized evidence
result_states: DESTROYED | PARTIAL | BLOCKED | UNKNOWN
PARTIAL/UNKNOWN != success
Cleanup باید در Branch حذفشده نیز بدون Checkout کد قدیمی کار کند؛ Controller مرکزی یا Tool نسخهدار روی Default branch مناسبتر است. Failure را Retry و پس از Budget به Dead-letter/Incident ببرید.
Orphan detector؛ Safety net مستقل
هر ساعت یا روز Inventory واقعی را با Owner registry مقایسه کنید:
- Resource بدون Environment ID؛
- Environment با PR بسته/ناموجود؛
- SHA غیر Desired؛
- عمر بیشتر از Max؛
- DNS بدون Backend یا Backend بدون DNS؛
- Secret/Volume/LoadBalancer پس از Destroy؛
- هزینه بدون Cost center؛
- Preview Public بدون Auth/Noindex.
برای Resource ناشناخته ابتدا Quarantine/Owner lookup و سپس حذف کنید؛ Sweep اشتباه میتواند Staging یا محیط Incident را پاک کند. Break-glass و Dry-run لازم است.
FinOps؛ هزینه را به تصمیم وصل کنید
متریکهای مفید:
- Cost per Environment-hour و per Reviewed PR؛
- Provision P50/P90/P95 و Queue time؛
- Active/Idle/Leaked resource-hours؛
- Cost تفکیکی Compute/DB/Storage/Network/LoadBalancer/License؛
- Utilization در Window استفاده، نه میانگین کل عمر؛
- Cleanup latency و Cost-after-close؛
- Preview adoption و تصمیمهایی که واقعاً از آن گرفته شد؛
- False confidence/escaped mismatch بهعنوان Guardrail.
Pay-as-you-go تضمین کاهش هزینه نیست. Database، NAT، LoadBalancer، IP، Log ingestion و License ممکن است هزینهٔ ثابت یا پنهان داشته باشند. Scale-to-zero نیز Cold-start و State limitation دارد.
Feedback time؛ زمان ساخت را مرحلهبندی کنید
queue → build/cache → policy/plan → provision
→ image pull/start → migration/seed → health/smoke
→ URL ready → first stakeholder action → trusted decision
فقط «Pipeline duration» علت را نشان نمیدهد. P95 و Failure rate هر فاز را بر اساس نوع تغییر و Environment cohort بسنجید. اگر ۸۰٪ PRها Preview باز نمیشوند، سریعتر ساختن آن شاید Outcome ندهد.
Failure taxonomy و مالک پاسخ
| کلاس | مثال | مالک اولیه | Verdict |
|---|---|---|---|
| Product | Smoke روی Revision fail | تیم تغییر | FAILED |
| Environment | Quota/Node/DNS/Certificate | Platform | BLOCKED/INVALID |
| Data | Seed ناقص یا Collision | تیم + Data owner | INVALID |
| Dependency | Sandbox down/rate limit | Dependency owner | BLOCKED |
| Policy/Security | Fork نیازمند Secret | Security/Platform | DENIED |
| Cleanup | Finalizer/PVC باقی | Platform | PARTIAL |
| Unknown | Artifact/Actual identity نامعلوم | Incident owner | INCONCLUSIVE |
Retry فقط برای Failure طبقهبندیشده و محدود Infrastructure باشد. Retry بیحد Product failure را پنهان و هزینه را زیاد میکند.
سناریوی ایرانی: Preview پرداخت Marketplace
برای PR تغییر Checkout، Environment شامل Web/API، PostgreSQL schema اختصاصی، PSP Simulator، Fake SMS و Outbox consumer است. قرارداد:
- مبلغ Canonical در IRR و نمایش تومان صریح؛
- اعداد فارسی، عربی و لاتین و `ی/ی`، `ک/ک`، ZWNJ/RTL؛
- نام فروشگاه بلند و Receipt موبایل؛
- Timeout-after-commit، Callback تکراری/دیرهنگام و Idempotency؛
- UTC، `Asia/Tehran`، مرز جمعه و تاریخ جلالی نمایشی؛
- Order/Ledger/Outbox/Reconciliation oracle مستقل؛
- هیچ PSP/SMS/Email واقعی و هیچ PAN/OTP/کد ملی واقعی؛
- Environment ID در Order، Queue و Log؛
- Default-deny Egress جز Registry، DNS و Sandbox؛
- TTL 24ساعت، Max هفتروز و Cleanup schema/topic/route.
Fidelity statement
Preview میتواند UI، Validation، Contract و برخی State transitionها را بسنجد؛ رفتار واقعی PSP، Latency شبکه ایران، تسویه بانکی، ظرفیت Production و Failure ترکیبی را ثابت نمیکند. این محدودیت در Review banner و Evidence report نمایش داده میشود.
Promotion Evidence؛ Preview چه چیزی را اجازه میدهد؟
نتیجهٔ Preview یکی از این حالتهاست:
- READY_FOR_REVIEW: Environment سالم است؛ هنوز تغییر تأیید نشده؛
- REVIEWED: ذینفع Scope مشخص را دید و Artifact ثبت شد؛
- FAILED: Product/Contract failure معتبر؛
- INVALID/BLOCKED: Environment/Data/Dependency Evidence نامعتبر؛
- APPROVED_FOR_MERGE: فقط Gateهای تعریفشده پاساند؛
- NOT_RELEASE_EVIDENCE: Performance/Security/Recovery یا Fidelity خارج Scope است.
کلیک Product روی URL یا «Looks good» جای Required checks، Automated test و Risk acceptance را نمیگیرد.
متریکهای سالم برای Preview Platform
- Provision success و Ready success جدا؛
- P50/P90/P95 زمان تا Ready و تا First review؛
- درصد Previewهای استفادهشده و نوع تصمیم؛
- Stale-SHA exposure duration؛
- Cleanup success، latency، orphan count/age و cost-after-close؛
- Environment-caused invalid test rate؛
- Policy denial به تفکیک Trust/Resource/Security؛
- Shared-dependency collision و data leak؛
- Cost per useful reviewed PR؛
- Escaped defectهای مرتبط با Fidelity gap.
«تعداد Environment ساختهشده» Outcome نیست و هدفکردن آن Waste میسازد. Thresholdها را بر اساس SLO داخلی و هزینهٔ تصمیم خود تعیین کنید.
SLO و Error budget پلتفرم Preview
نمونهٔ SLI:
- درصد Requestهای مجاز که در X دقیقه به READY میرسند؛
- درصد Updateها که Desired SHA را بدون Downgrade نشان میدهند؛
- درصد Closeها که در Y دقیقه تمام منابع را حذف میکنند؛
- درصد Environment-hourهای بدون Policy violation؛
- درصد Environmentهای Ready با Manifest/Evidence کامل.
Error budget باید Investment را هدایت کند: اگر Cleanup SLO نقض شده، افزودن Feature جدید به پلتفرم اولویت پایینتری دارد. اما SLO داخلی مجوز نادیدهگرفتن Leak امنیتی Critical نیست.
مدل مالکیت Platform و Team
- Platform: Controller، Template امن، Policy، Identity، Network، Observability، Cleanup/Sweep و SLO؛
- Product team: Component manifest، Dependency/Fidelity، Migration، Seed، Health/Smoke و Cost budget؛
- Security: Trust model، admission، Secret/Egress و Incident response؛
- Data/Dependency owner: Sandbox contract، isolation و capacity؛
- Reviewer: Scope بررسی و Evidence/Decision؛
- FinOps: Allocation، anomaly و budget policy.
Self-service یعنی Guardrail و Golden path، نه واگذاری Cluster-admin به هر تیم. Platform باید Escape hatch محدود، Reviewشده و زماندار داشته باشد.
ضدالگوهای رایج Preview Environment
- یک Environment برای هر Commit بدون حذف Revision قبلی؛
- Webhook-only cleanup بدون Desired-state sweep؛
- Namespace را Sandbox امنیتی کامل دانستن؛
- Secret تولید یا Credential ثابت در PR؛
- اجرای Fork code با Workflow privileged؛
- Shared mutable database بدون Tenant/Schema isolation؛
- PSP، Email یا SMS واقعی برای Demo؛
- URL Public بدون Auth، Noindex و مالک؛
- Branch name خام در Shell، DNS یا Namespace؛
- Build مجدد Artifact در هر محیط؛
- Deployment success بهجای Health/Evidence gate؛
- TTL بدون Max lifetime یا Activity معتبر؛
- Stop job وابسته به Branch حذفشده؛
- Delete API success بدون Verify-absent؛
- Quota بدون Capacity و Network policy؛
- Sync سبز GitOps بهعنوان Product pass؛
- Preview بهعنوان جایگزین Staging/Performance/Security؛
- Count محیطها بهعنوان KPI موفقیت.
برنامهٔ ۳۰روزهٔ پیادهسازی
روزهای ۱ تا ۷: Use case و Threat model
یک Repo و یک نوع Review انتخاب کنید. Trust source، Dependency، داده، Secret، Side effect، Cost و Fidelity را ثبت کنید. Baseline زمان انتظار و تصمیم Review را بسنجید.
روزهای ۸ تا ۱۴: Golden path و Contract
Artifact immutable، Environment manifest، PR identity، Namespace/DB isolation، Health gate، Auth و TTL را بسازید. فقط PRهای Trusted داخلی را وارد Pilot کنید.
روزهای ۱۵ تا ۲۱: Cleanup و Failure injection
Close/Merge، Commit race، Runner cancel، API timeout، Migration fail، DNS fail و Finalizer stuck را تزریق کنید. Idempotency، Sweep، Partial state و Verify-absent را تست کنید.
روزهای ۲۲ تا ۳۰: SLO، Cost و توسعهٔ Scope
Ready/usage/cleanup/cost metrics را مرور کنید. فقط اگر Environment واقعاً تصمیم را بهتر کرده، Scope را به تیم یا Dependency بعدی گسترش دهید. Fork و Public exposure را پس از Threat review جداگانه فعال کنید.
چکلیست نهایی Preview Environment
- □ هدف تصمیم و موارد خارج Scope مشخصاند.
- □ PR/Commit/Session ownership و Desired SHA روشن است.
- □ Artifact با Digest immutable ساخته و Promote میشود.
- □ Environment Contract Machine-readable است.
- □ Trust level PR و Fork policy تعیین شده است.
- □ Runner، Token و Secret Least-privilege و کوتاهعمرند.
- □ Namespace همراه RBAC/Network/Quota/Pod policy است.
- □ Data، DB، Queue، Cache و Storage جدا یا Namespaced هستند.
- □ Side effect خارجی به Sandbox/Sink میرود.
- □ Fidelity gap در UI و Evidence دیده میشود.
- □ Health gate از Deployment status فراتر میرود.
- □ Telemetry به Environment/PR/SHA/Pipeline وصل است.
- □ Concurrency از Downgrade به SHA قدیمی جلوگیری میکند.
- □ URL Auth، TLS، Noindex و Rate limit دارد.
- □ Idle TTL، Lease و Max lifetime تعریف شدهاند.
- □ Cleanup مستقل، idempotent و پس از حذف Branch قابل اجراست.
- □ Desired-state Sweep و Orphan detector وجود دارد.
- □ Destroy با Verify-absent و Cost closure ثابت میشود.
- □ Product/Environment/Data/Dependency/Policy verdict جداست.
- □ Cost per useful review و Fidelity escape guardrail سنجیده میشود.
جمعبندی
محیط موقت، «چند خط YAML برای Deploy هر PR» نیست؛ یک Controller کوچک چرخهعمر است. ارزش آن از ساخت سریع URL نمیآید، بلکه از پیوند مطمئن Revision به Environment، Isolation متناسب، داده و Secret امن، Health evidence، بازخورد قابل استفاده و حذف قابل اثبات میآید.
از یک Use case محدود شروع کنید، Contract را Machine-readable کنید، Artifact را Promote کنید، Webhook را با Desired-state Reconciliation و Sweep تکمیل کنید و Cleanup را یک نتیجهٔ قابلسنجش بدانید. Preview خوب سرعت را با پنهانکردن Fidelity یا انتقال ریسک به امنیت و هزینه نمیخرد.
سؤالات متداول
۱. آیا Preview Environment باید دقیقاً شبیه Production باشد؟
خیر. باید برای تصمیم موردنظر Fit-for-purpose باشد. تفاوت Topology، داده، Scale، Dependency و Security باید صریح باشد. Staging یا تستهای تخصصی Evidenceهای خارج Scope را تکمیل میکنند.
۲. برای هر Commit محیط جدا بسازیم یا برای هر PR؟
اغلب یک Environment برای PR با یک SHA فعال مناسبتر است. هر Commit همان محیط را Update و نسخهٔ قبلی را حذف میکند. Environment per Commit فقط وقتی ارزش دارد که مقایسه یا Reproduction همزمان لازم و بودجه/cleanup کافی باشد.
۳. آیا Namespace کوبرنتیز برای Isolation کافی است؟
نه. Namespace یک واحد منطقی مهم است، اما باید با RBAC، NetworkPolicy، Pod Security، Quota، Data/Queue/Storage namespace و در ریسک بالا Node یا Cluster جدا تکمیل شود.
۴. بهترین TTL برای محیط موقت چقدر است؟
عدد عمومی وجود ندارد. Idle TTL را از الگوی Review، زمان کاری و هزینه تعیین کنید؛ Max lifetime جدا داشته باشید و Close/Merge را Trigger فوری قرار دهید. تمدید باید مالک و دلیل داشته باشد و Sweep مستقل Leak را پیدا کند.
۵. چگونه مطمئن شویم محیط واقعاً حذف شده است؟
پس از Destroy، Inventory را با Manifest مقایسه کنید و نبود Workload، Route، DNS، Load balancer، Volume/Snapshot، DB/Schema، Queue، Secret، Identity binding و Cost label فعال را تأیید کنید. نتیجهٔ Partial یا Unknown را Success گزارش نکنید.

