CI/CD עבור Azure DevOps: YAML, AKS ואוטומציה
פריסה ידנית דרך ממשק האינטרנט של Azure DevOps הופכת לבלתי אמינה כאשר משחררים גרסה פעם בשבוע. שגיאות, זמני השבתה, סודות שאבדו—תמונה מוכרת. בממוצע, 3 תקלות בחודש כתוצאה מפריסה ידנית. אנו מתכננים צינורות YAML רב-שלביים המשלבים ACR, AKS ו-Key Vault כדי להפוך את הפריסה לאוטומטית ללא הפתעות. עם ניסיון של למעלה מ-30 פרויקטים בהעברה והגדרה של Azure DevOps, הניסיון שלנו עוזר לצפות טעויות אופייניות ולהימנע מזמני השבתה.
בעיות שאנו פותרים
צינורות קלאסיים במקום YAML. מיקרוסופט הפסיקה לתמוך ב-Classic Release Pipelines, אך חברות רבות עדיין משתמשות בהם. זהו חוב טכני: Classic אינו תומך ב-GitOps, בבדיקות אישור ברמת הסביבה, או בתרחישים רב-שלביים. צינורות קלאסיים מובילים לכמות שגיאות כפולה במהלך הפריסה. אנו עוזרים לבצע הגירה ל-YAML מבלי לעצור את הגרסאות הנוכחיות.
אין חיבור מאובטח למשאבי Azure. Service Principals עם סודות הם סיכון: סודות יכולים לפקוע, להגיע ללוגים או למאגרי קוד. Workload Identity Federation (WIF) פותר זאת. הצינור מקבל טוקן זמני דרך OIDC—ללא סיבובים, ללא דליפות. Workload Identity Federation
פריסות ארוכות ולא יציבות. ללא צינור רב-שלבי, פריסות staging ו-production נעשות לעיתים קרובות ידנית או דרך צינורות release נפרדים. זה מאט את האספקה ומגביר את הסיכוי לשגיאות. אנו בונים צינור YAML אחד עם שלבים מותנים שמופעלים אוטומטית ב-push ל-main או ב-tag. השוואה: צינורות YAML מקצרים את זמן מחזור האספקה פי 3 בהשוואה ל-Classic בזכות אוטומציה של שלבים וביצוע מקבילי.
איך אנחנו עושים את זה
נבחן מקרה אמיתי: צוות של 8 אנשים, מיקרוסרביסים ב-.NET, AKS, PostgreSQL דרך Azure Database. בעבר, הפריסה נראתה כך: build ב-Classic Build, push ידני ל-ACR, ולאחר מכן צינור release עם שלבים ידניים רבים. הצענו צינור YAML אחד עם שלושה שלבים:
- Build: npm ci, lint, test, docker build & push ל-ACR.
- DeployStaging: אוטומטי ב-push ל-main; Helm upgrade עם בדיקת מוכנות של ה-pod.
- DeployProduction: רק ב-tag v*; דורש אישור מ-tech lead; משתמש באסטרטגיית canary (replicaCount=3, פיצול תעבורה).
הנה גרסה פשוטה של צינור כזה:
---
# azure-pipelines.yml
trigger:
branches:
include:
- main
tags:
include:
- 'v*'
variables:
containerRegistry: 'myapp-acr'
imageRepository: 'myapp'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.SourceVersion)'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
pool:
vmImage: 'ubuntu-22.04'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: |
npm ci
npm run lint
npm run test -- --reporters=jest-junit
displayName: 'Build and Test'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
- task: Docker@2
displayName: 'Build and Push to ACR'
inputs:
command: buildAndPush
containerRegistry: '$(containerRegistry)'
repository: '$(imageRepository)'
dockerfile: '$(Dockerfile)'
tags: |
$(tag)
latest
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployStaging
environment: 'staging'
pool:
vmImage: 'ubuntu-22.04'
strategy:
runOnce:
deploy:
steps:
- task: HelmDeploy@0
inputs:
connectionType: 'Azure Resource Manager'
azureSubscription: 'MyAzureSubscription'
azureResourceGroup: 'myapp-rg'
kubernetesCluster: 'myapp-aks-staging'
namespace: 'staging'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(Pipeline.Workspace)/charts/myapp'
releaseName: 'myapp'
overrideValues: 'image.tag=$(tag)'
waitForExecution: true
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: DeployStaging
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
jobs:
- deployment: DeployProduction
environment: 'production'
pool:
vmImage: 'ubuntu-22.04'
strategy:
runOnce:
deploy:
steps:
- task: HelmDeploy@0
inputs:
connectionType: 'Azure Resource Manager'
azureSubscription: 'MyAzureSubscription'
azureResourceGroup: 'myapp-rg'
kubernetesCluster: 'myapp-aks-prod'
namespace: 'production'
command: 'upgrade'
chartType: 'FilePath'
chartPath: '$(Pipeline.Workspace)/charts/myapp'
releaseName: 'myapp'
overrideValues: 'image.tag=$(tag),replicaCount=3'
waitForExecution: true
---
# azure-pipelines.yml trigger: branches: include: - main tags: include: - 'v*' variables: containerRegistry: 'myapp-acr' imageRepository: 'myapp' dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile' tag: '$(Build.SourceVersion)' stages: - stage: Build displayName: 'Build and Test' jobs: - job: BuildJob pool: vmImage: 'ubuntu-22.04' steps: - task: NodeTool@0 inputs: versionSpec: '20.x' - script: | npm ci npm run lint npm run test -- --reporters=jest-junit displayName: 'Build and Test' - task: PublishTestResults@2 inputs: testResultsFormat: 'JUnit' testResultsFiles: '**/junit.xml' - task: Docker@2 displayName: 'Build and Push to ACR' inputs: command: buildAndPush containerRegistry: '$(containerRegistry)' repository: '$(imageRepository)' dockerfile: '$(Dockerfile)' tags: | $(tag) latest - stage: DeployStaging displayName: 'Deploy to Staging' dependsOn: Build condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: DeployStaging environment: 'staging' pool: vmImage: 'ubuntu-22.04' strategy: runOnce: deploy: steps: - task: HelmDeploy@0 inputs: connectionType: 'Azure Resource Manager' azureSubscription: 'MyAzureSubscription' azureResourceGroup: 'myapp-rg' kubernetesCluster: 'myapp-aks-staging' namespace: 'staging' command: 'upgrade' chartType: 'FilePath' chartPath: '$(Pipeline.Workspace)/charts/myapp' releaseName: 'myapp' overrideValues: 'image.tag=$(tag)' waitForExecution: true - stage: DeployProduction displayName: 'Deploy to Production' dependsOn: DeployStaging condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) jobs: - deployment: DeployProduction environment: 'production' pool: vmImage: 'ubuntu-22.04' strategy: runOnce: deploy: steps: - task: HelmDeploy@0 inputs: connectionType: 'Azure Resource Manager' azureSubscription: 'MyAzureSubscription' azureResourceGroup: 'myapp-rg' kubernetesCluster: 'myapp-aks-prod' namespace: 'production' command: 'upgrade' chartType: 'FilePath' chartPath: '$(Pipeline.Workspace)/charts/myapp' releaseName: 'myapp' overrideValues: 'image.tag=$(tag),replicaCount=3' waitForExecution: true בצינור הזה הוא לא רק שם. ב-Azure DevOps Environments הוספנו בדיקות אישור (Approval Checks): לפני פריסה ל-production נדרש אישור מראש המחלקה. הגדרנו גם שעות פעילות (פריסה רק בין 9:00 ל-18:00 UTC) וגלגול אוטומטי לאחור במקרה של כשל.
למה לנטוש צינורות קלאסיים?
Classic Pipelines הם מורשת שמעכבת אוטומציה. חסרה בהם יכולת versioning, בדיקות אישור מבוססות סביבה, ותמיכה בשלבים מותנים. ההשוואה מראה את ההבדל:
| פרמטר | Classic Pipelines | YAML Pipelines |
|---|---|---|
| Versioning | לא (רק בקובץ) | במאגר, דרך PR |
| בדיקות אישור | רק ברמת release | ברמת סביבה: גמיש, עם פונקציות |
| תמיכה ב-GitOps | לא | כן (אינטגרציה עם GitHub Actions או ArgoCD) |
| רב-שלבי | רק דרך Linked Pipelines | מובנה, קובץ אחד |
| שימוש חוזר | לא (תבניות רק באמצעות העתקה) | דרך תבניות ו-extends |
צינורות YAML הם הסטנדרט המינימלי ל-CI/CD מודרני.
איך מחברים את Azure Key Vault לצינור?
ללא Key Vault, סודות מגיעים לעיתים קרובות כמשתני צינור פתוחים. אנו מגדירים את המשימה environment: 'production':
---
- task: AzureKeyVault@2
inputs:
azureSubscription: 'MyAzureSubscription'
KeyVaultName: 'myapp-keyvault'
SecretsFilter: 'DB-PASSWORD,API-KEY'
RunAsPreJob: true
---לאחר מכן, AzureKeyVault@2 זמין כמשתנה, והערך שלו מוסתר בלוגים. אם הסיסמה משתנה ב-Key Vault, הצינור קולט את השינוי אוטומטית ללא צורך בהפעלה מחדש.
מה כלול
אנו מספקים סט שירותים מלא להקמת צינורות CI/CD:
- ביקורת תשתית: ניתוח צינורות קיימים, Service Connections, Environments ו-RBAC.
- תכנון: ארכיטקטורת צינור רב-שלבי, בחירת אסטרטגיית פריסה (canary, blue-green, rolling).
- יישום: כתיבת תבניות YAML, הגדרת WIF, חיבור Key Vault ו-ACR.
- בדיקות: הרצת הצינור בסביבת בדיקה, אימות בדיקות אישור וגלגולים לאחור.
- תיעוד: תיאור הצינור, הוראות להוספת שירותים חדשים.
- הדרכה: סשן הדגמה לצוות, שאלות ותשובות.
- תמיכה: חודש של תמיכה לאחר השחרור לפתרון כל בעיה.
תהליך העבודה
- ניתוח – ביקורת על צינורות קיימים, הסכמה על דרישות (1-2 ימים).
- תכנון – פיתוח תכנית הצינור, בחירת אסטרטגיות, הסכמה (יום אחד).
- יישום – כתיבת קוד YAML, הגדרת Environments, Service Connections (2-4 ימים).
- בדיקות – הרצת צינורות ב-staging, מדידת זמן, התאמות (1-2 ימים).
- פריסה – העברת התשתית לניהול הצינור (יום אחד).
- הדרכה ותמיכה – מסירת תיעוד, שאלות ותשובות, חודש תמיכה.
לוחות זמנים משוערים
| היקף | לוח זמנים |
|---|---|
| צינור YAML חדש עם פריסת AKS | 2–3 ימים |
| הגירה מ-Classic + רפקטורינג | 3–5 ימים |
| מספר סביבות + WIF + Key Vault | 4–6 ימים |
העלות מחושבת באופן אישי לאחר ביקורת תשתית.
קבלו ייעוץ על הקמת צינור CI/CD—המהנדסים שלנו יעריכו את התשתית שלכם ויציעו פתרון אופטימלי. בקשו ביקורת תשתית CI/CD כדי לזהות צווארי בקבוק ולהגדיל את מהירות השחרור ב-40%. צרו קשר כדי לדון בפרויקט שלכם.
מקור: תיעוד Microsoft Azure Pipelines







