Mostrando entradas con la etiqueta ansible. Mostrar todas las entradas
Mostrando entradas con la etiqueta ansible. Mostrar todas las entradas

domingo, mayo 27, 2018

Usar Ansible para instalar Python

Los módulos de Ansible están escritos en Python. Por lo tanto, si la máquina destino no tiene Python instalado, no se puede ejecutar hasta que no se instale. Ansible tiene la opción raw para poder ejecutar un comando usando la conexión ssh directamente, saltándose todo el sistema de módulos. El uso más normal de este comando es instalar Python para poder usar posteriormente el sistema de módulos. Un ejemplo de uso, es poder usar Ansible para provisionar una Ubuntu 16.04 instalada en EC2, ya que por defecto la ami que se usa no tiene Python. Si incluimos en nuestro playbook una tarea de este tipo:

  tasks:
    - name: install python 2
      raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
      become: yes

Sino encuentra el ejecutable de Python, nos instalará el mismo. Luego, podremos usar los módulos de Ansible como siempre. Un ejemplo completo de un playbook que despliega una máquina en EC2 usando la AMI de Ubuntu 16.04 y se instala python en la misma para poder usar el resto de los módulos. Las variables se pueden definir en un fichero de configuración y pasarlas con -e a ansible-playbook.

- name: Deploy EC2 machine and install python if no exists
  hosts: 127.0.0.1
  connection: local
  gather_facts: False
  tasks:
    - name: Create the security group
      ec2_group:
        name: "{{ec2securitygroup}}"
        description: Access
        rules:
          - proto: tcp
            ports:
              - 22
            cidr_ip: 0.0.0.0/0

    - name: Deploy the machine
      ec2:
        region: "{{ec2region}}"
        key_name: "{{ec2sshkey}}"
        instance_type: "{{ec2instancetype}}"
        image: "{{ec2ami}}"
        wait: yes
        count: 1
        group: "{{ec2securitygroup}}"
        vpc_subnet_id: "{{ec2subnetid}}"
      register: ec2

    - name: If EC2 exists, register its IP
      add_host:
         hostname: "{{item.public_ip}}"
         groups:
          - deployedMachines
      with_items: "{{ec2.instances|default([])}}"

    - name: Wait for SSH to come up
      wait_for:
          host: "{{item}}"
          port: 22
          delay: 10
          timeout: 60
          state: started
      with_items: "{{groups[\"deployedMachines\"]|default([])}}"


- name: Configure instance
  hosts: deployedMachines
  user: "{{remoteuser}}"
  gather_facts: False
  tasks:
    - name: install python 2
      raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
      become: yes
    - name: Update the system
      shell: apt-get update && apt-get -y install  python-apt
      become: yes


Las variables a definir en el fichero yaml de configuración son:

ec2machinename: Nombre de la máquina
ec2sshkey: Clave de la instancia que se va a usar.
ec2instancetype: Tipo de instancia
ec2region: Región de Amazon
ec2ami: "ami-43a15f3e" # Esta es la AMI de Ubuntu en us-east-1
ec2subnetid: ID de la subred donde se va a desplegar
ec2securitygroup: Nombre grupo de seguridad
remoteuser: "ubuntu" # Por defecto, el usuario de las AMI es este

sábado, marzo 24, 2018

Desplegar máquinas en Amazon EC2 con ayuda de Ansible

Para hacer ciertas tareas, despliego máquinas en la nube EC2 de Amazon. Con ayuda de varios scripts y ssh, desde mi ordenador voy realizando trabajos necesito. Cuando acabo, destruyo las máquinas virtuales para que no tenga coste para la empresa.

Llevaba un tiempo pensando como automatizar de una manera sencilla esta operación. Sé que en Amazon se puede hacer algo similar con CloudFormation e incluso llamar a ese API de Amazon desde scripts escritos en Python, pero quería ver si fuera posible hacer algo más sencillo y con lo que tuviese alguna experiencia previa.

Conozco la herramienta de automatización Ansible de otro proyecto en el que estuve trabajando hace dos años. Hacía bastante tiempo que no la usaba, pero letendo su documentación he visto que tiene dos módulos,ec2 y ec2_instance_facts que me podían servir para lo que quería.

Mi idea era desarrollar un playbook de Ansible, que ejecutándolo en el ordenador local, se encargara de:

  • Desplegar una determinada instancia de EC2, si no hay ninguna instancia con un nombre dado.
  • Esta instancia se desplegará en una red asociada a una VPC de Amazon determinada, con una clave ssh que se usará para acceder a la misma y con un grupo de seguridad asociado a dicho VPC.
  • La imagen AMI que vamos a usar corresponde a Ubuntu 16.04.3 LTS (xenial), usando virtualización de tipo hvm.
  • Una vez desplegada la instancia, el playbook entrará en el sistema donde realizará las siguientes operaciones:
    • Instarlar el git
    • Bajarse el JDK de Java e instalarlo.
    • Bajarse el maven e instalarlo.
    • Copiar desde el ordenador local la configuración de maven, el fichero situado en $HOME/.m2/settings.xml , al ordenador remoto.
    • Clonar varios repositorios desde el git.

Instalar Ansible

Para instalar ansible, lo más cómodo es usar entorno virtual de Python gestionado por un programa como virtualenv. Si se tiene este programa instalado hay que hacer lo siguiente para crear el entorno virtual e instalar dentro del mismo Ansible:

$virtualenv vansible
$source vansible/bin/activate
(vansible)$pip install ansible
(vansible)$ansible --version

El script

