Monday, November 7, 2011

Build & Deploy Decoupling (and get some tests running in between!)


Pre-preface: We have about 50 heavily linked builds that make up our “core” environment. We’re trying to cut "major" release times from ~3 months to ~3 weeks. Check out the prior blog post for more background information.
---

In a continuous integration/continuous deployment shop, if you decouple the deployments away from the builds you will improve tester productivity, decrease test environment outages throughout the day, find defects earlier, speed up the builds, you are encouraged to invest and rewarded by investing in automated tests and ultimately position yourself for continuous production deployments ala Push Engineering. (scary, but woo!) That’s a lot of claims to make, so let’s get on with it.

Preface - this isn’t a step away from the Continuous Integration or Continuous Deployment methodology, it’s a refinement to keep it from becoming Continuous Interruption or Continuous Regression. 

Productive testers

I’m willing to bet that if you build on every commit, 95% of your test environment stakeholders (development, QA, product owners, etc) that are using the environment in question aren’t looking for that specific fix/feature/revision at that moment. They want it eventually – usually after they are done testing the story they picked up after they sent the first story back to development with a defect.  While they want that fix/feature/etc, they want a Quality change; they want the commit to build successfully so the build isn’t dead on arrival, they want it to pass the unit/system test suite (hopefully you’re at 100% coverage) so there is a high confidence you aren’t regressing existing critical functionality, and they want their test environment to get that revision at an expected time – either scheduled or on demand.  The last thing testers want is to have their valuable time spent setting up a test wasted when the environment is reset on them for a defect fix or change they could not care less about.

Outages

A moderately sized software development unit  – say, two teams adding up to 10 developers with Quality Assurance, Business Analysts, Project Managers, Database Admins and Developer Operations people – can generate a lot of environmental churn on a daily basis. Your busiest environment will probably be your lowest environment;  I frequently see 10 developers able to commit 20-30 revisions every day to the dev branches,  and then those revisions ( once they pass whatever gateway test/merge qualifications necessary)  being selectively merged to the next more-mature environment – generally it divides by 2 or 3 each environment. So your 30 revisions in Dev turns to 10 in the next environment up, then 5, then 2, then maybe you’re up to Prod and it’s hopefully just one build ;) If you clump these deployment cycles into “a” number – without knowing your business or processes, there is no magic number – but let’s assume for a moment we go to four deployments a day in non-production – 9AM-Noon-3PM-6PM, and only when there are changes to push – you’re now limited to 4 deployment interruptions in your lowest environment, to one or two in all other non-prod environments, and it’s on an expected timetable.  From ~50 continuous interruption deployments a day down to ~8 – that’s a slight improvement.
If you have frequent “hot” builds that need to be slammed in as soon as possible, your people environment is probably lacking in software development process maturity. People need to learn the old fashioned way – experience – that there is never going to be a silver bullet fix that solves everything in one build & deploy . You can alleviate the need for “War-room ASAP” builds by working in as close to 100% test coverage percentage as you can afford, level-set the environment stakeholders by explaining how many people/groups are being impacted with a rebuild, communicate the end to end outage length, and put visibility on the rebuild & deployment, each and every one. When the world is watching, that super-important fix can wait 45 minutes until the scheduled, automated deploy happens.  And if you’re in a dynamic place where you have multiple groups working on multiple stories simultaneously, it’s extremely rare for a critical group of people to be blocked & waiting for a deployment.  They may say they are, but in reality they most likely have several other items ready to test, or items marked resolved that they need to sign off on. Don’t get me wrong – you will need to allow for manual deployment pushes – just make sure the button is pushable by the right stakeholders and there is clear visibility on the deployment occurring, why it’s occurring and who pushed the button/authorized it. Listen to the users too – don’t let a pushy manager terrorize everyone by smashing their builds in. You need to listen and push back if need be.

Defects & defect cost

If your deployments are coupled with your CI builds, even with stepped down commits as you go up in environments, that’s a lot of interruptions in each environment. The closer the environment is to Production makes the outages more expensive in the short-term, due to the perceived instability of the higher environments by senior leadership and external clients and the interruptions of client UAT, sales demos, etc.  These outages/interruptions tend to chase the legitimate testers away, and because the lower environments (closest to Development) are being rebuilt most often, you’re training your most valuable testers to stay out and stay away from the environments you actually need them in the most – the sooner you find a defect, the cheaper it is to fix it. When the people who you are making these changes for finally tests a few weeks before release or in an environment  right before Production, having the “you didn’t understand our requirements” card get played is somewhere on a level of fun between playing “Sorry” with your significant other (they show no mercy!) and having the Draw-Four card played on you in Uno, except in a large release  hundreds of thousands of dollars in time & effort are at stake.  That is the long-term cost of low-environment outage and over time will greatly outweigh the short-term cost of your close-to-production environments recycling during the day. Too often we let the “rough” development environments be wild-wild-west, not realizing it’s your most valuable environment to test in.

