Configuración de Cloud-init para Provisión de Servidores

Cloud-init es una herramienta potente para automatizar la configuración inicial de servidores en los principales proveedores en la nube. Proporciona una forma estándar de configurar instancias en el primer arranque usando sintaxis YAML cloud-config, permitiendo provisión de infraestructura como código sin herramientas externas. Esta guía cubre conceptos fundamentales de cloud-config, especificación de datos de usuario, instalación de paquetes, gestión de usuarios, scripts personalizados, provisión de archivos y características avanzadas.

Tabla de Contenidos

  1. Descripción General de Cloud-init
  2. Sintaxis Cloud-Config
  3. Instalación de Paquetes
  4. Gestión de Usuarios y Grupos
  5. Ejecución de Comandos
  6. Gestión de Archivos
  7. Configuración de Red
  8. Llamadas de Cloud-init
  9. Depuración de Cloud-init
  10. Conclusión

Descripción General de Cloud-init

Cloud-init es el estándar de la industria para inicialización de instancias en la nube. Se ejecuta en el primer arranque con privilegios de root, lo que lo hace perfecto para configuración inicial del sistema, instalación de paquetes, creación de usuarios y tareas de configuración personalizadas.

Beneficios clave:

  • Estándar en Múltiples Proveedores: Funciona en AWS, Azure, GCP, DigitalOcean, Linode
  • Sintaxis YAML Simple: Cloud-config proporciona configuración declarativa legible
  • Arranque Rápido: Se configura en el primer arranque, no requiere software adicional
  • Repetible: La misma configuración produce resultados consistentes cada vez
  • Extensible: Soporte para scripts personalizados y módulos
  • Listo para Integración: Funciona con Terraform, CloudFormation, Ansible

Flujo de trabajo de Cloud-init:

Lanzamiento de Instancia
    ↓
Agente Cloud-init Inicia (root)
    ↓
Obtener Datos de Usuario
    ↓
Analizar Cloud-Config
    ↓
Ejecutar Pasos de Inicialización
    │
    ├── Módulo: Instalar Paquetes
    ├── Módulo: Crear Usuarios
    ├── Módulo: Escribir Archivos
    ├── Módulo: Ejecutar Comandos
    └── Módulo: Configurar Servicios
    ↓
Inicialización Completada

Etapas de Cloud-init:

  • Generator: Genera servicios adicionales si es necesario
  • Local: Configuración temprana de red, montar sistemas de archivos
  • Network: Redes completamente disponibles, ejecutar tareas dependientes de red
  • Config: Configuración general
  • Final: Última etapa, ejecutar personalización final

Sintaxis Cloud-Config

Cloud-config utiliza formato YAML comenzando con #cloud-config.

Estructura básica:

#cloud-config

# Comments start with #
package_update: true
package_upgrade: true

packages:
  - nginx
  - curl
  - wget

runcmd:
  - systemctl start nginx
  - systemctl enable nginx

write_files:
  - path: /etc/nginx/sites-available/default
    content: |
      server {
        listen 80 default_server;
        server_name _;
        location / {
          return 200 "Hello from $(hostname)";
        }
      }

Conversiones de tipos de datos:

#cloud-config

# String
region: us-east-1

# Number
port: 8080

# Boolean
enabled: true
disabled: false

# List
packages:
  - nginx
  - curl

# Dictionary/Map
environment:
  APP_ENV: production
  LOG_LEVEL: debug

# Multiline string
config: |
  This is a multiline
  configuration string
  that preserves formatting

Instalación de Paquetes

Gestiona paquetes fácilmente con cloud-init.

Gestión de paquetes básica:

#cloud-config

# Update package index
package_update: true

# Upgrade existing packages
package_upgrade: true

# Upgrade distribution packages
distro_upgrade: true

# Install packages
packages:
  - nginx
  - curl
  - wget
  - htop
  - git
  - python3-pip

# Package reboot behavior
package_reboot_if_required: true

