Как я могу предоставить аргументы в свой скрипт, если он запускается как пользовательские данные в terraform?

Я написал скрипт, настраивающий стек LEMP:

#!/usr/bin/env bash

if tput colors >/dev/null 2>&1; then
  RED='\033[0;31m'
  YELLOW='\033[1;33m'
  CYAN='\033[1;35m'
  NC='\033[0m' # No Color
else
  RED=''
  GREEN=''
  YELLOW=''
  NC=''
fi

print_help() {
  echo -e "Usage: ${YELLOW}$0${NC} [options]"
  echo -e "${CYAN}Options:${NC}"
  echo "  --php_ver <version>        Specify PHP version (default is 8.2)"
  echo "  --nodb                     Do not install any database"
  echo "  --db_root_password <pass>  Set root password for the database"
  echo "  -h, --help                 Show this help message"
}

cleanup () {
  echo -e "${CYAN}Cleanup${NC}"
  apt-get autoremove && apt-get autoclean
  exit 0;
}

if [ "$EUID" -ne 0 ]; then 
  echo -e "${RED}ERROR: Run this script as root or via using sudo.${NC}"
  echo
  print_help
  exit 1;
fi

export DEBIAN_FRONTEND=noninteractive

PHP_VERSION = "8.2"
DB_TYPE = "mariadb"


while [ "$1" != "" ]; do
  case $1 in
    "--php_ver")
      PHP_VERSION=$2
      shift 2
      ;;
    
    "--nodb") 
        DB_TYPE = "none"
        shift
        ;;

    "--db_root_password")
      DB_ROOT_PASSWORD=$2
      shift 2
      ;;

    "-h" | "--help")
      print_help
      exit 0
      ;;

    *)
      echo -e " ${RED}Invalid option: ${YELLOW}$1${NC}"
      exit 1
      ;;
  esac
done

 apt-get update &&  apt-get upgrade -y


if [ "$PHP_VERSION"  == "" ]; then
    echo -e "${RED}No php version provided defaulting into 8.2${NC}"
    PHP_VERSION = "8.2"
fi

echo -e "${CYAN}PHP ${YELLOW}$PHP_VERSION${CYAN} will be installed ${NC}"

apt-get install -y nginx ca-certificates apt-transport-https software-properties-common
add-apt-repository -y ppa:ondrej/php 
apt-get update

apt-get install -y php${PHP_VERSION}-fpm  \
    php${PHP_VERSION}-mbstring \
    php${PHP_VERSION}-mysql \
    php${PHP_VERSION}-oauth \
    php${PHP_VERSION}-opcache \
    php${PHP_VERSION}-readline \
    php${PHP_VERSION}-xml

if [ "$DB_TYPE" == 'none' ];then
  echo -e  "${YELLOW}No Db support will be installed${NC}"
  cleanup
  exit 0;
fi

POOL_CONF = "/etc/php/${PHP_VERSION}/fpm/pool.d/www.conf"
if [ -f "$POOL_CONF" ]; then
  echo -e "${CYAN}Configuring PHP-FPM to listen on ${YELLOW}127.0.0.1:9000${NC}"
  sed -i "s|^listen = .*|listen = 127.0.0.1:9000|" "$POOL_CONF"
  systemctl restart php${PHP_VERSION}-fpm
else
  echo -e "${RED}Failed to configure PHP-FPM: ${POOL_CONF} not found${NC}"
  exit 1
fi

echo -e "${CYAN}Configuring default Vhost${NC}"