Al final de esta entrada podéis encontrar el script que he desarrollado para que se despliegue la máquina y me la deje configurada de acuerdo a lo que quiero. El formato del fichero que utiliza los playbooks de Ansible es yaml. Es un poco peculiar como podéis y el parser de ansible a veces hace cosas raras leyéndolo, con lo cual hay que tener bastante cuidado con lo que se pone.

Otra particularidad es que hago uso de los comandos ssh-agent y ssh-keyscan, así como la del reenvío del agente ssh sobre el túnel ssh para no tener que subir claves privadas a la máquina de Amazon.

Tampoco soy un usuario muy hardcode de Ansible. En cierta manera, quería tener algo que me ayudara a realizar mis tareas sin perder demasiado tiempo en la herramienta que fuera a usar. El script admite varias mejoras:

  • Comprobar antes de bajarse el JDK o maven que ya están instalados en la máquina.
  • Usar algunas opciones ssh para evitar tener que añadir al known_hosts las claves públicas de las máquinas remotas.
  • Ver si hay alguna construcción en las expresiones jinja2 que se usan, especialmente aquellas que usan la construccion expresion | default.
  • Comprobar si existe los repositorios git antes de bajarlos.

Para ejecutar el script:

(vansible)$ansible-playbook script.yml

Código fuente
- hosts: 127.0.0.1
  connection: local
  gather_facts: False
  vars:
    ec2machinename: <<ec2 instance name>>
    ec2sshkey: <<ssh key name to use in the instance>>
    ec2instancetype: "t2.xlarge" # instance type
    ec2region: "us-east-1"  # Amazon region
    ec2ami: "ami-43a15f3e" # AMI (this is the ubuntu one)
    ec2subnetid: <<subnet id>>
    ec2securitygroup: <<security group id>>
    privatekeypath: "~/.ssh/drizzt.key.pem"
    gitkeypath: "~/.ssh/id_drizzt_bitbucket"
    mavenconfigpath: "~/.m2/settings.xml"
    sshoptions: "-oForwardAgent=yes -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null"
    rcmd:
      - "sudo apt-get update"
      - "sudo apt-get -y install git-core"
      - "curl -L -O -# -H  'Cookie: oraclelicense=accept-securebackup-cookie' http://download.oracle.com/otn-pub/java/jdk/8u162-b12/0da788060d494f5095bf8624735fa2f1/jdk-8u162-linux-x64.tar.gz"
      - sudo mkdir -p /usr/local/java/
      - sudo tar zxvf jdk-8u162-linux-x64.tar.gz -C /usr/local/java/
      - sudo ln -sf /usr/local/java/jdk1.8.0_162/bin/* /usr/local/bin/
      - curl   -L -O -#  https://archive.apache.org/dist/maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.tar.gz
      - curl   -L -O -#  https://archive.apache.org/dist/maven/maven-3/3.3.3/binaries/apache-maven-3.3.3-bin.tar.gz.asc
      - sudo tar zxvf apache-maven-3.3.3-bin.tar.gz -C /usr/local/java
      - sudo ln -sf /usr/local/java/apache-maven-3.3.3/bin/* /usr/local/bin/
      - mkdir -p /home/ubuntu/{.m2,prog/atlas}
    remoterepos:
      - git@bitbucket.org:repo1/repo1.git
      - git@bitbucket.org:repo2/repo2.git



  tasks:
   - ec2_instance_facts:
      filters:
         "tag:Name": "{{ec2machinename}}"
         "instance-state-name": ["pending","running"]
     register: result
   - ec2:
      region: "{{ec2region}}"
      key_name: "{{ec2sshkey}}"
      instance_type: "{{ec2instancetype}}"
      image: "{{ec2ami}}"
      wait: yes
      count: 1
      group: "{{ec2securitygroup}}"
      vpc_subnet_id: "{{ec2subnetid}}"
      instance_tags:
        "Name": "{{ec2machinename}}"
     when: (result["instances"]|length == 0)
     register: ec2
   - name: Wait for SSH to come up
     wait_for:
        host: "{{ item.public_ip_address| default(item.public_ip) }}"
        port: 22
        delay: 10
        timeout: 60
        state: started
     with_items: "{{ ec2.instances |default(result.instances) }}"

   - name: Add the keys to ~/.ssh/known_host
     shell: ssh-keyscan -H {{item.public_ip_address|default(item.public_ip)}}  >> ~/.ssh/known_hosts
     with_items: "{{ ec2.instances |default(result.instances) }}"

   - name: Install git, java
     shell: ssh -i "{{privatekeypath}}" -l ubuntu "{{item[0].public_ip_address|default(item[0].public_ip)}}" "{{item[1]}}"
     with_nested:
      - "{{ ec2.instances |default(result.instances) }}"
      - "{{rcmd}}"
   - name: Copy the Maven config
     shell: "scp -i {{privatekeypath}} {{mavenconfigpath}} ubuntu@{{item.public_ip_address|default(item.public_ip)}}:.m2/"
     with_items: "{{ ec2.instances |default(result.instances) }}"
   - name: Add remote git key to ssh-agent
     shell: "ssh-add {{gitkeypath}}"



   - name: Copy the remote repositories
     shell: ssh -oForwardAgent=yes -i "{{privatekeypath}}" "ubuntu@{{item[0].public_ip_address|default(item[0].public_ip)}}" "cd /home/ubuntu/prog/atlas;env GIT_SSH_COMMAND='ssh {{sshoptions}}' git clone {{item[1]}}"
     with_nested:
      - "{{ ec2.instances |default(result.instances) }}"
      - "{{remoterepos}}"