Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn vừa dùng Terraform để tạo ra 20 con máy chủ ảo (VM) trên cloud. Terraform làm rất tốt việc đó: nó dựng lên 20 cái VM trống trơn, gắn mạng, gắn ổ đĩa, mở port. Nhưng rồi bạn nhận ra một vấn đề rất trần trụi: 20 con máy này giống như 20 căn nhà mới xây xong phần thô — có tường, có mái, nhưng bên trong chưa có điện nước, chưa có đồ đạc, chưa cài phần mềm. Ai sẽ vào từng con máy để cài Nginx, cấu hình PHP, tạo user, mở firewall theo đúng chính sách công ty, và đảm bảo cả 20 con máy đều giống hệt nhau?
Nếu bạn SSH vào từng máy gõ lệnh bằng tay, bạn sẽ gặp thảm họa kinh điển của ngành: configuration drift — mỗi máy một kiểu, không máy nào giống máy nào, và ba tháng sau không ai biết máy số 7 có gì khác máy số 12. Đây chính là lãnh địa của Configuration Management (quản lý cấu hình), và những công cụ như Ansible, Chef, Puppet ra đời để giải quyết đúng bài toán này.
Bài học này quan trọng vì nó lấp đầy khoảng trống mà IaC (Infrastructure as Code) để lại. Một kỹ sư DevOps giỏi phải hiểu rõ ranh giới: chỗ nào dùng Terraform, chỗ nào dùng Ansible, và làm sao hai công cụ này phối hợp nhịp nhàng. Hiểu sai ranh giới này là nguyên nhân số một khiến hệ thống trở nên rối rắm, khó bảo trì.
Khái niệm cốt lõi
IaC vs Configuration Management — ranh giới quan trọng nhất
Đây là điểm mà rất nhiều bạn mới học bị nhầm lẫn, nên tôi muốn làm rõ ngay từ đầu.
Infrastructure as Code (IaC) — đại diện là Terraform, CloudFormation, Pulumi — chịu trách nhiệm tạo ra hạ tầng: VM, network (VPC), subnet, load balancer, database managed, security group. IaC trả lời câu hỏi: "Tôi cần bao nhiêu máy, mạng thế nào, ổ đĩa bao nhiêu?" Nó làm việc ở tầng provisioning — dựng phần cứng ảo lên.
Configuration Management (Config Mgmt) — đại diện là Ansible, Chef, Puppet — chịu trách nhiệm cấu hình bên trong hệ điều hành (OS-level) sau khi máy đã tồn tại: cài package (Nginx, Docker, PHP), sửa file cấu hình, tạo user, quản lý service (start/stop/enable), copy code, mở firewall. Config Mgmt trả lời câu hỏi: "Bên trong mỗi máy cần cài gì, cấu hình ra sao?"
Một câu ví von tôi hay dùng với học viên: Terraform xây căn nhà và móng, Ansible sơn tường, lắp điện, kê đồ đạc bên trong. Bạn không dùng Terraform để cài Nginx, và bạn cũng không dùng Ansible để tạo một VPC mới (dù về lý thuyết Ansible có module làm được, nhưng đó không phải thế mạnh của nó).
Tư duy khai báo (declarative) và idempotency
Đặc trưng cốt lõi của mọi công cụ Config Mgmt hiện đại là tính khai báo và tính idempotent.
Khai báo (declarative) nghĩa là bạn mô tả trạng thái mong muốn chứ không viết từng bước ra lệnh. Thay vì viết "chạy apt install nginx", bạn khai báo "gói nginx phải ở trạng thái present (đã cài)". Công cụ tự kiểm tra: nếu chưa cài thì cài, nếu cài rồi thì bỏ qua.
Idempotency là hệ quả tuyệt vời của tư duy trên: bạn chạy playbook một lần hay một trăm lần, kết quả cuối cùng vẫn giống nhau, không gây tác dụng phụ. Đây là điểm khác biệt sống còn so với script bash — chạy script bash hai lần có thể tạo ra hai user trùng, hai dòng cấu hình lặp, hoặc lỗi "file đã tồn tại". Với Ansible, chạy bao nhiêu lần cũng an toàn.
Push vs Pull — hai triết lý kiến trúc
Ba công cụ chia thành hai trường phái kiến trúc:
- Push model (Ansible): không cần cài agent trên máy đích. Máy điều khiển (control node) đẩy cấu hình qua SSH tới các máy đích. Ưu điểm: đơn giản, không cần hạ tầng phức tạp, agentless. Đây là lý do Ansible thắng thế mạnh mẽ ở thị trường Việt Nam và Đông Nam Á — đội nhỏ, dễ bắt đầu.
- Pull model (Puppet, Chef): mỗi máy đích cài một agent, agent định kỳ (ví dụ mỗi 30 phút) tự "kéo" cấu hình mới nhất từ máy chủ trung tâm (Puppet Master / Chef Server) và áp dụng. Ưu điểm: quản lý hàng nghìn, chục nghìn máy rất mượt, tự động sửa drift liên tục. Nhược điểm: phải dựng và duy trì server trung tâm, phức tạp hơn.
So sánh nhanh ba công cụ
- Ansible (Red Hat/IBM): agentless, viết bằng YAML (playbook), dễ học nhất, cộng đồng lớn nhất hiện nay. Phù hợp đội vừa và nhỏ, dự án cần bắt đầu nhanh.
- Puppet: dùng ngôn ngữ khai báo riêng (Puppet DSL), pull-based, rất mạnh cho quy mô cực lớn và môi trường doanh nghiệp lâu năm.
- Chef: dùng Ruby DSL (recipe/cookbook), linh hoạt như code thật, phù hợp đội có nền lập trình mạnh nhưng đường học dốc hơn.
Tình huống thực tế
Tình huống 1 — Sàn thương mại điện tử tại TP.HCM và cơn ác mộng "máy số 7 khác lạ"
Một công ty e-commerce quy mô vừa ở TP.HCM (giả định tên ChợViệt) chạy 12 web server EC2 phía sau một load balancer trên AWS. Ban đầu, đội vận hành 3 người SSH vào từng máy cài đặt bằng tay. Sau 8 tháng, họ gặp sự cố kỳ lạ: cứ mỗi 12 request lại có 1 request trả về lỗi 500. Điều tra ròng rã 2 ngày mới phát hiện máy số 7 đang chạy PHP 8.1 trong khi 11 máy còn lại chạy PHP 8.2, do một lần "vá gấp" ai đó nâng cấp mà quên ghi lại.
Diễn giải: Đây là configuration drift kinh điển. Load balancer chia đều lưu lượng, nên đúng 1/12 request rơi vào máy lệch cấu hình. Không có nguồn sự thật chung (single source of truth) về cấu hình, không ai biết máy nào đang chạy gì.
Giải pháp: họ viết một Ansible playbook mô tả chính xác trạng thái chuẩn của web server (PHP 8.2, danh sách extension cụ thể, cấu hình Nginx, biến môi trường), rồi chạy ansible-playbook site.yml lên cả 12 máy. Sau 4 phút, cả 12 máy đồng bộ tuyệt đối. Từ đó, mỗi lần thay đổi cấu hình, họ sửa playbook và chạy lại — không ai SSH vào máy gõ lệnh bằng tay nữa.
Bài học rút ra: Playbook chính là tài liệu sống. Khi cấu hình được viết thành code, bạn có single source of truth, có lịch sử thay đổi qua Git, và drift bị triệt tiêu vì mọi máy đều được kéo về đúng một trạng thái khai báo.
Tình huống 2 — Fintech Đông Nam Á và quy trình "golden image + Ansible"
Một công ty fintech hoạt động ở Việt Nam và Indonesia (giả định tên PayNimbus) cần bung 40 server xử lý giao dịch mỗi khi có đợt khuyến mãi lớn, và thu hồi khi hết cao điểm. Yêu cầu: mỗi server bung ra phải sẵn sàng nhận traffic trong vòng dưới 90 giây để auto-scaling kịp phản ứng.
Ban đầu họ dùng Terraform tạo VM rồi chạy Ansible cài đặt từ đầu cho mỗi máy mới. Nhưng cài Docker, kéo image, cấu hình từ máy trống mất tận 6 phút — quá chậm cho auto-scaling.
Diễn giải: Họ tách quy trình làm hai giai đoạn. Giai đoạn "build image": dùng Packer kết hợp Ansible để "nướng" (bake) sẵn một golden image (AMI) đã cài đủ Docker, agent monitoring, cấu hình bảo mật cơ bản. Giai đoạn "runtime": Terraform bung VM từ golden image này, và Ansible chỉ còn phải làm phần cấu hình động nhỏ (inject secret, số version app hiện tại) trong khoảng 20 giây.
Kết quả: thời gian sẵn sàng của một server mới giảm từ 6 phút xuống còn khoảng 70 giây, đáp ứng đúng yêu cầu SLA của auto-scaling.
Bài học rút ra: Config Mgmt không chỉ chạy lúc runtime. Trong môi trường quy mô lớn và cần tốc độ, bạn nên đẩy phần lớn cấu hình vào giai đoạn build image (immutable infrastructure), chỉ để lại phần cấu hình động nhỏ cho runtime. Đây là mô hình được nhiều công ty cloud-native ưa chuộng.
Tình huống 3 — Ngân hàng và bài toán tuân thủ với Puppet
Một ngân hàng có hạ tầng lai (hybrid) với hơn 3.000 server (cả trên cloud lẫn data center riêng), chịu sự thanh tra tuân thủ nghiêm ngặt. Yêu cầu của bộ phận bảo mật: mọi server phải luôn tuân thủ chuẩn cấu hình (ví dụ SSH phải tắt đăng nhập root, phải có audit log, phải vá đúng version). Nếu ai đó lỡ tay đổi cấu hình, hệ thống phải tự động sửa lại trong vòng 30 phút.
Diễn giải: Với quy mô hơn 3.000 máy và yêu cầu tự động chống-drift liên tục, mô hình push của Ansible không lý tưởng bằng mô hình pull của Puppet. Họ triển khai Puppet: mỗi máy cài agent, cứ 30 phút agent tự kéo cấu hình chuẩn từ Puppet Master và áp lại. Nếu ai đó bật lại SSH root login lúc 2h chiều, thì chậm nhất 2h30 agent sẽ tự tắt nó đi và ghi log lại. Đội bảo mật có dashboard theo dõi máy nào đang lệch chuẩn.
Bài học rút ra: Chọn công cụ theo bối cảnh, không theo trào lưu. Ở quy mô nhỏ và vừa, Ansible thắng nhờ đơn giản. Nhưng ở quy mô rất lớn với yêu cầu tuân thủ liên tục và tự-chữa-lành (self-healing), mô hình pull-based agent như Puppet/Chef lại phù hợp hơn nhờ khả năng enforce cấu hình định kỳ tự động.
Hướng dẫn từng bước
Tôi sẽ minh họa bằng Ansible — công cụ bạn nên học đầu tiên. Mục tiêu: cấu hình một web server chạy Nginx.
Bước 1 — Chuẩn bị inventory (danh sách máy đích). Tạo file inventory.ini liệt kê các máy cần quản lý, nhóm chúng theo vai trò:
[webservers]
web1 ansible_host=10.0.1.11
web2 ansible_host=10.0.1.12
[webservers:vars]
ansible_user=ubuntu
Bước 2 — Kiểm tra kết nối. Chạy lệnh ping module để chắc chắn control node SSH được tới các máy:
ansible webservers -i inventory.ini -m ping
Nếu trả về pong, bạn đã sẵn sàng.
Bước 3 — Viết playbook mô tả trạng thái mong muốn. Tạo file site.yml:
- name: Cau hinh web server
hosts: webservers
become: true # chay voi quyen sudo
tasks:
- name: Cai dat Nginx
apt:
name: nginx
state: present
update_cache: true
- name: Dam bao Nginx luon chay va tu khoi dong
service:
name: nginx
state: started
enabled: true
- name: Copy file cau hinh trang chu
copy:
src: files/index.html
dest: /var/www/html/index.html
notify: Reload nginx
handlers:
- name: Reload nginx
service:
name: nginx
state: reloaded
Chú ý ba điểm: state: present là tư duy khai báo; handlers chỉ chạy khi có thay đổi thực sự (nhờ notify); toàn bộ playbook là idempotent.
Bước 4 — Chạy thử ở chế độ dry-run. Trước khi áp thật, dùng --check để xem Ansible dự định thay đổi gì mà không thực sự làm:
ansible-playbook site.yml -i inventory.ini --check
Bước 5 — Áp dụng thật. Bỏ --check:
ansible-playbook site.yml -i inventory.ini
Lần đầu bạn sẽ thấy các dòng changed. Chạy lần hai, bạn sẽ thấy toàn ok — bằng chứng của idempotency.
Bước 6 — Tổ chức thành Roles khi dự án lớn. Khi playbook phình to, hãy tách thành roles (mỗi role là một khối cấu hình tái sử dụng, ví dụ role nginx, role docker, role monitoring). Cấu trúc chuẩn giúp code sạch, dễ chia sẻ và tái dùng qua Ansible Galaxy.
Bước 7 — Quản lý bí mật với Ansible Vault. Đừng bao giờ để mật khẩu, API key trần trong playbook. Mã hóa chúng bằng ansible-vault encrypt secrets.yml và commit an toàn lên Git.
Lỗi thường gặp & mẹo
Lỗi 1 — Dùng module shell/command cho mọi thứ. Người mới hay viết shell: apt install nginx -y thay vì dùng module apt. Cách này phá vỡ idempotency (chạy lần hai vẫn "changed"), mất khả năng dry-run, và Ansible không kiểm soát được trạng thái. Mẹo: luôn ưu tiên module chuyên dụng (apt, service, copy, template, user...); chỉ dùng shell/command khi thực sự không có module nào thay thế, và khi đó thêm changed_when/creates để bảo toàn idempotency.
Lỗi 2 — Lẫn lộn vai trò Terraform và Ansible. Cố dùng Ansible để tạo VPC, hoặc dùng Terraform provisioner để cài phần mềm. Cả hai đều là anti-pattern. Mẹo: Terraform lo hạ tầng, Ansible lo OS-level. Ranh giới rõ ràng giúp mỗi công cụ làm đúng thế mạnh và dễ debug hơn.
Lỗi 3 — Không kiểm soát version của công cụ và collection. Playbook chạy tốt trên máy bạn nhưng lỗi trên CI vì khác version Ansible. Mẹo: pin version trong requirements.yml, dùng môi trường ảo (venv) hoặc container để đảm bảo nhất quán.
Lỗi 4 — Bỏ qua --check và --diff. Áp thẳng lên production rồi mới tá hỏa. Mẹo: luôn chạy --check --diff trên môi trường staging trước, dùng --limit để áp thử trên một máy trước khi bung toàn bộ.
Lỗi 5 — Không idempotent do dùng lineinfile cẩu thả. Sửa file cấu hình bằng cách append dòng mà không kiểm tra, dẫn đến file bị lặp dòng sau nhiều lần chạy. Mẹo: ưu tiên module template để quản lý cả file thay vì vá từng dòng; nó sạch và luôn idempotent.
Mẹo tổng quát: Bắt đầu nhỏ với Ansible, commit playbook vào Git như code thật, review qua Pull Request, và tích hợp vào CI/CD. Config Mgmt chỉ phát huy sức mạnh khi nó là một phần của quy trình tự động, không phải công cụ chạy tay thỉnh thoảng.
Bài tập thực hành
- Cơ bản — Cài đặt và idempotency: Dựng một VM Ubuntu (trên cloud hoặc VirtualBox). Viết một Ansible playbook cài Nginx, đảm bảo service chạy và tự khởi động. Chạy playbook hai lần và ghi lại: lần đầu bao nhiêu task
changed, lần hai bao nhiêu taskok. Giải thích tại sao.
- Trung cấp — Template và biến: Dùng module
template(Jinja2) để tạo file cấu hình Nginx vớiserver_namevà số worker process lấy từ biến. Chạy playbook với hai giá trị biến khác nhau (dev và prod) và quan sát kết quả.
- Nâng cao — Phân biệt vai trò: Viết một Terraform tạo 2 VM, sau đó viết một Ansible playbook cấu hình chúng. Chỉ ra rõ trong bài làm: dòng nào thuộc "provisioning" (Terraform) và dòng nào thuộc "configuration" (Ansible). Viết vài câu giải thích ranh giới.
- Thử thách — Chống drift: Sau khi playbook chạy xong, SSH vào máy và cố tình sửa một file cấu hình (ví dụ đổi port Nginx). Chạy lại playbook và chứng minh Ansible tự kéo cấu hình về đúng trạng thái khai báo. Ghi lại output
--diff.
Tóm tắt
- Configuration Management giải quyết bài toán cấu hình bên trong hệ điều hành (OS-level) sau khi hạ tầng đã được tạo ra, khác với IaC vốn lo việc dựng hạ tầng (VM, network, DB).
- Ranh giới vàng cần nhớ: Terraform tạo hạ tầng, Ansible/Chef/Puppet cấu hình bên trong. Đừng lẫn lộn hai vai trò này.
- Hai đặc tính cốt lõi của Config Mgmt hiện đại là tư duy khai báo (mô tả trạng thái mong muốn) và idempotency (chạy bao nhiêu lần cũng ra một kết quả). Đây là điểm hơn hẳn script bash.
- Ansible (agentless, push, YAML) dễ học nhất, thắng thế ở đội vừa và nhỏ, đặc biệt tại Việt Nam. Puppet/Chef (agent, pull) mạnh ở quy mô rất lớn và yêu cầu tự-chữa-lành, chống-drift liên tục.
- Config Mgmt triệt tiêu configuration drift bằng cách biến cấu hình thành code — có single source of truth, có lịch sử qua Git, và có thể tích hợp CI/CD.
- Ở quy mô lớn cần tốc độ, hãy đẩy phần lớn cấu hình vào giai đoạn build golden image (immutable infrastructure), chỉ để lại cấu hình động nhỏ cho runtime.