0

🚀 Chapter 2 — Automation: Phần A — Thiết lập CD với GitHub Actions + SSH trên AWS

Chapter 1, chúng ta đã deploy Todo List App thủ công lên Amazon EC2:

Developer
    ↓
SSH vào EC2
    ↓
git pull
    ↓
docker compose build
    ↓
docker compose up -d

Cách này hoạt động, nhưng mỗi lần có code mới chúng ta lại phải SSH vào server và thực hiện các command thủ công.

Trong Chapter 2, chúng ta sẽ tự động hóa quá trình này bằng GitHub Actions.

Mục tiêu:

Developer
    ↓
git push
    ↓
GitHub
    ↓
GitHub Actions
    ↓
SSH
    ↓
Amazon EC2
    ↓
git pull
    ↓
docker compose build
    ↓
docker compose up -d

Sau khi hoàn thành, developer chỉ cần:

git add .
git commit -m "update app"
git push

Phần còn lại sẽ được GitHub Actions thực hiện tự động.


1. Kiến trúc

Kiến trúc của Chapter 2:

┌──────────────┐
│  Developer   │
└──────┬───────┘
       │ git push
       ▼
┌──────────────┐
│    GitHub    │
└──────┬───────┘
       │ trigger
       ▼
┌──────────────────────┐
│   GitHub Actions     │
│                      │
│  1. Checkout         │
│  2. Test             │
│  3. SSH              │
└──────────┬───────────┘
           │ SSH
           ▼
┌──────────────────────┐
│      AWS EC2         │
│                      │
│  git pull            │
│  docker compose      │
│  build               │
│  up -d               │
└──────────────────────┘

Ở Chapter này:

  • GitHub Actions = CI/CD runner
  • EC2 = deployment server
  • SSH = phương thức kết nối
  • Docker Compose = phương thức chạy application

Chúng ta chưa sử dụng ECR, ECS hoặc Kubernetes.

Các phần đó sẽ được sử dụng ở các Chapter sau.


2. Chuẩn bị

Giả sử Chapter 1 đã hoàn thành.

EC2 đã có:

Ubuntu 24.04
Docker
Docker Compose
Nginx
Todo List App

Kiểm tra EC2:

ssh -i ~/Downloads/chapter1-key.pem ubuntu@<EC2_PUBLIC_IP>

Ví dụ:

ssh -i ~/Downloads/chapter1-key.pem ubuntu@13.250.xxx.xxx

Kiểm tra Docker:

docker --version

Kiểm tra Compose:

docker compose version

Kiểm tra application:

cd ~/todo-list-app
docker compose ps

Kết quả cần có container đang chạy.


3. Kiểm tra application trên EC2

Chạy:

git log -1

Kiểm tra Docker:

docker compose ps

Sau đó mở:

http://<EC2_PUBLIC_IP>

Nếu Todo List App đang hoạt động, tiếp tục.


4. Tạo SSH key dành riêng cho GitHub Actions

Không nên sử dụng trực tiếp key EC2 ban đầu cho mọi mục đích.

Trên máy Mac:

ssh-keygen -t ed25519 \
  -C "github-actions-deploy" \
  -f ~/.ssh/github-actions-ec2

Khi được hỏi:

Enter passphrase:

Trong lab có thể để trống bằng cách nhấn:

Enter

Sau đó sẽ có:

~/.ssh/github-actions-ec2
~/.ssh/github-actions-ec2.pub

Kiểm tra:

ls -l ~/.ssh/github-actions-ec2*

5. Copy public key lên EC2

Hiển thị public key:

cat ~/.ssh/github-actions-ec2.pub

Kết quả:

ssh-ed25519 AAAAC3... github-actions-deploy

SSH vào EC2 bằng key hiện tại:

ssh -i ~/Downloads/chapter1-key.pem ubuntu@<EC2_PUBLIC_IP>

Mở:

nano ~/.ssh/authorized_keys

Thêm public key vừa tạo vào cuối file:

ssh-ed25519 AAAAC3... github-actions-deploy

Lưu file.

Kiểm tra permission:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

6. Test SSH key mới

Thoát khỏi EC2:

exit

Từ Mac chạy:

ssh -i ~/.ssh/github-actions-ec2 ubuntu@<EC2_PUBLIC_IP>

Nếu đăng nhập thành công:

Welcome to Ubuntu ...

thì SSH key đã hoạt động.

Thoát:

exit

7. Lấy nội dung private key

Trên Mac:

cat ~/.ssh/github-actions-ec2

Nội dung có dạng:

-----BEGIN OPENSSH PRIVATE KEY-----
...
...
-----END OPENSSH PRIVATE KEY-----

Không commit file này vào Git repository.

GitHub Actions hỗ trợ lưu các giá trị nhạy cảm dưới dạng Secrets và chỉ workflow được khai báo sử dụng mới có thể đọc chúng.


8. Tạo GitHub Secrets

Mở repository Todo List App trên GitHub.

Đi tới:

Settings
    ↓
Secrets and variables
    ↓
Actions
    ↓
New repository secret

GitHub hiện cung cấp repository secrets từ khu vực Settings → Secrets and variables → Actions.

Tạo các secret:

EC2_HOST
EC2_USERNAME
EC2_SSH_KEY

8.1. EC2_HOST

Value:

<EC2_PUBLIC_IP>

Ví dụ:

13.250.xxx.xxx

8.2. EC2_USERNAME

Ubuntu EC2 sử dụng:

ubuntu

8.3. EC2_SSH_KEY

Value là toàn bộ nội dung:

-----BEGIN OPENSSH PRIVATE KEY-----
...
...
-----END OPENSSH PRIVATE KEY-----

Không thêm:

EC2_SSH_KEY=

Chỉ paste nội dung private key.


9. Kiểm tra GitHub Secrets

Trong GitHub:

Settings
    ↓
Secrets and variables
    ↓
Actions

Bạn sẽ thấy:

EC2_HOST
EC2_USERNAME
EC2_SSH_KEY

GitHub sẽ không hiển thị lại giá trị secret sau khi lưu.


10. Tạo GitHub Actions workflow

Ở local repository:

cd ~/todo-list-app

Tạo thư mục:

mkdir -p .github/workflows

Tạo file:

nano .github/workflows/deploy.yml

11. Workflow đầu tiên — Test SSH

Trước tiên không deploy.

Chỉ kiểm tra GitHub Actions có thể SSH vào EC2.

Thêm:

name: Deploy to EC2

on:
  workflow_dispatch:

jobs:
  test-ssh:
    runs-on: ubuntu-latest

    steps:
      - name: Test SSH connection
        uses: appleboy/ssh-action@v1.2.2
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            echo "SSH connection successful"
            hostname
            docker --version
            docker compose version

12. Push workflow lên GitHub

Kiểm tra:

git status

Add:

git add .github/workflows/deploy.yml

Commit:

git commit -m "add EC2 deployment workflow"

Push:

git push origin main

13. Chạy GitHub Actions

Mở GitHub:

Repository
    ↓
Actions
    ↓
Deploy to EC2

Chọn:

Run workflow

Chọn branch:

main

Sau đó:

Run workflow

14. Kiểm tra kết quả

Nếu thành công, log sẽ có:

SSH connection successful

Sau đó:

hostname

và:

Docker version ...
Docker Compose version ...

Flow lúc này:

GitHub
   ↓
GitHub Actions
   ↓
SSH
   ↓
EC2

Chúng ta đã chứng minh GitHub Actions có thể kết nối tới EC2.


15. Thay SSH test bằng deployment

Bây giờ sửa:

nano .github/workflows/deploy.yml

Thành:

name: Deploy to EC2

