Skip to content

Terminal Lab Checklist — Thực Hành Trên Terminal

🎯 Mục tiêu

Trang này cung cấp 5 bài lab thực hành trên terminal được thiết kế có cấu trúc rõ ràng. Mỗi lab gồm: mục tiêu (objective), thiết lập (setup), các bước thực hiện (step-by-step commands), tiêu chí xác minh (verification criteria), và lỗi thường gặp (common mistakes). Hoàn thành tất cả 5 lab để củng cố kiến thức Phase 1.

Chuẩn bị môi trường

Bạn cần một môi trường Linux để thực hành. Sử dụng: Docker (docker run -it ubuntu:22.04 bash), WSL2 trên Windows, hoặc một VM/VPS.


Lab 1: Permissions — Tạo Môi Trường Deploy

Mục tiêu

Tạo cấu trúc thư mục deployment hoàn chỉnh với ownership và permissions đúng chuẩn — đảm bảo chỉ user/group được phép mới có thể truy cập file nhạy cảm.

Nhiệm vụ

  1. Tạo user deploy và group web-team
  2. Tạo cấu trúc thư mục: /opt/myapp/, /opt/myapp/logs/, /opt/myapp/data/, /opt/myapp/config/
  3. Gán ownership: deploy:web-team
  4. Thiết lập permissions: thư mục 750, config files 640, scripts 750, SSH keys 600
  5. Tạo file test và xác minh user khác không truy cập được file riêng tư

Các bước thực hiện

bash
# === Bước 1: Tạo user và group ===
sudo useradd -m -s /bin/bash deploy
sudo groupadd web-team
sudo usermod -aG web-team deploy

# === Bước 2: Tạo cấu trúc thư mục ===
sudo mkdir -p /opt/myapp/{logs,data,config}

# === Bước 3: Gán ownership ===
sudo chown -R deploy:web-team /opt/myapp

# === Bước 4: Thiết lập permissions ===
sudo chmod 750 /opt/myapp
sudo chmod 750 /opt/myapp/logs /opt/myapp/data
sudo chmod 750 /opt/myapp/config

# === Bước 5: Tạo file config mẫu ===
sudo -u deploy bash -c 'echo "DB_URL=postgres://..." > /opt/myapp/config/app.env'
sudo chmod 640 /opt/myapp/config/app.env

# === Bước 6: Tạo script mẫu ===
sudo -u deploy bash -c 'cat > /opt/myapp/start.sh << "EOF"
#!/bin/bash
echo "Starting application..."
EOF'
sudo chmod 750 /opt/myapp/start.sh

# === Bước 7: Tạo SSH key mẫu ===
sudo -u deploy bash -c 'mkdir -p /opt/myapp/config/.ssh && echo "PRIVATE_KEY" > /opt/myapp/config/.ssh/id_ed25519'
sudo chmod 600 /opt/myapp/config/.ssh/id_ed25519

Xác minh kết quả

bash
# ✅ Check: deploy user có thể đọc config
sudo -u deploy cat /opt/myapp/config/app.env

# ✅ Check: user khác KHÔNG THỂ đọc config
sudo -u nobody cat /opt/myapp/config/app.env  # Phải thất bại: Permission denied

# ✅ Check: permissions đúng
ls -la /opt/myapp/
stat -c "%a %U:%G %n" /opt/myapp/config/app.env
# Kết quả mong đợi: 640 deploy:web-team /opt/myapp/config/app.env

# ✅ Check: SSH key có permission 600
stat -c "%a %U:%G %n" /opt/myapp/config/.ssh/id_ed25519
# Kết quả mong đợi: 600 deploy:web-team /opt/myapp/config/.ssh/id_ed25519

# ✅ Check: script có permission 750
stat -c "%a %U:%G %n" /opt/myapp/start.sh
# Kết quả mong đợi: 750 deploy:web-team /opt/myapp/start.sh

Lỗi thường gặp

Đừng dùng 777!

Khi gặp lỗi "Permission denied", nhiều người sẽ chạy chmod 777 cho nhanh. Đây là sai lầm nghiêm trọng — bất kỳ user nào trên hệ thống đều có thể đọc secrets (database password, API keys). Trong production, đây là lỗ hổng bảo mật cấp cao.

