วันพุธที่ 30 สิงหาคม พ.ศ. 2566

CI/CD (continuous integration and continuous delivery/deployment)

It's a set of practices and an automated workflow in software development that aims to deliver software updates frequently, reliably, and efficiently. It's a core component of the DevOps methodology.

The entire automated process, from code commit to deployment, is often referred to as a CI/CD pipeline. This pipeline typically consists of several stages:

  1. Source/Commit: Developer commits code to the repository.

  2. Build: Code is compiled and packaged.

  3. Test: Automated tests are run.

  4. Deploy (to staging/testing): Application is deployed to a non-production environment.

  5. Manual Approval (for Continuous Delivery): A human reviews and approves the release.

  6. Deploy (to production): Application is released to end-users.

--Gemini

https://www.redhat.com/en/topics/devops/what-is-ci-cd#:~:text=CI%2FCD%20is%20a%20method,continuous%20delivery%2C%20and%20continuous%20deployment.

https://blog.cloudhm.co.th/ci-cd/

Common Tools Used in the Industry

In a practical engineering stack, a CI/CD pipeline is stitched together using several specialized tools:

Pipeline Orchestrators: GitHub Actions, GitLab CI/CD, Jenkins, CircleCI, or AWS CodePipeline. These tools coordinate the entire workflow.

Build & Containerization: Docker, Maven, Gradle, or npm.

Infrastructure as Code (IaC): Terraform or Ansible, which allow the pipeline to dynamically spin up or configure servers on the fly.

Deployment Targets: Kubernetes, AWS ECS, Google Cloud Run, or traditional cloud instances (EC2).


The Core Components in Practice

To understand CI/CD, it helps to see it as three distinct phases that trigger automatically whenever a developer pushes a code change to a repository like GitHub or GitLab.

1. Continuous Integration (CI): The Safety Net

The Practical Goal: Prevent "integration hell"—the nightmare scenario where multiple developers write code independently, merge it all at once at the end of a sprint, and watch the entire system break.

When a developer submits a pull request (PR), the CI server automatically:

Spins up an isolated environment (usually via Docker containers).

Builds the application to ensure there are no syntax or compilation errors.

Runs the test suite (unit tests, integration tests, and linters) to verify that the new code doesn't break existing functionality or violate style guides.

Analyzes code quality using tools like SonarQube to look for security vulnerabilities or poorly optimized code blocks.

If any test fails, the build turns "red." The code cannot be merged until the developer fixes the issue.

2. Continuous Delivery (CD): Ready to Ship

The Practical Goal: Ensure that the software is always in a release-ready state.

Once the CI phase passes and the code is merged into the main branch, the Delivery phase takes over:

• It packages the application into a deployable artifact (like a Docker image, a .war file, or a compiled binary).

• It automatically deploys this artifact to a staging or UAT (User Acceptance Testing) environment that mimics production.

The Human Element: In Continuous Delivery, moving the code from staging to the actual live production environment requires a human to click a button (e.g., a product manager or release lead approving the release after manual QA or business review).

3. Continuous Deployment (CD): Fully Automated

The Practical Goal: Eliminate manual human intervention entirely to achieve maximum velocity.

Continuous Deployment takes the process one step further than delivery. If the code passes all automated tests in the CI pipeline, it goes straight to production users automatically, often within minutes of being merged.


CI/CD in practice:

To implement a CI/CD pipeline in practice, you need to move away from theoretical definitions and look at the concrete **architectural steps, configuration files, and tools** that make automation work.

In a modern engineering environment, a pipeline is treated exactly like software: it is written as code (usually in YAML) and stored directly inside your project repository.

Here is a step-by-step breakdown of how a production-ready CI/CD pipeline is actually built and executed.

## 1. The Blueprint: Infrastructure as Code (IaC)

Before code can flow anywhere, the target environments (Staging and Production) must exist. In practice, engineers don't manually click buttons in cloud consoles (AWS, Google Cloud, Azure) to set up servers.

Instead, they use tools like **Terraform** or **OpenTofu** to define the infrastructure.

 * **In Practice:** A configuration file describes the exact virtual machine specifications, database instances, and networking routes needed. The CI/CD system reads this file to spin up identical, predictable environments on demand.

## 2. The Practical CI Workflow (Step-by-Step)

Let's look at how a code change moves through an automated pipeline using a tool like **GitHub Actions** or **GitLab CI**.

### Step A: The Trigger

A developer finishes fixing a bug or adding a feature. They commit their code locally and push a **Pull Request (PR)** to the main branch.

### Step B: Code Linting and Static Analysis

The CI server detects the PR and instantly provisions a tiny, isolated container (usually running Linux). The first line of defense is ensuring the code is readable and secure:

 * **Linters** (like ESLint for JavaScript, Flake8 for Python) check for formatting errors and style violations.

 * **SAST Tools** (Static Application Security Testing like SonarQube or Snyk) scan the raw text of the code to find vulnerabilities, like hardcoded passwords or unsafe database queries that could lead to SQL injection.

### Step C: The Build Phase

If the static checks pass, the container compiles the code and packages it along with all its external dependencies.

 * **In Practice:** For a compiled language like Java or Go, this produces a binary. For modern web applications, this step often downloads npm packages and runs a production compilation script.

### Step D: Running the Test Suite

This is the most critical phase of Continuous Integration. The container executes the automated testing suite:

 1. **Unit Tests:** Isolate individual functions to ensure the core logic behaves correctly.

 2. **Integration Tests:** Spin up a mock database in the background to ensure the application can read and write data correctly without throwing runtime exceptions.

If a single test fails, the pipeline immediately halts, flags the PR as "broken," and sends a notification to the developer (via Slack, Teams, or email). The code cannot be merged.

## 3. The Practical CD Workflow (From Code to Container)

Once all CI tests pass and a senior engineer reviews and approves the PR, the code is merged into the main branch. This merge triggers the **Continuous Delivery / Deployment (CD)** engine.

### Step E: Creating the Immutable Artifact

In modern architectures, applications are rarely deployed directly onto bare virtual machines. Instead, they are wrapped inside **Docker containers**.

 * The CD pipeline reads a file called a Dockerfile.

 * It builds a lightweight container image containing the application binary, the runtime environment, and minimal OS utilities.

 * It tags this image with a unique version number (often the Git commit hash) and pushes it to a secure registry like Docker Hub, AWS ECR, or Google Artifact Registry.

### Step F: Deployment Strategies (How it hits the servers)

The pipeline now coordinates with the hosting infrastructure (such as a **Kubernetes** cluster or a cloud platform like Google Cloud Run) to pull down the newly built image and roll it out to users.

To prevent downtime while updating, teams practice one of two primary strategies:

 * **Rolling Updates:** The infrastructure updates instances one by one. Old container version A is taken offline, and new version B spins up to take its place. This continues until all instances are running the new code.

 * **Blue-Green Deployments:** The pipeline spins up a completely separate, identical environment running version B (Green) alongside the live version A (Blue). Once version B is fully verified, a load balancer instantaneously switches 100% of user traffic from Blue to Green. If anything goes wrong, reverting is as simple as flipping the switch back to Blue.

[Image diagram showing a Blue-Green deployment architecture switching user traffic from an old version to a new version via a router]

## What a Real CI/CD Script Looks Like

To see how this is configured, here is a simplified example of a real **GitHub Actions** pipeline file (.github/workflows/main.yml) stored in a project's repository:

```yaml

name: Production CI/CD Pipeline


on:

  push:

    branches: [ main ] # Triggers the pipeline every time code is merged to main


jobs:

  test_and_build:

    runs-on: ubuntu-latest

    steps:

    - name: Checkout Code

      uses: actions/checkout@v4


    - name: Set up Node.js

      uses: actions/setup-node@v4

      with:

        node-version: '20'


    - name: Install Dependencies & Run Tests

      run: |

        npm ci

        npm run test


    - name: Build Docker Image

      run: |

        docker build -t my-app:${{ github.sha }} .


    - name: Log in to Registry & Push Image

      run: |

        echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USER }}" --password-stdin

        docker push my-app:${{ github.sha }}


  deploy:

    needs: test_and_build # Only runs if the testing and building phase succeeds

    runs-on: ubuntu-latest

    steps:

    - name: Trigger Cloud Deployment

      run: |

        # Script or CLI command telling the cloud provider to pull the new image hash

        echo "Deploying image version ${{ github.sha }} to production..."


```

## Summary of Best Practices

If you are designing a pipeline from scratch, keep these industry-standard rules in mind:

 * **Build Once, Deploy Many:** Never compile your code or build your Docker image multiple times for different environments. Build it once during the CI phase, and promote that *exact same artifact* through Staging and into Production.

 * **Keep Pipelines Fast:** If a pipeline takes an hour to run, developers will start bypassing it or batching massive changes. Optimize dependency caching so your loop completes in under 10 minutes.

 * **Fail Fast:** Put your fastest, lightest tests (like linters and basic unit tests) at the very beginning of the file so you don't waste time and computing resources building images for code that has a syntax error.