Bring your infrastructure under control with Infrastructor

18
bring your infrastructure under control with Infrastructor Stanislav Tiurikov, infrastructor.io, July 2017 ©

Transcript of Bring your infrastructure under control with Infrastructor

Page 1: Bring your infrastructure under control with Infrastructor

bring your infrastructure under control with

Infrastructor

Stanislav Tiurikov, infrastructor.io, July 2017 ©

Page 2: Bring your infrastructure under control with Infrastructor

Introduction1. Infrastructor is an open source Infrastructure as Code framework2. It is implemented with Groovy and provides a Domain Specific Language to

describe, manage and provision unix based hosts: either bare-metal servers and virtual machines

3. Loops, conditions, closures, variables, etc. - whole power of Groovy Programming Language can be utilized

4. Portable, but requires Java Virtual Machine to run5. Flexible: there are many ways how to organize your code6. Unix oriented: currently developed to provision unix based hosts7. Agentless: only an SSH connectivity is required8. Has minimal dependencies on the host side, just a few posix utility programs

are used: cat, tee, mkdir, cp, etc.

Page 3: Bring your infrastructure under control with Infrastructor

Introduction: an example

inlineInventory { // define an inventory

node host: '10.0.0.10', port: 22, username: 'devops', keyfile: 'devops/private.key'

}.provision { // define a provisioning plan

task name: 'install common packages', actions: { // define a task - a group of actions to run on each inventory node

shell sudo: true, command: 'apt update'

['tmux', 'mc', 'htop'].each {

shell "sudo apt install $it -y"

}

}

}

~ $ infrustructor run -f example.groovy

A provisioning script usually consists of two main parts:1. An inventory definition - a set of nodes (servers, VMs) you want to provision2. A provisioning plan definition - which actions to run on the nodes

There is no limitation on inventory and provisioning plan number a single script can contain

A provisioning script can be executed using infrastructor command line interface:

Page 4: Bring your infrastructure under control with Infrastructor

InventoryThere are several ways to define a set of nodes you want to setup:1. Inline Inventory2. Aws Inventory3. Managed Aws Inventory4. Docker Inventory (for test and play only)

Page 5: Bring your infrastructure under control with Infrastructor

Inline Inventory

inlineInventory {

node host: '10.0.0.10', port: 22, username: 'devops', keyfile: 'devops/private.key', tags: [db: true, env: 'test']

node host: '10.0.0.11', port: 22, username: 'devops', keyfile: 'devops/private.key', tags: [db: true, env: 'live']

['10.0.1.10', '10.0.1.11', '10.0.1.12'].each { ip ->

node(host: ip, port: 22) {

username = 'devops'

keyfile = 'devops/private.key'

tags = [service: true, env: 'live']

}

}

}.provision {

// some tasks here

}

Inline Inventory defines a set of nodes in the same file where a provisioning plan is defined. You can use loops and other Groovy tricks to organize the definition:

Page 6: Bring your infrastructure under control with Infrastructor

Aws Inventory

awsInventory(AWS_ACCESS_KEY_ID, AWS_ACCESS_SECRET_KEY, AWS_REGION) {

username = 'devops'

keyfile = 'devops/private.key'

tags = [managed: 'true'] // each AWS instance with tag 'managed:true' will be added to the inventory

usePublicIp = true

}.provision {

task name: 'print all nodes', actions: {

info "$it.id: $it.host" // let’s print all nodes from the inventory

}

}

Aws Inventory can read a set of EC2 instances from AWS and convert it to a set of nodes. A provisioning plan will be applied to this set of nodes (i.e. EC2 instances):

$ infrastructor run -f aws_example.groovy -v AWS_ACCESS_KEY_ID="XXXXXX" -v AWS_SECRET_KEY_ID="YYYYYY" -v AWS_REGION="eu-central-1"

Page 7: Bring your infrastructure under control with Infrastructor

Managed Aws Inventory: EC2 support

managedAwsInventory(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY_ID, AWS_REGION) {

ec2(tags: [managed: true], parallel: 2) { // all EC2 instances with the tag 'managed:true' will be considered as existing nodes

(1..5).each {

node(name: "aws-node-$it") {

imageId = 'ami-12345678'

instanceType = 't2.small'

subnetId = 'sb-12345678'

securityGroupIds = ['sg-00000001', 'sg-00000002']

keyName = 'devops_private_key'

username = 'devops'

keyfile = 'devops/devops.key'

}

}

}

}.provision { ... }