Nguyên tắc: Luôn bắt đầu với permission hạn chế nhất (600/640/750) rồi mở rộng khi cần, không bao giờ làm ngược lại.


Lab 2: Processes — Quản Lý Tiến Trình

Mục tiêu

Khởi chạy tiến trình, giám sát trạng thái, gửi signal, nhận diện zombie process, và dọn dẹp — hiểu trọn vẹn vòng đời của một tiến trình Linux.

Nhiệm vụ

  1. Khởi chạy một tiến trình chạy nền (background process)
  2. Tìm PID bằng ps/proc
  3. Gửi tín hiệu SIGHUP, SIGTERM
  4. Tạo và nhận diện zombie process
  5. Dọn dẹp tiến trình

Các bước thực hiện

bash
# === Bước 1: Khởi chạy tiến trình với signal handler ===
python3 -c "
import time, signal, os

def handler(sig, frame):
    print(f'Received signal {sig}')

signal.signal(signal.SIGHUP, handler)
signal.signal(signal.SIGTERM, handler)

print(f'PID: {os.getpid()}')
while True:
    time.sleep(1)
" &

# === Bước 2: Tìm tiến trình ===
ps aux | grep python3
cat /proc/$(pgrep -f "import time")/status | grep -E "^(Name|State|Pid)"

# === Bước 3: Gửi signal ===
kill -HUP $(pgrep -f "import time")   # SIGHUP — tiến trình in "Received signal 1"
kill $(pgrep -f "import time")         # SIGTERM — yêu cầu dừng graceful

# === Bước 4: Tạo zombie process để quan sát ===
python3 -c "
import os, time

pid = os.fork()
if pid == 0:
    # Child process — thoát ngay
    os._exit(0)
else:
    # Parent process — không gọi wait(), tạo zombie
    print(f'Parent PID: {os.getpid()}, Child PID: {pid}')
    time.sleep(30)  # Giữ parent sống để child thành zombie
" &

# Quan sát zombie (sau 1-2 giây)
sleep 2
ps aux | grep -E "Z|defunct"

# === Bước 5: Dọn dẹp ===
kill $(pgrep -f "os.fork")   # Kill parent → init nhận zombie → tự dọn

Xác minh kết quả

bash
# ✅ Check: tiến trình nhận được SIGHUP (xem output "Received signal 1")
# ✅ Check: tiến trình đã dừng sau SIGTERM
ps aux | grep python3  # Không còn xuất hiện

# ✅ Check: zombie process xuất hiện với trạng thái Z
ps aux | grep defunct
# Cột STAT hiển thị "Z+" — đây là zombie

# ✅ Check: sau khi kill parent, zombie biến mất
sleep 1
ps aux | grep defunct  # Không còn zombie

Lỗi thường gặp

Đừng dùng kill -9 đầu tiên!

kill -9 (SIGKILL) buộc tiến trình chết ngay lập tức mà không cho cơ hội dọn dẹp — temp files không xóa, database connections không đóng, lock files vẫn còn. Luôn thử theo thứ tự: SIGTERM (15) → đợi vài giây → SIGKILL (9) chỉ khi thực sự cần thiết.


Lab 3: systemd — Chạy Service

Mục tiêu

Viết file unit systemd, khởi chạy và kích hoạt service, đọc logs — biến một script thành service chuyên nghiệp có thể tự khởi động lại khi lỗi.

Nhiệm vụ

  1. Tạo một Python HTTP server script đơn giản
  2. Viết file .service cho systemd
  3. Khởi chạy và kích hoạt service
  4. Xác minh service đang chạy và sống sót qua restart
  5. Đọc logs bằng journalctl

Các bước thực hiện

bash
# === Bước 1: Tạo script cho service ===
sudo mkdir -p /opt/mywebapp
sudo tee /opt/mywebapp/server.py > /dev/null << 'EOF'
#!/usr/bin/env python3
"""Simple HTTP server for systemd lab."""
from http.server import HTTPServer, SimpleHTTPRequestHandler
import logging
import os

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
logger = logging.getLogger(__name__)