on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Deploy application
        uses: appleboy/ssh-action@v1.2.2
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            set -e

            cd ~/todo-list-app

            echo "Pull latest code"
            git pull origin main

            echo "Build Docker image"
            docker compose build

            echo "Start application"
            docker compose up -d

            echo "Deployment completed"

16. Test deployment thủ công

Commit:

git add .github/workflows/deploy.yml
git commit -m "deploy todo app to EC2"
git push origin main

Vào:

GitHub
    ↓
Actions
    ↓
Deploy to EC2
    ↓
Run workflow

Chạy workflow.

GitHub Actions sẽ thực hiện:

SSH
 ↓
cd ~/todo-list-app
 ↓
git pull
 ↓
docker compose build
 ↓
docker compose up -d

17. Kiểm tra EC2

SSH vào EC2:

ssh -i ~/Downloads/chapter1-key.pem ubuntu@<EC2_PUBLIC_IP>

Đi tới application:

cd ~/todo-list-app

Kiểm tra commit:

git log -1

Kiểm tra container:

docker compose ps

Kiểm tra logs:

docker compose logs --tail=50

18. Chuyển sang tự động Deploy khi push

Hiện tại workflow chỉ chạy khi:

workflow_dispatch:

Tức là phải bấm:

Run workflow

Bây giờ thay bằng:

on:
  push:
    branches:
      - main

Workflow hoàn chỉnh:

name: Deploy to EC2

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Deploy application
        uses: appleboy/ssh-action@v1.2.2
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            set -e

            cd ~/todo-list-app

            echo "Pull latest code"
            git pull origin main

            echo "Build Docker image"
            docker compose build

            echo "Start application"
            docker compose up -d

            echo "Deployment completed"

19. Test Automatic Deployment

Sửa Todo List App.

Ví dụ:

nano <some-file>

Sau đó:

git status

Commit:

git add .
git commit -m "update todo app"

Push:

git push origin main

Không SSH vào EC2.

Không chạy:

git pull

Không chạy:

docker compose build

Không chạy:

docker compose up -d

GitHub Actions sẽ tự chạy.


20. Theo dõi Pipeline

GitHub:

Actions
    ↓
Deploy to EC2
    ↓
Running

Flow:

Developer
    │
    │ git push
    ▼
 GitHub
    │
    │ trigger
    ▼
GitHub Actions
    │
    │ SSH
    ▼
   EC2
    │
    ├── git pull
    │
    ├── docker compose build
    │
    └── docker compose up -d

21. Kiểm tra application

Sau khi workflow hoàn thành:

http://<EC2_PUBLIC_IP>

Mở Todo List App.

Nếu application đã thay đổi theo commit mới, CD đã hoạt động.


22. Thêm bước kiểm tra trước khi Deploy

Hiện tại:

git push
   ↓
Deploy

Không nên deploy ngay mà không kiểm tra code.

Ta chuyển thành:

git push
   ↓
Test
   ↓
Deploy

Workflow:

name: CI/CD

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to EC2
        uses: appleboy/ssh-action@v1.2.2
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            set -e

            cd ~/todo-list-app

            git pull origin main

            docker compose build

            docker compose up -d

Nếu repository hiện tại dùng Yarn thay vì npm, thay phần npm ci / npm test bằng command tương ứng với package.json của project.


23. Hiểu needs: test

Đoạn:

deploy:
  needs: test

có nghĩa:

Test
 │
 ├── SUCCESS
 │      ↓
 │    Deploy
 │
 └── FAILED
        ↓
      STOP

Nếu test fail:

Developer
    ↓
GitHub
    ↓
Test ❌
    ↓
STOP

Không deploy code lỗi lên EC2.


24. Kiểm tra trường hợp Test Failed

Tạo một thay đổi khiến test fail.

git add .
git commit -m "test failed deployment"
git push origin main

Vào:

GitHub
    ↓
Actions

Kết quả:

test      ❌
deploy    ⏭️

Kiểm tra EC2:

ssh -i ~/Downloads/chapter1-key.pem ubuntu@<EC2_PUBLIC_IP>
cd ~/todo-list-app
git log -1

