ai

CircleCI Orbs กับ SaaS Architecture — วิธีสร้าง

CircleCI Orbs กับ SaaS Architecture — วิธีสร้าง

CircleCI Orbs สำหรับ SaaS

CircleCI Orbs กับ SaaS Architecture — วิธีสร้าง

SaaS Architecture มักประกอบด้วยหลาย Microservices, Database, Message Queue, Cache และ External Services การสร้าง CI/CD Pipeline ที่มีประสิทธิภาพต้องจัดการ Build, Test และ Deploy หลาย Service พร้อมกัน CircleCI Orbs ช่วยลดความซับซ้อนด้วย Reusable Configuration

เนื้อหาเกี่ยวข้อง — ดูเพิ่มเติมเรื่อง คู่มือฉบับสมบูรณ์ PlanetScale Vitess AR VR Development 2026: เปลี่ยนโลกด้วยเท…

บทความนี้แสดงวิธีสร้าง CI/CD Pipeline สำหรับ SaaS ที่ครอบคลุม Multi-service Build, Database Migration, Feature Flags, Environment Promotion และ Deployment Strategies ต่างๆ

เนื้อหาเกี่ยวข้อง — แนะนำให้อ่าน PagerDuty Incident Pod Scheduling — จัดการ

Multi-service CI/CD Pipeline

# .circleci/config.yml — SaaS Multi-service Pipeline

version: 2.1



orbs:

  aws-ecr: circleci/aws-ecr@9.0.4

  aws-eks: circleci/aws-eks@2.2.0

  slack: circleci/slack@4.13.3

  node: circleci/node@5.2.0

  python: circleci/python@2.1.1



# Parameters สำหรับ Dynamic Configuration

parameters:

  run-api:

    type: boolean

    default: false

  run-web:

    type: boolean

    default: false

  run-worker:

    type: boolean

    default: false

  run-all:

    type: boolean

    default: false



# Executors

executors:

  node-executor:

    docker:

      - image: cimg/node:20.11

    resource_class: medium

  python-executor:

    docker:

      - image: cimg/python:3.12

      - image: cimg/postgres:16.1  # Test Database

        environment:

          POSTGRES_DB: test_db

          POSTGRES_USER: test

          POSTGRES_PASSWORD: test

      - image: cimg/redis:7.2      # Test Redis

    resource_class: medium



# Reusable Commands

commands:

  setup-env:

    parameters:

      service:

        type: string

    steps:

      - checkout

      - run:

          name: Setup Environment

          command: |

            echo "SERVICE=<< parameters.service >>" >> $BASH_ENV

            echo "IMAGE_TAG=" >> $BASH_ENV

            echo "REGISTRY=.dkr.ecr..amazonaws.com" >> $BASH_ENV



  run-migrations:

    parameters:

      environment:

        type: string

    steps:

      - run:

          name: Run Database Migrations

          command: |

            cd services/api

            # Dry-run migration ก่อน

            python manage.py migrate --check

            # รัน Migration จริง

            python manage.py migrate --no-input

          environment:

            DATABASE_URL: << parameters.environment >>



  notify-slack:

    parameters:

      status:

        type: string

      service:

        type: string

    steps:

      - slack/notify:

          channel: deployments

          event: << parameters.status >>

          template: |

            {

              "blocks": [

                {

                  "type": "section",

                  "text": {

                    "type": "mrkdwn",

                    "text": "<< parameters.status >> | *<< parameters.service >>* deployed to \nCommit:  by "

                  }

                }

              ]

            }



# Jobs

