<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://viperandleaf.xyz/feed.xml" rel="self" type="application/atom+xml" /><link href="https://viperandleaf.xyz/" rel="alternate" type="text/html" /><updated>2023-04-01T15:02:52+00:00</updated><id>https://viperandleaf.xyz/feed.xml</id><title type="html">Max’s notebook</title><subtitle>A collection of sorts</subtitle><author><name>Max G</name></author><entry><title type="html">Some Thoughts on Terraform CI for Monorepos</title><link href="https://viperandleaf.xyz/terraform-monorepo-ci" rel="alternate" type="text/html" title="Some Thoughts on Terraform CI for Monorepos" /><published>2022-01-08T00:00:00+00:00</published><updated>2022-01-08T00:00:00+00:00</updated><id>https://viperandleaf.xyz/terraform-ci</id><content type="html" xml:base="https://viperandleaf.xyz/terraform-monorepo-ci">&lt;p&gt;Continuous integration and deployment for &lt;a href=&quot;https://www.hashicorp.com/blog/terraform-mono-repo-vs-multi-repo-the-great-debate&quot;&gt;terraform monorepos&lt;/a&gt; is not a solved problem. I’m not proposing to solve it, but this is a record of my thoughts and experiments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As an aside, CI for terraform stand-alone repos is, in fact, a very solved problem. See “Automate Terraform with GitHub Actions” in the &lt;a href=&quot;#references&quot;&gt;References&lt;/a&gt; section for a very approachable example.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-problem-space&quot;&gt;The Problem Space&lt;/h2&gt;
&lt;p&gt;We have a repo storing multiple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform&lt;/code&gt; configurations or stacks (databases, users, kubernetes clusters, etc), and we want to organize it in a way that supports continuous integration and deployment.&lt;/p&gt;

&lt;h3 id=&quot;constraints&quot;&gt;Constraints&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;We are using terraform open source&lt;/li&gt;
  &lt;li&gt;We are using &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions&quot;&gt;Github Actions&lt;/a&gt; as our CI tool&lt;/li&gt;
  &lt;li&gt;We need to be able to deploy changes to stacks independently&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;using-terraform&quot;&gt;Using Terraform&lt;/h3&gt;
&lt;p&gt;To do this using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform&lt;/code&gt;, we had two options:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;create one &lt;a href=&quot;https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions#workflows&quot;&gt;workflow&lt;/a&gt; for each stack, as each stack will have it’s own &lt;a href=&quot;https://www.terraform.io/language/state&quot;&gt;state&lt;/a&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;pro: fully isolated components and life-cycle management&lt;/li&gt;
      &lt;li&gt;con: workflows scale linearly with stacks&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;pick a level of abstraction and have all resources share a workflow, state&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;pro: workflows scale linearly with the level of abstraction (for example, environment)&lt;/li&gt;
      &lt;li&gt;con: large blast radius as all resources share state&lt;/li&gt;
      &lt;li&gt;con: harder to review changes as resource quantity grows&lt;/li&gt;
      &lt;li&gt;con: every resource requires a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; attribute for conditional creation (for example, create resource &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$FOO&lt;/code&gt; in staging but not prod)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;using-terragrunt&quot;&gt;Using Terragrunt&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://terragrunt.gruntwork.io/&quot;&gt;Terragrunt&lt;/a&gt; allows full access to all of the features of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform&lt;/code&gt; while helping to address some of these concerns: maintain one workflow per level-of-abstraction, using &lt;a href=&quot;https://terragrunt.gruntwork.io/docs/reference/cli-options/#run-all&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terragrunt run-all&lt;/code&gt;&lt;/a&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plan&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apply&lt;/code&gt; resources while &lt;a href=&quot;https://terragrunt.gruntwork.io/docs/features/keep-your-remote-state-configuration-dry/#filling-in-remote-state-settings-with-terragrunt&quot;&gt;dynamic backend generation&lt;/a&gt; ensures separate state for each module. This is the route we chose in the end.&lt;/p&gt;

&lt;h2 id=&quot;final-repo-structure&quot;&gt;Final repo structure&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;├── prod
│   ├── app1
│   │   ├── main.tf
│   │   └── terragrunt.hcl
│   ├── app2
│   │   ├── main.tf
│   │   └── terragrunt.hcl
│   ├── cache
│   │   ├── main.tf
│   │   └── terragrunt.hcl
│   ├── database
│   │   ├── main.tf
│   │   └── terragrunt.hcl
│   └── terragrunt.hcl
└── staging
    ├── app1
    │   ├── main.tf
    │   └── terragrunt.hcl
    ├── app2
    │   ├── main.tf
    │   └── terragrunt.hcl
    ├── cache
    │   ├── main.tf
    │   └── terragrunt.hcl
    ├── database
    │   ├── main.tf
    │   └── terragrunt.hcl
    └── terragrunt.hcl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;what-worked-well&quot;&gt;What Worked Well&lt;/h2&gt;