# Configure package manager
apt:
  conf: |
    APT::Acquire::http::Timeout "60";
    APT::Acquire::ftp::Timeout "60";

Configuración de repositorio y PPA:

#cloud-config

# Add custom repository
apt:
  sources:
    docker.list:
      source: deb [arch=amd64] https://download.docker.com/linux/ubuntu $RELEASE stable
      keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88

# Or add PPA
apt_sources:
  - source: ppa:nginx/stable
  - source: ppa:deadsnakes/ppa

# After adding repos, install packages
packages:
  - docker-ce
  - docker-ce-cli
  - containerd.io
  - python3.9

Configuración de paquetes:

#cloud-config

packages:
  - nginx
  - mysql-server

# Configure packages
package_update: true
package_upgrade: true

# Prevent interactive prompts
debconf_selections: |
  mysql-server mysql-server/root_password password root
  mysql-server mysql-server/root_password_again password root
  mysql-server mysql-server/remove_databases_and_users boolean false
  mysql-server mysql-server/skip-install boolean false

Gestión de Usuarios y Grupos

Crea y configura cuentas de usuario.

Crear usuarios:

#cloud-config

users:
  - name: ubuntu
    groups: sudo
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1... [email protected]

  - name: app
    groups: appgroup
    shell: /usr/sbin/nologin
    home: /opt/app
    inactive: true

  - name: devops
    groups: [sudo, docker, wheel]
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1... [email protected]
      - ssh-rsa AAAAB3NzaC1... [email protected]

# Create groups
groups:
  - docker
  - appgroup: [app, ubuntu]

# Ensure default user disabled
disable_root: true

Opciones de configuración de usuario:

#cloud-config

users:
  - name: appuser
    # Set user ID
    uid: 1001
    
    # Set group ID
    gid: 1001
    
    # Home directory
    home: /opt/appuser
    
    # Shell
    shell: /bin/bash
    
    # Group membership
    groups: [sudo, adm]
    
    # Add to sudoers
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    
    # Don't create home directory
    no_create_home: false
    
    # Create home directory with specific permissions
    homedir: /home/appuser
    
    # SSH keys for login
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1... [email protected]
      - ssh-rsa AAAAB3NzaC1... [email protected]
    
    # Disable password login
    ssh_import_id: github:username
    
    # Lock account
    # lock_passwd: true
    
    # Set expiration
    expire: false
    
    # Create with password (usually avoided for security)
    # passwd: $1$salt$hashed_password

Gestión de grupos:

#cloud-config

groups:
  - docker
  - postgres
  - www-data: [ubuntu]
  - developers: [app, deploy, ubuntu]

users:
  - name: deploy
    groups: [developers, sudo]
    
  - name: app
    groups: developers

Ejecución de Comandos

Ejecuta scripts y comandos durante la inicialización.

Ejecución de comandos básica:

#cloud-config

# Run commands at final stage
runcmd:
  - /opt/bin/setup.sh
  - systemctl start nginx
  - systemctl enable nginx
  - echo "Server configured" | mail -s "Server Ready" [email protected]

# Run commands conditionally by list index
# First element runs before second, etc.
runcmd:
  - ['sh', '-c', 'echo "$HOSTNAME is up" > /tmp/status.txt']
  - systemctl restart nginx

Redirección de salida de comandos:

#cloud-config

runcmd:
  # Redirect to file
  - 'echo "Configuration log" > /var/log/init-log.txt'
  
  # Append to file
  - 'echo "Additional info" >> /var/log/init-log.txt'
  
  # Suppress output
  - 'systemctl start nginx > /dev/null 2>&1'
  
  # Log with timestamp
  - 'echo "$(date): Server configured" >> /var/log/cloud-init-custom.log'

Bootcmd para ejecución temprana:

#cloud-config

