Technology

CircleCI Orbs Cost Optimization ลดค่าใช้จ่าย

circleci orbs cost optimization ลดคาใชจาย
CircleCI Orbs Cost Optimization ลดค่าใช้จ่าย | SiamCafe Blog
2025-11-15· อ. บอม — SiamCafe.net· 1,919 คำ

CircleCI Orbs Cost Optimization ลดค่าใช้จ่าย

CircleCI เป็น CI/CD platform ยอดนิยมที่ใช้ credits-based pricing model Orbs คือ reusable packages ของ CircleCI configuration ที่ช่วยลด boilerplate และเพิ่ม consistency ข้าม projects Cost Optimization คือการลดค่าใช้จ่ายของ CI/CD pipeline โดยไม่กระทบ quality และ speed บทความนี้อธิบายวิธีใช้ Orbs อย่างมีประสิทธิภาพ ลด build time ลด credit usage และ optimize resource allocation เพื่อประหยัดค่าใช้จ่ายสำหรับทีม development

CircleCI Pricing & Credits

# pricing.py — CircleCI pricing model
import json

class CircleCIPricing:
    PLANS = {
        "free": {
            "name": "Free Plan",
            "credits": "30,000 credits/month",
            "users": "1 user",
            "parallelism": "1x",
            "cost": "$0",
        },
        "performance": {
            "name": "Performance Plan",
            "credits": "Custom (pay-as-you-go)",
            "users": "Unlimited",
            "parallelism": "Up to 80x",
            "cost": "From $15/month + credits",
        },
        "scale": {
            "name": "Scale Plan",
            "credits": "Custom + self-hosted runners",
            "users": "Unlimited",
            "parallelism": "Unlimited",
            "cost": "Custom pricing",
        },
    }

    CREDIT_RATES = {
        "docker_medium": {"name": "Docker Medium (2 vCPU, 4GB)", "credits": "10 credits/min", "use": "Default, general builds"},
        "docker_large": {"name": "Docker Large (4 vCPU, 8GB)", "credits": "20 credits/min", "use": "Heavy builds, compilation"},
        "docker_xlarge": {"name": "Docker XLarge (8 vCPU, 16GB)", "credits": "40 credits/min", "use": "Parallel tests, large projects"},
        "machine_medium": {"name": "Machine Medium (2 vCPU, 8GB)", "credits": "40 credits/min", "use": "Docker-in-Docker, privileged"},
        "machine_large": {"name": "Machine Large (4 vCPU, 16GB)", "credits": "80 credits/min", "use": "Heavy Docker builds"},
        "macos_medium": {"name": "macOS Medium", "credits": "50 credits/min", "use": "iOS/macOS builds"},
        "arm_medium": {"name": "ARM Medium", "credits": "13 credits/min", "use": "ARM builds (cheaper!)"},
    }

    def show_plans(self):
        print("=== CircleCI Plans ===\n")
        for key, plan in self.PLANS.items():
            print(f"[{plan['name']}] {plan['credits']} | {plan['cost']}")

    def show_rates(self):
        print(f"\n=== Credit Rates ===")
        for key, rate in self.CREDIT_RATES.items():
            print(f"  [{rate['name']}] {rate['credits']} — {rate['use']}")

    def cost_calculator(self):
        print(f"\n=== Monthly Cost Example ===")
        builds = [
            {"name": "Unit Tests", "resource": "docker_medium", "mins": 5, "runs": 200, "credits_per_min": 10},
            {"name": "Integration Tests", "resource": "docker_large", "mins": 10, "runs": 100, "credits_per_min": 20},
            {"name": "Docker Build", "resource": "machine_medium", "mins": 8, "runs": 100, "credits_per_min": 40},
            {"name": "Deploy", "resource": "docker_medium", "mins": 3, "runs": 50, "credits_per_min": 10},
        ]
        total = 0
        for b in builds:
            credits = b["mins"] * b["runs"] * b["credits_per_min"]
            total += credits
            print(f"  {b['name']}: {b['mins']}min × {b['runs']} runs × {b['credits_per_min']} = {credits:,} credits")
        cost = total * 0.0006
        print(f"\n  Total: {total:,} credits (~/month)")

pricing = CircleCIPricing()
pricing.show_plans()
pricing.show_rates()
pricing.cost_calculator()

Orbs สำหรับ Cost Optimization

# orbs_optimization.py — Orbs for cost optimization
import json

