Now we need to set up the pipeline stages in the .gitlab-ci.yml file. This file acts as the control
file for defining all CI/CD jobs.
The pipeline will validate the NaC data model, deploy the model to staging during a merge request, verify the deployed state, and then deploy the same model to production after the merge request is merged.
When managing your network with IaC, adding good validation is critical. Your test code should be equal to or better than the production code. In this lab, the pipeline performs three checks:
NOTE: The validation we do as part of this lab is a simple example. A production environment should include as many tests as required to prove that a proposed network change is safe.
NaC owns the deployment workflow. For the post-deploy check, this small playbook queries the ND REST API through the NDFC Ansible Collection dependency and asserts that the VRFs and Networks are not out of sync.
cat << EOF > ~/workspace/CiscoLive/DEVWKS-3928/verify_overlay.yaml
---
- name: Verify deployed overlay state in ND
hosts: all
gather_facts: false
tasks:
- name: Query VRFs from the fabric
cisco.dcnm.dcnm_rest:
path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{{ inventory_hostname }}/vrfs"
method: GET
register: vrf_result
- name: Assert VRFs are not out of sync
ansible.builtin.assert:
that:
- item.vrfStatus != "OUT-OF-SYNC"
quiet: true
loop: "{{ vrf_result.response.DATA }}"
- name: Query Networks from the fabric
cisco.dcnm.dcnm_rest:
path: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/top-down/fabrics/{{ inventory_hostname }}/networks"
method: GET
register: network_result
- name: Assert Networks are not out of sync
ansible.builtin.assert:
that:
- item.networkStatus != "OUT-OF-SYNC"
quiet: true
loop: "{{ network_result.response.DATA }}"
EOF
We now want to add a new VRF and two more Networks. The merge request pipeline will deploy and verify the change on
fabric-stage. After the merge request is approved and merged, the main branch pipeline will deploy and
verify the same change on fabric-prod. The command below updates both fabric data models in one copy
and paste operation.
cat << EOF > ~/workspace/CiscoLive/DEVWKS-3928/host_vars/fabric-stage/vrfs.nac.yaml
---
vxlan:
overlay:
vrfs:
- name: vrf_devnet
vrf_id: 150001
vlan_id: 2000
vrf_attach_group: all_leaf
- name: vrf_app1
vrf_id: 50001
vlan_id: 2010
vrf_attach_group: all_leaf
vrf_attach_groups:
- name: all_leaf
switches:
- hostname: staging-leaf1
- hostname: staging-leaf2
EOF
cat << EOF > ~/workspace/CiscoLive/DEVWKS-3928/host_vars/fabric-stage/networks.nac.yaml
---
vxlan:
overlay:
networks:
- name: network_devnet1
vrf_name: vrf_devnet
net_id: 130001
vlan_id: 2301
vlan_name: network_devnet1_vlan2301
gw_ip_address: 10.10.10.1/24
network_attach_group: esxi
- name: network_devnet2
vrf_name: vrf_devnet
net_id: 130002
vlan_id: 2302
vlan_name: network_devnet2_vlan2302
gw_ip_address: 10.10.11.1/24
network_attach_group: esxi
- name: network_web
vrf_name: vrf_app1
net_id: 30001
vlan_id: 2311
vlan_name: network_web_vlan2311
gw_ip_address: 10.1.1.1/24
network_attach_group: esxi
- name: network_app
vrf_name: vrf_app1
net_id: 30002
vlan_id: 2312
vlan_name: network_app_vlan2312
gw_ip_address: 10.1.2.1/24
network_attach_group: esxi
network_attach_groups:
- name: esxi
switches:
- hostname: staging-leaf1
ports:
- Ethernet1/15
- hostname: staging-leaf2
ports:
- Ethernet1/15
EOF
cat << EOF > ~/workspace/CiscoLive/DEVWKS-3928/host_vars/fabric-prod/vrfs.nac.yaml
---
vxlan:
overlay:
vrfs:
- name: vrf_devnet
vrf_id: 150001
vlan_id: 2000
vrf_attach_group: all_leaf
- name: vrf_app1
vrf_id: 50001
vlan_id: 2010
vrf_attach_group: all_leaf
vrf_attach_groups:
- name: all_leaf
switches:
- hostname: prod-leaf1
- hostname: prod-leaf2
EOF
cat << EOF > ~/workspace/CiscoLive/DEVWKS-3928/host_vars/fabric-prod/networks.nac.yaml
---
vxlan:
overlay:
networks:
- name: network_devnet1
vrf_name: vrf_devnet
net_id: 130001
vlan_id: 2301
vlan_name: network_devnet1_vlan2301
gw_ip_address: 10.10.10.1/24
network_attach_group: esxi
- name: network_devnet2
vrf_name: vrf_devnet
net_id: 130002
vlan_id: 2302
vlan_name: network_devnet2_vlan2302
gw_ip_address: 10.10.11.1/24
network_attach_group: esxi
- name: network_web
vrf_name: vrf_app1
net_id: 30001
vlan_id: 2311
vlan_name: network_web_vlan2311
gw_ip_address: 10.1.1.1/24
network_attach_group: esxi
- name: network_app
vrf_name: vrf_app1
net_id: 30002
vlan_id: 2312
vlan_name: network_app_vlan2312
gw_ip_address: 10.1.2.1/24
network_attach_group: esxi
network_attach_groups:
- name: esxi
switches:
- hostname: prod-leaf1
ports:
- Ethernet1/15
- hostname: prod-leaf2
ports:
- Ethernet1/15
EOF
Create the GitLab pipeline file. The CI path runs for merge requests into main and targets
fabric-stage. The CD path runs after changes land on main and targets
fabric-prod.
cat << EOF > ~/workspace/CiscoLive/DEVWKS-3928/.gitlab-ci.yml
---
stages:
- validate
- deploy
- verify
validate_nac_model:
stage: validate
image:
name: "cytopia/ansible:2.18-infra"
entrypoint: [""]
rules:
- if: '\$CI_PIPELINE_SOURCE == "push" && \$CI_COMMIT_BRANCH == "stage"'
- if: '\$CI_PIPELINE_SOURCE == "merge_request_event" && \$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
- if: '\$CI_PIPELINE_SOURCE == "push" && \$CI_COMMIT_BRANCH == "main"'
before_script:
- python3 -m pip install -r requirements.txt
- ansible-galaxy collection install -r requirements.yaml
script:
- ansible-lint --format=pep8 vxlan.yaml
- ansible-playbook -i hosts.stage.yaml vxlan.yaml --tags role_validate
- ansible-playbook -i hosts.prod.yaml vxlan.yaml --tags role_validate
deploy_on_stage:
stage: deploy
image:
name: "cytopia/ansible:2.18-infra"
entrypoint: [""]
rules:
- if: '\$CI_PIPELINE_SOURCE == "merge_request_event" && \$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
before_script:
- python3 -m pip install -r requirements.txt
- ansible-galaxy collection install -r requirements.yaml
script:
- ansible-playbook -i hosts.stage.yaml vxlan.yaml --tags role_validate,cr_manage_vrfs,cr_manage_networks,role_deploy
verify_on_stage:
stage: verify
image:
name: "cytopia/ansible:2.18-infra"
entrypoint: [""]
rules:
- if: '\$CI_PIPELINE_SOURCE == "merge_request_event" && \$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
before_script:
- python3 -m pip install -r requirements.txt
- ansible-galaxy collection install -r requirements.yaml
script:
- sleep 10
- ansible-playbook -i hosts.stage.yaml verify_overlay.yaml
deploy_on_prod:
stage: deploy
image:
name: "cytopia/ansible:2.18-infra"
entrypoint: [""]
rules:
- if: '\$CI_PIPELINE_SOURCE == "push" && \$CI_COMMIT_BRANCH == "main"'
before_script:
- python3 -m pip install -r requirements.txt
- ansible-galaxy collection install -r requirements.yaml
script:
- ansible-playbook -i hosts.prod.yaml vxlan.yaml --tags role_validate,cr_manage_vrfs,cr_manage_networks,role_deploy
verify_on_prod:
stage: verify
image:
name: "cytopia/ansible:2.18-infra"
entrypoint: [""]
rules:
- if: '\$CI_PIPELINE_SOURCE == "push" && \$CI_COMMIT_BRANCH == "main"'
before_script:
- python3 -m pip install -r requirements.txt
- ansible-galaxy collection install -r requirements.yaml
script:
- sleep 10
- ansible-playbook -i hosts.prod.yaml verify_overlay.yaml
EOF
The pipeline depends on GitLab CI/CD variables for ND_USERNAME, ND_PASSWORD,
ND_DOMAIN, NDFC_SW_USERNAME, and NDFC_SW_PASSWORD. These are the same
values you placed in the local .env file earlier but will be stored as secret variables in GitLab.
The validate stage installs the NaC requirements, runs ansible-lint, and runs the NaC validation
role. This stage catches model and syntax issues before any fabric state is changed.
The deploy stage runs the same NaC playbook you used locally. Merge requests deploy to
fabric-stage. Merges into main deploy to fabric-prod.
The verify stage queries ND after deployment and fails if any VRF or Network reports an out-of-sync state.
We will trigger the various pipeline stages in the next section.