Speeding up the build

If you have any tests baked into the build, you know they can take just a few seconds to several minutes, depending where they sit between pre-canned, self-contained nunit tests or if you are setting up data and comitting transactions. When you have high percentage levels of test coverage your tests can often exceed the compile times of the build itself; there’s nothing wrong with that. This is where you can successfully argue for more build server horsepower. The time spent on deployments is often out of your hands; you are constrained by network speeds, SAN or local disk speeds, and hard-timed delays waiting for services to stop and restart. The deployment tasks in your build quickly add up; even if deployment tasks are only 10% of your build time, it’s increasing your environment outage by 10%. If you asked QA “Hey, would it be worth trimming 10% off a complete environment rebuild?” they would jump on it. Time is money; a 10% decrease in time is literally dollars – often it’s many dollars added up over dozens of builds a day – being saved.  That’s something senior leadership can definitely understand and get behind.

Test coverage

Not slamming the build hot into the environment provides incentive to build up the automated test coverage percentage. Nobody wants a DOA build, and if they are now getting them 4 times a day max (let’s say) they want them to count. Assuming a 9AM/Noon/3PM/6PM deployment cycle, developers at 10AM might have an easier time making sure their change is covered in unit tests when they know the build won’t be deployed until Noon. I have seen project managers and directors literally standing behind a developer waiting for them to commit because “everyone is blocked!” or “The client is waiting to test!”. That’s not a conducive work environment, nor one anyone would want to work in. Taking the deployments out of the build also gives you the ability to put in longer, slower, deeper automated tests and post-build reports, like test coverage reports, in each and every build. It sounds counterintuitive to speed up the build by removing the deploy stage and then slow it down with more automated tests and post-compile reports, but those tests and reports will increase quality, which will decrease the amount of time spent in the whole “code it-throw it over the wall-have it thrown back-fix and throw it over the wall again” cycle.

Continuous Production Deployments

That’s where I am trying to get to, and that’s why I’m removing deployments from our build cycle. I need two major results out of this – our automated tests need to inc rease in quality & importance, and the decoupled deployment stage - broken out into a separate stage – can be perfected in the lower environments and then used in Production. The only reason I haven’t strung together our piecemeal deployment scripts from each build and run them against Production already is we don’t have good automated tests running (so we can’t automatically trust the build), and we don’t always have good visibility on what needs to be released due to how much shared code we use. Yanking deployments out and relying on and improving our test coverage and perfecting our deployment stage gives me half of this; my next post will be on how I’m going to solve the release build of materials question.

-Kelly Schoenhofen

Sunday, October 30, 2011

3AM Deployment? Why not?

Tip o'the hat to Bryan Gertonson for sending me this from Brian Crescimanno's blog - Why are you still deploying overnight?.

It's a good article; I can see alot of overlap between Brian Crescimanno's article and what Facebook is actually doing on a daily basis (see the Push Engineering video). Unfortunately, I'm going to bet that for most of us the article comes across as a white ivory tower/pie-in-the-sky wishes-were-fishes manifesto. It doesn't mean Brian doesn't hit alot of true notes, but pragmatically most of us are stuck doing midnight deployments to avoid customer impact. For me, midnight deployments have gotten really old, and as we expand outside North America our global market doesn't appreciate middle-of-the-day downtime for them. I want to change this.

A project manager where I work passed around the Push Engineering video to management, and the positive end-result that Facebook is reaping really resonated with them. But the problem with silver bullets is what makes it a werewolf killer for one company doesn't slow down the vampires at another company. One part in particular was FB's rich gatekeeper system of enabling features gradually to the customer base; that feature would be useful to us but it would be a huge effort to implement for a limited payback in our specific e-commerce niche. It may have resonated with our management, but it didn't really resonate with the engineers.

