<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Random Musings]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://chengl.com/</link><image><url>https://chengl.com/favicon.png</url><title>Random Musings</title><link>https://chengl.com/</link></image><generator>Ghost 3.22</generator><lastBuildDate>Tue, 03 Mar 2026 12:05:18 GMT</lastBuildDate><atom:link href="https://chengl.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Let's Encrypt Intranet]]></title><description><![CDATA[How to use Let's Encrypt to get certificates for internal websites?]]></description><link>https://chengl.com/lets-encrypt-intranet/</link><guid isPermaLink="false">5f01557e38333a60592f2653</guid><category><![CDATA[Let's Encrypt]]></category><category><![CDATA[Intranet]]></category><category><![CDATA[HTTPS]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sun, 03 Sep 2017 14:33:24 GMT</pubDate><media:content url="https://chengl.com/content/images/2020/07/le-logo-wide-1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://chengl.com/content/images/2020/07/le-logo-wide-1.png" alt="Let's Encrypt Intranet"><p><a href="https://letsencrypt.org/">Let's Encrypt</a> (LE) has been a popular choice to get certs for public websites. Because it's free and automated. But how to get certs for <strong>private</strong> websites, which are common in company's intranet?</p>
<h2 id="problem">Problem</h2>
<ul>
<li>There's a web app in your company's intranet.</li>
<li>The web app has a fully qualified domain name (FQDN), e.g. <strong>foo.example.com</strong>, not an internal one like <strong>foo.internal</strong>.</li>
<li>It only resolves to a private IP behind VPN. Therefore, it's inaccessible without a valid VPN.</li>
<li>You want to add an extra layer of security by enabling HTTPS.</li>
</ul>
<p>How to get a cert for it? And how to automate it and get it for free?</p>
<p>You may be wondering why not use the company's wild card cert (assuming it has one), i.e. *<strong>.example.com</strong>. Because wild card cert only supports one level of subdomain matching. If you have cert for *<strong>.example.com</strong>, it can be used for <strong>foo.example.com</strong>. But what about <strong>foo.bar.example.com</strong>? The problem remains.</p>
<h2 id="solution">Solution</h2>
<p>One quick and dirty way is to geneate a self-signed cert. But this will make browsers give a cert warning because it's not trusted. You don't want that.</p>
<p>How to use LE when the site is inaccessible publicly? There's a simple and elegant solution.</p>
<p>LE relies on <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html">ACME protocal</a> to verify domain ownership. There're a few types of <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.8">challenges</a> that ACME uses to verify domain ownership. The most common one is <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.8.3">HTTP Challenge</a>. But it's not applicable in this case because the intranet site's port 80 is inaccessible publicly. Similarly, <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.8.4">TLS SNI Challenge</a> can't be used because the intranet site's port 443 is inaccessible publicly either. <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#out-of-band-challenge">Out-of-Band Challenge</a> isn't really automated by definition. The only option left is <a href="https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#dns-challenge">DNS Challenge</a>. DNS Challenge works by provisioning a TXT record containing a designated value for the domain. Put simply,</p>
<ol>
<li>A designated value is generated by LE server</li>
<li>A TXT record containing the designated value has to be present on the domain</li>
<li>LE server queries the domain's TXT records</li>
<li>LE server verifies that the contents of one of the TXT records matches the designated value</li>
</ol>
<p>If all of the above steps succeed, the validation is successful. Cert is generated by LE.</p>
<p>The advantage of this challenge is that it only requires provisioning a TXT record and doesn't require a server to be publicly accessible on either port 80 or 443, which fits the intranet site case perfectly.</p>
<h2 id="implementation">Implementation</h2>
<p>How to automate DNS Challenge and get a cert depend on your existing infra automation (you're not manually creating your infra, are you?). I use <a href="https://www.terraform.io/">Terraform</a> with AWS provider. Below is an example to get a cert for <strong>foo.example.com</strong> using DNS Challenge and load it to ELB. Credits to the <a href="https://github.com/paybyphone/terraform-provider-acme">Terraform ACME provider</a></p>
<pre><code># code for acme registration and private key creation is omitted

resource &quot;acme_certificate&quot; &quot;certificate&quot; {
  server_url               = &quot;${var.acme_server_url}&quot;
  account_key_pem          = &quot;${tls_private_key.private_key.private_key_pem}&quot;
  common_name              = &quot;foo.example.com&quot;
  must_staple = true

  dns_challenge {
    provider = &quot;route53&quot;
  }

  registration_url = &quot;${acme_registration.reg.id}&quot;
}

resource &quot;aws_iam_server_certificate&quot; &quot;your_cert&quot; {
  name_prefix       = &quot;foo-example-cert&quot;
  certificate_body  = &quot;${acme_certificate.certificate.certificate_pem}&quot;
  certificate_chain = &quot;${acme_certificate.certificate.issuer_pem}&quot;
  private_key       = &quot;${acme_certificate.certificate.private_key_pem}&quot;

  lifecycle {
    create_before_destroy = true
  }
}

resource &quot;aws_elb&quot; &quot;foo&quot; {
  # other configs for the elb are omitted

  listener {
    instance_port      = 80
    instance_protocol  = &quot;http&quot;
    lb_port            = 443
    lb_protocol        = &quot;https&quot;
    ssl_certificate_id = &quot;${aws_iam_server_certificate.your_cert.arn}&quot;
  }
}
</code></pre>
<p>Of course, this assumes that the domain foo.example.com is managed by Route 53.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Multirepo vs Monorepo]]></title><description><![CDATA[How to choose between multirepo and monorepo? What to consider when choosing one over another?]]></description><link>https://chengl.com/multirepo-vs-monorepo/</link><guid isPermaLink="false">5f01557e38333a60592f264f</guid><category><![CDATA[monorepo]]></category><category><![CDATA[multirepo]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Thu, 20 Jul 2017 09:32:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Here's a conversion I keep hearing recently:</p>
<blockquote>
<p>A: Let's put all our projects in one repo.<br>
B: Why?<br>
A: Because Google and Facebook do monorepo.</p>
</blockquote>
<p>Whenever I hear this, I'm very tempted to show this picture.</p>
<p><img src="https://chengl.com/content/images/2017/07/JohnFrumCrossTanna1967-1.jpg" alt="JohnFrumCrossTanna1967-1"></p>
<p>Finding the origin of this picture is left as an exercise for the readers. On a more serious note, I want to write down my thoughts on multirepo vs monorepo.</p>
<h2 id="whatismultirepo">What is multirepo?</h2>
<p>One project one repository. Each project is an independent working unit. It can be a mobile app, frontend app, backend service or standalone CLI app.</p>
<ul>
<li>Each project has full autonomy to manage its evolution and deployment. There should be little to no coupling between projects. If projects depend on each other, the coupling between projects is API contracts, nothing else.</li>
<li>Each project manages dependencies on its own. Common library is in a repo of itself. Projects that depend on it can use any version of that library that they deem fit. It can be argued that sharing code is also introducing coupling. And it may result in long tail of maintenance of old libraries. Anyway, <a href="https://medium.com/netflix-techblog/towards-true-continuous-integration-distributed-repositories-and-dependencies-2a2e3108c051">Managing dependencies is hard.</a></li>
<li>Engineering teams are decoupled and can work on different projects in parallel without stepping on each other's toes.</li>
<li><a href="https://martinfowler.com/bliki/DeploymentPipeline.html">Deployment Pipeline</a> can be easily setup for each project.</li>
<li>Access control can be applied at project level.</li>
</ul>
<p>This repo structure is how most open source projects are run. And it's also probably what most developers are familiar with. Besides, it plays nicely with <a href="https://martinfowler.com/articles/microservices.html">microservices architecture</a>.</p>
<h2 id="whatismonorepo">What is monorepo?</h2>
<p>One monolithic repo that contains everything. Literally, everything.</p>
<ul>
<li>All projects (regardless they are related or not) and their dependent libraries, including 3rd party code that are not written by you nor your colleagues, live in one single repo.</li>
<li>There is one and only one verison of each dependency in the entire repo, which is the latest (<em>HEAD</em> in git terminology). Whenever a dependency needs to be updated, the update should be done for all projects depend on it and make sure that all projects still work. So the repo should always be in a consistent state. At any commit, all projects should work.</li>
<li><a href="http://danluu.com/monorepo/">Cross-project changes is easier.</a> Large scale refactoring is easier and can be done in one single atomic commit.</li>
<li>Extensive code sharing.</li>
<li>Everyone can see all code.</li>
</ul>
<h2 id="howtochoose">How to choose?</h2>
<p>If you are in a two-man startup, close this page now and keep working on your monolith. The choice between multirepo and monorepo is irrelevant to you. This question is only relevant when your company is operating at scale, i.e. &gt;100 developers.</p>
<p>Given the distinct characteristics of multirepo and monorepo, how to choose one over another? I think there are two main factors to consider, tooling and culture.</p>
<h3 id="tooling">Tooling</h3>
<p>In monorepo, running build is not as trivial as multirepo. You probably don't want to run tests and builds for all projects since that's just unnecessarily wasting time and computing resources. So the first thing to figure out is, given a change with one or more commits, which project(s) should build and what tests should run. And in order to figure this out, it's necessary to have acyclic directed graph (DAG) of dependencies for all projects. When a change is submitted, it's checked against the DAG of dependencies to see which projects are affected. All affected projects are possible to break, so tests are run only for these affected projects and their transitive dependents. Good news is that Google has open sourced their build tool <a href="https://bazel.build/">bazel</a> and Facebook has something similar called <a href="https://buckbuild.com/">buck</a>. While in multirepo, this problem doesn't exist because there is no need to figure out which project to build. Whenever a change happens to a project, that project's <a href="https://martinfowler.com/bliki/DeploymentPipeline.html">deployment pipeline</a> is triggered.</p>
<p>Source code version control is another tooling challenge imposed by monorepo. It's well known that git is bad at scaling. So is mercurial. <a href="http://git.net/ml/git/2009-05/msg00051.html">Quoting Linus Torvalds</a></p>
<blockquote>
<p>Git fundamnetally never really looks at less than the whole repo. Even if you limit things a bit (ie check out just a portion, or have the history go back just a bit), git ends up still always caring about the whole thing, and carrying the knowledge around.<br>
So git scales really badly if you force it to look at everything as one <em>huge</em> repository. I don't think that part is really fixable, although we can probably improve on it.</p>
</blockquote>
<p>Although <a href="https://schacon.github.io/git/git-read-tree.html#_sparse_checkout">sparse checkout</a> and <a href="https://schacon.github.io/git/git-clone">shallow clone</a> may alleviate the scaling problem, it's not a sustainable solution to large organizations. <a href="http://permalink.gmane.org/gmane.comp.version-control.git/189776">Some anecdote</a> suggests that the practical limit of git is 15GB of <code>.git</code> directory. This is probably why <a href="https://blogs.msdn.microsoft.com/bharry/2017/02/03/scaling-git-and-some-back-story/">Microsoft invented GVFS</a>, <a href="https://code.facebook.com/posts/218678814984400/scaling-mercurial-at-facebook/">Facebook chose to patch Mercurial</a> and <a href="https://cacm.acm.org/magazines/2016/7/204032-why-google-stores-billions-of-lines-of-code-in-a-single-repository/fulltext">Google builds Piper</a>. The point is, if your organization decides to go monorepo, think carefully about what version control to use.</p>
<p>Large scale refactoring in monorepo doesn't come for free. It requires <a href="http://www.hyrumwright.org/papers/icsm2013.pdf">dedicated</a> <a href="https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/41876.pdf">tooling</a> support.</p>
<p>Setting up deployment pipeline is complicated in monorepo. In multirepo, it's straightforward to have one project one pipeline. But in monorepo, one possible way is to have the first stage to figure out relevant projects and then trigger child pipelines for each relevant project. And each child pipeline may trigger other pipelines according to the DAG dependency graph. From what I see, the only off-the-shelf Continuous Delivery (CD) tool in the market that supports pipeline <a href="https://www.gocd.org/getting-started/part-3/#fan_out_fan_in">fan-in and fan-out</a> is <a href="https://www.gocd.org/">GoCD</a>. Other CD solutions in the market have very simple pipeline modeling. They are designed for multirepo, not monorepo. For example, there isn't an elegant solution for <a href="https://gitlab.com/gitlab-org/gitlab-ce/issues/18157">monorepo in GitLab</a> after one year, neither is <a href="https://github.com/travis-ci/travis-ci/issues/3540">Travis CI</a>.</p>
<p>In short, for multirepo to work, open source and commercial tools in the market are most likely sufficient. But for monorepo, depending on the scale, it may require high tooling investment.</p>
<h3 id="culture">Culture</h3>
<p>Multirepo and monorepo not only have different tooling requirments, but also varying engineering culture and philosophy. Multirepo values decoupling and engineering velocity, while monorepo favours standardization and consistency. It's all trade-offs. Whichever approach a company takes is a reflection of the company's culture. Netflix favours <a href="https://www.slideshare.net/reed2001/culture-1798664/2-Netflix_CultureFreedom_Responsibility2">Freedom &amp; Responsbility</a> so it prefers mutlirepo. And Google values consistency and code quality so it prefers monorepo. What's important here is to pick one approach that fits your organization's engineering culture, rather than fitting your organization's engineering culture to a certain repo structure.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Choosing multirepo or monorepo is not trivial. There is no single absolute right or wrong answer. Companies like Amazon and Netflix are living evidence that multirepo at large scale works. On the other hand, companies like Google and Facebook are living evidence that monorepo at large scale also works. Each approach has its own set of principles and practices to follow. Each approach also has its own challenges. Deciding between the two boils down to tooling and culture. Whichever approach an organization takes should be backed up by a list of solid reasons why one is preferred over the other in that organization, not <em>Because Google and Facebook do monorepo.</em> That's cargo cult engineering. And <a href="https://blog.bradfieldcs.com/you-are-not-google-84912cf44afb">You Are Not Google</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Be wary of  http/client.go]]></title><description><![CDATA[Does Go http client copy headers on redirect?]]></description><link>https://chengl.com/be-wary-of-go-http-client/</link><guid isPermaLink="false">5f01557e38333a60592f264e</guid><category><![CDATA[HTTP]]></category><category><![CDATA[Go]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sat, 25 Mar 2017 09:30:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently, I found out an interesting problem in Go. The problem can be reduced to a simple client request to a HTTP server.</p>
<p>Suppose we have a HTTP server, which serves only one rooted path <code>/foo/</code>.</p>
<pre><code class="language-go">package main