jobs:

  # Path Filtering — ตรวจสอบว่า Service ไหนเปลี่ยน

  detect-changes:

    docker:

      - image: cimg/base:current

    steps:

      - checkout

      - run:

          name: Detect Changed Services

          command: |

            # เปรียบเทียบกับ main branch

            CHANGED=$(git diff --name-only origin/main...HEAD)

            echo "Changed files:"

            echo "$CHANGED"



            API_CHANGED=$(echo "$CHANGED" | grep -c "services/api/" || true)

            WEB_CHANGED=$(echo "$CHANGED" | grep -c "services/web/" || true)

            WORKER_CHANGED=$(echo "$CHANGED" | grep -c "services/worker/" || true)

            SHARED_CHANGED=$(echo "$CHANGED" | grep -c "shared/" || true)



            # ถ้า Shared เปลี่ยน Build ทุก Service

            if [ "$SHARED_CHANGED" -gt 0 ]; then

              echo '{"run-all": true}' > /tmp/pipeline-params.json

            else

              echo "{\"run-api\": $([ $API_CHANGED -gt 0 ] && echo true || echo false), \"run-web\": $([ $WEB_CHANGED -gt 0 ] && echo true || echo false), \"run-worker\": $([ $WORKER_CHANGED -gt 0 ] && echo true || echo false)}" > /tmp/pipeline-params.json

            fi



            cat /tmp/pipeline-params.json

      - persist_to_workspace:

          root: /tmp

          paths: [pipeline-params.json]



  # Build & Test API Service

  build-api:

    executor: python-executor

    steps:

      - setup-env:

          service: api

      - python/install-packages:

          pkg-manager: pip

          app-dir: services/api

      - run:

          name: Run Linting

          command: |

            cd services/api

            ruff check .

            mypy .

      - run:

          name: Run Tests

          command: |

            cd services/api

            pytest --cov=. --cov-report=xml --junitxml=test-results/results.xml -v

      - store_test_results:

          path: services/api/test-results

      - store_artifacts:

          path: services/api/coverage.xml



  # Build & Test Web Service

  build-web:

    executor: node-executor

    steps:

      - setup-env:

          service: web

      - node/install-packages:

          app-dir: services/web

      - run:

          name: Lint & Type Check

          command: |

            cd services/web

            npm run lint

            npm run type-check

      - run:

          name: Run Tests

          command: |

            cd services/web

            npm run test -- --coverage --ci

      - run:

          name: Build

          command: |

            cd services/web

            npm run build

      - persist_to_workspace:

          root: .

          paths: [services/web/dist]



  # Docker Build & Push

  docker-build:

    parameters:

      service:

        type: string

    docker:

      - image: cimg/base:current

    steps:

      - setup-env:

          service: << parameters.service >>

      - setup_remote_docker:

          docker_layer_caching: true

      - run:

          name: Build & Push Docker Image

          command: |

            aws ecr get-login-password | docker login --username AWS --password-stdin $REGISTRY

            docker build -t $REGISTRY/<< parameters.service >>:$IMAGE_TAG \

              -f services/<< parameters.service >>/Dockerfile \

              --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \

              --build-arg VCS_REF=$CIRCLE_SHA1 \

              .

            docker push $REGISTRY/<< parameters.service >>:$IMAGE_TAG



  # Deploy to Environment

  deploy:

    parameters:

      service:

        type: string

      environment:

        type: string

    docker:

      - image: cimg/base:current

    steps:

      - checkout

      - run:

          name: Deploy << parameters.service >> to << parameters.environment >>

          command: |

            # Update Kubernetes Deployment

            kubectl set image deployment/<< parameters.service >> \

              << parameters.service >>=$REGISTRY/<< parameters.service >>:$IMAGE_TAG \

              -n << parameters.environment >>



            # Wait for Rollout

            kubectl rollout status deployment/<< parameters.service >> \

              -n << parameters.environment >> --timeout=300s

      - notify-slack:

          status: pass

          service: << parameters.service >>



# Workflows

workflows:

  build-test-deploy:

    jobs:

      - detect-changes:

          filters:

            branches:

              ignore: main



      - build-api:

          requires: [detect-changes]

          filters:

            branches:

              ignore: main



      - build-web:

          requires: [detect-changes]



      - docker-build:

          name: docker-api

          service: api

          requires: [build-api]

          context: aws-prod



      - docker-build:

          name: docker-web

          service: web

          requires: [build-web]

          context: aws-prod



      - deploy:

          name: deploy-staging-api

          service: api

          environment: staging

          requires: [docker-api]

          context: aws-staging



      - deploy:

          name: deploy-staging-web

          service: web

          environment: staging

          requires: [docker-web]

          context: aws-staging



      # Production Deploy — ต้อง Approve

      - hold-production:

          type: approval

          requires:

            - deploy-staging-api

            - deploy-staging-web



      - deploy:

          name: deploy-prod-api

          service: api

          environment: production

          requires: [hold-production]

          context: aws-prod



      - deploy:

          name: deploy-prod-web

          service: web

          environment: production

          requires: [hold-production]

          context: aws-prod

Database Migration Pipeline

CircleCI Orbs กับ SaaS Architecture — วิธีสร้าง
# migration-check.py — ตรวจสอบ Migration ก่อน Deploy

import subprocess

import sys

import json



def check_migration_safety(migration_file):

    """ตรวจสอบว่า Migration ปลอดภัยสำหรับ Zero-downtime"""

    dangerous_ops = [

        "DROP TABLE",

        "DROP COLUMN",

        "ALTER COLUMN",   # เปลี่ยน Type อาจ Lock Table

        "RENAME TABLE",

        "RENAME COLUMN",

        "NOT NULL",       # เพิ่ม NOT NULL constraint อาจ Fail

    ]



    with open(migration_file, "r") as f:

        content = f.read().upper()



    issues = []

    for op in dangerous_ops:

        if op in content:

            issues.append(f"Dangerous operation found: {op}")



    if issues:

        print(f"MIGRATION SAFETY CHECK FAILED: {migration_file}")

        for issue in issues:

            print(f"  - {issue}")

        print("\nUse Expand-Contract pattern:")

        print("  Phase 1 (Expand): Add new columns, keep old ones")

        print("  Phase 2 (Deploy): Update code to use both")

        print("  Phase 3 (Contract): Remove old columns")

        return False



    print(f"MIGRATION SAFE: {migration_file}")

    return True



