Ý tưởng: cấu hình mạng (biến, template, playbook) nằm trong Git. Mọi thay đổi đi qua merge request, pipeline tự kiểm tra từ rẻ đến đắt, người review đọc diff thật trên thiết bị chứ không chỉ diff YAML. Chỉ sau khi merge mới deploy, và deploy luôn có bước verify và đường rollback.
Cách hoạt động
flowchart LR A["merge request"] --> B["lint: yamllint, ansible-lint"] B --> C["render template"] C --> D["validate: Batfish"] D --> E["dry-run: --check --diff"] E --> F["review + merge"] F --> G["deploy: commit confirmed"] G --> H{"verify"} H -->|"OK"| I["confirm commit"] H -->|"lỗi"| J["rollback"]
Các giai đoạn
| Giai đoạn | Công cụ | Bắt được gì | Cần thiết bị? |
|---|---|---|---|
| Lint | yamllint, ansible-lint, pre-commit |
Sai cú pháp YAML, thụt lề, module sai tên, lộ secret (gitleaks) |
Không |
| Kiểm tra dữ liệu | JSON Schema / Pydantic trên host_vars |
Thiếu field, IP sai định dạng, ASN ngoài dải, trùng IP | Không |
| Render | Ansible state: rendered, Jinja2 |
Template lỗi, biến thiếu (StrictUndefined) |
Không |
| Validate | Batfish | Undefined reference (RPL/prefix-set chưa định nghĩa), BGP session không lên được, ACL chặn nhầm, reachability | Không (phân tích file config) |
| Dry-run | ansible-playbook --check --diff, NAPALM compare_config() |
Diff thật so với running config | Có (chỉ đọc + load/discard) |
| Deploy | Ansible / NAPALM / Nornir, commit confirmed |
Có | |
| Verify | show + parse, getter NAPALM, gNMI |
Session BGP down, prefix count lệch, interface down | Có |
| Rollback | Không confirm (tự hết giờ), rollback 1 |
Có |
Mỗi giai đoạn chi tiết: lint với pre-commit, template Jinja2, check mode IOS XR, commit confirmed Junos.
Batfish
Batfish đọc file config (IOS XR, Junos, EOS...), dựng mô hình mạng và trả lời câu hỏi mà không cần chạm vào thiết bị. Chạy service bằng Docker, gọi từ Python qua pybatfish.
docker run -d --name batfish -p 9996:9996 batfish/allinone
python3 -m pip install pybatfishsnapshots/candidate/
└── configs/
├── pe1-hni.cfg # config render từ template (bản sẽ đẩy)
└── mx1-hcm.cfgimport sys
from pybatfish.client.session import Session
bf = Session(host="localhost")
bf.set_network("backbone")
bf.init_snapshot("snapshots/candidate", name="candidate", overwrite=True)
checks = {
"parse": bf.q.fileParseStatus().answer().frame().query("Status != 'PASSED'"),
"undefined": bf.q.undefinedReferences().answer().frame(),
"bgp": bf.q.bgpSessionStatus().answer().frame().query("Established_Status != 'ESTABLISHED'"),
}
failed = False
for name, df in checks.items():
if not df.empty:
failed = True
print(f"--- {name} ---\n{df.to_string()}")
sys.exit(1 if failed else 0)Peer eBGP ra ngoài snapshot (transit, IXP) sẽ không bao giờ ESTABLISHED trong Batfish: lọc theo Remote_IP hoặc thêm file mô tả external BGP announcement, tránh pipeline đỏ giả.
Ví dụ pipeline
stages: [lint, validate, dryrun, deploy]
lint:
stage: lint
script:
- yamllint -s .
- ansible-lint playbooks/
validate:
stage: validate
services:
- name: batfish/allinone
alias: batfish
script:
- ansible-playbook playbooks/render.yml # ghi config vào snapshots/candidate/configs
- python3 ci/batfish_check.py
dryrun:
stage: dryrun
script:
- ansible-playbook -i inventory playbooks/deploy.yml --check --diff | tee diff.txt
artifacts:
paths: [diff.txt]
deploy:
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual # người bấm, không tự động hoàn toàn
script:
- ansible-playbook -i inventory playbooks/deploy.yml --diff -f 5Với GitHub Actions cấu trúc tương tự, xem GitHub Actions cheatsheet. Trong script dùng Session(host="batfish") khi Batfish chạy dưới dạng service có alias.
Runner chạy dry-run/deploy có credential vào thiết bị thật: dùng runner riêng trong mạng quản lý, credential lưu ở biến CI dạng masked/protected hoặc Vault, tài khoản dry-run chỉ có quyền đọc + load (không commit) nếu AAA cho phép.
Khi nào dùng
| Tình huống | Gợi ý |
|---|---|
| Đội mới bắt đầu, chưa có template | Bắt đầu với backup vào Git (backup config) + lint, chưa cần deploy tự động |
| Thay đổi lặp lại nhiều (peer, VLAN, description) | Pipeline đầy đủ, deploy thủ công bằng nút when: manual |
| Thay đổi lớn, hiếm (migrate IGP, đổi kiến trúc) | Dùng Batfish để phân tích trước; deploy theo change window, vẫn có người giám sát |
| Không có lab/Batfish | Tối thiểu: lint + --check --diff + commit confirmed |
| Muốn tránh drift | Job định kỳ chạy --check, báo khi thiết bị khác Git |