In my latest role I am architecting CI/CD pipelines and patterns. One of the major things I need is the ability to create the same application server and quickly deploy to AWS, "on prem", and developer workstations. Here's what I've come up with so far:


Tech:

http://www.packer.io/
https://www.vagrantup.com/
https://www.getchef.com/chef/
http://jenkins-ci.org/
http://aws.amazon.com/cloudformation/

Code:

packer.json

{
  "variables": {
    "home_directory": "/Users/cipher"
  },
  "builders": [
    {
      "type": "virtualbox-ovf",
      "source_path": "centos-box.ovf",
      "ssh_username": "vagrant",
      "ssh_key_path": "{{user `home_directory`}}/.vagrant.d/insecure_private_key",
      "ssh_wait_timeout": "30s",
      "shutdown_command": "echo 'packer' | sudo -S shutdown -P now"
    },
    {
      "type": "amazon-ebs",
      "ssh_username": "ec2-user",
      "region": "us-east-1",
      "instance_type": "m3.medium",
      "subnet_id": "subnet-f4****9b",
      "security_group_id": "sg-****9962",
      "source_ami": "ami-08842d60",
      "ami_name": "packer-{{timestamp}}"
    }
  ],
  "post-processors": [
    {
      "output": "ghost.box",
      "vagrantfile_template": "vagrantfile_templates/ghost.rb",
      "type": "vagrant"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": ["sudo mkdir /etc/chef",
      "sudo chmod 600 /etc/chef",
      "sudo mkdir /tmp/chef",
      "sudo chmod 777 /tmp/chef"]
    },
    {
      "type": "chef-client",
      "server_url": "https://chefserver.dowjones.net/",
      "validation_client_name": "packer",
      "node_name": "ghost-{{uuid}}",
      "staging_directory": "/tmp/chef",
      "validation_key_path": "{{user `home_directory`}}/packer.pem",
      "run_list": ["ghost::ghost"]
    }
  ]
}

recipe.rb

%w{postgresql-server postgresql-devel git gcc-c++}.each do |pkg|
  package pkg do
    action :upgrade
  end
end

execute "/sbin/service postgresql initdb" do  
  not_if { ::FileTest.exist?(File.join("/var/lib/pgsql/data/", "PG_VERSION")) }
end

cookbook_file "/var/lib/pgsql/data/pg_hba.conf" do  
  source "pg_hba.conf"
  mode "0600"
end

cookbook_file "/etc/init.d/ghost" do  
  source "ghost"
  mode "0755"
end

service 'postgresql' do  
  action [ :enable ]
end

remote_file "/root/node.tar.gz" do  
  source "http://nodejs.org/dist/v0.10.32/node-v0.10.32-linux-x64.tar.gz"
end

remote_file "/root/ghost.tar.gz" do  
  source "http://djin-jenkins01.dowjones.net:7777/job/ghost/lastSuccessfulBuild/artifact/ghost.tar.gz"
end

directory "/root/ghost" do  
  owner 'root'
  group 'root'
  mode '0755'
  action :create
end

execute "tar -xvf /root/node.tar.gz -C /usr/local --strip=1" do  
end

execute "tar -xvf /root/ghost.tar.gz -C /root/ghost" do  
end

execute "git config --system http.sslverify false" do  
end

service 'ghost' do  
  action [ :enable ]
end  

cf.template

{

    "AWSTemplateFormatVersion": "2010-09-09",

    "Description": "AWS CloudFormation template for Ghost",

    "Parameters": {
        "InstanceType": {
            "Description": "WebServer EC2 instance type",
            "Type": "String",
            "Default": "t2.micro",
            "AllowedValues": [
                "t2.micro"
            ],
            "ConstraintDescription": "must be a valid EC2 instance type."
        },
        "KeyName": {
            "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
            "Type": "String",
            "Default": "rahners"
        },
        "ImageId": {
            "Description": "The ImageId for the LaunchConfig",
            "Type": "String",
            "Default": "ami-1ae34d72"
        },
        "SecurityGroups": {
            "Description": "Security groups for the Launch Configuration.",
            "Type": "CommaDelimitedList",
            "Default": "sg-****962"
        },
        "Subnets": {
            "Description": "Subnets you wish to use for the Auto Scale Group.",
            "Type": "CommaDelimitedList",
            "Default": "subnet-f4****9b"
        },
        "AvailabilityZones": {
            "Description": "AZs you wish to use for the Auto Scale Group.",
            "Type": "CommaDelimitedList",
            "Default": "us-east-1b"
        },
        "LoadBalancerNames": {
            "Description": "Name of the ELBs to use.",
            "Type": "CommaDelimitedList",
            "Default": "ghost"
        }
    },

    "Resources": {
        "WebServerGroup": {
            "Type": "AWS::AutoScaling::AutoScalingGroup",

            "UpdatePolicy" : {
                "AutoScalingRollingUpdate" : {
                "MaxBatchSize" : "1",
                "MinInstancesInService" : "5",
                "PauseTime" : "PT0M30S"
              }
            },

            "Properties": {
                "VPCZoneIdentifier" : { "Ref": "Subnets"},
                "AvailabilityZones" : { "Ref": "AvailabilityZones"},
                "LaunchConfigurationName": { "Ref": "LaunchConfig" },
                "HealthCheckType": "ELB",
                "HealthCheckGracePeriod": "300",
                "MinSize": "5",
                "MaxSize": "10",
                "DesiredCapacity" : "5",
                "LoadBalancerNames": { "Ref": "LoadBalancerNames" }
            }
        },
        "LaunchConfig": {
            "Type": "AWS::AutoScaling::LaunchConfiguration",
            "Properties": {
                "AssociatePublicIpAddress": "false",
                "KeyName": { "Ref": "KeyName" },
                "ImageId": { "Ref": "ImageId" },
                "SecurityGroups": { "Ref": "SecurityGroups" },
                "InstanceType": { "Ref": "InstanceType" }
            }
        }
    }
}

*fiveby and FCM are proprietary (though I am hoping to open source fiveby)