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.

 

 



Jak na Terraform pro Azure služby, které jsou zatím jen v Preview Automatizace
Datové hřiště - zpracování proudu událostí s Azure Stream Analytics nakopnuté Terraformem Automatizace
Datové hřiště - generátory fake dat do kontejneru zabalené Terraformem v Azure nahozené Automatizace
Datové hřiště - jak si hrát s daty bez sebemenšího kliknutí s Terraform a Azure Automatizace
Federace tokenů GitHub Actions s Azure Active Directory pro přístup z vaší CI/CD do Azure bez hesel Automatizace