Bring your infrastructure under control with Infrastructor
-
Upload
stanislav-tiurikov -
Category
Software
-
view
179 -
download
1
Transcript of Bring your infrastructure under control with Infrastructor
bring your infrastructure under control with
Infrastructor
Stanislav Tiurikov, infrastructor.io, July 2017 ©
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.
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:
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)
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:
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"
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"
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
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
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:
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
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
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):
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.
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:
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'
}
Links
run it:
1. Infrastructor Project home: https://github.com/infrastructor2. Apache Groovy Project home: http://groovy-lang.org
Thank you!
Stanislav Tiurikov, infrastructor.io, July 2017 ©