# Run commands at boot (before packages installed)
bootcmd:
  - 'modprobe zfs'
  - 'mkfs -t ext4 /dev/sdb1'
  - 'mkdir -p /mnt/data'
  - 'mount /dev/sdb1 /mnt/data'

# Then install packages
packages:
  - nginx
  - curl

# Then run runcmd
runcmd:
  - systemctl start nginx

Scripts personalizados:

#cloud-config

packages:
  - python3

# Write custom script
write_files:
  - path: /opt/setup.py
    permissions: '0755'
    owner: root:root
    content: |
      #!/usr/bin/env python3
      import subprocess
      import logging
      
      logging.basicConfig(level=logging.INFO)
      logger = logging.getLogger(__name__)
      
      logger.info("Running custom setup")
      subprocess.run(['apt-get', 'install', '-y', 'nginx'])
      subprocess.run(['systemctl', 'start', 'nginx'])

runcmd:
  - /opt/setup.py

Gestión de Archivos

Crea y gestiona archivos durante la inicialización.

Escribir archivos:

#cloud-config

write_files:
  - path: /etc/app/config.yml
    owner: root:root
    permissions: '0644'
    content: |
      app:
        name: myapp
        port: 8080
        environment: production
        logging:
          level: info
          file: /var/log/app.log

  - path: /opt/app/run.sh
    owner: app:app
    permissions: '0755'
    content: |
      #!/bin/bash
      cd /opt/app
      ./myapp --config /etc/app/config.yml

  - path: /etc/systemd/system/app.service
    owner: root:root
    permissions: '0644'
    content: |
      [Unit]
      Description=My Application
      After=network.target
      
      [Service]
      Type=simple
      User=app
      WorkingDirectory=/opt/app
      ExecStart=/opt/app/run.sh
      Restart=on-failure
      RestartSec=10
      
      [Install]
      WantedBy=multi-user.target

  - path: /etc/environment
    append: true
    content: |
      APP_ENV=production
      LOG_LEVEL=info

Codificación y compresión de archivos:

#cloud-config

write_files:
  - path: /tmp/data.txt
    encoding: base64
    content: |
      VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIGZpbGUK

  - path: /opt/archive.tar.gz
    encoding: gz+base64
    content: H4sIAIWLpGUC/wvJyCzRUMgsS8wpTVWoVkgsS8wpTlWqAOJcJaX5JUWlJRnpxUX5JUWlJRmpxUWlJRmlmSWpyZkpGalFqWWpqUVQUqsUQGpqamlyUWlpRmlyUmlySmpRVGpJZWlJZllKUWlpZkpSZWlJUXlKZklrMxM9RWfMoRhiEqUSR+LKnFmZlZoKkSzhTLBqJTK0UBMsI0vMBb6yVnFJEkRhWhFgBTr3nMH0QzUSMqGCYHbPTi5JLEktLkksTs0rBsi55hUlJhWlKoEUlpQUl6ZmluQVpyZnlmSklmTkFpalJqakFhWnFpQWFqYWJAUVQyVkBUpgSmVOQWpJZUpiRWmmQWrDUsVKKJCOJCniBLSYC1EFWQ1sOQEhyAKG0o9KaLIFaMsRgO6y2oBl8yLmEzaLBqLqWvt9M27L+zJdmRpQMREONuA=

Copiar archivos desde origen:

#cloud-config

# Note: write_files copies content, not external files
# For copying from source during build, use provisioners in Packer
# or download during execution

write_files:
  - path: /opt/setup.sh
    permissions: '0755'
    content: |
      #!/bin/bash
      # Download configuration from S3
      aws s3 cp s3://config-bucket/app-config.yml /etc/app/config.yml
      
      # Clone application from GitHub
      git clone https://github.com/myorg/myapp /opt/app

Configuración de Red

Configura redes durante la inicialización.

Configuración de red:

#cloud-config

# Configure hostname
hostname: webserver-01
fqdn: webserver-01.example.com
prefer_fqdn_over_hostname: true

# Update /etc/hosts
manage_etc_hosts: true

