#ทำ CI/CD ด้วย GitHub Actions พร้อมตรวจสอบ Code Quality ด้วย SonarQube Cloud

การทำ CI/CD (Continuous Integration / Continuous Delivery) ช่วยให้ทีมพัฒนาซอฟต์แวร์สามารถตรวจสอบ Build, Test และ Deploy ระบบได้อัตโนมัติ แต่การที่โปรแกรม Build ผ่านหรือ Unit Test ผ่านไม่ได้หมายความว่า Source Code มีคุณภาพดีเสมอไป

บทความนี้จึงเพิ่ม SonarQube Cloud เข้าไปใน GitHub Actions Pipeline เพื่อวิเคราะห์คุณภาพและความปลอดภัยของ Source Code ก่อนอนุญาตให้เข้าสู่ขั้นตอน Deploy

หมายเหตุ: ผลิตภัณฑ์ Cloud ของ Sonar ปัจจุบันใช้ชื่อ SonarQube Cloud (เดิมรู้จักในชื่อ SonarCloud)


#1. Architecture ที่เราจะสร้าง

Developer
    │
    │ git push / Pull Request
    ▼
GitHub Repository
    │
    ▼
GitHub Actions
    │
    ├── Checkout Source Code
    │
    ├── Install Dependencies
    │
    ├── Build
    │
    ├── Unit Test
    │
    ├── Generate Test Coverage
    │
    └── SonarQube Scan
              │
              ▼
       SonarQube Cloud
              │
              ├── Reliability
              ├── Security
              ├── Maintainability
              ├── Coverage
              └── Duplications
              │
              ▼
         Quality Gate
          /       \
       PASS       FAIL
        │           │
        ▼           └── Stop
      Deploy
        │
        ▼
   Production

แนวคิดสำคัญคือ ไม่ควร Deploy เพียงเพราะ Build ผ่าน แต่ควรให้ Automated Test และ Quality Gate เป็นส่วนหนึ่งของเงื่อนไขก่อน Deploy


#2. CI/CD คืออะไร

#Continuous Integration (CI)

CI คือกระบวนการรวม Source Code และตรวจสอบโค้ดโดยอัตโนมัติ เช่น

Push Code
   ↓
Build
   ↓
Unit Test
   ↓
Coverage
   ↓
Static Analysis

GitHub Actions สามารถใช้เป็น CI Engine เพื่อรันขั้นตอนเหล่านี้ทุกครั้งที่เกิด push หรือ pull_request

#Continuous Delivery / Deployment (CD)

หลังจาก CI ผ่านแล้ว Pipeline สามารถดำเนินการต่อ เช่น

Quality Gate PASS
       ↓
Build Docker Image
       ↓
Push Image
       ↓
Deploy Staging
       ↓
Deploy Production

ในระบบจริง Production Environment อาจเพิ่ม Manual Approval ก่อน Deploy ได้


#3. SonarQube Cloud ทำหน้าที่อะไร

SonarQube Cloud เป็นบริการวิเคราะห์ Source Code แบบ Static Analysis โดยนำผลการวิเคราะห์มาใช้ตรวจสอบประเด็นด้านคุณภาพและความปลอดภัย เช่น

  • Reliability
  • Security
  • Maintainability
  • Security Hotspots
  • Test Coverage
  • Duplicated Code

ผลลัพธ์จะถูกนำไปประเมินด้วย Quality Gate

ตัวอย่างแนวคิด:

Source Code
    │
    ▼
SonarQube Scanner
    │
    ▼
SonarQube Cloud
    │
    ▼
Quality Gate
    │
    ├── Passed  → Pipeline ไปต่อ
    └── Failed  → หยุด / ไม่อนุญาต Merge หรือ Deploy

#4. สิ่งที่ต้องเตรียม

Workshop นี้สมมติว่าใช้ Node.js เป็นตัวอย่าง แต่แนวคิดเดียวกันสามารถประยุกต์กับ Java, Spring Boot, React, Vue, Angular, Python, Go, PHP/Laravel และภาษาอื่นที่ SonarQube Cloud รองรับ

สิ่งที่ต้องมี:

  1. GitHub Account
  2. GitHub Repository
  3. SonarQube Cloud Account
  4. Project ที่มี Source Code
  5. Unit Test
  6. GitHub Actions

เว็บไซต์ที่เกี่ยวข้อง:


#5. เชื่อม GitHub กับ SonarQube Cloud

เข้า SonarQube Cloud และ Sign in ด้วย GitHub จากนั้นเชื่อม GitHub Organization/Repository ที่ต้องการวิเคราะห์

ขั้นตอนโดยภาพรวม:

SonarQube Cloud
      ↓
Analyze new project
      ↓
Connect GitHub
      ↓
เลือก Organization
      ↓
Import Repository
      ↓
สร้าง SonarQube Cloud Project

หลังสร้าง Project แล้ว ให้จดค่า:

Organization Key
Project Key

ตัวอย่าง:

Organization: senpru
Project Key: senpru_demo-api

ค่าจริงต้องใช้ค่าที่ SonarQube Cloud กำหนดให้ Project ของคุณ


#6. สร้าง SONAR_TOKEN

การให้ GitHub Actions ส่งผลการวิเคราะห์ไป SonarQube Cloud จำเป็นต้องมี Authentication Token

สร้าง Token จาก SonarQube Cloud แล้วนำค่าไปเก็บใน GitHub Repository Secret

ห้ามนำ Token เขียนลง .github/workflows/*.yml, sonar-project.properties หรือ Commit ลง Repository

ไปที่ GitHub Repository:

Settings
  ↓
Secrets and variables
  ↓
Actions
  ↓
Secrets
  ↓
New repository secret

ตั้งชื่อ:

SONAR_TOKEN

แล้วใส่ Token เป็น Value

GitHub Actions จะเรียกใช้ด้วย:

${{ secrets.SONAR_TOKEN }}

#7. โครงสร้าง Project ตัวอย่าง

demo-project/
├── .github/
│   └── workflows/
│       └── ci.yml
├── src/
│   └── calculator.js
├── tests/
│   └── calculator.test.js
├── package.json
├── package-lock.json
└── sonar-project.properties

#8. Source Code ตัวอย่าง

ไฟล์:

src/calculator.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

module.exports = {
  add,
  subtract
};

#9. Unit Test ด้วย Jest

ไฟล์:

tests/calculator.test.js
const { add, subtract } = require("../src/calculator");

describe("Calculator", () => {

  test("2 + 3 = 5", () => {
    expect(add(2, 3)).toBe(5);
  });

  test("5 - 3 = 2", () => {
    expect(subtract(5, 3)).toBe(2);
  });

});

ติดตั้ง Jest:

npm install --save-dev jest

ตัวอย่าง package.json:

{
  "scripts": {
    "test": "jest",
    "test:coverage": "jest --coverage"
  },
  "devDependencies": {
    "jest": "^30.0.0"
  }
}

Version ในตัวอย่างเป็นเพียงแนวทาง ควรใช้ Version ที่เหมาะสมกับ Project จริงและ commit lock file ไว้เพื่อให้ CI ทำงานซ้ำได้อย่างสม่ำเสมอ

ทดลอง:

npm test

หรือสร้าง Coverage:

npm run test:coverage

จะได้ Directory ประมาณ:

coverage/
├── lcov-report/
└── lcov.info

ไฟล์สำคัญสำหรับ SonarQube คือ:

coverage/lcov.info

#10. สร้าง sonar-project.properties

สร้างไฟล์ที่ Root ของ Project:

sonar-project.properties

ตัวอย่าง:

sonar.projectKey=YOUR_PROJECT_KEY
sonar.organization=YOUR_ORGANIZATION_KEY

sonar.sources=src
sonar.tests=tests

sonar.javascript.lcov.reportPaths=coverage/lcov.info

sonar.sourceEncoding=UTF-8

เปลี่ยน:

YOUR_PROJECT_KEY
YOUR_ORGANIZATION_KEY

เป็นค่าจริงจาก SonarQube Cloud


#11. สร้าง GitHub Actions Workflow

สร้างไฟล์:

.github/workflows/ci.yml

ตัวอย่าง Pipeline:

name: CI with SonarQube Cloud

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

permissions:
  contents: read

jobs:
  test-and-scan:

    name: Build, Test and SonarQube Scan

    runs-on: ubuntu-latest

    steps:

      - name: Checkout source code
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests with coverage
        run: npm run test:coverage

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v7
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

Action versions เปลี่ยนได้ตามเวลา ควรตรวจสอบ Version ปัจจุบันจาก GitHub Marketplace หรือเอกสารของผู้พัฒนาก่อนนำไปใช้ใน Production


#12. ทำไม Checkout ต้องใช้ fetch-depth: 0

ตัวอย่าง:

- name: Checkout source code
  uses: actions/checkout@v6
  with:
    fetch-depth: 0

fetch-depth: 0 ทำให้ Runner ดึง Git History ทั้งหมด ซึ่งเป็นค่าที่เหมาะกับการวิเคราะห์ของ SonarQube ในหลายกรณี เช่น branch/SCM information และช่วยหลีกเลี่ยงปัญหาจาก shallow clone


#13. Workflow ทำงานอย่างไร

เมื่อ Developer:

git add .
git commit -m "Add calculator feature"
git push origin main

จะเกิด Pipeline:

Git Push
   │
   ▼
GitHub Actions
   │
   ├── Checkout
   │
   ├── Setup Node.js
   │
   ├── npm ci
   │
   ├── Jest
   │
   ├── Coverage
   │
   └── SonarQube Scan
             │
             ▼
       SonarQube Cloud

หาก Test ไม่ผ่าน Step จะล้มเหลวและ Scanner ที่อยู่ถัดไปจะไม่ทำงานตาม flow ปกติ


#14. ตรวจสอบผลบน SonarQube Cloud

หลัง Scan สำเร็จ เข้า Dashboard ของ Project

ข้อมูลที่ควรตรวจสอบ ได้แก่:

Reliability
Security
Maintainability
Security Hotspots
Coverage
Duplications

Dashboard ทำให้ทีมไม่ได้ดูเพียงว่า:

Test = PASS

แต่สามารถตรวจสอบภาพรวม:

Test              ✓
Coverage          ✓
Reliability       ✓
Security          ✓
Maintainability   ✓
Quality Gate      ✓

#15. Quality Gate

Quality Gate คือชุดเงื่อนไขสำหรับตัดสินว่า Code ที่วิเคราะห์มีคุณภาพตามเกณฑ์ที่กำหนดหรือไม่

แนวคิด:

              Analysis
                  │
                  ▼
             Quality Gate
               /      \
           Passed      Failed
             │            │
             ▼            ▼
          Merge/       Fix Code
          Deploy          │
                          └── Push ใหม่

แนวทางที่เหมาะสมคือเน้นคุณภาพของ New Code เพื่อไม่ให้ Technical Debt ใหม่เพิ่มเข้าสู่ระบบ


#16. Pull Request + SonarQube Cloud

Workflow กำหนด:

pull_request:
  branches:
    - main

ดังนั้นเมื่อ Developer สร้าง Pull Request:

feature/login
     │
     ▼
Pull Request → main
     │
     ▼
GitHub Actions
     │
     ├── Test
     ├── Coverage
     └── SonarQube Analysis

เมื่อ GitHub Integration ถูกตั้งค่าถูกต้อง SonarQube Cloud สามารถแสดงผล Quality Gate ในกระบวนการ Pull Request ได้

ทีมจึงสามารถใช้แนวคิด:

Pull Request
    │
    ├── Unit Test PASS
    ├── Build PASS
    ├── Sonar Analysis PASS
    └── Quality Gate PASS
            │
            ▼
        Allow Merge

ควรใช้ GitHub Branch Protection / Rulesets ร่วมด้วย เพื่อบังคับ Required Status Checks ตามนโยบายของทีม


#17. แยก CI และ CD

ระบบ Production ควรแยกความรับผิดชอบให้ชัดเจน

#CI

Checkout
   ↓
Install
   ↓
Build
   ↓
Test
   ↓
Coverage
   ↓
SonarQube Scan
   ↓
Quality Gate

#CD

CI PASS
   ↓
Build Docker Image
   ↓
Push Registry
   ↓
Deploy

ตัวอย่างแนวคิด Workflow:

jobs:

  ci:
    runs-on: ubuntu-latest

    steps:
      # Build
      # Test
      # SonarQube Scan

  deploy:
    needs: ci

    if: github.ref == 'refs/heads/main'

    runs-on: ubuntu-latest

    steps:

      - name: Deploy
        run: |
          echo "Deploy application"

needs: ci หมายถึง Job deploy จะขึ้นกับผลของ Job ci


#18. เพิ่ม Quality Gate Check ใน Pipeline

หากต้องการให้ Pipeline รอผล Quality Gate สามารถเพิ่ม Action ที่ SonarSource รองรับตามเอกสาร Version ปัจจุบัน

แนวคิด:

- name: SonarQube Scan
  uses: SonarSource/sonarqube-scan-action@v7
  env:
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

- name: SonarQube Quality Gate
  uses: SonarSource/sonarqube-quality-gate-action@v1
  timeout-minutes: 5
  env:
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

ก่อนใช้ควรตรวจสอบ Release/Version ล่าสุดของ Action อย่างเป็นทางการ เนื่องจาก Version ของ GitHub Action มีการเปลี่ยนแปลงได้

อีกแนวทางหนึ่งคือใช้ Quality Gate status ที่ SonarQube Cloud ส่งกลับไปยัง GitHub แล้วกำหนดเป็น Required Check ของ Branch/Ruleset


#19. Pipeline ฉบับสมบูรณ์

ภาพรวม:

Developer
   │
   ▼
GitHub
   │
   ▼
Push / Pull Request
   │
   ▼
GitHub Actions
   │
   ├── Checkout
   ├── Setup Runtime
   ├── Install Dependencies
   ├── Build
   ├── Unit Test
   ├── Coverage
   └── SonarQube Scan
             │
             ▼
      SonarQube Cloud
             │
             ▼
        Quality Gate
         /        \
      PASS        FAIL
       │            │
       ▼            └── Fix Code
 Build Artifact
       │
       ▼
 Docker Image
       │
       ▼
    Registry
       │
       ▼
    Deployment

#20. เพิ่ม Docker เข้า CD

หลัง CI ผ่าน สามารถ Build Docker Image:

- name: Build Docker image
  run: |
    docker build -t myapp:${{ github.sha }} .

ตัวอย่างการ Tag:

myapp:a83d95...

การใช้ Commit SHA ทำให้ Trace ได้ว่า Container Image ถูกสร้างจาก Source Code Revision ใด

จากนั้นจึง Push ไป Registry เช่น:

GitHub Container Registry
Docker Hub
Amazon ECR
Google Artifact Registry
Azure Container Registry

แล้ว Deploy ไปยัง:

Docker Server
Kubernetes
AWS ECS
Amazon EKS
Google Kubernetes Engine
Azure Kubernetes Service
DigitalOcean Kubernetes

#21. GitHub Environment สำหรับ Production

สำหรับ Production แนะนำให้ใช้ GitHub Environment:

deploy-production:

  needs: ci

  environment:
    name: production

  runs-on: ubuntu-latest

Environment สามารถใช้ร่วมกับ:

  • Environment Secrets
  • Deployment Protection
  • Required Reviewers
  • Environment-specific Variables

ทำให้สามารถออกแบบ flow เช่น:

CI PASS
   ↓
Quality Gate PASS
   ↓
Request Production Deployment
   ↓
Approval
   ↓
Access Production Secrets
   ↓
Deploy

#22. Security ของ Secrets

ข้อมูลลับ เช่น:

SONAR_TOKEN
DOCKER_PASSWORD
AWS_SECRET
DEPLOY_SSH_KEY

ไม่ควรอยู่ใน:

Source Code
Dockerfile
sonar-project.properties
Workflow แบบ Plain Text
Git Repository

ให้เก็บใน GitHub Secrets หรือระบบ Secret Management ที่เหมาะสม

ตัวอย่าง:

env:
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

ควรใช้หลัก Least Privilege คือให้ Credential มี Permission เท่าที่ Pipeline จำเป็นต้องใช้

สำหรับ Cloud Provider ที่รองรับ OIDC ควรพิจารณาใช้ GitHub Actions OIDC แทน Long-lived Cloud Access Key


#23. CI/CD กับ DevSecOps

เมื่อเพิ่ม Static Code Analysis เข้า Pipeline เราจะเริ่มเปลี่ยนจาก:

CI/CD

ไปสู่แนวคิด:

CI/CD + Security

หรือ DevSecOps:

Code
 │
 ▼
Build
 │
 ▼
Test
 │
 ▼
SAST / Code Quality
 │
 ▼
Quality Gate
 │
 ▼
Container Build
 │
 ▼
Deploy

และยังสามารถเพิ่มเครื่องมืออื่น เช่น Dependency Scanning, Secret Scanning, Container Image Scanning และ DAST ได้


#24. Pipeline ที่แนะนำสำหรับ Production

Pull Request
     │
     ▼
Lint
     │
     ▼
Unit Test
     │
     ▼
Coverage
     │
     ▼
SonarQube Scan
     │
     ▼
Quality Gate
     │
     ▼
Code Review
     │
     ▼
Merge main
     │
     ▼
Build Docker Image
     │
     ▼
Container Scan
     │
     ▼
Push Registry
     │
     ▼
Deploy Staging
     │
     ▼
Integration / E2E Test
     │
     ▼
Approval
     │
     ▼
Production

#25. Troubleshooting

#ปัญหา SONAR_TOKEN ไม่ถูกต้อง

ตรวจสอบว่า GitHub มี Secret:

SONAR_TOKEN

และ Workflow ใช้:

env:
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

#SonarQube ไม่พบ Coverage

ตรวจสอบว่า Test สร้าง:

coverage/lcov.info

และ Property ตรงกัน:

sonar.javascript.lcov.reportPaths=coverage/lcov.info

#npm ci ทำงานไม่ได้

npm ci ต้องการ lock file ที่สอดคล้องกับ package.json

ตรวจสอบว่ามี:

package-lock.json

และ Commit เข้า Repository แล้ว

#Workflow ไม่ทำงาน

ตรวจสอบ:

on:
  push:
    branches:
      - main

รวมถึงชื่อ Branch จริงของ Repository

#Pull Request จาก Fork ไม่มี SONAR_TOKEN

GitHub จำกัดการส่ง Repository Secrets ไปยัง Workflow ที่ถูก Trigger จาก Fork เพื่อป้องกันการขโมย Secret ดังนั้นควรออกแบบ PR workflow โดยคำนึงถึง Trust Boundary นี้


#26. Checklist ก่อนใช้งานจริง

  • [ ] Repository เชื่อมกับ SonarQube Cloud แล้ว
  • [ ] Project Key ถูกต้อง
  • [ ] Organization Key ถูกต้อง
  • [ ] สร้าง SONAR_TOKEN
  • [ ] Token อยู่ใน GitHub Secrets
  • [ ] Unit Test ทำงานได้
  • [ ] Coverage ถูก Generate
  • [ ] SonarQube อ่าน Coverage ได้
  • [ ] GitHub Actions ทำงานทั้ง Push/PR
  • [ ] Quality Gate ถูกกำหนดตามมาตรฐานทีม
  • [ ] Branch Protection/Ruleset บังคับ Required Checks
  • [ ] Production Secrets แยกจาก CI Secrets
  • [ ] Deploy ทำงานหลัง CI/Quality Gate ผ่าน
  • [ ] Production มี Approval หากระบบต้องการ
  • [ ] Credential ใช้ Least Privilege

#27. สรุป

GitHub Actions และ SonarQube Cloud สามารถทำงานร่วมกันเพื่อสร้าง CI/CD Pipeline ที่ไม่ได้ตรวจสอบเพียงว่าโปรแกรม Build หรือ Test ผ่าน แต่เพิ่ม Automated Code Quality และ Security Analysis เข้าไปใน Software Delivery Process

Architecture ที่ควรจำคือ:

GitHub
   ↓
GitHub Actions
   ↓
Build
   ↓
Test + Coverage
   ↓
SonarQube Scan
   ↓
SonarQube Cloud
   ↓
Quality Gate
   ↓
Deploy

แนวทางนี้ช่วยสร้าง Quality Gate ก่อนส่ง Software ไปยัง Environment ถัดไป และเหมาะสำหรับการต่อยอดสู่ DevSecOps, Docker, Kubernetes และ GitOps


#เอกสารอ้างอิง


Keywords: GitHub Actions, CI/CD, SonarQube, SonarQube Cloud, SonarCloud, DevOps, DevSecOps, Static Code Analysis, Quality Gate, Code Quality