class OrbsOptimization:
    USEFUL_ORBS = {
        "node": {
            "name": "circleci/node",
            "optimization": "Built-in caching สำหรับ node_modules — ลด install time 50-80%",
            "config": "node/install: with_cache: true",
        },
        "docker": {
            "name": "circleci/docker",
            "optimization": "Docker Layer Caching (DLC) — ลด Docker build time 60%+",
            "config": "docker/build: docker-layer-caching: true",
        },
        "aws_cli": {
            "name": "circleci/aws-cli",
            "optimization": "Pre-installed AWS CLI — ไม่ต้อง install ทุก build (ลด 1-2 min)",
            "config": "aws-cli/setup",
        },
        "slack": {
            "name": "circleci/slack",
            "optimization": "Notify เฉพาะ failure — ลด noise, ไม่เปลือง build time",
            "config": "slack/notify: event: fail",
        },
    }

    OPTIMIZATION_CONFIG = """
# .circleci/config.yml — Optimized configuration
version: 2.1

orbs:
  node: circleci/node@5.2
  docker: circleci/docker@2.6
  slack: circleci/slack@4.13

# 1. Use smallest resource class possible
executors:
  small:
    docker:
      - image: cimg/node:20.11
    resource_class: small    # 1 vCPU, 2GB — 5 credits/min (ถูกที่สุด!)
  medium:
    docker:
      - image: cimg/node:20.11
    resource_class: medium   # 2 vCPU, 4GB — 10 credits/min

jobs:
  # 2. Cache aggressively
  test:
    executor: small          # ใช้ small สำหรับ tests!
    steps:
      - checkout
      - node/install-packages:
          cache-path: node_modules
          override-ci-command: npm ci --prefer-offline
      - run:
          name: Run Tests
          command: npm test
      - store_test_results:
          path: test-results

  # 3. Build only when needed
  build:
    executor: medium
    steps:
      - checkout
      - node/install-packages:
          cache-path: node_modules
      - run: npm run build
      - persist_to_workspace:
          root: .
          paths: [dist]

workflows:
  # 4. Filter branches — don't build everything
  main:
    jobs:
      - test:
          filters:
            branches:
              ignore: [docs/*, dependabot/*]  # Skip non-code branches
      - build:
          requires: [test]
          filters:
            branches:
              only: [main, develop]  # Build only main branches
"""

    def show_orbs(self):
        print("=== Useful Orbs for Optimization ===\n")
        for key, orb in self.USEFUL_ORBS.items():
            print(f"[{orb['name']}]")
            print(f"  Optimization: {orb['optimization']}")
            print()

    def show_config(self):
        print("=== Optimized Config ===")
        print(self.OPTIMIZATION_CONFIG[:600])

orbs = OrbsOptimization()
orbs.show_orbs()
orbs.show_config()

Cost Reduction Strategies

# strategies.py — Cost reduction strategies
import json

class CostStrategies:
    STRATEGIES = {
        "resource_class": {
            "name": "1. ใช้ Resource Class เล็กที่สุด",
            "saving": "50-75%",
            "detail": "small (5 cr/min) แทน medium (10 cr/min) สำหรับ lint, unit tests",
            "example": "resource_class: small → ลดจาก 10 เป็น 5 credits/min",
        },
        "caching": {
            "name": "2. Cache ทุกอย่างที่ cache ได้",
            "saving": "30-60%",
            "detail": "node_modules, pip packages, Docker layers, build artifacts",
            "example": "npm ci: 120s → 15s (with cache) = ลด 87%",
        },
        "parallelism": {
            "name": "3. Parallelism อย่างชาญฉลาด",
            "saving": "20-40%",
            "detail": "แบ่ง tests เป็นกลุ่ม รัน parallel — ลด wall time แต่ใช้ credits เท่าเดิม",
            "example": "parallelism: 4 → tests 20min → 5min (same total credits, faster feedback)",
        },
        "conditional": {
            "name": "4. Conditional Execution",
            "saving": "30-50%",
            "detail": "รัน jobs เฉพาะเมื่อ files ที่เกี่ยวข้องเปลี่ยน",
            "example": "path-filtering orb: skip backend tests ถ้าเปลี่ยนแค่ frontend",
        },
        "branch_filter": {
            "name": "5. Branch Filtering",
            "saving": "20-40%",
            "detail": "ไม่ build ทุก branch — skip docs, dependabot, feature branches",
            "example": "filters: branches: ignore: [docs/*, dependabot/*]",
        },
        "docker_layer": {
            "name": "6. Docker Layer Caching (DLC)",
            "saving": "40-70% Docker build time",
            "detail": "Cache Docker layers ระหว่าง builds — จ่าย 200 credits/job แต่คุ้มถ้า build > 5min",
            "example": "Docker build 15min → 4min with DLC",
        },
        "arm": {
            "name": "7. ใช้ ARM Resource Class",
            "saving": "20-30%",
            "detail": "ARM medium = 13 credits/min vs x86 medium = 10 credits/min (comparable performance)",
            "example": "resource_class: arm.medium → ถูกกว่าสำหรับ compatible workloads",
        },
        "self_hosted": {
            "name": "8. Self-Hosted Runners",
            "saving": "50-80%",
            "detail": "รัน builds บน infrastructure ของตัวเอง — ไม่เสีย credits",
            "example": "Scale plan + self-hosted runners สำหรับ heavy builds",
        },
    }

    def show_strategies(self):
        print("=== Cost Reduction Strategies ===\n")
        for key, strat in self.STRATEGIES.items():
            print(f"[{strat['name']}] Saving: {strat['saving']}")
            print(f"  {strat['detail']}")
            print()

    def before_after(self):
        print("=== Before vs After Optimization ===")
        print(f"\n  Before (Unoptimized):")
        print(f"    Resource: medium (10 cr/min) for everything")
        print(f"    No caching, no filtering")
        print(f"    Monthly: 500,000 credits (~$300/month)")
        print(f"\n  After (Optimized):")
        print(f"    Resource: small for lint/test, medium for build")
        print(f"    Full caching, branch filtering, conditional jobs")
        print(f"    Monthly: 150,000 credits (~$90/month)")
        print(f"\n  Saving: 70% ($210/month)")