# NTP configuration
ntp:
  enabled: true
  ntp_client: systemd-timesyncd
  servers:
    - 0.ubuntu.pool.ntp.org
    - 1.ubuntu.pool.ntp.org

# DNS configuration
resolv_conf:
  nameservers:
    - 8.8.8.8
    - 8.8.4.4
  searchdomains:
    - example.com

Configuración de red personalizada:

#cloud-config

# Wait for network to be ready
wait_for_network: true
wait_for_network_timeout: 60

# Disable IPv6
bootcmd:
  - 'echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf'
  - 'sysctl -p'

# Configure static IP (if not using DHCP)
write_files:
  - path: /etc/netplan/99-custom-ip.yaml
    content: |
      network:
        version: 2
        ethernets:
          eth0:
            dhcp4: false
            addresses:
              - 192.168.1.100/24
            gateway4: 192.168.1.1
            nameservers:
              addresses: [8.8.8.8, 8.8.4.4]

runcmd:
  - netplan apply

Llamadas de Cloud-init

Envía notificaciones después de completar la configuración.

Llamada a inicio para notificar finalización:

#cloud-config

phone_home:
  url: http://example.com/callback?instance_id=$INSTANCE_ID&hostname=$HOSTNAME
  post: all
  tries: 10

# Or callback with webhook
phone_home:
  url: https://webhook.example.com/cloud-init
  post: all
  tries: 3
  retries: 5

Enviar notificación a monitoreo:

#cloud-config

packages:
  - curl

write_files:
  - path: /opt/notify.sh
    permissions: '0755'
    content: |
      #!/bin/bash
      INSTANCE_ID=$(ec2-metadata --instance-id | cut -d' ' -f2)
      HOSTNAME=$(hostname)
      
      # Send to monitoring system
      curl -X POST https://monitoring.example.com/api/events \
        -H "Content-Type: application/json" \
        -d "{
          \"event_type\": \"instance_ready\",
          \"instance_id\": \"$INSTANCE_ID\",
          \"hostname\": \"$HOSTNAME\",
          \"timestamp\": \"$(date -Iseconds)\"
        }"

runcmd:
  - /opt/notify.sh

Depuración de Cloud-init

Soluciona problemas de ejecución de cloud-init.

Ver registros de cloud-init:

# Main cloud-init log
cat /var/log/cloud-init-output.log

# Detailed log
cat /var/log/cloud-init.log

# System log
journalctl -u cloud-init -n 50

# Watch logs in real-time
tail -f /var/log/cloud-init.log

Verificar estado de cloud-init:

# Current status
cloud-init status

# Show all status
cloud-init status --long

# Check if running
cloud-init status | grep -q "running"

# Show available modules
cloud-init modules --list-sources

Habilitar registro de depuración:

#cloud-config

# Enable debug output
debug: true

# Set log level
output: {all: '>> /var/log/cloud-init-custom.log'}

Ejecución manual para pruebas:

# Clean up cloud-init state
sudo cloud-init clean --logs --seed

# Re-run cloud-init
sudo cloud-init init
sudo cloud-init modules --mode=config
sudo cloud-init modules --mode=final

# Or single run
sudo cloud-init -d single --name=runcmd

Validar sintaxis cloud-config:

# Check cloud-config validity
cloud-init schema --config-file user-data.txt --annotate

# Validate specific module
cloud-init schema --config-file user-data.txt --annotate | grep runcmd

Conclusión

Cloud-init proporciona una forma estándar y simple de automatizar el aprovisionamiento de servidores en múltiples proveedores en la nube. Al dominar la sintaxis cloud-config, gestión de paquetes, creación de usuarios, ejecución de scripts y provisión de archivos, puedes eliminar la configuración manual de servidores y crear infraestructura reproducible. Combinado con herramientas de infraestructura como código como Terraform, cloud-init forma una base poderosa para implementación de infraestructura automatizada, consistente y escalable.