Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DODDFW2017 - Configuration Management and Provisioning Are Different

DODDFW2017 - Configuration Management and Provisioning Are Different

Provisioning infrastructure with configuration management tools like Chef and Ansible can get tricky. This talk will explore how this unfolds and introduce using alternative tools like Terraform and CloudFormation for this purpose.

At many organizations I’ve worked at over the last few years, I’ve seen a common anti-pattern: configuration management (CM) tools used incorrectly as provisioning tools. This has been frustrating because using CM tools to provision infrastructure undoubtedly leads to complex code that is unmaintainable and hard to extend.

The purpose of this talk is to show how I’ve seen this unfold and how using tools such as Terraform and CloudFormation significantly reduce this complexity without compromising your reach with infrastructure as code.

DevOpsDays DFW

August 29, 2017
Tweet

More Decks by DevOpsDays DFW

Other Decks in Business

Transcript

  1. Provisioning In A Nutshell OLD NEW Get hardware* terraform plan

    terraform apply * Or AWS/Google Cloud/Heroku/etc account. Get hardware* Run install scripts Consult runbook Configure CM, if necessary Wait for CM to apply
  2. Why Provisioning Tools? Infrastructure deployment procedures as code --- no

    more docx runbooks Save you time ($$$) from writing your own provisioning code Enables infrastructure deployment testing just like code --- no fragile infrastructure
  3. Provisioning: An Example # ~/code/terraform/main.yml resource "aws_instance" "ec2_instance" { ami

    = "${var.ami_id}" count = "${var.number_of_instances}" subnet_id = "${var.subnet_id}" instance_type = "${var.instance_type}" user_data = "${var.user_data}" key_name = "${var.key_name}" vpc_security_group_ids = ["${var.security_group_ids}"] tags { Name = "${var.instance_name}-${count.index}" } associate_public_ip_address = "${var.create_public_ip_address}“ provisioner “remote-exec” { inline = [ “git clone $ANSIBLE_REPO /tmp/ansible”, “ansible-playbook –i ${var.ansible_inventory} –vvv ${var.playbook} ] } } $> cd ~/code/terraform $> terraform plan –var-file ./environments/dev –var number_of_instances=1000 $> terraform apply –var-file ./environments/dev –var number_of_instances=1000 This is how you define an AWS EC2 instance with Terraform. You can make your own providers for nearly any server, networking device, or other piece of infrastructure you want to provision. Boom. You’ve just deployed 1,000 development EC2 instances, all configured by Ansible. Nice. * Until it’s not. But it usually is.
  4. What’s Config Management? Consistent and repeatable infrastructure Simple domain-specific language

    --- anyone can read and contribute APIs allow easier tooling and scalability
  5. Config Management: An Example --- # an example of an

    ansible playbook main.yml - name: install tree sudo: yes apt: name={{ item }} state=present with_items: - tree --- # an example of which hosts would receive the above - hosts: useless_server.va1.example.com roles: - has_tree Roles in Ansible group specific actions to be applied onto hosts. This is an example of a role called has_tree. It installs tree. Yay, tree. This is how you tell Ansible to install tree onto servers. It really is that easy. Not kidding.* * Until it’s not. But it usually is.
  6. Complexity in simplicity --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision an

    EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ Here is a simple playbook that deploys an EC2 instance. We’re assuming that our options such as its access and secret keys and AMI ID will be provided through a variable file. --- # ~/code/ansible/configure_nginx_web_server.yml - hosts: webservers tasks: - name: Install nginx ... - name: Configure it ... This (incomplete) playbook will configure hosts in the webservers group with NGINX. We’ll assume that this will run on ec2 servers tagged with “application: web_server” by way of dynamic inventory.
  7. Complexity in simplicity --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision an

    EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ --- # ~/code/ansible/configure_nginx_web_server.yml - hosts: webservers tasks: - name: Install nginx ... - name: Configure it ... How can we guarantee this?
  8. Complexity in simplicity --- # ~/code/ansible/configure_nginx_web_server.yml - hosts: webservers tasks:

    - name: Install nginx ... - name: Configure it ... when: instance_count < 3 --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Obtain number of web instances currently present shell: “aws ec2 describe_instances --filter {{ ec2_filter }} --query ‘Reservations[].Instances[].Id’ –output text | wc –l” register: command_result - set_fact: instance_count: “{{ command_result.stdout | int }}” - fail: msg: “Invalid instance count!” when: instance_count < 0 ... # continued to the right -> --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision an EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ Get state externally is how!
  9. Complexity in simplicity --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Obtain number

    of web instances currently present shell: “aws ec2 describe_instances --filter {{ ec2_filter }} --query ‘Reservations[].Instances[].Id’ –output text | wc –l” register: command_result - set_fact: instance_count: “{{ command_result.stdout | int }}” - fail: msg: “Invalid instance count!” when: instance_count < ... 1. What happens if your external dependency isn’t available? 2. What happens with unexpected output? 3. What if this external dependency is slow?
  10. Complexity in simplicity --- # ~/code/ansible/tasks/ec2/retrieve_instance_count.yml - include: tasks/check_for_awscli.yml -

    name: Obtain number of web instances currently present shell: “{{ awscli_path }} ec2 describe_instances --filter {{ ec2_filter }} --query ‘Reservations[].Instances[].Id’ –output text | wc –l” register: command_result - set_fact: instance_count: “{{ command_result.stdout | int }}” - fail: msg: “Invalid instance count!” when: instance_count < 0 “Modularize” it! --- # ~/code/ansible/tasks/ec2/check_for_awscli.yml - name: Find awscli binary shell: “which awscli” register: command_result - fail: msg: “AWSCLI was not found.” when: command_result.exit_code != - set_fact: awscli_path: “{{ command_result.stdout }}” --- # ~/code/ansible/deploy_ec2_web_server.yml - include: tasks/ec2/retrieve_instance_count.yml - name: Provision an EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} ...
  11. Complexity in simplicity --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision an

    EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ Want to create things like security groups dynamically? Prepare for more of that.
  12. Complexity in simplicity --- # ~/code/terraform/web_servers.tf resource “aws_instance” “ec2_instance” {

    ami = “${var.ami_id}” count = “${var.number_of_instances}” subnet_id = “${var.instance_type}” user_data = “${var.user_data}” key_name = “${var.key_name}” tags { cost_center = “${var.cost_center}” application = “web servers” project_name = “${var.project_name}” } associate_public_ip_address = yes } simplicity.
  13. Stateless provisioning is tricky --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision

    an EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ --- # ~/code/ansible/configure_nginx_web_server.yml - hosts: webservers tasks: - name: Install nginx ... - name: Configure it ... What happens to existing instances when this changes?
  14. Stateless provisioning is tricky --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision

    an EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ --- # ~/code/ansible/configure_nginx_web_server.yml - hosts: webservers tasks: - name: Install nginx ... - name: Configure it ... Nothing. You have to write a new task to update them.
  15. Complexity in simplicity --- # ~/code/terraform/web_servers.tf resource “aws_instance” “ec2_instance” {

    ami = “${var.ami_id}” count = “${var.number_of_instances}” subnet_id = “${var.instance_type}” user_data = “${var.user_data}” key_name = “${var.key_name}” tags { cost_center = “${var.cost_center}” application = “web servers” project_name = “${var.project_name}” } associate_public_ip_address = yes } stateful.
  16. Stateless provisioning is tricky --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision

    an EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ --- # ~/code/ansible/configure_nginx_web_server.yml - hosts: webservers tasks: - name: Install nginx ... - name: Configure it ... What happens if this fails?
  17. Stateless provisioning is tricky --- # ~/code/ansible/deploy_ec2_web_server.yml - name: Provision

    an EC2 web server instance. ec2: aws_access_key: {{ aws_access_key }} aws_secret_key: {{ aws_secret_key }} exact_count: {{ number_of_instances }} group: [ ‘web’, ‘ansible_managed’ ] instance_type: t2.medium monitoring: yes key_name: {{ aws_ec2_access_key }} vpc_subnet_id: {{ vpc_subnet_id }} assign_public_ip: yes ami_id: {{ aws_ami_id }} instance_tags: cost_center: {{ cost_center }} project_code: {{ project_code }} application: ‘web_server’ --- # ~/code/ansible/configure_nginx_web_server.yml - hosts: webservers tasks: - name: Install nginx ... - name: Configure it ... Nothing. Rollback is on you.
  18. Complexity in simplicity --- # ~/code/terraform/web_servers.tf resource “aws_instance” “ec2_instance” {

    ami = “${var.ami_id}” count = “${var.number_of_instances}” subnet_id = “${var.instance_type}” user_data = “${var.user_data}” key_name = “${var.key_name}” tags { cost_center = “${var.cost_center}” application = “web servers” project_name = “${var.project_name}” } associate_public_ip_address = yes } safe.