PORT = int(os.environ.get('APP_PORT', 8080))

class HealthHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/health':
            self.send_response(200)
            self.send_header('Content-Type', 'text/plain')
            self.end_headers()
            self.wfile.write(b'OK')
            logger.info('Health check passed')
        else:
            self.send_response(200)
            self.send_header('Content-Type', 'text/plain')
            self.end_headers()
            self.wfile.write(b'Hello from systemd service!')

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', PORT), HealthHandler)
    logger.info(f'Server starting on port {PORT}')
    server.serve_forever()
EOF

sudo chmod +x /opt/mywebapp/server.py

# === Bước 2: Tạo systemd unit file ===
sudo tee /etc/systemd/system/mywebapp.service > /dev/null << 'EOF'
[Unit]
Description=My Web Application
After=network.target
Documentation=https://example.com/docs

[Service]
Type=simple
User=deploy
Group=web-team
WorkingDirectory=/opt/mywebapp
ExecStart=/usr/bin/python3 /opt/mywebapp/server.py
Restart=on-failure
RestartSec=5
Environment=APP_PORT=8080

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/mywebapp/logs

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mywebapp

[Install]
WantedBy=multi-user.target
EOF

# === Bước 3: Reload systemd, start và enable service ===
sudo systemctl daemon-reload
sudo systemctl start mywebapp.service
sudo systemctl enable mywebapp.service

# === Bước 4: Kiểm tra trạng thái ===
sudo systemctl status mywebapp.service

# === Bước 5: Đọc logs ===
# Xem logs gần nhất
sudo journalctl -u mywebapp.service -n 20 --no-pager

# Xem logs realtime (Ctrl+C để thoát)
sudo journalctl -u mywebapp.service -f

Xác minh kết quả

bash
# ✅ Check: service đang chạy
sudo systemctl is-active mywebapp.service
# Kết quả mong đợi: active

# ✅ Check: service được enable (tự khởi động khi boot)
sudo systemctl is-enabled mywebapp.service
# Kết quả mong đợi: enabled

# ✅ Check: HTTP server phản hồi
curl -s http://localhost:8080/health
# Kết quả mong đợi: OK

# ✅ Check: tự restart sau khi crash
sudo kill -9 $(pgrep -f "server.py")
sleep 6  # Đợi RestartSec (5s) + thêm 1s
sudo systemctl is-active mywebapp.service
# Kết quả mong đợi: active (systemd đã restart tự động)

# ✅ Check: logs ghi nhận restart
sudo journalctl -u mywebapp.service -n 10 --no-pager
# Phải thấy "Server starting on port 8080" xuất hiện 2 lần

Lỗi thường gặp

Quên daemon-reload!

Sau khi sửa file .service, bạn phải chạy sudo systemctl daemon-reload trước khi restart. Nếu không, systemd vẫn dùng bản cũ đã cache — thay đổi của bạn sẽ không có hiệu lực và bạn sẽ mất hàng giờ debug "tại sao config mới không hoạt động".

Thêm lỗi phổ biến:

  • Quên User= → service chạy bằng root (nguy hiểm)
  • Sai ExecStart path → service không start được, kiểm tra bằng journalctl -xe
  • Thiếu After=network.target → service start trước khi network sẵn sàng, kết nối DB thất bại

Lab 4: Bash — Viết Deploy Script

Mục tiêu

Viết một deploy script hoàn chỉnh với error handling, backup trước khi deploy, health check sau khi deploy, và rollback tự động khi có lỗi — mô phỏng quy trình deploy thực tế trong production.

Nhiệm vụ

  1. Viết script với set -euo pipefail
  2. Thêm logic backup phiên bản hiện tại
  3. Thêm health check với retry mechanism
  4. Thêm rollback tự động khi lỗi (dùng trap ERR)
  5. Kiểm tra script bằng shellcheck

Các bước thực hiện

bash
# === Bước 1: Tạo thư mục project ===
mkdir -p /opt/deploy-lab/{releases,backups,shared}
echo "v1.0.0 — old version" > /opt/deploy-lab/releases/current.txt

