Apparently I was on a quest and didn't even know it. Automation is great (essential, even), but at some point you need to wrap your arms around the whole thing. After spending some time with Puppet (and deciding it wasn't for me), I found Chef to be pretty approachable. I like the native versioning of recipes and the hands-free method of creating workstation separate from the master (the fewer hands that touch the master, the better). But there was a definite learning curve to be overcome.
Then I found Salt.
Pure simplicity.
How do I install Salt on CentOS?
yum install salt-master
How do I use Salt to install Apache?
salt myMinion pkg.install httpd
What about all my webservers?
salt -N myWebServers pkg.install httpd
Install Apache, pull down the latest version of my code, and restart Apache if the code changes!
my-awesome-app:
pkg.install:
- name: httpd
git.latest:
- name: http://github.com/awesome/sauce.git
- target: /var/www/html/awesomesauce
service:
- name: httpd
- enable: True
- running
- watch:
- git: my-awesome-app
And done! Every server. Latest code. Apache keeps up. All in easy to read yaml.
So simple.
Hell, it even works with Powershell. Fully automated MSSQL installation? Done. Local user management? Easy. I'm confident that, with the proper Salt setup, you can do away with System Center Config Manager. The implications are huge for people willing and able to do it themselves (instead of paying for Microsoft licenses and consultants).
I'm hoping to make some big changes in my environment with Salt. The more work we can get the machine to do, the more time we have to work on more interesting problems.
Tuesday, November 26, 2013
Monday, March 4, 2013
Chef 11 Deployment - Lessons Learned
I had rolled out a few proof-of-concept deployments of Chef server on CentOS and Ubuntu, each complete with their own workstation. I thought I had it down.
I was wrong.
This post will serve as a reference for my future self. Hopefully you, dear reader, have landed here after Googling an error. I hope I can save you some misery. This rollout was Chef Server 11.0.6 on Ubuntu.
I was wrong.
This post will serve as a reference for my future self. Hopefully you, dear reader, have landed here after Googling an error. I hope I can save you some misery. This rollout was Chef Server 11.0.6 on Ubuntu.
Changing The Hostname
I was rolling out my deployment on Amazon AWS and didn't particularly care for the default machine names of an IPv6 address. I changed the hostname of my Chef server before doing anything, figuring it would affect nothing. I was wrong.
As it turns out, your machine's hostname must resolve successfully in order for chef-server-ctl reconfigure to complete successfully. My fix was exactly as described in that Opscode ticket - drop a line in /etc/hosts:
<floating IP> ubuntu-chef-11-server
Onward...
Your Workstation and Creating Your Own knife.rb
The latest docs on setting up your workstation mention knife.rb seven times but the page assumes that you're using Hosted Chef or Private Chef. Not your own Chef server. As such, I consulted some older (but still very useful) docs to create my own knife.rb. The doc says that the API listener sits on port 4000 and the Web Interface on 4040. This is not the case.Both listen on port 80. I lost two hours looking at this error:
[root@chef-11-workstation chef-repo]# knife user list
ERROR: Connection refused connecting to ubuntu-chef-11-server:4000 for /users, retry 1/5
ERROR: Connection refused connecting to ubuntu-chef-11-server:4000 for /users, retry 2/5
ERROR: Connection refused connecting to ubuntu-chef-11-server:4000 for /users, retry 3/5
ERROR: Connection refused connecting to ubuntu-chef-11-server:4000 for /users, retry 4/5
ERROR: Connection refused connecting to ubuntu-chef-11-server:4000 for /users, retry 5/5
This is exacerbated by the default value provided when running knife configure:
Please enter the chef server URL: [http://localhost:4000]
Makes you think port 4000, right? Wrong. It's port 80.
In Summary
- If you must change the hostname, confirm that the new name can be resolved (via /etc/hosts, for example).
- The API listener and the WebUI both listen on port 80.
Wednesday, February 6, 2013
Sustainable Powershell - Cmdlets
Since we're automating work, and (currently) a lot of the work involves Windows ecosystems, the natural tool of choice is Powershell. Let's look at some general guidelines for writing sustainable Powershell cmdlets.
For the purpose of this post, I'll use functions and cmdlets interchangeably as they can both be considered atomic work units.
Protip: Not that you should use it, but you can pass the -DisableNameChecking parameter to Import-Module to suppress the warning. You should know, however, that if you need to suppress the warning, you're probably developing your cmdlets the wrong way.
The corollary to this is use descriptive names. The year is 2013. We have plenty of disk to store code with long names. Use it.
For the rest of us: A cmdlet should not hold onto a value. That is, it should not store something in a global/environment variable. It can check some environment variables if it needs to make a decision, but it should not communicate with the user/other cmdlets via manipulating those variables.
If you need data, put it in/get it from a data source, such as a database, the Windows registry, or even a flat text file. Don't store it in memory (e.g. a variable) because you'll create problems for yourself that distract you from the real work.
When writing modules, your functions need to start with 'approved' verbs. To get a list of those verbs, use the command Get-Verb or see Microsoft's writeup.
For example, if a cmdlet needs information about a server, don't create an object just to represent that server. Instead, have your cmdlet accept the text version of that information.
Don't Do This
Create-NewServer -serverDetails complicatedObject
Where complicatedObject is an object or a hash with values describing the server.
Instead, Do This
Create-NewServer -ServerName worker01 -IP 192.168.10.1 -Netmask 255.255.255.0
Writing your function will take more time and the incantations will be more verbose. B-U-T in the future when (not 'if', but 'when') you need to update that function, making the required changes will be so much easier than updating code that expects more complex input.
In summary, this is the state we're in today. The environment will change and constraints will improve but, in the meanwhile, following these guidelines will reduce (not eliminate!) tomorrow's maintenance cost of the software you write today.
For the purpose of this post, I'll use functions and cmdlets interchangeably as they can both be considered atomic work units.
Powershell Cmdlet Guidelines
Cmdlets shall:Use the Verb-Noun Naming Scheme
Using this scheme forces you to think more about what your functions/cmdlets should be doing. It's the decided 'best practice' from Microsoft and when you import a module that violates this scheme, you get a nice nagging message from Powershell telling you exactly that.Protip: Not that you should use it, but you can pass the -DisableNameChecking parameter to Import-Module to suppress the warning. You should know, however, that if you need to suppress the warning, you're probably developing your cmdlets the wrong way.
Use Camel Case for Capitalization
For instance, Get-DetailsFromReallyLongName.The corollary to this is use descriptive names. The year is 2013. We have plenty of disk to store code with long names. Use it.
Cmdlets Do Not Hold State
Cmdlets do stuff, they do not hold things. If you're familiar with RESTful interfaces and/or stateless architecture, move to the next point because you already understand.For the rest of us: A cmdlet should not hold onto a value. That is, it should not store something in a global/environment variable. It can check some environment variables if it needs to make a decision, but it should not communicate with the user/other cmdlets via manipulating those variables.
If you need data, put it in/get it from a data source, such as a database, the Windows registry, or even a flat text file. Don't store it in memory (e.g. a variable) because you'll create problems for yourself that distract you from the real work.
Use "Approved" Verbs
Personally, I'm a little bitter that the creators of Powershell drew a line around what we can and cannot use when writing our own modules but that doesn't change the fact that we have to deal with it. And besides, it does help to keep you in the right mindset when writing functions/cmdlets.When writing modules, your functions need to start with 'approved' verbs. To get a list of those verbs, use the command Get-Verb or see Microsoft's writeup.
Do One Thing. Do It Well.
See also the Unix Philosophy of software development. Forty years of tireless development and crushing testing environments have made this philosophy even more relevant.Use Command-Line Parameters
When thinking of how to pass information to your cmdlet, think in the same way you write your cmdlets/functions: simple and atomic. Don't pass an object representing information to your cmdlet (because that breeds complexity, which leaves you prone to errors). Instead, simply pass the information.For example, if a cmdlet needs information about a server, don't create an object just to represent that server. Instead, have your cmdlet accept the text version of that information.
Don't Do This
Create-NewServer -serverDetails complicatedObject
Where complicatedObject is an object or a hash with values describing the server.
Instead, Do This
Create-NewServer -ServerName worker01 -IP 192.168.10.1 -Netmask 255.255.255.0
Writing your function will take more time and the incantations will be more verbose. B-U-T in the future when (not 'if', but 'when') you need to update that function, making the required changes will be so much easier than updating code that expects more complex input.
In summary, this is the state we're in today. The environment will change and constraints will improve but, in the meanwhile, following these guidelines will reduce (not eliminate!) tomorrow's maintenance cost of the software you write today.
Tuesday, January 22, 2013
Sustainable Automation
When you automate technology (at a meaningful scale, anyway), you invariably wind up writing code. I've yet to see some automation technology that was all point-and-click. Chef, Puppet, Bash, Python, Powershell.... all of them languages.
Since we're writing code for our automation, we might as well write sustainable code. I can sum up the entire point like this:
Writing code has
Or, in more interesting terms (and I cannot take credit for this version), write your software as if it will be maintained by a homicidal maniac who knows where you sleep.
Now that we know what to do (reduce maintenance cost), we need some direction on how to do it. The answer, as is most often the case, is simplicity. How do we keep things simple?
I like to borrow a few notes from the Unix Philosophy, specifically 'keep things modular' (Small is Beautiful). Regardless of technology, you'll be modularizing your work, whether they be in functions, recipes, manifests, whatever. Think of these as your atomic work units, where the keyword here is atomic. Your units should do one thing, and they should do it well. If you want to add functionality to a unit, make another unit and have your first one call it.
Why? Why not just add functionality to the existing work units? It's easier, there are fewer units to juggle, and, yes, it takes less time.
Less time?
Less inital time, yes. But we're not working to minimize initial time. We accrue the initial time only once. The maintenance time (i.e. cost), however, we accrue every time we need to update our code. Every. Time. And god save us if we have to refactor our codebase. Forget progress, you'll be doing well just to get back to normal.
This is a quick (albeit contrived) example of my bigger point: Always strive to make things easier for the guy in the future. After all, it just may be you.
Since we're writing code for our automation, we might as well write sustainable code. I can sum up the entire point like this:
Writing code has
- An initial cost. The cost, in time, dollars, or other measurement to write the code and
- A maintenance cost. The cost you incur to keep the code relevant.
Or, in more interesting terms (and I cannot take credit for this version), write your software as if it will be maintained by a homicidal maniac who knows where you sleep.
Now that we know what to do (reduce maintenance cost), we need some direction on how to do it. The answer, as is most often the case, is simplicity. How do we keep things simple?
I like to borrow a few notes from the Unix Philosophy, specifically 'keep things modular' (Small is Beautiful). Regardless of technology, you'll be modularizing your work, whether they be in functions, recipes, manifests, whatever. Think of these as your atomic work units, where the keyword here is atomic. Your units should do one thing, and they should do it well. If you want to add functionality to a unit, make another unit and have your first one call it.
Why? Why not just add functionality to the existing work units? It's easier, there are fewer units to juggle, and, yes, it takes less time.
Less time?
Less inital time, yes. But we're not working to minimize initial time. We accrue the initial time only once. The maintenance time (i.e. cost), however, we accrue every time we need to update our code. Every. Time. And god save us if we have to refactor our codebase. Forget progress, you'll be doing well just to get back to normal.
This is a quick (albeit contrived) example of my bigger point: Always strive to make things easier for the guy in the future. After all, it just may be you.
Saturday, January 12, 2013
Every Job is a Custom Job
No matter how much we standardize, how repeatable or automated our processes are, we will never be able to deliver the exact same product/datacenter/service twice.
This isn't a problem experienced by many other domains (not to such a degree, anyway). I can order a pair of speakers from a manufacturer and they will be the exact same speakers that my neighbor orders. Likewise with most food at the store, even at restaurants. Sure, you can ask them to hold the onions on your burger but that level of customization is not what's experienced by developers/deployers/devops/what-have-you.
The customer will always have some indigenous nuance about their particular system - legacy software/hardware, limited budget, upper management has already decided on a particular product before you arrived - that will preclude you from delivering the *exact* same product every time.
The challenge, then (and what makes the work interesting), is working this reality into your automation processes.
This isn't a problem experienced by many other domains (not to such a degree, anyway). I can order a pair of speakers from a manufacturer and they will be the exact same speakers that my neighbor orders. Likewise with most food at the store, even at restaurants. Sure, you can ask them to hold the onions on your burger but that level of customization is not what's experienced by developers/deployers/devops/what-have-you.
The customer will always have some indigenous nuance about their particular system - legacy software/hardware, limited budget, upper management has already decided on a particular product before you arrived - that will preclude you from delivering the *exact* same product every time.
The challenge, then (and what makes the work interesting), is working this reality into your automation processes.
Tuesday, September 25, 2012
New-Datacenter?
Note: This post is to facilitate conversation on the topic of making a new Windows datacenter. This is far from a howto. If you came here looking for that, you will be disappointed.
In the beginning, there was nothing. Then there was the command line.
The Dream
A one-touch solution to deploy an entire Windows datacenter.
The Way Forward
The immediate answer is automation. Powershell automation, specifically. But where does the Powershell magic run from? Can't run on the bare hypervisors. Okay, so we'll need a bootstrapping Powershell.... server (a BPS?), of sorts. So assume we have a few hypervisors and a single Windows host (not on the hypervisor since that hasn't been configured yet).
We'll also need some binaries. So assume all binaries are also on this BPS. Let's also throw some templates in there, just to get a vCenter started up. This is getting long... for the sake of brevity, let's just say we have a set of distinct scripts that, on their own, will give us all the components we need for our Windows datacenter.
How do we tie them all together?
Devil, something something, Details
This is a bootstrapped environment, so we don't have the luxuries of Active Directory or DFS or anything else that makes life easier. We have a bunch of blank Windows installations. First challenge - we have to get the binaries & scripts to the blank VMs. We could set up a share on our BPS, but our blank VMs don't know how to get to it, and we can't even use a Workflow from our BPS because, with no server certs, WinRM will only use the 'default' configuration, which limits you to a single hop for cred-forwarding.
The only thing I've come up with is to set up a minimal IIS installation and have the blank VMs download the necessary files over HTTP. We can use some simpler 3rd party web servers, but then we'd be introducing non-native products, which only complicates things.
Assuming we can transfer the data (binaries) and the instructions(scripts) to the blank VMs, how do we choreograph the installation of the products that make up our datacenter? I'm imagining something like a Master Powershell Workflow run from the BPS that knows the order of operations.
This is getting complicated. And I'm concerned that it's unnecessarily so.
Externalize Away the Setbacks
And all of this is to say nothing about sustainment. You can create a bunch of stuff. Nice. Now how about configuration management? I don't know either. Maybe this has all been done before and I'm just not aware of it. Sounds like more research...
Tuesday, September 18, 2012
Welcome to Powershell Workflows. Please Follow These Rules
The most powerful feature introduced in Powershell 3.0 is The Workflow, no doubt. However, workflows are not something exclusive to Powershell. In fact, workflows (that is, the Windows Workflow Foundation) have been around since .NET 3.0 (recall that Powershell 3.0 requires .NET 4.0). When you are using workflows in Powershell, you have left the well-known comforts of Powershell. You are now a guest in the Windows Workflow Foundation.
Activites, not Commands
In each workflow you write, the individual lines are no longer commands. They are individual Activities, as per the Windows Workflow vocabulary. As such, they run in their own process. The implications here are great. For the sake of brevity, I'm only covering the cases that have occupied most of my time.
No Module Imports
Import-Module is one of the (many) disallowed cmdlets in workflows. In order to get around this, we have been given the -PSRequiredModule parameter.
Not this:
Import-Module AwesomeSauce
Import-Module RequiredModule
$goods = Get-OutTheGreat
This:
$goods = Get-OutTheGreat -PSRequiredModules 'AwesomeSauce','RequiredModule'
Snap-In Complications
Add-PSSnapin is also disallowed. This is immediately a problem for fans of PowerCLI, as it's written as a Snap-In, not a module. The best workaround I've found (though it's kludgy) is to wrap the necessary work in an InlineScript.
Not this:
Add-PSSnapin vmware.vimautomation.core
Get-VM
This:
InlineScript{
Add-PSSnapin vmware.vimautomation.core
Get-VM
}
No Implied Parameters
When using workflows, you must explicitly spell out each parameter for every cmdlet you use. This can be a bit fun when learning the name of that parameter you've been taking for granted. The new ISE can be helpful with discovering cmdlet parameters.
Not this:
Get-VM BigGiantVM
This:
Get-VM -Name BigGiantVM
Verbose Workflows
One overlooked feature of workflows is the Activity Common Parameters. These are parameters you pass to individual activities. My favorite so far is -DisplayName. With this parameter, you can label the activities in your workflow so they appear more human friendly, such as "Connecting to remote host AwesomeHost" instead of "Workflow: Line 12, Character 8" while executing.
Creativity Under Constraints
I've found myself complaining about these details along the way. But actually, they may be forcing me to write better code. By disallowing the easy (and unsustainable) path, I'm reducing what used to be complex scripts/functions into more atomic, modular functions/workflows. This is the beginning of your own personal framework. I say embrace the changes. Besides, it's not like we have much of a choice.
Activites, not Commands
In each workflow you write, the individual lines are no longer commands. They are individual Activities, as per the Windows Workflow vocabulary. As such, they run in their own process. The implications here are great. For the sake of brevity, I'm only covering the cases that have occupied most of my time.
No Module Imports
Import-Module is one of the (many) disallowed cmdlets in workflows. In order to get around this, we have been given the -PSRequiredModule parameter.
Not this:
Import-Module AwesomeSauce
Import-Module RequiredModule
$goods = Get-OutTheGreat
This:
$goods = Get-OutTheGreat -PSRequiredModules 'AwesomeSauce','RequiredModule'
Snap-In Complications
Add-PSSnapin is also disallowed. This is immediately a problem for fans of PowerCLI, as it's written as a Snap-In, not a module. The best workaround I've found (though it's kludgy) is to wrap the necessary work in an InlineScript.
Not this:
Add-PSSnapin vmware.vimautomation.core
Get-VM
This:
InlineScript{
Add-PSSnapin vmware.vimautomation.core
Get-VM
}
No Implied Parameters
When using workflows, you must explicitly spell out each parameter for every cmdlet you use. This can be a bit fun when learning the name of that parameter you've been taking for granted. The new ISE can be helpful with discovering cmdlet parameters.
Not this:
Get-VM BigGiantVM
This:
Get-VM -Name BigGiantVM
Verbose Workflows
One overlooked feature of workflows is the Activity Common Parameters. These are parameters you pass to individual activities. My favorite so far is -DisplayName. With this parameter, you can label the activities in your workflow so they appear more human friendly, such as "Connecting to remote host AwesomeHost" instead of "Workflow: Line 12, Character 8" while executing.
Creativity Under Constraints
I've found myself complaining about these details along the way. But actually, they may be forcing me to write better code. By disallowing the easy (and unsustainable) path, I'm reducing what used to be complex scripts/functions into more atomic, modular functions/workflows. This is the beginning of your own personal framework. I say embrace the changes. Besides, it's not like we have much of a choice.
Subscribe to:
Posts (Atom)