Live demo details
Create a brand new Kubernetes cluster in AWS’ Elastic Kubernetes Service
Creates Virtual Private Cloud
Creates Auto Scaling Group for worker nodes
Demonstrate some of the best practices for Kubernetes configuration management
From zero to ready-to-go cluster all in Ansible
About Kubernetes
Kubernetes is a platform for managing, scaling and deploying applications running on containers across distributed networks
Kubernetes continually runs a reconciliation loop to ensure the cluster is in the desired state and corrects it if possible
Kubernetes: a common language
Kubernetes allows the specification of common characteristics of applications and services:
an application’s deployable and its associated configuration (
Pod)how many replicas should run, and where; how should updates be handled (
Deployment/DaemonSet)what endpoints should be exposed to the applications (
Service)what traffic should go to the
Service(Ingress)
Kubernetes: a common language
ConfigMap—one or more configuration items in key-value form. Useful for setting environment variables or specifying the entire contents of one or more files for aPodSecret—similar toConfigMapbut better protected from casual view
Kubernetes resource definitions
Resource definitions are in YAML form
apiVersion: v1kind: ConfigMapmetadata:name: my-config-mapnamespace: my-namespacedata:hello: worldTypically managed by the
kubectlcommand line toolkubectl apply -f
resource.yml
Anti-pattern: using kubectl
in playbooks
in playbooks
kubectlis awesomeBut all the usual caveats to running commands apply†
You have to do a template/kubectl/delete dance
Are there reasons to use kubectl?
kubectldoes validation of resource definitions against their specificationkubectlcan append hashes toConfigMaps andSecrets to make them immutablead-hoc tasks:
kubectl get configmap -n some-namespace some-config-mapansible -m k8s_facts -a 'namespace=some-namespace kind=ConfigMap \api_version=v1 name=some-config-map' localhost
Reuse
Our goal is to use as much common code for Kubernetes management as possible
A single Ansible role that takes a set of resource manifests and ensures that Kubernetes meets those expectations
Ideally, one manifest template that works for most applications would be great, but harder
Ansible’s Kubernetes strengths
Templating
Roles
Hierarchical inventory
Secrets management
Modules, lookup plugins, filter plugins
Reuse
Our goal is to use as much common code for Kubernetes management as possible
A single Ansible role that takes a set of resource manifests and ensures that Kubernetes meets those expectations
Ideally, one manifest template that works for most applications would be great, but harder
Ansible’s Kubernetes strengths
Templating
Roles
Hierarchical inventory
Secrets management
Modules, lookup plugins, filter plugins
Templating
Templates are super-powerful
Reuse resource definition files for all environments
Use a common language where possible – e.g.
{{ kube_resource_name }}{{ kube_ingress_fqdn }}across all applicationsAvoid control structures where possible
replicas = {% 5 if env == 'prod' else 1 %}vs
replicas = {{ kube_deployment_replicas }}
Templating
dicts
dicts
Sometimes whole sections of manifests differ between environment
metadata:annotations:{{ kube_ingress_annotations | to_nice_yaml(indent=2) | indent(4) }}
Roles
Roles are excellent for sharing a common set of tasks across multiple playbooks
One role should be suitable for almost all Kubernetes operations
We have moved from per-application roles to one generic role for all but a couple of applications
Hierarchical inventory
Some properties are the same across many applications within an environment
Some properties are the same across all environments for an application
Some properties can be composed from other properties
Some properties may need specific overrides for certain application/environment combinations
All of these needs are met by inventory groups
Avoiding hosts: localhost
Typically people will use
hosts:to talk to Kubernetes
localhostThis reduces the power of inventory and reuse
Using the runner pattern
Runner pattern uses hosts declarations like
hosts: "{{ env }}-{{with e.g.
app }}-runner"-e
env=test -e app=webinventory hierarchies allow runners to gather their inventory from groups such as
test,webandtest-webSet
ansible_connection: localandansible_python_interpreter:"{{in the
ansible_playbook_python }}"runnergroup_vars file
Flat vs hierarchical inventory
Generating inventory
Group combinations explode as applications and environments increase
It’s easy to get this wrong with standard hosts files
generatorinventory plugin generates such group combinations from a list of layers
Standard hosts file
[test:children]test-webtest-api[api:children]test-api[web:children]test-web[test-web]test-web-runner[test-api]test-api-runner[runner]test-web-runnertest-api-runner
Generating inventory
Group combinations explode as applications and environments increase
It’s easy to get this wrong with standard hosts files
generatorinventory plugin generates such group combinations from a list of layers
Standard hosts file
[test:children]test-webtest-api[api:children]test-api[web:children]test-web[test-web]test-web-runner[test-api]
Generator plugin hosts file
# inventory.config file in YAML formatplugin: generatorstrict: Falsehosts:name: "{{ environment }}-{{ application }}-runner"parents:- name: "{{ environment }}-{{ application }}"parents:- name: "{{ application }}"vars:application: "{{ application }}"- name: "{{ environment }}"vars:environment: "{{ environment }}"- name: runnerlayers:environment:- testapplication:- web- api
Secrets
We use
ansible-vaultfor all of our secretsKubernetes expects secrets to be base64 encoded
Use
no_logwith thek8smodule when uploading secrets
Avoid vaulting whole variables files
Use
ansible-vault encrypt_stringto encrypt each secret inline
Don’t forget to use
echo -n $secretto avoid encrypting the newline!
| ansible-vault encrypt_string
Secrets in environment variables
Use a
Secretresource to store secret environment variablesUse
envFromif you then want to include all the secrets from that resource
Secrets in environment variables
key1: !vault |$ANSIBLE_VAULT;1.1;AES256616661626636666439393531653938333833313136646162343437396539373363376262636635383335336263303963623332666639666364356166393462370a396465393637363938656562393936616638343762356135643032373131313963353033366364663264303535306138363565643438326638393533663931640a6634383134616164363933653465663130376130343237386462343635343734my_secret_env:KEY1: "{{ key1 | b64encode }}"
A Secret manifest
apiVersion: v1kind: Secretmetadata:name: my-secret-envnamespace: my-namespacedata:{{ my_secret_env | to_nice_yaml(indent=2) | indent(2) }}
Using the Secret
---kind: Deploymentspec:template:spec:containers:- envFrom:- secretRef:name: my-secret-env
Secrets in environment variables key1: !vault | $ANSIBLE_VAULT;1.1;AES256 61666162663666643939353165393833383331313664616234343739653937336337626263663538 3335336263303963623332666639666364356166393462370a396465393637363938656562393936 61663834376235613564303237313131396335303336636466326430353530613836356564343832 6638393533663931640a663438313461616436393365346566313037613034323738646234363534 3734 my_secret_env: KEY1: “{{ key1 | b64encode }}”
A Secret manifest
apiVersion: v1kind: Secretmetadata:name: my-secret-envnamespace: my-namespacedata:{{ my_secret_env | to_nice_yaml(indent=2) | indent(2) }}
Using the Secret
---kind: Deploymentspec:template:spec:containers:- envFrom:- secretRef:name: my-secret-env
Modules
k8s—main module for managing Kubernetes resourcesk8s_facts—useful for run-time querying of resourcesaws_eks_cluster—manages AWS EKS clustersazure_rm_aks—manages Azure Kubernetes Service clustersgcp_container_clusterandgcp_container_nodepool—manage GKE clusters and node pools
k8s module
uses the same manifest definitions as kubectl
can take inline resource
definitions, orsrcfrom fileinline definitions work well with
templatelookupdefinition:
"{{ lookup('template','path/to/resource.j2')
| from_yaml }}"invoke once with a manifest containing a list of resources, or invoke in a
loopover a list of resourcescopes with Custom Resource Definitions (2.7)
Plugins
yamlstdout callback plugin is great for having output match inputk8slookup plugin returns information about Kubernetes resourcesfrom_yamlandfrom_yaml_all(2.7) read from templates into module datab64encodeencodes secrets in base64k8s_config_hashandk8s_config_resource_namefor immutableConfigMaps (likely 2.8)
Demo
Scenario:
We’re practising Continuous Delivery with Rolling Deployments and feature flags
Upgrade application
Enable feature flag
Realise feature is buggy
Disable feature flag
Why Immutable ConfigMaps?
Updating a
ConfigMapused in aDeploymentwill not update thePods in thatDeploymentRolling back to a previous configuration will not cause the
Pods to pick up theConfigMaporSecrets changeskubectl rollout undofor emergency purposes will only roll back containers, not configurationImmutable
ConfigMaps
Name
ConfigMaps based on a hash of its dataReference this
ConfigMapname in aDeploymentChanging a existing
ConfigMapwill change its name, triggeringPodupdatesRolling back a
Deploymentwill then roll back to the old configUse
append_hashto generate immutableConfigMapsUse
k8s_config_resource_namefilter plugin
Demo part two
Planned k8s improvements
append_hashwill enable immutableConfigMaps andSecrets (likely 2.8)validatewill return helpful warning and/or error messages if a resource manifest does not match the Kubernetes resource specification (likely 2.8)waitwill allow you to wait until the Kubernetes resources are actually in the desired state (hopefully 2.8)