# === Bước 2: Viết deploy script ===
cat > /opt/deploy-lab/deploy.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

# === Cấu hình ===
APP_DIR="/opt/deploy-lab"
RELEASES_DIR="${APP_DIR}/releases"
BACKUPS_DIR="${APP_DIR}/backups"
HEALTH_URL="http://localhost:8080/health"
MAX_RETRIES=5
RETRY_DELAY=2
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# === Logging ===
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

log_error() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ❌ ERROR: $1" >&2
}

# === Rollback khi có lỗi ===
rollback() {
    log_error "Deploy thất bại! Đang rollback..."

    local latest_backup
    latest_backup=$(ls -t "${BACKUPS_DIR}"/*.tar.gz 2>/dev/null | head -1)

    if [[ -n "${latest_backup}" ]]; then
        log "Khôi phục từ backup: ${latest_backup}"
        tar -xzf "${latest_backup}" -C "${RELEASES_DIR}/"
        log "✅ Rollback thành công"
    else
        log_error "Không tìm thấy backup để rollback!"
        exit 1
    fi
}

trap rollback ERR

# === Bước 1: Backup phiên bản hiện tại ===
log "📦 Đang backup phiên bản hiện tại..."
if [[ -f "${RELEASES_DIR}/current.txt" ]]; then
    tar -czf "${BACKUPS_DIR}/backup_${TIMESTAMP}.tar.gz" \
        -C "${RELEASES_DIR}" current.txt
    log "✅ Backup hoàn tất: backup_${TIMESTAMP}.tar.gz"
else
    log "⚠️  Không có phiên bản hiện tại để backup"
fi

# === Bước 2: Deploy phiên bản mới ===
log "🚀 Đang deploy phiên bản mới..."
echo "v2.0.0 — new version deployed at ${TIMESTAMP}" > "${RELEASES_DIR}/current.txt"
log "✅ Deploy file mới thành công"

# === Bước 3: Health check với retry ===
log "🏥 Đang kiểm tra health check..."
health_ok=false
for i in $(seq 1 "${MAX_RETRIES}"); do
    log "  Lần thử ${i}/${MAX_RETRIES}..."

    if curl -sf --max-time 5 "${HEALTH_URL}" > /dev/null 2>&1; then
        health_ok=true
        break
    fi

    if [[ "${i}" -lt "${MAX_RETRIES}" ]]; then
        log "  Đợi ${RETRY_DELAY}s trước lần thử tiếp..."
        sleep "${RETRY_DELAY}"
    fi
done

if [[ "${health_ok}" = true ]]; then
    log "✅ Health check passed!"
else
    log_error "Health check thất bại sau ${MAX_RETRIES} lần thử"
    exit 1  # trap ERR sẽ gọi rollback()
fi

# === Bước 4: Dọn dẹp backup cũ (giữ 5 bản gần nhất) ===
log "🧹 Dọn dẹp backup cũ..."
ls -t "${BACKUPS_DIR}"/*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f
log "✅ Deploy hoàn tất thành công!"
SCRIPT

chmod +x /opt/deploy-lab/deploy.sh
bash
# === Bước 3: Cài shellcheck và kiểm tra ===
# Trên Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y shellcheck

# Chạy shellcheck
shellcheck /opt/deploy-lab/deploy.sh
bash
# === Bước 4: Chạy deploy script ===
# Lưu ý: Health check sẽ thất bại nếu không có server chạy trên port 8080
# Để test rollback, chạy khi KHÔNG có server:
bash /opt/deploy-lab/deploy.sh

# Để test thành công, start server từ Lab 3 trước:
sudo systemctl start mywebapp.service
bash /opt/deploy-lab/deploy.sh

Xác minh kết quả

bash
# ✅ Check: shellcheck không có lỗi
shellcheck /opt/deploy-lab/deploy.sh
echo $?  # Kết quả mong đợi: 0

# ✅ Check: backup được tạo
ls -la /opt/deploy-lab/backups/
# Phải thấy file backup_YYYYMMDD_HHMMSS.tar.gz

# ✅ Check: rollback hoạt động (test khi không có server)
cat /opt/deploy-lab/releases/current.txt
# Nếu health check thất bại → nội dung phải là "v1.0.0 — old version" (đã rollback)

# ✅ Check: deploy thành công (test khi có server)
sudo systemctl start mywebapp.service
bash /opt/deploy-lab/deploy.sh
cat /opt/deploy-lab/releases/current.txt
# Kết quả mong đợi: "v2.0.0 — new version deployed at ..."

Lỗi thường gặp

Quên set -euo pipefail!

Không có set -euo pipefail, script sẽ tiếp tục chạy ngay cả khi có lệnh thất bại. Hậu quả: deploy phiên bản lỗi mà không rollback, dữ liệu production bị ảnh hưởng.

Giải thích từng flag:

  • set -e → Thoát ngay khi có lệnh trả về exit code khác 0
  • set -u → Báo lỗi khi dùng biến chưa khai báo (tránh rm -rf $UNSET_VAR/ → xóa /)
  • set -o pipefail → Pipe trả về exit code của lệnh thất bại cuối cùng (không phải lệnh cuối)

Thêm lỗi phổ biến:

  • Không quote biến ($VAR thay vì "${VAR}") → lỗi khi path có khoảng trắng
  • Dùng rm -rf mà không kiểm tra biến có rỗng không → tai nạn xóa toàn bộ filesystem

Lab 5: SSH — Thiết Lập Key Auth & Tunnel

Mục tiêu

Thiết lập SSH key-based authentication, cấu hình SSH config với aliases, và tạo local port forward tunnel — loại bỏ password authentication, tăng cường bảo mật.

Nhiệm vụ

  1. Tạo cặp key Ed25519
  2. Thiết lập ~/.ssh/config với aliases
  3. Tạo local port forward tunnel
  4. Xác minh tunnel hoạt động

Lab này có thể thực hành trên localhost

Bạn không cần VPS để thực hành. Dùng localhost hoặc Docker container làm target: docker run -d --name ssh-lab -p 2222:22 ubuntu:22.04.

Các bước thực hiện

bash
# === Bước 1: Chuẩn bị SSH server (dùng Docker) ===
docker run -d --name ssh-lab -p 2222:22 ubuntu:22.04 bash -c "
    apt-get update && apt-get install -y openssh-server sudo &&
    mkdir -p /run/sshd &&
    useradd -m -s /bin/bash labuser &&
    echo 'labuser:labpass' | chpasswd &&
    echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config &&
    /usr/sbin/sshd -D
"

# Đợi container sẵn sàng
sleep 3

# === Bước 2: Tạo cặp key Ed25519 ===
ssh-keygen -t ed25519 -C "lab@penalgo" -f ~/.ssh/lab_ed25519 -N ""

# Xem public key
cat ~/.ssh/lab_ed25519.pub

# === Bước 3: Copy public key lên server ===
ssh-copy-id -i ~/.ssh/lab_ed25519.pub -p 2222 labuser@localhost
# Nhập password: labpass

# === Bước 4: Thiết lập SSH config ===
cat >> ~/.ssh/config << 'EOF'

# --- Lab SSH Config ---
Host lab-server
    HostName localhost
    Port 2222
    User labuser
    IdentityFile ~/.ssh/lab_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host lab-tunnel
    HostName localhost
    Port 2222
    User labuser
    IdentityFile ~/.ssh/lab_ed25519
    IdentitiesOnly yes
    LocalForward 9090 localhost:8080
EOF

# Bảo vệ file config
chmod 600 ~/.ssh/config

# === Bước 5: Test kết nối bằng alias ===
ssh lab-server "echo 'SSH key auth hoạt động!'"

# === Bước 6: Tắt password authentication trên server ===
docker exec ssh-lab bash -c "
    sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config &&
    kill -HUP \$(cat /run/sshd.pid)
"

# Xác minh: đăng nhập bằng password phải thất bại
ssh -p 2222 -o IdentitiesOnly=yes -o PreferredAuthentications=password labuser@localhost
# Kết quả mong đợi: Permission denied

# === Bước 7: Tạo local port forward tunnel ===
# Mở tunnel trong background: forward local:9090 → remote:8080
ssh -f -N lab-tunnel
# -f: chạy nền, -N: không chạy command, chỉ forward port

# === Bước 8: Test tunnel (cần service chạy trên port 8080 bên server) ===
# Nếu bạn đã hoàn thành Lab 3, start server trên container:
docker exec ssh-lab bash -c "
    apt-get install -y python3 &&
    python3 -c '
from http.server import HTTPServer, BaseHTTPRequestHandler

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b\"Hello from tunnel!\")

HTTPServer((\"0.0.0.0\", 8080), H).serve_forever()
    ' &
"

sleep 2
# Truy cập qua tunnel
curl http://localhost:9090
# Kết quả mong đợi: Hello from tunnel!

Xác minh kết quả

bash
# ✅ Check: key pair tồn tại với permission đúng
ls -la ~/.ssh/lab_ed25519*
stat -c "%a %n" ~/.ssh/lab_ed25519
# Kết quả mong đợi: 600 /home/user/.ssh/lab_ed25519 (private key)
stat -c "%a %n" ~/.ssh/lab_ed25519.pub
# Kết quả mong đợi: 644 /home/user/.ssh/lab_ed25519.pub (public key)

# ✅ Check: SSH config hoạt động
ssh lab-server "hostname && whoami"
# Kết quả: hostname của container + "labuser"

# ✅ Check: password auth đã bị tắt
ssh -p 2222 -o PreferredAuthentications=password -o PubkeyAuthentication=no labuser@localhost 2>&1
# Kết quả mong đợi: Permission denied

# ✅ Check: tunnel đang chạy
ss -tlnp | grep 9090
# Hoặc: lsof -i :9090
# Phải thấy ssh process lắng nghe trên port 9090

# ✅ Check: tunnel chuyển tiếp đúng
curl -s http://localhost:9090
# Kết quả mong đợi: Hello from tunnel!
bash
# === Dọn dẹp sau lab ===
# Kill tunnel
kill $(pgrep -f "ssh -f -N lab-tunnel")

# Xóa container
docker stop ssh-lab && docker rm ssh-lab

# Xóa config lab (tuỳ chọn)
sed -i '/# --- Lab SSH Config ---/,/LocalForward/d' ~/.ssh/config
rm -f ~/.ssh/lab_ed25519 ~/.ssh/lab_ed25519.pub

Lỗi thường gặp

Permission quá mở trên SSH key!

SSH từ chối sử dụng private key nếu permission quá mở. Lỗi bạn sẽ gặp:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/user/.ssh/lab_ed25519' are too open.

Quy tắc permission SSH:

  • Private key (id_ed25519): 600 (chỉ owner đọc/ghi)
  • Public key (id_ed25519.pub): 644 (ai cũng đọc được, không sao)
  • ~/.ssh/ directory: 700
  • ~/.ssh/config: 600
  • ~/.ssh/authorized_keys: 600

Thêm lỗi phổ biến:

  • Quên IdentitiesOnly yes → SSH thử tất cả key, server khoá sau 3 lần thử sai
  • Dùng RSA thay vì Ed25519 → RSA chậm hơn và key dài hơn, Ed25519 là chuẩn hiện đại
  • Không thiết lập ServerAliveInterval → connection bị drop sau vài phút idle

Completion Checklist

✅ Checklist triển khai

Đánh dấu các lab bạn đã hoàn thành:

  • [ ] Lab 1: Cấu trúc thư mục với permissions đúng (640/750/600)
  • [ ] Lab 2: Quản lý vòng đời tiến trình với signals (SIGHUP, SIGTERM)
  • [ ] Lab 3: systemd service đang chạy, tự restart, và ghi log
  • [ ] Lab 4: Deploy script pass shellcheck, có backup + rollback
  • [ ] Lab 5: SSH key auth hoạt động, tunnel forward đúng port

Mục tiêu

Hoàn thành tất cả 5 lab trước khi chuyển sang bài tiếp theo. Nếu bạn gặp khó ở lab nào, hãy quay lại bài lý thuyết tương ứng để đọc lại.


Tiếp theo