rm -rf /var/www/html/*

echo "<?php phpinfo();" > /var/www/html/index.php
systemctl stop nginx

cat >/etc/nginx/sites-available/default <<EOL
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;

    index index.php index.html index.htm index.nginx-debian.html;

    server_name _;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
    
    
        # With php-cgi (or other tcp sockets):
        fastcgi_pass 127.0.0.1:9000;
    }

    location ~ /\.ht {
        deny all;
    }
}
EOL

systemctl start nginx

echo -e "${CYAN}Installing ${YELLOW}${DB_TYPE}${NC}"

apt-get -y install mariadb-server mariadb-client


if [ "$DB_ROOT_PASSWORD" == "" ]; then
  echo -e "${YELLOW}DB Root password is missing. skipping${NC}"
  cleanup
  exit 0;
fi

echo "${CYAN}Provisioning Root User${NC}"

# Make sure that NOBODY can access the server without a password
 mysql -e "UPDATE mysql.user SET Password = PASSWORD('${DB_ROOT_PASSWORD}') WHERE User = 'root'"
# Kill the anonymous users
 mysql -e "DROP USER ''@'localhost'"
# Because our hostname varies we'll use some Bash magic here.
 mysql -e "DROP USER ''@'$(hostname)'"
# Kill off the demo database
 mysql -e "DROP DATABASE IF EXISTS test"
# Make our changes take effect
 mysql -e "FLUSH PRIVILEGES"

Как видите, мне нужно предоставить следующие аргументы:

  • --php_ver
  • --nodb
  • --db_root_password

Скрипт расположен рядом с модулями terraform. Поэтому, как я могу его выполнить, а также предоставить необходимые аргументы в отношении сценария:

resource "aws_instance" "instance" {
    ami = "ami-0d342235295932397"
    instance_type = "t3a.micro"
    key_name = "ssh_key"
    iam_instance_profile = "myInstance"

    root_block_device {
        volume_size = 30
        volume_type = "gp3"
    }

    # rest of nessesary arguments
    userdata= # execute script here
}

Я хочу избежать изменения сценария, чтобы иметь возможность использовать его и вне terraform.

Какая часть текущего скрипта не работает корректно вне terraform?

user1934428 04.06.2024 10:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
52
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Что касается Terraform и AWS, user_data — это буквально набор байтов, которые сохраняются в API EC2 без какого-либо конкретного значения. Программное обеспечение, работающее внутри вашего экземпляра EC2, затем извлекает эти данные с помощью API метаданных экземпляра и пользовательских данных и самостоятельно решает, как интерпретировать эти данные.

Для большинства образов компьютеров Linux общего назначения программное обеспечение user_data обрабатывает cloud-init , поэтому интерпретация вашего user_data контента аналогична описанной в Форматы пользовательских данных.

В настоящее время вы используете интерпретацию сценария пользовательских данных, которая просто записывает данные на диск в виде исполняемого файла и пытается его выполнить. В этом случае Cloud-init не передает скрипту никаких аргументов, поэтому ваш текущий скрипт не подходит для использования таким образом.

Самый гибкий вариант — установить user_data в Данные конфигурации облака, которые представляют собой формат YAML, определенный Cloud-init, который позволяет описывать различные действия, которые Cloud-init должен выполнять при запуске во время загрузки системы.


Cloud-init имеет два «модуля», которые потенциально могут быть полезны для ваших требований: Write Files для записи произвольных небольших файлов в файловую систему и Bootcmd для запуска встроенного скрипта один раз во время ранней загрузки.

Следующая конфигурация создает облачную конфигурацию user_data, которая инструктирует cloud-init сначала записать сценарий начальной загрузки в определенное место на диске, а затем запустить другой небольшой сценарий, который выполняет этот файл с определенными аргументами:

resource "aws_instance" "instance" {
  # ...

  user_data = <<-EOT
    #cloud-config
    ${yamlencode({
      write_files = [
        {
          encoding    = "b64"
          content     = filebase64("${path.module}/setup-lemp.sh")
          owner       = "root:root"
          path        = "/usr/local/bin/setup-lemp"
          permissions = "0755"
        },
      ]
      bootcmd = [
        [
          "cloud-init-per", "once", "setup-lemp",
          "/usr/local/bin/setup-lemp",
          "--db_root_password", "foo",
        ],
      ]
    })}
  EOT
}

Модуль bootcmd настроен на запуск следующей командной строки:

cloud-init-per once setup-lemp /usr/local/bin/setup-lemp --db_root_password foo

При этом используется вспомогательный инструмент Cloud-init под названием cloud-init-per, который обеспечивает выполнение данной команды позже в процессе загрузки. bootcmd на самом деле запускается раньше write_files в типичной конфигурации Cloud-init, поэтому этот дополнительный помощник позволяет отложить фактическое выполнение скрипта до более позднего этапа процесса, после того как файл уже должен быть записан.


Некоторые соображения и предостережения, которые следует иметь в виду:

  • EC2 рассматривает user_data как обычный атрибут экземпляра EC2, поэтому его может получить любой, у кого есть доступ для получения атрибутов экземпляра EC2. Поэтому, если вы разместите здесь пароль root своей базы данных, этот пароль может быть виден другим людям, работающим с вашей учетной записью AWS.

    Альтернативой, позволяющей избежать этой проблемы, было бы вместо этого записать пароль в такой сервис, как AWS Secrets Manager, и использовать user_data, чтобы сообщить экземпляру, как получить пароль, вместо того, чтобы указывать пароль напрямую. Однако подробности этого выходят за рамки этого вопроса и ответа.

  • Порядок, в котором Cloud-init запускает свои модули, настраивается человеком, создавшим ваш AMI. Я построил вышеизложенное, предполагая порядок, указанный в документации для системы Ubuntu, что приводит к порядку bootcmd, write_files, scripts_per_once и, следовательно, должно работать. Если вы заметили странное поведение и подозреваете, что шаги выполняются в другом порядке, вам может потребоваться проверить фактическую конфигурацию облачной инициализации вашей системы, чтобы убедиться, что модули выполняются в подходящем порядке.

  • Некоторая часть работы, выполняемой вашим сценарием (в частности, установка пакета), при желании может быть выполнена с использованием других декларативных модулей Cloud-Init, а не императивного сценария bash, что может облегчить отладку результата, поскольку вы можете положиться на команды отчета о состоянии Cloud-init. Однако было бы сложно заменить весь ваш скрипт модулями cloud-init, поэтому в вашем случае в целом может быть проще разрешить всю проблему (кроме написания сценария в первую очередь) в bash.

Другой подход заключается в использовании следующих поставщиков:

resource "aws_instance" "instance" {

 #rest of config goes here

 provisioner "file" {
      source = "${path.module}/provision.sh"
      destination = "/home/ubuntu/provision.sh"
    }

    provisioner "remote-exec" {
      inline = [
        "chmod +x /home/ubuntu/provision.sh",
        local.final_provision_command
      ]
    }

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = "${file(var.private_key_path)}"
      host        = self.public_ip
    }
}

Это загрузит файл на сервер через поставщика файлов, а с помощью удаленного выполнения скрипт выполнится. Соединение настраивает ssh для соединения, которое будет использоваться для выполнения сценария.

Другие вопросы по теме