First Adventure with Ansible

So — Ansible… I saw the post from Jason* a while back and was very intrigued, then I saw the post(s) from Kirk** and was even more intrigued; BUT I unfortunately put it on the back burner while doing my SP lab studies. To be honest it all looked a bit over my head (not being very *nix competent). Well I was talking to my co-worker up here in Seattle about an upcoming project that he’s got, and turns out there will be a lot of repetitive configs to do. I sent him a link to the above sites and he just totally ran with it. The next day I saw him at the office he showed me this at work building multiple configs just based off of variables for each device with a single process. Super cool!

I guess at this point, the question is: how is this any better than using Excel w/ variables, or some VB script, a Python script, or even just ‘find+replace’? Well on the face of it maybe there isn’t a super compelling reason since all of those methods do the same thing at the end of the day. For me, the interesting points in favor of Ansible is that it can do stuff to things other than a text file — Ansible can be used to a far greater extent — dynamic host/variable files, actually deploying configurations, doing config diff type work, and do server stuff too! At this point, all I know first hand is that Ansible can do some cool config creation, and update text file configs (basically diff existing conifgs and update with appropriate variables etc.), but I’m hoping to do some more learning/labbing and see if I can get it to push configs to real devices and do config diffs etc.

The rest of this post is a combination of several things: Kirk’s post and all his examples, help from my co-worker to help clarify some of the *nix stuff for me, and some trial and error. I tried to comment everything out to explain in layman’s terms what each line/section was doing. I hope that they make sense and are accurate! 🙂

Basic Installation:

# use pip to install
sudo easy_install pip
sudo pip install ansible

# make directory for ansible host files in the etc directory
sudo mkdir /etc/ansible

# create your host file in /etc/ansible, example as follows:
#[local]
localhost ansible_ssh_user=[put your username here]

# make sure you have a local public key for SSH for whatever user you are using
ssh-keygen -t rsa -C “[put your username here]”

# after making the key, ensure that it’s in the .ssh authorized_keys file; I was just copying the key, but as my co-worker pointed out that would overwrite any existing config, so use cat to append to the file instead
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

# Super important note — on OSX, go to system pref, sharing, and enable remote login! If this is not enabled you won’t be able to SSH to local host and that breaks what we are trying to do

Initial Testing:

# at this point, you should be able to test running ansible against the localhost and have success
ansible 127.0.0.1 -m ping

# this should result in:
127.0.0.1 | success >> {
“changed”: false,
“ping”: “pong”
}

Create File Structure:

# make directory for ansible roles/tasks; putting mine in dropbox to make life easy for me
mkdir /Users/[user]/Dropbox/Ansible

# within the new directory, create directories for ‘configs’ and ‘roles’
cd /Users/[user]/Dropbox/Ansible
mkdir configs
mkdir roles

# within roles, create a ‘router’ role for testing, within ‘router’ create ‘tasks’ ‘templates’ and ‘vars’
mkdir /roles/router
mkdir /roles/router/tasks
mkdir /roles/router/templates
mkdir /roles/router/vars

Create Initial Files

# create a site file in yaml format; good link describing this: http://www.jedelman.com/1/post/2014/03/ansible-for-networking.html
# basically each site will have a site (perhaps customer in my case), the site will define which hosts are in the site, and which roles are applied to hose hosts; i believe that we can have multiple ‘names’ within the yaml file, and that each of those names (Routers, Edge Routers, Access Switch, etc.) can have different roles assigned within the file — also hosts can be assigned to multiple roles/names. This is a super simple initial test config
# last note; this is in the root ansible directory in my dropbox folder
vi site.yml

– name: Generate Configs for Site
hosts: localhost

roles:
– router

# create a tasks file; this is in /roles/router/tasks/
vi /roles/router/tasks/main.yml

– name: Generate Configs for Site
template: src=IOS_RTR_Test.j2 dest=/Users/[user]/Dropbox/Ansible/configs/{{item.hostname}}.txt
with_items: IOS_RTR_TEST

# create a file that holds the variables that will be put into the template, this will be put into /roles/router/vars
# important note: this variables file appears to need to be the same name as the tasks file!
vi /roles/router/vars/main.yml

IOS_RTR_TEST:
– {hostname: TEST1}
– {hostname: TEST2}

# create our basic template, that includes our items to be replaced by ansible; this will go in /roles/router/templates
vi /roles/router/templates/IOS_RTR_Test.j2
THIS IS A TEST CONFIG
hostname {{item.hostname}}

Here are some awesome links that were instrumental in getting this working, the first is Kirk Byers website who is a total stud and is running an awesome Python for Network folks email class that I’ve been digging. The second is a co-worker Jason Edelman out of the opposite side of the country who’s blog I found totally by accident but am also digging, you should check them both out.

** https://pynet.twb-tech.com/blog/ansible/ansible-cfg-template.html
* http://www.jedelman.com/1/post/2014/03/ansible-for-networking.html

I also had a lot of help from my co-worker up here in the Seattle area — especially surrounding the SSH key stuff since all that in unix/linux is foreign to me.

 

 

*Update*

Totally posted that all without actually showing how to run the script to do a thing, so heres how to do that. From your Ansible directory (in my case the one in Dropbox), run the following:

ansible-playbook site.yml