With Managed Aws Inventory we can define a target state of inventory. Infrastructor will create, update and remove EC2 instances to reach the target state:

$ infrastructor run -f aws_example.groovy -v AWS_ACCESS_KEY_ID="XXXXXX" -v AWS_SECRET_KEY_ID="YYYYYY" -v AWS_REGION="eu-central-1"

Page 8: Bring your infrastructure under control with Infrastructor

Managed Aws Inventory: Route53 support

managedAwsInventory(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY_ID, AWS_REGION) {

ec2(tags: [managed: true]) {

node(name: 'web-host-1', tags: ['front-server': true]) { ... }

node(name: 'web-host-2', tags: ['front-server': true]) { ... }

}

route53(hostedZoneId: 'ZZZZZZZZZZZZZZ') {

recordSet(type: 'A', name: 'myservice.infrastructor.io') {

ttl = 600 // seconds

resources = {'front-server:true'} // IPs of instances with tag 'front-server:true' will be added to the record set

usePublicIp = true

}

}

}.provision { ... }

There is an initial support for AWS Route53. Infrasrtuctor can automatically create, update and remove DNS records in accordance to the current inventory state:

$ infrastructor run -f route53_example.groovy

Page 9: Bring your infrastructure under control with Infrastructor

Docker Inventory

def inventory = inlineDockerInventory { // random ports and container ids will be used

node image: 'infrastructor/ubuntu-sshd', tags: [os: 'ubuntu'], username: 'root', password: 'infra'

node image: 'infrastructor/centos-sshd', tags: [os: 'centos'], username: 'devops', password: 'devops'

}.provision {

task parallel: 2, actions: { shell command: "apt update" }

task filter: {'os:ubuntu'}, actions: { shell command: "apt install tmux -y" }

task filter: {'os:centos'}, actions: { shell command: "apt install htop -y" }

}

inventory.shutdown()

Docker Inventory might be useful for some preliminary testing: then you want to check your provisioning plan quickly:

$ infrastructor run -f docker_inventory_example.groovy

Page 10: Bring your infrastructure under control with Infrastructor

Provisioning Plan

inlineInventory { ...

}.provision {

task name: 'install common packages', filter: {'db:true' || 'web:true'}, parallel: 2, actions: {

shell 'sudo apt install docker.io -y'

}

task name: 'install MySQL', filter: {'db:true'}, actions: {

shell "sudo docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD mysql:latest"

}

task name: 'install My Service', filter: {'web:true'}, actions: {

template(source: 'templates/myservice.conf', target: '/etc/myservice.conf') {

bindings = [dbhost: 'storage.internal.infrastructor.io', dbport: 3306, dbuser: 'root', dbpassword: MYSQL_ROOT_PASSWORD]

}

shell 'sudo docker run -d -p 80:80 -v /etc/myservice.conf:/etc/myservice.conf myservice:latest'

}

}.provision { ... }

$ infrastructor run -f provisioning_plan_example.groovy -v MYSQL_ROOT_PASSWORD=XXXXX

A provisioning plan consists of a list of tasks which are executed one by one. It is possible to define several provisioning plans for the same inventory in a single file:

Page 11: Bring your infrastructure under control with Infrastructor

Provisioning Plan: Tasks

inlineInventory {

node id: 'database', host: '10.0.0.10', tags: [db: true, priority: 1], username: 'devops', keyfile: 'devops/private.key'

node id: 'service', host: '10.0.0.11', tags: [service: true, priority: 2], username: 'devops', keyfile: 'devops/private.key'

node id: 'gateway', host: '10.0.0.12', tags: [loadbalancer: true, priority: 1], username: 'devops', keyfile: 'devops/private.key'

...

}.provision {

task name: 'install common packages', filter: {'db:true' || (!'service:true' || !'priority:2')}, parallel: 2, actions: {

shell 'sudo apt install htop tmux mc -y'

}

...

}

A task definition usually consists of:1. A human readable task name2. A filtering expression to define a subset of nodes to run actions on3. A level of parallelism - amount of nodes to provision in parallel4. A list of actions to run on the nodes

Page 12: Bring your infrastructure under control with Infrastructor

Actions

more info about actions: https://github.com/infrastructor/infrastructor/wiki/Action-Reference

