ساختن Workflow چندمرحله‌ای

از فرایند دستی تا Workflow

فرض کنید می‌خواهید یک برنامهٔ وب را deploy کنید. فرایند دستی شما این است:

  1. بررسی کنید که کد جدید روی Git هست
  2. تست‌ها را اجرا کنید
  3. اگر تست‌ها موفق بودند، build بگیرید
  4. پیکربندی production را آماده کنید
  5. سرویس قدیمی را متوقف کنید
  6. فایل‌های جدید را جایگزین کنید
  7. سرویس جدید را بالا بیاورید
  8. health check انجام دهید
  9. اگر سرویس سالم نیست، rollback کنید

این یک workflow است — مجموعه‌ای از مراحل مرتبط که باید به‌ترتیب اجرا شوند. حالا می‌خواهیم آن را خودکار کنیم.

اصول ساختن Workflow

1. هر مرحله یک وظیفهٔ مشخص

بهتر است هر مرحله را در یک تابع یا اسکریپت جداگانه قرار دهید:

 1#!/usr/bin/env bash
 2set -euo pipefail
 3
 4run_tests() {
 5  echo "Running tests..."
 6  cd /opt/myapp || exit 1
 7  npm test
 8}
 9
10build_app() {
11  echo "Building application..."
12  cd /opt/myapp || exit 1
13  npm run build
14}
15
16deploy_app() {
17  echo "Deploying application..."
18  systemctl stop myapp
19  cp -r /opt/myapp/dist/* /var/www/html/
20  systemctl start myapp
21}
22
23health_check() {
24  echo "Running health check..."
25  sleep 3
26  curl -f http://localhost/ > /dev/null
27}

2. مراحل باید مستقل باشند

هر مرحله باید بتواند به‌تنهایی اجرا شود و نتیجهٔ خودش را گزارش دهد:

 1#!/usr/bin/env bash
 2set -euo pipefail
 3
 4check_dependencies() {
 5  local missing=0
 6  
 7  for cmd in git npm systemctl curl; do
 8    if ! command -v "$cmd" &>/dev/null; then
 9      echo "ERROR: $cmd not found" >&2
10      missing=1
11    fi
12  done
13  
14  return $missing
15}
16
17# می‌توانید فقط این مرحله را تست کنید
18if check_dependencies; then
19  echo "All dependencies present"
20else
21  echo "Some dependencies are missing"
22  exit 1
23fi

3. مدیریت وابستگی بین مراحل

بعضی مراحل به خروجی مراحل قبلی نیاز دارند:

 1#!/usr/bin/env bash
 2set -euo pipefail
 3
 4# مرحله 1: گرفتن آخرین کد
 5fetch_code() {
 6  echo "Fetching latest code..."
 7  cd /opt/myapp || return 1
 8  git pull origin main
 9  git rev-parse --short HEAD  # خروجی: commit hash
10}
11
12# مرحله 2: ساختن image با commit hash
13build_image() {
14  local commit_hash="$1"
15  echo "Building image with tag: $commit_hash"
16  docker build -t "myapp:$commit_hash" .
17}
18
19# اجرای workflow
20main() {
21  local commit_hash
22  
23  commit_hash=$(fetch_code) || {
24    echo "ERROR: Failed to fetch code" >&2
25    exit 1
26  }
27  
28  build_image "$commit_hash" || {
29    echo "ERROR: Failed to build image" >&2
30    exit 1
31  }
32  
33  echo "Build complete: myapp:$commit_hash"
34}
35
36main

Workflow کامل: Deployment Pipeline

یک مثال واقعی‌تر که این اصول را رعایت می‌کند:

  1#!/usr/bin/env bash
  2set -euo pipefail
  3
  4readonly APP_DIR="/opt/myapp"
  5readonly BACKUP_DIR="/backups"
  6readonly LOG_FILE="/var/log/deploy.log"
  7
  8log() {
  9  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
 10}
 11
 12create_backup() {
 13  local backup_path="$BACKUP_DIR/app-$(date +%s)"
 14  log "Creating backup at $backup_path"
 15  
 16  mkdir -p "$BACKUP_DIR"
 17  cp -a "$APP_DIR" "$backup_path"
 18  echo "$backup_path"
 19}
 20
 21fetch_latest_code() {
 22  log "Fetching latest code"
 23  cd "$APP_DIR" || return 1
 24  
 25  git fetch origin
 26  local latest_commit
 27  latest_commit=$(git rev-parse origin/main)
 28  
 29  git checkout "$latest_commit"
 30  echo "$latest_commit"
 31}
 32
 33run_tests() {
 34  log "Running tests"
 35  cd "$APP_DIR" || return 1
 36  npm test
 37}
 38
 39build_application() {
 40  log "Building application"
 41  cd "$APP_DIR" || return 1
 42  npm run build
 43}
 44
 45deploy_new_version() {
 46  log "Deploying new version"
 47  systemctl stop myapp
 48  systemctl start myapp
 49}
 50
 51verify_deployment() {
 52  log "Verifying deployment"
 53  sleep 5
 54  
 55  if systemctl is-active --quiet myapp && \
 56     curl -f http://localhost:3000/health > /dev/null 2>&1; then
 57    return 0
 58  else
 59    return 1
 60  fi
 61}
 62
 63rollback() {
 64  local backup_path="$1"
 65  log "Rolling back to $backup_path"
 66  
 67  systemctl stop myapp
 68  rm -rf "$APP_DIR"
 69  cp -a "$backup_path" "$APP_DIR"
 70  systemctl start myapp
 71}
 72
 73main() {
 74  log "Starting deployment pipeline"
 75  
 76  # مرحله 1: backup
 77  local backup_path
 78  backup_path=$(create_backup) || {
 79    log "ERROR: Backup failed"
 80    exit 1
 81  }
 82  
 83  # مرحله 2: fetch code
 84  local commit_hash
 85  commit_hash=$(fetch_latest_code) || {
 86    log "ERROR: Failed to fetch code"
 87    exit 1
 88  }
 89  
 90  log "Deploying commit: $commit_hash"
 91  
 92  # مرحله 3: tests
 93  if ! run_tests; then
 94    log "ERROR: Tests failed"
 95    exit 1
 96  fi
 97  
 98  # مرحله 4: build
 99  if ! build_application; then
100    log "ERROR: Build failed"
101    exit 1
102  fi
103  
104  # مرحله 5: deploy
105  if ! deploy_new_version; then
106    log "ERROR: Deployment failed, rolling back"
107    rollback "$backup_path"
108    exit 1
109  fi
110  
111  # مرحله 6: verify
112  if ! verify_deployment; then
113    log "ERROR: Verification failed, rolling back"
114    rollback "$backup_path"
115    exit 1
116  fi
117  
118  log "Deployment successful"
119  rm -rf "$backup_path"
120}
121
122main "$@"

Workflow قابل پیکربندی

به‌جای hardcode کردن مقادیر، از environment variable استفاده کنید:

 1#!/usr/bin/env bash
 2set -euo pipefail
 3
 4# پیکربندی از محیط
 5readonly ENVIRONMENT="${DEPLOY_ENV:-staging}"
 6readonly APP_DIR="${APP_DIR:-/opt/myapp}"
 7readonly SKIP_TESTS="${SKIP_TESTS:-false}"
 8
 9log() {
10  echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$ENVIRONMENT] $*"
11}
12
13main() {
14  log "Starting deployment to $ENVIRONMENT"
15  
16  # اگر production است، tests را skip نکن
17  if [[ "$ENVIRONMENT" == "production" ]]; then
18    SKIP_TESTS=false
19  fi
20  
21  if [[ "$SKIP_TESTS" == "false" ]]; then
22    run_tests || exit 1
23  else
24    log "Skipping tests (not recommended)"
25  fi
26  
27  # بقیه workflow...
28}
29
30main "$@"

استفاده:

1# برای staging
2DEPLOY_ENV=staging ./deploy.sh
3
4# برای production
5DEPLOY_ENV=production ./deploy.sh
6
7# برای development با skip tests (فقط برای تست محلی!)
8DEPLOY_ENV=dev SKIP_TESTS=true ./deploy.sh

تصویرسازی Workflow

مطابق با دانسته‌های فعلی، یک workflow خوب را می‌توان به‌صورت یک نمودار ساده تصور کرد:

 1┌─────────────────┐
 2│ Create Backup   │
 3└────────┬────────┘
 4         │
 5         ▼
 6┌─────────────────┐
 7│  Fetch Code     │
 8└────────┬────────┘
 9         │
10         ▼
11┌─────────────────┐
12│   Run Tests     │────── fail ──┐
13└────────┬────────┘               │
14         │ pass                   │
15         ▼                        │
16┌─────────────────┐               │
17│     Build       │────── fail ───┤
18└────────┬────────┘               │
19         │ success                │
20         ▼                        │
21┌─────────────────┐               │
22│     Deploy      │────── fail ───┤
23└────────┬────────┘               │
24         │ success                │
25         ▼                        │
26┌─────────────────┐               │
27│ Health Check    │────── fail ───┤
28└────────┬────────┘               │
29         │ pass                   │
30         ▼                        │
31┌─────────────────┐               │
32│    Success      │               │
33└─────────────────┘               │
34                                  │
35         ┌────────────────────────┘
36         │
37         ▼
38┌─────────────────┐
39│    Rollback     │
40└─────────────────┘

هر مرحله می‌تواند موفق یا ناموفق باشد. اگر ناموفق شد، به مسیر rollback می‌رود.

منابع یادگیری

مستندات و مقالات