1. [Comment removed by author]

    1. 9

      I’ve used Ansible and Chef professionally to manage SoA’s. The following is my opinion – professional sure, but opinion definitely. (EDIT-after-posting): It’s also a wall of text, sorry for that.

      Ansible has a lot of things going for it, the language is simpler, there is less imposed structure, and a very good separation between things that are local and remote. It’s also very lightweight – you only need something installed on your local machine, everything else is done over ssh.

      It also has some issues – three big ones. First, there is no standard tool for managing external, versioned, private role dependencies with transitively closed dependency chasing. That is to say, there does not exist (or does not seem to exist) an equivalent of berkshelf as for Chef. There is librarian-ansible, but it doesn’t (or I cannot figure out how to make it do) transitive dependency resolution (i.e., downloading dependencies of a role, and their dependencies, and so on). Further ansible-galaxy is for open-source roles only, and though it does seem to download dependencies, it cannot be used for anything other than open-source roles, and doesn’t have (near as I can tell) any sort of manifest file (like berkshelf’s Berksfile or librarian-ansible’s Ansiblefile). Another major issue is that script variables don’t work in particularly useful way. Though Ansible supports nested hierarchies of variables – which are useful for organizing variables in a simple and DRY way – the defaults functionality isn’t properly merged with override variables. Unlike Chef’s notion of different ‘levels’ of variables, which allow your deployer-facing scripts to specify only what they need, Ansible’s defaults really are better termed documentation-of-defaults, since any overrides from the playbook simply replace rather than override, the defaults.

      However, by far, the biggest problem with ansible is that the versioning practices are the worst I’ve seen anywhere. They don’t follow semver, it seems, so I’ll upgrade my ansible from 1.6.5->1.6.6 and find that someone made some part of some module undefaulted, and now I have to go chase down a bunch of problems because public API changed in my patch update. Whats worse, is that often new features seem to be willy-nilly spinkled amongst patch and minor version updates, and breaking changes happen freely. If you’re going to use ansible, pick a version and stick with it, update it on a scheduled basis rather than assuming that patch versions are safe.

      This latter point contrasts with Chef, which is much more stable in it’s API exposure in my experience. Chef also doesn’t have the variable resolution problem or the no-standard-package manager (at least of the more recent releases, Berkshelf is baked in). The language is also more rich (what with being just ruby, you can build up some good abstractions easily if you need to), and in particular the presence of LWRP’s make it easy to present a very simple API for often complex parts of your infrastructure. Ansible loses there, too, though building custom modules is possible, it’s not nearly as simple as with Chef and LWRPs, and distributing them is also a bit more complicated.

      The major downsides to Chef are the need for the remote to have chef (and thus ruby and so on) installed. For ruby shops this usually isn’t an imposition, and the chef-dk package they now distribute is much easier to set up than the old omnibus packages (even on distros they don’t explicitly support, I maintain some vagrant boxes which run Arch and chef-dk, and whereas the old omnibus-based approach routinely broke, the chef-dk approach is not only simpler to install, but also much more reliable). It’s also a ‘heavier’ tool in terms of the structure it imposes and the sheer amount of stuff to deal with when creating a new recipe. The border between provisioner-action and provisioned-action is also not well established (everything basically happens on the remote, and I don’t know of any particular equivalent to Ansible’s “Local action” concept, which is very useful for automating, for instance, registration with loadbalancers/message queues/etc as well as simply being able to spin up resources or lock resources (to solve the inevitable “which machine migrates the database” problem).

      Ultimately I think that – given the choice, I’d still go for chef. The problems with chef are primarily in features it outright lacks (simplicity and local-action, in particular), rather than in features it outright gets wrong (like variable management and the lack of LWRPs / easy-to-build-custom-modules). Chef is also a lot more self-contained than Ansible, it seems like, in particular ansible is, at it’s core, a collection of utilities which operate on JSON passed through STDIN and STDOUT. That’s nice an unixy, but it does end up meaning that different modules vary in behavior subtly, and it also makes for less cohesion between various modules. For instance, the file, copy, and template modules all do related, but different things, in reality, they should probably just be one (or maybe two) modules. copy should be merged with template, and file should be renamed to filesystem or something like. Each take arguments for specifying a source, some for specifying a destination, but the naming is inconsistent between them, file uses path, copy uses src, etc. Most of the modules now alias each other’s argument names, but this actually makes things worse, since sometimes file will use src and other places file will use path and so on. I’m a ruby guy, but when it comes to this, I’m with PEP 8, one-way-to-do-it is better here, at least when it comes to naming.

      That’s not to say Ansible can’t be effectively used, rather that it feels still like a young project, not all the pieces are in place, and those that are are askew, waiting to be nudged correct by the other puzzle pieces yet to be placed. There is a lot of active development on various tools, and the overall complexity story will (I believe) someday be less than Chef; I didn’t get into Chef Solo vs. Chef Server, but it’s already far less complex to manage than that.

      For small projects, Ansible is a fine choice, if you’re aware of the caveats. If you’re managing something more, or if you want to provide a better maintenance story for a large cadre of scripts, then I think Chef is probably the way to go, it’s much more mature, and ultimately will serve you better as the number of recipes that need maintaining grows. For my part, running with ansible now on a project that is in the beginning stages of migrating to an SoA, I’m left with concerns about how well things will scale when we get past the ‘more than five services’ mark. Right now the strategy we’ve gone with is to limit the architecture of those services to something generic enough that it can be managed by a single script, but as we look to incorporate other stacks to suit our requirements, I suspect that such limitations might loosen, and the result may be a lot of work.

      Ask me in a year, I’ll let you know which system sucked harder. :)

      1. 7

        We mostly use Chef but are slowly moving to Ansible for the following reasons:

        • Chef is very complicated. @jfredett talks about the variable layering being a feature of Chef but we have found it mostly a complication. A variable can get its value from 15 possible locations depending on where you are in the execution of a your chef run.

        • There is no reasonable way to test chef. Ansible isn’t necessarily better here, but given that Ansible is significantly simpler it’s less of a problem. There have been multiple situations where you just have to run in production and cross your fingers.

        • The Chef language is far too powerful. It has all the power of Ruby. Ansible really just executes scripts defines in a YAML fine which is very simple. Limitations are good. The chef semantics are also rather terrible.

        • Chef contains no concept of orchestrating machines. We use Ansible to push new code out to a few new machines, monitor them, then move on if it looks good after sometime. One can do smart things like take a machine out of the load balancer as it’s being upgraded. Chef only has the concept of a single machine and you cannot do these ‘staging things’.

        If I were starting from scratch I might not use Ansible, but I would definitely not use Chef. In our experience Chef has generally resulted in spending 1.5 - 2x longer just to work through issues in chef. We’ve also had scaling issues and availability issues with the chef servers. Chef is one of the most complicated components most companies will deploy which I think is fundamentally incorrect.

        1. 2

          FWIW, your complaints about Chef basically mirror my issues with Puppet: it’s impossible to test, difficult to work with, the manifest language is terrible, and there’s no way to build a workflow other than the One True Blessed Puppet Workflow.

          I haven’t used Ansible, so I can’t directly comment on it, but my experience with Puppet definitely makes me lean towards simpler, more framework-like configuration management software like Ansible or Salt.

          1. 1

            I’ll grant that Chef is quite complicated, but I think Ansible is too far in the direction of simplicity-for-its-own-sake. Variable management is a big deal with any nontrivially large infrastructure, so (IMO) it’s better to have something that’s more complicated and feature-rich, then less complicated and lacking necessary features. Obviously, YMMV. It’s definitely something that you have to weigh as a consideration as a team, and it’s totally reasonable to pass on Chef because of the complication factor. Same argument for the config language. Ansible is nice in one sense, since the language is so simple, but in another, that disallows easy implementation of some of the nice features of chef – like LWRPs.

            As far as your final point, that is – honestly – the one major thing that Chef and Puppet both don’t have and desperately need. I have no idea why the Chef folks haven’t incorporated something similar. It seems reasonable to just add a ‘local’ block for recipe execution, but they haven’t. What this has meant practically for me in the past is that rather than having the provisioning tool do orchestration, I’ve had either Cloud Formation Templates do the work, or I’ve opted for the packer.io approach that Mitchell Hashimoto (of Vagrant, Packer.io, and Serf) recommended in a talk last year (I don’t have the link handy, but the title topic was something like “Vagrant, Packer, and Serf for Devops” or something) – the idea basically being that you use packer as a tool to ‘compile’ your application into a VM image, that image is uploaded to your cloud (or built directly there, if packer supports it) and then coordinated using Serf or some other equivalent tool to cause that new VM to become part of the collective and be organized appropriately. The benefit of this is that you can manage relatively large infrastructures with something like cloudformation just bringing up the machine image, then using another tool to trigger the infrastructure change, but it’s still a lot of work and there isn’t a very good way to simplify that process beyond the ‘just use the bare APIs’ approach.

            All told, I think the most important thing to note is that devops tooling – whether ansible, puppet, chef, saltstack or whatever – is really a team-based decision. If your team is okay paying the price for the simplicity that Ansible offers, and you dislike the weird puppet language and complication of Chef, then by all means, Ansible away. There is no silver bullet in devops – I suspect, like most things, there will never be one. The choice is ‘what is right for my team’ – not ‘what is right’.

            I’m with you on testing, but it’s definitely a problem everywhere. I have a few scripts that automate some basic testing, but it’s all homegrown. I’m displeased with some of the ‘devops-spec’ frameworks I’ve tried, they usually end up just being a separate, equivalent implementation in a lot of ways (if you go to any level of detail). My approach recently has been to have a shell script which spot checks the barest behavior I expect (i.e., test to see if this machine is accessible over this port, make sure this service is running on it, etc. A healthy combination of ping, curl, and elbow grease), then just rely on a good QA cycle to ferret out the bugs. I’m not sure it can really get much better than that.

          2. 3

            This is entirely subjective, but I played with Chef for a while. It seemed powerful, but it was just a lot more than I needed, and a lot more complicated. I didn’t care for the DSL, either.

            I kept looking and eventually found Ansible. It’s a lot more simple, I feel that the documentation is better, and even our non-technical employees can follow along with the playbooks when they have to.

          1. 3

            So it’s still flat. Flat keyboards drive me nuts.

            1. 5

              Well yeah, the whole point is that it’s intended to be complementary to a Kinesis Advantage or a tented ErgoDox rather than used as a daily driver. This is meant to be used in situations where you would otherwise be falling back to a terrible internal laptop keyboard.

              1. 2

                While I wish that I could pack my Kinesis up and take it with me everywhere, it sure is a monster as far as size/weight. If I ever get the time/will, I’ll have to play with an ErgoDox. It looks like a fun project, at the very least.

                Thanks for a peek inside the process!

            1. 2

              That’s great, but what if your device is destroyed or lost? What if I want to do some collaborative editing on something with my SO?

              Unhosted web apps also lack a good place to store valuable user data, and servers are good for that. So apart from brokering our connections with the outside world, the server can also function as cloud storage for unhosted web apps. This makes sure your data is safe if your device is broken or lost, and also lets you easily synchronize data between multiple devices.

              Oh… so you mean we’re going to send data to a server… That seems to contradict this statement on the front page:

              Also known as “serverless”, “client-side”, or “static” web apps, unhosted web apps do not send your user data to their server.

              Running 100% client-side apps can be useful for certain usage cases, but this website seems to be more zealous than helpful. It’s also light on details and contradicts itself.

              1. 3

                I’m Greg Taylor, one of the Pathwright co-founders.

                I spend most of my time working with Python and Linux. I like trying new things, and love to tinker. Sites like Lobsters are a valuable source of discussion and new shinies for me.

                1. 1

                  Quick note: While I tagged this web, Dart can be used to write CLIs and daemons (like Node). It seems to be primarily used for web applications right now, though.

                  1. 2

                    Error handling in go is really noisy and verbose. err checking everywhere. It’s similar to Java and checking for nulls all the time. I really don’t like this style. I don’t know why go and Java have constant error checking where as in my JavaScript or Python it’s never much of an issue.

                    1. 12

                      Feels natural, but I come from a C background.

                      1. 13

                        I come from a try catch background in java and python and I welcome the error handling. I’ve been ignoring errors for far too long. It’s time for me to step up and be a responsible developer.

                        1. 1

                          I would argue that a language where exceptions are thrown is a much safer environment to work with than one where exceptions don’t exist: The thing is. Yes. We all should be responsible developers, and yet we all make mistakes.

                          Yes. An uncaught exception will cause your program to terminate, but that’s not a bad thing. An unhandled exception is a spot where you screwed up and more often than not, your program is now in an inconsistent state and code that runs in the future and depends on the operation that just failed might also fail in inconsistent ways.

                          When your program terminates, then you know for sure that no further damage will be done.

                          I like that safety net. I much rather crash than destroy even more stuff as a consequence of missing checking for a possible error condition.

                          Exceptions also help a lot in keeping different components of a bigger application separate as you don’t have to manually propagate errors back up the stack. And when you have a good semantic hierarchy of exception classes, you can handle a whole lot of error conditions in one fell swoop.

                          In the web application I’m working for, I can throw a semantically named NotFoundException, no matter where in the framework it happens and I can be sure that this will eventually be turned into a 404 error for the client. No matter how deep down the stack I am: My method there and then failed to find something? Throw a NotFoundException and it will be dealt with.

                          Especially in the cases where I really can’t do anything but show an error page, I don’t have to add error checking all the way up the stack from where I am. Error handling is constrained to two places: Where the error happens and way up there in the request handler where it’s turned into a nice error page (and a HTTP error). All the pieces in between don’t need to care and thus read much nicer.

                          I think that’s very convenient and I really fail to see how this can be bad design.

                          1. 1

                            It’s actually pretty difficult to accidentally ignore an error condition in Go. The compiler throws out a lot of complaints. You can suppress the errors, but you have to pretty explicit about it.

                            But yeah, exceptions actually would be pretty nice (as long as you are forced to explicitly catch or throw them, as in Java). I think Go uses error codes instead because it encourages you to handle errors at the source instead of waiting for it to propagate out. In my code, I usually just do something like

                             err := functionThatCouldError()
                             if err != nil {
                                 panic(err);
                             }
                            

                            This has basically the same effect as an exception in Java or Python. The program exits immediately with a nice stack trace. Although I guess this does require me to type 3 extra lines.

                      2. 7

                        Because Go doesn’t have JavaScript’s ability to attempt to coerce things into whatever-the-hell-JavaScript-feels-like-doing-right-now, or either of their dynamic type system where things can be added to other things whenever you feel like it.

                        Python and JavaScript both have exceptions, and using try/catch when you’re running something that could do less than wonderful things is a good idea. Consider a case where you’re getting input from across the network, and you want to make sure something’s actually an integer. In Python you can’t just run type(variable) is int, because it came to you as a string or bytes. You need to cast it, but casting can cause a ValueError.

                        Having exceptions bubble up and kill your program is typically a bad idea. You’d want to handle that, and the way to do it, in both JavaScript and Python is using a try/catch block.

                        Go’s use of err everywhere is used instead of try/catch blocks. It makes you deal with errors when they happen, instead of letting them bubble up and be handled later. I think that’s nice. It’s much more explicit than a block at the end of your code that says “some sort of error could happen, and if it does, lets try something”.

                        1. 4

                          Yea, I definitely like the error handling better than I thought I would when I first started learning Go.

                        2. 4

                          Are you saying that handling any possible (or probable) error is a bad thing?

                          Also, last I checked Java’s error trapping was exception-based.

                          1. 4

                            Because of Java’s “checked exception handling”, which I think we can probably agree at this point was a failed experiment, you end up having to explicitly wrap exceptions at the point where you call an API that generates them. At the extreme, you have things like this (testData is a string, and this is in a test case):

                                InputStream in;
                                try {
                                    in = new ByteArrayInputStream(testData.getBytes("UTF-8"));
                                } catch (UnsupportedEncodingException e) {
                                    throw new RuntimeException(e);
                                }
                            
                                for (String line : new LineIterator(in)) {
                                    p.dispatchLine(line);
                                }
                            

                            If it weren’t for the checked exceptions, in wouldn’t even need to be a variable; it could be inside the for-each thing. bjorn could have been saying, and correctly in my experience, that Golang’s multiple-value returns for errors result in you having to write similarly sequential code.

                            However, I don’t think this is as bad in Golang as it is in Java, because the big problem with it in Java is that any new exception that can appear in some library you’re using must ripple upward through exception specifications, unless you wrap it like I did above. So a small change in one place can result in the need to make many functionally meaningless changes in many other places.

                            What I think bjorn is actually saying, though, is that it’s like how in poorly written Java you have to check for null all over the place, because lots of things that are supposedly a value of some type are instead null, especially failure return values. I think this is probably true of poorly written Golang, too.

                            1. 4

                              I’ve always wondered why Java’s checked exceptions get such a bad name and this is a great example, thanks.

                              I never understood it because I spend a lot of time programming Haskell where the convention is heavily in favor of various kinds of “checked exceptions” in that it’s very often impossible to use values which were created in failing contexts without either bubbling failures upward or handling them.

                              The difference is that Haskell has Functor/Applicative/Monad interfaces which allow you to compose failing contexts and the values within in much more sensible and syntactically light manners. Oftentimes it’s not terrifically difficult to “check” all of your exceptions upon calling an API since you just want to glom them all together and float that new failing context upward—and that’s just (<*>).

                              I don’t think Haskell’s notion of errors is completely successful. At the very least there’s a lack of strong cultural conventions which causes a proliferation of error types. Successful error handling thus becomes a small mire of mapping between various kinds of error types (Maybe, Either e for various e’s). The exception system provides slightly more strength of convention, but it’s broadly frowned upon since you now have to suspect every line of code for failures again.

                              Anyway, that’s my two cents and a bit of an epiphany thanks to your code snippet here. I’d encourage anyone interested in learning more about Haskell’s error types to look at the errors package [0] which has a large number of “mapping” and “code pattern” combinators for error handling.

                              [0] http://hackage.haskell.org/package/errors

                              1. 1

                                I’m glad it’s a helpful example! But I think it’s kind of an extreme case. Most of the time the code needed to cope with Java exceptions isn’t this bad. (For example, most exceptions you have to handle can actually arise, unlike this case.)

                                I haven’t tried Haskell, but it sounds quite a bit more pleasant. Thank you for the reference!

                            2. 1

                              Yeah, this is the answer. The alternative is to just error out whenever an exception happens (which is more or less what Python does). That might be OK for a simple script. It’d be pretty disastrous if you are writing a long-running server.

                              1. 3

                                In Python your server’s top-level loop can catch and log arbitrary exceptions, so that’s not really a huge problem with Python. The deeper problem, I think, is that exceptions can’t convert failure into success; they just let you determine what kind of failure you want to have. But in a language with exceptions, nearly every line of code can fail. It’s just implicit. (In a language with operator overloading and run-time polymorphism, like Python, essentially every line can fail.)

                                Writing code that handles, and especially prevents, every possible error requires a different mentality. You can do it in Python, to some extent (though running out of memory is pretty hard to guard against) but the fact that all those possible errors are invisible in the source code doesn’t help at all. If instead you have to write extra code for each thing that can fail, then you know what those things are, and you also have an incentive to minimize them — to make parts of your code that simply can’t fail.

                            3. 4

                              Sorry to bring up a meta discussion, but why are people downvoting bjorn’s comment?

                              Error codes vs exceptions is an ongoing debate and everyone has their own reasons for liking one over the other.

                              Go’s error handling is verbose.

                              0 points.

                              I’m used to error codes from C.

                              11 points.

                              Why are people downvoting an opinion they don’t agree with?

                              1. 3

                                Because that’s not all of bjorn’s comment. bjorn’s comment mentions that Python and JavaScript don’t have as verbose error handling, but that’s mostly because there isn’t compile time checking to make sure you don’t do anything that could cause an exception without dealing with it somehow.

                                You should still deal with the exceptions in Python and JavaScript, but because there’s no note in the code itself that says “this might cause some problems” you aren’t typically away that those could be problems until they become problems.


                                That said, I didn’t downvote bjorn’s comment, I just think bjorn’s opinion isn’t well formed, or is ignoring why Java and Go deal with errors the way they do.

                              2. 2

                                I have been using go for a while now, and I actually don’t mind the error checking. I am used to explicit-ness in other things from my Python days, so maybe that is why it doesn’t bother me, but I actually enjoy being able to handle the error where it happens

                                1. 2

                                  I spend 40+ hours a week in Python land, and I generally enjoy it very much. While I don’t mind Python-style exceptions when I’m responsible for most or all of the code I’m working on or maintaining, often it’s hard to predict what your various dependencies are going to throw at you. With Go, error handling is so explicit that you are more or less forced to know what’s going on, which I like.

                                  Go/C-style error handling is a lot more verbose, but it’s refreshing being 100% aware of the error cases and what is going to potentially be sent your way. With Python, you don’t know unless the documentation, unit tests, and the organization of the source code is good.

                                1. 6

                                  The author doesn’t seem to have done his homework here. antirez responds in the comments to this post and refutes nearly all of the author’s points fairly well.

                                  All in all, it ends up sounding like he’s saying “I tried to use redis as a Postgres replacement and it didn’t work”

                                  1. 2

                                    Yeah, I’m all for a well-researched, properly thought out rant, but this seems like getting in a car and complaining that it can’t take flight.

                                    1. 2

                                      In the comments he seems to indicated that it is as good as memcache but slightly slower, but using anything above memcache functionality and it sucks. I totally disagree. Redis is especially suited to putting a bunch of data into memory to manipulated. All the different data structures are amazing for working with a data set that fits in memory. I’d much rather use it then memcache. Redis is a pleasure to use, and that’s it’s strength.

                                    1. 2

                                      I would love to see benchmarks of serving a web application this way vs. proxying the Go part behind something like nginx and having nginx serve the static files

                                      1. 6

                                        The only thing by removing nginx (or any other buffering reverse-proxy) from your stack is that you may be opening yourself up to malicious slow clients. Go can handle each request in a goroutine, but that may not save you if you don’t carefully handle larger requests.

                                        You can run a safe, secury Go server directly, but given the amount of time the nginx team has spent making it great for this purpose…

                                        1. 5

                                          I will say that I have performance tested my Go web applications by themselves and behind nginx. I used the built in benchmark tools that come with Go and the nginx webserver starts erroring and not handling requests during the bench tests and the built in Go http server does just fine and far out performs the nginx proxy. This was done quite some time ago on a windows machine so take that for what it’s worth. I know that the http library in Go has since improved, and that the environment makes quite a difference. With nginx I usually see 20-25k req/sec be handled before it starts having issues, and with go I commonly see 60k without any issue. Go handles static content very well as does nginx, but I think nginx as a proxy is just a bottleneck. I’ve since built a Go proxy for the application instances because of this. Good luck!

                                          1. 2

                                            There are also other considerations besides performance. Nginx has a lot of features that the Go http server library doesn’t, like rate-limiting, https, load-balancing, etc. If all you want is to serve a web app from a single host, you wouldn’t care about these things, but otherwise Nginx gives you a lot of functionality that would take a while to implement yourself.

                                            1. 2

                                              The obvious solution to this, of course, would be for someone to implement a web proxy in Go that has all those features. Then you could get the best of both. I’m curious, though, if the performance drop was caused by the overhead of having to pass requests from one process to another, and not so much Nginx itself. Have you benchmarked your Go proxy to see how it compares?

                                              1. 1

                                                I can’t wait to see someone build a feature compatible with Nginx server in Go. I’m guessing performance wise they’d be darn close, and the code would certainly be much more easily maintained in Go. I’d miss openresty, though, which is extremely useful for providing custom functionality to Nginx, or just straight up raw, dynamic web apps.