Pubblicato il 01/10/2023 da alnao nella categoria AWS

All’avvio di istanze EC2 è possibile indicare quale istruzioni eseguire, questo gruppo di istruzioni è chiamato User data, nonostante il nome possa essere forviante si tratta di un sistema di configurazione e controllo dell’avvio delle istanze, per ovvi motivi questo tipo di script può essere eseguito solo in istanze con sistema operativo GNU Linux ma non può essere usata con istanze con sistema operative MsWindows. Di default queste istruzioni vengono eseguite solo al primo avvio ma è possibile modificare la configurazione da console per eseguire lo script anche ogni riavvio come suggerito da una pagina del sito ufficiale. Ci sono due tipi di script: shell o cloud-init, il primo tipo viene usato per installare software o eseguire istruzione mentre il secondo viene usato quando c’è la necessità di gestire il collegamento tra l’istanza EC2 e altri servizi come CloudFormation, RDS. Un semplice esempio di uso di user-data è l’installazione di un web server LAMP con una piccola infrastuttura di webserver, database MySql e il motore Php. Nel caso di sistema operativo Linux con distruzione Amazon con il gestore di pacchetti yum:

#!/bin/bash
yum update -y
amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
yum install -y httpd mariadb-server
systemctl start httpd
systemctl enable httpd
usermod -a -G apache ec2-user
chown -R ec2-user:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php

Per verificare il corretto funzionamento degli script è possibile verificare i log scritti dal sistema nei due file dedicati:

/var/log/cloud-init.log
/var/log/cloud-init-output.log

e alcuni sotto-servizi del Cloud scrivono i corrispettivi files di log nella cartella specifica:

/var/log/amazon/

Il tipo cloud-init configura aspetti specifici di una nuova istanza Amazon Linux al momento del lancio come la configurazione del file delle chiavi private .ssh/authorized_keys. Le direttive utente cloud-init possono essere passate a un’istanza all’avvio nello stesso modo in cui viene passato uno script ma con una sintassi molto diversa: la prima riga deve iniziare con

#cloud-config

così da indicare il tipo di script, un esempio completo corrispondente allo script per l’installazione di un server LAMP:

#cloud-config
repo_update: true
repo_upgrade: all
packages:
- httpd
- mariadb-server
runcmd:
- [ sh, -c, "amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2" ]
- systemctl start httpd
- sudo systemctl enable httpd
- [ sh, -c, "usermod -a -G apache ec2-user" ]
- [ sh, -c, "chown -R ec2-user:apache /var/www" ]
- chmod 2775 /var/www
- [ find, /var/www, -type, d, -exec, chmod, 2775, {}, \; ]
- [ find, /var/www, -type, f, -exec, chmod, 0664, {}, \; ]
- [ sh, -c, 'echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php' ]

in questo esempio è possibile notare la distinzione tra la sezione “packages” dove sono elencati tutti i pacchetti da installare e la sezione “runcmd” con l’elenco dei comandi da eseguire.


Attraverso la CLI è possibile avviare istanze con uno specifico script salvato in un file

aws ec2 run-instances --image-id ami-XXXXX --count 1 
--instance-type m3.medium --key-name my-key-pair 
--subnet-id subnet-abcd1234 --security-group-ids sg-abcd1234 
--user-data file://my_script.txt

E’ possibile anche recuperare l’informazione dell’user data con il comando

aws ec2 describe-instance-attribute --instance-id i-1234567890abcdef0 --attribute userData

Con la libreria boto3 di SDK è possibile indicare lo script user-data come parametro del metodo per avviare una istanza:

import boto3
ec2 = boto3.resource('ec2')
user_data_script = """#!/bin/bash
   echo "Hello, World!" > /home/ec2-user/hello.txt
"""
instance = ec2.create_instances(
  ImageId='<your-ami-id>',
  MinCount=1,
  MaxCount=1,
  InstanceType='t2.micro',
  UserData=user_data_script
)

Nei template CloudFormation è possibile indicare l’user data di una istanza EC2 come è possibile notare nell’esempio:

Resources:
  WebServerInstance:
    Type: 'AWS::EC2::Instance'
    Metadata:
      Comment1: >-
        Configure to install the Apache Web Server and PHP
      Comment2: Save website content to /var/www/html/index.php
      'AWS::CloudFormation::Init':
        configSets:
          InstallAndRun:
          - Install
          - Configure
        Install:
          packages:
            yum:
              mysql: []
              mysql-server: []
              mysql-libs: []
              httpd: []
              php: []
              php-mysql: []
          files:
            /var/www/html/index.php:
              content:
                ...: null
              mode: '000600'
              owner: apache
              group: apache
            /tmp/setup.mysql:
              content: !Join 
              - ''
              - - 'CREATE DATABASE '
                - !Ref DBName
                - |
                  ;
                - 'GRANT ALL ON '
                - !Ref DBName
                - .* TO '
                - !Ref DBUsername
                - '''@localhost IDENTIFIED BY '''
                - !Ref DBPassword
                - |
                ';
              mode: '000400'
              owner: root
              group: root
            /etc/cfn/cfn-hup.conf:
              content: !Join 
              - ''
              - - |
                  [main]
                - stack=
                - !Ref 'AWS::StackId'
                - |+
                  #riga vuota
                - region=
                - !Ref 'AWS::Region'
                - |+
                  #riga vuota
              mode: '000400'
              owner: root
              group: root
            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Join 
              - ''
              - - |
                  [cfn-auto-reloader-hook]
                - |
                  triggers=post.update
                - >
 path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init
                - 'action=/opt/aws/bin/cfn-init -v '
                - ' --stack '
                - !Ref 'AWS::StackName'
                - ' --resource WebServerInstance '
                - ' --configsets InstallAndRun '
                - ' --region '
                - !Ref 'AWS::Region'
                - |+
                  #riga vuota
                - |
                  runas=root
          services:
            sysvinit:
              mysqld:
                enabled: 'true'
                ensureRunning: 'true'
              httpd:
                enabled: 'true'
                ensureRunning: 'true'
              cfn-hup:
                enabled: 'true'
                ensureRunning: 'true'
                files:
                - /etc/cfn/cfn-hup.conf
                - /etc/cfn/hooks.d/cfn-auto-reloader.conf
        Configure:
          commands:
            01_set_mysql_root_password:
              command: !Join 
              - ''
              - - mysqladmin -u root password '
                - !Ref DBRootPassword
                - ''''
                  test: !Join 
                - ''
                - - '$(mysql '
                - !Ref DBUsername
                - ' -u root --password='''
                - !Ref DBRootPassword
                - ''' >/dev/null 2>&1 </dev/null); (( $? != 0 ))'
            02_create_database:
              command: !Join 
                - ''
                - - mysql -u root --password='
                  - !Ref DBRootPassword
                  - ''' < /tmp/setup.mysql'
              test: !Join 
                - ''
                - - '$(mysql '
                  - !Ref DBUsername
                  - ' -u root --password='''
                  - !Ref DBRootPassword
                  - ''' >/dev/null 2>&1 </dev/null); (( $? != 0 ))'
  Properties:
    ImageId: !FindInMap 
      - AWSRegionArch2AMI
      - !Ref 'AWS::Region'
      - !FindInMap 
        - AWSInstanceType2Arch
        - !Ref InstanceType
        - Arch
    InstanceType: !Ref InstanceType
    SecurityGroups:
    - !Ref WebServerSecurityGroup
    KeyName: !Ref KeyName
    UserData: !Base64 
      'Fn::Join':
      - ''
      - - |
          #!/bin/bash -xe
        - |
          yum install -y aws-cfn-bootstrap
        - |
          # Install the files and packages from the metadata
        - '/opt/aws/bin/cfn-init '
        - ' --stack '
        - !Ref 'AWS::StackName'
        - ' --resource WebServerInstance '
        - ' --configsets InstallAndRun '
        - ' --region '
        - !Ref 'AWS::Region'
        - |+

In questo semplice esempio è possibile notare che sono presenti script di entrambi i tipi di user data separati:

  • cloud-init: in una sezione Metadata vengono divisi i comandi tra package, files, service e command, si rimanda alla documentazione ufficiale per la descrizione approfondita della sintassi di queste sezioni.
  • script: nello script viene installato il pacchetto “aws-cfn-bootstrap” e successivamente viene eseguito un comando “cfn-init” per avviare lo script indicato nel cloud-init.

Questa tecnica è necessaria per permettere a CloudFormation di avviare i vari script nella istanza in fase di creazione, si rimanda alla documentazione ufficiale. Molti esempi di user data sono consultabili nel sito ufficiale.

MENU