import (
	&quot;io&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;net/http/httputil&quot;
)

func handleFoo(w http.ResponseWriter, req *http.Request) {
	// request details
	dump, _ := httputil.DumpRequest(req, true)
	log.Println(string(dump))

	if auth := req.Header.Get(&quot;Authorization&quot;); auth != &quot;Bearer GoodToken&quot; {
		http.Error(w, &quot;401 Unauthorized&quot;, http.StatusUnauthorized)
		return
	}

	io.WriteString(w, &quot;Hello World!&quot;)
}

func main() {
	http.HandleFunc(&quot;/foo/&quot;, handleFoo)
	log.Fatal(http.ListenAndServe(&quot;:12345&quot;, nil))
}
</code></pre>
<p><code>handleFoo</code> simplily verifies that correct token is sent in the <code>Authorization</code> header. Otherwise, it returns <code>401 Unauthorized</code>.</p>
<p>The client sends request with correct token to the server.</p>
<pre><code class="language-go">package main

import (
	&quot;io&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;os&quot;
)

func main() {
	req, _ := http.NewRequest(http.MethodGet, &quot;http://localhost:12345/foo&quot;, nil)

	req.Header.Set(&quot;Authorization&quot;, &quot;Bearer GoodToken&quot;)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	defer resp.Body.Close()
	if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
		log.Fatal(err)
	}
}
</code></pre>
<p>What do you think the response is? <code>401 Unauthorized</code> or <code>Hello World!</code>?</p>
<p>The answer is <em>it depends</em>. It depends on the version of Go that the <strong>client</strong> code is running. If the client code is running on Go <strong>&lt;1.8.0</strong>, the response is <code>401 Unauthorized</code>. Otherwise, it's <code>Hello World!</code>.</p>
<p>But why?</p>
<p>It's not trivial to see at first glance. Let me list down what happens step by step.</p>
<ol>
<li>
<p>Client sends</p>
<pre><code>     GET /foo HTTP/1.1
     Host: localhost:12345
     Authorization: Bearer GoodToken
     ...
</code></pre>
</li>
<li>
<p>Server receives the request. <code>ServeMux</code> determines that the requested path <code>/foo</code> match the registered rooted path <code>/foo/</code>. <code>ServeMux</code> decides to send redirect (<a href="https://github.com/golang/go/commit/aaa0bc1043883390e052ec6f6775cbf0395dceb1">doc</a>).</p>
</li>
<li>
<p>Server responds with header</p>
<pre><code>     HTTP/1.1 301 Moved Permanently
     Location: /foo/
     ...
</code></pre>
</li>
<li>
<p>Client receives response and follows redirect by sending another request to server.</p>
</li>
<li>
<p>Server receives the 2nd request and let <code>handleFoo</code> handle it.</p>
</li>
</ol>
<p>Both <code>1.8.0</code> and versions &lt;<code>1.8.0</code> follow the same steps when processing the reqeust. The difference lies in Step #4.</p>
<p>Prior to <code>1.8.0</code>, following redirect in Go will <strong>NOT</strong> copy the original headers even if it's for the same domain. This wasn't changed util <a href="https://github.com/golang/go/commit/6e87082d41f0267b39e6a1854d655b1d1c2f7541">this commit</a>. What happened in Go &lt;<code>1.8.0</code> is that the <code>Authorization</code> header wasn't copied unpon redirect. Therefore, <code>handleFoo</code> returns <code>401 Unauthorized</code>.</p>
<p>Initially, I was quite surprised by the behaviour. After reading through <a href="https://github.com/golang/go/issues/4800">this issue</a> and <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3">HTTP spec</a>, I realized that <code>http/client.go</code> didn't actually do anything wrong because the HTTP spec didn't specify  following redirect should copy original headers. It's just that the well known HTTP clients, e.g. <a href="https://linux.die.net/man/1/curl">curl</a>, <a href="https://github.com/jakubroztocil/httpie">httpie</a>, <a href="https://github.com/rest-client/rest-client">rest-client</a>, etc., have established the convention.</p>
<p>Be wary of <code>http/client.go</code>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Keeping configurations sane for multiple projects on Google Container Engine]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>In my <a href="https://chengl.com/kubectl-authentication-made-simple/">previous post</a>, I present the easist and most secure way to get <code>kubectl</code> working for one project. But what about mutiple projects? Juggle mutiple projects on Google Container Engine (GKE) can be hard, especially when its configurations are admittedly <a href="https://github.com/kubernetes/kubernetes/issues/20605">quirky</a>. This post describes the best practice, in my</p>]]></description><link>https://chengl.com/working-with-multiple-projects-on-gke/</link><guid isPermaLink="false">5f01557e38333a60592f264d</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[GKE]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sat, 18 Feb 2017 09:27:00 GMT</pubDate><media:content url="https://chengl.com/content/images/2017/07/gke.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://chengl.com/content/images/2017/07/gke.png" alt="Keeping configurations sane for multiple projects on Google Container Engine"><p>In my <a href="https://chengl.com/kubectl-authentication-made-simple/">previous post</a>, I present the easist and most secure way to get <code>kubectl</code> working for one project. But what about mutiple projects? Juggle mutiple projects on Google Container Engine (GKE) can be hard, especially when its configurations are admittedly <a href="https://github.com/kubernetes/kubernetes/issues/20605">quirky</a>. This post describes the best practice, in my opinion, to keep configurations sane and easy to switch.</p>
<h2 id="problem">Problem</h2>
<p>Suppose you have an awesome app that runs on GKE. You probably want to have two different environments <code>staging</code> and <code>production</code>, and the environments should be completely isolated. So you create two projects on GKE, <code>awesome-app-staging</code> and <code>awesome-app-production</code>, and provisioned resources for each. Now the question is how to effectively switch between the two projects on command line without repeating <a href="https://github.com/kubernetes/kubernetes/issues/20605#issuecomment-218322105">these commands</a> over and over again.</p>
<h2 id="solution">Solution</h2>
<p>Assuming <code>gcloud</code> and <code>kubectl</code> are installed, but not configured,</p>
<h4 id="1createaconfigurationforeachproject">1. Create a configuration for each project</h4>
<p>Create an empty configuration. Don't use <code>default</code></p>
<pre><code class="language-bash">gcloud config configurations create awesome-app-staging
</code></pre>
<p><a href="https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account">Activate service account</a></p>
<pre><code class="language-bash">gcloud auth activate-service-account --key-file /path/to/your/key.json
</code></pre>
<p>Set project</p>
<pre><code class="language-bash">gcloud config set project awesome-app-staging
</code></pre>
<p>It's good to set <code>DEFAULT_ZONE</code> and <code>DEFAULT_REGION</code> too.</p>
<pre><code class="language-bash">gcloud config set compute/region ${REGION}
gcloud config set compute/zone ${ZONE}
</code></pre>
<p>Verify that your newly-created configuration has correct values</p>
<pre><code class="language-bash">gcloud config configurations describe awesome-app-staging
</code></pre>
<p><code>gcloud</code> is ready.</p>
<p>Get <code>kubectl</code> ready by getting GKE credentials for the project</p>
<pre><code class="language-bash">gcloud container clusters get-credentials ${CLUSTER} --zone ${ZONE} --project awesome-app-staging  
</code></pre>
<p>This will insert auth data and project info in <code>~/.kube/config</code>. Verify your context is correct</p>
<pre><code class="language-bash">kubectl config current-context
</code></pre>
<p>It should return a string which consists of project, zone and cluster.</p>
<p>Repeat the above process for each project.</p>
<h4 id="2switchprojects">2. Switch projects</h4>
<p>Once configurations are created for all projects, switching is easy.</p>
<p>List all contexts</p>
<pre><code class="language-bash">kubectl config get-contexts
</code></pre>
<p>Switch to a context</p>
<pre><code class="language-bash">kubectl config use-context ${CONTEXT}
</code></pre>
<p>See current context</p>
<pre><code class="language-bash">kubectl config current-context
</code></pre>
<p>Please note that switching context in <code>kubectl</code> does <em>NOT</em> automatically switch the corresponding <code>gcloud</code> configuration. This means that unless you instruct <code>gcloud</code> and <code>kubectl</code> to work on the same project, they can work on completely differnt projects. Therefore, as a good practice, remember to switch <code>gcloud</code> configuration whenever you switch <code>kubectl</code> context and vice versa, unless you know what you're doing.</p>
<p>Activate configuration</p>
<pre><code class="language-bash">gcloud config configurations activate ${CONFIGURATION}
</code></pre>
<h2 id="summary">Summary</h2>
<p>This is a very simple and elegant solution to manage multiple projects on GKE. If you have better ideas, please let me know.</p>
<p>Happy switching!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[kubectl Authentication Made Simple]]></title><description><![CDATA[Simplest way to get kubectl working]]></description><link>https://chengl.com/kubectl-authentication-made-simple/</link><guid isPermaLink="false">5f01557e38333a60592f264c</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[kubectl]]></category><category><![CDATA[Continuous Delivery]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Mon, 30 Jan 2017 09:25:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>While working on a continuous delivery pipeline to automate deployment to Google Container Engine (GKE), I found that getting <code>kubectl</code> to work is very <a href="https://kubernetes.io/docs/user-guide/sharing-clusters/#manually-generating-kubeconfig">complex</a> <a href="http://stackoverflow.com/questions/40426071/kubectl-access-to-google-cloud-container-engins-fails">and</a> <a href="http://stackoverflow.com/questions/40408321/whats-the-cli-authentication-process-as-of-google-container-engine-kubernetes-1">convoluted</a>, especially when it needs to be <a href="https://circleci.com/docs/continuous-deployment-with-google-container-engine/">noninteractive</a>. So I want to find out the easiest way to get <code>kubectl</code> working noninteractively.</p>
<p>Assuming that <code>gcloud</code> and <code>kubectl</code> are already installed but not necessarily setup, <strong>ONLY</strong> two commands are needed to get <code>kubectl</code> working noninteractively (verified with Google Cloud SDK 141.0.0 and kubectl 1.5.2)</p>
<pre><code class="language-bash">gcloud auth activate-service-account --key-file ${PATH_TO_KEY}

gcloud container clusters get-credentials ${CLUSTER} --zone ${ZONE} --project ${PROJECT}
</code></pre>
<p>The first command <a href="https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account">gcloud auth activate-service-account</a> is to authorize access to Google Cloud Platform using a <a href="https://cloud.google.com/compute/docs/access/service-accounts">service account</a>. <code>PATH_TO_KEY</code> is the path to the private key of the service account. The idea is very similar to IAM in AWS. One service account is roughly equivalent to an IAM group in AWS. And the private key of the service account is like Access key ID and Secret access key. You can create a service account and generate its private key <a href="https://console.cloud.google.com/iam-admin/serviceaccounts">here</a>. If you only need to deploy to GKE, <code>Container Engine Developer</code> is enough for the role.</p>
<p>The second command <a href="https://cloud.google.com/sdk/gcloud/reference/container/clusters/get-credentials">gcloud container clusters get-credentials</a> fetches cluster credentials and saves it in <code>~/.kube/config</code>. The environment variables in the command are self-explanatory.</p>
<p>You can now use <code>kubectl</code> to deploy to GKE. Probably this?</p>
<pre><code class="language-bash">kubectl set image deployment/${DEPLOYMENT} ${CONTAINER_NAME}=${IMAGE}:${IMAGE_VERSION}
</code></pre>
<p>In summary, this solution is simple, noninteractive (great for CI/CD) and secure (fine-grained permissions defined by service account). If you have a better solution, please let me know.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Speed Up SSH]]></title><description><![CDATA[Speed up SSH connection with simple config]]></description><link>https://chengl.com/speed-up-ssh/</link><guid isPermaLink="false">5f01557e38333a60592f264b</guid><category><![CDATA[ssh]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sun, 04 Dec 2016 09:23:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Recently I worked on a project where executing remote commands is very slow to start. This is expected because an SSH connection has to be established first.</p>
<pre><code class="language-bash">$ time ssh server exit
ssh server exit  0.04s user 0.01s system 0% cpu 7.901 total
</code></pre>
<p>It took nearly <strong>8</strong> seconds to ssh into <code>server</code>.</p>
<p>That's slow!</p>
<p>After a bit of googling, there is an elegant way to speed this up. Simply put this at the bottom of your <code>~/.ssh/config</code>.</p>
<pre><code>Host *
  Compression yes
  ControlMaster auto
  ControlPath ~/.ssh/sockets/%r@%h:%p
  ControlPersist 4h
  ServerAliveInterval 60
