Jak z Red Hat Ansible automatizovat Azure i vaše aplikační prostředí
Z rodiny configuration management nástrojů jako jsou Puppet, Chef, Ansible a Salt Stack mám Ansible osobně nejraději - nebojím se totiž mezer, takže YAML a Python mi vyhovují (pokud jste typ složených závorek či jiných na odsazení nezávislých zápisech, asi půjdete spíše do Chef a JSON :) ). Víte ale, že z Red Hat Ansible můžete nejen automatizovat instalaci aplikačního prostředí i aplikací a udržovat OS, ale i provisionovat zdroje v Azure? Podívejme se jak na to.
Připravíme si prostředí
Co budete potřebovat? Já použiji Linux stroj, na něm Python, Ansible, dále potřebujeme Azure Python SDK. Pro zjednodušení jsem si připravil Docker image, abych to na svém Windows 10 notebooku mohl řešit jednoduše a efektivně (s Docker for Windows). Spouštím ho tak, aby trvale běžel (proces tail) a pak teprve vyvolám bash (takže když se odpojím, kontejner jede dál).
docker run --name ansible -d tkubica/ansibleazure tail -f /dev/null docker exec -it ansible bash
Dnes budu přes Ansible ovládat pouze Azure a Linux VMka v něm, pokud ale budete Ansiblem instalovat komponenty ve Windows OS (což si zkusíme někdy příště), budete v případě Linux hostitele potřebovat ještě pár dalších dependencies (WinRM pro Python, Kerberos).
Jak se Ansible napojí do vaší Azure subscription? Možností je několik, ale pokud stejně jako já máte dvoufaktorové ověření, je rozhodně nejjednoduší vytvořit service principála. Nejrychlejší cesta k tomu vede přes Azure CLI, ale můžete použít i GUI nebo PowerShell.
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/vase-subscription-id"
{
"appId": "blabla1",
"displayName": "azure-cli-2017-02-19-08-27-11",
"name": "http://azure-cli-2017-02-19-08-27-11",
"password": "blabla2",
"tenant": "blabla3"
}
Není vhodné mít jakákoli hesla přímo součástí playbooků, tedy samotných Ansible předpisů. Důvodem je, že ty chcete obvykle dát do version control systému a sdílet je, což je opak toho, co chcete udělat s hesly. Můžete zvolit jiné soubory, zapsat to do Ansible inventáře, já ale nejraději environmentální proměnné. V rámci mé session tedy stačí udělat tohle:
export AZURE_TENANT=blabla3 export AZURE_CLIENT_ID=blabla1 export AZURE_SECRET=blabla2 export AZURE_SUBSCRIPTION_ID=vase-subscription-is
Vytváříme VM v Azure z Ansible
Všechny příklady jsem uložil na GitHub: https://github.com/tkubica12/ansible-azure/tree/master
Náš playbook (infrastrukturní předpis) je v souboru web-servers.yaml (jak uvidíte později používám z něj ještě vnořený web-server.yaml playbook). Prohlédněme si ho postupně.
- name: Ensure we have one Linux server with tags apache and nginx
hosts: localhost
connection: local
gather_facts: False
vars:
group: "{{ lookup('env', 'AZURE_RESOURCE_GROUPS') }}"
vms:
- name: mujApache1
tag: apache
- name: mujApache2
tag: apache
- name: mujApache3
tag: apache
- name: mujNGINX
tag: nginx
Tohle je popis a vstupní podmínky playbooku. Zaměříme se na vars, tedy proměnné (v praxi bych je asi dal do samostatného souboru, ale nechtěl jsem to komplikovat). Group použijeme později jako název Resource Group v Azure a v playbooku jej beru z proměnné prostředí (místo přímého zápisu zde... ostatně při spuštění můžete také všechno přepsat parametrem -e group=mojeskupina). Dále jsem si vymyslel vms, což je pole objektů, kde každý objekt reprezentuje jednu VM a má atributy name a tag. Tohle použijeme později ve skriptu. Proč i tag? Jde o metadata, kteráuložíme v Azure a později je použijeme k rozhodnutí, co na VM nainstalovat.
Podívejme se na sekci tasků.
tasks:
- name: Ensure resource group exists
azure_rm_resourcegroup:
name: "{{ group }}"
location: westeurope
- name: Ensure virtual network exists
azure_rm_virtualnetwork:
resource_group: "{{ group }}"
name: mynet
address_prefixes: "10.10.0.0/16"
- name: Ensure subnet exists
azure_rm_subnet:
name: mysub
virtual_network_name: mynet
resource_group: "{{ group }}"
address_prefix_cidr: "10.10.1.0/24"
- name: Ensure Linux VMs exists
include: web-server.yaml
with_items: "{{ vms }}"
V prvních třech úkolech se ujistíme, že resource group existuje, že v ní je vnet a subnet tak, jak potřebuji. Čtvrtý krok je smyčka, která projde všechy objekty pole vms (tedy všechna požadovaná VMka) a pro každý vyvolá vnořený playbook web-server.yaml. V něm už se budou provádět úkony specifické pro samotné VM.
Tohle je web-server.yaml:
- name: Make sure VM exist
azure_rm_virtualmachine:
resource_group: "{{ group }}"
name: "{{ item.name }}"
vm_size: Standard_A0
admin_username: tomas
ssh_password_enabled: false
ssh_public_keys:
- path: /home/tomas/.ssh/authorized_keys
key_data: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFhm1FUhzt/9roX7SmT/dI+vkpyQVZp3Oo5HC23YkUVtpmTdHje5oBV0LMLBB1Q5oSNMCWiJpdfD4VxURC31yet4mQxX2DFYz8oEUh0Vpv+9YWwkEhyDy4AVmVKVoISo5rAsl3JLbcOkSqSO8FaEfO5KIIeJXB6yGI3UQOoL1owMR9STEnI2TGPZzvk/BdRE73gJxqqY0joyPSWOMAQ75Xr9ddWHul+v//hKjibFuQF9AFzaEwNbW5HxDsQj8gvdG/5d6mt66SfaY+UWkKldM4vRiZ1w11WlyxRJn5yZNTeOxIYU4WLrDtvlBklCMgB7oF0QfiqahauOEo6m5Di2Ex"
os_type: Linux
image:
offer: UbuntuServer
publisher: Canonical
sku: "14.04.4-LTS"
version: latest
open_ports:
- 22
- 80
tags:
ansiblegroup: "{{ item.tag }}"
register: mojelinuxvm
- name: Print IP
debug: msg="Access your Linux VM at {{ mojelinuxvm.ansible_facts.azure_vm.properties.networkProfile.networkInterfaces[0].properties.ipConfigurations[0].properties.publicIPAddress.properties.ipAddress}}"
První úloha vytvoří VM o konkrétní velikosti, jménu, SSH klíči a OS. Definuji otevřené porty (NSG) a přiřazuji tag (všimněte si, jak Ansible umí dosazovat proměnné, ať už přímo jednoduše group nebo to z konkrétní iteraci smyčky nad vms). Tato úloha vrátí obrovský JSON výstupů, který registruji do proměnné mojelinuxvm. V druhé úloze právě z toho výstupu vytisknu jednu jedinou věc - veřejnou IP adresu (pokud chcete pro studijní účely vidět všechno, dejte debug: var=mojelinuxvm.
Vyzkoušejme si automatizovat poskládání infrastruktury v Azure
Otestujeme si teď tento playbook. Nezapomeňte vyexportovat jméno groupy a jedeme.
export AZURE_RESOURCE_GROUPS=ansible
ansible-playbook web-servers.yaml
[WARNING]: Host file not found: /etc/ansible/hosts
[WARNING]: provided hosts list is empty, only localhost is available
PLAY [Ensure we have one Linux server with tags apache and nginx] **************
TASK [Ensure resource group exists] ********************************************
changed: [localhost]
TASK [Ensure virtual network exists] *******************************************
changed: [localhost]
TASK [Ensure subnet exists] ****************************************************
changed: [localhost]
TASK [Ensure Linux VMs exists] *************************************************
included: /ansible-azure/web-server.yaml for localhost
included: /ansible-azure/web-server.yaml for localhost
included: /ansible-azure/web-server.yaml for localhost
included: /ansible-azure/web-server.yaml for localhost
TASK [Make sure VM exist] ******************************************************
changed: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 13.94.130.144"
}
TASK [Make sure VM exist] ******************************************************
changed: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 52.166.204.184"
}
TASK [Make sure VM exist] ******************************************************
changed: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 13.81.113.13"
}
TASK [Make sure VM exist] ******************************************************
changed: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 40.118.101.66"
}
PLAY RECAP *********************************************************************
localhost : ok=15 changed=7 unreachable=0 failed=0
Ověřte v GUI, že se toho docela hodně stalo.
V mém případě bylo u všech kroků napsáno changed, tedy Ansible došel k závěru, že skutečný stav neodpovídá požadovanému a musel zasáhnout (což je logické, moje resource group vůbec neexistovala). Udělejme teď to, že jednu (pouze jednu) z VM smažu ručně z portálu a spustím playbook znova. Co myslíte, že se bude dít? Všechno pojede mnohem rychleji a v okamžiku, kdy Ansible zjistí rozpor požadovaného a skutečného stavu, vyřeší to.
ansible-playbook web-servers.yaml
[WARNING]: Host file not found: /etc/ansible/hosts
[WARNING]: provided hosts list is empty, only localhost is available
PLAY [Ensure we have one Linux server with tags apache and nginx] **************
TASK [Ensure resource group exists] ********************************************
ok: [localhost]
TASK [Ensure virtual network exists] *******************************************
ok: [localhost]
TASK [Ensure subnet exists] ****************************************************
ok: [localhost]
TASK [Ensure Linux VMs exists] *************************************************
included: /ansible-azure/web-server.yaml for localhost
included: /ansible-azure/web-server.yaml for localhost
included: /ansible-azure/web-server.yaml for localhost
included: /ansible-azure/web-server.yaml for localhost
TASK [Make sure VM exist] ******************************************************
changed: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 13.94.130.144"
}
TASK [Make sure VM exist] ******************************************************
ok: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 52.166.204.184"
}
TASK [Make sure VM exist] ******************************************************
ok: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 13.81.113.13"
}
TASK [Make sure VM exist] ******************************************************
ok: [localhost]
TASK [Print IP] ****************************************************************
ok: [localhost] => {
"msg": "Access your Linux VM at 40.118.101.66"
}
PLAY RECAP *********************************************************************
localhost : ok=15 changed=1 unreachable=0 failed=0
Konfigurace OS a dynamický Azure inventář
Jak už jsem říkal - Ansible mám rád a mám k tomu dva hlavní důvody. První je ten, že playbooky jsou v YAMLu (já se mezer nebojím), který je lidsky čitelný a neprogramátory neděsí (nebo alespoň méně, než JSON nebo Chef cookook v Ruby). Ten druhý je ale důležitější - Ansible nevyžaduje žádného agenta v OS. Nemusíte řešit, jak ho tam nainstalovat, jak je bezpečně propojit a tohle celé automatizovat (takže odpadají komplikace typu Chef extension v Azure nebo Chef Knife pro provisioning). Stačí vám jen SSH přístup a Python v OS (standardní výbava dnes už každé Linux distribuce) - v případě Windows se používá WinRM, ale o tom jindy.
Co tedy potřebujeme je v zásadě seznam IP adres rozřazených do skupin podle typu serveru (pokud vás teď napadlo co když VM v Azure nebude mít public IP a vy nechcete mít Ansible server nainstalovaný v Azure, tak vězte, že je možné použít jednu VM ve stejném VNet jako SSH proxy). To je co chceme. Na základě výstupu předchozího tasku si to můžu opsat a vytvořit ručně. Ale co by to pak bylo za automatizaci. Ansible podporuje dynamický inventář pro Azure. V zásadě jde o "očuchávátko". Namíříte na vaši subscription či resource group a ono to stáhne názvy VM, přihlašovací jména do nich a z tagů vytvoří automaticky různé skupiny. V našem případě tedy rozhodně získáme skupinu ansiblegroup_apache a ansiblegroup_nginx a to je přesně to, co nám stačí pro instalaci správného web serveru na tu kterou VM v Azure.
Takhle vypadá můj playbook hydrate.yaml (slovo vychází z termínu, kdy prázdnou VM jako květináč ze semínkem zalijete a ono vyroste to, co potřebujete).
- name: Make sure apache is installed on correct system
hosts: ansiblegroup_apache
gather_facts: no
remote_user: tomas
become: true
tasks:
- name: Make sure Apache2 is installed
apt: name=apache2 update_cache=yes state=latest
- name: Enabled mod_rewrite
apache2_module: name=rewrite state=present
notify:
- Restart apache2
- name: Make sure Apache2 is started
service:
name: apache2
state: started
handlers:
- name: Restart apache2
service:
name: apache2
state: restarted
- name: Make sure nginx is installed on correct system
hosts: ansiblegroup_nginx
gather_facts: no
remote_user: tomas
become: true
tasks:
- name: Make sure nginx is installed
apt: name=nginx update_cache=yes state=latest
- name: Make sure nginx is started
service:
name: nginx
state: started
Co v něm dělám? Jsou to dva playbooky v jednom souboru - jeden se spouští na serverech, které jsou ve skupině ansiblegroup_apache (tedy tag, který jsme jim dali na začátku) a druhé pro ansiblegroup_nginx. Apache nainstalujeme package managerem (pokud je to potřeba), zapneme apache module (je-li to potřeba a v takovém případě ještě handlerem zrestartujeme službu) a ujistíme se, že je služba spuštěná. U NGINX postupuji podobně, ale jen nainstaluji a ujistím se, že je služba nahoře. Je to jen drobná ukázka - v praxi bychom provedli veškerá potřebná nastavení, rozjeli virtuální servery, vytvořili certifikáty a zapnuli HTTPS, nakopírovali naší webovou aplikaci z Gitu a tak podobně.
Vyzkoušíme si to. Všimněte si přepínače -i, který definuje inventář. Ale místo statického souboru je to azure_rm.py skript, který Ansible oficiálně vytvořil pro automatickou inventarizaci Azure (autentizace do Azure je stejná jako výše, takže vámi vyexportované údaje stačí). Ještě jedna věc - očekávám, že vaše stanice se může k VM připojit bez hesla (tzn. privátní klíč je v mašině). Můžete použít i různé jiné postupy, ale SSH klíče a password-less autentizace jsou rozhodně best practice. Ještě vypneme cacheování public klíčů, aby nás to neobtěžovalo (správný postup by byl veřejné klíče cílových serverů nějakým způsobem přenést k vám a uložit do autorizovaných klíčů).
export ANSIBLE_HOST_KEY_CHECKING=false ansible-playbook -i "azure_rm.py" hydrate.yaml PLAY [Make sure apache is installed on correct system] ************************* TASK [Make sure Apache2 is installed] ****************************************** changed: [mujApache2] changed: [mujApache1] changed: [mujApache3] TASK [Enabled mod_rewrite] ***************************************************** changed: [mujApache1] changed: [mujApache2] changed: [mujApache3] TASK [Make sure Apache2 is started] ******************************************** ok: [mujApache1] ok: [mujApache2] ok: [mujApache3] RUNNING HANDLER [Restart apache2] ********************************************** changed: [mujApache2] changed: [mujApache1] changed: [mujApache3] PLAY [Make sure nginx is installed on correct system] ************************** TASK [Make sure nginx is installed] ******************************************** changed: [mujNGINX] TASK [Make sure nginx is started] ********************************************** ok: [mujNGINX] PLAY RECAP ********************************************************************* mujApache1 : ok=4 changed=3 unreachable=0 failed=0 mujApache2 : ok=4 changed=3 unreachable=0 failed=0 mujApache3 : ok=4 changed=3 unreachable=0 failed=0 mujNGINX : ok=2 changed=1 unreachable=0 failed=0
Klidně si teď vyzkoušejte, že servery skutečně reagují.
Red Hat Ansible je sice původně nástroj pro configuration management, tedy správu "vnitřku vašich VM", ale v poslední době se velmi rozšířil co do možností automatizovat i hodně dalších věcí včetně provisioningu Azure infrastruktury. Někdo půjde cestou ARM šablon (pokud automatizujete jen Azure, jasná volba) a k nim nějaký configuration management. Jiný bude preferovat univerzálnější provisioning infrastruktury různých typu (Azure, VMware, AWS, OpenStack) a půjde do Terraform (to by byla moje volba pro takový scénář) nebo CloudForms a k nim configuration management. Třetí přístup je vzít configuration management, třeba Ansible, a tím udělat všechno od infra až po běžící aplikaci. Volba je na vás.