In my opinion, the hardest challenge that Facebook faced (no pun intended) and overcame is the divorce of database changes & code (backwards) compatibility. They have achieved one of the holy grails of release engineering by successfully decoupling the two; database changes and code changes are entirely independent of each other. It gives them a tremendous amount of flexibility and makes their top-to-bottom Push Engineering concept a reality.

So after watching the Push Engineering video, management has challenged us (with some funding) to do some of the same things. Our stated goal is get our software release cycle from 3-4 months down to 3-4 weeks. Our current process & technology has been an evolution for the last few years; we last made major changes to our develop-build-deploy process/platform/methodology about 2 years ago and we've been coasting on it since then, reaping the rewards and writing, testing and releasing software as fast as we can.

So while I don't think we're going to achieve database & code separation any time soon, I think we can pull from the Push Engineering concept some of the achievable bits that best apply to how we work and  accomplish a few notable things. A handful of us leads sat down and sketched out what it would take (at a high level) to accomplish this.

Here's a few of "my" issues that I intend on addressing. 
  1. Our "core" book of sites and services comprise 50 linked builds in a very complex parent-child relationship with a handful of pseudo-circular references thrown in for fun. A full core rebuild on a build server cluster made from virtual machines takes at least 60 long minutes. Big changes or small changes, it takes 60 minutes to see them.  
  2. For those 60 minutes, the environment is intermittently unavailable, which testers just love. There is always something unavailable during the rebuild process; the environment may look like it's up but often the core build cycle is single-threaded on building a back-end process or service that is largely transparent to end-users, so just when testers think the environment is stable, whammo, they are kicked out. While testers have the ability to check the build status, it can be difficult to spot pending builds on a build controller hosting 250+ automated builds. I like to say tell them we're a CI shop - it just stands for Continuous Interruptions ;)  They don't laugh :(
  3. Sorta-falsely broken builds -  builds are often "broken" because after an atomic commit when a child of a child needs a change from a parent of a parent build; because each build is on a separate timer to check for source control changes, and I can only define a single layer of parent and child relationships, often a grandchild build will try to build before a needed grandparent change and the build goes red.
  4.  We use tremendous number of post-build events at the project level to keep developers working locally; these events can be fragile and they copy unnecessary binaries around constantly. We need them because developers build out of Visual Studio, not an msbuild script, so none of the msbuild scripted events to push shared dll's into a common library folder occur. Why do we use library folders for these common build artifacts? Since the build server software isolates each build away from all other source to generate a clean build that will stand up to DoD-level regulations, you can't reference other projects outside your top level solution. You have to use static references and maintain a huge library of binary artifacts from your codebase. 
  5. Because we go 3-4 months between major releases, when we do a major release code & db drop into Production, we have days of intense firefighting, then a couple weeks of medium firefighting, then a few more weeks of putting our lingering brushfires and smoldering stumps. Then we gear up for our next major release ;)
  6. Our releases also end up being so large, they take 4 hours to deploy and 8 more hours to validate.
  7. Because we use all static references and no project references (outside the solution), I've seen developers keep eight visual studio's open simultaneously to make a change in one webservice; we don't have the funding to make hot-rod workstations, so we're burning expensive developer time having them wait on their machines. There's only so much performance you can get out of a $600 PC from Dell or HP even after you add another $400 of ram and a $50 video card.
  8. We don't have unit tests running consistently due to the evolution of our build system. The developers have written more than 1000 unit tests, but the automated build server is only running 1/4 of them reliably.
  9. Our test coverage tools are manual run only, they don't really fit in our current build structure. 
So to sum it up: we have a long rebuild cycle and the environment is down virtually the entire time, Continuous Integration really means Continuous Interruptions, the build frequently breaks because of the actual build order, we are spending alot of our time copying around large binaries and storing them in source control, we release too infrequently and our major releases take 12 hours to deploy, developers have to run several visual studios and constantly have to trigger complex build orders to get changes to show up locally and their PC's aren't equipped to do any of this, only 1/4 of our unit tests get run outside the developer's workstations and test coverage reports aren't being generated.

There's more, but let's stop with these major ones. We have alot of challenges! My next post after this will be my plans for the next 3 months to resolve them.

Think 1 stone, 20 birds. Unfortunately, I have about 100 birds to take out. But that's only 5 stones if I throw them just right!

-Kelly

Wednesday, October 12, 2011

Facebook "Push" system

Chuck Rossi, a head engineer at Facebook, gives a rough description of FB's build and release process as part of the developer onboarding session. It's about 52 minutes long, and well worth it.