This makes lotsa cool output happen on the terminal, then POOF you have two config files (if you did the same test stuff as in this post) sitting in your configs file. Cool part here is that you can go in and change the hostnames of these files (the actual configs) then go back in and re-run the playbook and it updates the hostnames automagically… lots of magicness happening here.

BGP Communities Pt. 1

BGP communities are awesome. I could end the post there, but I’m too long-winded for that…

Every customer I go to that is running BGP with their provider I make the same recommendation — start tagging routes with communities (I’ll get into what routes in a minute). Most customers will never ever have any real use case to take advantage of these tags, but in those cases where you have to do some interesting things with routes its great to already have a mechanism in place with which to do things.

Before going any further I’ll point out that every problem has many solutions, and with BGP that’s particularly true — many ways to skin the poor cats. My love affair with communities is one way to skin some cats. In the particular case of ensuring that your AS does not become a transit AS you should probably be doing a secondary layer of protection (filter list or something) in addition to community stuff.

So what routes need to be tagged, what does an organizations community policy look like, and what the hell else do we need to care about with regards to communities? Well we can start by blatantly plagiarizing the way service providers have been using communities for years. One Step Consulting has an excellent list of ISPs and lists of communities that those ISPs honor: http://onesc.net/communities/. Lets pick on L3 since they are a lovable giant, some of the more commonly used communities would be the ability to change local pref or prepend prefixes based on communities as follows:

customer traffic engineering communities – LocalPref
——————————————————–
3356:70 – set local preference to 70
3356:80 – set local preference to 80
3356:90 – set local preference to 90

Note that this is all standards based, and has been around FOREVER (1996!!) and can be found in RFC1998: http://tools.ietf.org/html/rfc1998

So why bring this up? Customers should be aware of these options, and because it gives us a framework for putting communities to work for us. So in terms of an enterprise customer, where would you want to put communities to work? The obvious answer is at the edge, so here is my ‘go-to’ community strategy at the CE routers.

Where 1234 = Customer ASN, 5678 = ISP1 ASN, and 9012 = ISP2 ASN

1234:5678
1234:5679

The above communities correspond to ISP1. The first is to be associated with ALL routes learned from ISP1, the second is all ISP1 provider local routes (ISP1+1 AS Path). ISP2 would have similar communities assigned.

1234:9012
1234:9013

A third community would be assigned to ALL ISP learned routes:

1234:999

So why bother with all this? Firstly, we now have a super simple way to deny any prefixes learned from ISP1 from being advertised to ISP2 and visa versa. Inbound from the providers we apply our communities to our desired routes along with whatever else we have going on, here’s an example in XR and IOS:
XR (Note that RPLs can call other RPLs, so the first one here is the ‘parent’ RPL basically, these obviously reference community lists that I’ll leave out so this doesn’t take up a million lines):

 route-policy RPL_ISP1_Inbound
 apply RPL_Deny_Bogons
 apply RPL_All_Provider_Inbound
 apply RPL_ISP1_All_Prefixes
 apply RPL_ISP1_Provider_Local_Prefixes
 end-policy
 !
 route-policy RPL_Deny_Bogons
 if destination in PS_Bogons then
 drop
 else
 pass
 endif
 end-policy
 !
 route-policy RPL_All_Provider_Inbound
 set community COMM_Any_Provider additive
 set local-preference 90
 pass
 end-policy
 !
 route-policy RPL_ISP1_All_Prefixes
 set community COMM_ISP1_All_Prefixes additive
 pass
 end-policy
 !
 route-policy RPL_ISP1_Provider_Local_Prefixes
 if as-path in AS_ISP1_Local then
 set community COMM_ISP1_Provider_Local additive
 pass
 endif
 end-policy

IOS:

 route-map RM_ISP1_Inbound deny 10
 match ip address prefix-list PL_Bogons
 !
 route-map RM_ISP1_Inbound permit 20
 match as-path 1
 set local-preference 90
 set community 1234:999 1234:5678 1234:5679
 !
 route-map RM_ISP1_Inbound permit 30
 set local-preference 90
 set community 1234:999 1234:5678

Basically all this is just doing what we’ve discussed — plop some communities on some routes so that you can do stuff with them later. The obvious as mentioned is to prevent yourself from becoming transit; here is a simple RPL/Route-map to do just that:
XR:

route-policy RPL_ISP1_Outbound
 if community matches-any COMM_All_Providers then
  drop
 endif
 pass
 end-policy

IOS:

 route-map RM_ISP1_Outbound deny 10
 match community COMM_All_Providers
 !
 route-map RM_ISP1_Outbound permit 1000

We could also have forgone the 1234:999 tag and instead just used the 1234:5678 and 1234:9012 communities to deny advertising those outbound. I find it simpler to just use a single community though. The communities that are applied to provider local routes can be used to allow that smaller subset of prefixes to routers that maybe don’t have enough memory to run full tables, or just don’t have a real business case to have full tables, but still wants to be able to make semi-intelligent outbound routing decisions. This sounds kind of hokey off-hand, but I promise it’s a real thing, and I will elaborate on it in the future 🙂

I think I’ll wrap this up at this point since its already rather long. I’ll try to get a write up of a topology I’ve deployed a few times at the edge of mid-large sized enterprise customers that have taken advantage of communities in order to help keep route-tables smaller were needed, and yet provide the best possible load balancing outbound across multiple ISPs.

Side note, cats are cool. Do NOT skin real cats. Here is my cat, his name is Luca and he is the greatest cat on the entire planet:

Untitled