DevOps 2 — Git & Quản lý mã nguồn
Version control là gì và vì sao cần nó
Version control (quản lý phiên bản) là hệ thống ghi lại mọi thay đổi của mã nguồn theo thời gian, để bạn có thể xem lại lịch sử, quay về một trạng thái cũ, và hiểu ai đã thay đổi cái gì, lúc nào, vì sao. Nếu không có nó, bạn sẽ rơi vào cảnh code_final.py, code_final_v2.py, code_final_THAT_lan_nay.py — vừa rối, vừa dễ mất dữ liệu, vừa không thể làm việc nhóm an toàn.
Một hệ thống version control tốt giải quyết bốn vấn đề cốt lõi:
- Lịch sử (history): mọi commit là một ảnh chụp (snapshot) của dự án, kèm thông điệp giải thích. Bạn luôn truy ngược được "cái gì đã đổi và tại sao".
- Hợp tác (collaboration): nhiều người cùng sửa một dự án mà không ghi đè lên nhau, nhờ cơ chế nhánh và merge.
- Phục hồi (recovery): lỡ tay xóa file hay phá vỡ tính năng? Quay lại một commit cũ chỉ trong vài giây.
- Thử nghiệm an toàn: tạo nhánh riêng để thử ý tưởng mới mà không động đến mã đang chạy.
Trong văn hóa DevOps, version control còn là nguồn chân lý duy nhất (single source of truth). Không chỉ code mà cả cấu hình hạ tầng (Infrastructure as Code), pipeline CI/CD, và tài liệu đều được lưu trong Git. Mọi tự động hóa — build, test, deploy — đều khởi nguồn từ một thay đổi trong repository.
Git phân tán vs hệ thống tập trung
Trước Git, các hệ thống phổ biến như SVN hay CVS là tập trung (centralized): chỉ có một máy chủ duy nhất giữ toàn bộ lịch sử. Lập trình viên phải kết nối tới server để commit, xem log hay tạo nhánh. Nếu server sập, mọi người tê liệt; nếu mất server, mất luôn lịch sử.
Git là hệ thống phân tán (distributed). Khi bạn clone một repo, bạn nhận toàn bộ lịch sử về máy mình, không chỉ ảnh chụp mới nhất. Hệ quả:
- Hầu hết thao tác (commit, diff, log, tạo nhánh) chạy cục bộ, cực nhanh, không cần mạng.
- Mỗi bản clone là một bản sao lưu đầy đủ. Mất server trung tâm vẫn khôi phục được.
- Bạn có thể commit khi offline (trên máy bay, mất mạng), rồi đồng bộ sau.
Lưu ý: GitHub/GitLab không phải là Git. Git là công cụ; GitHub/GitLab/Bitbucket là dịch vụ host repo Git trên đám mây kèm các tính năng cộng tác (Pull Request, issue, CI/CD). Repo trên đó chỉ đóng vai trò remote — một điểm hẹn để các bản clone đồng bộ với nhau.
Mô hình Git: ba khu vực và một repository
Đây là khái niệm nền tảng nhất. Một thay đổi đi qua bốn nơi:
- Working directory (thư mục làm việc): các file thực tế bạn đang chỉnh sửa.
- Staging area / index (vùng dàn dựng): nơi bạn "chọn lọc" những thay đổi sẽ đưa vào commit tiếp theo. Đây là điểm khác biệt lớn của Git — bạn không phải commit tất cả, mà chỉ commit cái mình muốn.
- Local repository (.git): kho chứa toàn bộ commit đã được ghi cố định trên máy bạn.
- Remote repository: kho trên server (GitHub...) để đồng bộ với người khác.
Mỗi commit có một mã băm SHA-1 (40 ký tự, ví dụ a1b2c3d...) đại diện duy nhất cho ảnh chụp đó, và trỏ về commit cha của nó. Chuỗi các commit nối nhau tạo thành lịch sử. Một branch chỉ đơn giản là một con trỏ (pointer) di động trỏ tới một commit; HEAD là con trỏ trỏ tới nhánh (hoặc commit) bạn đang đứng. Hiểu được "branch chỉ là con trỏ" sẽ giúp bạn nắm vững mọi thao tác nhánh về sau.
Lệnh cốt lõi
Khởi tạo và sao chép
# Biến thư mục hiện tại thành một repo Git mới
git init
# Sao chép một repo từ remote về máy (kèm toàn bộ lịch sử)
git clone https://github.com/user/project.git
# Cấu hình danh tính (làm một lần cho máy)
git config --global user.name "Nguyen Van A"
git config --global user.email "[email protected]"
Ghi nhận thay đổi
# Xem trạng thái: file nào đã sửa, đã stage, chưa theo dõi
git status
# Đưa thay đổi vào staging area
git add file.py # một file cụ thể
git add . # tất cả thay đổi trong thư mục hiện tại
git add -p # chọn từng đoạn (hunk) một cách tương tác
# Ghi commit với thông điệp
git commit -m "Thêm chức năng đăng nhập"
# Gộp add + commit cho các file đã được theo dõi
git commit -am "Sửa lỗi validate email"
Xem lịch sử và khác biệt
git log # lịch sử commit đầy đủ
git log --oneline --graph # gọn, kèm sơ đồ nhánh dạng text
git log -p file.py # lịch sử kèm nội dung thay đổi của 1 file
git diff # khác biệt giữa working dir và staging
git diff --staged # khác biệt đã stage so với commit cuối
git diff main feature # so sánh hai nhánh
git show a1b2c3d # xem chi tiết một commit
Đồng bộ với remote
git remote -v # liệt kê các remote đã cấu hình
git remote add origin https://github.com/user/project.git
git push origin main # đẩy commit local lên remote
git push -u origin feature # đẩy và thiết lập tracking lần đầu
git fetch origin # tải về cập nhật từ remote, KHÔNG merge
git pull origin main # = fetch + merge (cập nhật nhánh hiện tại)
Phân biệt fetch và pull: fetch chỉ tải dữ liệu mới về kho local, để bạn xem trước rồi quyết định. pull tải về và merge ngay vào nhánh đang làm. Nếu muốn kiểm soát kỹ, hãy fetch rồi xem git log origin/main trước khi merge.
Nhánh và merge
Tạo và chuyển nhánh
git branch # liệt kê nhánh
git branch feature-login # tạo nhánh mới (chưa chuyển sang)
# git switch là lệnh hiện đại để chuyển nhánh (rõ nghĩa hơn checkout)
git switch feature-login
git switch -c feature-login # tạo và chuyển trong một lệnh
# checkout là lệnh cũ, đa năng (vừa chuyển nhánh vừa khôi phục file)
git checkout feature-login
git checkout -b feature-login
Vì branch chỉ là con trỏ, tạo nhánh trong Git gần như tức thì và rất rẻ — đó là lý do văn hóa "tạo nhánh cho mọi việc nhỏ" trở nên phổ biến.
Merge
Khi tính năng hoàn tất, bạn gộp nhánh đó về nhánh chính:
git switch main
git merge feature-login
Có hai kiểu merge:
- Fast-forward: nếu
mainkhông có commit mới nào kể từ khi tách nhánh, Git chỉ cần "tua" con trỏmaintới commit cuối của nhánh — không tạo commit merge. - Three-way merge: nếu cả hai nhánh đều có commit mới, Git tạo một merge commit mới có hai cha, hợp nhất lịch sử của cả hai.
Merge vs Rebase
rebase là một cách khác để hợp nhất thay đổi. Thay vì tạo merge commit, nó di dời các commit của nhánh bạn lên trên đỉnh nhánh đích, viết lại lịch sử thành một đường thẳng.
git switch feature
git rebase main # đặt các commit của feature lên đỉnh main
So sánh:
- Merge giữ nguyên lịch sử thật, dễ thấy nhánh tách-gộp ở đâu, nhưng đồ thị có thể rối với nhiều merge commit.
- Rebase cho lịch sử tuyến tính, sạch sẽ, dễ đọc — nhưng viết lại commit (đổi SHA), nên rất nguy hiểm nếu áp dụng trên nhánh đã được người khác dùng.
Quy tắc vàng của rebase: đừng bao giờ rebase một nhánh công khai mà người khác đã pull về. Chỉ rebase nhánh riêng của bạn.
Conflict và cách giải quyết
Conflict xảy ra khi hai nhánh sửa cùng một dòng của cùng một file theo cách khác nhau, và Git không biết chọn bên nào. Khi đó Git dừng lại và đánh dấu vào file:
<<<<<<< HEAD
giá = 100 # phiên bản ở nhánh hiện tại
=======
giá = 120 # phiên bản từ nhánh được merge vào
>>>>>>> feature-login
Cách xử lý:
# 1. Git báo conflict; xem những file đang conflict
git status
# 2. Mở từng file, xóa các dấu <<<<<<< ======= >>>>>>>,
# giữ lại nội dung đúng (có thể kết hợp cả hai)
# 3. Đánh dấu đã giải quyết
git add file_conflict.py
# 4. Hoàn tất
git commit # với merge
git rebase --continue # với rebase
# Muốn hủy giữa chừng:
git merge --abort
git rebase --abort
Lời khuyên: pull/merge thường xuyên để conflict luôn nhỏ và dễ xử lý, thay vì để nhánh sống quá lâu rồi gộp một cục lớn.
.gitignore
Không phải file nào cũng nên được theo dõi: thư mục build, dependencies, file bí mật, log... .gitignore liệt kê các mẫu (pattern) file mà Git sẽ bỏ qua.
# .gitignore ví dụ cho dự án Python/Node
__pycache__/
*.pyc
node_modules/
.env # KHÔNG BAO GIỜ commit secret
dist/
*.log
.DS_Store
Quy tắc quan trọng: .gitignore chỉ tác động lên file chưa được theo dõi. Nếu một file đã trót được commit, thêm vào .gitignore không xóa nó khỏi tracking — bạn phải git rm --cached file rồi commit lại. Tuyệt đối tránh commit khóa API, mật khẩu, file .env — một khi đã vào lịch sử, chúng tồn tại mãi và phải xoay (rotate) lại secret.
Workflow nhóm
Cùng một tập lệnh Git, nhưng cách tổ chức nhánh sẽ định hình quy trình của cả đội.
Feature branch (nền tảng chung)
Ý tưởng cốt lõi: không bao giờ commit trực tiếp lên nhánh chính. Mỗi tính năng/sửa lỗi nằm trên một nhánh riêng, hoàn tất rồi mới gộp về main qua review.
git switch -c feature/gio-hang
# ... code, commit nhiều lần ...
git push -u origin feature/gio-hang
# Mở Pull Request để review, rồi merge
Git Flow
Mô hình quy củ với nhiều loại nhánh, phù hợp cho sản phẩm phát hành theo phiên bản (versioned release):
main— mã production, mỗi commit là một bản phát hành.develop— nhánh tích hợp các tính năng đang phát triển.feature/*— tách từdevelop, gộp lại vềdevelop.release/*— chuẩn bị phát hành, sửa lỗi nhỏ cuối.hotfix/*— sửa khẩn cấp tách thẳng từmain.
Ưu điểm: cấu trúc rõ ràng, kiểm soát chặt. Nhược điểm: nặng nề, nhiều nhánh sống lâu, chậm — ngày càng ít phù hợp với tinh thần giao hàng liên tục của DevOps.
Trunk-based development
Triết lý đối lập, được ưa chuộng trong các đội CI/CD trưởng thành: mọi người làm việc trên một nhánh chính (trunk) duy nhất, các nhánh tính năng rất ngắn (sống vài giờ tới một, hai ngày) rồi gộp về ngay. Tính năng chưa xong được giấu sau feature flag thay vì sống trên nhánh dài.
Ưu điểm: tích hợp liên tục thật sự, ít conflict lớn, deploy nhanh và thường xuyên. Đây là nền móng cho CI/CD và là lựa chọn của nhiều đội hiệu năng cao. Đánh đổi: đòi hỏi kỷ luật cao, test tự động mạnh và feature flag.
Pull / Merge Request và code review
Pull Request (PR) trên GitHub — hay Merge Request (MR) trên GitLab — là cơ chế đề xuất gộp một nhánh vào nhánh khác, kèm thảo luận và kiểm duyệt. Đây là điểm hội tụ của con người và tự động hóa trong DevOps.
Một PR điển hình kích hoạt:
- Review của đồng đội: xem diff, để lại bình luận theo từng dòng, yêu cầu chỉnh sửa, rồi approve.
- Kiểm tra tự động (CI): chạy test, lint, quét bảo mật, kiểm tra độ phủ. Nhiều repo bắt buộc các check này phải xanh mới cho merge (branch protection).
- Thảo luận và lưu vết quyết định: vì sao chọn cách này, đánh đổi gì — lưu lại làm tài liệu sống.
Code review tốt giúp lan tỏa kiến thức, bắt lỗi sớm, giữ chất lượng đồng đều. Nguyên tắc thực dụng: PR nhỏ (dễ review hơn nhiều so với PR khổng lồ), mô tả rõ ràng, và phản hồi tập trung vào code chứ không vào người.
Tag và release
Trong khi branch là con trỏ di động, tag là một nhãn cố định gắn vào một commit cụ thể — thường dùng để đánh dấu phiên bản phát hành theo Semantic Versioning (vMAJOR.MINOR.PATCH).
# Tag có chú thích (annotated) — khuyến nghị cho release
git tag -a v1.2.0 -m "Phát hành 1.2.0"
git tag # liệt kê các tag
git push origin v1.2.0 # đẩy tag lên remote
git push origin --tags # đẩy tất cả tag
git checkout v1.2.0 # xem mã tại thời điểm phát hành đó
Trên GitHub/GitLab, một tag có thể được nâng cấp thành Release — gói lại kèm ghi chú phát hành (changelog) và file đính kèm (binary, installer). Trong pipeline DevOps, việc đẩy một tag v* thường là sự kiện kích hoạt build và deploy bản chính thức lên production.
Tóm tắt
- Git là hệ thống version control phân tán: mỗi clone chứa toàn bộ lịch sử, hầu hết thao tác chạy cục bộ và nhanh.
- Thay đổi đi qua bốn nơi: working directory → staging area (
git add) → local repo (git commit) → remote (git push). - Branch chỉ là con trỏ tới một commit; tạo nhánh rất rẻ. Gộp bằng
merge(giữ lịch sử thật) hoặcrebase(lịch sử tuyến tính, đừng rebase nhánh công khai). - Conflict xảy ra khi hai bên sửa cùng một dòng; giải quyết thủ công rồi
git addvà hoàn tất. - Workflow nhóm: feature branch là nền tảng; Git Flow quy củ cho release theo phiên bản; trunk-based hợp với CI/CD nhanh.
- Pull Request là điểm hội tụ của review thủ công và kiểm tra tự động; tag đánh dấu các bản phát hành cố định.
Tự kiểm tra
- Khác biệt cốt lõi giữa hệ thống version control phân tán (Git) và tập trung (SVN) là gì?
- Giải thích vai trò của staging area. Vì sao Git tách bước
addra khỏicommit? - Khi nào nên dùng
git mergevà khi nào nên dùnggit rebase? "Quy tắc vàng của rebase" nói gì? - Một merge conflict xảy ra trong tình huống nào, và các bước để giải quyết nó là gì?
- So sánh Git Flow và trunk-based development về độ phù hợp với CI/CD.
- Phân biệt branch và tag; tag thường được dùng cho mục đích gì trong release?
Đọc tiếp
Bài viết liên quan
Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.
Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.
Vì sao cần orchestration; Pod, Deployment, Service, Ingress; scaling, self-healing và cấu hình.
Bản chất DevOps: phá bỏ rào cản Dev–Ops, vòng lặp vô tận, CALMS, và vì sao tự động hoá.