Saturday, January 22, 2011

Anatomy Of A Continuous Integration System Part Two

No matter what generation you grew up in - Mary Poppins tried to entice you with a spoon full of sugar, Barney used out-of-copyright melodies and hypnotising lyrics, and Dora just remixed what Barney came up with - but you have to clean up, and it doesn't have to be painful.

Most people don't get around to writing cleanup routines until long after everything is setup and running (if at all!), but you, the Wise Reader, are going to start with a cleanup routine. Waiting until you have even a basic build compiling before you start cleaning up makes little sense. If you're going to fail, fail early and fail fast. You don't want an accidentally committed binary file from a developer pre-loaded into a project's object folder giving you a false sense of build success, only to have QA report very odd and hard to reproduce errors. Hours or days later you finally track it down to an artifact that was inadvertently added into your source control system from the developer's PC.

While there are lots of ways to limit your exposure from a developer (or wherever) accidentally adding and committing artifacts you don't want or need into source control (and it's fine to spend a little time implementing walls and filters) they are pretty easily bypassed. A lot of times they have to be set at the project folder level for each and every project, so it's ripe for getting forgotten to be added in the first place when the project is first setup (because the developer isn't going to remember to do it), or forgotten in the migration of code from branch to another, or it's just lost when someone commits clearing the properties of the parent folder. It also doesn't address rebuild scenarios on your Continuous Integration (CI) implementation. The word "Continuous" in CI literally says each build isn't a one-time event, so every ounce of robustness you can put into your build strategy & implementation, it's going to pay off quickly. While most CI systems have the option of a clean build each time, I've never seen anyone utilize that option. It adds alot of time to your builds - especially on your flagship product - when you build servers have to pull it down fresh each and every time. And if you're a virtualized shop, all that I/O on your build server farm is going to get you the stink-eye from your infrastructure engineers when the SAN starts to get bogged down.

To avoid this, go for the simplest routines. I remove specific folders and do a top of the project down to the furthest corners cleanup.
Here's a sample cleanup target block.
<Target Name="MasterClean">
  <Exec Command="BuildCleanup.cmd"/>
  <RemoveDir Directories="
   %(ProjectList.RelativeDir)obj;
   %(ProjectList.RelativeDir)bin" />
 </Target>
BuildCleanup.cmd is a script that does a recursive delete of specific filetypes mere moments before the compile starts. I love simple - here's the entire contents of my basic BuildCleanUp.cmd -
@echo off
del *.dll /s
del *.pdb /s
del *.cache /s
del *.exe /s
call CompanyName-LibraryUpdate.cmd
exit 0
It does three things:
  1. Delete the bits you absolutely don't want (dll/pdb/cache/exe)
  2. Makes sure your local library of precompiled/3rd party software is up to date.
  3. Exit with a return code of 0 so nothing calling it confuses a successful return code with an error level. 
The other piece I try to accomplish in my cleanup blocks is to remove specific subdirectories that "need" removing (such as obj and bin folders) if it's a quick hit. Some solutions call 12 project files and scripting out a removal of each project's obj & bin folder has a low return rate for your effort. At that point, just rely on your cleanup cmd to remove the bits you absolutely need to get rid of.
Side note - your starting directory by default in an MSBuild script is the location of the .build file, so you don't even need to feed BuildCleanUp.cmd any arguments or pre-load it with fixed directories. It's going to execute at the level of your .build file, which is where you are building out of. You don't need to worry about parent paths because you're not calling any other projects higher than your current level, so a simple script that cleans downhill from your starting point is just what the doctor ordered.

This is really good stuff - you want to make each subsequent build regenerate all local code from scratch so that there's no doubt in QA's mind that a new build of XYZ is going to have exactly what the build notes say it has. Letting a dll from a prior build/different environment creep unintended into a compile is literally horrifying to me. QA's tests start showing odd results, people collectively spend a tremendous amount of time trying to reproduce and understand the issue, and when they track it down to an old dll that is getting carried along each build, I feel like I have personally wasted everyone's time, energy, though processes, bits of their lives, etc, because I could have prevented it if I had been a little more careful or a little more thorough.

You can do this. If you can do this consistently, eventually development, QA and project management/BA's won't jump to the build being the issue when a defect that was reported as fixed isn't showing up as fixed (or with any change in its behavior) in the next deployment it was reportedly fixed in. They will start to believe the builds are rock solid because you've showed them they are. (or at least, they are as good as the input going into them - GIGO)