</code></pre>
<p>What this does is to <em>try</em> to share the master connection via a socket with multiple ssh sessions. It falls back to creating a new one if one does not already exist. The socket file is defined by <code>ControlPath</code>. <code>ControlPersist 4h</code> specifies that keep the master connection in background for four hours after the client connection is closed. <code>ServerAliveInterval 60</code> means that the client will wait for 60s to send a message to the server (if no data has been received during this period) to keep the connection alive.</p>
<p>Create sockets directory</p>
<pre><code class="language-bash">$ mkdir ~/.ssh/sockets
</code></pre>
<p>Running <code>time ssh server exit</code> again won't see any difference. But look into <code>~/.ssh/sockets</code>, a socket file is created.</p>
<pre><code class="language-bash">srw-------  1 user group    0 Dec  4 22:12 user@***.***.***.***:22
</code></pre>
<p>Try again</p>
<pre><code class="language-bash">$ time ssh server exit
ssh server exit  0.01s user 0.00s system 40% cpu 0.027 total
</code></pre>
<p>Now it takes less than <strong>0.5%</strong> of <strong>8</strong> seconds.</p>
<p>What's interesting is that, this config not only speeds up SSH connections, but also <code>git</code>, if you're authenticating with SSH.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ruby and Java Stack Level]]></title><description><![CDATA[What limits Ruby and Java's stack level? And how to change the limits?]]></description><link>https://chengl.com/ruby-and-java-stack-level/</link><guid isPermaLink="false">5f01557e38333a60592f264a</guid><category><![CDATA[Ruby]]></category><category><![CDATA[Java]]></category><category><![CDATA[recursion]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sat, 05 Nov 2016 09:17:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>While coding for an algorithmic problem, I discovered that Ruby's stack level is much shallower than Java. This caused a recursive DFS solution written in Ruby failed due to <code>stack level too deep (SystemStackError)</code>, while the same code written in Java passed. Whether recursion or tail recursion should be used is not the point of this post. This post is to find out what the max stack level is and what limits the stack level.</p>
<h2 id="ruby">Ruby</h2>
<p>Consider the following code</p>
<pre><code class="language-ruby">def recurse(n)
  return 1 if n == 1
  1 + recurse(n-1)
end

def binary_search
  answer, a, b = 0, 0, 1_000_000_000

  while a&lt;=b
    mid = (a+b)/2
    begin
      recurse(mid)
      answer = mid
      a = mid + 1
    rescue SystemStackError
      b = mid - 1
    end
  end

  answer
end

puts &quot;Max Stack Level: #{binary_search}&quot;
</code></pre>
<p>What do you think <code>Max Stack Level</code> is?</p>
<p>Running the code reports that the the <code>Max Stack Level</code> is <strong>10080</strong> on my MBP. This is because the stack size of the default Ruby 2.3.1 VM (MRI) is limited to <strong>1MB</strong>.</p>
<p>This is very limiting for even a medium-sized dataset. To increase it, one can <a href="http://magazine.rubyist.net/?Ruby200SpecialEn-note#l16">specify VM stack size</a> for Ruby &gt;= 2.0.0. To check current max stack size, open <code>irb</code></p>
<pre><code class="language-bash">irb(main):001:0&gt; RubyVM::DEFAULT_PARAMS
{
         :thread_vm_stack_size =&gt; 1048576,
    :thread_machine_stack_size =&gt; 1048576,
          :fiber_vm_stack_size =&gt; 131072,
     :fiber_machine_stack_size =&gt; 524288
}
</code></pre>
<p>Let's set <code>RUBY_THREAD_VM_STACK_SIZE</code> to 2MB</p>
<pre><code class="language-bash">export RUBY_THREAD_VM_STACK_SIZE=2097152
</code></pre>
<p>And running the same code again returns <code>Max Stack Level:  20162</code>, which is roughly 2 * <strong>10080</strong>. Setting <code>RUBY_THREAD_VM_STACK_SIZE</code> to 3MB will increase <code>Max Stack Level</code> to <strong>30243</strong>. This proves that <code>Max Stack Level</code> is linearly proportional to <code>RUBY_THREAD_VM_STACK_SIZE</code> in Ruby.</p>
<h2 id="java">Java</h2>
<pre><code class="language-java">public class Solution {
    public static void main(String[] args) {
        System.out.println(&quot;Max Stack Level: &quot; + binarySearch());
    }

    private static long binarySearch() {
        long a = 0, b = Long.MAX_VALUE, answer = 0;
        while (a &lt;= b) {
            long mid = (a + b) / 2;
            try {
                recurse(mid);
                answer = mid;
                a = mid + 1;
            } catch (StackOverflowError e) {
                b = mid - 1;
            }
        }
        return answer;
    }

    private static long recurse(long n) {
        if (n == 1) return 1;
        return 1 + recurse(n - 1);
    }
}
</code></pre>
<p>Running this code with</p>
<pre><code class="language-bash">java Solution
</code></pre>
<p>returns <code>Max Stack Level: 49150</code>. This is because the thead stack size on my MBP defaults to <strong>1MB</strong>. The defaults are platform-specific, see <a href="http://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html#wp1024112">here</a>.</p>
<p>JVM has an option <a href="https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html#wp999540">ss</a> for setting the thread stack size. Running the code with thread stack size of 2M</p>
<pre><code class="language-bash">java -Xss2M Solution
</code></pre>
<p>returns <code>Max Stack Level: 98302</code>, which roughly doubled the stack level. Running it with 4MB gives <strong>196606</strong> and it matches what I thought. Therefore, in Java, <code>Max Stack Level</code> is linearly proportional to <code>ss</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Both Ruby and Java have clearly defined max stack size, which limits the number of levels code can recurse. In Ruby, it's defined by environment variable <code>RUBY_THREAD_VM_STACK_SIZE</code>. While in Java, it's defined by JVM option <code>ss</code>. A clear observation is that when given the same max stack size and the same code, Java performs much better than Ruby. This is not a surprise because Java (on JVM) is a compiled, while Ruby MRI is interpreted.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Docker Workflow]]></title><description><![CDATA[What's the workflow to develop Docker image on local machine, build and test it on CI, and deploy to production?]]></description><link>https://chengl.com/docker-workflow/</link><guid isPermaLink="false">5f01557e38333a60592f2649</guid><category><![CDATA[Docker]]></category><category><![CDATA[Workflow]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[Continuous Delivery]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Wed, 25 May 2016 09:07:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>In my previous posts, I demoed how to orchestrate Docker with <a href="https://chengl.com/orchestrating-docker-using-swarm/">Swarm</a> and <a href="https://chengl.com/orchestrating-docker-with-kubernetes/">Kubernetes</a>. They all assume the Docker image <a href="https://hub.docker.com/r/chenglong/simple-node/">chenglong/simple-node</a> is already there and ready to be deployed. But how to develop that image in the first place? How to streamline and automate the process of developing it on local machine, building and testing it on Continuous Integration (CI) server, and finally deploying to production?</p>
<p>This post introduces one possible Docker workflow.</p>
<p>I recommend reading <a href="https://chengl.com/orchestrating-docker-with-kubernetes/">my previous post</a> before this to understand the app architecture and Kubernetes.</p>
<h3 id="workflow">Workflow</h3>
<p><img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-05-25-at-1-35-53-PM.png" alt="Screen-Shot-2016-05-25-at-1-35-53-PM"></p>
<p>The flow is summarized as follows:</p>
<ol>
<li>Code on local machine. Using Docker for local development is highly recommended. Find out more from my post <a href="https://chengl.com/using-docker-for-rails-development/">Using Docker for Rails Development</a></li>
<li>Push commits to Github</li>
<li>Travis is notified and builds new a new Docker image and runs tests against the newly-built image.</li>
<li>If all work well, Travis pushes the new image to Docker Hub.</li>
<li>Travis deploys the new image to Google Container Engine (GKE). Prerequisite: you need to setup a container cluster on GKE first and get the app up and running. Find out more from my post <a href="https://chengl.com/orchestrating-docker-with-kubernetes/">Orchestrating Docker with Kubernetes</a>.</li>
<li>Kubernetes starts <a href="http://kubernetes.io/docs/user-guide/rolling-updates/">rolling update</a>.</li>
</ol>
<p>This flow works pretty well except that some hacks are necessary for Travis to deploy to GKE. I will explain the hacks and why they are necessary in details in the subsequent section.</p>
<p>Please note that the tools used in this flow can be easily replaced with their respective equivalents. For example, Github can be replaced with Gitlab, Bitbucket or whatever you like. Travis can be replaced by <a href="https://circleci.com/">CircleCI</a>, <a href="https://app.shippable.com/">Shippable</a>, or any CI that <strong>supports Docker</strong>. Docker Hub can be replaced by your private registry. GKE can be replaced by <a href="https://aws.amazon.com/ecs/">Amazon EC2 Container Service</a> or any <a href="https://blog.docker.com/2016/02/containers-as-a-service-caas/">Containers as a Service (CaaS)</a>.</p>
<p>All source code can be found in <a href="https://github.com/ChengLong/docker-nodejs-redis">my repo</a>.</p>
<h3 id="thedevilisinthedetail">The devil is in the detail</h3>
<pre><code>sudo: required
language: node_js
node_js:
  - '5'

services:
  - docker

env:
  global:
    - secure: AoSvVfpX77AtMBpXKyHH67wTKWCN9xdXoMWroU2TwewBRToebQmLUesT+6gP2rCVoQ282IpESkZkIUumj38rvCGWpL3fLU67Fb1Fa/SqKQ++OYri3NNmoOLltkHMCHHz2bl7B8/72KJQ8e+sl8KCmKBTaLp1g2/36+DZwL9KZjFOqsQg2pwv3zjOUkZte6v6Igsl7lV1pbLkg9Fq1KEvOZl8D6bHgtNuHJGYZZrHCUDHMXxXeQ8/wL0v7GpUkZAe85Ve+fkPoX7AXYumbi5SsxbwVYG64j4zdU2ydlhmMRfOrT4BIbOHUO1kmP5uSf4/xX+fGJZuGDlVT1P5RTZyvu1dA+X10a0t2pOYHAjTjJp6CNRIKuJ4izBvxCZaLAUd42FfU9BAFyUPlViaDzNZAW3DCoapfnS0xM+UY1U8Z7bs+lIXycqIE6OR4KZXfKtpoKakmh8d0eY8LpUNu81VS9Z2mxfRg0xsYhp/1E/X+LZM/YCzPpOhQCc0ZGJEtbqj1/Qebp/GRBIx9oabBH+JxldqrZApIF0MsGiaIT8Q0LJ8wNVVc7QQqhjwdtvP1Wh2pxOvnzEyAdB4AeKrYv0SMwa3OxpIDtJEP1AvXRTZWXPObSSgAhK2VPnOK9Y++N2BlSKJh9k1nlnzn0FQsHlPUV/r9Ui62tmQbzOyL69xltQ=
    - secure: KLweub+lnStvWmKY/XoDy8nXo4v5lYTHapXtewfDFrnWPL6UQV//kklUAswzGEWp4id9pOolZB4RNfucCBzB1Kb1KgSp23wz9mj+HWY+qUQRsLyIxYwsZf/o9H/MMkesG8jOF/vU35Ne3e7MdSmpNuqEqErIqc25691rBMLSDxi5YS3jcB+vGkv38xtlcDilnUrSneKuNEzuI5Q7MKr6vqIVaW3vbwEs5hEh2oPSAgVno67s9NyfUi2Dl4sIDDwzV+iXc9FNk4Ww6Vkm8uzZvIXhZDuyVnFWSJhgfNgElUna7XfqnWOyPdZQ0rh2iOtKzO9uZQiOoNq8JuT9VstFcFlhsfAtlynB5Xgp/EH9NUIZvUiueRhs0bOH9SKRUijIeTQ/OimuX0y8EZdz7TKSigZkc7iVqD0g2E2kLtv3avTCodwts54V8bX7//r8Y2FE7DFkZRGL7Khf5LPjRj7xVEb9OhEkuSYfs0oEhqyXCSMUNyov3BDFbZ/AWh0smQmKYv36U2JvMfPghNlgAE+b/Xc4F/sJMmNSDdLBmGwoJDJtVA5iB3RCfkZfEeqhMoj2C8uu8s8IASzxh2HnaaO5IQKy7kGwAzzRfazTq6JhvLPfteOKStvRYrLLBll7l2DPazLJK8ctKZ6CTFxoycU+R73IliwwpqLCAhasD9N7Q8w=
    - secure: htu5A6fjnQvJTvinumWI1u1unoDMgY5FleGM8JwVLBQl6Srr1GU/2ulii1LSbypG/JimwMzT6BIRMfNgCzAeBc6aWM8xbZUsLl7p+WFyYNxOW26mVm8Z5R+2JZXm3vZoevjHD35gWIBMReqVySIRLXanZ9SOVRC1IcX2Om1uOoQHAqwrFh4KERWnzepAXylfUtFatqmRmCRczH4m9MKF7OgbZD/7xKHHoJxXLHjmWaCQrLmucb01/e/vl3C/LwOvc+A6fwXhhMFdL5UUn9iVWLBhTYknULjOKDF2AgvahvStxGUEAcscaW4qATW32Ylamn3K4l5X+3ic+fLKQHOR+oCaVmaqcpExbxuBfLHGQkV7DiUePQsS1D5NLfAHBdbd8MJCwxQRlSIt/z/aemkx6uWEBu053c47yuuKltFhAm6B7Z52UkHC8fpFfuy03xgorjNTayghPlYDeFZRzItkibTEY1EH4Sh9yVkAIhv5hqomMQMAkxaEFgmBkK7/8+UHB+sMlBupMiejTqIgZOzIhj1uqDGkokqI2UqlNx1wuqzGJ/KnQWzsgFsnoU0Q1zIzw0i1RAiDu8qj5vSnioqw8pvZWuBDuRltE5U4fup529ZtFSNx4Ceb3ECathc6+3Em8OvtqvBOr44xNBW8XCFbi82Ys3Dd/SEHGwlcwYoT4/A=
    - secure: W1CzoMk6zUyhKhx+Q4mX/O1rh7GKFTrMNpkJ+QqN4WN56K3v49t6kotABXbFU4K8ZP84DhUkQYtm6DbEgCkxeNaT0k0gzKyG+CZo5sI6yqx15CwmR/qCkFruDQqUG/2hZNTP5ZP/7hMQwNCxATxuzu2GOcnwk1YuIJsJ5by9+RdWwKBYYhSG/BokNq6JaYxZx8NeMTa5W/CJXtu/5qFsMDQ07Syg4MgAnRHbRB9uCxpUOtJ44HDhZFOOY48qIDVpYhVpTg0XVzw5VkFNgOuczIYWw/SaWyOyoKgt8FtLhUPDsHtvueFqSJffYlyoh81Uxu+LNAtZoact5wFOwkDM+mzqRe/lzm2hcYY0nc4Dxc1ito43GedQmFnadZSozZfKAEmRV0m+1W6iE6fi+DpHXm3JCmgvolB9khLiYVTHkgaSzbfZDfdBmoXPEcaDuKnY6KTB410sGnadO6IT3Z5x8FtMQ/In/dB7XQr/G1aXve3KjUz8/oLZbS+IWEH8eQEOAR8V6w6HpvQIZE6ccwEzmLILlygne7TSAnOPQX1hiEDdUYYOtoyVEv/NUzd/OpGqNl6edYsXZB1bcY7FWy2YhmJD4mqe5h6mp4yRXYyuFbdC3OIBGpr1vnNaF9uUdayZ59pZ4kIojCQL2SKZKqOYdezyV/h0ohP93FIboi/nL7Q=
    - secure: jxY+/q6svho30zk1RMtnwHhHkYMw+NqQS4n7dOILpsI6zKwC4n2c+xjD0MoOYjVsd/SiJucXeeNR4BcS4+OglsVxja68rBWqV4F3xloGwtMZebqMhNGSV4kitgAkdDNktQid2fQAwUsoCtEfq3A6ijMdOgoehZwxXqFg5+5cRwjI58vVOfdFDGPUO6KXTDhwwXIgTi9zN8nXSNCgHUZfrIGw6jTdFvL2NGkejbX4AqutethTlXlA7lVeE/O1SUW4H8MRl59MyKUwWm/j7qkmM/TX+PUaXSwXgoR0Nzbt2DKTbY56qff1olHaUMuV5c8A5gYlThvVYbMnGRTK7JYOzmchyvRsqc1inxIwU7bi/J1DnQpCX4qF06NBU+OLbx8+qDc9E6l9XGdQ09HFVPOctlNrkfcPDgGVfRYwomU8+gb8MykgBxtUqLIpJN0pr9sNZN4L4jGuv58ThkCfoaH6YgiLKTITJ7cLAE2tNmHj5Kw+vHNvJcUltRdMOSaDI6TyAGkVpUSHaTEOvdvsj5y5wFS3TdIHnTQOWMpBiYSHY7D4trvv6KRagDjoB7DKaAohYPwataJdu3Wu+A0UIsukYPG6uVn4RPJmQHBMT8vTtf9q9eSpoRU68zSnhAL5aXtq1vqtfy4859QQDzpH7JMA87WtJqCQ4Fp8MHOg3uvb+ZM=
    - secure: FXEj1gwSi+xjpc4GFFS4ehxy6Q4wpmmPq/rlfWijOAYsNwukfkL2gCmnzjTT6ZZVHM47bNtHlTdqTM8bmBjWVeVEPKwvULq6i2neiGI7OI0vh2dsbCMIZ7bCMuRL0/WRq/A8YPsXIK76YIlMg8sSzdGdAmvZpjTbLywSl9KoSlH3S56e2Nz1kb8DhP4emeFcxdXul463JUdOBpCZa8oJLopOaX5+PypOk6XOjOyWkxN938F4w5/4MzhgghcrTRwtOQCPHyJR2zg08MWm5COKi3L2EmgPyctXGGr31YVeRTC9Gg3SJ+Mt8OfsX8f7TnUzhnfA3IszJpxw4cF1snpVmXZED2J1Aa6Gi7mSIhb1CdYNUxUKx6BVnaUGDRfmcxtGOt8Uy0A2sRl3FGhVVH6xQaaTmG3nzU0fm44cO1RwxqXfP4kXqUNj+LlCUC+3hIRrWRMfxjIWwGA0LZIqnVlou2TRLkxdM3IHx46cFqEfpD6Do/NXOCUZYRcE95FqvK6ZrobsZapkI0WkmoO4YdzCkuW9nSc8Y121OWlP9urkzaU4X7tO7FbLshKmjYNL4vLqyJ0gkN21yVJTIJWE6l/TGGi+RxwFF2jzN8oRjOJrWESmUSPa5WhNvuIOu1t0RObwRup0yjArRuUIXxuQt99c1tkfsn52fZWZih3t4bM0bxE=
    - CLOUDSDK_CORE_DISABLE_PROMPTS=1
    - COMMIT=${TRAVIS_COMMIT::8}
    - IMAGE=chenglong/simple-node
    - IMAGE_VERSION=$IMAGE:$COMMIT
    - DEPLOYMENT=frontend
    - CONTAINER_NAME=nodejs

before_script:
  - docker build -t $IMAGE:$COMMIT app
  - docker tag $IMAGE:$COMMIT $IMAGE:latest
  - docker tag $IMAGE:$COMMIT $IMAGE:travis-$TRAVIS_BUILD_NUMBER

script:
  - docker version
  - docker-compose version
  - docker-compose -f docker-compose.ci.yml run test

after_success:
  - docker login -e=&quot;$DOCKER_EMAIL&quot; -u=&quot;$DOCKER_USERNAME&quot; -p=&quot;$DOCKER_PASSWORD&quot;
  - docker push $IMAGE
  - curl https://sdk.cloud.google.com | bash
  - source /home/travis/.bashrc
  - gcloud components update kubectl
  - kubectl config set-credentials default --username=${GKE_USERNAME} --password=${GKE_PASSWORD}
  - kubectl config set-cluster default --server=${GKE_SERVER} --insecure-skip-tls-verify=true
  - kubectl config set-context default --cluster=default --user=default
  - kubectl config use-context default
  - kubectl patch deployment $DEPLOYMENT -p '{&quot;spec&quot;:{&quot;template&quot;:{&quot;spec&quot;:{&quot;containers&quot;:[{&quot;name&quot;:&quot;'&quot;$CONTAINER_NAME&quot;'&quot;,&quot;image&quot;:&quot;'&quot;$IMAGE_VERSION&quot;'&quot;}]}}}}'
</code></pre>
<p>This is the <code>.travis.yml</code> that instructs Travis to</p>
<ul>
<li>build the Docker image <code>chenglong/simple-node</code> with 3 tags: <code>latest</code>, <code>${TRAVIS_COMMIT::8}</code> and <code>travis-$TRAVIS_BUILD_NUMBER</code></li>
<li>run tests against the newly-built image <code>chenglong/simple-node:latest</code>. Note that the tests are run in a container named <code>test</code>. The tests basicially verify that the app gives expected response. See test code <a href="https://github.com/ChengLong/docker-nodejs-redis/blob/master/test/test.js">here</a>.</li>
<li>push the image to <a href="https://hub.docker.com/r/chenglong/simple-node/tags/">Docker Hub</a> if tests pass</li>
<li>install latest Google Cloud SDK because the one on Travis is <a href="https://github.com/travis-ci/travis-ci/issues/5530">too old</a></li>
<li>install <code>kubectl</code></li>
<li>config <code>kubectl</code> to connect to the a pre-existing cluster on GKE</li>
<li>rolling update deployment <code>frontend</code> with the newly-built image <code>chenglong/simple-node</code></li>
</ul>
<p>A few key points:</p>
<ul>
<li><code>chenglong/simple-node</code> is <em>ONLY</em> built once. It's the same image that is tested against and deployed to GKE. See the logs for a build <a href="https://travis-ci.org/ChengLong/docker-nodejs-redis/builds/132763728">here</a>.</li>
<li>Tests are run in a container named <code>test</code>, which simply starts <code>app</code> and <code>redis</code>, fires a few requests and verifies the response. To run the tests, I used <code>docker-compose -f docker-compose.ci.yml run test</code>. See <a href="https://github.com/ChengLong/docker-nodejs-redis/blob/master/docker-compose.ci.yml">docker-compose.ci.yml</a></li>
<li>At the time of writing, Travis CI only has <code>docker-compose</code> <a href="https://docs.travis-ci.com/user/docker/">1.4.2</a>, which <em>does not</em> support compose file <a href="https://docs.docker.com/compose/compose-file/#upgrading">version 2</a>. This requires the compose file to be in version 1, unless you want to install <code>docker-compose</code> 1.6+.</li>
<li>Travis doesn't support deploying to GKE yet, which requires some hacks to install the latest Google Cloud SDK and <code>kubectl</code>, so that I can do <code>kubectl patch deployment</code>.</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<p>I hope this simple demo gives you a better idea of how Docker can help in your day to day work. A few obvious advantages of this Docker workflow:</p>
<ul>
<li>It significantly improves how we ship software. The artifact from CI is <em>NOT</em> anymore a tar ball, jar file, war file, nor ear file etc. It's simply a Docker image. A Docker image has not only the source code baked in, but also a complete running environment. Therefore, it removes the need to first provision server before deploying source code. Do you see Chef, Puppet or Ansible in the demo? In the age of containers, all you need is a server that runs Docker.</li>
<li>Since it's the same image that is tested in CI gets deployed to production (or maybe staging), it's guaranteed that it works in exactly the same way.</li>
<li>By containerizing the app, each component of the app is run as a container. And each container can be individually updated and deployed. Does this ring a bell? If not, read <a href="http://martinfowler.com/microservices/">Microservices</a>.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Orchestrating Docker with Kubernetes]]></title><description><![CDATA[How to orchestrate Docker with Kubernetes?]]></description><link>https://chengl.com/orchestrating-docker-with-kubernetes/</link><guid isPermaLink="false">5f01557e38333a60592f2648</guid><category><![CDATA[Docker]]></category><category><![CDATA[Orchestration]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sun, 15 May 2016 09:04:00 GMT</pubDate><media:content url="https://chengl.com/content/images/2017/07/Kubernetes_logo.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://chengl.com/content/images/2017/07/Kubernetes_logo.jpg" alt="Orchestrating Docker with Kubernetes"><p>This is my second post on Docker orchestration. In <a href="https://chengl.com/orchestrating-docker-using-swarm/">the first post</a>, I demonstrated orchestrating Docker with Swarm, Machine, Compose and Consul. This post is to demonstrate orchestrating the same app with Kubernetes and draw comparisons between them. I recommend reading <a href="https://chengl.com/orchestrating-docker-using-swarm/">that post</a> before this.</p>
<h3 id="introductiontokubernetes">Introduction to Kubernetes</h3>
<blockquote>
<p>Kubernetes is an open-source system for automating deployment, operations, and scaling of containerized applications.</p>
</blockquote>
<blockquote>
<p>It groups containers that make up an application into logical units for easy management and discovery. Kubernetes builds upon <a href="http://queue.acm.org/detail.cfm?id=2898444">a decade and a half of experience of running production workloads at Google</a>, combined with best-of-breed ideas and practices from the community.</p>
</blockquote>
<p>Find out more <a href="http://kubernetes.io/">here</a>.</p>
<p>Kubernetes has a number of interesting concepts:</p>
<ul>
<li><a href="http://kubernetes.io/docs/user-guide/pods/">Pods</a></li>
<li><a href="http://kubernetes.io/docs/user-guide/labels/">Labels</a></li>
<li><a href="http://kubernetes.io/docs/user-guide/replication-controller/">Replication Controller</a></li>
<li><a href="http://kubernetes.io/docs/user-guide/services/">Services</a></li>
<li><a href="http://kubernetes.io/docs/user-guide/deployments/">Deployments</a></li>
</ul>
<p>Kubernetes is definitely not a trivial tool. Luckily, <a href="http://kubernetes.io/docs/">the official guides</a> does a great job at explaining things. I highly recommend going through it to learn Kubernetes.</p>
<h3 id="architecture">Architecture</h3>
<p>This is a much simplified version of the architecture.<br>
<img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-05-15-at-10-16-08-PM.png" alt="Orchestrating Docker with Kubernetes"></p>
<p>Neither load balancer nor service discovery is in the diagram because they are both handled by Kubernetes internally. See Kubernetes' architecture <a href="https://github.com/kubernetes/kubernetes/blob/release-1.2/docs/design/architecture.md">here</a>.</p>
<p>For this demo, I will use <a href="https://cloud.google.com/container-engine/">Google Container Engine</a> as a hosted solution. You can run Kubernetes on <a href="http://kubernetes.io/docs/getting-started-guides/#cloud">various platforms</a>, including local machine, Cloud IaaS providers, bare metals, etc. By the way, <a href="https://cloud.google.com/free-trial/">Google Cloud Platform</a> gives $300 in the 60-day free trial. Try it out!</p>
<h3 id="0prerequisites">0. Prerequisites</h3>
<p>This example requires a running Kubernetes cluster. If you want to use Google Container Engine, follow <a href="https://cloud.google.com/container-engine/docs/quickstart">this</a>. Or let <code>gcloud init</code> guide you.</p>
<p>Verify <code>kubectl</code> is configed and the cluster is ready</p>
<pre><code>kubectl cluster-info
</code></pre>
<h3 id="1runbackend">1. Run backend</h3>
<p>According to <a href="http://kubernetes.io/docs/user-guide/config-best-practices/">the best practices</a>, <em>create a Service before corresponding Deployments so that the scheduler can spread the pods comprising the Service</em>. So the redis service is created before deployment and they are defined in <a href="https://github.com/ChengLong/kubernetes-demo/blob/master/backend.yaml">backend.yaml</a>.</p>
<pre><code>kubectl create -f backend.yaml
</code></pre>
<p>Verify the service is created</p>
<pre><code>$ kubectl get services redis
NAME      CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
redis     10.7.246.226   &lt;none&gt;        6379/TCP   7m
</code></pre>
<p>Verify the deployment is created:</p>
<pre><code>$ kubectl get deployments
NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
redis     1         1         1            1           53s
</code></pre>
<p>Verify one redis pod is created</p>
<pre><code>$ kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
redis-3180978658-4y13o   1/1       Running   0          3m
</code></pre>
<p>See the pod's logs</p>
<pre><code>kubectl logs redis-3180978658-4y13o
</code></pre>
<h3 id="2runfrontend">2. Run frontend</h3>
<p>Create <a href="https://github.com/ChengLong/kubernetes-demo/blob/master/frontend.yaml">frontend</a> by</p>
<pre><code>kubectl create -f frontend.yaml
</code></pre>
<p>Note that I specify <code>type: LoadBalancer</code> because I want this service to be accessible by the public. By default, services and pods are <em>only</em> accessible inside the internal Kubernetes network. Also, the Nodejs deployment will maintain 3 pods, each runnning one container based on this Docker image <a href="https://hub.docker.com/r/chenglong/simple-node/">chenglong/simple-node:v1</a>.</p>
<p>You can verify that the service and deployment for Nodejs are created correctly in the same way as backend. If everything goes well, the app should be up and running. If you do <code>kubectl get pods</code>, there should be 3 frontend pods and 1 backend pod.</p>
<p>Get the external IP</p>
<pre><code>$ kubectl get service frontend
NAME       CLUSTER-IP     EXTERNAL-IP       PORT(S)   AGE
frontend   10.7.246.128   104.155.202.254   80/TCP    8m
</code></pre>
<p>You need to wait a while for the external IP to be available. Repeat <code>curl &lt;EXTERNAL-IP&gt;</code> a few times to verify that both load balancing and page counting work. You can also see the page in browser.</p>
<p><img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-05-15-at-5-45-44-PM.png" alt="Orchestrating Docker with Kubernetes"></p>
<h3 id="3selfhealing">3. Self-healing</h3>
<p>One important feature Kubernetes offers out of the box is self-healing. Put simply, Kubernetes will ensure that the specified number of replicas are running. In the event of pods or nodes fail, Kubernetes recreate new ones.</p>
<p>To test this feature, I delete one frontend pod and immediately list all pods.</p>
<pre><code>$ kubectl delete pod frontend-2747139405-bk4ul; kubectl get pods
NAME                        READY     STATUS              RESTARTS   AGE
backend-3180978658-i1ipl    1/1       Running             0          13m
frontend-2747139405-bk4ul   1/1       Terminating         0          8m
frontend-2747139405-hjnb9   1/1       Running             0          39s
frontend-2747139405-luukn   0/1       ContainerCreating   0          3s
frontend-2747139405-mfhky   1/1       Running             0          8m
</code></pre>
<p>From the pods status, <code>frontend-2747139405-bk4ul</code> is terminating. But notice that there is a new pod <code>frontend-2747139405-luukn</code> automatically being created.</p>
<h3 id="4scaling">4. Scaling</h3>
<p>Scaling the frontend is as simple as</p>
<pre><code>$ kubectl scale deployment/frontend --replicas=6; kubectl get pods
deployment &quot;frontend&quot; scaled
NAME                        READY     STATUS              RESTARTS   AGE
backend-3180978658-i1ipl    1/1       Running             0          41m
frontend-2747139405-4xhlz   0/1       ContainerCreating   0          4s
frontend-2747139405-autox   0/1       ContainerCreating   0          4s
frontend-2747139405-hjnb9   1/1       Running             0          28m
frontend-2747139405-luukn   1/1       Running             0          27m
frontend-2747139405-mfhky   1/1       Running             0          36m
frontend-2747139405-r8ayi   0/1       ContainerCreating   0          4s
</code></pre>
<p>As seen from the above, Kubernetes immediately starts creating new replicas to match the desired state.</p>
<p>Scaling down is similar</p>
<pre><code>kubectl scale deployment/frontend --replicas=3
</code></pre>
<p>Depending on the nature of the app, it's probably more useful to define <a href="http://kubernetes.io/docs/user-guide/horizontal-pod-autoscaling/">Horizontal Pod Autoscaler</a> to do autoscaling.</p>
<h3 id="5rollingupdate">5. Rolling update</h3>
<p>Suppose we need to update the frontend app to <a href="https://hub.docker.com/r/chenglong/simple-node/tags/">chenglong/simple-node:v2</a>. Right now it's v1. How to roll out this release without service disruption? Kubernetes supports this natively and makes it simple.</p>
<p>To reduce risk, I want to do a <a href="http://martinfowler.com/bliki/CanaryRelease.html">Canary Release</a> first. Create the <a href="https://github.com/ChengLong/kubernetes-demo/blob/master/frontend-canary.yaml">canary deployment</a></p>
<pre><code>kubectl create -f frontend-canary.yaml
</code></pre>
<p>Note that I set <code>replicas: 1</code> so that the ratio of <code>stable</code> pods to <code>canary</code> pod is 3:1. And since the <code>canary</code> has labels <code>app: nodejs</code> and <code>tier: frontend</code>, it will be automatically load balanced by the frontend service.</p>
<p>List all pods with label <code>track</code></p>
<pre><code>$ kubectl get pods -L track
NAME                               READY     STATUS    RESTARTS   AGE       TRACK
backend-3180978658-23la7           1/1       Running   0          11m       &lt;none&gt;
frontend-1287392616-0lfxx          1/1       Running   0          9m        stable
frontend-1287392616-95b6q          1/1       Running   0          7m        stable
frontend-1287392616-bpstl          1/1       Running   0          9m        stable
frontend-canary-1722551660-6aaon   1/1       Running   0          5m        canary
</code></pre>
<p>Verify the deployment works</p>
<pre><code>$ kubectl get deployments frontend-canary
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
frontend-canary   1         1         1            1           3m
</code></pre>
<p>Hit the frontend service a few times to verify that only one pod is updated to <a href="https://hub.docker.com/r/chenglong/simple-node/tags/">chenglong/simple-node:v2</a></p>
<pre><code>$ curl &lt;EXTERNAL-IP&gt;
This request is served by frontend-canary-1722551660-o4qcu
You have viewed this page 20 times!
Server Time: 2016-05-15T11:30:08.649Z

$ curl &lt;EXTERNAL-IP&gt;
This request is served by frontend-1287392616-wgl39. You have viewed this page 22 times!
</code></pre>
<p>Since the Canary Release is working fine, I want to roll out to all pods.</p>
<pre><code>$ vim frontend.yaml # update simple-node:v1 to simple-node:v2
$ kubectl apply -f frontend.yaml
</code></pre>
<p>Kubernetes will progressively kill old pods and create new pods. <em>It does not kill old Pods until a sufficient number of new Pods have come up, and does not create new Pods until a sufficient number of old Pods have been killed.</em>  Find out more <a href="http://kubernetes.io/docs/user-guide/deployments/#updating-a-deployment">here</a>.</p>
<p>Find out details of one of the none-canary pods. Note that the image is <code>chenglong/simple-node:v2</code> not <code>v1</code>.</p>
<pre><code>$ kubectl describe pods frontend-1389432169-aszzd
Name:		frontend-1389432169-aszzd
Namespace:	default
...
Containers:
  nodejs:
    Container ID: docker://faf884e2da293f6de66e275614d...
    Image:		chenglong/simple-node:v2
</code></pre>
<p>Delete Canary deployment</p>
<pre><code>$ kubectl delete deployments frontend-canary
deployment &quot;frontend-canary&quot; deleted
</code></pre>
<p>If this release is not ideal, we could easily roll back</p>
<pre><code>$ kubectl rollout undo deployment/frontend
deployment &quot;frontend&quot; rolled back
</code></pre>
<h3 id="kubernetesvsswarm">Kubernetes vs Swarm</h3>
<p>Based on <a href="https://chengl.com/orchestrating-docker-using-swarm/">the previous post</a> and this one, it's clear to me that there're quite a few prominent differences between Kubernetes and Swarm:</p>
<ul>
<li>Kubernetes is a more mature and powerful orchestration tool than Swarm. Swarm provides basic and essential native clustering capabilities. But Kubernetes has built-in self-healing, service discovery (etcd), load balancing, automated rollouts and rollbacks, etc. Building all these functions on Swarm is not trivial. However, this may or may not be a good thing depending on use cases. If you do need all the features that Kubernetes provides and don't intend to do any customization, Kubernetes is perfect for you. Otherwise, the complexity of Kubernetes might become a burden because it requires more efforts to adopt and support.</li>
<li>Different philosophies. Kubernetes has clearly taken an all-in-one approach, while Swarm is <a href="https://blog.docker.com/2016/03/swarmweek-container-orchestration-docker-swarm/">batteries included but swappable</a>. So if I want to use Consul as the service discovery backend, I can easily do that in Swarm. But Kubernetes uses etcd by default and <a href="https://github.com/kubernetes/kubernetes/issues/1957">it's still not supported after more than one year.</a>.</li>
<li>Kubernetes is primarily based on Google's experience on managing containers. So it's opinionated by definition. I'm not saying being opinionated is necessarily bad. But if you do decide to use it, you probably have to live with its choices. <a href="https://github.com/kubernetes/kubernetes/issues/1957">Consul is just one example.</a></li>
<li>Command Line. Unlike Swarm, Kubernetes is not native to Docker. It has its own set of commands. See the the differences <a href="http://kubernetes.io/docs/user-guide/docker-cli-to-kubectl/">here</a>. But in general, <code>kubectl</code> is quite similar to <code>docker-cli</code>.</li>
<li><a href="https://medium.com/on-docker/evaluating-container-platforms-at-scale-5e7b44d93f2c#.kio1oocz5">Swarm performs better than Kubernetes. </a> I think this only matters when you are running hundreds or even thousands of nodes and containers. At a small to medium scale, other factors (e.g the points above) play a more important part when deciding which one to use.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Orchestrating Docker with Swarm, Machine, Compose and Consul]]></title><description><![CDATA[How to orchestrate Docker containers with Swarm, Machine, Compose and Consul]]></description><link>https://chengl.com/orchestrating-docker-using-swarm/</link><guid isPermaLink="false">5f01557e38333a60592f2647</guid><category><![CDATA[Docker]]></category><category><![CDATA[Orchestration]]></category><category><![CDATA[Swarm]]></category><category><![CDATA[Consul]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Fri, 15 Apr 2016 09:01:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>With <a href="https://blog.docker.com/2015/11/docker-multi-host-networking-ga/">multi-host networking ready for production</a> and <a href="https://blog.docker.com/2015/11/swarm-1-0/">the announcement of Swarm 1.0</a>, I think it's time to give Docker a serious try. This post details the steps I took to orchestrate a multi-host and multi-container app.</p>
<h3 id="architecture">Architecture</h3>
<p><img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-04-15-at-12-57-55-AM.png" alt="Screen-Shot-2016-04-15-at-12-57-55-AM"></p>
<p>This is a simple Node app that uses Redis as database, load balanced by Nginx. Each blue box is a Docker host, which runs several containers. All hosts talk to Consul for service discovery. The cluster has 5 nodes, each serves a specific purpose. We want to easily scale up and down the number of <code>app</code> containers.</p>
<p>To achieve this, we will use the following stack</p>
<ul>
<li><a href="https://docs.docker.com/compose/">Docker Compose</a> &gt;= 1.6</li>
<li><a href="https://docs.docker.com/machine/">Docker Machine</a> &gt;= 0.6</li>
<li><a href="https://docs.docker.com/swarm/">Docker Swarm</a> &gt;= 1.0</li>
<li><a href="https://hub.docker.com/r/progrium/consul/">Consul</a></li>
<li><a href="https://hub.docker.com/r/gliderlabs/registrator/">Registrator</a></li>
</ul>
<p>All scripts and the compose file for this demo are available <a href="https://github.com/ChengLong/docker-orchestration-swarm-demo">here</a>.</p>
<h4 id="1createandrunconsul">1. Create and Run Consul</h4>
<p><a href="https://www.consul.io/">Consul</a> is an excellent tool for service discovery. It works great with Docker. You could also use <a href="https://github.com/coreos/etcd">etcd</a>.</p>
<p>Let's create a Docker host for Consul</p>
<pre><code>docker-machine create -d virtualbox consul
</code></pre>
<p>This will create a new Docker host named <code>consul</code> on my local virtualbox. It's certainly possible to create a Docker host on a supported cloud provider, e.g. AWS, Digital Ocean, Google Compute Engine, etc. The difference is the driver. Checkout <a href="https://docs.docker.com/machine/drivers/os-base/">driver options and arguments</a>.</p>
<p>Once <code>consul</code> is created, connect to it</p>
<pre><code>eval $(docker-machine env consul)
</code></pre>
<p>Run <a href="https://hub.docker.com/r/progrium/consul/">progrium/consul</a> in background.</p>
<pre><code>docker run -d -p 8500:8500 -h consul --restart always gliderlabs/consul-server -bootstrap
</code></pre>
<p>Verify Consul is working</p>
<pre><code>curl $(docker-machine ip consul):8500/v1/catalog/services
</code></pre>
<p>You can also go to Consul's web UI <code>http://$(docker-machine ip consul):8500/ui</code></p>
<h4 id="2createtheswarm">2. Create The Swarm</h4>
<p>Create the Swarm master</p>
<pre><code>docker-machine create \
    -d virtualbox \
    --swarm \
    --swarm-master \
    --swarm-discovery=&quot;consul://$(docker-machine ip consul):8500&quot;\
    --engine-opt=&quot;cluster-store=consul://$(docker-machine ip consul):8500&quot; \
    --engine-opt=&quot;cluster-advertise=eth1:2376&quot; \
    swarm-master
</code></pre>
<p>This is a very long command. Let's take a detailed look.</p>
<ul>
<li><code>-d virtualbox</code> indicates the driver is virtualbox</li>
<li><code>--swarm</code> configs the newly created machine with Swarm</li>
<li><code>--swarm-master</code> dictates the newly created machine as the Swarm master</li>
<li><code>--swarm-discovery=&quot;consul://$(docker-machine ip consul):8500&quot;</code> designates Consul as the discovery service</li>
<li><code>--engine-opt=&quot;cluster-store=consul://$(docker-machine ip consul):8500&quot;</code> designates Consul as the distributed KV storage backend for the cluster</li>
<li><code>--engine-opt=&quot;cluster-advertise=eth1:2376&quot;</code> advertises the machine on the network</li>
</ul>
<p>Create a node for load balancer</p>
<pre><code>docker-machine create \
	-d virtualbox \
    --swarm \
    --swarm-discovery=&quot;consul://$(docker-machine ip consul):8500&quot;\
    --engine-opt=&quot;cluster-store=consul://$(docker-machine ip consul):8500&quot; \
    --engine-opt=&quot;cluster-advertise=eth1:2376&quot; \
    --engine-label host=load-balancer \
    load-balancer
</code></pre>
<p>Note that we give this machine a label <code>host</code> with value <code>load-balancer</code>, which will be used for scheduling later.</p>
<p>Create app server 1</p>
<pre><code>docker-machine create \
	-d virtualbox \
    --swarm \
    --swarm-discovery=&quot;consul://$(docker-machine ip consul):8500&quot;\
    --engine-opt=&quot;cluster-store=consul://$(docker-machine ip consul):8500&quot; \
    --engine-opt=&quot;cluster-advertise=eth1:2376&quot; \
    --engine-label host=app-server \
    --virtualbox-cpu-count &quot;2&quot; \
    --virtualbox-memory &quot;2048&quot; \
    app-server-1