Application không được deploy version lỗi.


25. Kiểm tra Deployment thành công

Sửa lại code.

git add .
git commit -m "fix test"
git push origin main

Flow:

GitHub
   ↓
GitHub Actions
   ↓
Test ✅
   ↓
Deploy
   ↓
SSH
   ↓
EC2
   ↓
git pull
   ↓
docker compose build
   ↓
docker compose up -d

26. Kiểm tra GitHub Actions Secrets

Không được viết:

host: 13.250.xxx.xxx
key: |
  -----BEGIN OPENSSH PRIVATE KEY-----
  ...

trực tiếp vào workflow.

Thay vào đó:

host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}

GitHub Secrets được thiết kế để lưu các thông tin nhạy cảm cho workflow; GitHub cũng khuyến nghị sử dụng credential với quyền tối thiểu cần thiết.


27. Kiểm tra toàn bộ hệ thống

GitHub

Repository
    ↓
Actions

Phải thấy:

CI/CD
  ✓

EC2

docker compose ps

Application

http://<EC2_PUBLIC_IP>

Git

git log -1

Commit trên EC2 phải giống commit mới nhất trên GitHub.


28. Chapter 2 hoàn chỉnh

Sau khi hoàn thành:

                     Developer
                         │
                         │ git push
                         ▼
                    ┌─────────┐
                    │ GitHub  │
                    └────┬────┘
                         │
                         ▼
                ┌─────────────────┐
                │ GitHub Actions  │
                │                 │
                │ Checkout        │
                │ Test            │
                │                 │
                │      SSH        │
                └────────┬────────┘
                         │
                         ▼
                ┌─────────────────┐
                │     AWS EC2     │
                │                 │
                │   git pull      │
                │       ↓         │
                │ docker build    │
                │       ↓         │
                │ docker compose  │
                └────────┬────────┘
                         │
                         ▼
                  Todo List App

So sánh trước và sau

Chapter 1

Developer
    ↓
SSH
    ↓
EC2
    ↓
git pull
    ↓
docker compose build
    ↓
docker compose up

Chapter 2

Developer
    ↓
git push
    ↓
GitHub
    ↓
GitHub Actions
    ↓
Test
    ↓
SSH
    ↓
EC2
    ↓
Deploy

Developer không còn phải thực hiện deployment thủ công nữa.


29. Những gì Chapter 2 đã đạt được

✓ GitHub Actions
✓ CI
✓ CD
✓ SSH deployment
✓ EC2 deployment
✓ Docker Compose deployment
✓ GitHub Secrets
✓ Automatic deployment
✓ Test trước Deploy

Nhưng architecture hiện tại vẫn còn một vấn đề:

GitHub Actions
      ↓
SSH
      ↓
EC2
      ↓
git pull
      ↓
docker compose build

Khi số lượng service tăng:

service A → workflow A
service B → workflow B
service C → workflow C
service D → workflow D
...

Pipeline bắt đầu bị lặp lại.

Đó chính là vấn đề mà Chapter 3 — Scale sẽ giải quyết bằng Google Online Boutique + Reusable GitHub Actions Workflow + Matrix CI.


Chapter 2 → Chapter 3

Chapter 1
Manual Deployment
      │
      ▼
Chapter 2
GitHub Actions + SSH
      │
      │ "Một service đã tự động deploy"
      ▼
Chapter 3
Google Online Boutique
      │
      ▼
Nhiều Microservices
      │
      ▼
Reusable Workflow
      │
      ▼
Matrix CI
      │
      ▼
Scalable CI/CD Platform

Đây là bước chuyển:

Automation
    ↓
Scale

và là nền tảng để các Chapter sau tiếp tục phát triển thành:

Chapter 4
ECR + Security + Artifact Governance
        ↓
Chapter 5
EKS + Helm + Argo CD + GitOps
        ↓
Chapter 6
Observability + Reliability

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí