Over the past 3 posts we have covered what it means, how to start and what is required to begin down the Infra-As-Code journey. Hopefully things have been making sense and you have found them to be of some use. Obviously the main goal is just bringing awareness to what is involved and to help start the discussions around this journey. In the last post I also mentioned that I had created a Vagrant lab for learning and testing out some of the tooling. If you have not checked it out, you can do so by heading over to here. This lab is great for mocking up test scenarios and learning the methodologies involved.

 

In this post we will take what we have covered in the previous posts and mock up an example to see how the workflow might look. And for our mock example we will be making some configuration changes to a Cisco ACI network environment using Ansible to perform the configuration changes for our desired state.

 

The details below is what our workflow looks like for this mock-up.

  • - Developer – Writes Ansible playbooks and submits code to Gerrit
  • - Gerrit – Git Repository and Code-Review (Both master and dev branches)
  • - Code-Reviewer – Either signs off on changes or pushes back
  • - Jenkins – CI/CD – Monitors master/dev branches on Git Repository (Gerrit)
  • - Jenkins – Initiates the workflow when a change is detected on master/dev branches

 

And below outlines what our mock-up example entails from a new request received.

 

Change request:

  • - Create a new tenant for the example environment, which will consist of some web-servers and DB-servers. The web-servers will need to communicate with the DB-servers over tcp/1433 for MS SQL.
  • - After bringing all of the respective teams together to discuss in detail on the request and identify each object which must be defined, configured and made available for this request to be successful. (Below is what was gathered based on the open discussion)
    • Tenant:
      • § Name: Example1
      • Context name(s) (VRF):
        • § Name: Example1-VRF
      • Bridge-Domains:
        • § Name: Example1-BD
        • § Subnet: 10.0.0.0/24
      • Application Network Profile
        • § Name: Example1-ANP
      • Filters:
        • § Name: Example1-web-filter
        • § Entries:
          • Name: Example1-web-filter-entry-80
            • Proto: tcp
            • Port: 80
            • Name: Example1-web-filter-entry-443
              • Proto: tcp
              • Port: 443
        • § Name: Example1-db-filter
        • § Entries:
          • Name: Example1-db-filter-entry-1433
            • Proto: tcp
            • Port: 1433
      • Contracts:
        • § Name: Example1-web-contract
          • Filters:
            • Name: Example1-web-filter
            • Subjects:
              • Name: Example1-web-contract-subject
        • § Name: Example1-db-contract
          • Filters:
            • Name: Example1-db-filter
            • Subjects:
              • Name: Example1-db-contract-subject

Open discussion:

 

So based on the open discussion we have come up with the above details on what is required from a Cisco ACI configuration perspective in order to deliver the request as defined. We will use the above information to begin creating our Ansible playbook to implement the new request.

 

Development:

We are now ready for the development phase of creating our Ansible playbook in order to deliver the environment from the request. And knowing that Gerrit is used for our version control/code repository we need to ensure that we are continually committing our changes to a new dev branch on our Ansible-ACI Git repository as we are developing our playbook.

 

**Note – Never make changes directly to the master branch…Always create/use a different branch to develop your changes and then merge those into master.

 

Now we need to pull down our Ansible-ACI Git repository to begin our development.

$mkdir -p ~/Git_Projects/Gerrit

$cd ~/Git_Projects/Gerrit

$git clone git@gerrit:29418/Ansible-ACI.git

$cd Ansible-ACI

$git checkout -b dev

 

We are now in our dev branch and can now begin our coding.

 

We now create our new Ansible playbook.

$vi playbook.yml

 

And as we create our playbook we can begin committing changes as we go. (Follow the steps below on every change you want to commit)

$git add playbook.yml

$git commit -sm “Added ACI Tenants, Contracts and etc.”

$git push

 

In the example above we used -sm as part of our git commit. The -s adds a sign-off by the user making the changes and the -m designates the message that we are adding as part of our commit. You can also just use the -s and then your editor will open for you to enter your message details.

 

So we end up coming up with the following playbook which we can now proceed with testing in our test environment.

---

- name: Manages Cisco ACI

  hosts: apic

  connection: local

  gather_facts: no

  vars:

    - aci_application_network_profiles:

        - name: Example1-ANP

          description: Example1 App Network Profile

          tenant: Example1

          state: present

    - aci_bridge_domains:

        - name: Example1-BD

          description: Example1 Bridge Domain

          tenant: Example1

          subnet: 10.0.0.0/24

          context: Example1-VRF

          state: present

    - aci_contexts:

        - name: Example1-VRF

          description: Example1 Context

          tenant: Example1

          state: present

    - aci_contract_subjects:

        - name: Example1-web-contract-subject

          description: Example1 Web Contract subject

          tenant: Example1

          contract: Example1-web-contract

          filters: Example1-web-filter

          state: present

        - name: Example1-db-contract-subject

          description: Example1 DB Contract Subject

          tenant: Example1

          contract: Example1-db-contract

          filters: Example1-db-filter

          state: present

    - aci_contracts:

        - name: Example1-web-contract

          description: Example1 Web Contract

          tenant: Example1

          state: present

    - aci_filter_entries:

        - name: Example1-web-filter-entry-80

          description: Example1 Web Filter Entry http

          tenant: Example1

          filter: Example1-web-filter  #defined in aci_filters

          proto: tcp

          dest_to_port: 80

          state: present

        - name: Example1-web-filter-entry-443

          description: Example1 Web Filter Entry https

          tenant: Example1

          filter: Example1-web-filter  #defined in aci_filters

          proto: tcp

          dest_to_port: 443

          state: present

        - name: Example1-db-filter-entry-1433

          description: Example1 DB Filter MS-SQL

          tenant: Example1

          filter: Example1-db-filter

          proto: tcp

          dest_to_port: 1433

          state: present

    - aci_filters:

        - name: Example1-web-filter

          description: Example1 Web Filter

          tenant: Example1

          state: present

        - name: Example1-db-filter

          description: Example1 DB Filter

          tenant: Example1

          state: present

    - aci_tenants:

        - name: Example1

          description: Example1 Tenant

          state: present

  vars_prompt:  #Prompts for below info upon execution

    - name: "aci_apic_host"

      prompt: "Enter ACI APIC host"

      private: no

      default: "127.0.0.1"

    - name: "aci_username"

      prompt: "Enter ACI username"

      private: no

    - name: "aci_password"

      prompt: "Enter ACI password"

      private: yes

  tasks:

    - name: manages aci tenant(s)

      aci_tenant:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-tenants

      with_items: aci_tenants

 

    - name: manages aci context(s)

      aci_context:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        tenant: "{{ item.tenant }}"

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-contexts

      with_items: aci_contexts

 

    - name: manages aci bridge domain(s)

      aci_bridge_domain:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        context: "{{ item.context }}"

        tenant: "{{ item.tenant }}"

        subnet: "{{ item.subnet }}"

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-bridge-domains

      with_items: aci_bridge_domains

 

    - name: manages aci application network profile(s)

      aci_anp:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        tenant: "{{ item.tenant }}"

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-application-network-profiles

      with_items: aci_application_network_profiles

 

    - name: manages aci filter(s)

      aci_filter:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        tenant: "{{ item.tenant }}"      

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-filters

      with_items: aci_filters

 

    - name: manages aci filter entries

      aci_filter_entry:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        tenant: "{{ item.tenant }}"

        filter: "{{ item.filter }}"

        proto: "{{ item.proto }}"

        dest_to_port: "{{ item.dest_to_port }}"

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-filter-entries

      with_items: aci_filter_entries

 

    - name: manages aci contract(s)

      aci_contract:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        tenant: "{{ item.tenant }}"

        scope: "{{ item.scope|default(omit) }}"

        prio: "{{ item.prio|default(omit) }}"

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-contracts

      with_items: aci_contracts

 

    - name: manages aci contract subject(s)

      aci_contract_subject:

        name: "{{ item.name }}"

        descr: "{{ item.description|default(omit) }}"

        tenant: "{{ item.tenant }}"

        contract: "{{ item.contract }}"

        filters: "{{ item.filters }}"

        apply_both_directions: "{{ item.apply_both_directions|default('True') }}"

        prio: "{{ item.prio|default(omit) }}"

        state: "{{ item.state }}"

        host: "{{ aci_apic_host }}"

        username: "{{ aci_username }}"

        password: "{{ aci_password }}"

      tags:

        - aci-contract-subjects

      with_items: aci_contract_subjects

 

 

Testing:

The assumption here is that we have already configured our Jenkins job to do the following as part of the workflow for our test environment:

  • - Monitor the dev branch on git@gerrit:29418/Ansible-ACI.git for changes.
  • - Trigger a backup of the existing Cisco ACI environment (Read KB on this here)
  • - Execute the playbook.yml Ansible playbook against our Cisco ACI test gear and report back on the status via email as well as what is available from our Jenkins job report. (Ensuring that our test APIC controller is specified as the host)

 

Now assuming that all of our testing has been successful and we have validated that the appropriate Cisco ACI changes have been implemented successfully. We are now ready to push our new configuration changes up to the master branch for code-review.

 

Code-Review:

We are now ready to merge our dev branch into our master branch and commit for review. Remember that you should not be the one who also signs off on code-review and the person who does should have knowledge in regards to the change being implemented. So we will assume that the above is true for this mock-up.

 

So we can now merge the dev branch with our master branch.

$git checkout master

$git merge dev

 

Now we can push our code up for review.

$git review

 

Now our new code changes are staged on our Gerrit server ready for someone to either sign-off on the change and merge the new changes in our master branch or push the changes back for additional information. But before we proceed with the sign-off we need to engage our peer-review phase by following the next section.

 

Peer-Review:

We now should re-engage the original teams and discuss the testing phase results, the actual changes to be made and ensure that there is absolutely nothing missing from the implementation. This is also a good stage to include the person who will be signing off on the change as part of the discussion. In doing so will ensure that they are fully aware of the changes being implemented and have a better understanding in order to proceed or not.

 

After a successful peer-review the person who is in charge of signing off on the code-review should be ready to proceed or not. So for this mock-up we will assume that all is a go and they proceed with signing off and the changes get merged into our master branch. Those changes are now ready for Jenkins to pick up and implement in production.

 

Implementation:

So now that all of our configurations have been defined into an Ansible playbook, all testing phases have been successful and our code-review has been signed off on we are now ready to enter the implementation phase in our production environment.

 

Our production Jenkins workflow should look identical to our testing phase setup so this should be an easy one to setup. The only differences here should be the ACI controller that is configured for our production environment therefore our workflow should look similar to the following.

  • - Monitor the master branch on git@gerrit:29418/Ansible-ACI.git for changes.
  • - Trigger a backup of the existing Cisco ACI environment (Read KB on this here)
  • - Execute the playbook.yml Ansible playbook against our Cisco ACI production gear and report back on the status via email as well as what is available from our Jenkins job report. (Ensuring that our production APIC controller is specified as the host)

 

And again assuming that our Jenkins workflow ran successfully we should be good and all changes should have been implemented successfully.

 

Final thoughts

 

I hope you found the above useful and informational on what a typical Infra-As-Code change might look like. There are some additional methodologies that you may want to implement as part of your workflow as we did with this mock-up. And one of those may be some additional automation steps and/or end-user capabilities. And we will cover some of those items in the next post which will cover next steps in our journey.