One problem I’ve been strugglinging with over the past few months is how to configure a secure way to set and get variables for my various environments. I’ve considered building a tool for this (one that you’ll probably hear about soon) but while deciding what the MVP should be I realized that I might be able to strap a few different tools together to get the result I’m looking for.
Vault is a…. security management framework built by hashicorp, and Consul is a distributed database / service discovery service.
From a high level, I need the ability to run
deploy $environment and have the environment deployed.
At the moment I have [a make script based system]() that can do this, but it’s limited to getting it’s inputs from environment variables.
Without changing the deploy script, how can I pipe in new variables? (hopefully) consul + vault + envconsul to the rescue!
OK, let’s get consul & vault setup and talking to each other then let’s get some variables set.
So first I’ll go ahead and create a folder with a docker-compose.yml config (great for prototyping networked services) which looks like this
Then I export the variables for vault and check it’s status
# using 8250 because in dev mode it auto creates a listener on 8250 apparently! $ export VAULT_ADDR=http://127.0.0.1:8250 $ vault status Sealed: false Key Shares: 1 Key Threshold: 1 Unseal Progress: 0 Version: Vault v0.6.1 Cluster Name: vault-cluster-5a34b707 Cluster ID: bbe6cfc4-42b1-69c1-88a0-ce2914ce4d0b High-Availability Enabled: true Mode: active Leader: http://127.0.0.1:8250
Great, looks like it’s working.
OK, now let’s get envconsul working
For testing, I’ll add a busybox service to my docker-compose file, and mount the envconsul.hcl file I’ll need to create inside of it. Since the normal busybox image doesn’t have ca-certs or openssl and I need to download envconsul, I’ll use a different image like so
# docker-compose.yml busybox: image: yikaus/alpine-bash command: /bin/bash depends_on: - consul - vault
$ docker-compose run busybox bash-4.3# apk update fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz v3.4.6-17-gbd473fa [http://dl-cdn.alpinelinux.org/alpine/v3.4/main] v3.4.6-18-g07184c8 [http://dl-cdn.alpinelinux.org/alpine/v3.4/community] OK: 5978 distinct packages available bash-4.3# apk add wget openssl ca-certificates (1/3) Installing ca-certificates (20160104-r4) (2/3) Installing openssl (1.0.2j-r0) (3/3) Installing wget (1.18-r0) Executing busybox-1.24.2-r9.trigger Executing ca-certificates-20160104-r4.trigger OK: 15 MiB in 19 packages bash-4.3# wget https://releases.hashicorp.com/envconsul/0.6.1/envconsul_0.6.1_linux_amd64.zip --2016-12-08 02:48:57-- https://releases.hashicorp.com/envconsul/0.6.1/envconsul_0.6.1_linux_amd64.zip Resolving releases.hashicorp.com... 126.96.36.199 Connecting to releases.hashicorp.com|188.8.131.52|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 3341174 (3.2M) [application/zip] Saving to: 'envconsul_0.6.1_linux_amd64.zip' envconsul_0.6.1_linux_amd64.zip 100%[===========================================================================>] 3.19M 13.6MB/s in 0.2s 2016-12-08 02:48:57 (13.6 MB/s) - 'envconsul_0.6.1_linux_amd64.zip' saved [3341174/3341174] bash-4.3# unzip envconsul_0.6.1_linux_amd64.zip Archive: envconsul_0.6.1_linux_amd64.zip inflating: envconsul
With that all setup I can now do this
bash-4.3# ./envconsul -consul consul:8500 -prefix cluster/production -once -sanitize -upcase env HOSTNAME=c8d1683c6c2a TERM=xterm PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ SHLVL=1 HOME=/root no_proxy=*.local, 169.254/16 _=./envconsul REDIS_= REDIS_ADDR=testing
Now of course, those redis keys at the bottom came from me adding them (I used the ui at consul:8500/ui)
But you can see how this could be useful with my makefile if I did
envconsul make $environment deploy
Now that I’ve shown you a toy example, I’m sure you’re curious as to how we could use this in a real production environment.
Well for my particular use case, my company has our infrastructure setup something like this
clusters: cluster_a: mesos_master_ip: some-value services: - logging (cluster wide) - redis (cluster wide) - postgres (cluster wide) environments: some-partner-staging: variables: - RAILS_ENV: staging - DEFAULT_HOST_NAME: partner-staging.mycomapny.com - DATABASE_URL: postgres://$(database)/some-databasename secrets: - JWT_SECRET: 1234abc # no really secret in staging but anyway some-partner-production: variables: - RAILS_ENV: production - DEFAULT_HOST_NAME: partner.mycomapny.com - DATABASE: postgres://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(URL_OF_PRIVATE_DATABASE)/some-databasename secrets: - JWT_SECRET: secret - DATABASE_USERNAME: secret - DATABASE_PASSWORD: secret services: PRIVATE_DATABASE: postgres:9.4