I found it most helpful to think of monads as being similar to the pipe operator in Unix, but one that has a hook function it calls as it passes results along.
Also, the IO monad is probably the worst to start with; it's one of the most complicated. The Maybe monad is much simpler -- Think of a function that takes a function, f, and a second function, next_f. It calls f and grabs the result: if it's an error, then don't bother calling next_f, just return the error; otherwise, call next_f with the result as its argument. You can chain a bunch of Maybes together, and if any of them return an error, the whole chain immediately passes the error to the end. It's like how || (or) short-circuits in other languages, but much more general.
A similar Countdown monad could start with a time and a list of functions to call, then call them in succession, passing along the results, but immediately aborting and returning an error if the chain has exceeded the specified time to run.
The monad is the chaining construct, but you define what it does with the results it passes along.
Because of Haskell's type classes, it's able to recognize that any construct that supports that kind of chaining behavior (among other things) is an instance of the same general construct, and is therefore able to work with them in a common way. That's surprisingly useful, much like when an object's class is a Class object that supports various interfaces for interacting with the OO system itself (A "meta-object protocol").
Haskell makes a big deal of monads because it needs them to do some things in a functionally pure manner, of course. Still, they shouldn't be considered some completely alien by-PhDs-for-PhDs concept. (I remember Simon Peyton-Jones saying he wishes he'd instead named monads "warm fuzzy things"... :) )
You don't even need Monads to have completely functional IO code. Clean's uniqueness types are another method, but you can actually do it with just plain types if you're dedicated.
Oddly, monads just make things easier. Really.