</code></pre>
<p>Note the two added parameters <code>--virtualbox-cpu-count &quot;2&quot;</code> and <code>--virtualbox-memory &quot;2048&quot;</code>, which gives the app server more resources. You should adjust these values according to your needs.</p>
<p>Create app server 2</p>
<pre><code>docker-machine create \
	-d virtualbox \
    --swarm \
    --swarm-discovery=&quot;consul://$(docker-machine ip consul):8500&quot;\
    --engine-opt=&quot;cluster-store=consul://$(docker-machine ip consul):8500&quot; \
    --engine-opt=&quot;cluster-advertise=eth1:2376&quot; \
    --engine-label host=app-server \
    --virtualbox-cpu-count &quot;2&quot; \
    --virtualbox-memory &quot;2048&quot; \
    app-server-2
</code></pre>
<p>It's best practice to put your app servers in different availability zones or even different cloud providers to achieve high availability. Checkout <a href="https://docs.docker.com/machine/drivers/">the drivers reference</a> for options.</p>
<p>Create database node</p>
<pre><code>docker-machine create \
	-d virtualbox \
    --swarm \
    --swarm-discovery=&quot;consul://$(docker-machine ip consul):8500&quot;\
    --engine-opt=&quot;cluster-store=consul://$(docker-machine ip consul):8500&quot; \
    --engine-opt=&quot;cluster-advertise=eth1:2376&quot; \
    --engine-label host=database \
    --virtualbox-disk-size &quot;40000&quot; \
    database
</code></pre>
<p>Note that we specify <code>--virtualbox-disk-size &quot;40000&quot;</code> because it's for running database. You can adjust this according to your needs.</p>
<p>Connect to the Swarm</p>
<pre><code>eval $(docker-machine env -swarm swarm-master)
</code></pre>
<p>Check cluster info</p>
<pre><code>docker info
</code></pre>
<p>You should see 5 nodes in the cluster, namely <code>swarm-master</code>, <code>load-balancer</code>, <code>database</code>, <code>app-server-1</code> and <code>app-server-2</code>.</p>
<p>You can also run</p>
<pre><code>docker run --rm swarm list consul://$(docker-machine ip consul):8500
</code></pre>
<p>to find out all nodes in the cluster.</p>
<h4 id="3runregistratorineachhost">3. Run registrator in each host</h4>
<p>To automatically register and deregister services for all Docker containers in each host, we need to run <a href="https://hub.docker.com/r/gliderlabs/registrator/">gliderlabs/registrator</a> in each node of the cluster. Registrator will also use Consul as the KV store.</p>
<p>Run the following script</p>
<script src="https://gist-it.appspot.com/github/ChengLong/docker-orchestration-swarm-demo/blob/master/run_registrator_in_all_hosts">
</script>
<p>After this, the infrastructure is ready. It's time to deploy.</p>
<h4 id="4dockercompose">4. Docker Compose</h4>
<p>We will use the following compose file to start the app</p>
<script src="https://gist-it.appspot.com/github/ChengLong/docker-orchestration-swarm-demo/blob/master/docker-compose.yml"></script>
<p>A few important points:</p>
<ul>
<li><a href="https://hub.docker.com/r/chenglong/nginx-consul-template/">chenglong/nginx-consul-template</a> is a simple image that use NGINX and <a href="https://github.com/hashicorp/consul-template">consul-template</a> to load balance any service as instructed. In this case, it's service <code>myapp</code>. It requires a running Consul for service discovery. <em>Environment variable <code>CONSUL_URL</code> should be set</em>.</li>
<li><a href="https://hub.docker.com/r/chenglong/simple-node/">chenglong/simple-node</a> is a simple Node image that uses redis as database. All it does is to display the hostname and the number of times the page is visited.</li>
<li><code>constraint:host==load-balancer</code>, <code>constraint:host==app-server</code> and <code>constraint:host==database</code> are <a href="https://docs.docker.com/swarm/scheduler/filter/">node filters</a> which tell the Swarm master to schedule the corresponding containers on matching hosts. So in this case, the <code>load-balancer</code> container will be scheduled to run on Docker host <code>load-balancer</code>. All <code>app</code> containers will be scheduled to run on either <code>app-server-1</code> or <code>app-server-2</code>.</li>
<li>There are two <a href="https://docs.docker.com/engine/userguide/networking/get-started-overlay/">overlay networks</a> <code>frontend</code> and <code>backend</code>. This is what makes multi-host networking possible, i.e. <code>load-balancer</code> can talk to <code>app</code> and <code>app</code> can talk to <code>redis</code>.</li>
</ul>
<p>Set <code>CONSUL_URL</code></p>
<pre><code>export CONSUL_URL=$(docker-machine ip consul):8500
</code></pre>
<p>Start the app</p>
<pre><code>docker-compose up -d
</code></pre>
<p>Verify that all containers are running</p>
<pre><code>docker-compose ps
</code></pre>
<p>Test the app works by repeating <code>curl $(docker-machine ip load-balancer)</code> a few times. You should see the counter running.</p>
<h4 id="5scaling">5. Scaling</h4>
<p>Now you can easily scale the number of <code>app</code> containers</p>
<pre><code>docker-compose scale app=4
</code></pre>
<p>This creates 3 more <code>app</code> containers.</p>
<p>By default, Swarm use <a href="https://docs.docker.com/swarm/scheduler/strategy/">spread</a> scheduling strategy. So all <code>app</code> containers will be shared evenly by <code>app-server-1</code> and <code>app-server-2</code>.</p>
<p>Test load balancing works by repeating <code>curl $(docker-machine ip load-balancer)</code> a few times. You should see the hostname cycles the 4 containers and the counter is running.</p>
<p>Scaling down is easy too</p>
<pre><code>docker-compose scale app=2
</code></pre>
<h3 id="summary">Summary</h3>
<p>With Docker Swarm, Machine, Compose and Consul, it's not hard to scale and schedule Docker containers to a cluster of nodes. Although I demoed with virtualbox, you could do the same with Digital Ocean, AWS or any supported cloud provider to deploy Swarm clusters for production.</p>
<p>Since Swarm is native for Docker and it follows &quot;batteries included but swappable&quot; principle, I feel it's much more natural and easier to use than Kubernetes. I will try to do a post about Swarm vs Kubernetes in the near future.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Gmail Sending Limit]]></title><description><![CDATA[Gmail has daily sending limits. So is Google Apps for Work. The limits are way lower in trial period. Find out how to deal with it.]]></description><link>https://chengl.com/gmail-sending-limit/</link><guid isPermaLink="false">5f01557e38333a60592f2646</guid><category><![CDATA[Gmail]]></category><category><![CDATA[Limit]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Fri, 08 Apr 2016 08:58:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Many people use Gmail as personal emails. Companies use <a href="https://apps.google.com/">Google Apps for Work</a>. Everyone is happy using it. Not until you hit its limits.</p>
<p>I recently learnt <a href="https://support.google.com/a/answer/166852?hl=en">its limits</a> (shown below) in a hard way.</p>
<p><img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-04-08-at-3-33-16-PM.png" alt="Screen-Shot-2016-04-08-at-3-33-16-PM"></p>
<p>I will hightlight the most restrictive constraints from the above:</p>
<ul>
<li>You can send max 2000 emails per day from one account, <strong>500</strong> for trial accounts</li>
<li>3000 unique recipients per day, <strong>500</strong> for trial accounts</li>
</ul>
<p>From what I understand, normal Gmail accounts have the same limits as trial accounts. What are trial accounts? When you sign up <a href="https://apps.google.com/">Google Apps for Work</a>, all users are in trial period for 30 days. And it's free during this period. After trial, you have to pay ~$5/user/month.</p>
<p>The catch is <strong>What if you want to send &gt; 500 emails during trial period?</strong> This is the problem that I had.</p>
<p>According to <a href="https://support.google.com/a/answer/166852?hl=en">Google</a>,</p>
<blockquote>
<p>At the end of your free trial period, your sending limits will be automatically increased when your domain is cumulatively billed for at least $30 USD (or the same amount in your currency). To expedite this process, you can manually prepay this amount as suggested here. It will take up to 48 hours to upgrade your sending limits after you submit the manual payment. Note: while you're still in your trial period, your sending limits will not be increased.</p>
</blockquote>
<p>So I signed up Google Apps and setup Email with my custom domain. Made manual payment of $31. After ~550 emails, it stopped with following erorr</p>
<pre><code>Daily user sending quota exceeded. s197sm16092990pfs.62 - gsmtp
</code></pre>
<p>I contacted online chat support. Surprisingly, I got reply immediately. It turned out that even though I had made the payment of $31, it <strong>DOES NOT</strong> automatically end my trial period. So the limit of 500 emails still applies. The solution is  Google support staff sent me a new Google Apps contract via email. Unpon agreeing to its T&amp;C, it effectively expired my trial period and changed my account to paid subscription. I was told that it may take 48 hours for the new limit of 2000 emails/day to be effective.</p>
<p>So far, contacting Google support seems to be the only way to upgrade from trial limits within trial period. I haven't found any setting that I can do it on my own, which is weird. If customers don't want to enjoy the free month, they should have the capability to do so.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Life is Short. Run Tests in Parallel]]></title><description><![CDATA[How to run RSpec and Cucumber in parallel]]></description><link>https://chengl.com/life-is-short-run-tests-in-parallel/</link><guid isPermaLink="false">5f01557e38333a60592f2645</guid><category><![CDATA[Testing]]></category><category><![CDATA[Parallel]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sun, 20 Mar 2016 08:56:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>If you are doing Rails, chances are that you have <a href="http://rspec.info/">RSpec</a> and <a href="https://cucumber.io/">Cucumber</a> tests. They are great tools to give you the confidence that your software is working as expected. But as project grows, you may find that your tests are getting slower and slower, expecially the Cucumber tests. It not only slows down the speed of your local development, but also your Continuous Integration pipeline (Test is probably the first stage in your CI). We don't want to waste time waiting tests to finish in either local or CI. We want to develop and integrate faster. Here is how.</p>
<h3 id="1installparallel_tests">1. Install parallel_tests</h3>
<p><a href="https://github.com/grosser/parallel_tests">parallel_tests</a> is a great gem to run Test::Unit, RSpec, Cucumber and Spinach tests parallel on multiple CPU cores.</p>
<p>Update your Gemfile</p>
<pre><code>group :development do
	gem &quot;parallel_tests&quot;
    # other gems...
end
</code></pre>
<p>Update config/database.yml</p>
<pre><code>test:
	database: db/test&lt;%= ENV['TEST_ENV_NUMBER'] %&gt;.sqlite3
</code></pre>
<p>If you are not using sqlite3 for test db, update it accordingly.<br>
Each CPU core will run its share of tests using its own test db. So if you have 8 CPU cores, you will see 8 sqlite3 files generated in  <code>db</code> when running tests.</p>
<p>Then</p>
<pre><code>bin/bundle install
</code></pre>
<h3 id="2usebinstubstorunparalleltests">2. Use Binstubs to Run Parallel Tests</h3>
<p>If you don't know what is binstubs and why you should use it for all commands, take a look at <a href="https://robots.thoughtbot.com/use-bundlers-binstubs">this</a> and <a href="https://github.com/rbenv/rbenv/wiki/Understanding-binstubs">this</a>.</p>
<p>Generate binstubs only for <code>parallel_tests</code></p>
<pre><code>bin/bundle binstubs parallel_tests
</code></pre>
<p>It will generate 4 binstubs in <code>bin</code></p>
<ul>
<li>parallel_cucumber</li>
<li>parallel_rspec</li>
<li>parallel_spinach</li>
<li>parallel_test</li>
</ul>
<p>You don't need to keep all of them. Only keep the ones that you need. In my case, I only use RSpec and Cucumber. So I deleted <code>parallel_spinach</code> and <code>parallel_test</code>. Add the rest to version control.</p>
<h3 id="3runtestsinparallel">3. Run Tests in Parallel</h3>
<p>Run all specs in parallel</p>
<pre><code>bin/parallel_rspec spec
</code></pre>
<p>Run all features in parallel</p>
<pre><code>bin/parallel_cucumber features
</code></pre>
<p>This will launch N browsers, where N is the number of your CPU cores.</p>
<p>Be amazed by how long it takes to run all tests now. Theoretically, the test running time can be reduced to <code>T/N</code>, where <code>T</code> is the time to run in serial and <code>N</code> is number of CPU cores. But that's not achievable in practice because the number of tests may not be split evenly among the processes. And even if they are split evenly, they may not take equal time to run. Besides, there are overheads to coordinate the processes and collate results. However, it's very easy and common to achieve 20% ~ 50% reduction in test running time, which can potentially save 5 ~ 10 mins in a medium size project. In a long run, it's a huge time saving.</p>
<h3 id="4tips">4. Tips</h3>
<p>Not all tests are equal. Some tests are for must-have features and some are for nice-to-have features. Some tests fail more often than others. To get feedback faster, it's a good practice to group  tests into categories and run them in order, e.g.</p>
<pre><code>bin/parallel_cucumber features -o '-t @smoke'
bin/parallel_cucumber features -o '-t @flaky'
bin/parallel_cucumber features -o '-t ~@smoke,~@flaky'
</code></pre>
<p>The above example will</p>
<ol>
<li>Run all smoke features. More on <a href="https://en.wikipedia.org/wiki/Smoke_testing_(software)">smoke tests</a></li>
<li>Run all flaky features</li>
<li>Run the rest</li>
</ol>
<p>This way, it avoids running the smoke tests and flaky tests at the very end.</p>
<h3 id="summary">Summary</h3>
<p>It's quite easy to setup parallel tests. It not only speeds up your local development, but also CI pipeline. And the time saving is tremendous in the long run.</p>
<p>Life is Short. Run Tests in Parallel!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[HTTP/2]]></title><description><![CDATA[What is HTTP/2 and how to upgrade]]></description><link>https://chengl.com/http2/</link><guid isPermaLink="false">5f01557e38333a60592f2644</guid><category><![CDATA[HTTP]]></category><category><![CDATA[HTTP/2]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Mon, 14 Mar 2016 08:53:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h3 id="whatishttp2andwhy">What is HTTP/2 and Why</h3>
<p><a href="https://tools.ietf.org/html/rfc2068">HTTP/1.1</a> has been serving most part of the Web since 1997. As websites get more and more sophisticated and resource intensive, it starts to show its limitations, e.g. one outstanding request per TCP connection. So its next-generation emerged: <a href="http://http2.github.io/">HTTP/2</a>.</p>
<p><a href="http://http2.github.io/faq/">HTTP/2 FAQ</a> does a great job explaining the background and specifications. Highly recommended. Here is an executive summary, HTTP/2:</p>
<ul>
<li>is specifically designed to improve performance</li>
<li>is based on <a href="http://tools.ietf.org/html/draft-mbelshe-httpbis-spdy-00">SPDY</a></li>
<li>is binary, instead of textual</li>
<li>is fully multiplexed, instead of ordered and blocking</li>
<li>can therefore use one connection for parallelism</li>
<li>uses header compression to reduce overhead</li>
<li>allows servers to push responses proactively into client caches</li>
<li>is backward-compatible, designed to be drop-in replacement for HTTP/1.1</li>
<li>is <a href="http://caniuse.com/#search=http%2F2">supported by most broswers over TLS</a></li>
</ul>
<p>HttpWatch  <a href="https://blog.httpwatch.com/2015/01/16/a-simple-performance-comparison-of-https-spdy-and-http2/">reported</a> good performance improvement by using HTTP/2.</p>
<h3 id="howtoupgrade">How to Upgrade</h3>
<p>Upgrading from HTTP/1.1 to HTTP/2 is quite easy. You just need to make sure that your web server supports HTTP/2 and &quot;turn it on&quot;. I will use NGINX as an example.</p>
<h4 id="installnginx195">Install NGINX 1.9.5+</h4>
<p>In the case of NGINX, only <a href="https://www.nginx.com/blog/nginx-1-9-5/">1.9.5+</a> supports HTTP/2.</p>
<p>Check your NGINX version</p>
<pre><code>nginx -V
</code></pre>
<p>If it's lower than 1.9.5, you need to upgrade NGINX first. Otherwise, head over to <a href="#turn-on-http2">Turn On HTTP/2</a>.<br>
At the time of writing, the latest stable release of NGINX is 1.8.1, which is lower than 1.9.5. So you need to install NGINX Mainline version. Don't worry that Mainline version is not stable. It's actually <a href="https://www.nginx.com/blog/nginx-1-6-1-7-released/">better than Stable version</a> because it has the latest bug fixes.</p>
<p>On Ubuntu,</p>
<p>Install NGINX signing key</p>
<pre><code>wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key
</code></pre>
<p>Add the following in <code>/etc/apt/sources.list</code></p>
<pre><code>deb http://nginx.org/packages/mainline/ubuntu/ &lt;codename&gt; nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ &lt;codename&gt; nginx
</code></pre>
<p>Note that <code>&lt;codename&gt;</code> should be the result of <code>lsb_release -c | cut -f2</code>.</p>
<p>Then install</p>
<pre><code>sudo apt-get update
sudo apt-get install nginx
</code></pre>
<h4 id="compilenginxfromsourcetosupportalpnoptional">Compile NGINX from Source to Support ALPN (Optional)</h4>
<p>Depending on the OpenSSL version that your NGINX is built with, it may not support <a href="https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation">Application Layer Protocol Negotiation</a> (ALPN). More details <a href="http://serverfault.com/questions/732474/nginx-configured-with-http2-doesnt-deliver-http-2">here</a>. Besides, starting from May 15th 2016, <a href="http://blog.chromium.org/2016/02/transitioning-from-spdy-to-http2.html">Chrome will ONLY support ALPN</a>, which means supporting ALPN is necessary for HTTP/2 to work fully.</p>
<p>Find out NGINX build details</p>
<pre><code>nginx -V
</code></pre>
<p>If it says <code>built with OpenSSL 1.0.2f  28 Jan 2016</code>, you don't need to compile NGINX from source. Jump to <a href="#turn-on-http2">Turn On HTTP/2</a>.</p>
<p>If it's built with OpenSSL lower than <code>1.0.2f</code>, e.g. <code>1.0.1f</code>. You need to compile NGINX from source with OpenSSL 1.0.2f. The detailed steps can be found <a href="https://www.clay.fail/posts/ubuntu-http2-in-mere-hours/">here</a>.</p>
<h4 id="anameturnonhttp2aturnonhttp2"><a name="turn-on-http2"></a> Turn on HTTP/2</h4>
<p>In <code>site.conf</code>,</p>
<pre><code>server {
	listen 443 ssl http2;
    ...
}
</code></pre>
<p>That's all you need to turn on HTTP/2!</p>
<p>Please note that although HTTP/2 doesn't require HTTPS, <a href="http://caniuse.com/#search=http%2F2">most web browsers only support HTTP/2 via TLS</a>. So you do need to serve your site via HTTPS in order to use HTTP/2. If your site isn't using HTTPS yet, check out <a href="https://chengl.com/lets-encrypt-nginx/">my post</a> on how to use <a href="https://letsencrypt.org/">Let's Encrypt</a> to make it HTTPS.</p>
<p>Reload NGINX</p>
<pre><code>sudo nginx -s reload
</code></pre>
<p>Refresh your site and inspect the page, you should see that the assets from your site are loaded via protocal <code>HTTP/2 (h2)</code>.</p>
<p><img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-03-14-at-12-56-51-AM.png" alt="Screen-Shot-2016-03-14-at-12-56-51-AM"></p>
<p>This blog is using HTTP/2. You can inspect this page to see <code>HTTP/2</code> in action.</p>
<p>Another way is to let <a href="https://tools.keycdn.com/http2-test">KeyCDN</a> do the test.<br>
<img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-06-06-at-3-38-49-PM.png" alt="Screen-Shot-2016-06-06-at-3-38-49-PM"></p>
<p>If you prefer command line</p>
<pre><code>echo | openssl s_client -alpn h2 -connect yourserver.com:443 | grep ALPN
</code></pre>
<p>You should see <code>ALPN protocol: h2</code>.</p>
<h3 id="summary">Summary</h3>
<p>Although HTTP/2 is only out for about one year, it has very <a href="https://www.keycdn.com/blog/http2-statistics/">good adoption</a> thanks to its backward-compatibility, easy of upgrading and performance benefits. I'm convinced that <a href="https://http2.akamai.com/">HTTP/2 is the future of the Web</a>.</p>
<p>Wait no more, upgrade!</p>
<h3 id="reference">Reference</h3>
<ul>
<li><a href="https://http2.github.io/">HTTP/2 Home Page</a></li>
<li>HTTP/2 Specifications <a href="http://httpwg.org/specs/rfc7540.html">RFC7540</a> and <a href="http://httpwg.org/specs/rfc7541.html">RFC7541<br>
</a></li>
<li><a href="https://bagder.gitbooks.io/http2-explained/content/en/index.html">http2 explained</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Using Docker for Rails Development]]></title><description><![CDATA[How to use Docker for Rails Development]]></description><link>https://chengl.com/using-docker-for-rails-development/</link><guid isPermaLink="false">5f01557e38333a60592f2643</guid><category><![CDATA[Docker]]></category><category><![CDATA[Rails]]></category><category><![CDATA[Development]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sun, 14 Feb 2016 08:49:00 GMT</pubDate><media:content url="https://chengl.com/content/images/2017/07/docker_logo.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h3 id="why">Why</h3>
<img src="https://chengl.com/content/images/2017/07/docker_logo.png" alt="Using Docker for Rails Development"><p>There are many use cases of Docker. I see people primarily using it for Continous Integration and deployment. But Docker is also good for development. The obvious advantages of using Docker for development are:</p>
<ul>
<li>No need to install app dependencies on dev machines. App dependencies are built into Docker images. Hence, the dev machines are not messed up with crazy dependencies. The only dependency needed on dev machines is Docker, nothing else.</li>
<li>Have a consistent development environment for all developers. No more excuse like &quot;<strong>It works on my machine</strong>&quot;!</li>
<li>Onboard new developers quickly. No need to spend hours setting up new dev machine and configuring it. You only need <code>docker-compose up</code> and you can start coding.</li>
</ul>
<h3 id="prerequisites">Prerequisites</h3>
<p>This post will show you how to setup a Ruby on Rails development environment using Docker. My dev machine has</p>
<ul>
<li>OS X EI Capitan</li>
<li>Docker version 1.10.1</li>
<li>docker-compose version 1.6.0</li>
</ul>
<p>Please note my Docker and docker-compose versions. If yours have lower versions, the example here will <em>NOT</em> work for you. In particular, I'm using docker-compose file <a href="https://docs.docker.com/compose/compose-file/#version-2">Version 2</a>, which are supported by Compose 1.6.0+ and require a Docker Engine of version 1.10.0+.</p>
<h3 id="architecture">Architecture</h3>
<p>The architecture of the app looks like this<br>
<img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-02-13-at-9-47-14-PM.png" alt="Using Docker for Rails Development"></p>
<p>We have two containers, one for app and one for db. The web container has Nginx, Passenger and the Rails app. The db container only has Postgres in it. This architecture is probably common for many Rails apps running in production. And as a good practice, you should make your development environment as close to production as possible so that the potential production issues are exposed in the development stage, not when you receive calls from unhappy customers.</p>
<h3 id="buildandruncontainerweb">Build and Run Container Web</h3>
<h5 id="step1createrailsproject">Step 1. Create Rails Project</h5>
<p>My dev machine only has Docker installed. It doesn't have ruby. How do I create a Rails project? Use Docker.</p>
<pre><code>docker run -it --rm --user &quot;$(id -u):$(id -g)&quot; -v &quot;$PWD&quot;:/apps -w /apps rails:4.2.5 rails new rails_on_docker_for_dev --skip-bundle
</code></pre>
<p>The above command (btw, it's one line) will pull the image <a href="https://hub.docker.com/_/rails/"><strong>rails:4.2.5</strong></a> from Docker Hub and run a one-off container based on the image with the command <code>rails new rails_on_docker_for_dev --skip-bundle</code>. The result is that it creates a Rails 4.2.5 project called <code>rails_on_docker_for_dev</code> in your current directory. Take a look at the newly created project <code>rails_on_docker_for_dev</code>.</p>
<h5 id="step2dockerizetheapp">Step 2. Dockerize the app</h5>
<p>Create Dockerfile in the project</p>
<pre><code>FROM phusion/passenger-ruby22:0.9.18
MAINTAINER Cheng Long &lt;me@chengl.com&gt;

# Hack to get around the volume mount issue on Mac
# https://github.com/boot2docker/boot2docker/issues/581
# https://github.com/docker/docker/issues/7198#issuecomment-159736577
ARG LOCAL_USER_ID
RUN usermod -u ${LOCAL_USER_ID} app

ENV APP_NAME rails_on_docker_for_dev

# Enable Nginx and add config
RUN rm -f /etc/service/nginx/down
RUN rm /etc/nginx/sites-enabled/default
COPY $APP_NAME.conf /etc/nginx/sites-enabled/$APP_NAME.conf

# Create project root and change owner
ENV APP_PATH /home/app/$APP_NAME
RUN mkdir -p $APP_PATH
RUN chown -R app:app $APP_PATH

WORKDIR $APP_PATH

RUN apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Use baseimage-docker's init process.
CMD [&quot;/sbin/my_init&quot;]
</code></pre>
<p>This is a standard Dockerfile. It's based on <a href="https://hub.docker.com/r/phusion/passenger-ruby22">phusion/passenger-ruby22:0.9.18</a>, which includes <strong>Nginx 1.8.0</strong>, <strong>Passenger 5.0.22</strong> and <strong>Ruby 2.2</strong>. It basically has all the dependencies to run the container web. You probably notice that the image  doesn't include the Rails app. That's because we want to <a href="https://docs.docker.com/engine/userguide/containers/dockervolumes/">mount the Rails app as a volume</a> so that when we edit source code of the app we don't have to rebuild the image. This is much needed in development mode to shorten the feedback loop. But in production, you should include the app in the image. We will see how it's mounted in <code>docker-compose.yml</code></p>
<p>Two lines in the Dockerfile need a bit explanation</p>
<pre><code>ARG LOCAL_USER_ID
RUN usermod -u ${LOCAL_USER_ID} app
</code></pre>
<p>First, <a href="https://github.com/phusion/passenger-docker#app_user">Passenger recommends</a> running the app as user <code>app</code> because it's a good security practice. So user <code>app</code> needs <code>rw</code> access to the mounted volume.</p>
<p>Second, in order to let <code>app</code> have <code>rw</code> access and not to change the ownership of the files (because we want to edit the source code on local machine and the files are owned by the local user), we change <code>app</code>'s uid to be the same as the local user. This is <a href="https://github.com/docker/docker/issues/7198#issuecomment-159736577">a trick</a> to get around <a href="https://chengl.com/using-docker-for-rails-development/(https://github.com/boot2docker/boot2docker/issues/581)">the permission issue</a> of mounted volume on OS X.</p>
<p>Create <code>rails_on_docker_for_dev.conf</code></p>
<pre><code>server {
    listen 80;
    server_name 0.0.0.0;
    root /home/app/rails_on_docker_for_dev/public;

    passenger_enabled on;
    passenger_user app;
    passenger_ruby /usr/bin/ruby;
    passenger_app_env development;
}
</code></pre>
<p>Note <code>passenger_user app;</code>, the app is running as <code>app</code> not <code>root</code>.</p>
<p>Create docker-compose.yml</p>
<pre><code>version: '2'
services:
  web:
    build:
      context: .
      args:
        - LOCAL_USER_ID=${LOCAL_USER_ID}
    ports:
      - &quot;80:80&quot;
    volumes:
      - &quot;.:/home/app/rails_on_docker_for_dev&quot;
</code></pre>
<p>Note the <code>volumes</code> part. We mount the project directory in the local dev machine as a volume to <code>/home/app/rails_on_docker_for_dev</code> inside the container so that the container has the source code and Rails auto-reloading still works.</p>
<h5 id="step3buildandrun">Step 3. Build and Run</h5>
<p>Build the image</p>
<pre><code>LOCAL_USER_ID=$(id -u) docker-compose build
</code></pre>
<p>We need <code>LOCAL_USER_ID=$(id -u)</code> because <code>docker-compose.yml</code> expects an environment varialbe <code>LOCAL_USER_ID</code> so that it can change user <code>app</code>'s uid to the local user's uid.</p>
<p>Run the image</p>
<pre><code>docker-compose up
</code></pre>
<p>You will see some logs that Passenger prints out in the console. When it's ready, point your broswer to your docker machine ip. To find out your docker machine ip, <code>docker-machine ip default</code>, if you are using machine default.</p>
<p><img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-02-13-at-11-47-14-PM.png" alt="Using Docker for Rails Development"></p>
<p>You should see an error that Passenger complains about <code>bundle install</code> is not run yet. That's correct. The app doesn't have any gem yet.</p>
<p>First Bundle install</p>
<pre><code>docker-compose run --rm --user $(id -u):$(id -g) web bundle install --path=vendor/bundle
</code></pre>
<p>We want to install gems in <code>./vendor/bundle</code> because the gems will persist in <code>./vendor/bundle</code> regardless of the lifecyle of the container. When we update the Gemfile and do <code>bundle install</code> again, it will only install the newly added gems, not everything again.</p>
<p><code>--user $(id -u):$(id -g)</code> is to make sure that newly created files are owned by the local user. Without it, they will be owned by <code>root</code>.</p>
<p><code>--rm</code> is to remove the container after it's done.</p>
<p>Remove the running container</p>
<pre><code>docker rm -f $(docker ps -ql)
</code></pre>
<p>Run the image again</p>
<pre><code>docker-compose up
</code></pre>
<p>If you refresh your broswer, you should see a familiar welcome page!</p>
<h5 id="step4developusingdocker">Step 4. Develop using Docker</h5>
<p>Having only a welcome page is not very exciting. Let's add a <code>Post</code> model.</p>
<pre><code>docker-compose run --rm --user $(id -u):$(id -g) web bundle exec rails g scaffold post title:string body:text
</code></pre>
<p>DB migrate</p>
<pre><code>docker-compose run --rm --user &quot;$(id -u):$(id -g)&quot; web bundle exec rake db:migrate
</code></pre>
<p>Now you can CRUD posts. Everything works!</p>
<h3 id="buildandruncontainerdb">Build and Run Container DB</h3>
<h5 id="step5replacesqlite3withpg">Step 5. Replace sqlite3 with pg</h5>
<p>Replace sqlite3 with pg in Gemfile</p>
<pre><code>gem 'pg', '~&gt; 0.18.4'
</code></pre>
<p>bundle install</p>
<pre><code>docker-compose run --rm --user $(id -u):$(id -g) web bundle install --path=vendor/bundle
</code></pre>
<h5 id="step6updatewebimage">Step 6. Update Web Image</h5>
<p>Add Postgres dependency in Dockerfile</p>
<pre><code>RUN apt-get update &amp;&amp; apt-get install -qq -y libpq-dev --fix-missing --no-install-recommends
</code></pre>
<p>Update docker-compose.yml so that web and db are linked</p>
<pre><code>version: '2'
services:
  web:
    build:
      context: .
      args:
        - LOCAL_USER_ID=${LOCAL_USER_ID}
    ports:
      - &quot;80:80&quot;
    volumes:
      - &quot;.:/home/app/rails_on_docker_for_dev&quot;
    links:
      - db

  db:
    image: postgres:9.4.5
    ports:
      - '5432:5432'
</code></pre>
<p>Update database.yml to use Postgres</p>
<pre><code>development: &amp;default
  adapter: postgresql
  encoding: utf8
  database: myapp_dev
  pool: 5
  username: postgres
  host: db

test:
  &lt;&lt;: *default
  database: myapp_test
</code></pre>
<h5 id="step7rebuildimagesandrun">Step 7. Rebuild images and Run</h5>
<pre><code>LOCAL_USER_ID=$(id -u) docker-compose build
</code></pre>
<p>Run</p>
<pre><code>docker-compose up
</code></pre>
<p>You should see logs from both web and db printed in the console.</p>
<p>Of course, you should setup database first</p>
<pre><code>docker-compose run --rm --user &quot;$(id -u):$(id -g)&quot; web bundle exec rake db:setup
</code></pre>
<p>Everything works as before!</p>
<p>All source code is <a href="https://github.com/ChengLong/rails_on_docker_for_dev">here</a>.</p>
<h3 id="summary">Summary</h3>
<p>As you see, it's not that hard to use Docker for Rails development. Essentially, we just need <code>Dockerfile</code> and<code>docker-compose.yml</code> and link the containers properly. The development workflow with Docker is pretty much the same as without Docker. But the advantages that Docker brings to development are invaluable. It abstracts away the difference between local dev machines and let every developer in your team have a consistent development environment. Isn't that awesome?</p>
<p>Happy Dockering!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Let's Encrypt Nginx]]></title><description><![CDATA[How to setup Let's Encrypt on Nginx and setup auto renewal]]></description><link>https://chengl.com/lets-encrypt-nginx/</link><guid isPermaLink="false">5f01557e38333a60592f2642</guid><category><![CDATA[Nginx]]></category><category><![CDATA[Let's Encrypt]]></category><dc:creator><![CDATA[Cheng Long]]></dc:creator><pubDate>Sun, 31 Jan 2016 08:41:00 GMT</pubDate><media:content url="https://chengl.com/content/images/2017/07/Screen-Shot-2016-02-01-at-12-06-30-AM.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-02-01-at-12-06-30-AM.png" alt="Let's Encrypt Nginx"><p><em>Update [2017 Aug 5]: Certbot has been developed by EFF and others as an easy-to-use automatic client that fetches and deploys SSL/TLS certificates. I would recommend using <a href="https://certbot.eff.org/">it</a>.</em></p>
<h2 id="why">Why</h2>
<p>Since you are here, you probably know what <a href="https://letsencrypt.org">Let's Encrypt</a> is and why it exists. If not, below is an executive summary (copied from <a href="https://letsencrypt.org/howitworks/">here</a>):</p>
<blockquote>
<p>Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate can be. Let’s Encrypt automates away the pain and lets site operators turn on and manage HTTPS with simple commands.<br>
No validation emails, no complicated configuration editing, no expired certificates breaking your website. And of course, because Let’s Encrypt provides certificates for free, no need to arrange payment.</p>
</blockquote>
<p>At the time of writing, Nginx plugin is <a href="https://github.com/letsencrypt/letsencrypt#current-features">highly experimental, not included in letsencrypt-auto</a>. So I didn't use it. This post documents how to upgrade your site from <em>http</em> to <em>https</em> and setup auto renewal. If your site is already using <em>https</em> and just want to setup auto renewal, you can skip to <a href="#auto-renew">Auto Renew</a>.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Nginx</li>
<li>Debian-based distros, e.g. Ubuntu</li>
<li>Git</li>
<li>openssl</li>
</ul>
<h2 id="setupletsencrypt">Setup Let's Encrypt</h2>
<h4 id="1installletsencryptclient">1. Install Let's Encrypt client</h4>
<pre><code>git clone https://github.com/letsencrypt/letsencrypt ~/letsencrypt
cd ~/letsencrypt
</code></pre>
<h4 id="2freeport80and443">2. Free port 80 and 443</h4>
<p>With the <em>standalone</em> plugin, Let's Encrypt need to use port 80 and 443 to issue a cert for you. So you need to make sure that port 80 and 443 are <em>NOT</em> in use.<br>
Check if port 80 and 443 are used</p>
<pre><code>  netstat -na | grep ':80 .*LISTEN'
  netstat -na | grep ':443 .*LISTEN'
</code></pre>
<p>If nothing shows up, you are good to go. Otherwise, you need to temporarily stop whatever process is using them.</p>
<h4 id="3generatecertificate">3. Generate certificate</h4>
<pre><code>./letsencrypt-auto certonly --standalone
</code></pre>
<p>You will be prompted to enter email and domain. Just follow instructions.<br>
After it's done, you should see something like:</p>
<blockquote>
<p>Congratulations! Your certificate and chain have been saved at<br>
/etc/letsencrypt/live/your.domain.com/fullchain.pem. Your cert<br>
will expire on YYYY-MM-DD. To obtain a new version of the<br>
certificate in the future, simply run Let's Encrypt again.</p>
</blockquote>
<p>That means a cert has been issued to you. You can check it out in directory <code>/etc/letsencrypt/live/your.domain.com</code>. It should have <code>cert.pem</code>, <code>chain.pem</code>, <code>fullchain.pem</code> and <code>privkey.pem</code>.<br>
Now we need to config Nginx to use it.</p>
<h4 id="4confignginx">4. Config Nginx</h4>
<p>In the server block for https, you should have the following lines:</p>
<pre><code>  listen 443 ssl;
  server_name your.domain.com;
  ssl on;
  ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
  ssl_protocols TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_ciphers &quot;EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH&quot;;
  ssl_ecdh_curve secp384r1;
</code></pre>
<h5 id="anamestrongdha41usestrongdhgroup"><a name="strong-dh"></a>4.1 Use strong DH group</h5>
<p>For security reasons, I would strongly recommend using a strong DH group (<a href="https://weakdh.org/sysadmin.html">Here is why</a>).</p>
<p>To generate a strong DH group, 4096 bits</p>
<pre><code>openssl dhparam -out /path/to/dhparams.pem 4096
</code></pre>
<p>It will take some time. After it's done, add this line in the same https server block</p>
<pre><code>ssl_dhparam /path/to/dhparams.pem;
</code></pre>
<h5 id="anamehstsa42usehttpstricttransportsecurity"><a name="hsts"></a>4.2 Use HTTP Strict Transport Security</h5>
<p>I would also recommend to use <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">HTTP Strict Transport Security</a>. You just need to add this line in the https block</p>
<pre><code>add_header Strict-Transport-Security &quot;max-age=63072000; includeSubdomains; preload&quot;;
</code></pre>
<p>The complete Nginx config looks like</p>
<script src="https://gist.github.com/ChengLong/47a9ceff0480af0ada2e.js"></script>
<h4 id="5reloadnginx">5. Reload Nginx</h4>
<pre><code>sudo nginx -s reload
</code></pre>
<h4 id="6redirecthttptohttps">6. Redirect Http to Https</h4>
<p>This step is optional. But I think it's very useful. If you want to redirect your users who are still using <code>http</code> to <code>https</code> automatically, add the following http block</p>
<pre><code>  server {
    listen 80;
    server_name your.domain.com;
    return 301 https://$host$request_uri;
  }
</code></pre>
<p>If everything goes right, your site should be serving https now and http (if you did Step 6). Point your broser to <code>https://your.domain.com</code>. And you should see a green lock on the location bar and you can see the certicate information. It looks like this<br>
<img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-01-31-at-9-57-49-PM.png" alt="Let's Encrypt Nginx"></p>
<p>If you are a bit skeptical about the quality of your cert or your configuration, head over to <a href="https://www.ssllabs.com/ssltest/index.html">SSL Labs</a> to do a test. If you followed my recommendations to <a href="#strong-dh">use a strong DH group</a> and <a href="#hsts">HTTP Strict Transport Security</a>, your site should receive grade <strong>A+</strong>.<br>
<img src="https://chengl.com/content/images/2017/07/Screen-Shot-2016-01-31-at-11-47-48-PM.png" alt="Let's Encrypt Nginx"></p>
<h2 id="anameautorenewaautorenew"><a name="auto-renew"></a>Auto Renew</h2>
<p>By default, Let's Encrypt certs expire in 90 days. Right now, there is no built-in way to automatically renew it. But it's not hard to write a shell script and cronjob to do the job. You only need three steps</p>
<h4 id="1createwebrootconfigfile">1. Create webroot config file</h4>
<p>To renew, we need to use the <code>webroot</code> plugin,<br>
create <code>/usr/local/etc/le-renew-webroot.ini</code> with the following content</p>
<pre><code>rsa-key-size = 4096
email = youremail@example.com
domains = your.domain.com
authenticator = webroot
webroot-path = /usr/share/nginx/html
</code></pre>
<p>This file will be used a config file when running <code>webroot</code>.<br>
Btw, you should have root directive and /.well-known defined in the https server block. They are needed by <code>webroot</code> for <a href="https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md">ACME</a> chanllenge.</p>
<pre><code>root /usr/share/nginx/html;
location ~ /.well-known {
	allow all;
}
</code></pre>
<p>Then you can run this to get a new cert</p>
<pre><code>./letsencrypt-auto certonly -a webroot --agree-tos --renew-by-default --config /usr/local/etc/le-renew-webroot.ini
</code></pre>
<h4 id="2createrenewalscript">2. Create renewal script</h4>
<pre><code>sudo curl -L -o /usr/local/sbin/le-renew-webroot https://gist.githubusercontent.com/thisismitch/e1b603165523df66d5cc/raw/fbffbf358e96110d5566f13677d9bd5f4f65794c/le-renew-webroot
sudo chmod +x /usr/local/sbin/le-renew-webroot
</code></pre>
<p>You probably want to take a look at the script <code>/usr/local/sbin/le-renew-webroot</code> and customize it, especially <code>le_path</code> and <code>exp_limit</code>.</p>
<p>You can also run the script manually. If your cert is too new to renew, depending on your configuration in the script, you will see how many days are left. Otherwise, it will renew.</p>
<h4 id="3cronjob">3. Cronjob</h4>
<p>The last step is to create a cronjob to automatically run <code>/usr/local/sbin/le-renew-webroot</code>. So that you can sleep well and not to worry that your cert expires.</p>
<pre><code>sudo crontab -e
</code></pre>
<p>And add in</p>
<pre><code>30 2 * * 6 /usr/local/sbin/le-renew-webroot &gt;&gt; /var/log/le-renewal.log 2&gt;&amp;1
</code></pre>
<p>This cronjob will attempt to renew Let's Encrypt at 2:30 AM every Saturday.</p>
<h2 id="summary">Summary</h2>
<p>As you see, setting up Let's Encrypt and making it auto renew is not that hard. It used to be much more painful due to the cost of SSL cert and the efforts to create and maintain it, which gives many people (including myself) excuses not to use <em>https</em>. Now I can't find any excuse not to use <em>https</em>.</p>
<p>Let's Encrypt!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>