🚀 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 testbằng command tương ứng vớipackage.jsoncủ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