ืฉืืื, ืืคืชืืื! ืืืืืจ ืื ืืืืจ ืขื CI/CD โ ืืงืื ืกืคื.
- ืืื CI/CD pipeline ืืืงืฉืจ ืฉื ืชืื ืืช?
- ๐ ืืื ืืืจืื ืืืจื ืืื CI/CD pipeline?
- ๐ ืืืื ืคืืคืืืจืืื ื-CI/CD:
- ๐ง ืืื ืืืื ืฆืจืื CI/CD?
- ๐ฆ ืืืืืืืช ืคืฉืืืืช ื-CI/CD ืขื GitHub Actions
- ๐ CI/CD ืขืืืจ Node.js (ืขื
npm testcode>span> ื-eslint) <code># .github/workflows/node-ci.yml name: Node.js CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [18.x] # ืฆืืื ืืช ืืจืกืช ื-Node.js ืฉืื steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm install # ืื npm ci ืืืชืงื ื ืฆืคืืื ืืืชืจ - name: Lint with ESLint run: npx eslint . # ืืื ืฉ-ESLint ืืืืืจ ืืคืจืืืงื - name: Run tests run: npm test - ๐ณ CI/CD ืขืืืจ Docker (ืื ืืื ื-push ื-Docker Hub)
- ๐ CI/CD ืขืืืจ Node.js (ืขื
- ๐ ืคืจืืกื ืืคืืืคืืจืืืช ืคืืคืืืจืืืช
- ๐ CI/CD ืืชืงืื: ืื ืืืช Docker โ Push ื-GHCR โ Staging/Production ื-GCP Cloud Run
- ๐ ืืืืื โ ืื ืืฉืื!
- ืืื CI/CD pipeline ืืืงืฉืจ ืฉื ืชืื ืืช?
- ๐ ืืื ืืืจืื ืืืจื ืืื CI/CD pipeline?
- ๐ ืืืื ืคืืคืืืจืืื ื-CI/CD:
- ๐ง ืืื ืืืื ืฆืจืื CI/CD?
- ๐ฆ ืืืืืืืช ืคืฉืืืืช ื-CI/CD ืขื GitHub Actions
- ๐ CI/CD ืขืืืจ Node.js (ืขื
npm testcode>span> ื-eslint) <code># .github/workflows/node-ci.yml name: Node.js CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [18.x] # ืฆืืื ืืช ืืจืกืช ื-Node.js ืฉืื steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm install # ืื npm ci ืืืชืงื ื ืฆืคืืื ืืืชืจ - name: Lint with ESLint run: npx eslint . # ืืื ืฉ-ESLint ืืืืืจ ืืคืจืืืงื - name: Run tests run: npm test - ๐ณ CI/CD ืขืืืจ Docker (ืื ืืื ื-push ื-Docker Hub)
- ๐ CI/CD ืขืืืจ Node.js (ืขื
- ๐ ืคืจืืกื ืืคืืืคืืจืืืช ืคืืคืืืจืืืช
- ๐ CI/CD ืืชืงืื: ืื ืืืช Docker โ Push ื-GHCR โ Staging/Production ื-GCP Cloud Run
- ๐ ืืืืื โ ืื ืืฉืื!
ืืื CI/CD pipeline ืืืงืฉืจ ืฉื ืชืื ืืช?
CI/CD pipeline (Continuous Integration / Continuous Delivery ืื Continuous Deployment) ืืื ืชืืืื ืืืืืืื, ืืืืคืฉืจ ืืืคืชืืื ืืกืคืง ืฉืื ืืื ืงืื ืืืืืจืืช ืืืืืื ืืช ืืกืืืืช ืขืืืื (ืคืจืืืงืฉื).
ืืืื ื ืคืจืง ืืช ืืืฉืื ืืืคืชื:
๐ง CI โ Continuous Integration (ืืื ืืืจืฆืื ืืชืืฉืืช)
ืืืื ืคืจืงืืืงื ืฉืื ืืคืชืืื ืืืฆืขืื ืฉืื ืืืื ืชืืืคืื ืืืกืืก ืงืื ืืฉืืชืฃ. ืื ืฉืื ืื ืืื ืืืืคื ืืืืืืื:
- ื ืื ื (build)
- ื ืืืง (ืืืืงืืช ืืืืื, ืืืืงืืช ืืื ืืืจืฆืื)
- ื ืืืง ืืืชืืื ืืชืงื ืื (linting, ื ืืชืื ืกืืื)
๐ ืืืจืช CI: ืืืืืช ืฉืืืืืช ืืฉืื ืืืงืื ืืื ืืืคืฉืจ, ืืคื ื ืฉืื ืฉืืืจืืช ืืฉืื ืืฉืื ืื ืืืืขืืช ืืืจืกื.
๐ CD โ Continuous Delivery (ืืกืคืงื ืืชืืฉืืช) ืื Continuous Deployment (ืคืจืืกื ืืชืืฉืืช)
ืืื ืืฉ ืฉืชื ืืคืฉืจืืืืช:
โ
Continuous Delivery (ืืกืคืงื ืืชืืฉืืช)
ืืืืจ ืืฉืืื ืืืฆืืืช ืฉื ืฉืื ื-CI, ืืฉืื ืืืื ืืืืคื ืืืืืืื:
- ืขืืืจืื ืืืืงืืช ื ืืกืคืืช (ืืืืืื, E2E โ ืืืืงืืช ืืงืฆื ืืงืฆื)
- ืืืืขืื ืืฉืจืช staging (ืืืืงื)
๐ ืื ืคืจืืกื ืืคืจืืืงืฉื ืขืืืื ืืืจืฉืช ืืืฉืืจ ืืื ื. ืื ื ืืชื ืืฆืืืช ืฉืืืื ืขื ืืชื ืืืืืง ืืืฉืชืืฉืื ืืจืื ืืช ืืฉืื ืืืื.
๐ค Continuous Deployment (ืคืจืืกื ืืชืืฉืืช)
ืืื ืืฉืื ืืื ืืืืจ Continuous Delivery. ืืื ืืคืจืืกื ืืคืจืืืงืฉื ืืชืจืืฉืช ืืืืคื ืืืืืืื ืืืืืืื, ืื ืื ืฉืืื ื-pipeline ืืงืืืืื (ืื ืืื, ืื ืืืืืงืืช) ืขืืจื ืืืฆืืื. ืืืื ืืจืื ืืืชืงืืืช ืืืืชืจ ืฉื ืืืืืืฆืื.
๐ ืืื ืืืจืื ืืืจื ืืื CI/CD pipeline?
Pipeline ืืืคืืกื ืืืื ืืช ืืฉืืืื ืืืืื:
- Checkout โ ืฉืืืื ืืืจืกื ืืืืจืื ื ืฉื ืืงืื ืืืืืืจ.
- Build โ ืื ืืืช ืืคืจืืืงื (ืงืืืคืืืฆืื, ืืจืืืช ืืจืืืคืงืืื, ืชืืื ืืช Docker).
- Test โ ืืคืขืืช ืกืืืื ืฉืื ืื ืฉื ืืืืงืืช (ืืืืื, ืืื ืืืจืฆืื, E2E).
- Lint/Code Quality โ ืืืืงืช ืงืื ืืืชืืื ืืกืื ืื ืืฉืืืืืช ืคืืื ืฆืืืืืืช ืืืืฆืขืืช ืื ืชืืื ืกืืืืื.
- Deploy โ ืคืจืืกืช ืืืืฉืื (ืืฉืจืช staging ืื production).
- Notify โ ืฉืืืืช ืืชืจืืืช ืขื ืกืืืืก ื-pipeline ืืฆืืืช (ืืืืืื, ื-Slack, Email).
๐ ืืืื ืคืืคืืืจืืื ื-CI/CD:
- GitHub Actions (ืืืืงื ืฉืื ื ืืืื!)
- GitLab CI/CD
- Jenkins
- CircleCI
- Bitbucket Pipelines
- Azure DevOps
- TeamCity
๐ง ืืื ืืืื ืฆืจืื CI/CD?
- ืืคืืืช ืืขืืืืช ืื ืืฉ: ืืืืืืฆืื ืืืืืช ืฉืืืืืช ืืงืฉืืจืืช ืืคืขืืืืช ืืื ืืืช.
- ืืืืื ืืืืจ ืฉื ืืืืื: ืฉืืืืืช ื ืืฆืืืช ืืืงืื ืืืชืจ, ืงื ืืืื ืืืชืจ ืืชืงื ืืืชื.
- ืืืืืืฆืื ืฉื ืืฉืืืืช ืฉืืจืชืืืช: ืืคืชืืื ืืงืืืฉืื ืคืืืช ืืื ืืื ืืื ืืคืจืืกื, ืืืืชืจ โ ืืงืื.
- ืฉืืคืืจ ืืืืืช ืืงืื: ืืืืงืืช ืืืืื ืื ืืชืืืืื ืืขืืื ืืช ืจืฃ ืืืืืืช ืืืืื.
- ืืกืคืงื ืืืืจื ืฉื ืคืืฆ'ืจืื ืืืฉืชืืฉืื: ืืืืืืช ืืืฉืืช ืืืืขืืช ืืืฉืชืืฉ ืืงืฆื ืืืจ ืืืชืจ ืืืชืืืจืืช ืืืืื ืืืชืจ.
๐ฆ ืืืืืืืช ืคืฉืืืืช ื-CI/CD ืขื GitHub Actions
ืืืื ื ืกืชืื ืขื pipelines ืืกืืกืืื ืืืื ืืืืืืืช ืคืืคืืืจืืืช. ืื ืืืืืืืืช ืืฉืชืืฉืืช ื-GitHub Actions ืื ืฉืืจืืช ืืกืคืจืืืช ืืืืืื ืื, ืชืฆืืจื ืกืืืืช `DOCKER_USERNAME` ื-`DOCKER_PASSWORD` (ืื ืืกืืืื) ืืืืืจืืช ืืืืจ ื-GitHub ืฉืื (`Settings -> Secrets and variables -> Actions`). ืืขืช, ืืฉืืฉ ืื ื ืืจืืืคืงืืื ืื ืืืื ืื ืืืงืื (ืืืืืื, ืชืืื ืช Docker), ืืืื ื ืจืื ืืืฆื ื ืืชื ืืคืจืืก ืืืชื. **๐ ืกืืืืช GitHub:** `HEROKU_API_KEY`, `HEROKU_APP_NAME`. ืื ืืชื ืคืืจืก ืชืืื ืช Docker ื-Heroku: **๐ ืกืืืืช GitHub:** `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `S3_BUCKET_NAME`. ืืคืจืืกื ื-**AWS** **Elastic** **Beanstalk** ืืืจื ืืื ืืฉืชืืฉืื ื-EB CLI, ื-pipeline ืืืื ืืืื, ืื ืขื ืคืงืืืืช Render ืืขืืชืื ืงืจืืืืช ืคืืจืก ืืืืืืืืช ื-push ื-GitHub, ืื ืืืืืจ ืืืืืจ. ืืื ืขืืืจ ืืจืืืจ ืืื ื (ืื ืืืืง ื-pipeline ืืืจืื ืืืชืจ) ื ืืชื ืืืฉืชืืฉ ื-Deploy Hook. **๐ ืกืืืืช GitHub:** `RENDER_DEPLOY_HOOK` (URL, ืฉืืชืงืื ืืืืืจืืช ืืฉืืจืืช ื-Render). ืืขืืฉืื ืืืืืืื ืฉืืงืฆืคืช! ื ืื ื pipeline ืืชืงืื: ืื ืืืช ืชืืื ืช Docker. ืืฉื ืื ื ืฆืืจื ืืกืคืจ ืงืืฆื workflow. **ืกืืืืช GitHub ื ืืจืฉืื:** ื-workflow ืืื ืืืคืขื ืืืืืืืืช ืืืืจ ืืฉืืื ืืืฆืืืช ืฉื `build.yml`. ื-workflow ืืื ืืืคืขื ืืื ืืช ืืจื UI ื-GitHub Actions. **ื ืงืืืืช ืืฉืืืืช ื-pipeline ืืชืงืื ืื:**GitHub Container Registry (ghcr.io): ืื ื ืืฉืชืืฉืื ืื ืืืืกืื ืชืืื ืืช Docker. ืื ื ืื ืืืืืื ืฉืืื ืืฉืืื ืืืื ืขื GitHub Actions. ืืฉืชืืฉ ืืกืืืืช GitHub: ืืขืืื ืื ืชืฉืืืจ ืืกืืืื ืื, ืกืืกืืืืช, ืืคืชืืืช API ืืฉืืจืืช ืืงืืฆื YAML. ืืฉืชืืฉ ื-Settings -> Secrets and variables -> Actions ืืืืืจ ืฉืื. ๐ CI/CD: ืืืกืืืืช ืืืืฆืืจ ื-GCP ืขื GitHub Actions โ ืืืจืื ืืื ืขื ืืืืืืืช ๐ ืฉืืื, ืืคืชืืื! ืืืืืจ ืื ืืืืจ ืขื CI/CD โ ืืงืื ืกืคื. CI/CD pipeline (Continuous Integration / Continuous Delivery ืื Continuous Deployment) ืืื ืชืืืื ืืืืืืื, ืืืืคืฉืจ ืืืคืชืืื ืืกืคืง ืฉืื ืืื ืงืื ืืืืืจืืช ืืืืืื ืืช ืืกืืืืช ืขืืืื (ืคืจืืืงืฉื). ืืืื ื ืคืจืง ืืช ืืืฉืื ืืืคืชื: ๐ง CI โ Continuous Integration (ืืื ืืืจืฆืื ืืชืืฉืืช) ๐ ืืืจืช CI: ืืืืืช ืฉืืืืืช ืืฉืื ืืืงืื ืืื ืืืคืฉืจ, ืืคื ื ืฉืื ืฉืืืจืืช ืืฉืื ืืฉืื ืื ืืืืขืืช ืืืจืกื. ๐ CD โ Continuous Delivery (ืืกืคืงื ืืชืืฉืืช) ืื Continuous Deployment (ืคืจืืกื ืืชืืฉืืช) โ
Continuous Delivery (ืืกืคืงื ืืชืืฉืืช) ๐ค Continuous Deployment (ืคืจืืกื ืืชืืฉืืช) Pipeline ืืืคืืกื ืืืื ืืช ืืฉืืืื ืืืืื: ืืืื ื ืกืชืื ืขื pipelines ืืกืืกืืื ืืืื ืืืืืืืช ืคืืคืืืจืืืช. ืื ืืืืืืืืช ืืฉืชืืฉืืช ื-GitHub Actions ืื ืฉืืจืืช ืืกืคืจืืืช ืืืืืื ืื, ืชืฆืืจื ืกืืืืช `DOCKER_USERNAME` ื-`DOCKER_PASSWORD` (ืื ืืกืืืื) ืืืืืจืืช ืืืืจ ื-GitHub ืฉืื (`Settings -> Secrets and variables -> Actions`). ืืขืช, ืืฉืืฉ ืื ื ืืจืืืคืงืืื ืื ืืืื ืื ืืืงืื (ืืืืืื, ืชืืื ืช Docker), ืืืื ื ืจืื ืืืฆื ื ืืชื ืืคืจืืก ืืืชื. **๐ ืกืืืืช GitHub:** `HEROKU_API_KEY`, `HEROKU_APP_NAME`. ืื ืืชื ืคืืจืก ืชืืื ืช Docker ื-Heroku: **๐ ืกืืืืช GitHub:** `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `S3_BUCKET_NAME`. ืืคืจืืกื ื-**AWS** **Elastic** **Beanstalk** ืืืจื ืืื ืืฉืชืืฉืื ื-EB CLI, ื-pipeline ืืืื ืืืื, ืื ืขื ืคืงืืืืช Render ืืขืืชืื ืงืจืืืืช ืคืืจืก ืืืืืืืืช ื-push ื-GitHub, ืื ืืืืืจ ืืืืืจ. ืืื ืขืืืจ ืืจืืืจ ืืื ื (ืื ืืืืง ื-pipeline ืืืจืื ืืืชืจ) ื ืืชื ืืืฉืชืืฉ ื-Deploy Hook. **๐ ืกืืืืช GitHub:** `RENDER_DEPLOY_HOOK` (URL, ืฉืืชืงืื ืืืืืจืืช ืืฉืืจืืช ื-Render). ืืขืืฉืื ืืืืืืื ืฉืืงืฆืคืช! ื ืื ื pipeline ืืชืงืื: ืื ืืืช ืชืืื ืช Docker. ืืฉื ืื ื ืฆืืจื ืืกืคืจ ืงืืฆื workflow. **ืกืืืืช GitHub ื ืืจืฉืื:** ื-workflow ืืื ืืืคืขื ืืืืืืืืช ืืืืจ ืืฉืืื ืืืฆืืืช ืฉื `build.yml`. ื-workflow ืืื ืืืคืขื ืืื ืืช ืืจื UI ื-GitHub Actions. **ื ืงืืืืช ืืฉืืืืช ื-pipeline ืืชืงืื ืื:**GitHub Container Registry (ghcr.io): ืื ื ืืฉืชืืฉืื ืื ืืืืกืื ืชืืื ืืช Docker. ืื ื ืื ืืืืืื ืฉืืื ืืฉืืื ืืืื ืขื GitHub Actions. ืืฉืชืืฉ ืืกืืืืช GitHub: ืืขืืื ืื ืชืฉืืืจ ืืกืืืื ืื, ืกืืกืืืืช, ืืคืชืืืช API ืืฉืืจืืช ืืงืืฆื YAML. ืืฉืชืืฉ ื-Settings -> Secrets and variables -> Actions ืืืืืจ ืฉืื..github/workflows/code>span> ืฉื ืืคืจืืืงื ืฉืืื.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":4} -->
<h4 class="wp-block-heading">๐ CI/CD ืขืืืจ Python (ืขื <span dir="ltr"><code>pytestcode>span> ื-flake8)</h4>
<!-- /wp:heading -->
<!-- wp:code -->
<pre class="wp-block-code"><code>
# .github/workflows/python-ci.yml
name: Python CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11' # ืฆืืื ืืช ืืืจืกื ืฉืื
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # ืืื ืฉืืฉ ืื requirements.txt
pip install flake8 pytest
- name: Lint with flake8
run: |
# ืืืืง ืงืื ืืชืืงืืืช src ื-tests (ืืชืื ืืคืจืืืงื ืฉืื)
flake8 src tests
- name: Run tests
run: |
pytest
๐ CI/CD ืขืืืจ Node.js (ืขื
npm testcode>span> ื-eslint) <code># .github/workflows/node-ci.yml name: Node.js CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [18.x] # ืฆืืื ืืช ืืจืกืช ื-Node.js ืฉืื steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm install # ืื npm ci ืืืชืงื ื ืฆืคืืื ืืืชืจ - name: Lint with ESLint run: npx eslint . # ืืื ืฉ-ESLint ืืืืืจ ืืคืจืืืงื - name: Run tests run: npm test๐ณ CI/CD ืขืืืจ Docker (ืื ืืื ื-push ื-Docker Hub)
# .github/workflows/docker-ci.yml
name: Docker CI/CD
on:
push:
branches: [ main ] # ืืคืขื ืจืง ืขืืืจ ืขื ืฃ main
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Build Docker image
# ืืืืฃ ืืช myapp ืืฉื ืืืืฉืื ืฉืื
run: docker build -t ${{ secrets.DOCKER_USERNAME }}/myapp:latest .
- name: Push Docker image
run: docker push ${{ secrets.DOCKER_USERNAME }}/myapp:latest
๐ ืคืจืืกื ืืคืืืคืืจืืืช ืคืืคืืืจืืืช
๐ฃ ืคืจืืกื ื-Heroku
# .github/workflows/deploy-heroku.yml
name: Deploy to Heroku
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Heroku CLI
run: curl https://cli-assets.heroku.com/install.sh | sh
- name: Login to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: heroku auth:token
- name: Deploy to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: |
heroku git:remote -a ${{ secrets.HEROKU_APP_NAME }}
git push heroku main -f # ืืืืืจ ืขื -f (force push)
# ... (ืฉืืื ืื ืืื ืืืชืืืจืืช ื-Docker Hub/GHCR ืืืืืืืืช ืงืืืืืช) ...
# deploy:
# name: Deploy to Heroku
# needs: build # ืชืืื ืืืฉืืืช ืื ืืืช ืืชืืื ื
# runs-on: ubuntu-latest
# steps:
# # ...
# - name: Login to Heroku container registry
# run: echo "${{ secrets.HEROKU_API_KEY }}" | docker login --username=_ --password-stdin registry.heroku.com
# - name: Tag image for Heroku
# # ืืื ืื ืฉืืชืืื ื ื ืื ืืช ื-ghcr.io/username/repo/myapp:latest
# run: docker tag ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
# - name: Push image to Heroku
# run: docker push registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
# - name: Release Heroku App
# env:
# HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
# run: heroku container:release web --app ${{ secrets.HEROKU_APP_NAME }}
๐จ ืคืจืืกื ื-AWS (ืืืืืื, ืกืืื ื-S3)
# .github/workflows/deploy-aws-s3.yml
name: Deploy Static Site to AWS S3
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Sync files to S3
# ืืืืฃ ืืช ./public ืื ืชืื ืืงืืฆืื ืืกืืืืื ืฉืื
run: aws s3 sync ./public s3://${{ secrets.S3_BUCKET_NAME }} --delete
eb deploycode>span>.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":4} -->
<h4 class="wp-block-heading">๐ต ืคืจืืกื ื-Google Cloud Platform (GCP App Engine)</h4>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>**๐ ืกืืืืช GitHub:** `GCP_CREDENTIALS` (ืืคืชื JSON ืฉื ืืฉืืื ืฉืืจืืช), `GCP_PROJECT_ID`.</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code>
# .github/workflows/deploy-gcp-app-engine.yml
name: Deploy to GCP App Engine
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}
service_account_key: ${{ secrets.GCP_CREDENTIALS }}
export_default_credentials: true
- name: Deploy to App Engine
# ืืื ืฉืืฉ ืื app.yaml ืืฉืืจืฉ ืืคืจืืืงื
run: gcloud app deploy --quiet
๐ช ืคืจืืกื ื-Render.com
# .github/workflows/deploy-render.yml
name: Trigger Render Deploy
on:
workflow_dispatch: # ืืคืขืื ืืื ืืช ื-UI GitHub
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger Render Deploy Hook
run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK }}
๐ CI/CD ืืชืงืื: ืื ืืืช Docker โ Push ื-GHCR โ Staging/Production ื-GCP Cloud Run
ืคืจืกืื ืืชืืื ื ื-GitHub Container Registry (ghcr.io).
ืคืจืืกื ืืืืืืืืช ืืกืืืืช staging ื-GCP Cloud Run.
ืคืจืืกื ืืกืืืืช production ื-GCP Cloud Run ืืืืจ ืืืฉืืจ ืืื ื.GCP_PROJECT_ID: ืืืื ืืคืจืืืงื ืฉืื ื-GCP.GCP_CREDENTIALS: ืืคืชื JSON ืฉื ืืฉืืื ืฉืืจืืช GCP ืขื ืืจืฉืืืช ืคืจืืกื ื-Cloud Run ืืืืฉื ื-GHCR (ืื ื ืืจืฉ). ืืืจื ืืื GITHUB_TOKEN ืืกืคืืง ืืืืฉื ื-GHCR ื-Actions.GCP_REGION: ืืืืจ ืขืืืจ Cloud Run (ืืืืืื, europe-west1).1. ืื ืืื ืืคืจืกืื ืชืืื ืช Docker ื-GHCR
# .github/workflows/build.yml
name: Build & Push to GHCR
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read # ืขืืืจ checkout
packages: write # ืขืืืจ push ื-GHCR
steps:
- uses: actions/checkout@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
# ืืืืฃ ืืช myapp ืืฉื ืืืืฉืื ืฉืื
run: docker build -t ${{ secrets.DOCKER_USERNAME }}/myapp:latest .
- name: Push Docker image
run: docker push ${{ secrets.DOCKER_USERNAME }}/myapp:latest
github.repository_owner: ืืขื ืืืืืจ (ืฉื ืืืฉืชืืฉ ืื ืืืจืืื ืฉืื).github.event.repository.name: ืฉื ืืืืืจ.myapp: ืฉื ืืืืฉืื/ืืชืืื ื ืฉืื.2. ืคืจืืกื ืืืืืืืืช ื-Staging (GCP Cloud Run)
# .github/workflows/deploy-staging.yml
name: Deploy to GCP Cloud Run (Staging)
on:
workflow_run:
workflows: ["Build & Push to GHCR"] # ืฉื ื-workflow ืฉื ืืื ืืื
types:
- completed
jobs:
deploy-staging:
runs-on: ubuntu-latest
# ืืคืขื ืจืง ืื ื-workflow ืฉื ืืื ืืื ืืกืชืืื ืืืฆืืื
if: ${{ github.event.workflow_run.conclusion == 'success' }}
# ืืฉืชืืฉ ืืกืืืืืช GitHub ืขืืืจ staging (ืืืคืฆืืื ืื, ืื ืคืจืงืืืงื ืืืื)
environment:
name: staging
url: ${{ steps.deploy.outputs.url }} # ื-URL ืืืื ืืืื ืืืืจ ืืคืจืืกื
steps:
- uses: actions/checkout@v3
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: 'Deploy to Cloud Run (Staging)'
id: deploy
uses: 'google-github-actions/deploy-cloudrun@v2'
with:
service: 'myapp-staging' # ืฉื ืฉืืจืืช ื-staging ืฉืื ื-Cloud Run
region: '${{ secrets.GCP_REGION }}'
# ืืฉืชืืฉ ืืชืืื ื ืฉื ืฉืืื ื-build.yml
image: 'ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest'
project_id: '${{ secrets.GCP_PROJECT_ID }}'
flags: '--allow-unauthenticated --platform=managed' # ืืคืฉืจ ืืืฉื ืื ืืืืืชืช ืืืืืื
3. ืคืจืืกื ื-Production ืขื ืืืฉืืจ ืืื ื (GCP Cloud Run)
# .github/workflows/deploy-prod.yml
name: Deploy to GCP Cloud Run (Production)
on:
workflow_dispatch: # ืืืคืฉืจ ืืคืขืื ืืื ืืช
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v3
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: 'Deploy to Cloud Run (Production)'
id: deploy
uses: 'google-github-actions/deploy-cloudrun@v2'
with:
service: 'myapp-production' # ืฉื ืฉืืจืืช ื-production ืฉืื
region: '${{ secrets.GCP_REGION }}'
image: 'ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest' # ืืฉืชืืฉ ืืืืชื ืชืืื ืช 'latest'
project_id: '${{ secrets.GCP_PROJECT_ID }}'
flags: '--allow-unauthenticated --platform=managed'
# ืขืืืจ production ื ืืชื ืืืืกืืฃ --no-traffic ืืืืืจ ืืื ืืืืืืฃ ืชื ืืขื ืืืืจืื
# traffic:
# latest: true
# percent: 100
workflow_run: ืืืคืฉืจ ืืืคืขืื workflow ืืื (ืคืจืืกืช staging) ืขื ืืฉืืืช workflow ืืืจ (ืื ืืื).workflow_dispatch: ืืืคืฉืจ ืืคืขืื ืืื ืืช ืฉื workflow (ืคืจืืกืช production), ืืืกืคืงืช ืฉืืืื.
GitHub Environments: ืืืคืฉืจืื ืื ืืืืืืจ ืืืื ืืื ื ืขืืืจ production (ืืืืืื, ืืจืืฉืช ืืืฉืืจ ืืืืงืจืื ืกืคืฆืืคืืื) ืืืืืกื ืกืืืืช ืกืคืฆืืคืืื ืืกืืืื.
GCP Cloud Run: ืืคืฉืจืืช serverless ืืฆืืื ืช ืืืคืขืืช ืืืฉืืืื ืืืืกืกื ืงืื ืืืื ืจืื.๐ ืืืืื โ ืื ืืฉืื!
ืืจืฉืืืช ืืื ืืืืืืช: ืขืืืจ ืืฉืืื ืืช ืฉืืจืืช (ืืืืืื, GCP), ืืขื ืง ืจืง ืืช ืืืจืฉืืืช ืื ืืืฆืืช ืืืืช ืืืืฆืืข ืืฉืืืืช CI/CD.
ืืืืื ืกืืืืืช: Staging ื-Production ืฆืจืืืื ืืืืืช ืืืืืืื ืืื ืืืคืฉืจ. ืคืจืืืงืืื/ืืฉืืื ืืช ืฉืื ืื ืืกืคืงื ืขื ื โ ืื ืคืจืงืืืงื ืืืื.
ืืื ืช ืขื ืคืื: ืืืืจ ืืื ื ืขืืืจ ืขื ืฃ main (ืื master) ืื ืฉ-push ืืืื ืืชืืคืฉืจ ืจืง ืืืืฆืขืืช Pull Request ืขื ืืืืงืืช CI ืืืื.ืืื CI/CD pipeline ืืืงืฉืจ ืฉื ืชืื ืืช?
ืืืื ืคืจืงืืืงื ืฉืื ืืคืชืืื ืืืฆืขืื ืฉืื ืืืื ืชืืืคืื ืืืกืืก ืงืื ืืฉืืชืฃ. ืื ืฉืื ืื ืืื ืืืืคื ืืืืืืื:
ืืื ืืฉ ืฉืชื ืืคืฉืจืืืืช:
ืืืืจ ืืฉืืื ืืืฆืืืช ืฉื ืฉืื ื-CI, ืืฉืื ืืืื ืืืืคื ืืืืืืื:
๐ ืื ืคืจืืกื ืืคืจืืืงืฉื ืขืืืื ืืืจืฉืช ืืืฉืืจ ืืื ื. ืื ื ืืชื ืืฆืืืช ืฉืืืื ืขื ืืชื ืืืืืง ืืืฉืชืืฉืื ืืจืื ืืช ืืฉืื ืืืื.
ืืื ืืฉืื ืืื ืืืืจ Continuous Delivery. ืืื ืืคืจืืกื ืืคืจืืืงืฉื ืืชืจืืฉืช ืืืืคื ืืืืืืื ืืืืืืื, ืื ืื ืฉืืื ื-pipeline ืืงืืืืื (ืื ืืื, ืื ืืืืืงืืช) ืขืืจื ืืืฆืืื. ืืืื ืืจืื ืืืชืงืืืช ืืืืชืจ ืฉื ืืืืืืฆืื.๐ ืืื ืืืจืื ืืืจื ืืื CI/CD pipeline?
๐ ืืืื ืคืืคืืืจืืื ื-CI/CD:
๐ง ืืื ืืืื ืฆืจืื CI/CD?
๐ฆ ืืืืืืืช ืคืฉืืืืช ื-CI/CD ืขื GitHub Actions
.github/workflows/code>span> ืฉื ืืคืจืืืงื ืฉืืื.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":4} -->
<h4 class="wp-block-heading">๐ CI/CD ืขืืืจ Python (ืขื <span dir="ltr"><code>pytestcode>span> ื-flake8)</h4>
<!-- /wp:heading -->
<!-- wp:code -->
<pre class="wp-block-code"><code>
# .github/workflows/python-ci.yml
name: Python CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11' # ืฆืืื ืืช ืืืจืกื ืฉืื
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # ืืื ืฉืืฉ ืื requirements.txt
pip install flake8 pytest
- name: Lint with flake8
run: |
# ืืืืง ืงืื ืืชืืงืืืช src ื-tests (ืืชืื ืืคืจืืืงื ืฉืื)
flake8 src tests
- name: Run tests
run: |
pytest
๐ CI/CD ืขืืืจ Node.js (ืขื
npm testcode>span> ื-eslint) <code># .github/workflows/node-ci.yml name: Node.js CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [18.x] # ืฆืืื ืืช ืืจืกืช ื-Node.js ืฉืื steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm install # ืื npm ci ืืืชืงื ื ืฆืคืืื ืืืชืจ - name: Lint with ESLint run: npx eslint . # ืืื ืฉ-ESLint ืืืืืจ ืืคืจืืืงื - name: Run tests run: npm test๐ณ CI/CD ืขืืืจ Docker (ืื ืืื ื-push ื-Docker Hub)
# .github/workflows/docker-ci.yml
name: Docker CI/CD
on:
push:
branches: [ main ] # ืืคืขื ืจืง ืขืืืจ ืขื ืฃ main
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Build Docker image
# ืืืืฃ ืืช myapp ืืฉื ืืืืฉืื ืฉืื
run: docker build -t ${{ secrets.DOCKER_USERNAME }}/myapp:latest .
- name: Push Docker image
run: docker push ${{ secrets.DOCKER_USERNAME }}/myapp:latest
๐ ืคืจืืกื ืืคืืืคืืจืืืช ืคืืคืืืจืืืช
๐ฃ ืคืจืืกื ื-Heroku
# .github/workflows/deploy-heroku.yml
name: Deploy to Heroku
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Heroku CLI
run: curl https://cli-assets.heroku.com/install.sh | sh
- name: Login to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: heroku auth:token
- name: Deploy to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: |
heroku git:remote -a ${{ secrets.HEROKU_APP_NAME }}
git push heroku main -f # ืืืืืจ ืขื -f (force push)
# ... (ืฉืืื ืื ืืื ืืืชืืืจืืช ื-Docker Hub/GHCR ืืืืืืืืช ืงืืืืืช) ...
# deploy:
# name: Deploy to Heroku
# needs: build # ืชืืื ืืืฉืืืช ืื ืืืช ืืชืืื ื
# runs-on: ubuntu-latest
# steps:
# # ...
# - name: Login to Heroku container registry
# run: echo "${{ secrets.HEROKU_API_KEY }}" | docker login --username=_ --password-stdin registry.heroku.com
# - name: Tag image for Heroku
# # ืืื ืื ืฉืืชืืื ื ื ืื ืืช ื-ghcr.io/username/repo/myapp:latest
# run: docker tag ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
# - name: Push image to Heroku
# run: docker push registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
# - name: Release Heroku App
# env:
# HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
# run: heroku container:release web --app ${{ secrets.HEROKU_APP_NAME }}
๐จ ืคืจืืกื ื-AWS (ืืืืืื, ืกืืื ื-S3)
# .github/workflows/deploy-aws-s3.yml
name: Deploy Static Site to AWS S3
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Sync files to S3
# ืืืืฃ ืืช ./public ืื ืชืื ืืงืืฆืื ืืกืืืืื ืฉืื
run: aws s3 sync ./public s3://${{ secrets.S3_BUCKET_NAME }} --delete
eb deploycode>span>.</p>
<!-- /wp:paragraph -->
<!-- wp:heading {"level":4} -->
<h4 class="wp-block-heading">๐ต ืคืจืืกื ื-Google Cloud Platform (GCP App Engine)</h4>
<!-- /wp:heading -->
<!-- wp:paragraph -->
<p>**๐ ืกืืืืช GitHub:** `GCP_CREDENTIALS` (ืืคืชื JSON ืฉื ืืฉืืื ืฉืืจืืช), `GCP_PROJECT_ID`.</p>
<!-- /wp:paragraph -->
<!-- wp:code -->
<pre class="wp-block-code"><code>
# .github/workflows/deploy-gcp-app-engine.yml
name: Deploy to GCP App Engine
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}
service_account_key: ${{ secrets.GCP_CREDENTIALS }}
export_default_credentials: true
- name: Deploy to App Engine
# ืืื ืฉืืฉ ืื app.yaml ืืฉืืจืฉ ืืคืจืืืงื
run: gcloud app deploy --quiet
๐ช ืคืจืืกื ื-Render.com
# .github/workflows/deploy-render.yml
name: Trigger Render Deploy
on:
workflow_dispatch: # ืืคืขืื ืืื ืืช ื-UI GitHub
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger Render Deploy Hook
run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK }}
๐ CI/CD ืืชืงืื: ืื ืืืช Docker โ Push ื-GHCR โ Staging/Production ื-GCP Cloud Run
ืคืจืกืื ืืชืืื ื ื-GitHub Container Registry (ghcr.io).
ืคืจืืกื ืืืืืืืืช ืืกืืืืช staging ื-GCP Cloud Run.
ืคืจืืกื ืืกืืืืช production ื-GCP Cloud Run ืืืืจ ืืืฉืืจ ืืื ื.GCP_PROJECT_ID: ืืืื ืืคืจืืืงื ืฉืื ื-GCP.GCP_CREDENTIALS: ืืคืชื JSON ืฉื ืืฉืืื ืฉืืจืืช GCP ืขื ืืจืฉืืืช ืคืจืืกื ื-Cloud Run ืืืืฉื ื-GHCR (ืื ื ืืจืฉ). ืืืจื ืืื GITHUB_TOKEN ืืกืคืืง ืืืืฉื ื-GHCR ื-Actions.GCP_REGION: ืืืืจ ืขืืืจ Cloud Run (ืืืืืื, europe-west1).1. ืื ืืื ืืคืจืกืื ืชืืื ืช Docker ื-GHCR
# .github/workflows/build.yml
name: Build & Push to GHCR
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read # ืขืืืจ checkout
packages: write # ืขืืืจ push ื-GHCR
steps:
- uses: actions/checkout@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
# ืืืืฃ ืืช myapp ืืฉื ืืืืฉืื ืฉืื
run: docker build -t ${{ secrets.DOCKER_USERNAME }}/myapp:latest .
- name: Push Docker image
run: docker push ${{ secrets.DOCKER_USERNAME }}/myapp:latest
github.repository_owner: ืืขื ืืืืืจ (ืฉื ืืืฉืชืืฉ ืื ืืืจืืื ืฉืื).github.event.repository.name: ืฉื ืืืืืจ.myapp: ืฉื ืืืืฉืื/ืืชืืื ื ืฉืื.2. ืคืจืืกื ืืืืืืืืช ื-Staging (GCP Cloud Run)
# .github/workflows/deploy-staging.yml
name: Deploy to GCP Cloud Run (Staging)
on:
workflow_run:
workflows: ["Build & Push to GHCR"] # ืฉื ื-workflow ืฉื ืืื ืืื
types:
- completed
jobs:
deploy-staging:
runs-on: ubuntu-latest
# ืืคืขื ืจืง ืื ื-workflow ืฉื ืืื ืืื ืืกืชืืื ืืืฆืืื
if: ${{ github.event.workflow_run.conclusion == 'success' }}
# ืืฉืชืืฉ ืืกืืืืืช GitHub ืขืืืจ staging (ืืืคืฆืืื ืื, ืื ืคืจืงืืืงื ืืืื)
environment:
name: staging
url: ${{ steps.deploy.outputs.url }} # ื-URL ืืืื ืืืื ืืืืจ ืืคืจืืกื
steps:
- uses: actions/checkout@v3
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: 'Deploy to Cloud Run (Staging)'
id: deploy
uses: 'google-github-actions/deploy-cloudrun@v2'
with:
service: 'myapp-staging' # ืฉื ืฉืืจืืช ื-staging ืฉืื ื-Cloud Run
region: '${{ secrets.GCP_REGION }}'
# ืืฉืชืืฉ ืืชืืื ื ืฉื ืฉืืื ื-build.yml
image: 'ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest'
project_id: '${{ secrets.GCP_PROJECT_ID }}'
flags: '--allow-unauthenticated --platform=managed' # ืืคืฉืจ ืืืฉื ืื ืืืืืชืช ืืืืืื
3. ืคืจืืกื ื-Production ืขื ืืืฉืืจ ืืื ื (GCP Cloud Run)
# .github/workflows/deploy-prod.yml
name: Deploy to GCP Cloud Run (Production)
on:
workflow_dispatch: # ืืืคืฉืจ ืืคืขืื ืืื ืืช
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v3
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: 'Deploy to Cloud Run (Production)'
id: deploy
uses: 'google-github-actions/deploy-cloudrun@v2'
with:
service: 'myapp-production' # ืฉื ืฉืืจืืช ื-production ืฉืื
region: '${{ secrets.GCP_REGION }}'
image: 'ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest' # ืืฉืชืืฉ ืืืืชื ืชืืื ืช 'latest'
project_id: '${{ secrets.GCP_PROJECT_ID }}'
flags: '--allow-unauthenticated --platform=managed'
# ืขืืืจ production ื ืืชื ืืืืกืืฃ --no-traffic ืืืืืจ ืืื ืืืืืืฃ ืชื ืืขื ืืืืจืื
# traffic:
# latest: true
# percent: 100
workflow_run: ืืืคืฉืจ ืืืคืขืื workflow ืืื (ืคืจืืกืช staging) ืขื ืืฉืืืช workflow ืืืจ (ืื ืืื).workflow_dispatch: ืืืคืฉืจ ืืคืขืื ืืื ืืช ืฉื workflow (ืคืจืืกืช production), ืืืกืคืงืช ืฉืืืื.
GitHub Environments: ืืืคืฉืจืื ืื ืืืืืืจ ืืืื ืืื ื ืขืืืจ production (ืืืืืื, ืืจืืฉืช ืืืฉืืจ ืืืืงืจืื ืกืคืฆืืคืืื) ืืืืืกื ืกืืืืช ืกืคืฆืืคืืื ืืกืืืื.
GCP Cloud Run: ืืคืฉืจืืช serverless ืืฆืืื ืช ืืืคืขืืช ืืืฉืืืื ืืืืกืกื ืงืื ืืืื ืจืื.๐ ืืืืื โ ืื ืืฉืื!
ืืจืฉืืืช ืืื ืืืืืืช: ืขืืืจ ืืฉืืื ืืช ืฉืืจืืช (ืืืืืื, GCP), ืืขื ืง ืจืง ืืช ืืืจืฉืืืช ืื ืืืฆืืช ืืืืช ืืืืฆืืข ืืฉืืืืช CI/CD.
ืืืืื ืกืืืืืช: Staging ื-Production ืฆืจืืืื ืืืืืช ืืืืืืื ืืื ืืืคืฉืจ. ืคืจืืืงืืื/ืืฉืืื ืืช ืฉืื ืื ืืกืคืงื ืขื ื โ ืื ืคืจืงืืืงื ืืืื.
ืืื ืช ืขื ืคืื: ืืืืจ ืืื ื ืขืืืจ ืขื ืฃ main (ืื master) ืื ืฉ-push ืืืื ืืชืืคืฉืจ ืจืง ืืืืฆืขืืช Pull Request ืขื ืืืืงืืช CI ืืืื.