As a bonus, put all these utility scripts in a folder on your build server - I use C:\BuildTools. Then add C:\BuildTools to the system path and reboot the server. From then on, all builds will be able to call any .cmd file in C:\BuildTools without specifying the path or having to make multiple copies of your cmd files and putting them in the root of all your projects. Putting that single folder with some simple cleanup & deployment assisting scripts is part of the short checklist we have for making a Production build server.

We'll leave the clean-up block behind us for abit, but it will come back for a brief howdy-do when we go over automated deployments.

-Kelly Schoenhofen

Wednesday, December 1, 2010

.Net 3.5 WCF Application + .Net 4.0 Conversion = Missing Config Sections?

As we convert .Net 3.5 projects to .Net 4.0, I had a couple really mysterious issues where WCF Windows Services or command line (console) applications quit working on some servers, and only some environments. Eventually they would get fixed but I hadn't been able to pin down why they quit working in the first place, or what I did to fix them. 


Example: we use MSEntLib logging in this particular WCF Windows Service. The project was converted to .Net 4.0, committed to source control, the continuous integration build server detected the changes, built the project and deployed it to the first test environment. Here's the error it would throw when the build server's deployment routine started the windows service after deployment:


Service cannot be started. System.Configuration.ConfigurationErrorsException: The configuration section for Logging cannot be found in the configuration source.
   at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.EnterpriseLibraryFactory.BuildUp[T](IReadWriteLocator locator, ILifetimeContainer lifetimeContainer, IConfigurationSource configurationSource)
   at Microsoft.Practices.EnterpriseLibrary.Logging.Logger.get_Writer()
   at Microsoft.Practices.EnterpriseLibrary.Logging.Logger.Write(Object message, ICollection`1 categories, Int32 priority, Int32 eventId, TraceEventType severity, String title, IDictionary`2 properties)
   at Microsoft.Practices.EnterpriseLibrary.Logging.Logger.Write(Object message)
   at <snip> - that's enough, you get the picture. 