&lt;p&gt;Having a single point of entry makes it very easy to understand the changes getting rolled out to each environment, and we can (and do) include an environment-level &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plan&lt;/code&gt; for drift-detection during code review. Also, not needing to manage backend or &lt;a href=&quot;https://terragrunt.gruntwork.io/docs/reference/config-blocks-and-attributes/#generate&quot;&gt;provider configurations&lt;/a&gt; for each stack by hand is really nice.&lt;/p&gt;

&lt;h2 id=&quot;what-didnt-work-so-well&quot;&gt;What Didn’t Work so Well&lt;/h2&gt;
&lt;p&gt;There’s no consistent path for a stack to get from staging to production. I really like Kief Morris’ pipeline-per-stack model (read more about it in the “Using Pipelines to Manage Environments with Infrastructure as Code” article, linked in the &lt;a href=&quot;#references&quot;&gt;References&lt;/a&gt; section), but I found a few drawbacks for our use-case:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;more complex workflows are needed to manage multiple environments for each stack (and maybe the logic &lt;em&gt;should&lt;/em&gt; live there?)&lt;/li&gt;
  &lt;li&gt;as mentioned above, the code in the stack itself becomes harder to reason about: &lt;a href=&quot;https://www.terraform.io/language/meta-arguments/count&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt;&lt;/a&gt;s or other conditional attributes are needed for each resource and/or module as it moves from dev to prod&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;
&lt;p&gt;There are many other features that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terragrunt&lt;/code&gt; has, like &lt;a href=&quot;https://terragrunt.gruntwork.io/docs/reference/config-blocks-and-attributes/#dependency&quot;&gt;dependency blocks&lt;/a&gt;, which provide more in-depth configuration options (and remove the need for many &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; blocks) that I’m excited to explore. On the CI front, I’m eagerly awaiting updates on Hashicorp’s &lt;a href=&quot;https://www.terraform.io/language/modules/testing-experiment&quot;&gt;testing experiment&lt;/a&gt;, and whatever usability improvements we come across as we put more and more pressure on the current CI pattern.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/@kief/https-medium-com-kief-using-pipelines-to-manage-environments-with-infrastructure-as-code-b37285a1cbf5&quot;&gt;Kief Morris, “Using Pipelines to Manage Environments with Infrastructure as Code”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.hashicorp.com/tutorials/terraform/github-actions&quot;&gt;Hashicorp, “Automate Terraform with GitHub Actions”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://faun.pub/terraform-at-scale-modualized-hierachical-layout-cb5dbe5a368d&quot;&gt;Tianchen Wu, “Terraform at Scale — Modualized Hierachical Layout and Continuous Delivery of Infrastructure”&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://terragrunt.gruntwork.io/docs/features/keep-your-terragrunt-architecture-dry/#considerations-for-cicd-pipelines&quot;&gt;Gruntwork, “Considerations for CI/CD Pipelines”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Max G</name></author><summary type="html">Continuous integration and deployment for terraform monorepos is not a solved problem. I’m not proposing to solve it, but this is a record of my thoughts and experiments.</summary></entry><entry><title type="html">Boto Over Time</title><link href="https://viperandleaf.xyz/boto-over-time" rel="alternate" type="text/html" title="Boto Over Time" /><published>2020-07-05T00:00:00+00:00</published><updated>2020-07-05T00:00:00+00:00</updated><id>https://viperandleaf.xyz/boto-attacks</id><content type="html" xml:base="https://viperandleaf.xyz/boto-over-time">&lt;p&gt;If you’ve worked with AWS using python, then you’ve come across the &lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/latest/index.html&quot;&gt;AWS SDK&lt;/a&gt;. The current generation is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boto3&lt;/code&gt;, the previous version is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boto&lt;/code&gt;, and you can use both side-by-side in the same code-base, and after a few incidents due to this, I will never do this thing.&lt;/p&gt;

&lt;h3 id=&quot;how-many-ways-can-you-grant-an-app-running-on-an-ec2-instance-access-to-aws-resources&quot;&gt;How many ways can you grant an app running on an EC2 instance access to AWS resources?&lt;/h3&gt;
&lt;p&gt;Here are a few:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;dedicated IAM credentials in an app-specific config file&lt;/li&gt;
  &lt;li&gt;dedicated IAM credentials in the &lt;a href=&quot;http://boto.cloudhackers.com/en/latest/boto_config_tut.html#details&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.boto&lt;/code&gt; file&lt;/a&gt; or in the &lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.aws/&lt;/code&gt; directory&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html&quot;&gt;instance profiles&lt;/a&gt; or &lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html&quot;&gt;roles&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Guess what I inherited? (hint: it was all of them.)&lt;/p&gt;

&lt;h3 id=&quot;bonus-round-configuration-management&quot;&gt;Bonus round: Configuration management&lt;/h3&gt;
&lt;p&gt;This application uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ansible&lt;/code&gt; for config management, which suffers from the &lt;em&gt;exact same issue&lt;/em&gt; since it uses the same libraries, sometimes in parallel too (&lt;a href=&quot;https://docs.ansible.com/ansible/latest/modules/aws_s3_module.html#requirements&quot;&gt;for an example, see the s3 module&lt;/a&gt;), so debugging and deploying reliable fixes was harder still.&lt;/p&gt;

&lt;h2 id=&quot;wat&quot;&gt;…WAT&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;The application originally got it’s access by reading dedicated creds from the config file. While this isn’t ideal (roles with short-lived credentials ftw), I’ve seen it a lot.&lt;/li&gt;
  &lt;li&gt;Some crons and app functionality needed access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$UNIQUE_SERVICE_SET_1&lt;/code&gt; and didn’t read from the config file, so it read from that user’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.boto&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.aws&lt;/code&gt; files&lt;/li&gt;
  &lt;li&gt;When a new instance was provisioned, scripts run by &lt;a href=&quot;https://cloudinit.readthedocs.io/en/latest/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloud_init&lt;/code&gt;&lt;/a&gt; needed access to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$UNIQUE_SERVICE_SET_2&lt;/code&gt;, so it read from the environment and got access through the instance profile and role&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ansible&lt;/code&gt;… well, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ansible&lt;/code&gt; DGAF #YOLOSWAG &lt;a href=&quot;https://docs.ansible.com/ansible/latest/modules/aws_s3_module.html#notes&quot;&gt;From the aws_s3 module docs:&lt;/a&gt;
    &lt;blockquote&gt;
      &lt;p&gt;Ansible uses the boto configuration file (typically ~/.boto) if no credentials are provided. See https://boto.readthedocs.io/en/latest/boto_config_tut.html&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/937dc959-8701-4bf0-9524-2148d092d618/db2st8c-8c23768e-584b-435a-b1da-3d506cf28153.gif?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3sicGF0aCI6IlwvZlwvOTM3ZGM5NTktODcwMS00YmYwLTk1MjQtMjE0OGQwOTJkNjE4XC9kYjJzdDhjLThjMjM3NjhlLTU4NGItNDM1YS1iMWRhLTNkNTA2Y2YyODE1My5naWYifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6ZmlsZS5kb3dubG9hZCJdfQ.oBG9npaxU0-X-2cZPT8-axaeOaK8wup3asYWaSOCWwY&quot; alt=&quot;sob&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;and-then-we-find-it-and-kill-it&quot;&gt;And then? We find it and kill it&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Spelunk the App the first: find all the code that loads the IAM creds, and identify the services and calls made&lt;/li&gt;
  &lt;li&gt;Spelunk the App again: compare these calls against the IAM policy, and patch to match the code when needed&lt;/li&gt;
  &lt;li&gt;Remove the creds from the config file and cross your fingers&lt;/li&gt;
  &lt;li&gt;Test: did it work?&lt;/li&gt;
  &lt;li&gt;Ship it if so, fix it if not&lt;/li&gt;
  &lt;li&gt;Remove the user creds&lt;/li&gt;
  &lt;li&gt;Spelunk the config management: find the calls and services, remove unused, patch the policy where required&lt;/li&gt;
  &lt;li&gt;Remove the non-instance profile creds&lt;/li&gt;
  &lt;li&gt;Test it again: how about now?&lt;/li&gt;
  &lt;li&gt;How about the crons? You did check the crons, didn’t you? (Narrator: they &lt;em&gt;did&lt;/em&gt; check the crons)&lt;/li&gt;
  &lt;li&gt;Ship it if so, fix it if not&lt;/li&gt;
  &lt;li&gt;Find surprise edge cases and cross-service library usage by watching breakage in prod&lt;/li&gt;
  &lt;li&gt;Cry while fixing and testing and shipping&lt;/li&gt;
  &lt;li&gt;Express anger at vague error messages&lt;/li&gt;
  &lt;li&gt;Express gratitude for fast deployments&lt;/li&gt;
  &lt;li&gt;Go to bed. It was a very long week&lt;/li&gt;
&lt;/ol&gt;</content><author><name>Max G</name></author><summary type="html">If you’ve worked with AWS using python, then you’ve come across the AWS SDK. The current generation is boto3, the previous version is boto, and you can use both side-by-side in the same code-base, and after a few incidents due to this, I will never do this thing.</summary></entry><entry><title type="html">Ansible Notes</title><link href="https://viperandleaf.xyz/ansible-notes" rel="alternate" type="text/html" title="Ansible Notes" /><published>2020-05-03T00:00:00+00:00</published><updated>2020-05-03T00:00:00+00:00</updated><id>https://viperandleaf.xyz/ansible-notes</id><content type="html" xml:base="https://viperandleaf.xyz/ansible-notes">&lt;p&gt;A quick and dirty reference for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ansible&lt;/code&gt; things I find useful.&lt;/p&gt;

&lt;h2 id=&quot;define-inventory&quot;&gt;define inventory&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜  ~ cat server_inventory
host1.example.com
host2.example.com

[server_group]
10.10.10.101
10.10.10.102    ansible_user=myuser ansible_ssh_private_key_file=my_key
10.10.10.103    ansible_user=not-me ansible_ssh_private_key_file=yo_key
10.10.10.104
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;remote-ssh-ftw&quot;&gt;remote ssh ftw&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# run `systemctl restart yo-service` as root across all the servers in server_group, 
defined in server_inventory, and do it in groups of 10 at a time
ansible server_group -i server_inventory -b -m shell -a \
    &quot;systemctl restart yo-service&quot; -f10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;remote-script-execution&quot;&gt;remote script execution&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# run a local script across a bunch of servers
ansible server_group -i server_inventory -b -m script -a my_script.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;run-an-ad-hoc-command-across-specific-servers&quot;&gt;run an ad-hoc command across specific servers&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# run `systemctl stop yo-other-service` on these specific hosts
ansible -b -m shell -a &quot;systemctl stop yo-other-service&quot; \
    10.10.10.10:\
    10.10.10.11:\
    10.10.10.12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;playbook-structure&quot;&gt;playbook structure&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜  ~ cat playbook.yml
---
- hosts: &quot;{{ hosts }}&quot; # read hosts at runtime
  become: yes # with sudo
  gather_facts: false # don't need server metadata, just go

  tasks:
    - name: install nginx
      apt: name=nginx state=latest
    - name: restart nginx
      service: name=nginx state=restarted
      register: nginx_status
    - name: debug nginx status
      debug:
        var: nginx_status
    - name: debug message
      debug:
        msg: &quot;output of nginx restart: &quot;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;run-the-playbook&quot;&gt;run the playbook&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# run the playbook `playbook.yml` against localhost
ansible-playbook playbook.yml -e hosts=localhost
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;role-structure&quot;&gt;role structure&lt;/h2&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;➜  ansible-role tree
.
├── files # static files
│   └── pub_key
├── meta # dependencies on other roles
│   └── main.yml
├── tasks
│   ├── main.yml # top level tasks
│   └── nginx.yml # other tasks, needs to be included in `main.yml`
├── templates # templates ala jinja2
│   └── nginx.conf.j2 
└── vars
    ├── main.yml
    └── secrets.yml # needs to be included in `tasks/main.yml`

5 directories, 6 files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Max G</name></author><summary type="html">A quick and dirty reference for ansible things I find useful.</summary></entry><entry><title type="html">Bash Notes</title><link href="https://viperandleaf.xyz/bash-notes" rel="alternate" type="text/html" title="Bash Notes" /><published>2019-07-23T00:00:00+00:00</published><updated>2019-07-23T00:00:00+00:00</updated><id>https://viperandleaf.xyz/bash-notes</id><content type="html" xml:base="https://viperandleaf.xyz/bash-notes">&lt;p&gt;A quick and dirty reference for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; things I find useful.&lt;/p&gt;

&lt;h2 id=&quot;loops&quot;&gt;Loops&lt;/h2&gt;
&lt;h3 id=&quot;one-liner&quot;&gt;one-liner:&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for i in $(ls); do cat $i; done
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;in-a-shell-script&quot;&gt;in a shell script:&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash
for i in $(ls)
do
    cat $i
done
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;read-a-csv-as-input&quot;&gt;Read a csv as input&lt;/h2&gt;
&lt;h3 id=&quot;one-liner-1&quot;&gt;one-liner:&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;while IFS=&quot;,&quot; read col1 col2 col3; do echo $col3 | md5sum; done &amp;lt; my.csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;in-a-shell-script-1&quot;&gt;in a shell script:&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash
## usage: ./script.sh my.csv

CSV_FILE=$1

while IFS=, read col1 col2 col3
do 
    echo $col3 | md5sum
done &amp;lt; $CSV_FILE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;kill-all-the-pids&quot;&gt;kill all the pids&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for pid in $(ps aux | grep [m]yprocess | awk '{ print $2} '); do kill -9 $pid; done
# explanation of the bracket syntax: https://askubuntu.com/a/153430
# The square bracket expression is part of ... grep's character class pattern matching.
# omission mine
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;compression&quot;&gt;Compression&lt;/h2&gt;
&lt;h3 id=&quot;ignore-dir-tree-just-compress-the-files&quot;&gt;ignore dir tree, just compress the files&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# one file from the dir, ignore the tree
tar czfv archive.tgz -C /path/to/dir/ file_in_dir_to_archive

# the whole dir, ignore the tree
tar czfv archive.tgz -C /path/to/dir .

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;extract-the-files-to-dir&quot;&gt;extract the files to dir&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# tar Xtract Zip File
tar xzfv archive.tgz -C files/should/live/here
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;unzip-to-dir&quot;&gt;unzip to dir&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;unzip archive.zip -d files/should/live/here
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;misc&quot;&gt;misc&lt;/h2&gt;
&lt;h3 id=&quot;listen-on-port-with-netcat&quot;&gt;listen on $PORT with netcat&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nc -lvnp 9000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;serve-pwd-with-python&quot;&gt;serve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pwd&lt;/code&gt; with python&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python -m SimpleHTTPServer 9000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;move-stuff-when-you-only-have-a-network-connection&quot;&gt;move stuff when you only have a network connection&lt;/h3&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cat my_bin | base64 | nc -v my.otherhost.com:9000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Max G</name></author><summary type="html">A quick and dirty reference for bash things I find useful.</summary></entry><entry><title type="html">When Scaling Out Isn’t an Option</title><link href="https://viperandleaf.xyz/when-scaling-out-isnt-an-option" rel="alternate" type="text/html" title="When Scaling Out Isn’t an Option" /><published>2019-06-09T00:00:00+00:00</published><updated>2019-06-09T00:00:00+00:00</updated><id>https://viperandleaf.xyz/loadbalancers-part-2</id><content type="html" xml:base="https://viperandleaf.xyz/when-scaling-out-isnt-an-option">&lt;p&gt;This is a follow up of sorts to &lt;a href=&quot;https://viperandleaf.xyz/dont-aws-elb-all-the-things&quot;&gt;this post&lt;/a&gt;, where I wrote about load balancing, but I’m going to focus on configuring the application itself.&lt;/p&gt;

&lt;h2 id=&quot;first-as-transport-then-as-logic&quot;&gt;First as Transport, Then as Logic&lt;/h2&gt;
&lt;p&gt;Everything I discussed previously made the assumption that the application was horizontally scalable: we could successfully run many instances of the application/business logic component, and all would share the same data store. It turns out this was not true.&lt;/p&gt;

&lt;p&gt;The application wasn’t actually capable of scaling horizontally, in spite of our tests and the assurances of the vendor/shepherd of the open source project. We learned when our three-node group became a one-node group, in Production. Like you do. Since running multiple nodes in parallel was no longer an option, we needed to figure out a way to deploy and manage what I’ll call a stateful pair: an application server using a unique configuration to access a dedicated database.&lt;/p&gt;

&lt;h2 id=&quot;chef-to-the-rescue&quot;&gt;Chef to the Rescue(!)&lt;/h2&gt;
&lt;p&gt;As mentioned before, my employer is a big user (and I am a huge fan of) &lt;a href=&quot;https://learn.chef.io/&quot;&gt;Chef&lt;/a&gt;. When building out the cookbook for the application, our targets were functionally identical: all of the nodes would be sharing the same configuration, so changes should be rolled out to all of the nodes. One cookbook reading the Chef environment for its attributes deployed to all the nodes–pretty clean and understandable, but this pattern only works when there’s one configuration per environment. Our use case had changed, and our cookbooks must as well.&lt;/p&gt;

&lt;p&gt;I ended up re-writing the cookbook as a &lt;a href=&quot;https://docs.chef.io/custom_resources.html&quot;&gt;custom resource&lt;/a&gt;, which was thankfully straightforward since I’d already written the install and configuration logic. The biggest additions to the custom resource were adding a version specification for installation, and getting configuration values from variables instead of attributes.&lt;/p&gt;

&lt;p&gt;Once completed, I wrote one cookbook using the new resource, provisioned a database, and updated the environment attributes for each node.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# using custom resource looks like this:

maxs_app 'node one' do
  my_database node['node_one']['db_name'].to_s
  my_user node['node_one']['db_user'].to_s
  my_password node['node_one']['db_password'].to_s
  my_heap node['node_one']['heap'].to_s
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;in-the-end&quot;&gt;In The End&lt;/h2&gt;
&lt;p&gt;We have a unified configuration and installation for this application, both in a (sadly non-functional) clustered configuration, and in a stateful-pair, my broken three-node cluster in Prod is now a functional five un-clustered node group, and my configurations can still be versioned, and deployments can even be &lt;a href=&quot;https://martinfowler.com/bliki/CanaryRelease.html&quot;&gt;canary’ed!&lt;/a&gt; I’m still not thrilled that the clustering was broken, but it’s a much lower priority now that we’re running with more capacity and safe(er).&lt;/p&gt;</content><author><name>Max G</name></author><summary type="html">This is a follow up of sorts to this post, where I wrote about load balancing, but I’m going to focus on configuring the application itself.</summary></entry><entry><title type="html">CIS Benchmarks and Packer</title><link href="https://viperandleaf.xyz/cis-benchmarks-and-packer" rel="alternate" type="text/html" title="CIS Benchmarks and Packer" /><published>2018-08-27T00:00:00+00:00</published><updated>2018-08-27T00:00:00+00:00</updated><id>https://viperandleaf.xyz/cis</id><content type="html" xml:base="https://viperandleaf.xyz/cis-benchmarks-and-packer">&lt;p&gt;We wanted to start using the security benchmarks provided by the &lt;a href=&quot;https://www.cisecurity.org/cis-benchmarks/&quot;&gt;Center for Internet Security&lt;/a&gt; around the same timeas needing to build an updated version of our &lt;a href=&quot;https://searchservervirtualization.techtarget.com/definition/golden-image&quot;&gt;Golden Image&lt;/a&gt;, so I thought it’d be a good time to kill two birds with one stone and put them together as one project.&lt;/p&gt;

&lt;p&gt;The golden image had one particularly challenging and time consuming requirement: an encrypted root volume.&lt;/p&gt;

&lt;p&gt;At the time, it was all done manually:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;create temp_instance using a base image&lt;/li&gt;
  &lt;li&gt;stop temp_instance&lt;/li&gt;
  &lt;li&gt;create temp_ami from temp_instance&lt;/li&gt;
  &lt;li&gt;copy temp_ami and tell aws to encrypt the copy, creating encrypted_ami&lt;/li&gt;
  &lt;li&gt;terminate temp_instance and associated resources&lt;/li&gt;
  &lt;li&gt;deregister temp_ami&lt;/li&gt;
  &lt;li&gt;here is encrypted_ami&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wrote a script in python to accomplish this and have a record of the process. But wait a sec, isn’t this what &lt;a href=&quot;https://packer.io/intro/index.html&quot;&gt;Packer&lt;/a&gt; does?&lt;/p&gt;

&lt;p&gt;At the time, the answer was “no.” When I started this project, I was working with Packer 1.2.X, possibly 1.1.X (unfortunately, I don’t know the specific version), which did not produce an encrypted AMI even when explicitly defining it in the template. Creating encrypted AMIs (not ec2 instances) out of the box also wasn’t suported by AWS at the time, though this may have changed.&lt;/p&gt;

&lt;p&gt;Once we had the encrypted AMI, we started working on the CIS benchmarks using Packer and Chef. Thankfully, &lt;a href=&quot;https://www.packer.io/docs/provisioners/chef-solo.html&quot;&gt;Packer supports Chef&lt;/a&gt;, so we could leverage all of our existing work and focus on the items found by scanning our instances using &lt;a href=&quot;https://docs.chef.io/chef_compliance.html&quot;&gt;Chef Compliance&lt;/a&gt;, and add them to the Chef cookbooks used by Packer.&lt;/p&gt;

&lt;p&gt;One issue we ran into was that occasionally cookbook dependencies weren’t found, breaking the Packer build, even after a successful &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;berks install&lt;/code&gt;. My then-coworker (Thanks again, Andrew!) found a workaround for this: remove the version number from the cookbook in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;berks&lt;/code&gt; directory, changing the missing directory from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cookbook_0.1.0&lt;/code&gt; to  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cookbook&lt;/code&gt;, which let Packer find it at run time.&lt;/p&gt;

&lt;p&gt;For CentOS, the process was pretty straight forward. Find the open item from Chef Compliance, research the fix, add a recipe introducing the fix, or document the exception and associated reasoning, rinse, repeat. For Ubuntu, not so much. We were working of a 16.04 AMI that ran &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt update&lt;/code&gt; during boot (which I believe is standard practice for all AMIs from Canonical). This presents an issue for installing the chef client: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt&lt;/code&gt; is already running as part of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-daily.service&lt;/code&gt;, so we get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Could not get lock /var/lib/apt/lists/lock&lt;/code&gt; , stopping chef from installing, breaking the build. &lt;a href=&quot;https://github.com/chef/bento/issues/609&quot;&gt;This issue&lt;/a&gt; &lt;a href=&quot;https://github.com/geerlingguy/packer-ubuntu-1604/issues/7&quot;&gt;appears to be&lt;/a&gt; &lt;a href=&quot;http://lists.opscode.com/sympa/arc/chef/2015-08/msg00108.html&quot;&gt;well-documented&lt;/a&gt;. The solution? &lt;a href=&quot;https://github.com/hashicorp/packer/issues/2639#issue-102050238&quot;&gt;Many sleeps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We dealt with it using additional provisioners before running Chef:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &quot;provisioners&quot;: [
    {
      &quot;type&quot;: &quot;shell&quot;,
      &quot;inline&quot;: [
        &quot;while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for cloud-init...'; sleep 1; done&quot;
      ]
    },
    {
      &quot;type&quot;: &quot;shell&quot;,
      &quot;script&quot;: &quot;stop_updates.sh&quot;
    },
    {
        &quot;type&quot;: &quot;chef-solo&quot;,
    # etc, etc, etc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stop_updates.sh&lt;/code&gt; itself:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash

date
echo &quot;stopping apt-daily.service&quot;
sudo systemctl stop apt-daily.service
echo &quot;stopped service&quot;
sudo systemctl kill --kill-who=all apt-daily.service
echo &quot;services should be killed&quot;

echo &quot;sleeping for 60 seconds&quot;
sleep 60

echo &quot;wait until apt run has completed...&quot;
while [ `ps aux | grep -E '[a]pt|[d]pkg|[l]ock_is_held' | wc -l` -ne 0 ]; do
    echo &quot;apt, dpkg, or lock process still found&quot;
    sleep 5;
done
echo &quot;there should be no more apt processes by now&quot;
date

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Kind of ugly all in all, but that’s how we did it.&lt;/p&gt;

&lt;p&gt;Eventually, Packer updated and supported encrypted AMIs (hooray!), which allowed us to retire the python script, and move forward using Packer and Chef.&lt;/p&gt;

&lt;h4 id=&quot;useful-snippits&quot;&gt;Useful Snippits&lt;/h4&gt;
&lt;p&gt;For the packer file itself:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
  &quot;provisioners&quot;: [
    {
        &quot;type&quot;: &quot;chef-solo&quot;,
        &quot;cookbook_paths&quot;: [
            &quot;{{user `cookbook_path`}}&quot;,
            &quot;{{user `berks_path`}}&quot;
        ],
        &quot;run_list&quot; : [&quot;recipe[your_hardening_cookbook]&quot;],
    }]
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the config file used by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;packer&lt;/code&gt;, since we had to support different accounts:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    &quot;berks_path&quot; : &quot;&quot;,
    &quot;cookbook_path&quot;: &quot;&quot;,
    &quot;profile&quot;: &quot;&quot;,
    &quot;region&quot;: &quot;&quot;,
    &quot;source_ami&quot;: &quot;&quot;,
    &quot;target_ami_regions&quot;: &quot;&quot;,
    &quot;subnet_id&quot; : &quot;&quot;,
    &quot;vpc_id&quot; : &quot;&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;other-resources&quot;&gt;Other Resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/chef-boneyard/cis-el7-l1-hardening&quot;&gt;Chef cookbook for CIS level 1 (now deprecated)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dev-sec/chef-os-hardening&quot;&gt;Dev-sec.io Chef cookbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Max G</name></author><summary type="html">We wanted to start using the security benchmarks provided by the Center for Internet Security around the same timeas needing to build an updated version of our Golden Image, so I thought it’d be a good time to kill two birds with one stone and put them together as one project.</summary></entry><entry><title type="html">TIL you cannot AWS-ELB All the Things</title><link href="https://viperandleaf.xyz/dont-aws-elb-all-the-things" rel="alternate" type="text/html" title="TIL you cannot AWS-ELB All the Things" /><published>2018-08-21T00:00:00+00:00</published><updated>2018-08-21T00:00:00+00:00</updated><id>https://viperandleaf.xyz/loadbalancers</id><content type="html" xml:base="https://viperandleaf.xyz/dont-aws-elb-all-the-things">&lt;p&gt;&lt;em&gt;08.14.2019 Update: After many conversations with AWS Support, the limit of 50 listeners is now a soft limit!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A recent project of mine was to take one of the core applications at my job from hand-rolled snowflake servers to documented and repeatable infrastructure. This is meant to be a record of how I built it, what problems I ran into, and how I did, or did not, solve them, in hopes of helping someone in the future–or possibly myself the next time around.&lt;/p&gt;

&lt;p&gt;So here we go.&lt;/p&gt;

&lt;h2 id=&quot;what-is-it&quot;&gt;WHAT IS IT?&lt;/h2&gt;
&lt;p&gt;The application in question processes and routes messages sent over via TCP streams, as VPN tunneling is the preferred method of data transport (the application can receive messages via HTTP, but only a few customers deliver their data this way).&lt;/p&gt;

&lt;h2 id=&quot;why-was-it-hard&quot;&gt;WHY WAS IT HARD?&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;AWS Load Balancer limitations&lt;/li&gt;
  &lt;li&gt;Replacing the load balancer&lt;/li&gt;
  &lt;li&gt;Fail over&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;howd-you-do-it&quot;&gt;HOW’D YOU DO IT?&lt;/h2&gt;

&lt;h3 id=&quot;step-one-get-a-clean-and-reliable-installation-of-the-application&quot;&gt;Step One: Get a clean and reliable installation of the application&lt;/h3&gt;
&lt;p&gt;Nothing crazy, CloudFormation for infrastructure provisioning, Chef for configuration management, and Artifactory for artifact storage. Small highlight: the vendor doesn’t support running this application as a non-privileged user, and getting to the point where that was made clear took a very, very long time.&lt;/p&gt;

&lt;h3 id=&quot;step-two-reliably-provision-the-database&quot;&gt;Step Two: reliably provision the database&lt;/h3&gt;
&lt;p&gt;Manual schema and user creation for the win. As far as I know, there isn’t a way to include database provisioning as a part of CloudFormation–once the RDS instance was created, we had to connect to the db and create the relevant schema and users. There is a possibility of addressing this by having the database master user/password used by the application, but I chose against this in the name of separation of concern.&lt;/p&gt;

&lt;h3 id=&quot;step-two-and-a-half-provide-new-db-details-to-chef&quot;&gt;Step Two-and-a-half: Provide new db details to Chef&lt;/h3&gt;
&lt;p&gt;The urls and passwords are created at runtime, so they can’t be passed to Chef in advance to allow for a more ephemeral approach.&lt;/p&gt;

&lt;h3 id=&quot;step-three-load-balancers&quot;&gt;Step Three: Load balancers&lt;/h3&gt;
&lt;p&gt;Shouldn’t that be part of step two? Aren’t you using AWS, so can’t you just use one of the Elastic Load Balancers? No. No I cannot. Even though that was the design provided by the vendor, still, I cannot. 
This is where I hit the first major issue: AWS load balancer limitations. The application requires port-driven routing: client one on port 9001, client two on port 9002, client two sending data type two on port 9003, etc, etc, etc. The AWS load balancers max out at 50 port-bound listeners, effectively supporting less than 50 clients. I have defeated the purpose of a load balancer when I go down the path of having multiple load balancers for the same application, each with their own configurations. 
As a replacement, I chose HAProxy, which supports load balancing TCP streams and is simple to configure (!).&lt;/p&gt;

&lt;p&gt;After much research and fighting with embedded ruby and help from a very, very patient former coworker (Thanks Andrew!) I was able to use Chef to build a config for HAProxy that let me store client-specific configurations and application nodes in JSON:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;node['maxs_haproxy']['clients'] = [
  {
    &quot;client_name&quot;: &quot;client_one&quot;,
    &quot;client_port&quot;: 9001
  } 
]
node['maxs_haproxy']['app_nodes'] = [
  {
  &quot;app_node_name&quot;: &quot;node_one&quot;,
  &quot;app_node_ip&quot;: &quot;192.168.0.1&quot;
  }
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Hooray! I have a load balancer! It’s easy to manage the config! I know what’s what! This is great! I’m going home!
I did not go home.&lt;/p&gt;

&lt;h3 id=&quot;step-three-and-a-half-provide-new-load-balancer-and-application-instance-details-to-chef&quot;&gt;Step Three-and-a-half: Provide new load balancer and application instance details to Chef&lt;/h3&gt;
&lt;p&gt;Ips are created at runtime as well, can’t be passed to Chef in advance.&lt;/p&gt;

&lt;h3 id=&quot;step-four-how-do-i-fail-over&quot;&gt;Step Four: How do I Fail Over?&lt;/h3&gt;
&lt;p&gt;I’m using a software-defined load balancer instead of one provided by my IaaS provider, and I don’t want a single point of failure. After some reasearch, I found that using keepalived for fail over with HAProxy is a popular pattern, but there were two issues I kept coming across while trying to set this up:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;managing master/backup state (the nodes have to be self-aware) and IP addresses (the nodes have to be other-aware) for configuration at infrastructure provisioning time&lt;/li&gt;
  &lt;li&gt;understanding how fail over and recovery work in keepalived&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Managing state – IPs change when you bring up new nodes (I didn’t want to hard-code the IPs in CloudFormation), so how do you manage dynamic ip addresses that directly impact application configuration? How do I know what the IP of my master AND backup node is at provisioning time, so this info can be passed to each node and keepalived can be configured correctly?&lt;/p&gt;

&lt;p&gt;I don’t think I solved it the best way, but here’s what I did: during infrastructure provisioning, I assigned master/backup state to the nodes via Chef roles, and added the IPs as attributes to the Chef server, allowing the nodes to properly configure keepalived.&lt;/p&gt;

&lt;p&gt;Failing over – keepalived can use VRRP as a heartbeat between nodes, checking to make sure that the master node is up and running. If the master isn’t available, or declares a failed status check, the backup will designate itself the master node, and run a script containing whatever application logic is required.&lt;/p&gt;

&lt;p&gt;There are lots of tutorials and examples of keepalived configuration, but one thing that I kept missing (or that wasn’t in the guides I found), was the link between the health check and priority.&lt;/p&gt;

&lt;p&gt;The initial state of a node is master or backup, and each is given a priority value, commonly 150 for the backup, and 151 for the master.&lt;/p&gt;

&lt;p&gt;I didn’t realize that the health check should be weighted as well, allowing for a failed check to change the priority value of a node, along with changing the master-backup state.&lt;/p&gt;

&lt;p&gt;This kept me stuck in a state where I could not recover to my master node, even when it became healthy again.&lt;/p&gt;

&lt;h3 id=&quot;how-it-looks-at-the-end&quot;&gt;How it looks at the end&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/TCP_Stream.png&quot; alt=&quot;Architecture diagram&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;room-for-improvement&quot;&gt;Room for improvement&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Service discovery to handle master-backup state assignment and transfer&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.rapid7.com/2014/12/03/keepalived-and-haproxy-in-aws-an-exploratory-guide/&quot;&gt;Rapid7 overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://e-mc2.net/keepalived-documentation-nightmare&quot;&gt;Keepalived is not very well documented&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://codepen.io/tsabat/post/assign-a-floating-secondary-ip-address-in-aws-vpc&quot;&gt;AWS CLI to move the IP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Max G</name></author><summary type="html">08.14.2019 Update: After many conversations with AWS Support, the limit of 50 listeners is now a soft limit!</summary></entry></feed>