Currently implemented actions:1. apply - runs a closure within current node context2. directory - creates a new directory on a remote host3. fetch - fetches a file from a remote host4. file - creates a new file with a specified content 5. upload - uploads a file to a remote host6. group - creates a group on a remote host 7. input - asks for user input: a plain value or a secret8. insertBlock - inserts a block of text to a file specified on a remote host9. load - loads a closure from an external file

10. replaceLine - replaces a line in a text file on a remote host11. shell - runs a shell command on a remote host12. template - creates a text file based on a specified template13. user - creates a user on a remote host 14. waitForPort - waits till a specified port of a remote host is ready to accept

connections

Page 13: Bring your infrastructure under control with Infrastructor

External configs, runtime parameters and user input

$ infrastructor run -f my_provisioning.groovy -v PARAM1=VALUE1 -v PARAM2=VALUE2 -v PARAM3=VALUE3

inlineInventory { ...

}.provision {

def not_a_secret = input message: 'please, enter a value: ', secret: false

def a_secret = input message: 'please, enter a value: ', secret: true

}

inlineInventory { ...

}.provision {

def configuration = config('configurations/app.config')

info "this is a value from configuration: $configuration.somevalue"

}

more info about external configs: https://github.com/infrastructor/infrastructor/wiki/External-Configuration

Pass parameters via CLI:

Ask user for input during execution:

Use an external file with predefined configuration (will be loaded with Groovy’s ConfigSlurper):

Page 14: Bring your infrastructure under control with Infrastructor

Encryption of sensitive data

dbuser=root

dbpassword=mysecrethere

$ infrastructor encrypt -f my_template_with_secret_data.properties -m FULL

1v8sKrlv2J7AKgKUFiFFdsElRL2fYyT6a+oNlq3+jVPnjXJEMMB1W326ltR4

OmAW

$ infrastructor encrypt -f my_secret_data.properties -m PART

dbuser=root

dbpassword=${encrypt('mysecret')}

dbuser=root

dbpassword=${decrypt('AJTwsjM+CbKjmOtV6fD6Mg==')}

more info about encryption: https://github.com/infrastructor/infrastructor/wiki/Encryption-and-Decryption

It is possible to encrypt and decrypt files with sensitive data and then use these files with actions. In this case the sensitive data it can be safely stored under source control. There are two supported modes: full (a file will be encrypted completely) and partial (only specific part of the file will be encrypted). Encryption can be done via CLI using ‘encrypt’ command as well as decryption with ‘decrypt’ one. AES and Base64 encoding are used to do encryption.

Page 15: Bring your infrastructure under control with Infrastructor

Encryption of sensitive data

run it:

inlineInventory { ...

}.provision {

def DECRYPTION_KEY = input message: 'decryption key: ', secret: true

task name: 'install common packages', filter: {'web:true'}, actions: {

template(source: 'templates/myservice.conf', target: '/etc/myservice.conf') {

decryptionKey = DECRYPTION_KEY

decryptionType = PART // or FULL - depends on how it was done before.

}

shell 'sudo docker run -d -p 80:80 -v /etc/myservice.conf:/etc/myservice.conf myservice:latest'

}

}

more info about encryption: https://github.com/infrastructor/infrastructor/wiki/Encryption-and-Decryption

Decryption is directly supported by ‘upload’ (full mode only) and ‘template’ (both full and part modes) actions. We can specify a decryption key at runtime:

Page 16: Bring your infrastructure under control with Infrastructor

External actions

run it:

inlineInventory { ...

}.provision {

def install_common_packages = load 'actions/install_common_pakages.groovy'

task name: 'install common packages', actions: {

apply closure: install_common_packages

}

}

It is possible to move some actions to an external file to make this logic reusable for several provisioning scripts. To run such actions we can use a combination of ‘apply’ action and ‘load’ utility function:

install_common_packages.groovy contain a closure:

return {

info "running action on node $node.id" // node variable will be available in this context

shell 'sudo apt update -y'

shell 'sudo apt install tmux htop mc -y'

}

Page 17: Bring your infrastructure under control with Infrastructor

Links

run it:

1. Infrastructor Project home: https://github.com/infrastructor2. Apache Groovy Project home: http://groovy-lang.org

Page 18: Bring your infrastructure under control with Infrastructor

Thank you!

Stanislav Tiurikov, infrastructor.io, July 2017 ©