strat = CostStrategies()
strat.show_strategies()
strat.before_after()

Monitoring & Analytics

# monitoring.py — CI/CD cost monitoring
import json
import random

class CICDMonitoring:
    INSIGHTS_API = """
# circleci_insights.py — CircleCI Insights API
import requests

class CircleCIInsights:
    def __init__(self, token, org_slug):
        self.token = token
        self.org_slug = org_slug
        self.base_url = "https://circleci.com/api/v2"
        self.headers = {"Circle-Token": token}
    
    def get_project_workflows(self, project_slug):
        resp = requests.get(
            f"{self.base_url}/insights/{project_slug}/workflows",
            headers=self.headers,
        )
        return resp.json().get("items", [])
    
    def get_workflow_metrics(self, project_slug, workflow_name):
        resp = requests.get(
            f"{self.base_url}/insights/{project_slug}/workflows/{workflow_name}",
            headers=self.headers,
        )
        return resp.json()
    
    def get_job_metrics(self, project_slug, workflow_name):
        resp = requests.get(
            f"{self.base_url}/insights/{project_slug}/workflows/{workflow_name}/jobs",
            headers=self.headers,
        )
        return resp.json().get("items", [])
    
    def find_expensive_jobs(self, project_slug, workflow_name):
        jobs = self.get_job_metrics(project_slug, workflow_name)
        sorted_jobs = sorted(jobs, key=lambda j: j.get("metrics", {}).get("duration_metrics", {}).get("median", 0), reverse=True)
        return sorted_jobs[:5]

# Usage
# insights = CircleCIInsights("your-token", "gh/org")
# expensive = insights.find_expensive_jobs("gh/org/repo", "build-and-test")
"""

    def show_api(self):
        print("=== CircleCI Insights API ===")
        print(self.INSIGHTS_API[:500])

    def cost_dashboard(self):
        print(f"\n=== CI/CD Cost Dashboard ===")
        jobs = [
            {"name": "unit-tests", "avg_min": random.uniform(2, 8), "runs": random.randint(100, 300), "resource": "small"},
            {"name": "integration-tests", "avg_min": random.uniform(5, 15), "runs": random.randint(50, 150), "resource": "medium"},
            {"name": "docker-build", "avg_min": random.uniform(3, 12), "runs": random.randint(50, 100), "resource": "medium"},
            {"name": "deploy", "avg_min": random.uniform(1, 5), "runs": random.randint(20, 60), "resource": "small"},
        ]
        rates = {"small": 5, "medium": 10, "large": 20}
        total_credits = 0
        for j in jobs:
            credits = j["avg_min"] * j["runs"] * rates[j["resource"]]
            total_credits += credits
            print(f"  {j['name']:<20} {j['avg_min']:>5.1f}min × {j['runs']:>3} runs = {credits:>8,.0f} credits")
        cost = total_credits * 0.0006
        print(f"\n  Total: {total_credits:,.0f} credits (~/month)")

    def optimization_tips(self):
        print(f"\n=== Quick Wins ===")
        tips = [
            "ลด resource_class ของ lint job จาก medium เป็น small → ลด 50%",
            "เพิ่ม cache สำหรับ npm/pip → ลด install time 60-80%",
            "Skip CI สำหรับ docs-only changes → ลด 20-30% runs",
            "ใช้ path-filtering → รัน tests เฉพาะ changed modules",
        ]
        for tip in tips:
            print(f"  • {tip}")

mon = CICDMonitoring()
mon.show_api()
mon.cost_dashboard()
mon.optimization_tips()

Custom Orb สำหรับองค์กร

# custom_orb.py — Custom Orb for organization
import json

class CustomOrb:
    ORB_TEMPLATE = """
# orb.yml — Custom cost-optimized orb
version: 2.1
description: "Organization CI/CD orb with cost optimization built-in"

executors:
  # Pre-defined optimized executors
  light:
    docker:
      - image: cimg/base:current
    resource_class: small
  standard:
    docker:
      - image: cimg/node:20.11
    resource_class: medium
  heavy:
    docker:
      - image: cimg/node:20.11
    resource_class: large

commands:
  # Cached install with fallback
  smart-install:
    parameters:
      package-manager:
        type: enum
        enum: [npm, yarn, pnpm]
        default: npm
    steps:
      - restore_cache:
          keys:
            - deps-v2-{{ checksum "package-lock.json" }}
            - deps-v2-
      - run:
          name: Install Dependencies
          command: |
            if [ -d node_modules ]; then
              echo "Cache hit - skipping install"
            else
              npm ci --prefer-offline
            fi
      - save_cache:
          key: deps-v2-{{ checksum "package-lock.json" }}
          paths: [node_modules]

  # Conditional step based on changed files
  run-if-changed:
    parameters:
      paths:
        type: string
      command:
        type: string
    steps:
      - run:
          name: Check changed files
          command: |
            CHANGED=$(git diff --name-only HEAD~1 | grep -E "<< parameters.paths >>" || true)
            if [ -z "$CHANGED" ]; then
              echo "No relevant changes — skipping"
              circleci-agent step halt
            fi
      - run:
          name: Execute
          command: << parameters.command >>

jobs:
  test:
    executor: light
    steps:
      - checkout
      - smart-install
      - run: npm test
"""

    def show_template(self):
        print("=== Custom Orb Template ===")
        print(self.ORB_TEMPLATE[:600])

    def publish_steps(self):
        print(f"\n=== Publish Custom Orb ===")
        steps = [
            "circleci namespace create my-org github",
            "circleci orb create my-org/cost-optimized",
            "circleci orb pack src/ > orb.yml",
            "circleci orb validate orb.yml",
            "circleci orb publish orb.yml my-org/cost-optimized@0.1.0",
        ]
        for i, step in enumerate(steps, 1):
            print(f"  {i}. {step}")

orb = CustomOrb()
orb.show_template()
orb.publish_steps()

FAQ - คำถามที่พบบ่อย

Q: CircleCI แพงไหม?

A: Free plan: 30,000 credits/month (พอสำหรับ side projects) Performance plan: เริ่ม $15/month + credits ($0.0006/credit) ทีมเล็ก (5 devs): ~$50-150/month (optimized) ทีมกลาง (20 devs): ~$200-800/month ลดค่าใช้จ่าย: optimize resource class, caching, branch filtering

Q: Orbs ช่วยลดค่าใช้จ่ายอย่างไร?

A: 1. Built-in caching (node, python orbs) — ลด install time 50-80% 2. Pre-configured executors — ใช้ resource class ที่เหมาะสม 3. Reusable config — ลด config errors ที่ทำให้ build ช้า 4. Docker Layer Caching orb — ลด Docker build time 60%+ 5. Path-filtering orb — skip jobs ที่ไม่เกี่ยวข้อง

Q: Resource class ไหนคุ้มที่สุด?

A: small (5 cr/min): lint, unit tests, simple scripts → ถูกที่สุด medium (10 cr/min): general builds, integration tests → default ดี large (20 cr/min): parallel tests, heavy compilation → ใช้เมื่อจำเป็น machine (40 cr/min): Docker-in-Docker → แพง ใช้เฉพาะ Docker builds กฎ: เริ่มจาก small แล้ว upgrade เมื่อ job ช้าเกินไป

Q: Self-hosted runners คุ้มไหม?

A: คุ้มเมื่อ: ใช้ credits > 500,000/month, มี idle servers, ต้องการ custom hardware (GPU) ไม่คุ้มเมื่อ: ทีมเล็ก, ไม่มี DevOps ดูแล infrastructure, ใช้ credits น้อย ข้อดี: ไม่เสีย credits, custom environment, faster (local network) ข้อเสีย: ต้องดูแล infrastructure, security, scaling เอง

📖 บทความที่เกี่ยวข้อง

CircleCI Orbs Team Productivityอ่านบทความ → CircleCI Orbs Zero Downtime Deploymentอ่านบทความ → CircleCI Orbs Performance Tuning เพิ่มความเร็วอ่านบทความ → CircleCI Orbs DNS Managementอ่านบทความ →

📚 ดูบทความทั้งหมด →