<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Microservices on dwmkerr.com</title><link>https://dwmkerr.com/categories/microservices/</link><description>Recent content in Microservices on dwmkerr.com</description><generator>Hugo -- gohugo.io</generator><language>en-uk</language><managingEditor>Dave Kerr</managingEditor><copyright>Copright &amp;copy; Dave Kerr</copyright><lastBuildDate>Fri, 12 Jan 2018 10:52:25 +0000</lastBuildDate><atom:link href="https://dwmkerr.com/categories/microservices/index.xml" rel="self" type="application/rss+xml"/><item><title>The Death of Microservice Madness in 2018</title><link>https://dwmkerr.com/the-death-of-microservice-madness-in-2018/</link><pubDate>Fri, 12 Jan 2018 10:52:25 +0000</pubDate><guid>https://dwmkerr.com/the-death-of-microservice-madness-in-2018/</guid><description>&lt;p&gt;&lt;a href="https://www.campusmvp.es/recursos/post/la-muerte-de-la-locura-de-los-microservicios-en-2018.aspx"&gt;En Español&lt;/a&gt; | &lt;a href="https://www.reddit.com/r/programming/comments/7pxriw/the_death_of_microservice_madness_in_2018/"&gt;Reddit Thread&lt;/a&gt; | &lt;a href="https://news.ycombinator.com/item?id=16200007"&gt;Hacker News Thread&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Microservices became a very popular topic over the last couple of years&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. &amp;lsquo;Microservice madness&amp;rsquo; goes something like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Netflix are great at devops.
Netflix do microservices.
Therefore: If I do microservices, I am great at devops.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are many cases where great efforts have been made to adopt microservice patterns without necessarily understanding how the costs and benefits will apply to the specifics of the problem at hand.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to describe in detail what microservices are, why the pattern is so appealing, and also some of the key challenges that they present.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll finish with a set of simple questions might be valuable to ask yourself when you are considering whether microservices are the right pattern &lt;em&gt;for you&lt;/em&gt;. The questions are at the end of the article.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/letterbox.png" alt="Letterbox sample of diagram"&gt;&lt;/p&gt;
&lt;h2 id="what-are-microservices-and-why-are-they-so-popular"&gt;What are microservices, and why are they so popular?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with the basics. Here is how a hypothetical video sharing platform might be implemented, first in the form of a monolith (single large unit) and then in the form of microservices:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/video-platform-monolith-microservices.png" alt="Diagram: Comparison of a Video Sharing Platform, Monolith vs Microservice"&gt;&lt;/p&gt;
&lt;p&gt;The difference between the two systems is that the first is a single large unit; a monolith. The second is a set of small, specific services. Each service has a specific role.&lt;/p&gt;
&lt;p&gt;When the diagram is drawn &lt;em&gt;at this level of detail&lt;/em&gt;, it is easy to see the appeal. There are a whole host of potential benefits:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Independent Development&lt;/strong&gt;: Small, independent components can be built by small, independent teams. A group can work on a change to the &amp;lsquo;Upload&amp;rsquo; service without interfering with the &amp;lsquo;Transcode&amp;rsquo; service, or even knowing about it. The amount of time to learn about a component is greatly reduced, and it is easier to develop new features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Independent Deployment&lt;/strong&gt;: Each individual component can be deployed independently. This allows new features to be released with greater velocity and less risk. Fixes or features for the &amp;lsquo;Streaming&amp;rsquo; component can be deployed without requiring other components to be deployed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Independent Scalability&lt;/strong&gt;: Each component can be scaled independently of each other. During busy periods when new shows are released, the &amp;lsquo;Download&amp;rsquo; component can be scaled up to handle the increased load, without having to scale up every component, which makes elastic scaling more feasible and reduces costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reusability&lt;/strong&gt;: Components fulfil a small, specific function. This means that they can more easily be adapted for use in other systems, services or products. The &amp;lsquo;Transcode&amp;rsquo; component could be used by other business units, or even turned into a new business, perhaps offering transcoding services for other groups.&lt;/p&gt;
&lt;p&gt;At this level of detail, the benefits of a microservice model over a monolithic model seem obvious. So if that&amp;rsquo;s the case - why is this pattern only recently in vogue? Where has it been all my life?&lt;/p&gt;
&lt;h2 id="if-this-is-so-great-why-hasnt-it-been-done-before"&gt;If this is so great, why hasn&amp;rsquo;t it been done before?&lt;/h2&gt;
&lt;p&gt;There are two answers to this question. One is that &lt;em&gt;it has&lt;/em&gt; - to the best of our technical capabilities, and the other is that more recent technical advances have allowed us to take it to a new level.&lt;/p&gt;
&lt;p&gt;When I started writing the answer to this question, it turned into a &lt;em&gt;long&lt;/em&gt; description, so I&amp;rsquo;m actually going to separate it into another article and publish it a little later&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. At this stage, I will skip the journey from single program to many programs, ignore ESBs and Service Orientated Architecture, component design and bounded contexts, and so on.&lt;/p&gt;
&lt;p&gt;Those who are interested can read more about the journey separately. Instead I&amp;rsquo;ll say that in many ways we&amp;rsquo;ve been doing this for a while, but with the recent explosion in popularity of container technology (Docker in particular) and in orchestration technology (such as Kubernetes, Mesos, Consul and so on) this pattern has become much more viable to implement from a technical standpoint.&lt;/p&gt;
&lt;p&gt;So if we take it as a given that we &lt;em&gt;can&lt;/em&gt; implement a microservice arrangement, we need to think carefully about the &lt;em&gt;should&lt;/em&gt;. We&amp;rsquo;ve seen the high-level theoretical benefits, but what about the challenges?&lt;/p&gt;
&lt;h2 id="whats-the-problem-with-microservices"&gt;What&amp;rsquo;s the problem with microservices?&lt;/h2&gt;
&lt;p&gt;If microservices are so great, what&amp;rsquo;s the big deal? Here are some of the biggest issues I&amp;rsquo;ve seen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Increased complexity for developers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Things &lt;em&gt;can&lt;/em&gt; get a lot harder for developers. In the case where a developer wants to work on a &lt;em&gt;journey&lt;/em&gt;, or feature which might span many services, that developer has to run them all on their machine, or connect to them. This is often more complex than simply running a single program.&lt;/p&gt;
&lt;p&gt;This challenge can be partially mitigated with tooling&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, but as the number of services which makes up a system increases, the more challenges developers will face when running the system as a whole.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Increased complexity for operators&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For teams who don&amp;rsquo;t develop services, but maintain them, there is an explosion in potential complexity. Instead of perhaps managing a few running services, they are managing dozens, hundreds or thousands of running services. There are more services, more communication paths, and more areas of potential failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Increased complexity for devops&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Reading the two points above, it may grate that operations and development are treated separately, especially given the popularity of devops as a practice (which I am a big proponent of). Doesn&amp;rsquo;t devops mitigate this?&lt;/p&gt;
&lt;p&gt;The challenge is that many organisations still run with separated development and operations teams - and a organisation that does is much more likely to struggle with adoption of microservices.&lt;/p&gt;
&lt;p&gt;For organisations which have adopted devops, it&amp;rsquo;s still hard. Being both a developer and an operator is already tough (but critical to build good software), but having to also understand the nuances of container orchestration systems, particularly systems which are evolving at a rapid pace, is very hard. Which brings me onto the next point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It requires serious expertise&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When done by experts, the results can be wonderful. But imagine an organisation where perhaps things are not running smoothly with a single monolithic system. What possible reason would there be that things would be any better by increasing the number of systems, which increases the operational complexity?&lt;/p&gt;
&lt;p&gt;Yes, with effective automation, monitoring, orchestration and so on, this is all possible. But the challenge is rarely the technology - the challenge is finding people who can use it effectively. These skillsets are currently in very high demand, and may be difficult to find.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real world systems often have poorly defined boundaries&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In all of the examples we used to describe the benefits of microservices, we spoke about &lt;em&gt;independent&lt;/em&gt; components. However in many cases components are simply not independent. On paper, certain domains may look bounded, but as you get into the muddy details, you may find that they are more challenging to model than you anticipated.&lt;/p&gt;
&lt;p&gt;This is where things can get &lt;em&gt;extremely&lt;/em&gt; complex. If your boundaries are actually not well defined, then what happens is that even though &lt;em&gt;theoretically&lt;/em&gt; services can be deployed in isolation, you find that due to the inter-dependencies between services, you have to deploy &lt;em&gt;sets&lt;/em&gt; of services as a group.&lt;/p&gt;
&lt;p&gt;This then means that you need to manage coherent versions of services which are proven and tested when working together, you don&amp;rsquo;t actually have an independently deployable system, because to deploy a new feature, you need to carefully orchestrate the simultaneous deployment of many services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The complexities of state are often ignored&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the previous example, I mentioned that a feature deployment may require the simultaneous rollout of many versions of many services in tandem. It is tempting to assume that sensible deployment techniques will mitigate this, for example blue/green deployments (which most service orchestration platforms handle with little effort), or multiple versions of a service being run in parallel, with consuming channels deciding which version to use.&lt;/p&gt;
&lt;p&gt;These techniques mitigate a large number of the challenges &lt;em&gt;if the services are stateless&lt;/em&gt;. But stateless services are quite frankly, easy to deal with. In fact, if you have stateless services, then I&amp;rsquo;d be inclined to consider skipping microservices altogether and consider using a serverless model.&lt;/p&gt;
&lt;p&gt;In reality, many services require state. An example from our video sharing platform might be the subscription service. A new version of the subscriptions service may store data in the subscriptions database in a different shape. If you are running both services in parallel, you are running the system with two schemas at once. If you do a blue green deployment, and other services depend on data in the new shape, then they must be updated &lt;em&gt;at the same time&lt;/em&gt;, and if the subscription service deployment fails and rolls back, they might need to roll back too, with cascading consequences.&lt;/p&gt;
&lt;p&gt;Again, it might be tempting to think that with NoSQL databases these issues of schema go away, but they don&amp;rsquo;t. Databases which don&amp;rsquo;t enforce schema do not lead to schemaless systems - they just mean that schema tends to be managed at the application level, rather than the database level. The fundamental challenge of understanding the shape of your data, and how it evolves, cannot be eliminated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The complexitities of communication are often ignored&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As you build a large network of services which depend on each other, the liklihood is that there will be a lot of inter-service communication. This leads to a few challenges. Firstly, there are a lot more points at which things can fail. We must expect that network calls will fail, which means when one service calls another, it should expect to have to retry a number of times at the least. Now when a service has to potentially call many services, we end up in a complicated situation.&lt;/p&gt;
&lt;p&gt;Imagine a user uploads a video in the video sharing service. We might need to run the upload service, pass data to the transcode service, update subscriptions, update recommendations and so on. All of these calls require a degree of orchestration, if things fail we need to retry.&lt;/p&gt;
&lt;p&gt;This retry logic can get hard to manage. Trying to do things synchronously often ends up being untenable, there are too many points of failure. In this case, a more reliable solution is to use asynchronous patterns to handle communication. The challenge here is that asynchronous patterns inherently make a system stateful. As mentioned in the previous point, stateful systems and systems with distributed state are very hard to handle.&lt;/p&gt;
&lt;p&gt;When a microservice system uses message queues for intra-service communication, you essentially have a large database (the message queue or broker) glueing the services together. Again, although it might not seem like a challenge at first, schema will come back to bite you. A service at version X might write a message with a certain format, services which depend on this message will also need to be updated when the sending service changes the details of the message it sends.&lt;/p&gt;
&lt;p&gt;It is possible to have services which can handle messages in many different formats, but this is hard to manage. Now when deploying new versions of services, you will have times where two different versions of a service may be trying to process messages from the same queue, perhaps even messages sent by different versions of a sending service. This can lead to complicated edge cases. To avoid these edge cases, it may be easier to only allow certain versions of messages to exist, meaning that you need to deploy a set of versions of a set of services as a coherent whole, ensuring messages of older versions are drained appropriately first.&lt;/p&gt;
&lt;p&gt;This highlights again that the idea of independent deployments may not hold as expected when you get into the details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Versioning can be hard&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To mitigate the challenges mentioned previously, versioning needs to be very carefully managed. Again, there can be a tendency to assume that following a standard such as semver[4] will solve the problem. It doesn&amp;rsquo;t. Semver is a sensible convention to use, but you will still have to track the versions of services and APIs which can work together.&lt;/p&gt;
&lt;p&gt;This can get very challenging very quickly, and may get to the point where you don&amp;rsquo;t know which versions of services will actually work properly together.&lt;/p&gt;
&lt;p&gt;Managing dependencies in software systems is notoriously hard, whether it is node modules, Java modules, C libraries or whatever. The challenges of &lt;em&gt;conflicts between independent components&lt;/em&gt; when consumed by a single entity are very hard to deal with.&lt;/p&gt;
&lt;p&gt;These challenges are hard to deal with when the dependencies are static, and can be patched, updated, edited and so on, but if the dependencies are themselves &lt;em&gt;live services&lt;/em&gt;, then you may not be able to just update them - you may have to run many versions (with the challenges already described) or bring down the system until it is fixed holistically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distributed Transactions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In situations where you need transaction integrity across an operation, microservices can be very painful. Distributed state is hard to deal with, many small units which can fail make orchestrating transactions very hard.&lt;/p&gt;
&lt;p&gt;It may be tempting to attempt to avoid the problem by making operations idempotent, offering retry mechanisms and so on, and in many cases this might work. But you may have scenarios where you simply need a transaction to fail or succeed, and never be in an intermediate state. The effort involved in working around this or implementing it in a microservice model may be very high.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Microservices can be monoliths in disguise&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes, individual services and components &lt;em&gt;may&lt;/em&gt; be deployed in isolation, however in most cases you are going to have to be running some kind of orchestration platform, such as Kubernetes. If you are using a managed service, such as Google&amp;rsquo;s GKE&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; or Amazon&amp;rsquo;s EKS&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;, then a large amount of the complexity of managing the cluster is handled for you.&lt;/p&gt;
&lt;p&gt;However, if you are managing the cluster yourself, you are managing a large, complicated, mission critical system. Although the individual services may have all of the benefits described earlier, you need to very carefully manage your cluster. Deployments of this system can be hard, updates can be hard, failover can be hard and so on.&lt;/p&gt;
&lt;p&gt;In many cases the overall benefits are still there, but it is important not to trivialise or underestimate the additional complexity of managing another big, complex system. Managed services may help, but in many cases these services are nascent (Amazon EKS was only announced at the end of 2017 for example).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Networking Nightmares&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A more traditional model of services running on known hosts, with known addresses, has a fairly simple networking setup.&lt;/p&gt;
&lt;p&gt;However, when using microservices, generally there will be many services distributed across many nodes, which typically means there&amp;rsquo;s going to be a &lt;em&gt;much&lt;/em&gt; more complicated networking arrangement. There will be load balancing between services, DNS may be more heavily used, virtual networking layers, etc etc, to attempt to &amp;lsquo;hide&amp;rsquo; the complexity of this networking.&lt;/p&gt;
&lt;p&gt;However, as per &lt;a href="https://github.com/dwmkerr/hacker-laws/#the-law-of-conservation-of-complexity-teslers-law"&gt;Tesler&amp;rsquo;s Law&lt;/a&gt; (or the Law of Conservation of Compexlity), this networking complexity is inherent - when you are finding real, runtime issues in larger scale clusters, it can often be at a very low networking level. These sorts of issues can be &lt;em&gt;very&lt;/em&gt; hard to diagnose. I have started tracking some examples at the end of the article, but I think that &lt;a href="https://medium.com/@tinder.engineering/tinders-move-to-kubernetes-cda2a6372f44"&gt;Tinder&amp;rsquo;s Migration to Kuberenetes&lt;/a&gt; shows this challenge very well.&lt;/p&gt;
&lt;p&gt;Overall - the transition is still likely to be for the best, but doesn&amp;rsquo;t come without some serious challenges at the networking level, which will require some serious expertise to deal with!&lt;/p&gt;
&lt;h2 id="the-death-of-microservice-madness"&gt;The Death of Microservice Madness!&lt;/h2&gt;
&lt;p&gt;Avoid the madness by making careful and considered decisions. To help out on this I&amp;rsquo;ve noted a few questions you might want to ask yourself, and what the answers might indicate:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/questions.png" alt="Diagram: Questions to ask yourself when considering microservices"&gt;&lt;/p&gt;
&lt;p&gt;You can download a PDF copy here: &lt;a href="https://github.com/dwmkerr/blog/blob/master/articles/2018/microservice-madness/images/microservice-questions.pdf"&gt;microservice-questions.pdf&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="final-thoughts-dont-confuse-microservices-with-architecture"&gt;Final Thoughts: Don&amp;rsquo;t Confuse Microservices with Architecture&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve deliberately avoided the &amp;lsquo;a&amp;rsquo; word in this article. But my friend &lt;a href="http://twitter.com/zoltanarvai"&gt;Zoltan&lt;/a&gt; made a very good point when proofing this article (which he has contributed to).&lt;/p&gt;
&lt;p&gt;There is no microservice architecture. Microservices are just another pattern or implementation of components, nothing more, nothing less. Whether they are present in a system or not does not mean that the architecture of the system is solved.&lt;/p&gt;
&lt;p&gt;Microservices relate in many ways more to the technical processes around packaging and operations rather than the intrinsic design of the system. Appropriate boundaries for components continues to be one of the most important challenges in engineering systems.&lt;/p&gt;
&lt;p&gt;Regardless of the size of your services, whether they are in Docker containers or not, you will always need to think carefully about how to put a system together. There are no right answers, and there are a &lt;em&gt;lot&lt;/em&gt; of options.&lt;/p&gt;
&lt;p&gt;I hope you found this article interesting! As always, please do comment below if you have any questions or thoughts. You can also follow some lively discussions on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/programming/comments/7pxriw/the_death_of_microservice_madness_in_2018/"&gt;Reddit - The Death of Microservice Madness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=16200007"&gt;Hacker News - The Death of Microservice Madness&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="appendix-further-reading"&gt;Appendix: Further Reading&lt;/h2&gt;
&lt;p&gt;The following links might be of interest:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/BoundedContext.html"&gt;Martin Fowler - Bounded Context&lt;/a&gt; - Martin&amp;rsquo;s articles are great, I&amp;rsquo;d thoroughly recommend this.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/articles/microservices.html"&gt;Martin Fowler - Microservices&lt;/a&gt; - An often recommended introduction to the pattern.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://r2m.se/microservices-good-or-bad/"&gt;Microservices - Good or Bad?&lt;/a&gt; - Björn Frantzén&amp;rsquo;s thoughts on microservices, after reading this article.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.christianposta.com/microservices/when-not-to-do-microservices/"&gt;When Not To Do Microservices&lt;/a&gt; - Excellent post on the topic from Christian Posta&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.iheavy.com/2017/03/13/30-questions-to-ask-a-serverless-fanboy/"&gt;Sean Hull - 30 questions to ask a serverless fanboy&lt;/a&gt; - Interesting thoughts on the challenges of serverless, from a serverless fan!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/NVb7aljfKYo?t=6657"&gt;Dave Kerr - Monoliths to Microservices - Practical tips for CI/CD and DevOps in the Microservice world&lt;/a&gt; - A recent conference presentation I did on devops with microservices.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yermakov.net/microservices-without-fundamentals/"&gt;Alexander Yermakov - Microservices without fundamentals&lt;/a&gt; - A response to this article, with Alex&amp;rsquo;s thoughts and counterpoints to the points raised here (see also &lt;a href="https://yermakov.net/microservices-as-a-self-sufficient-concept/"&gt;Microservices as a self sufficient concept&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please do share anything else you think makes great reading or watching on the topic!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="thanks"&gt;Thanks&lt;/h2&gt;
&lt;p&gt;Thanks José from &lt;a href="https://www.campusmvp.es"&gt;campusmvp.es&lt;/a&gt; for having the article translated in Spanish - &lt;a href="https://www.campusmvp.es/recursos/post/la-muerte-de-la-locura-de-los-microservicios-en-2018.aspx"&gt;La muerte de la locura de los microservicios en 2018&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="case-studies"&gt;Case Studies&lt;/h2&gt;
&lt;p&gt;Some interesting examples of experiences I am collecting of larger organisations who have made large scale transitions to microservices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@tinder.engineering/tinders-move-to-kubernetes-cda2a6372f44"&gt;Tinder&amp;rsquo;s Move to Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://trends.google.com/trends/explore?date=today%205-y&amp;amp;q=microservice"&gt;https://trends.google.com/trends/explore?date=today%205-y&amp;amp;q=microservice&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;If you don&amp;rsquo;t want to miss the article, you can subscribe to the &lt;a href="http://www.dwmkerr.com/rss/"&gt;RSS Feed&lt;/a&gt;, or follow me on &lt;a href="https://www.linkedin.com/in/dwmkerr/"&gt;LinkedIn&lt;/a&gt; or &lt;a href="https://twitter.com/dwmkerr"&gt;Twitter&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Docker Compose is a good solution, &lt;a href="https://github.com/apparatus/fuge"&gt;Fuge&lt;/a&gt; is very clever, and there is also the option of running orchestration locally as is the case with something like MiniKube.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Google Kubernetes Engine, a managed service from Google Cloud Platform for Kubernetes: &lt;a href="https://cloud.google.com/kubernetes-engine/"&gt;https://cloud.google.com/kubernetes-engine/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Amazon Elastic Container Services for Kubernetes, a managed service from Amazon Web Services for Kubernetes: &lt;a href="https://aws.amazon.com/eks/"&gt;https://aws.amazon.com/eks/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item><item><title>Get up and running with OpenShift on AWS</title><link>https://dwmkerr.com/get-up-and-running-with-openshift-on-aws/</link><pubDate>Thu, 02 Feb 2017 07:47:00 +0000</pubDate><guid>https://dwmkerr.com/get-up-and-running-with-openshift-on-aws/</guid><description>&lt;p&gt;&lt;a href="https://www.openshift.com/"&gt;OpenShift&lt;/a&gt; is Red Hat&amp;rsquo;s platform-as-a-service offering for hosting and scaling applications. It&amp;rsquo;s built on top of Google&amp;rsquo;s popular &lt;a href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; system.&lt;/p&gt;
&lt;p&gt;Getting up and running with OpenShift Online is straightforward, as it is a cloud hosted solution. Setting up your own cluster is a little more complex, but in this article I&amp;rsquo;ll show you how to make it fairly painless.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/welcome.png" alt="OpenShift Login"&gt;&lt;/p&gt;
&lt;p&gt;The repo for this project is at: &lt;a href="https://github.com/dwmkerr/terraform-aws-openshift"&gt;github.com/dwmkerr/terraform-aws-openshift&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="creating-the-infrastructure"&gt;Creating the Infrastructure&lt;/h2&gt;
&lt;p&gt;OpenShift has some fairly specific requirements about what hardware it runs on&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. There&amp;rsquo;s also DNS to set up, as well as internet access and so on.&lt;/p&gt;
&lt;p&gt;All in all, for a bare-bones setup, you&amp;rsquo;ll need something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/network-diagram-2.png" alt="Network Diagram"&gt;&lt;/p&gt;
&lt;p&gt;Which is (deep breath):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A network&lt;/li&gt;
&lt;li&gt;A public subnet, with internet access via a gateway&lt;/li&gt;
&lt;li&gt;A master host, which will run the OpenShift master&lt;/li&gt;
&lt;li&gt;A pair of node hosts, which will run additional OpenShift nodes&lt;/li&gt;
&lt;li&gt;A hosted zone, which allows us to configure DNS&lt;/li&gt;
&lt;li&gt;A bastion, which allows us to SSH onto hosts, without directly exposing them&lt;/li&gt;
&lt;li&gt;Some kind of basic log aggregation, which I&amp;rsquo;m using CloudWatch for&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is not a production grade setup, which requires redundant masters and so on, but it provides the basics.&lt;/p&gt;
&lt;p&gt;Rather than setting this infrastructure up by hand, this is all scripted with &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt;. To set up the infrastructure, clone the &lt;a href="https://github.com/dwmkerr/terraform-aws-openshift"&gt;github.com/dwmkerr/terraform-aws-openshift&lt;/a&gt; repo:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ git clone git@github.com:dwmkerr/terraform-aws-openshift
...
Resolving deltas: 100% (37/37), done.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then use the terraform CLI&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; to create the infrastructure:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cd terraform-aws-openshift/
$ terraform get &amp;amp;&amp;amp; terraform apply
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You&amp;rsquo;ll be asked for a region, to deploy the network into, here I&amp;rsquo;m using &lt;code&gt;us-west-1&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/Screenshot-at-Feb-02-21-16-44.png" alt="Enter Region"&gt;&lt;/p&gt;
&lt;p&gt;After a few minutes the infrastructure will be set up:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/output.png" alt="Terraform complete"&gt;&lt;/p&gt;
&lt;p&gt;A quick glance at the AWS console shows the new hosts we&amp;rsquo;ve set up:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/aws.png" alt="AWS Console"&gt;&lt;/p&gt;
&lt;p&gt;The next step is to install OpenShift.&lt;/p&gt;
&lt;h2 id="installing-openshift"&gt;Installing OpenShift&lt;/h2&gt;
&lt;p&gt;There are a few different ways to install OpenShift, but the one we&amp;rsquo;ll use is called the &amp;lsquo;advanced installation&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&amp;rsquo;. This essentially involves:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating an &amp;lsquo;inventory&amp;rsquo;, which specifies the hosts OpenShift will be installed on and the installation options&lt;/li&gt;
&lt;li&gt;Downloading the advanced installation code&lt;/li&gt;
&lt;li&gt;Running the advanced installation Ansible Playbook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To create the inventory, we just run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sed &lt;span style="color:#e6db74"&gt;&amp;#34;s/\${aws_instance.master.public_ip}/&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;terraform output master-public_ip&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;/&amp;#34;&lt;/span&gt; inventory.template.cfg &amp;gt; inventory.cfg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This takes our &amp;lsquo;inventory template&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;&amp;rsquo; and populates it with the public IP of our master node, which is recorded in a Terraform output variable.&lt;/p&gt;
&lt;p&gt;We can then copy the inventory to the bastion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh-add ~/.ssh/id_rsa
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;scp ./inventory.cfg ec2-user@&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;terraform output bastion-public_dns&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;:~
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can again use the Terraform output variables, this time to get the bastion IP. Finally, we pipe our install script to the bastion host:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat install-from-bastion.sh | ssh -A ec2-user@&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;terraform output bastion-public_dns&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a &lt;a href="https://github.com/dwmkerr/terraform-aws-openshift/issues/1"&gt;bug&lt;/a&gt; which means you might see &lt;code&gt;ansible-playbook: command not found&lt;/code&gt;, if so, just run the script again. The install script clones the installation scripts and runs them, using the inventory we&amp;rsquo;ve provided:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/ansible.png" alt="Ansible Output"&gt;&lt;/p&gt;
&lt;p&gt;This&amp;rsquo;ll probably take about 10 minutes to run. And that&amp;rsquo;s it, OpenShift is installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;open &lt;span style="color:#e6db74"&gt;&amp;#34;https://&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;terraform output master-public_dns&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:8443&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hit &amp;lsquo;advanced&amp;rsquo; and continue, as we&amp;rsquo;re using a self-signed certificate most browsers will complain:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/console1.png" alt="Invalid Certificate"&gt;&lt;/p&gt;
&lt;p&gt;Enter any username and password (the system is configured to allow anyone to access it by default) and you&amp;rsquo;ll be presented with the OpenShift console:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/console2.png" alt="OpenShift console"&gt;&lt;/p&gt;
&lt;p&gt;As the setup requires three t2.large instances, which are not available on the free plan, you might want to clean up when you are done with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;terraform destroy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Hopefully you&amp;rsquo;ve found this useful, there are more details and references on the README of the github repo:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/terraform-aws-openshift"&gt;https://github.com/dwmkerr/terraform-aws-openshift&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Comments and feedback are always welcome!&lt;/p&gt;
&lt;hr&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;See &lt;a href="https://docs.openshift.org/latest/install_config/install/prerequisites.html#system-requirements"&gt;https://docs.openshift.org/latest/install_config/install/prerequisites.html#system-requirements&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Use &amp;lsquo;brew install terraform&amp;rsquo;, full instructions in the &lt;a href="https://github.com/dwmkerr/terraform-aws-openshift"&gt;README.md&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;See &lt;a href="https://docs.openshift.org/latest/install_config/install/advanced_install.html"&gt;https://docs.openshift.org/latest/install_config/install/advanced_install.html&lt;/a&gt;&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;See &lt;a href="https://github.com/dwmkerr/terraform-aws-openshift/blob/master/inventory.template.cfg"&gt;https://github.com/dwmkerr/terraform-aws-openshift/blob/master/inventory.template.cfg&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item><item><title>Creating a Resilient Consul Cluster for Docker Microservice Discovery with Terraform and AWS</title><link>https://dwmkerr.com/creating-a-resilient-consul-cluster-for-docker-microservice-discovery-with-terraform-and-aws/</link><pubDate>Mon, 09 Jan 2017 07:10:40 +0000</pubDate><guid>https://dwmkerr.com/creating-a-resilient-consul-cluster-for-docker-microservice-discovery-with-terraform-and-aws/</guid><description>&lt;p&gt;In this article I&amp;rsquo;m going to show you how to create a resilient Consul cluster, using Terraform and AWS. We can use this cluster for microservice discovery and management. No prior knowledge of the technologies or patterns is required!&lt;/p&gt;
&lt;p&gt;The final code is at &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster"&gt;github.com/dwmkerr/terraform-consul-cluster&lt;/a&gt;. Note that it has evolved somewhat since the time of writing, see the Appendices at the end of the article for details.&lt;/p&gt;
&lt;h2 id="consul-terraform--aws"&gt;Consul, Terraform &amp;amp; AWS&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.consul.io/"&gt;Consul&lt;/a&gt; is a technology which enables &lt;em&gt;Service Discovery&lt;/em&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, a pattern which allows services to locate each other via a central authority.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; is a technology which allows us to script the provisioning of infrastructure and systems. This allows us to practice the &lt;em&gt;Infrastructure as Code&lt;/em&gt; pattern. The rigour of code control (versioning, history, user access control, diffs, pull requests etc) can be applied to our systems.&lt;/p&gt;
&lt;p&gt;And why &lt;a href="https://aws.amazon.com/"&gt;AWS&lt;/a&gt;? We need to create many servers and build a network to see this system in action. We can simulate parts of this locally with tools such as &lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt;, but we can use the arguably most popular&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; IaaS platfom for this job at essentially zero cost, and learn some valuable skills which are readily applicable to other projects at the same time.&lt;/p&gt;
&lt;p&gt;A lot of what we will learn is not really AWS specific - and the Infrastructure as Code pattern which Terraform helps us apply allows us to apply these techniques easily with other providers.&lt;/p&gt;
&lt;h2 id="the-goal"&gt;The Goal&lt;/h2&gt;
&lt;p&gt;The goal is to create a system like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-0-goal.png" alt="Overall System Diagram"&gt;&lt;/p&gt;
&lt;p&gt;In a nutshell:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have a set of homogenous Consul nodes&lt;/li&gt;
&lt;li&gt;The nodes form a cluster and automatically elect a leader&lt;/li&gt;
&lt;li&gt;The nodes span more than one availability zone, meaning the system is redundant and can survive the failure of an entire availability zone (i.e. data centre)&lt;/li&gt;
&lt;li&gt;The Consul UI is available to view via a gateway&lt;/li&gt;
&lt;li&gt;We have two example microservices which register themselves on the cluster, so we can actually see some registered services in the console&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a quick caveat, in reality this setup would typically live in a private subnet, not directly accessible to the outside work except via public facing load balancers. This adds a bit more complexity to the Terraform setup but not much value to the walk-though. A network diagram of how it might look is below, I invite interested readers to try and move to this model as a great exercise to cement the concepts!&lt;/p&gt;
&lt;h2 id="step-1---creating-our-network"&gt;Step 1 - Creating our Network&lt;/h2&gt;
&lt;p&gt;The first logical step is to create the network itself. This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The network (in AWS terminology, this is a &lt;em&gt;VPC&lt;/em&gt; or &lt;em&gt;Virtual Private Cloud&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;The &amp;lsquo;public&amp;rsquo; subnet, which defines our IP ranges for hosts&lt;/li&gt;
&lt;li&gt;The internet gateway, which provides an entry/exit point for traffic from/to the internet&lt;/li&gt;
&lt;li&gt;The firewall rules, which define what traffic can come in and out of the network&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All together, that&amp;rsquo;s this:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-1-network.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Our solution will be made more resilient by ensuring we host our Consul nodes across multiple &lt;em&gt;availability zones&lt;/em&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Creating a VPC and building a subnet is fairly trivial if you have done some network setup before or spent much time working with AWS, if not, you may be a little lost already. There&amp;rsquo;s a good course on Udemy&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; which will take you through the process of setting up a VPC which I recommend if you are interested in this, as it is quite hands on. It&amp;rsquo;ll also show you how to build a more &amp;lsquo;realistic&amp;rsquo; network, which also contains a private subnet and NAT, but that&amp;rsquo;s beyond the scope of this write-up. Instead, I&amp;rsquo;ll take you through the big parts.&lt;/p&gt;
&lt;h3 id="the-network"&gt;The Network&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re using AWS, we need to create a VPC. A VPC is a Virtual Private Cloud. The key thing is that it is &lt;em&gt;isolated&lt;/em&gt;. Things you create in this network will be able to talk to each other if you let them, but cannot communicate with the outside world, unless you specifically create the parts needed for them to do so.&lt;/p&gt;
&lt;p&gt;A private network is probably something you regularly use if you work in a company&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. Most companies have their own internal network - when you use a computer on that network it can talk to other company computers (such as the company mail server). When you are off that network, you might not be able to access your company email (unless it is publicly available, like gmail, or over a VPN [and by accessing a VPN, you are actually &lt;em&gt;joining&lt;/em&gt; the network again, albeit remotely]).&lt;/p&gt;
&lt;p&gt;Perhaps the most immediately obvious part of a VPC is that &lt;em&gt;you control the IP addresses&lt;/em&gt;. You specify the &lt;em&gt;range&lt;/em&gt; of IP addresses which are available to give to machines on the network. When a machine joins, it is given an IP in that range. I&amp;rsquo;m not going to go into too much detail here, if you are interested let me know and I&amp;rsquo;ll write up an article on VPCs in detail!&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-3-vpc.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I&amp;rsquo;d suggest scripting AWS infrastructure with Terraform if you haven&amp;rsquo;t done this before.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use the AWS console to create what you want&lt;/li&gt;
&lt;li&gt;Search the Terraform documentation for the entity you want to create (e.g. &lt;a href="https://www.terraform.io/docs/providers/aws/r/vpc.html"&gt;VPC&lt;/a&gt;), &lt;em&gt;script&lt;/em&gt; the component and &lt;em&gt;apply&lt;/em&gt; the provisioning&lt;/li&gt;
&lt;li&gt;Compare the hand-made VPC to the script-made VPC, if the two are the same, you are done&lt;/li&gt;
&lt;li&gt;If the two are different, check the documentation and try again&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ensure you have an AWS account, and note your Secret Key and Access Key. We&amp;rsquo;ll need these to remotely control it. Here&amp;rsquo;s the terraform script to create a VPC:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Setup the core provider information.
provider &amp;#34;aws&amp;#34; {
access_key = &amp;#34;${var.access_key}&amp;#34;
secret_key = &amp;#34;${var.secret_key}&amp;#34;
region = &amp;#34;${var.region}&amp;#34;
}
// Define the VPC.
resource &amp;#34;aws_vpc&amp;#34; &amp;#34;consul-cluster&amp;#34; {
cidr_block = &amp;#34;10.0.0.0/16&amp;#34; // i.e. 10.0.0.0 to 10.0.255.255
enable_dns_hostnames = true
tags {
Name = &amp;#34;Consul Cluster VPC&amp;#34;
Project = &amp;#34;consul-cluster&amp;#34;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This script uses &lt;a href="https://www.terraform.io/docs/configuration/variables.html"&gt;Terraform Variables&lt;/a&gt;, such as &lt;code&gt;var.access_key&lt;/code&gt;, which we keep in a &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster/blob/master/variables.tf"&gt;variables.tf&lt;/a&gt; file. Terraform will use the default values defined in the file if they are present, or ask the user to supply them. Let&amp;rsquo;s build the network:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After supplying the values for the variables, Terraform will provision the network, using the AWS SDK internally.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-2-terraform-apply.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll see lots of info about what it is creating, then a success message.&lt;/p&gt;
&lt;h3 id="the-public-subnet"&gt;The Public Subnet&lt;/h3&gt;
&lt;p&gt;You don&amp;rsquo;t put hosts directly into a VPC, they need to go into a structure called a &amp;lsquo;subnet&amp;rsquo;, which is a &lt;em&gt;part&lt;/em&gt; of a VPC. Subnets get their own subset of the VPC&amp;rsquo;s available IP addresses, which you specify.&lt;/p&gt;
&lt;p&gt;Subnets are used to build &lt;em&gt;zones&lt;/em&gt; in a network. Why would you need this? Typically it is to manage security. You might have a &amp;lsquo;public zone&amp;rsquo; in which all hosts can be accessed from the internet, and a &amp;lsquo;private zone&amp;rsquo; which is inaccessible directly (and therefore a better location for hosts with sensitive data). You might have an &amp;lsquo;operator&amp;rsquo; zone, which only sysadmins can access, but they can use to get diagnostic information.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a common subnet layout for multi-tiered applications:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-4-subnets.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The defining characteristics of zones is that they are used to create &lt;em&gt;boundaries&lt;/em&gt; to isolate hosts. These boundaries are normally secured by firewalls, traversed via gateways or NATs etc. We&amp;rsquo;re going to create two public subnets, one in each of the availability zones&lt;sup id="fnref1:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Create a public subnet for each AZ.
resource &amp;#34;aws_subnet&amp;#34; &amp;#34;public-a&amp;#34; {
vpc_id = &amp;#34;${aws_vpc.consul-cluster.id}&amp;#34;
cidr_block = &amp;#34;10.0.1.0/24&amp;#34; // i.e. 10.0.1.0 to 10.0.1.255
availability_zone = &amp;#34;ap-southeast-1a&amp;#34;
map_public_ip_on_launch = true
}
resource &amp;#34;aws_subnet&amp;#34; &amp;#34;public-b&amp;#34; {
vpc_id = &amp;#34;${aws_vpc.consul-cluster.id}&amp;#34;
cidr_block = &amp;#34;10.0.2.0/24&amp;#34; // i.e. 10.0.2.0 to 10.0.1.255
availability_zone = &amp;#34;ap-southeast-1b&amp;#34;
map_public_ip_on_launch = true
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With Terraform, resources can depend on each other. In this case, the subnets need to reference the ID of the VPC we want to place them in (so we use &lt;code&gt;aws_vpc.consul-cluster.id&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id="the-internet-gateway-route-tables-and-security-groups"&gt;The Internet Gateway, Route Tables and Security Groups&lt;/h3&gt;
&lt;p&gt;The final parts of the network you can see in the &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster/blob/master/network.tf"&gt;./infrastructure/network.tf&lt;/a&gt; script. These are the Internet Gateway, Route Table and Security Group resources. Essentially they are for controlling access between hosts and the internet. AWS have a &lt;a href="http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Scenario1.html"&gt;good guide&lt;/a&gt; if you are not familiar with these resources; they don&amp;rsquo;t add much to the article so I&amp;rsquo;ll leave you to explore on your own.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it for the network, we now have the following structure:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-1-network-1.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If you want to see the code as it stands now, check the &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster/tree/step-1"&gt;Step 1&lt;/a&gt; branch. Now we need to look at creating the hosts to install Consul on.&lt;/p&gt;
&lt;h2 id="step-2---creating-the-consul-hosts"&gt;Step 2 - Creating the Consul Hosts&lt;/h2&gt;
&lt;p&gt;The Consul documentation recommends running in a cluster or 3 or 5 nodes&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;. We want to set up a system which is self-healing - if we lose a node, we want to create a new one.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="http://docs.aws.amazon.com/autoscaling/latest/userguide/AutoScalingGroup.html"&gt;Auto-Scaling Groups&lt;/a&gt;. Auto-scaling groups allow us to define a template for an instance, and ask AWS to make sure there are always a certain number of these instances. If we lose an instance, a new one will be created to keep the group at the correct size&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;So we now need to create:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &amp;lsquo;Launch Configuration&amp;rsquo; which determines what instances our Auto-scaling Group creates&lt;/li&gt;
&lt;li&gt;A &amp;lsquo;user data script&amp;rsquo; which runs on newly created instances, which must install and start Consul&lt;/li&gt;
&lt;li&gt;An Auto-scaling group, configured to run five instances across the two public subnets&lt;/li&gt;
&lt;li&gt;A load balancer, configured to pass incoming requests for the Consul Admin console to the nodes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or visually:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-5-cluster-basic-2.png" alt="Basic Cluster Diagram"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s get to it.&lt;/p&gt;
&lt;h3 id="the-launch-configuration--auto-scaling-group"&gt;The Launch Configuration &amp;amp; Auto-scaling Group&lt;/h3&gt;
&lt;p&gt;The Launch Configuration will define the characteristics of our instances and the auto-scaling group determines the size of our cluster:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Launch configuration for the consul cluster auto-scaling group.
resource &amp;#34;aws_launch_configuration&amp;#34; &amp;#34;consul-cluster-lc&amp;#34; {
name_prefix = &amp;#34;consul-node-&amp;#34;
image_id = &amp;#34;${lookup(var.ami_ecs_optimised, var.region)}&amp;#34;
instance_type = &amp;#34;t2.micro&amp;#34;
security_groups = [&amp;#34;${aws_security_group.consul-cluster-vpc.id}&amp;#34;]
lifecycle {
create_before_destroy = true
}
}
// Auto-scaling group for our cluster.
resource &amp;#34;aws_autoscaling_group&amp;#34; &amp;#34;consul-cluster-asg&amp;#34; {
name = &amp;#34;consul-asg&amp;#34;
launch_configuration = &amp;#34;${aws_launch_configuration.consul-cluster-lc.name}&amp;#34;
min_size = 5
max_size = 5
vpc_zone_identifier = [
&amp;#34;${aws_subnet.public-a.id}&amp;#34;,
&amp;#34;${aws_subnet.public-b.id}&amp;#34;
]
lifecycle {
create_before_destroy = true
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A few key things to note:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I have omitted the &lt;code&gt;tag&lt;/code&gt; properties in the scripts for brevity&lt;/li&gt;
&lt;li&gt;The &amp;lsquo;image&amp;rsquo; for the launch configuration is looked up based on the region we&amp;rsquo;ve specified - we&amp;rsquo;re a basic linux image&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;We are using micro instances, which are free-tier eligible&lt;/li&gt;
&lt;li&gt;The auto-scaling group spans both availability zones.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once we run &lt;code&gt;terraform apply&lt;/code&gt;, we&amp;rsquo;ll see our auto-scaling group, which references the new launch configuration and works over multiple availability zones:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-6-lc-asg.png" alt="Auto scaling group and launch configuration"&gt;&lt;/p&gt;
&lt;p&gt;We can also see the new instances:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-7-instances.png" alt="Instances"&gt;&lt;/p&gt;
&lt;p&gt;These instances don&amp;rsquo;t do much yet though, we&amp;rsquo;ve not installed Docker or Consul.&lt;/p&gt;
&lt;h3 id="installing-consul-and-accessing-the-admin-interface"&gt;Installing Consul and Accessing the Admin Interface&lt;/h3&gt;
&lt;p&gt;To set up our instances we use a &amp;lsquo;userdata&amp;rsquo; script&amp;rsquo; A userdata runs once when an instance is created. We can create a script in our repository, and reference it in our Terraform files.&lt;/p&gt;
&lt;p&gt;We add a new file called &lt;code&gt;consul-node.sh&lt;/code&gt; to a &lt;code&gt;files&lt;/code&gt; folder. This script installs Docker and runs Consul:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum install -y docker
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;usermod -a -G docker ec2-user
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service docker start
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Get my IP address.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IP&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;curl http://169.254.169.254/latest/meta-data/local-ipv4&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Instance IP is: &lt;/span&gt;$IP&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Start the Consul server.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --net&lt;span style="color:#f92672"&gt;=&lt;/span&gt;host &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;consul &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; consul agent -server -ui &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -bind&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$IP&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -client&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -bootstrap-expect&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s a breakdown of what we&amp;rsquo;re doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install Docker. These scripts run as root, so we add the ec2-user to the Docker group, meaning when we log in later on via SSH, we can run Docker&lt;/li&gt;
&lt;li&gt;Get our IP address. AWS provide a magic address (169.254.169.254) which lets you query data about your instance, see &lt;a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html"&gt;Instance Metadata &amp;amp; User Metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Run the Consul docker image in server mode, with the UI enabled, expecting only one instance&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The actual scripts contains more!&lt;/strong&gt; Getting userdata scripts right, testing and debugging them is tricky. See how I do it in detail in &lt;a href="#Appendix-1-Logging"&gt;Appendix 1: Logging&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now we need to tell Terraform to include this script as part of the instance metadata. Here&amp;rsquo;s how we do that:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;aws_launch_configuration&amp;#34; &amp;#34;consul-cluster-lc&amp;#34; {
/// ...add the line below....
user_data = &amp;#34;${file(&amp;#34;files/consul-node.sh&amp;#34;)}&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When Consul is running with the &lt;code&gt;-ui&lt;/code&gt; option, it provides an admin UI. You can try it by running Consul locally with &lt;code&gt;docker run -p8500:8500 consul&lt;/code&gt; and navigating to http://localhost:8500/ui.&lt;/p&gt;
&lt;p&gt;We can install a load balancer in front of our auto-scaling group, to automatically forward incoming traffic to a host. Here&amp;rsquo;s the config:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;aws_elb&amp;#34; &amp;#34;consul-lb&amp;#34; {
name = &amp;#34;consul-lb-a&amp;#34;
security_groups = [
&amp;#34;${aws_security_group.consul-cluster-vpc.id}&amp;#34;,
&amp;#34;${aws_security_group.web.id}&amp;#34;
]
subnets = [
&amp;#34;${aws_subnet.public-a.id}&amp;#34;,
&amp;#34;${aws_subnet.public-b.id}&amp;#34;
]
listener {
instance_port = 8500
instance_protocol = &amp;#34;http&amp;#34;
lb_port = 80
lb_protocol = &amp;#34;http&amp;#34;
}
health_check {
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 3
target = &amp;#34;HTTP:8500/ui/&amp;#34;
interval = 30
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Blow-by-blow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a load balancer, with the same security groups as the rest of the VPC, but also a security group which allows web access&lt;/li&gt;
&lt;li&gt;Point to two subnets first subnet&lt;/li&gt;
&lt;li&gt;Forward HTTP 8500 traffic&lt;/li&gt;
&lt;li&gt;Configure a healthcheck&lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The final change we make is to add an &lt;code&gt;outputs.tf&lt;/code&gt; file, which lists all of the properties Terraform knows about which we want to save. All it includes is:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;output &amp;#34;consul-dns&amp;#34; {
value = &amp;#34;${aws_elb.consul-lb.dns_name}&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When we finally run &lt;code&gt;terraform apply&lt;/code&gt;, we see the public DNS of our load balancer:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-8-cluster-dns.png" alt="Screenshot showing &amp;rsquo;terraform apply&amp;rsquo; output, indicating our newly generated ELB&amp;rsquo;s public DNS"&gt;&lt;/p&gt;
&lt;p&gt;And running in a browser on port 8500 we see the Consul admin interface:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-9-admin-ui.png" alt="Screenshot showing the Consul admin interface"&gt;&lt;/p&gt;
&lt;p&gt;Every time we refresh we will likely see a different node. We&amp;rsquo;ve actually created five clusters each of one node - what we now need to do is connect them all together into a single cluster of five nodes.&lt;/p&gt;
&lt;p&gt;If you want to see the code as it stands now, check the &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster/tree/step-2"&gt;Step 2&lt;/a&gt; branch.&lt;/p&gt;
&lt;h2 id="step-3---creating-the-cluster"&gt;Step 3 - Creating the Cluster&lt;/h2&gt;
&lt;p&gt;Creating the cluster is now not too much of a challenge. We will update the userdata script to tell the consul process we are expecting 5 nodes (via the &lt;a href="https://www.consul.io/docs/agent/options.html#_bootstrap_expect"&gt;&lt;code&gt;bootstrap-expect&lt;/code&gt;&lt;/a&gt; flag.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the updated script:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Get my IP address.
IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
echo &amp;#34;Instance IP is: $IP&amp;#34;
# Start the Consul server.
docker run -d --net=host \
--name=consul \
consul agent -server -ui \
-bind=&amp;#34;$IP&amp;#34; \
-client=&amp;#34;0.0.0.0&amp;#34; \
-bootstrap-expect=&amp;#34;5&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The problem is &lt;strong&gt;this won&amp;rsquo;t work&lt;/strong&gt;&amp;hellip; We need to tell each node the address of &lt;em&gt;another&lt;/em&gt; server in the cluster. For example, if we start five nodes, we should tell nodes 2-5 the address of node 1, so that the nodes can discover each other.&lt;/p&gt;
&lt;p&gt;The challenge is how do we get the IP of node 1? The IP addresses are determined by the network, we don&amp;rsquo;t preset them so cannot hard code them. Also, we can expect nodes to occasionally die and get recreated, so the IP addresses of nodes will in fact change over time.&lt;/p&gt;
&lt;h3 id="getting-the-ip-addresses-of-nodes-in-the-cluster"&gt;Getting the IP addresses of nodes in the cluster&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s a nice trick we can use here. We can ask AWS to give us the IP addresses of each host in the auto-scaling group. If we tell each node the addresses of the &lt;em&gt;other nodes&lt;/em&gt;, then they will elect a leader themselves&lt;sup id="fnref:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-12-choose-leader-1.png" alt="Diagram showing how we decide on a leader IP"&gt;&lt;/p&gt;
&lt;p&gt;There are a couple of things we need to do to get this right. First, update the userdata script to provide the IPs of other nodes when we&amp;rsquo;re starting up, then update the &lt;strong&gt;role&lt;/strong&gt; of our nodes so that they have permissions to use the APIs we&amp;rsquo;re going to call.&lt;/p&gt;
&lt;h3 id="getting-the-cluster-ips"&gt;Getting the Cluster IPs&lt;/h3&gt;
&lt;p&gt;This is actually fairly straightforward. We update our userdata script to the below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# A few variables we will refer to later...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ASG_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;consul-asg
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;REGION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ap-southeast-1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;EXPECTED_SIZE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Return the id of each instance in the cluster.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; cluster-instance-ids &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Grab every line which contains &amp;#39;InstanceId&amp;#39;, cut on double quotes and grab the ID:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# &amp;#34;InstanceId&amp;#34;: &amp;#34;i-example123&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#....^..........^..^.....#4.....^...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; aws --region&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$REGION&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; autoscaling describe-auto-scaling-groups &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --auto-scaling-group-name $ASG_NAME &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | grep InstanceId &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | cut -d &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#34;&amp;#39;&lt;/span&gt; -f4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Return the private IP of each instance in the cluster.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; cluster-ips &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; id in &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;cluster-instance-ids&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; aws --region&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$REGION&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; ec2 describe-instances &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --query&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Reservations[].Instances[].[PrivateIpAddress]&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --output&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --instance-ids&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$id&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Wait until we have as many cluster instances as we are expecting.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; COUNT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;cluster-instance-ids | wc -l&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$COUNT&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -lt &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$EXPECTED_SIZE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$COUNT&lt;span style="color:#e6db74"&gt; instances in the cluster, waiting for &lt;/span&gt;$EXPECTED_SIZE&lt;span style="color:#e6db74"&gt; instances to warm up...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sleep &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Get my IP address, all IPs in the cluster, then just the &amp;#39;other&amp;#39; IPs...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IP&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;curl http://169.254.169.254/latest/meta-data/local-ipv4&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mapfile -t ALL_IPS &amp;lt; &amp;lt;&lt;span style="color:#f92672"&gt;(&lt;/span&gt;cluster-ips&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;OTHER_IPS&lt;span style="color:#f92672"&gt;=(&lt;/span&gt; &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;ALL_IPS[@]/&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;IP&lt;span style="color:#e6db74"&gt;}}&lt;/span&gt;/&lt;span style="color:#f92672"&gt;}&lt;/span&gt; &lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Instance IP is: &lt;/span&gt;$IP&lt;span style="color:#e6db74"&gt;, Cluster IPs are: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;CLUSTER_IPS[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, Other IPs are: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;OTHER_IPS[@]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Start the Consul server.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --net&lt;span style="color:#f92672"&gt;=&lt;/span&gt;host &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;consul &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; consul agent -server -ui &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -bind&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$IP&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -retry-join&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;OTHER_IPS[0]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -retry-join&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;OTHER_IPS[1]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -retry-join&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;OTHER_IPS[2]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -retry-join&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;OTHER_IPS[3]&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -bootstrap-expect&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$EXPECTED_SIZE&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Right, here&amp;rsquo;s what&amp;rsquo;s going on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We create a few variables we&amp;rsquo;ll use repeatedly&lt;/li&gt;
&lt;li&gt;We create a &lt;code&gt;cluster-instance-ids&lt;/code&gt; function which returns the ID of each instance in the auto-scaling group&lt;/li&gt;
&lt;li&gt;We create a &lt;code&gt;cluster-ips&lt;/code&gt; function which returns the private IP address of each instance in the cluster.&lt;/li&gt;
&lt;li&gt;We wait until the auto-scaling group has our expected number of instances (it can take a while for them all to be created)&lt;/li&gt;
&lt;li&gt;We get the 5 IP addresses&lt;/li&gt;
&lt;li&gt;We remove our IP from the array, leaving us with the IPs of the &lt;em&gt;other&lt;/em&gt; nodes&lt;/li&gt;
&lt;li&gt;We start the Consul agent in server mode, expecting 5 nodes and offering the IP of each other agent&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The problem is, if we try to run the script we will fail, because calling the AWS APIs requires some permissions we don&amp;rsquo;t have. Let&amp;rsquo;s fix that.&lt;/p&gt;
&lt;h3 id="creating-a-role-for-our-nodes"&gt;Creating a Role for our nodes&lt;/h3&gt;
&lt;p&gt;Our nodes now have a few special requirements. They need to be able to query the details of an auto-scaling group and get the IP of an instance&lt;sup id="fnref:11"&gt;&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref"&gt;11&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;We will need to create a policy which describes the permissions we need, create a role, attach the policy to the role and then ensure our instances are assigned the correct role. This is &lt;code&gt;consul-node-role.tf&lt;/code&gt; file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// This policy allows an instance to discover a consul cluster leader.
resource &amp;#34;aws_iam_policy&amp;#34; &amp;#34;leader-discovery&amp;#34; {
name = &amp;#34;consul-node-leader-discovery&amp;#34;
path = &amp;#34;/&amp;#34;
policy = &amp;lt;&amp;lt;EOF
{
&amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;,
&amp;#34;Statement&amp;#34;: [
{
&amp;#34;Sid&amp;#34;: &amp;#34;Stmt1468377974000&amp;#34;,
&amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;,
&amp;#34;Action&amp;#34;: [
&amp;#34;autoscaling:DescribeAutoScalingInstances&amp;#34;,
&amp;#34;autoscaling:DescribeAutoScalingGroups&amp;#34;,
&amp;#34;ec2:DescribeInstances&amp;#34;
],
&amp;#34;Resource&amp;#34;: [
&amp;#34;*&amp;#34;
]
}
]
}
EOF
}
// Create a role which consul instances will assume.
// This role has a policy saying it can be assumed by ec2
// instances.
resource &amp;#34;aws_iam_role&amp;#34; &amp;#34;consul-instance-role&amp;#34; {
name = &amp;#34;consul-instance-role&amp;#34;
assume_role_policy = &amp;lt;&amp;lt;EOF
{
&amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;,
&amp;#34;Statement&amp;#34;: [
{
&amp;#34;Action&amp;#34;: &amp;#34;sts:AssumeRole&amp;#34;,
&amp;#34;Principal&amp;#34;: {
&amp;#34;Service&amp;#34;: &amp;#34;ec2.amazonaws.com&amp;#34;
},
&amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;,
&amp;#34;Sid&amp;#34;: &amp;#34;&amp;#34;
}
]
}
EOF
}
// Attach the policy to the role.
resource &amp;#34;aws_iam_policy_attachment&amp;#34; &amp;#34;consul-instance-leader-discovery&amp;#34; {
name = &amp;#34;consul-instance-leader-discovery&amp;#34;
roles = [&amp;#34;${aws_iam_role.consul-instance-role.name}&amp;#34;]
policy_arn = &amp;#34;${aws_iam_policy.leader-discovery.arn}&amp;#34;
}
// Create a instance profile for the role.
resource &amp;#34;aws_iam_instance_profile&amp;#34; &amp;#34;consul-instance-profile&amp;#34; {
name = &amp;#34;consul-instance-profile&amp;#34;
roles = [&amp;#34;${aws_iam_role.consul-instance-role.name}&amp;#34;]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Terraform is a little verbose here! Finally, we update our launch configuration to ensure that the instances assume this role.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;resource &amp;#34;aws_launch_configuration&amp;#34; &amp;#34;consul-cluster-lc&amp;#34; {
// Add this line!!
iam_instance_profile = &amp;#34;${aws_iam_instance_profile.consul-instance-profile.id}&amp;#34;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s create the cluster again, with &lt;code&gt;terraform apply&lt;/code&gt;. When we log into the UI we should now see a cluster containing all five nodes:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-13-cluster.png" alt="Screenshot of the Consul UI, showing that the Consul server is running on five nodes in the Datacenter"&gt;&lt;/p&gt;
&lt;p&gt;This code is all in the &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster/tree/step-3"&gt;Step 3&lt;/a&gt; branch.&lt;/p&gt;
&lt;p&gt;If you are familiar with Consul, this may be all you need. If not, you might be interested in seeing how we actually create a new instance to host a service, register it with Consul and query its address.&lt;/p&gt;
&lt;h2 id="step-4---adding-a-microservice"&gt;Step 4 - Adding a Microservice&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve created a docker image for as simple a microservice as you can get. It returns a quote from Futurama&amp;rsquo;s Zapp Brannigan. The image is tagged as &lt;code&gt;dwmkerr/zapp-service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On a new EC2 instance, running in either subnet, with the same roles as the Consul nodes, we run the following commands:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Install Docker
sudo su
yum update -y
yum install -y docker
service docker start
# Get my IP and the IP of any node in the server cluster.
IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
NODE_ID=$(aws --region=&amp;#34;ap-southeast-1&amp;#34; autoscaling describe-auto-scaling-groups --auto-scaling-group-name &amp;#34;consul-asg&amp;#34; \
| grep InstanceId \
| cut -d &amp;#39;&amp;#34;&amp;#39; -f4 \
| head -1)
NODE_IP=$(aws --region=&amp;#34;ap-southeast-1&amp;#34; ec2 describe-instances \
--query=&amp;#34;Reservations[].Instances[].[PrivateIpAddress]&amp;#34; \
--output=&amp;#34;text&amp;#34; \
--instance-ids=&amp;#34;$NODE_ID&amp;#34;)
# Run the consul agent.
docker run -d --net=host \
consul agent \
-bind=&amp;#34;$IP&amp;#34; \
-join=$NODE_IP
# Run registrator - any Docker images will then be auto registered.
docker run -d \
--name=registrator \
--net=host \
--volume=/var/run/docker.sock:/tmp/docker.sock \
gliderlabs/registrator:latest \
consul://localhost:8500
# Run the example microservice - registrator will take care of letting consul know.
docker run -d -p 5000:5000 dwmkerr/zapp-service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What&amp;rsquo;s going on here?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We grab our own IP address and the IP address of the first instance we find in the server cluster, using the same tricks as before&lt;/li&gt;
&lt;li&gt;We run the Consul agent - telling it the IP to use to join the cluster&lt;/li&gt;
&lt;li&gt;We run &lt;a href="https://github.com/gliderlabs/registrator"&gt;Registrator&lt;/a&gt;, a handy utility which will automatically register any new services we run to Consul&lt;/li&gt;
&lt;li&gt;We run a goofy sample microservice (which registrator will register for us)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now we can check the Consul UI:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-15-sample-service.png" alt="The Consul UI showing a new service"&gt;&lt;/p&gt;
&lt;p&gt;And there we have it. Our new node joins the cluster (as a client), we can register a new service with Consul.&lt;/p&gt;
&lt;p&gt;We can call this service from any node in the subnet, seeing output like the below:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-x-zapp.png" alt="Screenshot of the Zapp service"&gt;&lt;/p&gt;
&lt;p&gt;In this example, I used a DNS SRV query to ask where the &lt;code&gt;zapp-service&lt;/code&gt; is, was told it was at &lt;code&gt;10.0.2.158&lt;/code&gt; on port &lt;code&gt;5000&lt;/code&gt;, then called the service, receiving a response. I can discover any service using this method, from any node. As services are added, removed, moved etc, I can ask Consul for accurate information on where to find them.&lt;/p&gt;
&lt;p&gt;Check the &lt;a href=""&gt;Step 4&lt;/a&gt; branch to see the code in its current state.&lt;/p&gt;
&lt;h2 id="step-5---spanner-throwing"&gt;Step 5 - Spanner Throwing&lt;/h2&gt;
&lt;p&gt;We can now try to throw some spanners in the works, to see how resilient the system is.&lt;/p&gt;
&lt;p&gt;According to the &lt;a href="https://www.consul.io/docs/internals/consensus.html#deployment-table"&gt;Deployment Table&lt;/a&gt; from the Consul documentation, a cluster of five nodes means we have a quorum of three nodes (i.e. a minimum of three nodes are needed for a working system). This means we can tolerate the failure of two nodes.&lt;/p&gt;
&lt;p&gt;The easiest way to test this is to simply manually kill two nodes:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-16-terminate.png" alt="Screenshot showing two AWS instances being terminated"&gt;&lt;/p&gt;
&lt;p&gt;If we pick two random nodes, as above, and terminate them, we see the cluster determines that we have two failed nodes but will still function (if one was the leader, a new leader will be automatically elected):&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-17-node-failure.png" alt="Screenshot showing the cluster highlighting two failed nodes"&gt;&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s nice about this setup is that no manual action is needed to recover. Our load balancer will notice the nodes are unhealthy and stop forwarding traffic. Our auto-scaling group will see the nodes have terminated and create two new ones, which will join the cluster in the same way as the original nodes. Once they join, the load balancer will find them healthy and bring them back into rotation.&lt;/p&gt;
&lt;p&gt;We can see from the load balancer monitoring that it notices we have unhealthy nodes and also notices when new ones come into service:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-18-recovery-1.png" alt="Screenshot showing the load balancer monitoring"&gt;&lt;/p&gt;
&lt;p&gt;A quick check of the admin dashboard shows we now have a recovered system, with five healthy nodes:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-18b-recovered.png" alt="Screenshot showing recovered system"&gt;&lt;/p&gt;
&lt;p&gt;The nodes which were terminated are still listed as failing. After 72 hours Consul will stop trying to periodically reconnect to these nodes and completely remove them&lt;sup id="fnref:12"&gt;&lt;a href="#fn:12" class="footnote-ref" role="doc-noteref"&gt;12&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Hopefully this should provide a good starting point to think about building your own resilient and robust systems for services like Consul.&lt;/p&gt;
&lt;p&gt;Interesting areas to look into to extend the project would be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Setting up alerts so that if we lose more than one node, we are informed&lt;/li&gt;
&lt;li&gt;Automating resilience tests by programatically bringing down servers and monitoring how long it takes the system to return to five nodes&lt;/li&gt;
&lt;li&gt;Instead of using a userdata script to set up a node, bake it into a new custom AMI with &lt;a href="https://www.packer.io/"&gt;Packer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Adding alerts for if we lose three of more nodes, which always requires manual intervention (see &lt;a href="https://www.consul.io/docs/guides/outage.html"&gt;Outage Recovery&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As always, any questions or comments are welcome! All code is available at &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster"&gt;github.com/dwmkerr/terraform-consul-cluster&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="appendix-1-logging"&gt;Appendix 1: Logging&lt;/h2&gt;
&lt;p&gt;Small typos or mistakes in the userdata script are almost impossible to effectively diagnose. The scripts were actually built in the following way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Draft a script on my local machine which configures script logging and CloudWatch&lt;sup id="fnref:13"&gt;&lt;a href="#fn:13" class="footnote-ref" role="doc-noteref"&gt;13&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Spin up a new EC2 instance manually&lt;/li&gt;
&lt;li&gt;SSH onto the instance, and run the script line by line until I&amp;rsquo;m sure it&amp;rsquo;s right&lt;/li&gt;
&lt;li&gt;Ensure the logs are forwarded to CloudWatch, then add the more complex features and repeatedly test&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;ve included CloudWatch logging in the code. In this write-up I&amp;rsquo;ve omitted this code as it is purely for diagnostics and doesn&amp;rsquo;t contribute to the main topic. The setup is in the &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster/blob/master/files/consul-node.sh"&gt;&lt;code&gt;consul-node.sh&lt;/code&gt;&lt;/a&gt; and &lt;a href="%60https://github.com/dwmkerr/terraform-consul-cluster/blob/master/consul-node-role.tf"&gt;&lt;code&gt;consul-node-role.tf&lt;/code&gt;&lt;/a&gt; files.&lt;/p&gt;
&lt;p&gt;If you want more details, let me know, or just check the code. I would heartily recommend setting up logging like this for all but the most straightforward projects:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/img-19-cloudwatch-1.png" alt="Screenshot showing logs"&gt;&lt;/p&gt;
&lt;p&gt;Being able to diagnose issues like this is vital when working with distributed systems which may be generating many log files.&lt;/p&gt;
&lt;h2 id="appendix-2-modularisaton"&gt;Appendix 2: Modularisaton&lt;/h2&gt;
&lt;p&gt;I got some a great PR from &lt;a href="https://github.com/arehmandev"&gt;arehmandev&lt;/a&gt; which modularises the code. This makes it more reusable and cleans up the structure significantly. If you want to see the before/after, check the original PR at &lt;a href="https://github.com/dwmkerr/terraform-consul-cluster/pull/4"&gt;https://github.com/dwmkerr/terraform-consul-cluster/pull/4&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Further Reading&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.consul.io/docs/internals/consensus.html"&gt;Consul - Consensus Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sitano.github.io/2015/10/06/abt-consul-outage/"&gt;What you have to know about Consul and how to beat the outage problem&lt;/a&gt;, John Koepi&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;This kind of pattern is critical in the world of microservices, where many small services will be running on a cluster. Services may die, due to errors or failing hosts, and be recreated on new hosts. Their IPs and ports may be ephemeral.It is essential that the system as a whole has a registry of where each service lives and how to access it. Such a registry must be &lt;em&gt;resilient&lt;/em&gt;, as it is an essential part of the system.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Most popular is a fairly loose term. Well ranked by Gartner and anecdotally with the largest infrastructure footprint. &lt;a href="https://www.gartner.com/doc/reprints?id=1-2G2O5FC&amp;amp;ct=150519&amp;amp;st=sb"&gt;https://www.gartner.com/doc/reprints?id=1-2G2O5FC&amp;amp;ct=150519&amp;amp;st=sb&lt;/a&gt;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;This is AWS parlance again. An availabilty zone is an isolated datacenter. Theoretically, spreading nodes across AZs will increase resilience as it is less likely to have catastrophic failures or outages across multiple zones.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;I don&amp;rsquo;t get money from Udemy or anyone else for writing anything on this blog. All opinions are purely my own and influenced by my own experience, not sponsorship. Your milage may vary (yada yada) but I found the course quite good: &lt;a href="https://www.udemy.com/aws-certified-solutions-architect-associate/"&gt;https://www.udemy.com/aws-certified-solutions-architect-associate/&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;For more expert readers that may sound horribly patronising, I don&amp;rsquo;t mean it to be. For many less experienced technologists the basics of networking might be more unfamiliar!&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href="#fnref1:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;See &lt;a href="https://www.consul.io/docs/internals/consensus.html"&gt;https://www.consul.io/docs/internals/consensus.html&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;A common pattern is to actually make the group size dynamic, responding to events. For example, we could have a group of servers which increases in size if the average CPU load of the hosts stays above 80% for five minutes, and scales down if it goes below 10% for ten minutes. This is more common for app and web servers and not needed for our system.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;Specifically, the current latest &lt;a href="https://aws.amazon.com/amazon-linux-ami/"&gt;Amazon Linux AMI&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;Check the admin UI every 30 seconds, more than 3 seconds indicates a timeout and failure. Two failures in a row means an unhealthy host, which will be destroyed, two successes in a row for a new host means healthy, which means it will receive traffic.&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:10"&gt;
&lt;p&gt;This is a fairly sophisticated topic in itself, see &lt;a href="https://www.consul.io/docs/internals/consensus.html"&gt;Consul - Consensus Protocol&lt;/a&gt; for details.&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:11"&gt;
&lt;p&gt;In fact, we actually have more permissions required, because in the &amp;lsquo;real&amp;rsquo; code we also have logs forwarded to CloudWatch.&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:12"&gt;
&lt;p&gt;These nodes can be removed manually, see &lt;a href="https://www.consul.io/docs/commands/force-leave.html"&gt;Consul Force Leave&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:12" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:13"&gt;
&lt;p&gt;Amazon&amp;rsquo;s service for managing and aggregating logs&amp;#160;&lt;a href="#fnref:13" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item></channel></rss>