# ตรวจสอบ Migration ใน CI

def ci_check():

    result = subprocess.run(

        ["python", "manage.py", "showmigrations", "--plan", "--format", "json"],

        capture_output=True, text=True,

    )

    pending = json.loads(result.stdout)

    all_safe = True



    for migration in pending:

        if not migration.get("applied"):

            sql = subprocess.run(

                ["python", "manage.py", "sqlmigrate",

                 migration["app"], migration["name"]],

                capture_output=True, text=True,

            )

            # เขียน SQL ไปไฟล์ชั่วคราว

            tmp = f"/tmp/{migration['name']}.sql"

            with open(tmp, "w") as f:

                f.write(sql.stdout)



            if not check_migration_safety(tmp):

                all_safe = False



    if not all_safe:

        sys.exit(1)

    print("All migrations are safe for zero-downtime deployment")



ci_check()

Feature Flags Integration

# feature_flags.py — Feature Flags สำหรับ SaaS Deployment

import os

import json

import hashlib

from datetime import datetime



class FeatureFlagManager:

    """จัดการ Feature Flags สำหรับ Gradual Rollout"""



    def __init__(self, config_path="feature_flags.json"):

        with open(config_path) as f:

            self.flags = json.load(f)



    def is_enabled(self, flag_name, user_id=None, tenant_id=None):

        """ตรวจสอบว่า Feature Flag เปิดหรือไม่"""

        flag = self.flags.get(flag_name)

        if not flag:

            return False



        # Global kill switch

        if not flag.get("enabled", False):

            return False



        # Check rollout percentage

        percentage = flag.get("rollout_percentage", 100)

        if percentage < 100 and user_id:

            # Consistent hashing — User เดิมได้ผลเดิมเสมอ

            hash_input = f"{flag_name}:{user_id}"

            hash_val = int(hashlib.md5(hash_input.encode()).hexdigest(), 16)

            if (hash_val % 100) >= percentage:

                return False



        # Check tenant allowlist

        allowed_tenants = flag.get("allowed_tenants", [])

        if allowed_tenants and tenant_id:

            if tenant_id not in allowed_tenants:

                return False



        # Check date range

        start = flag.get("start_date")

        end = flag.get("end_date")

        now = datetime.now().isoformat()

        if start and now < start:

            return False

        if end and now > end:

            return False



        return True



    def get_all_flags(self, user_id=None, tenant_id=None):

        """ดึง Flag ทั้งหมดพร้อมสถานะ"""

        result = {}

        for name in self.flags:

            result[name] = self.is_enabled(name, user_id, tenant_id)

        return result



# feature_flags.json

# {

#   "new_dashboard": {

#     "enabled": true,

#     "rollout_percentage": 25,

#     "allowed_tenants": ["tenant_001", "tenant_002"],

#     "description": "New dashboard UI"

#   },

#   "ai_assistant": {

#     "enabled": true,

#     "rollout_percentage": 10,

#     "start_date": "2025-01-01",

#     "description": "AI-powered assistant feature"

#   }

# }



# ใช้ใน CI/CD — Deploy แล้วเปิด Feature Flag ทีละ %

# Step 1: Deploy code (Feature Flag off)

# Step 2: Enable 5% → Monitor

# Step 3: Enable 25% → Monitor

# Step 4: Enable 100% → Stable

Deployment Strategies สำหรับ SaaS

  • Blue-Green Deployment: มี 2 Environment (Blue/Green) Deploy ไป Green แล้ว Switch Traffic ทั้งหมด Rollback ง่ายแค่ Switch กลับ
  • Canary Deployment: Deploy Version ใหม่ให้ 5% ของ Traffic ก่อน Monitor Error Rate และ Latency แล้วค่อยขยายเป็น 25%, 50%, 100%
  • Rolling Update: อัปเดต Pod ทีละตัว ค่อยเป็นค่อยไป ไม่ต้องมี Environment เพิ่ม แต่ Rollback ช้ากว่า
  • Feature Flags: Deploy Code ทั้งหมดแต่ซ่อนหลัง Feature Flag เปิดให้ User ทีละกลุ่ม ยืดหยุ่นที่สุดแต่ซับซ้อนกว่า
  • Ring Deployment: แบ่ง Users เป็น Rings (Internal → Beta → GA) Deploy ไปทีละ Ring เหมาะกับ SaaS ที่มี Multi-tenant

CircleCI Orbs คืออะไร

CircleCI Orbs เป็น Reusable Configuration Packages รวม Jobs, Commands และ Executors ลดการเขียน Config ซ้ำ มี Orbs สำเร็จรูปสำหรับ AWS, Docker, Kubernetes, Slack ใช้แค่ Reference แล้ว Config ไม่กี่บรรทัด ช่วยให้สร้าง Pipeline ได้เร็ว

แนะนำเพิ่มเติม — XM Signal

เนื้อหาเกี่ยวข้อง — Scrum Tool คืออะไร — ข้อมูลครบถ้วน 2026

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง