Task03
CI/CD Pipeline

Setup CI/CD Pipeline

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.

Pipeline test cases

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:

  • Run Ansible lint against the NaC playbook and model files
  • Run the NaC validation role before deployment
  • Query ND after deployment to confirm VRFs and Networks are not out of sync

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.

Step 1 - Create a post-deploy verification playbook

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

Step 2 - Add a new VRF and two new Networks

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

Step 3 - Define the CI/CD pipeline stages

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.

Validate Stage

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.

Deploy Stage

The deploy stage runs the same NaC playbook you used locally. Merge requests deploy to fabric-stage. Merges into main deploy to fabric-prod.

Verify Stage

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.