I looked at the config (“{AppName}.config”), and the logging section was in there. Restart the service, same error. Look for XML errors in the config, find none, mess with it a little, start the service, same error. Rinse and repeat until you've torn it apart so much that when you put it back together it works and you're like "Whuck?" and then you start drinking on the job :(

I finally figured it out - I'm not 100% sure this is accurate, but the best I can figure out is pre-.Net 4.0 - say 2.0-3.5 - on a WCF Windows Service at least and probably for all .net command line (console) applications you could get away with the config file being “{AppName}.config” even though the book says “{AppName}.exe.config”. When we straight converted to .Net 4.0 the non-exe variant became illegal, you have to use  “{AppName}.exe.config” or it won't go. 

-Kelly Schoenhofen


Sunday, November 28, 2010

Anatomy Of A Continuous Integration System Part One

I occasionally give my eleven year old a particular piece of advice - "keep your eye on the prize" - and that's a good way to approach continuous integration. The architecture of a well-functioning msbuild script consists of milestones (significant events) tied together in a linear fashion with a clearly defined "prize" result. That's singular. If you find you need something more complex, you should be cutting your builds up into smaller builds and setting up logical relationships between them.
As I said in part zero I wanted to stay away from philosophy, but I want to talk about that last sentence a little more. Trying to address multiple needed outcomes/deliverables in a single container approaches a worst practice. You will need to check for and track multiple fatal failure conditions and conversely, multiple success conditions, and you're going to fail trying. It's not worth it. You can't effectively serve two masters at the same time - listen to what Yoda said in the movies. A successful msbuild has just one required output, everything else is frosting. Take your uber-complex build monster script, break down the requirements until you have a list of the prizes you need and silo each one into its own build. Capisce?

Back to our build script. Remember wikipedia's definition of CI from part zero of this series?
"small pieces of effort, applied frequently" 
Identify your prize, break it down to the smallest piece of discrete effort, and write it so you can apply it frequently. That's the magic formula. In fact, 99% of the build scripts I write can be boiled down to the same skeleton over and over.  Cleanup, build (clean) and deploy (to Test). Step 1, Step 2 and Step 3. Everything else in your build is window dressing and any failure of those three steps is fatal.
If other factors come into play, such as multiple environmental targets in a single build (deploy to Test, QA, Staging, offsite tape and burn to a DVD gold master), while I may pragmatically add an extra piece or two  at first, it doesn't take very much to push me into cutting one build into multiple builds and chaining them together in some fashion.

Here's a molecular build script - under normal circumstances you don't want to break it down any further than this, and conversely you don't want to get too much more complicated than this. If you're deep in a thorny issue, you happen to step back and take a look at your creation and you don't recognize your msbuild script having a direct, familial relationship with the Clean, Compile, Deploy pseudo-code skeleton below, then you probably made a left turn at Albuquerque along the way and some refactoring needs to occur, perhaps along the lines of creating multiple builds out of your problem build.

 Starter stub skeleton of 99% of my build scripts:


<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="Publish" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="MasterClean">
<Cleanup Actions/>
</Target>

<Target Name="MasterBuild" DependsOnTargets="MasterClean">
<MSBuild (compile) Actions/>
</Target>

<Target Name="Publish" DependsOnTargets="MasterBuild">
<Publish Actions/>
</Target>
</Project>

For troubleshooting purposes, I'll leave three manual cmd files committed into source control in the root of each project; one to kick off the cleanup, one to kick off the compile (which has a prereq of cleanup) and one to kick off publish (which has a prereq of compile, which has a prereq of cleanup - you get the idea). Here's the meat of the Publish.cmd I use in a .Net 4.0 project for instance -

%SYSTEMROOT%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe master.build %*  /t:Publish %*

Very occasionally developers get some use out of them, but they are really for me when I'm initially setting up a build and troubleshooting build issues, build server/CI system issues or when I'm doing maintenance on a build.

-Kelly Schoenhofen

Saturday, November 27, 2010

Anatomy Of A Continuous Integration System Part Zero

Wikipedia puts the definition of software engineering's Continuous Integration (CI) simply as - "small pieces of effort, applied frequently", and that's pretty accurate. The various slices of what is packaged up these days as continuous integration  have been around for decades and re-invented every few years under various names and methodologies, but it's been a semi-recent branding effort to put these particular best practices together under the common name of Continuous Integration. 
Rather than be a flash-in-the-pan methodology, CI has become the underlying platform - the bedrock - that all the trendy methodologies have been built on for the last 10 years including Extreme Programming (XP), Agile, RUP, Scrum, etc. If your methodology isn't Cowboy Coding - and I mean the classic definition of cowboy coding - a lone developer or a handful of developers acting alone practicing anarchy for argument's sake: "your rules stifle me!" - I bet your methodology has most or all of the continuous integration basics at the bottom of it.

The industry book definition of CI has it defined as a handful of best practices - 
  • Using a source code repository system
  • Everyone gets latest (code) on a frequent basis
  • Frequent commits
  • Every source code commit generates a build*
  • Build automation
  • Build fast (and fail fast!)
  • Build results should be public
  • Automated unit tests in the build
  • Test environment should mimic Production environment
* To many people, this is all continuous integration is. It's important, and achieving that standard (every commit being built) means you practically have to be doing half of the other CI best practices. It's a good milestone to shoot for when you are converting to CI or building a CI system from scratch, but it's not the destination. Heck, I don't think achieving every facet and aspect of CI means you've reached your destination, CI is just a means - a tool - to make your software production more efficient and productive with the end result of higher quality at every output. 

All of these best practices can be compared to and validated against the boiled-down idea of "a small iteration - a small complete effort - increases the quality of a larger effort". If these pieces of effort don't pay huge dividends by the time your project is hitting its stride, let alone its delivery date, then simply remove that practice from your playbook. CI shouldn't be stifling or handcuffing the developer - everything about it is about improving the quality of the software being engineered, this is not about a Configuration Manager or System Administrator power tripping. 

For the next few blog posts, I want to just write up some basic articles on constructing & running a CI environment for a work group or entire shop developing in .Net but isn't using TFS* (Team Foundations System), and I'll be mainly focusing on the art of the msbuild file and how your entire CI architecture can and should be seen in the way you craft your msbuild scripts. 

* Maybe you don't use TFS because you're the lone group in your company doing .Net, and you can't get the budget for a TFS farm, or maybe you are required to use source control system XYZ for regulatory purposes and TFS isn't going to happen for purely technical reasons. 

None of the other posts are going to be as wordy or philosophical as this one - I promise they will be short and straight to the point. They are also all works in progress - they are the best practices I have today - I not only can't guarantee they won't evolve in a year from now, I certainly hope they will.

-Kelly Schoenhofen