Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn vừa gia nhập một team backend ở một startup fintech tại Sài Gòn. Ngày đầu tiên, bạn được giao một link Confluence dài 40 trang mô tả cách deploy hệ thống. Bạn hào hứng làm theo, gõ đúng từng lệnh — và nó không chạy. Hóa ra tài liệu được viết cách đây 8 tháng, từ hồi hệ thống còn dùng một biến môi trường tên khác, một cổng khác, một script khác. Người viết đã nghỉ việc. Không ai dám sửa vì "sợ sai". Đây là câu chuyện lặp đi lặp lại ở gần như mọi công ty.
Vấn đề cốt lõi không phải là "team lười viết tài liệu". Vấn đề là tài liệu bị tách rời khỏi code. Code thì thay đổi mỗi ngày qua Pull Request, được review, được version, được test. Còn tài liệu thì nằm ở một nơi khác — Confluence, Google Docs, Notion, một file Word trên Drive — không ai review, không ai biết nó có còn đúng hay không.
Documentation as Code (viết tắt là Docs as Code, hay DaC) là triết lý giải quyết chính xác vấn đề này: coi tài liệu như code. Tài liệu sống trong Git repo, viết bằng plain text (Markdown, AsciiDoc, reStructuredText), đi qua đúng quy trình Pull Request, được review, được kiểm tra tự động, và được publish tự động thông qua pipeline CI/CD.
Trong bối cảnh khóa học CI/CD này, đây là bài học đặc biệt quan trọng vì nó khép kín một vòng tròn: pipeline của bạn không chỉ build, test, deploy code — nó còn có thể build, test, deploy tài liệu. Và khi tài liệu chạy qua cùng một pipeline, nó không bao giờ bị "lỗi thời" một cách âm thầm nữa.
Khái niệm cốt lõi
Docs as Code là gì?
Về bản chất, Docs as Code là việc áp dụng toàn bộ công cụ và quy trình của kỹ sư phần mềm vào việc viết tài liệu. Cụ thể gồm bốn trụ cột:
- Plain text & version control: Tài liệu viết bằng định dạng văn bản thuần (thường là Markdown), lưu trong Git. Nhờ đó bạn có thể
git diffđể xem ai đổi gì,git blameđể biết dòng này do ai viết, vàgit revertkhi cần quay lại. - Review qua Pull Request: Mọi thay đổi tài liệu đều đi qua PR, có người review giống như review code. Không còn cảnh "một người tự sửa Confluence, không ai biết".
- Automated checks: Pipeline tự động kiểm tra tài liệu — link chết, lỗi chính tả, sai định dạng, thuật ngữ không nhất quán.
- Automated publishing: Khi PR được merge, pipeline tự build tài liệu thành một website tĩnh (static site) và deploy lên hosting.
Vì sao Confluence/Wiki lại thất bại?
Đây là câu hỏi mà mentor nào cũng phải trả lời cho học viên. Hãy so sánh thẳng:
Khi tài liệu nằm trong Confluence, nó không có review process bắt buộc. Ai cũng có thể sửa bất cứ lúc nào, không có bước phê duyệt. Kết quả là tài liệu "trôi" khỏi thực tế: code đổi mà không ai cập nhật docs, vì việc cập nhật docs nằm ở một hệ thống khác, một thói quen khác, một tab trình duyệt khác. Việc tìm kiếm cũng khó — bạn không thể grep toàn bộ Confluence từ terminal, không thể tìm tài liệu ngay trong IDE khi đang code.
Khi tài liệu nằm trong Git repo cạnh code, mọi thứ đảo ngược: bạn sửa code trong file payment.go, thì trong cùng một PR bạn sửa luôn docs/payment.md. Reviewer nhìn thấy cả hai. Nếu bạn quên cập nhật docs, reviewer sẽ nhắc. Bạn có thể grep -r "webhook" docs/ để tìm mọi chỗ nhắc đến webhook. Đây là điểm mấu chốt: giảm khoảng cách giữa nơi viết code và nơi viết docs xuống gần bằng không.
Các định dạng và công cụ phổ biến
Về format, ba lựa chọn chính:
- Markdown: Phổ biến nhất, dễ học, đủ dùng cho 90% trường hợp. GitHub/GitLab render trực tiếp.
- AsciiDoc: Mạnh hơn Markdown về tính năng (bảng phức tạp, include file, cross-reference), phù hợp tài liệu kỹ thuật dài.
- reStructuredText (reST): Chuẩn của hệ sinh thái Python, đi cùng công cụ Sphinx.
- MkDocs (đặc biệt theme Material for MkDocs): Nhẹ, đẹp, cực dễ setup — lựa chọn số một cho team vừa và nhỏ.
- Docusaurus (của Meta): Mạnh cho tài liệu sản phẩm lớn, hỗ trợ versioning, i18n.
- Hugo, Sphinx, Antora: Cho các nhu cầu chuyên biệt.
Nguyên tắc "docs sống cạnh code"
Có hai mô hình đặt tài liệu:
- Docs cùng repo với code (co-located): Thư mục
docs/nằm ngay trong repo dịch vụ. Ưu điểm: cập nhật cùng PR, không bao giờ lệch phiên bản. Phù hợp tài liệu kỹ thuật của một service. - Docs ở repo riêng (centralized): Một repo
docs/tập trung, tổng hợp tài liệu toàn công ty. Phù hợp tài liệu tổng quan, onboarding, kiến trúc liên-service.
Tình huống thực tế
Ví dụ 1 — Sàn TMĐT "ShopViet" và cơn ác mộng onboarding
ShopViet (tên giả định, mô hình phổ biến ở các sàn TMĐT Việt Nam) có 6 team, khoảng 45 kỹ sư. Tài liệu API nội bộ nằm rải rác trên Confluence. Đo lường thực tế của họ: một kỹ sư mới mất trung bình 9 ngày để deploy được thay đổi đầu tiên lên production, phần lớn thời gian là "hỏi người cũ" vì tài liệu sai.
Team platform quyết định chuyển sang Docs as Code. Họ tạo thư mục docs/ trong mỗi repo service, dùng Material for MkDocs, và thêm một job vào pipeline GitLab CI: mỗi khi merge vào main, pipeline build docs và deploy lên GitLab Pages nội bộ. Quan trọng hơn, họ thêm một quy tắc review: PR nào đổi API mà không đổi file docs/api.md tương ứng sẽ bị chặn bởi một check tự động.
Sau 3 tháng, thời gian onboarding trung bình giảm từ 9 ngày xuống còn 4 ngày. Số câu hỏi lặp lại trên kênh Slack #help-backend giảm khoảng 40%.
Bài học rút ra: Giá trị lớn nhất không đến từ công cụ đẹp, mà đến từ việc buộc docs đi cùng PR. Khi cập nhật tài liệu trở thành một phần bắt buộc của quy trình merge code, tài liệu tự động cập nhật theo.
Ví dụ 2 — Startup fintech "PayNhanh" và tài liệu vận hành runbook
PayNhanh (giả định) có một hệ thống xử lý thanh toán chạy 24/7. Đội on-call thường xuyên gặp sự cố lúc 2 giờ sáng, và runbook xử lý sự cố nằm trong Google Docs. Vấn đề: người trực đêm mở Google Docs trên điện thoại, làm theo — nhưng runbook đã cũ, lệnh restart service đã đổi tên từ đợt migration sang Kubernetes.
Họ chuyển toàn bộ runbook vào repo infra-runbooks, viết bằng Markdown. Điểm hay: họ thêm một job CI dùng công cụ kiểm tra link chết (lychee) và một job chạy thử các đoạn lệnh trong môi trường staging để xác nhận chúng còn chạy được. Mỗi lần hạ tầng đổi, người sửa hạ tầng phải sửa runbook trong cùng PR, nếu không job kiểm tra sẽ đỏ.
Kết quả sau một quý: thời gian trung bình khôi phục sự cố (MTTR) giảm từ 47 phút xuống 28 phút. Đội on-call tin tưởng runbook hơn vì họ biết nó "được test".
Bài học rút ra: Docs as Code không chỉ giúp tài liệu dễ đọc — nó cho phép bạn kiểm chứng tài liệu tự động. Một runbook được pipeline xác nhận đáng tin gấp nhiều lần một Google Docs không ai đảm bảo.
Ví dụ 3 — Team open-source và tài liệu đa phiên bản
Một dự án thư viện mã nguồn mở của cộng đồng dev Đông Nam Á dùng Docusaurus để làm tài liệu. Họ có nhiều phiên bản (v1, v2, v3) chạy song song. Mỗi khi release, pipeline tự động "đóng băng" bản docs của phiên bản đó và publish phiên bản mới.
Đáng chú ý là quy trình PR: người ngoài đóng góp tài liệu qua Pull Request trên GitHub, một GitHub Actions workflow tự build preview và comment link preview vào PR. Reviewer chỉ cần click vào link để xem tài liệu render ra sao trước khi merge.
Bài học rút ra: Với Docs as Code, cộng tác viên bên ngoài cũng đóng góp được tài liệu đúng cách. Preview tự động trong PR biến việc review docs thành trải nghiệm mượt mà y như review một tính năng.
Hướng dẫn từng bước
Ta sẽ dựng một pipeline Docs as Code tối thiểu nhưng hoàn chỉnh, dùng MkDocs và GitHub Actions.
Bước 1 — Cấu trúc thư mục
Trong repo, tạo cấu trúc:
repo/
├── docs/
│ ├── index.md
│ ├── getting-started.md
│ └── api.md
├── mkdocs.yml
└── .github/workflows/docs.yml
Bước 2 — Cấu hình MkDocs
File mkdocs.yml:
site_name: ShopViet Internal Docs
theme:
name: material
language: vi
nav:
- Trang chủ: index.md
- Bắt đầu: getting-started.md
- API: api.md
markdown_extensions:
- admonition
- toc:
permalink: true
Chạy thử local để xem ngay trên máy:
pip install mkdocs-material
mkdocs serve # mở http://127.0.0.1:8000
mkdocs serve có live-reload: bạn sửa file Markdown, trình duyệt tự cập nhật. Đây là "local dev loop" cho tài liệu.
Bước 3 — Thêm kiểm tra tự động (quality gate cho docs)
Trước khi publish, ta chạy hai check: build phải thành công (không có lỗi cú pháp, không link nội bộ chết) và kiểm tra link ngoài.
MkDocs hỗ trợ chế độ nghiêm ngặt: mkdocs build --strict sẽ fail nếu có bất kỳ cảnh báo nào (ví dụ link tới file không tồn tại). Đây chính là "docs test" của bạn.
Bước 4 — Viết GitHub Actions workflow
File .github/workflows/docs.yml:
name: Docs
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install mkdocs-material
# Quality gate: build ở chế độ strict, link nội bộ chết -> fail
- run: mkdocs build --strict
# Kiểm tra link ngoài bằng lychee
- uses: lycheeverse/lychee-action@v2
with:
args: --no-progress "docs/**/*.md"
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install mkdocs-material
# Chỉ deploy khi đã merge vào main
- run: mkdocs gh-deploy --force
Điểm cần hiểu ở đây:
- Job
buildchạy cho cả PR lẫn push — nghĩa là mọi PR động vào docs đều được kiểm tra trước khi merge. Đây là quality gate. - Job
deploychỉ chạy khi đã ởmain(if:guard) và phụ thuộcneeds: build— docs hỏng thì không bao giờ được publish.
Bước 5 — Buộc docs đi cùng code
Đây là bước "văn hóa" nhưng quan trọng nhất. Thêm một check nhắc nhở: nếu PR sửa file trong src/api/ mà không đụng gì tới docs/api.md, hãy để một job cảnh báo (hoặc chặn tùy mức độ nghiêm khắc). Cách đơn giản là dùng một script so sánh danh sách file thay đổi (git diff --name-only origin/main...HEAD), hoặc dùng label/CODEOWNERS để buộc người viết docs review.
Bước 6 — Publish và xác nhận
Sau khi merge, mở URL GitHub Pages (hoặc GitLab Pages nội bộ) để xác nhận tài liệu đã lên. Từ đây trở đi, mỗi lần merge vào main, tài liệu tự cập nhật — không ai phải "nhớ" đi publish thủ công nữa.
Lỗi thường gặp & mẹo
Lỗi 1 — Coi Docs as Code là "chỉ đổi công cụ". Nhiều team migrate từ Confluence sang MkDocs rồi... vẫn để docs lỗi thời như cũ. Vì họ không thay đổi quy trình. Công cụ chỉ là 20%; 80% giá trị nằm ở việc buộc docs đi qua PR và review cùng code. Nếu không có kỷ luật này, bạn chỉ đổi chỗ để tài liệu bị bỏ quên.
Lỗi 2 — Không bật chế độ strict. Nếu mkdocs build không dùng --strict, link nội bộ chết vẫn build thành công và bạn publish một trang có link 404. Luôn bật strict để biến docs thành thứ có thể "fail".
Lỗi 3 — Deploy chạy cả trên PR. Nếu bạn quên if: github.ref == 'refs/heads/main', mỗi PR có thể ghi đè trang docs production. Luôn tách rõ: PR chỉ build + kiểm tra, chỉ main mới deploy.
Lỗi 4 — Nhồi ảnh và file nặng vào repo docs. Nhiều screenshot dung lượng lớn commit thẳng làm repo phình to. Mẹo: nén ảnh, hoặc lưu ở object storage nếu cần; giữ repo docs gọn nhẹ.
Mẹo 1 — Preview cho reviewer. Cấu hình pipeline tự deploy một bản preview cho mỗi PR (Netlify, Cloudflare Pages, hoặc GitHub Actions comment link). Reviewer thấy tài liệu render thật, review nhanh hơn nhiều so với đọc Markdown thô.
Mẹo 2 — Thêm linter cho văn phong. Công cụ như vale giúp bạn định nghĩa quy tắc phong cách (ví dụ: thuật ngữ phải nhất quán, không viết tắt tùy tiện) và kiểm tra tự động trong CI. Rất hữu ích khi nhiều người cùng viết.
Mẹo 3 — Tận dụng git blame và diff. Khi ai đó hỏi "sao đoạn này viết vậy", git blame docs/api.md cho bạn biết ngay ai viết, khi nào, trong PR nào. Đây là siêu năng lực mà Confluence không bao giờ có.
Mẹo 4 — Đặt docs cạnh code cho tài liệu kỹ thuật. Với runbook, ADR (kiến trúc), tài liệu API của một service — hãy để chúng trong chính repo service đó. Khoảng cách càng gần, khả năng cập nhật càng cao.
Bài tập thực hành
Bài tập 1 — Dựng pipeline docs cơ bản (bắt buộc).
Tạo một repo mới trên GitHub, thêm thư mục docs/ với ít nhất 3 trang Markdown (index.md, getting-started.md, api.md), cấu hình mkdocs.yml với theme Material tiếng Việt. Chạy mkdocs serve để xem local. Sau đó viết workflow GitHub Actions build ở chế độ --strict và deploy lên GitHub Pages khi merge main. Xác nhận URL Pages hiển thị đúng.
Bài tập 2 — Thêm quality gate (nâng cao).
Bổ sung job kiểm tra link chết bằng lychee-action. Cố tình thêm một link [test](docs/khong-ton-tai.md) để xác nhận job build --strict fail đúng như mong đợi, rồi sửa lại cho pass. Ghi lại quan sát: pipeline đã "bảo vệ" tài liệu bạn ra sao.
Bài tập 3 — Preview cho PR (thử thách).
Cấu hình để mỗi Pull Request tạo ra một bản preview (dùng Cloudflare Pages/Netlify hoặc đơn giản là upload artifact và comment link). Tạo một PR sửa nội dung api.md, mở PR và kiểm tra xem reviewer có thể xem preview không.
Bài tập tư duy 4. Ở công ty (hoặc dự án cá nhân) của bạn, tài liệu đang nằm ở đâu? Hãy chỉ ra một trang tài liệu bạn biết chắc đang lỗi thời, và viết ra 3 bước cụ thể để chuyển nó sang mô hình Docs as Code. Ai sẽ review? Check nào cần thêm?
Tóm tắt
Documentation as Code là việc đối xử với tài liệu y như code: viết bằng plain text (thường là Markdown), lưu trong Git, review qua Pull Request, kiểm tra tự động và publish tự động qua pipeline CI/CD. Nó giải quyết căn bệnh kinh niên của Confluence và wiki truyền thống — tài liệu lỗi thời, không có review process, khó tìm kiếm — bằng cách kéo tài liệu về sống cạnh code và bắt nó đi qua đúng quy trình kỹ thuật.
Ba điểm cần nhớ nhất: (1) Giá trị lớn nhất đến từ quy trình — buộc docs đi cùng PR — chứ không phải công cụ đẹp. (2) Bật chế độ strict và thêm kiểm tra link để tài liệu có thể "fail" như một bài test, biến nó thành thứ đáng tin cậy. (3) Tách rõ build và deploy: mọi PR đều được build/kiểm tra, nhưng chỉ main mới được publish.
Khi bạn làm đúng, tài liệu không còn là gánh nặng "viết cho có" nữa, mà trở thành một sản phẩm sống, luôn đồng bộ với hệ thống thật — và pipeline CI/CD của bạn chính là thứ giữ cho nó luôn đúng.