Я написал скрипт, настраивающий стек 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 и 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 для соединения, которое будет использоваться для выполнения сценария.
Какая часть текущего скрипта не работает корректно вне terraform?