1
0
mirror of https://github.com/funkypenguin/geek-cookbook/ synced 2025-12-13 09:46:23 +00:00

Added recipe on docker-mailserver

This commit is contained in:
David Young
2017-07-30 13:19:02 +12:00
parent bfbc87e772
commit 9c81bbf088
63 changed files with 10459 additions and 36 deletions

View File

@@ -0,0 +1,767 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="A short description of my project">
<link rel="canonical" href="https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/design/">
<meta name="author" content="David Young">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-0.16.3, mkdocs-material-1.7.4">
<title>Design - Funky Penguin's Geek's Cookbook</title>
<script src="../../assets/javascripts/modernizr-1df76c4e58.js"></script>
<link rel="stylesheet" href="../../assets/stylesheets/application-769c285a91.css">
<link rel="stylesheet" href="../../assets/stylesheets/application-02c2a4388f.palette.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body data-md-color-primary="indigo" data-md-color-accent="indigo">
<svg class="md-svg">
<defs>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://geeks-cookbook.funkypenguin.co.nz" title="Funky Penguin's Geek's Cookbook" class="md-logo md-header-nav__button">
<img src="../../images/site-logo.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
<span class="md-header-nav__parent">
Essential
</span>
Design
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" required placeholder="Search" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset">close</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result" data-md-lang-search="">
<div class="md-search-result__meta" data-md-lang-result-none="No matching documents" data-md-lang-result-one="1 matching document" data-md-lang-result-other="# matching documents">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<i class="md-logo md-nav__button">
<img src="../../images/site-logo.png">
</i>
Funky Penguin's Geek's Cookbook
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Home" class="md-nav__link">
Home
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-2" type="checkbox" id="nav-2">
<label class="md-nav__link" for="nav-2">
Introduction
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-2">
Introduction
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../README/" title="README" class="md-nav__link">
README
</a>
</li>
<li class="md-nav__item">
<a href="../../whoami/" title="whoami" class="md-nav__link">
whoami
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-3" type="checkbox" id="nav-3" checked>
<label class="md-nav__link" for="nav-3">
Essential
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-3">
Essential
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<label class="md-nav__link md-nav__link--active" for="toc">
Design
</label>
<a href="./" title="Design" class="md-nav__link md-nav__link--active">
Design
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#design-decisions" title="Design Decisions" class="md-nav__link">
Design Decisions
</a>
</li>
<li class="md-nav__item">
<a href="#security" title="Security" class="md-nav__link">
Security
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#network-flows" title="Network Flows" class="md-nav__link">
Network Flows
</a>
</li>
<li class="md-nav__item">
<a href="#authentication" title="Authentication" class="md-nav__link">
Authentication
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#high-availability" title="High availability" class="md-nav__link">
High availability
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#normal-function" title="Normal function" class="md-nav__link">
Normal function
</a>
</li>
<li class="md-nav__item">
<a href="#node-failure" title="Node failure" class="md-nav__link">
Node failure
</a>
</li>
<li class="md-nav__item">
<a href="#node-restore" title="Node restore" class="md-nav__link">
Node restore
</a>
</li>
<li class="md-nav__item">
<a href="#total-cluster-failure" title="Total cluster failure" class="md-nav__link">
Total cluster failure
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../vms/" title="VMs" class="md-nav__link">
VMs
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-nav__link">
Shared Storage (Ceph)
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-nav__link">
Shared Storage (GlusterFS)
</a>
</li>
<li class="md-nav__item">
<a href="../keepalived/" title="Keepalived" class="md-nav__link">
Keepalived
</a>
</li>
<li class="md-nav__item">
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-nav__link">
Docker Swarm Mode
</a>
</li>
<li class="md-nav__item">
<a href="../traefik/" title="Traefik" class="md-nav__link">
Traefik
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-4" type="checkbox" id="nav-4">
<label class="md-nav__link" for="nav-4">
Recommended
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-4">
Recommended
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../recipies/mail/" title="Mail Server" class="md-nav__link">
Mail Server
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#design-decisions" title="Design Decisions" class="md-nav__link">
Design Decisions
</a>
</li>
<li class="md-nav__item">
<a href="#security" title="Security" class="md-nav__link">
Security
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#network-flows" title="Network Flows" class="md-nav__link">
Network Flows
</a>
</li>
<li class="md-nav__item">
<a href="#authentication" title="Authentication" class="md-nav__link">
Authentication
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#high-availability" title="High availability" class="md-nav__link">
High availability
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#normal-function" title="Normal function" class="md-nav__link">
Normal function
</a>
</li>
<li class="md-nav__item">
<a href="#node-failure" title="Node failure" class="md-nav__link">
Node failure
</a>
</li>
<li class="md-nav__item">
<a href="#node-restore" title="Node restore" class="md-nav__link">
Node restore
</a>
</li>
<li class="md-nav__item">
<a href="#total-cluster-failure" title="Total cluster failure" class="md-nav__link">
Total cluster failure
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="design">Design<a class="headerlink" href="#design" title="Permanent link">&para;</a></h1>
<p>In the design described below, the "private cloud" platform is:</p>
<ul>
<li><strong>Highly-available</strong> (<em>can tolerate the failure of a single component</em>)</li>
<li><strong>Scalable</strong> (<em>can add resource or capacity as required</em>)</li>
<li><strong>Portable</strong> (<em>run it on your garage server today, run it in AWS tomorrow</em>)</li>
<li><strong>Secure</strong> (<em>access protected with LetsEncrypt certificates</em>)</li>
<li><strong>Automated</strong> (<em>requires minimal care and feeding</em>)</li>
</ul>
<h2 id="design-decisions">Design Decisions<a class="headerlink" href="#design-decisions" title="Permanent link">&para;</a></h2>
<p><strong>Where possible, services will be highly available.</strong></p>
<p>This means that:</p>
<ul>
<li>At least 3 docker swarm manager nodes are required, to provide fault-tolerance of a single failure.</li>
<li>GlusterFS is employed for share filesystem, because it too can be made tolerant of a single failure.</li>
</ul>
<p><strong>Where multiple solutions to a requirement exist, preference will be given to the most portable solution.</strong></p>
<p>This means that:</p>
<ul>
<li>Services are defined using docker-compose v3 YAML syntax</li>
<li>Services are portable, meaning a particular stack could be shut down and moved to a new provider with minimal effort.</li>
</ul>
<h2 id="security">Security<a class="headerlink" href="#security" title="Permanent link">&para;</a></h2>
<p>Under this design, the only inbound connections we're permitting to our docker swarm are:</p>
<h3 id="network-flows">Network Flows<a class="headerlink" href="#network-flows" title="Permanent link">&para;</a></h3>
<ul>
<li>HTTP (TCP 80) : Redirects to https</li>
<li>HTTPS (TCP 443) : Serves individual docker containers via SSL-encrypted reverse proxy</li>
</ul>
<h3 id="authentication">Authentication<a class="headerlink" href="#authentication" title="Permanent link">&para;</a></h3>
<ul>
<li>Where the proxied application provides a trusted level of authentication, or where the application requires public exposure,</li>
</ul>
<h2 id="high-availability">High availability<a class="headerlink" href="#high-availability" title="Permanent link">&para;</a></h2>
<h3 id="normal-function">Normal function<a class="headerlink" href="#normal-function" title="Permanent link">&para;</a></h3>
<p>Assuming 3 nodes, under normal circumstances the following is illustrated:</p>
<ul>
<li>All 3 nodes provide shared storage via GlusterFS, which is provided by a docker container on each node. (i.e., not running in swarm mode)</li>
<li>All 3 nodes participate in the Docker Swarm as managers.</li>
<li>The various containers belonging to the application "stacks" deployed within Docker Swarm are automatically distributed amongst the swarm nodes.</li>
<li>Persistent storage for the containers is provide via GlusterFS mount.</li>
<li>The <strong>traefik</strong> service (in swarm mode) receives incoming requests (on http and https), and forwards them to individual containers. Traefik knows the containers names because it's able to access the docker socket.</li>
<li>All 3 nodes run keepalived, at different priorities. Since traefik is running as a swarm service and listening on TCP 80/443, requests made to the keepalived VIP and arriving at <strong>any</strong> of the swarm nodes will be forwarded to the traefik container (no matter which node it's on), and then onto the target backend.</li>
</ul>
<p><img alt="HA function" src="../images/docker-swarm-ha-function.png" /></p>
<h3 id="node-failure">Node failure<a class="headerlink" href="#node-failure" title="Permanent link">&para;</a></h3>
<p>In the case of a failure (or scheduled maintenance) of one of the nodes, the following is illustrated:</p>
<ul>
<li>The failed node no longer participates in GlusterFS, but the remaining nodes provide enough fault-tolerance for the cluster to operate.</li>
<li>The remaining two nodes in Docker Swarm achieve a quorum and agree that the failed node is to be removed.</li>
<li>The (possibly new) leader manager node reschedules the containers known to be running on the failed node, onto other nodes.</li>
<li>The <strong>traefik</strong> service is either restarted or unaffected, and as the backend containers stop/start and change IP, traefik is aware and updates accordingly.</li>
<li>The keepalived VIP continues to function on the remaining nodes, and docker swarm continues to forward any traffic received on TCP 80/443 to the appropriate node.</li>
</ul>
<p><img alt="HA function" src="../images/docker-swarm-node-failure.png" /></p>
<h3 id="node-restore">Node restore<a class="headerlink" href="#node-restore" title="Permanent link">&para;</a></h3>
<p>When the failed (or upgraded) host is restored to service, the following is illustrated:</p>
<ul>
<li>GlusterFS regains full redundancy</li>
<li>Docker Swarm managers become aware of the recovered node, and will use it for scheduling <strong>new</strong> containers</li>
<li>Existing containers which were migrated off the node are not migrated backend</li>
<li>Keepalived VIP regains full redundancy</li>
</ul>
<p><img alt="HA function" src="../images/docker-swarm-node-restore.png" /></p>
<h3 id="total-cluster-failure">Total cluster failure<a class="headerlink" href="#total-cluster-failure" title="Permanent link">&para;</a></h3>
<p>A day after writing this, my environment suffered a fault whereby all 3 VMs were unexpectedly and simultaneously powered off.</p>
<p>Upon restore, docker failed to start on one of the VMs due to local disk space issue<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>. However, the other two VMs started, established the swarm, mounted their shared storage, and started up all the containers (services) which were managed by the swarm.</p>
<p>In summary, although I suffered an <strong>unplanned power outage to all of my infrastructure</strong>, followed by a <strong>failure of a third of my hosts</strong>... <mark>all my platforms are 100% available with <strong>absolutely no manual intervention</strong></mark>.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>Since there's no impact to availability, I can fix (or just reinstall) the failed node whenever convenient.&#160;<a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>
<h2 id="__comments">Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = "https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/design/";
this.page.identifier =
"/ha-docker-swarm/design/";
};
(function() {
var d = document, s = d.createElement("script");
s.src = "//geeks-cookbook.disqus.com/embed.js";
s.setAttribute("data-timestamp", +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../../whoami/" title="whoami" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
whoami
</span>
</div>
</a>
<a href="../vms/" title="VMs" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
VMs
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2016 - 2017 David Young
</div>
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/" title="Material for MkDocs">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<a href="https://github.com/funkypenguin" class="md-footer-social__link fa fa-github"></a>
<a href="https://twitter.com/funkypenguin" class="md-footer-social__link fa fa-twitter"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application-c35428f87f.js"></script>
<script>app.initialize({url:{base:"../.."}})</script>
<script src="../../extras/javascript/piwik.js"></script>
<script>!function(e,t,a,n,o,c,i){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,c=t.createElement(a),i=t.getElementsByTagName(a)[0],c.async=1,c.src=n,i.parentNode.insertBefore(c,i)}(window,document,"script","https://www.google-analytics.com/analytics.js","ga"),ga("create","UA-139253-18","auto"),ga("set","anonymizeIp",!0),ga("send","pageview");var links=document.getElementsByTagName("a");Array.prototype.map.call(links,function(e){e.host!=document.location.host&&e.addEventListener("click",function(){var t=e.getAttribute("data-md-action")||"follow";ga("send","event","outbound",t,e.href)})});var query=document.forms.search.query;query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})</script>
</body>
</html>

View File

@@ -0,0 +1,976 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="A short description of my project">
<link rel="canonical" href="https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/docker-swarm-mode/">
<meta name="author" content="David Young">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-0.16.3, mkdocs-material-1.7.4">
<title>Docker Swarm Mode - Funky Penguin's Geek's Cookbook</title>
<script src="../../assets/javascripts/modernizr-1df76c4e58.js"></script>
<link rel="stylesheet" href="../../assets/stylesheets/application-769c285a91.css">
<link rel="stylesheet" href="../../assets/stylesheets/application-02c2a4388f.palette.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body data-md-color-primary="indigo" data-md-color-accent="indigo">
<svg class="md-svg">
<defs>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://geeks-cookbook.funkypenguin.co.nz" title="Funky Penguin's Geek's Cookbook" class="md-logo md-header-nav__button">
<img src="../../images/site-logo.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
<span class="md-header-nav__parent">
Essential
</span>
Docker Swarm Mode
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" required placeholder="Search" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset">close</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result" data-md-lang-search="">
<div class="md-search-result__meta" data-md-lang-result-none="No matching documents" data-md-lang-result-one="1 matching document" data-md-lang-result-other="# matching documents">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<i class="md-logo md-nav__button">
<img src="../../images/site-logo.png">
</i>
Funky Penguin's Geek's Cookbook
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Home" class="md-nav__link">
Home
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-2" type="checkbox" id="nav-2">
<label class="md-nav__link" for="nav-2">
Introduction
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-2">
Introduction
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../README/" title="README" class="md-nav__link">
README
</a>
</li>
<li class="md-nav__item">
<a href="../../whoami/" title="whoami" class="md-nav__link">
whoami
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-3" type="checkbox" id="nav-3" checked>
<label class="md-nav__link" for="nav-3">
Essential
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-3">
Essential
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../design/" title="Design" class="md-nav__link">
Design
</a>
</li>
<li class="md-nav__item">
<a href="../vms/" title="VMs" class="md-nav__link">
VMs
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-nav__link">
Shared Storage (Ceph)
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-nav__link">
Shared Storage (GlusterFS)
</a>
</li>
<li class="md-nav__item">
<a href="../keepalived/" title="Keepalived" class="md-nav__link">
Keepalived
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<label class="md-nav__link md-nav__link--active" for="toc">
Docker Swarm Mode
</label>
<a href="./" title="Docker Swarm Mode" class="md-nav__link md-nav__link--active">
Docker Swarm Mode
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#release-the-swarm" title="Release the swarm!" class="md-nav__link">
Release the swarm!
</a>
</li>
<li class="md-nav__item">
<a href="#create-registry-mirror" title="Create registry mirror" class="md-nav__link">
Create registry mirror
</a>
</li>
<li class="md-nav__item">
<a href="#enable-registry-mirror-and-experimental-features" title="Enable registry mirror and experimental features" class="md-nav__link">
Enable registry mirror and experimental features
</a>
</li>
<li class="md-nav__item">
<a href="#setup-automated-cleanup" title="Setup automated cleanup" class="md-nav__link">
Setup automated cleanup
</a>
</li>
<li class="md-nav__item">
<a href="#tweaks" title="Tweaks" class="md-nav__link">
Tweaks
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#setup-registry" title="Setup registry" class="md-nav__link">
Setup registry
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../traefik/" title="Traefik" class="md-nav__link">
Traefik
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-4" type="checkbox" id="nav-4">
<label class="md-nav__link" for="nav-4">
Recommended
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-4">
Recommended
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../recipies/mail/" title="Mail Server" class="md-nav__link">
Mail Server
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#release-the-swarm" title="Release the swarm!" class="md-nav__link">
Release the swarm!
</a>
</li>
<li class="md-nav__item">
<a href="#create-registry-mirror" title="Create registry mirror" class="md-nav__link">
Create registry mirror
</a>
</li>
<li class="md-nav__item">
<a href="#enable-registry-mirror-and-experimental-features" title="Enable registry mirror and experimental features" class="md-nav__link">
Enable registry mirror and experimental features
</a>
</li>
<li class="md-nav__item">
<a href="#setup-automated-cleanup" title="Setup automated cleanup" class="md-nav__link">
Setup automated cleanup
</a>
</li>
<li class="md-nav__item">
<a href="#tweaks" title="Tweaks" class="md-nav__link">
Tweaks
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#setup-registry" title="Setup registry" class="md-nav__link">
Setup registry
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="docker-swarm-mode">Docker Swarm Mode<a class="headerlink" href="#docker-swarm-mode" title="Permanent link">&para;</a></h1>
<p>For truly highly-available services with Docker containers, we need an orchestration system. Docker Swarm (as defined at 1.13) is the simplest way to achieve redundancy, such that a single docker host could be turned off, and none of our services will be interrupted.</p>
<h2 id="ingredients">Ingredients<a class="headerlink" href="#ingredients" title="Permanent link">&para;</a></h2>
<ul>
<li>3 x CentOS Atomic hosts (bare-metal or VMs). A reasonable minimum would be:</li>
<li>1 x vCPU</li>
<li>1GB repo_name</li>
<li>10GB HDD</li>
<li>Hosts must be within the same subnet, and connected on a low-latency link (i.e., no WAN links)</li>
</ul>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permanent link">&para;</a></h2>
<h3 id="release-the-swarm">Release the swarm!<a class="headerlink" href="#release-the-swarm" title="Permanent link">&para;</a></h3>
<p>Now, to launch my swarm:</p>
<p><code class="codehilite">docker swarm init</code></p>
<p>Yeah, that was it. Now I have a 1-node swarm.</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@ds1 ~]# docker swarm init
Swarm initialized: current node (b54vls3wf8xztwfz79nlkivt8) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-2orjbzjzjvm1bbo736xxmxzwaf4rffxwi0tu3zopal4xk4mja0-bsud7xnvhv4cicwi7l6c9s6l0 \
202.170.164.47:2377
To add a manager to this swarm, run &#39;docker swarm join-token manager&#39; and follow the instructions.
[root@ds1 ~]#
</pre></div>
</td></tr></table>
<p>Run <code class="codehilite">docker node ls</code> to confirm that I have a 1-node swarm:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@ds1 ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
b54vls3wf8xztwfz79nlkivt8 * ds1.funkypenguin.co.nz Ready Active Leader
[root@ds1 ~]#
</pre></div>
</td></tr></table>
<p>Note that when I ran <code class="codehilite">docker swarm init</code> above, the CLI output gave me a command to run to join further nodes to my swarm. This would join the nodes as <strong>workers</strong> (as opposed to <strong>managers</strong>). Workers can easily be promoted to managers (and demoted again), but since we know that we want our other two nodes to be managers too, it's simpler just to add them to the swarm as managers immediately.</p>
<p>On the first swarm node, generate the necessary token to join another manager by running <code class="codehilite">docker swarm join-token manager</code>:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@ds1 ~]# docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-2orjbzjzjvm1bbo736xxmxzwaf4rffxwi0tu3zopal4xk4mja0-cfm24bq2zvfkcwujwlp5zqxta \
202.170.164.47:2377
[root@ds1 ~]#
</pre></div>
</td></tr></table>
<p>Run the command provided on your second node to join it to the swarm as a manager. After adding the second node, the output of <code class="codehilite">docker node ls</code> (on either host) should reflect two nodes:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@ds2 davidy]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
b54vls3wf8xztwfz79nlkivt8 ds1.funkypenguin.co.nz Ready Active Leader
xmw49jt5a1j87a6ihul76gbgy * ds2.funkypenguin.co.nz Ready Active Reachable
[root@ds2 davidy]#
</pre></div>
</td></tr></table>
<p>Repeat the process to add your third node. <strong>You need a new token for the third node, don't re-use the manager token you generated for the second node</strong>.</p>
<div class="admonition warning">
<p class="admonition-title">Seriously. Don't use a token more than once, else it's swarm-rebuilding time.</p>
</div>
<p>Finally, <code class="codehilite">docker node ls</code> should reflect that you have 3 reachable manager nodes, one of whom is the "Leader":</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@ds3 ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
36b4twca7i3hkb7qr77i0pr9i ds1.openstack.dev.safenz.net Ready Active Reachable
l14rfzazbmibh1p9wcoivkv1s * ds3.openstack.dev.safenz.net Ready Active Reachable
tfsgxmu7q23nuo51wwa4ycpsj ds2.openstack.dev.safenz.net Ready Active Leader
[root@ds3 ~]#
</pre></div>
</td></tr></table>
<h3 id="create-registry-mirror">Create registry mirror<a class="headerlink" href="#create-registry-mirror" title="Permanent link">&para;</a></h3>
<p>Although we now have shared storage for our persistent container data, our docker nodes don't share any other docker data, such as container images. This results in an inefficiency - every node which participates in the swarm will, at some point, need the docker image for every container deployed in the swarm.</p>
<p>When dealing with large container (looking at you, GitLab!), this can result in several gigabytes of wasted bandwidth per-node, and long delays when restarting containers on an alternate node. (<em>It also wastes disk space on each node, but we'll get to that in the next section</em>)</p>
<p>The solution is to run an official Docker registry container as a <a href="https://docs.docker.com/registry/recipes/mirror/">"pull-through" cache, or "registry mirror"</a>. By using our persistent storage for the registry cache, we can ensure we have a single copy of all the containers we've pulled at least once. After the first pull, any subsequent pulls from our nodes will use the cached version from our registry mirror. As a result, services are available more quickly when restarting container nodes, and we can be more aggressive about cleaning up unused containers on our nodes (more later)</p>
<p>The registry mirror runs as a swarm stack, using a simple docker-compose.yml. Customize <strong>your mirror FQDN</strong> below, so that Traefik will generate the appropriate LetsEncrypt certificates for it, and make it available via HTTPS.</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>version: &quot;3&quot;
services:
registry-mirror:
image: registry:2
networks:
- traefik
deploy:
labels:
- traefik.frontend.rule=Host:&lt;your mirror FQDN&gt;
- traefik.docker.network=traefik
- traefik.port=5000
ports:
- 5000:5000
volumes:
- /var/data/registry/registry-mirror-data:/var/lib/registry
- /var/data/registry/registry-mirror-config.yml:/etc/docker/registry/config.yml
networks:
traefik:
external: true
</pre></div>
</td></tr></table>
<div class="admonition note">
<p class="admonition-title">Unencrypted registry</p>
<p>We create this registry without consideration for SSL, which will fail if we attempt to use the registry directly. However, we're going to use the HTTPS-proxied version via Traefik, leveraging Traefik to manage the LetsEncrypt certificates required.</p>
</div>
<p>Create registry/registry-mirror-config.yml as follows:
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22</pre></div></td><td class="code"><div class="codehilite"><pre><span></span><span class="n">version</span><span class="o">:</span> <span class="mf">0.1</span>
<span class="n">log</span><span class="o">:</span>
<span class="n">fields</span><span class="o">:</span>
<span class="n">service</span><span class="o">:</span> <span class="n">registry</span>
<span class="n">storage</span><span class="o">:</span>
<span class="n">cache</span><span class="o">:</span>
<span class="n">blobdescriptor</span><span class="o">:</span> <span class="n">inmemory</span>
<span class="n">filesystem</span><span class="o">:</span>
<span class="n">rootdirectory</span><span class="o">:</span> <span class="sr">/var/lib/</span><span class="n">registry</span>
<span class="n">delete</span><span class="o">:</span>
<span class="n">enabled</span><span class="o">:</span> <span class="kc">true</span>
<span class="n">http</span><span class="o">:</span>
<span class="n">addr</span><span class="o">:</span> <span class="o">:</span><span class="mi">5000</span>
<span class="n">headers</span><span class="o">:</span>
<span class="n">X</span><span class="o">-</span><span class="n">Content</span><span class="o">-</span><span class="n">Type</span><span class="o">-</span><span class="n">Options</span><span class="o">:</span> <span class="o">[</span><span class="n">nosniff</span><span class="o">]</span>
<span class="n">health</span><span class="o">:</span>
<span class="n">storagedriver</span><span class="o">:</span>
<span class="n">enabled</span><span class="o">:</span> <span class="kc">true</span>
<span class="n">interval</span><span class="o">:</span> <span class="mi">10</span><span class="n">s</span>
<span class="n">threshold</span><span class="o">:</span> <span class="mi">3</span>
<span class="n">proxy</span><span class="o">:</span>
<span class="n">remoteurl</span><span class="o">:</span> <span class="n">https</span><span class="o">://</span><span class="n">registry</span><span class="o">-</span><span class="mi">1</span><span class="o">.</span><span class="na">docker</span><span class="o">.</span><span class="na">io</span>
</pre></div>
</td></tr></table></p>
<h3 id="enable-registry-mirror-and-experimental-features">Enable registry mirror and experimental features<a class="headerlink" href="#enable-registry-mirror-and-experimental-features" title="Permanent link">&para;</a></h3>
<p>To tell docker to use the registry mirror, and in order to be able to watch the logs of any service from any manager node (<em>an experimental feature in the current Atomic docker build</em>), edit <strong>/etc/docker-latest/daemon.json</strong> on each node, and change from:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>{
&quot;log-driver&quot;: &quot;journald&quot;,
&quot;signature-verification&quot;: false
}
</pre></div>
</td></tr></table>
<p>To:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>{
&quot;log-driver&quot;: &quot;journald&quot;,
&quot;signature-verification&quot;: false,
&quot;experimental&quot;: true,
&quot;registry-mirrors&quot;: [&quot;https://&lt;your registry mirror FQDN&gt;&quot;]
}
</pre></div>
</td></tr></table>
<div class="admonition tip">
<p>Note the extra comma required after "false" above</p>
</div>
<h3 id="setup-automated-cleanup">Setup automated cleanup<a class="headerlink" href="#setup-automated-cleanup" title="Permanent link">&para;</a></h3>
<p>This needs to be a docker-compose.yml file, excluding trusted images (like glusterfs, traefik, etc)
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock:rw \
-v /var/lib/docker:/var/lib/docker:rw \
meltwater/docker-cleanup:latest
</pre></div>
</td></tr></table></p>
<h3 id="tweaks">Tweaks<a class="headerlink" href="#tweaks" title="Permanent link">&para;</a></h3>
<p>Add some handy bash auto-completion for docker. Without this, you'll get annoyed that you can't autocomplete <code class="codehilite">docker stack deploy &lt;blah&gt; -c &lt;blah.yml&gt;</code> commands.</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>cd /etc/bash_completion.d/
curl -O https://raw.githubusercontent.com/docker/cli/b75596e1e4d5295ac69b9934d1bd8aff691a0de8/contrib/completion/bash/docker
</pre></div>
</td></tr></table>
<p>Install some useful bash aliases on each host
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>cd ~
curl -O https://gitlab.funkypenguin.co.nz/funkypenguin/geeks-cookbook-recipies/raw/master/bash/gcb-aliases.sh
echo &#39;source ~/gcb-aliases.sh&#39; &gt;&gt; ~/.bash_profile
</pre></div>
</td></tr></table></p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>mkdir ~/dockersock
cd ~/dockersock
curl -O https://raw.githubusercontent.com/dpw/selinux-dockersock/master/Makefile
curl -O https://raw.githubusercontent.com/dpw/selinux-dockersock/master/dockersock.te
make &amp;&amp; semodule -i dockersock.pp
</pre></div>
</td></tr></table>
<h2 id="setup-registry">Setup registry<a class="headerlink" href="#setup-registry" title="Permanent link">&para;</a></h2>
<p>docker run -d \
-p 5000:5000 \
--restart=always \
--name registry \
-v /mnt/registry:/var/lib/registry \
registry:2</p>
<p>{
"log-driver": "journald",
"signature-verification": false,
"experimental": true,
"registry-mirrors": ["<a href="https://registry-mirror.funkypenguin.co.nz">https://registry-mirror.funkypenguin.co.nz</a>"]
}</p>
<p>registry-mirror:
image: registry:2
ports:
- 5000:5000
environment:
volumes:
- /var/data/registry:/var/lib/registry</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8</pre></div></td><td class="code"><div class="codehilite"><pre><span></span> [root@ds1 dockersock]# docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-09c94wv0opw0y6xg67uzjl13pnv8lxxn586hrg5f47spso9l6j-6zn3dxk7c4zkb19r61owasi15 \
192.168.31.11:2377
[root@ds1 dockersock]#
</pre></div>
</td></tr></table>
<h2 id="__comments">Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = "https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/docker-swarm-mode/";
this.page.identifier =
"/ha-docker-swarm/docker-swarm-mode/";
};
(function() {
var d = document, s = d.createElement("script");
s.src = "//geeks-cookbook.disqus.com/embed.js";
s.setAttribute("data-timestamp", +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../keepalived/" title="Keepalived" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Keepalived
</span>
</div>
</a>
<a href="../traefik/" title="Traefik" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Traefik
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2016 - 2017 David Young
</div>
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/" title="Material for MkDocs">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<a href="https://github.com/funkypenguin" class="md-footer-social__link fa fa-github"></a>
<a href="https://twitter.com/funkypenguin" class="md-footer-social__link fa fa-twitter"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application-c35428f87f.js"></script>
<script>app.initialize({url:{base:"../.."}})</script>
<script src="../../extras/javascript/piwik.js"></script>
<script>!function(e,t,a,n,o,c,i){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,c=t.createElement(a),i=t.getElementsByTagName(a)[0],c.async=1,c.src=n,i.parentNode.insertBefore(c,i)}(window,document,"script","https://www.google-analytics.com/analytics.js","ga"),ga("create","UA-139253-18","auto"),ga("set","anonymizeIp",!0),ga("send","pageview");var links=document.getElementsByTagName("a");Array.prototype.map.call(links,function(e){e.host!=document.location.host&&e.addEventListener("click",function(){var t=e.getAttribute("data-md-action")||"follow";ga("send","event","outbound",t,e.href)})});var query=document.forms.search.query;query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -0,0 +1,709 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="A short description of my project">
<link rel="canonical" href="https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/keepalived/">
<meta name="author" content="David Young">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-0.16.3, mkdocs-material-1.7.4">
<title>Keepalived - Funky Penguin's Geek's Cookbook</title>
<script src="../../assets/javascripts/modernizr-1df76c4e58.js"></script>
<link rel="stylesheet" href="../../assets/stylesheets/application-769c285a91.css">
<link rel="stylesheet" href="../../assets/stylesheets/application-02c2a4388f.palette.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body data-md-color-primary="indigo" data-md-color-accent="indigo">
<svg class="md-svg">
<defs>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://geeks-cookbook.funkypenguin.co.nz" title="Funky Penguin's Geek's Cookbook" class="md-logo md-header-nav__button">
<img src="../../images/site-logo.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
<span class="md-header-nav__parent">
Essential
</span>
Keepalived
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" required placeholder="Search" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset">close</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result" data-md-lang-search="">
<div class="md-search-result__meta" data-md-lang-result-none="No matching documents" data-md-lang-result-one="1 matching document" data-md-lang-result-other="# matching documents">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<i class="md-logo md-nav__button">
<img src="../../images/site-logo.png">
</i>
Funky Penguin's Geek's Cookbook
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Home" class="md-nav__link">
Home
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-2" type="checkbox" id="nav-2">
<label class="md-nav__link" for="nav-2">
Introduction
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-2">
Introduction
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../README/" title="README" class="md-nav__link">
README
</a>
</li>
<li class="md-nav__item">
<a href="../../whoami/" title="whoami" class="md-nav__link">
whoami
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-3" type="checkbox" id="nav-3" checked>
<label class="md-nav__link" for="nav-3">
Essential
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-3">
Essential
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../design/" title="Design" class="md-nav__link">
Design
</a>
</li>
<li class="md-nav__item">
<a href="../vms/" title="VMs" class="md-nav__link">
VMs
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-nav__link">
Shared Storage (Ceph)
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-nav__link">
Shared Storage (GlusterFS)
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<label class="md-nav__link md-nav__link--active" for="toc">
Keepalived
</label>
<a href="./" title="Keepalived" class="md-nav__link md-nav__link--active">
Keepalived
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#enable-ipvs-module" title="Enable IPVS module" class="md-nav__link">
Enable IPVS module
</a>
</li>
<li class="md-nav__item">
<a href="#setup-nodes" title="Setup nodes" class="md-nav__link">
Setup nodes
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's notes" class="md-nav__link">
Chef's notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-nav__link">
Docker Swarm Mode
</a>
</li>
<li class="md-nav__item">
<a href="../traefik/" title="Traefik" class="md-nav__link">
Traefik
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-4" type="checkbox" id="nav-4">
<label class="md-nav__link" for="nav-4">
Recommended
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-4">
Recommended
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../recipies/mail/" title="Mail Server" class="md-nav__link">
Mail Server
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#enable-ipvs-module" title="Enable IPVS module" class="md-nav__link">
Enable IPVS module
</a>
</li>
<li class="md-nav__item">
<a href="#setup-nodes" title="Setup nodes" class="md-nav__link">
Setup nodes
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's notes" class="md-nav__link">
Chef's notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="keepalived">Keepalived<a class="headerlink" href="#keepalived" title="Permanent link">&para;</a></h1>
<p>While having a self-healing, scalable docker swarm is great for availability and scalability, none of that is any good if nobody can connect to your cluster.</p>
<p>In order to provide seamless external access to clustered resources, regardless of which node they're on and tolerant of node failure, you need to present a single IP to the world for external access.</p>
<p>Normally this is done using a HA loadbalancer, but since Docker Swarm aready provides the load-balancing capabilities (routing mesh), all we need for seamless HA is a virtual IP which will be provided by more than one docker node.</p>
<p>This is accomplished with the use of keepalived on at least two nodes.</p>
<h2 id="ingredients">Ingredients<a class="headerlink" href="#ingredients" title="Permanent link">&para;</a></h2>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>Already deployed:
[X] At least 2 x CentOS/Fedora Atomic VMs
[X] low-latency link (i.e., no WAN links)
New:
[ ] 3 x IPv4 addresses (one for each node and one for the virtual IP)
</pre></div>
</td></tr></table>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permanent link">&para;</a></h2>
<h3 id="enable-ipvs-module">Enable IPVS module<a class="headerlink" href="#enable-ipvs-module" title="Permanent link">&para;</a></h3>
<p>On all nodes which will participate in keepalived, we need the "ip_vs" kernel module, in order to permit serivces to bind to non-local interface addresses.</p>
<p>Set this up once for both the primary and secondary nodes, by running:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>echo &quot;modprobe ip_vs&quot; &gt;&gt; /etc/rc.local
modprobe ip_vs
</pre></div>
</td></tr></table>
<h3 id="setup-nodes">Setup nodes<a class="headerlink" href="#setup-nodes" title="Permanent link">&para;</a></h3>
<p>Assuming your IPs are as follows:</p>
<ul>
<li>192.168.4.1 : Primary</li>
<li>192.168.4.2 : Secondary</li>
<li>192.168.4.3 : Virtual</li>
</ul>
<p>Run the following on the primary
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run -d --name keepalived --restart=always \
--cap-add=NET_ADMIN --net=host \
-e KEEPALIVED_UNICAST_PEERS=&quot;#PYTHON2BASH:[&#39;192.168.4.1&#39;, &#39;192.168.4.2&#39;]&quot; \
-e KEEPALIVED_VIRTUAL_IPS=192.168.4.3 \
-e KEEPALIVED_PRIORITY=200 \
osixia/keepalived:1.3.5
</pre></div>
</td></tr></table></p>
<p>And on the secondary:
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run -d --name keepalived --restart=always \
--cap-add=NET_ADMIN --net=host \
-e KEEPALIVED_UNICAST_PEERS=&quot;#PYTHON2BASH:[&#39;192.168.4.1&#39;, &#39;192.168.4.2&#39;]&quot; \
-e KEEPALIVED_VIRTUAL_IPS=192.168.4.3 \
-e KEEPALIVED_PRIORITY=100 \
osixia/keepalived:1.3.5
</pre></div>
</td></tr></table></p>
<h2 id="serving">Serving<a class="headerlink" href="#serving" title="Permanent link">&para;</a></h2>
<p>That's it. Each node will talk to the other via unicast (no need to un-firewall multicast addresses), and the node with the highest priority gets to be the master. When ingress traffic arrives on the master node via the VIP, docker's routing mesh will deliver it to the appropriate docker node.</p>
<h2 id="chefs-notes">Chef's notes<a class="headerlink" href="#chefs-notes" title="Permanent link">&para;</a></h2>
<ol>
<li>Some hosting platforms (OpenStack, for one) won't allow you to simply "claim" a virtual IP. Each node is only able to receive traffic targetted to its unique IP. In this case, keepalived is not the right solution, and a platform-specific load-balancing solution should be used. In OpenStack, this is Neutron's "Load Balancer As A Service" (LBAAS) component. AWS and Azure would likely include similar protections.</li>
<li>More than 2 nodes can participate in keepalived. Simply ensure that each node has the appropriate priority set, and the node with the highest priority will become the master.</li>
</ol>
<h2 id="__comments">Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = "https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/keepalived/";
this.page.identifier =
"/ha-docker-swarm/keepalived/";
};
(function() {
var d = document, s = d.createElement("script");
s.src = "//geeks-cookbook.disqus.com/embed.js";
s.setAttribute("data-timestamp", +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Shared Storage (GlusterFS)
</span>
</div>
</a>
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Docker Swarm Mode
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2016 - 2017 David Young
</div>
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/" title="Material for MkDocs">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<a href="https://github.com/funkypenguin" class="md-footer-social__link fa fa-github"></a>
<a href="https://twitter.com/funkypenguin" class="md-footer-social__link fa fa-twitter"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application-c35428f87f.js"></script>
<script>app.initialize({url:{base:"../.."}})</script>
<script src="../../extras/javascript/piwik.js"></script>
<script>!function(e,t,a,n,o,c,i){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,c=t.createElement(a),i=t.getElementsByTagName(a)[0],c.async=1,c.src=n,i.parentNode.insertBefore(c,i)}(window,document,"script","https://www.google-analytics.com/analytics.js","ga"),ga("create","UA-139253-18","auto"),ga("set","anonymizeIp",!0),ga("send","pageview");var links=document.getElementsByTagName("a");Array.prototype.map.call(links,function(e){e.host!=document.location.host&&e.addEventListener("click",function(){var t=e.getAttribute("data-md-action")||"follow";ga("send","event","outbound",t,e.href)})});var query=document.forms.search.query;query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})</script>
</body>
</html>

View File

@@ -0,0 +1,976 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="A short description of my project">
<link rel="canonical" href="https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/shared-storage-ceph/">
<meta name="author" content="David Young">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-0.16.3, mkdocs-material-1.7.4">
<title>Shared Storage (Ceph) - Funky Penguin's Geek's Cookbook</title>
<script src="../../assets/javascripts/modernizr-1df76c4e58.js"></script>
<link rel="stylesheet" href="../../assets/stylesheets/application-769c285a91.css">
<link rel="stylesheet" href="../../assets/stylesheets/application-02c2a4388f.palette.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body data-md-color-primary="indigo" data-md-color-accent="indigo">
<svg class="md-svg">
<defs>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://geeks-cookbook.funkypenguin.co.nz" title="Funky Penguin's Geek's Cookbook" class="md-logo md-header-nav__button">
<img src="../../images/site-logo.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
<span class="md-header-nav__parent">
Essential
</span>
Shared Storage (Ceph)
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" required placeholder="Search" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset">close</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result" data-md-lang-search="">
<div class="md-search-result__meta" data-md-lang-result-none="No matching documents" data-md-lang-result-one="1 matching document" data-md-lang-result-other="# matching documents">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<i class="md-logo md-nav__button">
<img src="../../images/site-logo.png">
</i>
Funky Penguin's Geek's Cookbook
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Home" class="md-nav__link">
Home
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-2" type="checkbox" id="nav-2">
<label class="md-nav__link" for="nav-2">
Introduction
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-2">
Introduction
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../README/" title="README" class="md-nav__link">
README
</a>
</li>
<li class="md-nav__item">
<a href="../../whoami/" title="whoami" class="md-nav__link">
whoami
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-3" type="checkbox" id="nav-3" checked>
<label class="md-nav__link" for="nav-3">
Essential
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-3">
Essential
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../design/" title="Design" class="md-nav__link">
Design
</a>
</li>
<li class="md-nav__item">
<a href="../vms/" title="VMs" class="md-nav__link">
VMs
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<label class="md-nav__link md-nav__link--active" for="toc">
Shared Storage (Ceph)
</label>
<a href="./" title="Shared Storage (Ceph)" class="md-nav__link md-nav__link--active">
Shared Storage (Ceph)
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#design" title="Design" class="md-nav__link">
Design
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#why-not-glusterfs" title="Why not GlusterFS?" class="md-nav__link">
Why not GlusterFS?
</a>
</li>
<li class="md-nav__item">
<a href="#why-ceph" title="Why Ceph?" class="md-nav__link">
Why Ceph?
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#selinux" title="SELinux" class="md-nav__link">
SELinux
</a>
</li>
<li class="md-nav__item">
<a href="#setup-monitors" title="Setup Monitors" class="md-nav__link">
Setup Monitors
</a>
</li>
<li class="md-nav__item">
<a href="#setup-osds" title="Setup OSDs" class="md-nav__link">
Setup OSDs
</a>
</li>
<li class="md-nav__item">
<a href="#setup-mdss" title="Setup MDSs" class="md-nav__link">
Setup MDSs
</a>
</li>
<li class="md-nav__item">
<a href="#apply-tweaks" title="Apply tweaks" class="md-nav__link">
Apply tweaks
</a>
</li>
<li class="md-nav__item">
<a href="#create-credentials-for-swarm" title="Create credentials for swarm" class="md-nav__link">
Create credentials for swarm
</a>
</li>
<li class="md-nav__item">
<a href="#mount-mds-volume" title="Mount MDS volume" class="md-nav__link">
Mount MDS volume
</a>
</li>
<li class="md-nav__item">
<a href="#install-docker-volume-plugin" title="Install docker-volume plugin" class="md-nav__link">
Install docker-volume plugin
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's Notes" class="md-nav__link">
Chef's Notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-nav__link">
Shared Storage (GlusterFS)
</a>
</li>
<li class="md-nav__item">
<a href="../keepalived/" title="Keepalived" class="md-nav__link">
Keepalived
</a>
</li>
<li class="md-nav__item">
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-nav__link">
Docker Swarm Mode
</a>
</li>
<li class="md-nav__item">
<a href="../traefik/" title="Traefik" class="md-nav__link">
Traefik
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-4" type="checkbox" id="nav-4">
<label class="md-nav__link" for="nav-4">
Recommended
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-4">
Recommended
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../recipies/mail/" title="Mail Server" class="md-nav__link">
Mail Server
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#design" title="Design" class="md-nav__link">
Design
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#why-not-glusterfs" title="Why not GlusterFS?" class="md-nav__link">
Why not GlusterFS?
</a>
</li>
<li class="md-nav__item">
<a href="#why-ceph" title="Why Ceph?" class="md-nav__link">
Why Ceph?
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#selinux" title="SELinux" class="md-nav__link">
SELinux
</a>
</li>
<li class="md-nav__item">
<a href="#setup-monitors" title="Setup Monitors" class="md-nav__link">
Setup Monitors
</a>
</li>
<li class="md-nav__item">
<a href="#setup-osds" title="Setup OSDs" class="md-nav__link">
Setup OSDs
</a>
</li>
<li class="md-nav__item">
<a href="#setup-mdss" title="Setup MDSs" class="md-nav__link">
Setup MDSs
</a>
</li>
<li class="md-nav__item">
<a href="#apply-tweaks" title="Apply tweaks" class="md-nav__link">
Apply tweaks
</a>
</li>
<li class="md-nav__item">
<a href="#create-credentials-for-swarm" title="Create credentials for swarm" class="md-nav__link">
Create credentials for swarm
</a>
</li>
<li class="md-nav__item">
<a href="#mount-mds-volume" title="Mount MDS volume" class="md-nav__link">
Mount MDS volume
</a>
</li>
<li class="md-nav__item">
<a href="#install-docker-volume-plugin" title="Install docker-volume plugin" class="md-nav__link">
Install docker-volume plugin
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's Notes" class="md-nav__link">
Chef's Notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="shared-storage-ceph">Shared Storage (Ceph)<a class="headerlink" href="#shared-storage-ceph" title="Permanent link">&para;</a></h1>
<p>While Docker Swarm is great for keeping containers running (<em>and restarting those that fail</em>), it does nothing for persistent storage. This means if you actually want your containers to keep any data persistent across restarts (<em>hint: you do!</em>), you need to provide shared storage to every docker node.</p>
<h2 id="design">Design<a class="headerlink" href="#design" title="Permanent link">&para;</a></h2>
<h3 id="why-not-glusterfs">Why not GlusterFS?<a class="headerlink" href="#why-not-glusterfs" title="Permanent link">&para;</a></h3>
<p>I originally provided shared storage to my nodes using GlusterFS (see the next recipe for details), but found it difficult to deal with because:</p>
<ol>
<li>GlusterFS requires (n) "bricks", where (n) <strong>has</strong> to be a multiple of your replica count. I.e., if you want 2 copies of everything on shared storage (the minimum to provide redundancy), you <strong>must</strong> have either 2, 4, 6 (etc..) bricks. The HA swarm design calls for minimum of 3 nodes, and so under GlusterFS, my third node can't participate in shared storage at all, unless I start doubling up on bricks-per-node (which then impacts redundancy)</li>
<li>GlusterFS turns out to be a giant PITA when you want to restore a failed node. There are at <a href="https://access.redhat.com/documentation/en-US/Red_Hat_Storage/3/html/Administration_Guide/sect-Replacing_Hosts.html">least 14 steps to follow</a> to replace a brick.</li>
<li>I'm pretty sure I messed up the 14-step process above anyway. My replaced brick synced with my "original" brick, but produced errors when querying status via the CLI, and hogged 100% of 1 CPU on the replaced node. Inexperienced with GlusterFS, and unable to diagnose the fault, I switched to a Ceph cluster instead.</li>
</ol>
<h3 id="why-ceph">Why Ceph?<a class="headerlink" href="#why-ceph" title="Permanent link">&para;</a></h3>
<ol>
<li>I'm more familiar with Ceph - I use it in the OpenStack designs I manage</li>
<li>Replacing a failed node is <strong>easy</strong>, provided you can put up with the I/O load of rebalancing OSDs after the replacement.</li>
<li>CentOS Atomic includes the ceph client in the OS, so while the Ceph OSD/Mon/MSD are running under containers, I can keep an eye (and later, automatically monitor) the status of Ceph from the base OS.</li>
</ol>
<h2 id="ingredients">Ingredients<a class="headerlink" href="#ingredients" title="Permanent link">&para;</a></h2>
<div class="admonition summary">
<p class="admonition-title">Ingredients</p>
<p>3 x Virtual Machines (configured earlier), each with:</p>
<ul class="task-list">
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> CentOS/Fedora Atomic</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> At least 1GB RAM</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> At least 20GB disk space (<em>but it'll be tight</em>)</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> Connectivity to each other within the same subnet, and on a low-latency link (<em>i.e., no WAN links</em>)</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled/><span class="task-list-indicator"></span></label> A second disk dedicated to the Ceph OSD</li>
</ul>
</div>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permanent link">&para;</a></h2>
<h3 id="selinux">SELinux<a class="headerlink" href="#selinux" title="Permanent link">&para;</a></h3>
<p>Since our Ceph components will be containerized, we need to ensure the SELinux context on the base OS's ceph files is set correctly:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>chcon -Rt svirt_sandbox_file_t /etc/ceph
chcon -Rt svirt_sandbox_file_t /var/lib/ceph
</pre></div>
</td></tr></table>
<h3 id="setup-monitors">Setup Monitors<a class="headerlink" href="#setup-monitors" title="Permanent link">&para;</a></h3>
<p>Pick a node, and run the following to stand up the first Ceph mon. Be sure to replace the values for <strong>MON_IP</strong> and <strong>CEPH_PUBLIC_NETWORK</strong> to those specific to your deployment:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run -d --net=host \
--restart always \
-v /etc/ceph:/etc/ceph \
-v /var/lib/ceph/:/var/lib/ceph/ \
-e MON_IP=192.168.31.11 \
-e CEPH_PUBLIC_NETWORK=192.168.31.0/24 \
--name=&quot;ceph-mon&quot; \
ceph/daemon mon
</pre></div>
</td></tr></table>
<p>Now <strong>copy</strong> the contents of /etc/ceph on this first node to the remaining nodes, and <strong>then</strong> run the docker command above (<em>customizing MON_IP as you go</em>) on each remaining node. You'll end up with a cluster with 3 monitors (odd number is required for quorum, same as Docker Swarm), and no OSDs (yet)</p>
<h3 id="setup-osds">Setup OSDs<a class="headerlink" href="#setup-osds" title="Permanent link">&para;</a></h3>
<p>Since we have a OSD-less mon-only cluster currently, prepare for OSD creation by dumping the auth credentials for the OSDs into the appropriate location on the base OS:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>ceph auth get client.bootstrap-osd -o \
/var/lib/ceph/bootstrap-osd/ceph.keyring
</pre></div>
</td></tr></table>
<p>On each node, you need a dedicated disk for the OSD. In the example below, I used <em>/dev/vdd</em> (the entire disk, no partitions) for the OSD.</p>
<p>Run the following command on every node:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run -d --net=host \
--privileged=true \
--pid=host \
-v /etc/ceph:/etc/ceph \
-v /var/lib/ceph/:/var/lib/ceph/ \
-v /dev/:/dev/ \
-e OSD_DEVICE=/dev/vdd \
-e OSD_TYPE=disk \
--name=&quot;ceph-osd&quot; \
--restart=always \
ceph/daemon osd
</pre></div>
</td></tr></table>
<p>Watch the output by running <code class="codehilite">docker logs ceph-osd -f</code>, and confirm success.</p>
<div class="admonition note">
<p class="admonition-title">Zapping the device</p>
<p>The Ceph OSD container will refuse to destroy a partition containing existing data, so it may be necessary to "zap" the target disk, using:
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run -d --privileged=true \
-v /dev/:/dev/ \
-e OSD_DEVICE=/dev/sdd \
ceph/daemon zap_device
</pre></div>
</td></tr></table></p>
</div>
<h3 id="setup-mdss">Setup MDSs<a class="headerlink" href="#setup-mdss" title="Permanent link">&para;</a></h3>
<p>In order to mount our ceph pools as filesystems, we'll need Ceph MDS(s). Run the following on each node:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8
9</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run -d --net=host \
--name ceph-mds \
--restart always \
-v /var/lib/ceph/:/var/lib/ceph/ \
-v /etc/ceph:/etc/ceph \
-e CEPHFS_CREATE=1 \
-e CEPHFS_DATA_POOL_PG=256 \
-e CEPHFS_METADATA_POOL_PG=256 \
ceph/daemon mds
</pre></div>
</td></tr></table>
<h3 id="apply-tweaks">Apply tweaks<a class="headerlink" href="#apply-tweaks" title="Permanent link">&para;</a></h3>
<p>The ceph container seems to configure a pool default of 3 replicas (3 copies of each block are retained), which is one too many for our cluster (we are only protecting against the failure of a single node).</p>
<p>Run the following on any node to reduce the size of the pool to 2 replicas:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>ceph osd pool set cephfs_data size 2
ceph osd pool set cephfs_metadata size 2
</pre></div>
</td></tr></table>
<p>Disabled "scrubbing" (which can be IO-intensive, and is unnecessary on a VM) with:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>ceph osd set noscrub
ceph osd set nodeep-scrub
</pre></div>
</td></tr></table>
<h3 id="create-credentials-for-swarm">Create credentials for swarm<a class="headerlink" href="#create-credentials-for-swarm" title="Permanent link">&para;</a></h3>
<p>In order to mount the ceph volume onto our base host, we need to provide cephx authentication credentials.</p>
<p>On <strong>one</strong> node, create a client for the docker swarm:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>ceph auth get-or-create client.dockerswarm osd \
&#39;allow rw&#39; mon &#39;allow r&#39; mds &#39;allow&#39; &gt; /etc/ceph/keyring.dockerswarm
</pre></div>
</td></tr></table>
<p>Grab the secret associated with the new user (you'll need this for the /etc/fstab entry below) by running:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>ceph-authtool /etc/ceph/keyring.dockerswarm -p -n client.dockerswarm
</pre></div>
</td></tr></table>
<h3 id="mount-mds-volume">Mount MDS volume<a class="headerlink" href="#mount-mds-volume" title="Permanent link">&para;</a></h3>
<p>On each noie, create a mountpoint for the data, by running <code class="codehilite">mkdir /var/data</code>, add an entry to fstab to ensure the volume is auto-mounted on boot, and ensure the volume is actually <em>mounted</em> if there's a network / boot delay getting access to the gluster volume:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>mkdir /var/data
MYHOST=`hostname -s`
echo -e &quot;
# Mount cephfs volume \n
$MYHOST:6789:/ /var/data/ ceph \
name=dockerswarm\
,secret=&lt;YOUR SECRET HERE&gt;\
,noatime,_netdev,context=system_u:object_r:svirt_sandbox_file_t:s0\
0 2&quot; &gt;&gt; /etc/fstab
mount -a
</pre></div>
</td></tr></table>
<h3 id="install-docker-volume-plugin">Install docker-volume plugin<a class="headerlink" href="#install-docker-volume-plugin" title="Permanent link">&para;</a></h3>
<p>Upstream bug for docker-latest reported at <a href="https://bugs.centos.org/view.php?id=13609">https://bugs.centos.org/view.php?id=13609</a></p>
<p>And the alpine fault:
<a href="https://github.com/gliderlabs/docker-alpine/issues/317">https://github.com/gliderlabs/docker-alpine/issues/317</a></p>
<h2 id="serving">Serving<a class="headerlink" href="#serving" title="Permanent link">&para;</a></h2>
<p>After completing the above, you should have:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[X] Persistent storage available to every node
[X] Resiliency in the event of the failure of a single node
</pre></div>
</td></tr></table>
<h2 id="chefs-notes">Chef's Notes<a class="headerlink" href="#chefs-notes" title="Permanent link">&para;</a></h2>
<p>Future enhancements to this recipe include:</p>
<ol>
<li>Rather than pasting a secret key into /etc/fstab (which feels wrong), I'd prefer to be able to set "secretfile" in /etc/fstab (which just points ceph.mount to a file containing the secret), but under the current CentOS Atomic, we're stuck with "secret", per <a href="https://bugzilla.redhat.com/show_bug.cgi?id=1030402">https://bugzilla.redhat.com/show_bug.cgi?id=1030402</a></li>
</ol>
<h2 id="__comments">Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = "https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/shared-storage-ceph/";
this.page.identifier =
"/ha-docker-swarm/shared-storage-ceph/";
};
(function() {
var d = document, s = d.createElement("script");
s.src = "//geeks-cookbook.disqus.com/embed.js";
s.setAttribute("data-timestamp", +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../vms/" title="VMs" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
VMs
</span>
</div>
</a>
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Shared Storage (GlusterFS)
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2016 - 2017 David Young
</div>
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/" title="Material for MkDocs">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<a href="https://github.com/funkypenguin" class="md-footer-social__link fa fa-github"></a>
<a href="https://twitter.com/funkypenguin" class="md-footer-social__link fa fa-twitter"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application-c35428f87f.js"></script>
<script>app.initialize({url:{base:"../.."}})</script>
<script src="../../extras/javascript/piwik.js"></script>
<script>!function(e,t,a,n,o,c,i){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,c=t.createElement(a),i=t.getElementsByTagName(a)[0],c.async=1,c.src=n,i.parentNode.insertBefore(c,i)}(window,document,"script","https://www.google-analytics.com/analytics.js","ga"),ga("create","UA-139253-18","auto"),ga("set","anonymizeIp",!0),ga("send","pageview");var links=document.getElementsByTagName("a");Array.prototype.map.call(links,function(e){e.host!=document.location.host&&e.addEventListener("click",function(){var t=e.getAttribute("data-md-action")||"follow";ga("send","event","outbound",t,e.href)})});var query=document.forms.search.query;query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})</script>
</body>
</html>

View File

@@ -0,0 +1,904 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="A short description of my project">
<link rel="canonical" href="https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/shared-storage-gluster/">
<meta name="author" content="David Young">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-0.16.3, mkdocs-material-1.7.4">
<title>Shared Storage (GlusterFS) - Funky Penguin's Geek's Cookbook</title>
<script src="../../assets/javascripts/modernizr-1df76c4e58.js"></script>
<link rel="stylesheet" href="../../assets/stylesheets/application-769c285a91.css">
<link rel="stylesheet" href="../../assets/stylesheets/application-02c2a4388f.palette.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body data-md-color-primary="indigo" data-md-color-accent="indigo">
<svg class="md-svg">
<defs>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://geeks-cookbook.funkypenguin.co.nz" title="Funky Penguin's Geek's Cookbook" class="md-logo md-header-nav__button">
<img src="../../images/site-logo.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
<span class="md-header-nav__parent">
Essential
</span>
Shared Storage (GlusterFS)
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" required placeholder="Search" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset">close</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result" data-md-lang-search="">
<div class="md-search-result__meta" data-md-lang-result-none="No matching documents" data-md-lang-result-one="1 matching document" data-md-lang-result-other="# matching documents">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<i class="md-logo md-nav__button">
<img src="../../images/site-logo.png">
</i>
Funky Penguin's Geek's Cookbook
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Home" class="md-nav__link">
Home
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-2" type="checkbox" id="nav-2">
<label class="md-nav__link" for="nav-2">
Introduction
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-2">
Introduction
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../README/" title="README" class="md-nav__link">
README
</a>
</li>
<li class="md-nav__item">
<a href="../../whoami/" title="whoami" class="md-nav__link">
whoami
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-3" type="checkbox" id="nav-3" checked>
<label class="md-nav__link" for="nav-3">
Essential
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-3">
Essential
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../design/" title="Design" class="md-nav__link">
Design
</a>
</li>
<li class="md-nav__item">
<a href="../vms/" title="VMs" class="md-nav__link">
VMs
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-nav__link">
Shared Storage (Ceph)
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<label class="md-nav__link md-nav__link--active" for="toc">
Shared Storage (GlusterFS)
</label>
<a href="./" title="Shared Storage (GlusterFS)" class="md-nav__link md-nav__link--active">
Shared Storage (GlusterFS)
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#design" title="Design" class="md-nav__link">
Design
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#why-glusterfs" title="Why GlusterFS?" class="md-nav__link">
Why GlusterFS?
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#create-gluster-bricks" title="Create Gluster "bricks"" class="md-nav__link">
Create Gluster "bricks"
</a>
</li>
<li class="md-nav__item">
<a href="#create-glusterfs-container" title="Create glusterfs container" class="md-nav__link">
Create glusterfs container
</a>
</li>
<li class="md-nav__item">
<a href="#create-trusted-pool" title="Create trusted pool" class="md-nav__link">
Create trusted pool
</a>
</li>
<li class="md-nav__item">
<a href="#create-gluster-volume" title="Create gluster volume" class="md-nav__link">
Create gluster volume
</a>
</li>
<li class="md-nav__item">
<a href="#mount-gluster-volume" title="Mount gluster volume" class="md-nav__link">
Mount gluster volume
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's Notes" class="md-nav__link">
Chef's Notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../keepalived/" title="Keepalived" class="md-nav__link">
Keepalived
</a>
</li>
<li class="md-nav__item">
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-nav__link">
Docker Swarm Mode
</a>
</li>
<li class="md-nav__item">
<a href="../traefik/" title="Traefik" class="md-nav__link">
Traefik
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-4" type="checkbox" id="nav-4">
<label class="md-nav__link" for="nav-4">
Recommended
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-4">
Recommended
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../recipies/mail/" title="Mail Server" class="md-nav__link">
Mail Server
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#design" title="Design" class="md-nav__link">
Design
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#why-glusterfs" title="Why GlusterFS?" class="md-nav__link">
Why GlusterFS?
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#create-gluster-bricks" title="Create Gluster "bricks"" class="md-nav__link">
Create Gluster "bricks"
</a>
</li>
<li class="md-nav__item">
<a href="#create-glusterfs-container" title="Create glusterfs container" class="md-nav__link">
Create glusterfs container
</a>
</li>
<li class="md-nav__item">
<a href="#create-trusted-pool" title="Create trusted pool" class="md-nav__link">
Create trusted pool
</a>
</li>
<li class="md-nav__item">
<a href="#create-gluster-volume" title="Create gluster volume" class="md-nav__link">
Create gluster volume
</a>
</li>
<li class="md-nav__item">
<a href="#mount-gluster-volume" title="Mount gluster volume" class="md-nav__link">
Mount gluster volume
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's Notes" class="md-nav__link">
Chef's Notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="shared-storage-glusterfs">Shared Storage (GlusterFS)<a class="headerlink" href="#shared-storage-glusterfs" title="Permanent link">&para;</a></h1>
<p>While Docker Swarm is great for keeping containers running (<em>and restarting those that fail</em>), it does nothing for persistent storage. This means if you actually want your containers to keep any data persistent across restarts (<em>hint: you do!</em>), you need to provide shared storage to every docker node.</p>
<h2 id="design">Design<a class="headerlink" href="#design" title="Permanent link">&para;</a></h2>
<h3 id="why-glusterfs">Why GlusterFS?<a class="headerlink" href="#why-glusterfs" title="Permanent link">&para;</a></h3>
<p>This GlusterFS recipe was my original design for shared storage, but I <a href="../ha-docker-swarm/shared-storage-ceph/#why-not-glusterfs">found it to be flawed</a>, and I replaced it with a <a href="http://localhost:8000/ha-docker-swarm/shared-storage-ceph/#why-ceph">design which employs Ceph instead</a>. This recipe is an alternate to the Ceph design, if you happen to prefer GlusterFS.</p>
<h2 id="ingredients">Ingredients<a class="headerlink" href="#ingredients" title="Permanent link">&para;</a></h2>
<div class="admonition summary">
<p class="admonition-title">Ingredients</p>
<p>3 x Virtual Machines (configured earlier), each with:</p>
<ul class="task-list">
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> CentOS/Fedora Atomic</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> At least 1GB RAM</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> At least 20GB disk space (<em>but it'll be tight</em>)</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled checked/><span class="task-list-indicator"></span></label> Connectivity to each other within the same subnet, and on a low-latency link (<em>i.e., no WAN links</em>)</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled/><span class="task-list-indicator"></span></label> A second disk, or adequate space on the primary disk for a dedicated data partition</li>
</ul>
</div>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permanent link">&para;</a></h2>
<h3 id="create-gluster-bricks">Create Gluster "bricks"<a class="headerlink" href="#create-gluster-bricks" title="Permanent link">&para;</a></h3>
<p>To build our Gluster volume, we need 2 out of the 3 VMs to provide one "brick". The bricks will be used to create the replicated volume. Assuming a replica count of 2 (<em>i.e., 2 copies of the data are kept in gluster</em>), our total number of bricks must be divisible by our replica count. (<em>I.e., you can't have 3 bricks if you want 2 replicas. You can have 4 though - We have to have minimum 3 swarm manager nodes for fault-tolerance, but only 2 of those nodes need to run as gluster servers.</em>)</p>
<p>On each host, run a variation following to create your bricks, adjusted for the path to your disk.</p>
<div class="admonition note">
<p class="admonition-title">The example below assumes /dev/vdb is dedicated to the gluster volume</p>
</div>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>(
echo o # Create a new empty DOS partition table
echo n # Add a new partition
echo p # Primary partition
echo 1 # Partition number
echo # First sector (Accept default: 1)
echo # Last sector (Accept default: varies)
echo w # Write changes
) | sudo fdisk /dev/vdb
mkfs.xfs -i size=512 /dev/vdb1
mkdir -p /var/no-direct-write-here/brick1
echo &#39;&#39; &gt;&gt; /etc/fstab &gt;&gt; /etc/fstab
echo &#39;# Mount /dev/vdb1 so that it can be used as a glusterfs volume&#39; &gt;&gt; /etc/fstab
echo &#39;/dev/vdb1 /var/no-direct-write-here/brick1 xfs defaults 1 2&#39; &gt;&gt; /etc/fstab
mount -a &amp;&amp; mount
</pre></div>
</td></tr></table>
<div class="admonition warning">
<p class="admonition-title">Don't provision all your LVM space</p>
<p>Atomic uses LVM to store docker data, and <strong>automatically grows</strong> Docker's volumes as requried. If you commit all your free LVM space to your brick, you'll quickly find (as I did) that docker will start to fail with error messages about insufficient space. If you're going to slice off a portion of your LVM space in /dev/atomicos, make sure you leave enough space for Docker storage, where "enough" depends on how much you plan to pull images, make volumes, etc. I ate through 20GB very quickly doing development, so I ended up provisioning 50GB for atomic alone, with a separate volume for the brick.</p>
</div>
<h3 id="create-glusterfs-container">Create glusterfs container<a class="headerlink" href="#create-glusterfs-container" title="Permanent link">&para;</a></h3>
<p>Atomic doesn't include the Gluster server components. This means we'll have to run glusterd from within a container, with privileged access to the host. Although convoluted, I've come to prefer this design since it once again makes the OS "disposable", moving all the config into containers and code.</p>
<p>Run the following on each host:
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>docker run \
-h glusterfs-server \
-v /etc/glusterfs:/etc/glusterfs:z \
-v /var/lib/glusterd:/var/lib/glusterd:z \
-v /var/log/glusterfs:/var/log/glusterfs:z \
-v /sys/fs/cgroup:/sys/fs/cgroup:ro \
-v /var/no-direct-write-here/brick1:/var/no-direct-write-here/brick1 \
-d --privileged=true --net=host \
--restart=always \
--name=&quot;glusterfs-server&quot; \
gluster/gluster-centos
</pre></div>
</td></tr></table></p>
<h3 id="create-trusted-pool">Create trusted pool<a class="headerlink" href="#create-trusted-pool" title="Permanent link">&para;</a></h3>
<p>On a single node (doesn't matter which), run <code class="codehilite">docker exec -it glusterfs-server bash</code> to launch a shell inside the container.</p>
<p>From the node, run
<code class="codehilite">gluster peer probe &lt;other host&gt;</code></p>
<p>Example output:
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@glusterfs-server /]# gluster peer probe ds1
peer probe: success.
[root@glusterfs-server /]#
</pre></div>
</td></tr></table></p>
<p>Run <code class="codehilite">gluster peer status</code> on both nodes to confirm that they're properly connected to each other:</p>
<p>Example output:
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@glusterfs-server /]# gluster peer status
Number of Peers: 1
Hostname: ds3
Uuid: 3e115ba9-6a4f-48dd-87d7-e843170ff499
State: Peer in Cluster (Connected)
[root@glusterfs-server /]#
</pre></div>
</td></tr></table></p>
<h3 id="create-gluster-volume">Create gluster volume<a class="headerlink" href="#create-gluster-volume" title="Permanent link">&para;</a></h3>
<p>Now we create a <em>replicated volume</em> out of our individual "bricks".</p>
<p>Create the gluster volume by running
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>gluster volume create gv0 replica 2 \
server1:/var/no-direct-write-here/brick1 \
server2:/var/no-direct-write-here/brick1
</pre></div>
</td></tr></table></p>
<p>Example output:
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@glusterfs-server /]# gluster volume create gv0 replica 2 ds1:/var/no-direct-write-here/brick1/gv0 ds3:/var/no-direct-write-here/brick1/gv0
volume create: gv0: success: please start the volume to access data
[root@glusterfs-server /]#
</pre></div>
</td></tr></table></p>
<p>Start the volume by running <code class="codehilite">gluster volume start gv0</code></p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[root@glusterfs-server /]# gluster volume start gv0
volume start: gv0: success
[root@glusterfs-server /]#
</pre></div>
</td></tr></table>
<p>The volume is only present on the host you're shelled into though. To add the other hosts to the volume, run <code class="codehilite">gluster peer probe &lt;servername&gt;</code>. Don't probe host from itself.</p>
<p>From one other host, run <code class="codehilite">docker exec -it glusterfs-server bash</code> to shell into the gluster-server container, and run <code class="codehilite">gluster peer probe &lt;original server name&gt;</code> to update the name of the host which started the volume.</p>
<h3 id="mount-gluster-volume">Mount gluster volume<a class="headerlink" href="#mount-gluster-volume" title="Permanent link">&para;</a></h3>
<p>On the host (i.e., outside of the container - type <code class="codehilite">exit</code> if you're still shelled in), create a mountpoint for the data, by running <code class="codehilite">mkdir /var/data</code>, add an entry to fstab to ensure the volume is auto-mounted on boot, and ensure the volume is actually <em>mounted</em> if there's a network / boot delay getting access to the gluster volume:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>mkdir /var/data
MYHOST=`hostname -s`
echo &#39;&#39; &gt;&gt; /etc/fstab &gt;&gt; /etc/fstab
echo &#39;# Mount glusterfs volume&#39; &gt;&gt; /etc/fstab
echo &quot;$MYHOST:/gv0 /var/data glusterfs defaults,_netdev,context=&quot;system_u:object_r:svirt_sandbox_file_t:s0&quot; 0 0&quot; &gt;&gt; /etc/fstab
mount -a
</pre></div>
</td></tr></table>
<p>For some reason, my nodes won't auto-mount this volume on boot. I even tried the trickery below, but they stubbornly refuse to automount.
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>echo -e &quot;\n\n# Give GlusterFS 10s to start before \
mounting\nsleep 10s &amp;&amp; mount -a&quot; &gt;&gt; /etc/rc.local
systemctl enable rc-local.service
</pre></div>
</td></tr></table></p>
<p>For non-gluster nodes, you'll need to replace $MYHOST above with the name of one of the gluster hosts (I haven't worked out how to make this fully HA yet)</p>
<h2 id="serving">Serving<a class="headerlink" href="#serving" title="Permanent link">&para;</a></h2>
<p>After completing the above, you should have:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[X] Persistent storage available to every node
[X] Resiliency in the event of the failure of a single (gluster) node
</pre></div>
</td></tr></table>
<h2 id="chefs-notes">Chef's Notes<a class="headerlink" href="#chefs-notes" title="Permanent link">&para;</a></h2>
<p>Future enhancements to this recipe include:</p>
<ol>
<li>Migration of shared storage from GlusterFS to Ceph ()<a href="https://gitlab.funkypenguin.co.nz/funkypenguin/geeks-cookbook/issues/2">#2</a>)</li>
<li>Correct the fact that volumes don't automount on boot (<a href="https://gitlab.funkypenguin.co.nz/funkypenguin/geeks-cookbook/issues/3">#3</a>)</li>
</ol>
<h2 id="__comments">Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = "https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/shared-storage-gluster/";
this.page.identifier =
"/ha-docker-swarm/shared-storage-gluster/";
};
(function() {
var d = document, s = d.createElement("script");
s.src = "//geeks-cookbook.disqus.com/embed.js";
s.setAttribute("data-timestamp", +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Shared Storage (Ceph)
</span>
</div>
</a>
<a href="../keepalived/" title="Keepalived" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Keepalived
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2016 - 2017 David Young
</div>
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/" title="Material for MkDocs">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<a href="https://github.com/funkypenguin" class="md-footer-social__link fa fa-github"></a>
<a href="https://twitter.com/funkypenguin" class="md-footer-social__link fa fa-twitter"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application-c35428f87f.js"></script>
<script>app.initialize({url:{base:"../.."}})</script>
<script src="../../extras/javascript/piwik.js"></script>
<script>!function(e,t,a,n,o,c,i){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,c=t.createElement(a),i=t.getElementsByTagName(a)[0],c.async=1,c.src=n,i.parentNode.insertBefore(c,i)}(window,document,"script","https://www.google-analytics.com/analytics.js","ga"),ga("create","UA-139253-18","auto"),ga("set","anonymizeIp",!0),ga("send","pageview");var links=document.getElementsByTagName("a");Array.prototype.map.call(links,function(e){e.host!=document.location.host&&e.addEventListener("click",function(){var t=e.getAttribute("data-md-action")||"follow";ga("send","event","outbound",t,e.href)})});var query=document.forms.search.query;query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})</script>
</body>
</html>

View File

@@ -0,0 +1,869 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="A short description of my project">
<link rel="canonical" href="https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/traefik/">
<meta name="author" content="David Young">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-0.16.3, mkdocs-material-1.7.4">
<title>Traefik - Funky Penguin's Geek's Cookbook</title>
<script src="../../assets/javascripts/modernizr-1df76c4e58.js"></script>
<link rel="stylesheet" href="../../assets/stylesheets/application-769c285a91.css">
<link rel="stylesheet" href="../../assets/stylesheets/application-02c2a4388f.palette.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body data-md-color-primary="indigo" data-md-color-accent="indigo">
<svg class="md-svg">
<defs>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://geeks-cookbook.funkypenguin.co.nz" title="Funky Penguin's Geek's Cookbook" class="md-logo md-header-nav__button">
<img src="../../images/site-logo.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
<span class="md-header-nav__parent">
Essential
</span>
Traefik
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" required placeholder="Search" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset">close</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result" data-md-lang-search="">
<div class="md-search-result__meta" data-md-lang-result-none="No matching documents" data-md-lang-result-one="1 matching document" data-md-lang-result-other="# matching documents">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<i class="md-logo md-nav__button">
<img src="../../images/site-logo.png">
</i>
Funky Penguin's Geek's Cookbook
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Home" class="md-nav__link">
Home
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-2" type="checkbox" id="nav-2">
<label class="md-nav__link" for="nav-2">
Introduction
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-2">
Introduction
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../README/" title="README" class="md-nav__link">
README
</a>
</li>
<li class="md-nav__item">
<a href="../../whoami/" title="whoami" class="md-nav__link">
whoami
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-3" type="checkbox" id="nav-3" checked>
<label class="md-nav__link" for="nav-3">
Essential
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-3">
Essential
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../design/" title="Design" class="md-nav__link">
Design
</a>
</li>
<li class="md-nav__item">
<a href="../vms/" title="VMs" class="md-nav__link">
VMs
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-nav__link">
Shared Storage (Ceph)
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-nav__link">
Shared Storage (GlusterFS)
</a>
</li>
<li class="md-nav__item">
<a href="../keepalived/" title="Keepalived" class="md-nav__link">
Keepalived
</a>
</li>
<li class="md-nav__item">
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-nav__link">
Docker Swarm Mode
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<label class="md-nav__link md-nav__link--active" for="toc">
Traefik
</label>
<a href="./" title="Traefik" class="md-nav__link md-nav__link--active">
Traefik
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#prepare-the-host" title="Prepare the host" class="md-nav__link">
Prepare the host
</a>
</li>
<li class="md-nav__item">
<a href="#prepare-traefiktoml" title="Prepare traefik.toml" class="md-nav__link">
Prepare traefik.toml
</a>
</li>
<li class="md-nav__item">
<a href="#prepare-the-docker-service-config" title="Prepare the docker service config" class="md-nav__link">
Prepare the docker service config
</a>
</li>
<li class="md-nav__item">
<a href="#launch" title="Launch" class="md-nav__link">
Launch
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's Notes" class="md-nav__link">
Chef's Notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-4" type="checkbox" id="nav-4">
<label class="md-nav__link" for="nav-4">
Recommended
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-4">
Recommended
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../recipies/mail/" title="Mail Server" class="md-nav__link">
Mail Server
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#prepare-the-host" title="Prepare the host" class="md-nav__link">
Prepare the host
</a>
</li>
<li class="md-nav__item">
<a href="#prepare-traefiktoml" title="Prepare traefik.toml" class="md-nav__link">
Prepare traefik.toml
</a>
</li>
<li class="md-nav__item">
<a href="#prepare-the-docker-service-config" title="Prepare the docker service config" class="md-nav__link">
Prepare the docker service config
</a>
</li>
<li class="md-nav__item">
<a href="#launch" title="Launch" class="md-nav__link">
Launch
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#chefs-notes" title="Chef's Notes" class="md-nav__link">
Chef's Notes
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="traefik">Traefik<a class="headerlink" href="#traefik" title="Permanent link">&para;</a></h1>
<p>The platforms we plan to run on our cloud are generally web-based, and each listening on their own unique TCP port. When a container in a swarm exposes a port, then connecting to <strong>any</strong> swarm member on that port will result in your request being forwarded to the appropriate host running the container. (<em>Docker calls this the swarm "<a href="https://docs.docker.com/engine/swarm/ingress/">routing mesh</a>"</em>)</p>
<p>So we get a rudimentary load balancer built into swarm. We could stop there, just exposing a series of ports on our hosts, and making them HA using keepalived.</p>
<p>There are some gaps to this approach though:</p>
<ul>
<li>No consideration is given to HTTPS. Implementation would have to be done manually, per-container.</li>
<li>No mechanism is provided for authentication outside of that which the container providers. We may not <strong>want</strong> to expose every interface on every container to the world, especially if we are playing with tools or containers whose quality and origin are unknown.</li>
</ul>
<p>To deal with these gaps, we need a front-end load-balancer, and in this design, that role is provided by <a href="https://traefik.io/">Traefik</a>.</p>
<h2 id="ingredients">Ingredients<a class="headerlink" href="#ingredients" title="Permanent link">&para;</a></h2>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permanent link">&para;</a></h2>
<h3 id="prepare-the-host">Prepare the host<a class="headerlink" href="#prepare-the-host" title="Permanent link">&para;</a></h3>
<p>The traefik container is aware of the <strong>other</strong> docker containers in the swarm, because it has access to the docker socket at <strong>/var/run/docker.sock</strong>. This allows traefik to dynamically configure itself based on the labels found on containers in the swarm, which is hugely useful. To make this functionality work on our SELinux-enabled Atomic hosts, we need to add custom SELinux policy.</p>
<p>Run the following to build and activate policy to permit containers to access docker.sock:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>mkdir ~/dockersock
cd ~/dockersock
curl -O https://raw.githubusercontent.com/dpw/\
selinux-dockersock/master/Makefile
curl -O https://raw.githubusercontent.com/dpw/\
selinux-dockersock/master/dockersock.te
make &amp;&amp; semodule -i dockersock.pp
</pre></div>
</td></tr></table>
<h3 id="prepare-traefiktoml">Prepare traefik.toml<a class="headerlink" href="#prepare-traefiktoml" title="Permanent link">&para;</a></h3>
<p>While it's possible to configure traefik via docker command arguments, I prefer to create a config file (traefik.toml). This allows me to change traefik's behaviour by simply changing the file, and keeps my docker config simple.</p>
<p>Create /var/data/traefik/traefik.toml as follows:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>checkNewVersion = true
defaultEntryPoints = [&quot;http&quot;, &quot;https&quot;]
# This section enable LetsEncrypt automatic certificate generation / renewal
[acme]
email = &quot;&lt;your LetsEncrypt email address&gt;&quot;
storage = &quot;acme.json&quot; # or &quot;traefik/acme/account&quot; if using KV store
entryPoint = &quot;https&quot;
acmeLogging = true
onDemand = true
OnHostRule = true
[[acme.domains]]
main = &quot;&lt;your primary domain&gt;&quot;
# Redirect all HTTP to HTTPS (why wouldn&#39;t you?)
[entryPoints]
[entryPoints.http]
address = &quot;:80&quot;
[entryPoints.http.redirect]
entryPoint = &quot;https&quot;
[entryPoints.https]
address = &quot;:443&quot;
[entryPoints.https.tls]
[web]
address = &quot;:8080&quot;
watch = true
[docker]
endpoint = &quot;tcp://127.0.0.1:2375&quot;
domain = &quot;&lt;your primary domain&gt;&quot;
watch = true
swarmmode = true
</pre></div>
</td></tr></table>
<h3 id="prepare-the-docker-service-config">Prepare the docker service config<a class="headerlink" href="#prepare-the-docker-service-config" title="Permanent link">&para;</a></h3>
<p>Create /var/data/traefik/docker-compose.yml as follows:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>version: &quot;3.2&quot;
services:
traefik:
image: traefik
command: --web --docker --docker.swarmmode --docker.watch --docker.domain=funkypenguin.co.nz --logLevel=DEBUG
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 8080
published: 8080
protocol: tcp
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/data/traefik/traefik.toml:/traefik.toml:ro
- /var/data/traefik/acme.json:/acme.json
labels:
- &quot;traefik.enable=false&quot;
networks:
- public
deploy:
mode: global
placement:
constraints: [node.role == manager]
restart_policy:
condition: on-failure
networks:
public:
driver: overlay
ipam:
driver: default
config:
- subnet: 10.1.0.0/24
</pre></div>
</td></tr></table>
<p>Docker won't start an image with a bind-mount to a non-existent file, so prepare acme.json by running <code class="codehilite">touch /var/data/traefik/acme.json</code>.</p>
<h3 id="launch">Launch<a class="headerlink" href="#launch" title="Permanent link">&para;</a></h3>
<p>Deploy traefik with <code class="codehilite">docker stack deploy traefik -c /var/data/traefik/docker-compose.yml</code></p>
<p>Confirm traefik is running with <code class="codehilite">docker stack ps traefik</code></p>
<h2 id="serving">Serving<a class="headerlink" href="#serving" title="Permanent link">&para;</a></h2>
<p>You now have:</p>
<ol>
<li>Frontend proxy which will dynamically configure itself for new backend containers</li>
<li>Automatic SSL support for all proxied resources</li>
</ol>
<h2 id="chefs-notes">Chef's Notes<a class="headerlink" href="#chefs-notes" title="Permanent link">&para;</a></h2>
<p>Additional features I'd like to see in this recipe are:</p>
<ol>
<li>Include documentation of oauth2_proxy container for protecting individual backends</li>
<li>Traefik webUI is available via HTTPS, protected with oauth_proxy</li>
<li>Pending a feature in docker-swarm to avoid NAT on routing-mesh-delivered traffic, update the design</li>
</ol>
<h2 id="__comments">Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = "https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/traefik/";
this.page.identifier =
"/ha-docker-swarm/traefik/";
};
(function() {
var d = document, s = d.createElement("script");
s.src = "//geeks-cookbook.disqus.com/embed.js";
s.setAttribute("data-timestamp", +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Docker Swarm Mode
</span>
</div>
</a>
<a href="../../recipies/mail/" title="Mail Server" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Mail Server
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2016 - 2017 David Young
</div>
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/" title="Material for MkDocs">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<a href="https://github.com/funkypenguin" class="md-footer-social__link fa fa-github"></a>
<a href="https://twitter.com/funkypenguin" class="md-footer-social__link fa fa-twitter"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application-c35428f87f.js"></script>
<script>app.initialize({url:{base:"../.."}})</script>
<script src="../../extras/javascript/piwik.js"></script>
<script>!function(e,t,a,n,o,c,i){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,c=t.createElement(a),i=t.getElementsByTagName(a)[0],c.async=1,c.src=n,i.parentNode.insertBefore(c,i)}(window,document,"script","https://www.google-analytics.com/analytics.js","ga"),ga("create","UA-139253-18","auto"),ga("set","anonymizeIp",!0),ga("send","pageview");var links=document.getElementsByTagName("a");Array.prototype.map.call(links,function(e){e.host!=document.location.host&&e.addEventListener("click",function(){var t=e.getAttribute("data-md-action")||"follow";ga("send","event","outbound",t,e.href)})});var query=document.forms.search.query;query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})</script>
</body>
</html>

View File

@@ -0,0 +1,731 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="A short description of my project">
<link rel="canonical" href="https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/vms/">
<meta name="author" content="David Young">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-0.16.3, mkdocs-material-1.7.4">
<title>VMs - Funky Penguin's Geek's Cookbook</title>
<script src="../../assets/javascripts/modernizr-1df76c4e58.js"></script>
<link rel="stylesheet" href="../../assets/stylesheets/application-769c285a91.css">
<link rel="stylesheet" href="../../assets/stylesheets/application-02c2a4388f.palette.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body data-md-color-primary="indigo" data-md-color-accent="indigo">
<svg class="md-svg">
<defs>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://geeks-cookbook.funkypenguin.co.nz" title="Funky Penguin's Geek's Cookbook" class="md-logo md-header-nav__button">
<img src="../../images/site-logo.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
<span class="md-header-nav__parent">
Essential
</span>
VMs
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" required placeholder="Search" accesskey="s" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset">close</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result" data-md-lang-search="">
<div class="md-search-result__meta" data-md-lang-result-none="No matching documents" data-md-lang-result-one="1 matching document" data-md-lang-result-other="# matching documents">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<i class="md-logo md-nav__button">
<img src="../../images/site-logo.png">
</i>
Funky Penguin's Geek's Cookbook
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Home" class="md-nav__link">
Home
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-2" type="checkbox" id="nav-2">
<label class="md-nav__link" for="nav-2">
Introduction
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-2">
Introduction
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../README/" title="README" class="md-nav__link">
README
</a>
</li>
<li class="md-nav__item">
<a href="../../whoami/" title="whoami" class="md-nav__link">
whoami
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-3" type="checkbox" id="nav-3" checked>
<label class="md-nav__link" for="nav-3">
Essential
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-3">
Essential
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../design/" title="Design" class="md-nav__link">
Design
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<label class="md-nav__link md-nav__link--active" for="toc">
VMs
</label>
<a href="./" title="VMs" class="md-nav__link md-nav__link--active">
VMs
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#install-virtual-machines" title="Install Virtual machines" class="md-nav__link">
Install Virtual machines
</a>
</li>
<li class="md-nav__item">
<a href="#prefer-docker-latest" title="Prefer docker-latest" class="md-nav__link">
Prefer docker-latest
</a>
</li>
<li class="md-nav__item">
<a href="#upgrade-atomic" title="Upgrade Atomic" class="md-nav__link">
Upgrade Atomic
</a>
</li>
<li class="md-nav__item">
<a href="#permit-connectivity-between-vms" title="Permit connectivity between VMs" class="md-nav__link">
Permit connectivity between VMs
</a>
</li>
<li class="md-nav__item">
<a href="#enable-host-resolution" title="Enable host resolution" class="md-nav__link">
Enable host resolution
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-nav__link">
Shared Storage (Ceph)
</a>
</li>
<li class="md-nav__item">
<a href="../shared-storage-gluster/" title="Shared Storage (GlusterFS)" class="md-nav__link">
Shared Storage (GlusterFS)
</a>
</li>
<li class="md-nav__item">
<a href="../keepalived/" title="Keepalived" class="md-nav__link">
Keepalived
</a>
</li>
<li class="md-nav__item">
<a href="../docker-swarm-mode/" title="Docker Swarm Mode" class="md-nav__link">
Docker Swarm Mode
</a>
</li>
<li class="md-nav__item">
<a href="../traefik/" title="Traefik" class="md-nav__link">
Traefik
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-toggle md-nav__toggle" data-md-toggle="nav-4" type="checkbox" id="nav-4">
<label class="md-nav__link" for="nav-4">
Recommended
</label>
<nav class="md-nav" data-md-component="collapsible" data-md-level="1">
<label class="md-nav__title" for="nav-4">
Recommended
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../recipies/mail/" title="Mail Server" class="md-nav__link">
Mail Server
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#ingredients" title="Ingredients" class="md-nav__link">
Ingredients
</a>
</li>
<li class="md-nav__item">
<a href="#preparation" title="Preparation" class="md-nav__link">
Preparation
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#install-virtual-machines" title="Install Virtual machines" class="md-nav__link">
Install Virtual machines
</a>
</li>
<li class="md-nav__item">
<a href="#prefer-docker-latest" title="Prefer docker-latest" class="md-nav__link">
Prefer docker-latest
</a>
</li>
<li class="md-nav__item">
<a href="#upgrade-atomic" title="Upgrade Atomic" class="md-nav__link">
Upgrade Atomic
</a>
</li>
<li class="md-nav__item">
<a href="#permit-connectivity-between-vms" title="Permit connectivity between VMs" class="md-nav__link">
Permit connectivity between VMs
</a>
</li>
<li class="md-nav__item">
<a href="#enable-host-resolution" title="Enable host resolution" class="md-nav__link">
Enable host resolution
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#serving" title="Serving" class="md-nav__link">
Serving
</a>
</li>
<li class="md-nav__item">
<a href="#__comments" title="Comments" class="md-nav__link md-nav__link--active">
Comments
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="virtual-machines">Virtual Machines<a class="headerlink" href="#virtual-machines" title="Permanent link">&para;</a></h1>
<p>Let's start building our cloud with virtual machines. You could use bare-metal machines as well, the configuration would be the same. Given that most readers (myself included) will be using virtual infrastructure, from now on I'll be referring strictly to VMs.</p>
<p>I chose the "<a href="https://www.projectatomic.io/">Atomic</a>" CentOS/Fedora image for the VM layer because:</p>
<ol>
<li>I want less responsibility for maintaining the system, including ensuring regular software updates and reboots. Atomic's idempotent nature means the OS is largely real-only, and updates/rollbacks are "atomic" (haha) procedures, which can be easily rolled back if required.</li>
<li>For someone used to administrating servers individually, Atomic is a PITA. You have to employ <a href="../atomic-trick2">tricky</a> <a href="../atomic-trick1">tricks</a> to get it to install in a non-cloud environment. It's not designed for tweaking or customizing beyond what cloud-config is capable of. For my purposes, this is good, because it forces me to change my thinking - to consider every daemon as a container, and every config as code, to be checked in and version-controlled. Atomic forces this thinking on you.</li>
<li>I want the design to be as "portable" as possible. While I run it on VPSs now, I may want to migrate it to a "cloud" provider in the future, and I'll want the most portable, reproducible design.</li>
</ol>
<h2 id="ingredients">Ingredients<a class="headerlink" href="#ingredients" title="Permanent link">&para;</a></h2>
<div class="admonition summary">
<p class="admonition-title">Ingredients</p>
<p>3 x Virtual Machines, each with:</p>
<ul class="task-list">
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled/><span class="task-list-indicator"></span></label> CentOS/Fedora Atomic</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled/><span class="task-list-indicator"></span></label> At least 1GB RAM</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled/><span class="task-list-indicator"></span></label> At least 20GB disk space (<em>but it'll be tight</em>)</li>
<li class="task-list-item"><label class="task-list-control"><input type="checkbox" disabled/><span class="task-list-indicator"></span></label> Connectivity to each other within the same subnet, and on a low-latency link (<em>i.e., no WAN links</em>)</li>
</ul>
</div>
<h2 id="preparation">Preparation<a class="headerlink" href="#preparation" title="Permanent link">&para;</a></h2>
<h3 id="install-virtual-machines">Install Virtual machines<a class="headerlink" href="#install-virtual-machines" title="Permanent link">&para;</a></h3>
<ol>
<li>Install / launch virtual machines.</li>
<li>The default username on CentOS atomic is "centos", and you'll have needed to supply your SSH key during the build process.</li>
</ol>
<div class="admonition tip">
<p class="admonition-title">Tip</p>
<p>If you're not using a platform with cloud-init support (i.e., you're building a VM manually, not provisioning it through a cloud provider), you'll need to refer to <a href="https://spinningmatt.wordpress.com/2014/01/08/a-recipe-for-starting-cloud-images-with-virt-install/">trick #1</a> and <a href="http://blog.oddbit.com/2015/03/10/booting-cloud-images-with-libvirt/">#2</a> for a means to override the automated setup, apply a manual password to the CentOS account, and enable SSH password logins.</p>
</div>
<h3 id="prefer-docker-latest">Prefer docker-latest<a class="headerlink" href="#prefer-docker-latest" title="Permanent link">&para;</a></h3>
<p>Run the following on each node to replace the default docker 1.12 with docker 1.13 (<em>which we need for swarm mode</em>):
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>systemctl disable docker --now
systemctl enable docker-latest --now
sed -i &#39;/DOCKERBINARY/s/^#//g&#39; /etc/sysconfig/docker
</pre></div>
</td></tr></table></p>
<h3 id="upgrade-atomic">Upgrade Atomic<a class="headerlink" href="#upgrade-atomic" title="Permanent link">&para;</a></h3>
<p>Finally, apply any Atomic host updates, and reboot, by running: <code class="codehilite">atomic host upgrade &amp;&amp; systemctl reboot</code>.</p>
<h3 id="permit-connectivity-between-vms">Permit connectivity between VMs<a class="headerlink" href="#permit-connectivity-between-vms" title="Permanent link">&para;</a></h3>
<p>By default, Atomic only permits incoming SSH. We'll want to allow all traffic between our nodes, so add something like this to /etc/sysconfig/iptables:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span># Allow all inter-node communication
-A INPUT -s 192.168.31.0/24 -j ACCEPT
</pre></div>
</td></tr></table>
<p>And restart iptables with <code class="codehilite">systemctl restart iptables</code></p>
<h3 id="enable-host-resolution">Enable host resolution<a class="headerlink" href="#enable-host-resolution" title="Permanent link">&para;</a></h3>
<p>Depending on your hosting environment, you may have DNS automatically setup for your VMs. If not, it's useful to set up static entries in /etc/hosts for the nodes. For example, I setup the following:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>192.168.31.11 ds1 ds1.funkypenguin.co.nz
192.168.31.12 ds2 ds2.funkypenguin.co.nz
192.168.31.13 ds3 ds3.funkypenguin.co.nz
</pre></div>
</td></tr></table>
<h2 id="serving">Serving<a class="headerlink" href="#serving" title="Permanent link">&para;</a></h2>
<p>After completing the above, you should have:</p>
<table class="codehilitetable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2</pre></div></td><td class="code"><div class="codehilite"><pre><span></span>[X] 3 x fresh atomic instances, at the latest releases,
running Docker v1.13 (docker-latest)
</pre></div>
</td></tr></table>
<h2 id="__comments">Comments</h2>
<div id="disqus_thread"></div>
<script>
var disqus_config = function () {
this.page.url = "https://geeks-cookbook.funkypenguin.co.nz/ha-docker-swarm/vms/";
this.page.identifier =
"/ha-docker-swarm/vms/";
};
(function() {
var d = document, s = d.createElement("script");
s.src = "//geeks-cookbook.disqus.com/embed.js";
s.setAttribute("data-timestamp", +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../design/" title="Design" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Design
</span>
</div>
</a>
<a href="../shared-storage-ceph/" title="Shared Storage (Ceph)" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Shared Storage (Ceph)
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2016 - 2017 David Young
</div>
powered by
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
and
<a href="http://squidfunk.github.io/mkdocs-material/" title="Material for MkDocs">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<a href="https://github.com/funkypenguin" class="md-footer-social__link fa fa-github"></a>
<a href="https://twitter.com/funkypenguin" class="md-footer-social__link fa fa-twitter"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application-c35428f87f.js"></script>
<script>app.initialize({url:{base:"../.."}})</script>
<script src="../../extras/javascript/piwik.js"></script>
<script>!function(e,t,a,n,o,c,i){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,c=t.createElement(a),i=t.getElementsByTagName(a)[0],c.async=1,c.src=n,i.parentNode.insertBefore(c,i)}(window,document,"script","https://www.google-analytics.com/analytics.js","ga"),ga("create","UA-139253-18","auto"),ga("set","anonymizeIp",!0),ga("send","pageview");var links=document.getElementsByTagName("a");Array.prototype.map.call(links,function(e){e.host!=document.location.host&&e.addEventListener("click",function(){var t=e.getAttribute("data-md-action")||"follow";ga("send","event","outbound",t,e.href)})});var query=document.forms.search.query;query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})</script>
</body>
</html>