SaLUG! @ Manifatture KNOS
10 Marzo 2015
Configuration Management is a systems engineering process for establishing and maintaining consistency of a product's performance, functional and physical attributes with its requirements, design and operational information throughout its life.
CM is the practice of handling changes systematically so that a system maintains its integrity over time. CM implements the policies, procedures, techniques, and tools that are required to manage, evaluate proposed changes, track the status of changes, and to maintain an inventory of system and support documents as the system changes.
Configuration management can be used to maintain OS configuration files. Example systems include Quattor, CFEngine, Bcfg2, Puppet, and Chef.
A theory of configuration maintenance was worked out by Mark Burgess, with a practical implementation on present day computer systems in the software CFEngine able to perform real time repair as well as preventive maintenance.
http://en.wikipedia.org/wiki/Configuration_management
Paper (PDF):
M. Burgess, On the theory of system administration,
Science of Computer Programming 49, 2003. p1-46
Revision control, also known as version control and source control (and an aspect of software configuration management), is the management of changes to documents, computer programs, large web sites, and other collections of information. Each revision is associated with a timestamp and the person making the change. Revisions can be compared, restored, and with some types of files, merged.
Git is a distribuited revision control system with an emphasis on speed, data integrity and support for distribuited, non-linear workflows.
Git was initially designed and developed by Linus Torvalds for Linux kernel development in 2005, and has since become the most widely adopted version control system for software development.
$ mkdir my-new-project
$ cd my-new-project
$ touch README.md
$ git init
Initialized empty Git repository in /home/rpl/my-new-project/.git/
$ git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
$ git add README.md
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
$ git commit -m "added README.md"
[master (root-commit) 7101a13] added README.md
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README.md
$ git status
On branch master
nothing to commit, working directory clean
$ git log
commit 7101a13530a691be291b8fde44b8499dc25bd471
Author: Luca Greco <luca.greco@alcacoop.it>
Date: Wed Feb 05 17:39:30 2015 +0100
added README.md
$ git checkout -b feature/new-awesome-stuff
Switched to a new branch 'feature/new-awesome-stuff'
$ git branch -a
* feature/new-awesome-stuff
master
$ touch app.js
$ echo "- new awesome stuff" > README.md
$ git add app.js
$ git status
On branch feature/new-awesome-stuff
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: app.js
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: README.md
$ git commit -a -m "Add new awesome stuff"
[feature/new-awesome-stuff cbd6775] Add new awesome stuff
2 files changed, 1 insertion(+)
create mode 100644 app.js
$ git log --graph --oneline --decorate
* cbd6775 (HEAD, feature/new-awesome-stuff) Add new awesome stuff
* 7101a13 (master) added README.md
$ git checkout master
Switched to branch 'master'
$ echo "- this will be a conflict" > README.md
$ git commit -a -m "updated README.md"
[master d854a05] updated README.md
1 file changed, 1 insertion(+)
$ git log --graph --oneline --decorate --all
* d854a05 (HEAD, master) updated README.md
| * cbd6775 (feature/new-awesome-stuff) Add new awesome stuff
|/
* 7101a13 added README.md
$ git merge feature/new-awesome-stuff
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Changes to be committed:
new file: app.js
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
$ cat README.md
<<<<<<< HEAD
- this will be a conflict
=======
- new awesome stuff
>>>>>>> feature/new-awesome-stuff
$ git mergetool
Merging:
README.md
Normal merge conflict for 'README.md':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (meld):
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: README.md
new file: app.js
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md.orig
$ git commit -m "merged feature/new-awesome-stuff"
[master b364f8f] merged feature/new-awesome-stuff
$ git log --graph --oneline --decorate
* b364f8f (HEAD, master) merged feature/new-awesome-stuff
|\
| * cbd6775 (feature/new-awesome-stuff) Add new awesome stuff
* | d854a05 updated README.md
|/
* 7101a13 added README.md
Ansible is the simplest way to automate IT.
Rather than writing custom code to automate your systems, your team writes simple task descriptions that even the newest team member can understand on first read.
- name: provision php packages
apt: name={{item}} update_cache=yes cache_valid_time=3600
with_items:
- lsof
- php5-cli
- php5-mysql
- name: provision apache packages
apt: name={{item}} update_cache=yes cache_valid_time=3600
with_items:
- apache2
- libapache2-mod-php5
Ansible uses SSH by default instead of requiring agents everywhere
- avoid extra open ports, improve security
- eliminate "managing the management"
- system resource utilization is dramatically improved
Ansible automates:
- app deployment
- configuration management
- workflow orchestration and even cloud provisioning
all from one system (e.g. your laptop).
For the more advanced features:
- On the managed nodes, you only need Python 2.4 or later
- If you are running Python < 2.5 on the remotes, you will also need python-simplejson
sudo apt-get update && \
sudo apt-get install python-pip -y && \
sudo pip install --system ansible
Detailed info here
[webservers]
www1.example.com
www2.example.com
[dbservers]
db0.example.com
db1.example.com
$ ssh-agent bash
$ ssh-add ~/.ssh/id_rsa
$ ansible all -m ping
# run as bruce
$ ansible all -m ping -u bruce
# run as bruce, sudoing to root
$ ansible all -m ping -u bruce --sudo
# run as bruce, sudoing to batman
$ ansible all -m ping -u bruce --sudo --sudo-user batman
# run a live command on all of your nodes
$ ansible all -a "/bin/echo hello"
$ ansible webservers -m service -a "name=httpd state=restarted"
groups are how we decide which hosts to manage
$ ansible all -m fetch -a "src=/etc/ntp.conf dest=tmp/"
edit files in tmp/*/etc/ntp.conf and then
$ ansible all -m copy -a \
"src=tmp/{{inventory_hostname}}/etc/ntp.conf dest=/etc/ntp.conf backup=yes"
$ ansible all -m service -a "name=ntp status=restarted"
You
Ansible
- hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
apt: pkg=apache2 state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/apache2/httpd.conf
notify:
- restart apache
- name: ensure apache is running
service: name=httpd state=started
handlers:
- name: restart apache
service: name=httpd state=restarted
$ ansible-playbook playbook.yml
$ ansible-playbook -i routers-inventory playbook.yml
$ ansible-playbook playbook.yml --list-tasks
$ ansible-playbook -i inventory platform-lamp.yml --list-tasks
playbook: platform-lamp.yml
play #1 (PROVISION - platform lamp):
provision php packages
provision apache packages
provision mysql-client
provision mysql-server
$ ansible-playbook playbook.yml --list-hosts
$ ansible-playbook -i inventory platform-lamp.yml --list-hosts
playbook: platform-lamp.yml
play #1 (PROVISION - platform lamp): host count=1
localhost
$ ansible-playbook playbook.yml --step
$ ansible-playbook -i inventory playbook.yml --step
PLAY [PROVISION - platform lamp] *****************************************
GATHERING FACTS **********************************************************
ok: [localhost]
Perform task: platform-lamp | provision php packages (y/n/c): y
An ansible Playbook is composed of 4 sections:
First of all we have to define in the playbook which target server or group we will run on:
- hosts: webservers
In the same section we can use the following options to customize the behaviors:
Then we usually define a number of variables to be re-used in the tasks during the run:
- hosts: webservers
vars:
ntpd_enabled: yes
apache_version: 2.6
motd_message: 'WARNING: service reserved to IT employees only'
or we can load even more data from other YAML or JSON files:
- hosts: webservers
vars_files:
- conf/common.yaml
- conf/webservers.yaml
and sometimes we need to ask for a variable value during the run:
- hosts: webservers
var_prompt:
- name: "https_passphrase"
prompt: "HTTPS Passphrase"
private: yes
we can use our own variables (and variables gathered by the setup module), everywhere in the playbooks and templates using the following syntaxes:
{{ variables_name }}
or
${ variables_name }
or
$variables_name
The tasks section will include the sequence of tasks we want to run on the target:
- hosts: webservers
...
tasks:
- name: install ntpd
apt: name=ntpd state=installed
- name: configure ntpd
copy: src=files/ntpd.conf dest=/etc/ntpd.conf
name is just a documentation string, which will be then followed by the ansible module we want to run and its parameters.
The module parameters can be written as nested YAML attributes if it helps the readability:
- apt:
name: ntpd
state: installed
The handlers section use the same syntax of the tasks section, but they are not run unless called by tasks:
- hosts: webservers
...
tasks:
- name: update the ntpd config
copy: src=files/ntpd.conf dest=/etc/ntpd.conf
notify:
- restart ntpd
handlers:
- name: restart ntpd
service: name=ntpd state=restarted
Ansible Playbooks use Vars to parametrize tasks and templates.
Vars can be specified in various places and syntaxes:
$ ansible-playbook playbook.yml --extra-vars "db=N mail=Y" \
--extra-vars @filepath.yml
You can also pass variables into includes. We call this a "parameterized include".
tasks:
- {include: wordpress.yml, user: timmy,
ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }
- include: gpg-keys.yml
when: sysadmins.gpg_pub_keys
Facts are auto-collected info about the connected system (accessible as vars):
$ ansible -i inventory all -m setup
wpapp.ci.local | success >> {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"172.111.15.125"
],
"ansible_all_ipv6_addresses": [
"fe80::216:1eff:fe13:8b22"
],
"ansible_architecture": "x86_64",
"ansible_bios_date": "12/27/2007",
"ansible_bios_version": "1.1.0",
"ansible_cmdline": {
"ro": true,
"root": "/dev/md126"
},
...
Ansible use Jinja2 Templates
- template:
src: apache-webapp.conf.j2
dest: /etc/apache2/sites-available/mywebapp.conf
owner: www-data
group: www-data
mode: 0600
...
<Directory /srv/mywebapp>
Options FollowSymLinks
### NOTE: Limit needed to harden wordpress uploads dir using .htaccess
AllowOverride FileInfo Options Limit
Order allow,deny
Allow from all
Require all granted
{% if provision_env == "production" %}
AuthUserFile /var/lib/mywebapp/mywebapp.htpasswd
AuthName MYWEBAPP
AuthType Basic
require valid-user
{% endif %}
</Directory>
...
roles are ways of automatically loading certain vars_files, tasks, and handlers based on a known file structure
grouping content by roles also allows easy sharing of roles with other users.
Roles are just automation around "include" directives as described above, and really don't contain much additional magic beyond some improvements to search path handling for referenced files.
site.yml # master playbook
webservers.yml # playbook for webserver tier
dbservers.yml # playbook for dbserver tier
roles/
common/ # this hierarchy represents a "role"
tasks/ #
main.yml # <-- tasks file can include smaller files if warranted
handlers/ #
main.yml # <-- handlers file
templates/ # <-- files for use with the template resource
ntp.conf.j2 # <------- templates end in .j2
files/ #
bar.txt # <-- files for use with the copy resource
foo.sh # <-- script files for use with the script resource
vars/ #
main.yml # <-- variables associated with this role
defaults/ #
main.yml # <-- default lower priority variables for this role
meta/ #
main.yml # <-- role dependencies
hardening/
monitoring/
host_vars/
group_vars/
inventories/
production
staging
- hosts: webservers
roles:
- common
- webservers
This designates the following behaviors, for each role "x":
# Update repositories cache and install "foo" package
- apt: name=foo update_cache=yes
# Remove "foo" package
- apt: name=foo state=absent
# Install the package "foo"
- apt: name=foo state=present
# Install the version '1.00' of package "foo"
- apt: name=foo=1.00 state=present
$ ansible-doc apt
> APT
Manages `apt' packages (such as for Debian/Ubuntu).
Options (= is mandatory):
- cache_valid_time
If `update_cache' is specified and the last run is less or
equal than `cache_valid_time' seconds ago, the `update_cache'
gets skipped.
- deb
Path to a local .deb package file to install.
- default_release
Corresponds to the `-t' option for `apt' and sets pin
priorities
ansible-doc apt
Docker Compose (previously named fig) is a fast and simple orchestration tool, which helps to manage a group of containers as a whole.
It's especially useful as minimal orchestration tool during development and testing.
As an example is pretty common for a project which use docker to contain the app we're developing, to have a Dockerfile (used to build the app server) and a docker-compose.yml file which describe the entire architecture:
myapp:
build: ./
ports:
- "8080:8080"
volumes:
- ./src:/srv/app
environment:
- APP_PORT: 8080
- APP_ENV: development
- APP_DB_HOST: myappdb
links:
- db:myappdb
db:
image: "postgresql:9.4"
When a provisioning is too complex to be managed directly in a Dockerfile using the docker building commands, we can switch to use Docker and Ansible together:
FROM ansible/ubuntu14.04-ansible:stable
MAINTAINER Luca Greco <luca.greco@alcacoop.it>
# import provisioning assets
ADD ansible-playbook /etc/ansible/playbooks/devbox-base
# run provisioning
RUN apt-get update && \
ansible-playbook /etc/ansible/playbooks/devbox-base/site.yml -c local && \
ansible-playbook /etc/ansible/playbooks/devbox-base/cleanup.yml -c local
# base cli environment vars
ENV TERM "xterm-256color"
ENV LC_ALL "C"
ENV SHELL "/bin/bash"
CMD ["/bin/bash", "-l"]