Sổ tay kỹ thuật

CI/CD cho cấu hình mạng

concept#ci-cd#ansible#batfish#git· cập nhật 2026-11-06

Dùng khi muốn đưa thay đổi cấu hình router qua pipeline Git - lint, validate bằng Batfish, dry-run, deploy, verify và rollback - thay vì gõ tay trên CLI.

Sửa trang này
Mục lục

Ý 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 pybatfish
snapshots/candidate/
└── configs/
    ├── pe1-hni.cfg      # config render từ template (bản sẽ đẩy)
    └── mx1-hcm.cfg
ci/batfish_check.py
import 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

.gitlab-ci.yml
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 5

Vớ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

Tham khảo