0

I'm trying to set environment variables to my EC2 instance(s), using NodeJS SDK. I was unable to find an answer in other threads, after trying alternate approaches they suggested.

My goal is to bind USER_ID and SERVER_ID to the new EC2 instance for a configuration script I would like to run.

TL;DR

  • I tried various approaches, including Parameter Store, and was unable to set environment variables
  • implementation details provided below
  • I've only tried with UserData with NodeJS SDK and the CLI, both resulted in the same outcome
  • Open to alternate approaches to bind variables to a new instance including SSH
  • seeking suggestions or the identification of issues that may help

Approach 1 I tried to set variables with UserData, but this failed. There was no indication user data was acted upon in the cloud init logs (/var/log/cloud-init-output.log). I was not able to find any output or reason why this failed.

function userDataScript(userId, serverId) {
  return `
  #!/usr/bin/bash
  echo 'export USER_ID=${userId}'
  echo 'export SERVER_ID=${serverId}'
  `;
}

// Launch EC2 instance
exports.launchEC2 = async function(server) {
    try {
      // Set the name of the key pair
      const keyName = server.id;

      // Create a new key pair in EC2
      const keyPair = await EC2.createKeyPair({KeyName: keyName}).promise();
      server.key = keyPair.KeyMaterial;

      const instanceParams = {
        ImageId: imageAMI,
        InstanceType: 't2.micro',
        MinCount: 1,
        MaxCount: 1,
        KeyName: keyName,
        SecurityGroupIds: [SEC_GROUP],
        UserData: Buffer.from(userDataScript(server.userId, server.id)).toString('base64')
      };

      const data = await EC2.runInstances(instanceParams).promise();
      const instanceId = data.Instances[0].InstanceId;
      server.instanceId = instanceId;
      await server.save();

      console.log(`Launched instance ${instanceId}`);
    } catch (err) {
      console.log(`Error launching instance: ${err.message}`);
    }
}

Approach 2 I tried the above as a file, same results. The environment variables did not exist or were not set in my instance.

Approach 3 Based on an earlier suggestion from SO, I tried using Parameter Store. My VM is pre-configured with AWS-CLI and I can run the commands successfully in the terminal of the VM, however, I was unsuccessful to get this to run with UserData.

async function createParameterStoreKeys(userId, serverId) {
  const params = {
    Name: `/paramstore/${serverId}/environment-variables`,
    Type: 'String',
    Value: JSON.stringify({
      USER_ID: userId,
      SERVER_ID: serverId
    })
  };
  await SSM.putParameter(params).promise();
  console.log(`Created parameter store key for ${serverId}`);
}


async function userDataScript(identifier) {
  return `
    export USER_ID=$(aws --region=us-east-2 ssm get-parameter --name /paramstore/${identifier}/environment-variables --query 'Parameter.Value' --output text | jq -r '.USER_ID')
    export SERVER_ID=$(aws --region=us-east-2 ssm get-parameter --name /paramstore/${identifier}/environment-variables --query 'Parameter.Value' --output text | jq -r '.SERVER_ID')
  `;
}

// Launch EC2 instance
exports.launchEC2 = async function(server) {
    try {
      await createParameterStoreKeys(server.userId, server.id);
  
      // Set the name of the key pair
      const keyName = server.id;

      // Create a new key pair in EC2
      const keyPair = await EC2.createKeyPair({KeyName: keyName}).promise();
      server.key = keyPair.KeyMaterial;

      const instanceParams = {
        ImageId: imageAMI,
        InstanceType: 't2.micro',
        MinCount: 1,
        MaxCount: 1,
        KeyName: keyName,
        SecurityGroupIds: [SEC_GROUP],
        UserData: Buffer.from(await userDataScript(server.id)).toString('base64')
      };

      const data = await EC2.runInstances(instanceParams).promise();
      const instanceId = data.Instances[0].InstanceId;
      server.instanceId = instanceId;
      await server.save();

      console.log(`Launched instance ${instanceId}`);
    } catch (err) {
      console.log(`Error launching instance: ${err.message}`);
    }
}

On SSH in to the EC2 instance, echo $USER_ID returned empty.

Approach 4 I tried to explicitly get cloud init to work, since I think the script is not being run at all. This was a change to userDataScript to use #cloud-config as per examples in the documentation.


async function userDataScript(userId, serverId) {
  return `#cloud-config
  write_files:
    - content: |
        export USER_ID=${userId}
        export SERVER_ID=${serverId}
      path: /etc/profile.d/env.sh
      owner: ubuntu:ubuntu
      permissions: '0755'
  runcmd:
    - source /etc/profile.d/env.sh
  `  
}

exports.launchEC2 = async function(server) {
    try {
      // Set the name of the key pair
      const keyName = server.id;

      const instanceParams = {
        ImageId: imageAMI,
        InstanceType: 't2.micro',
        MinCount: 1,
        MaxCount: 1,
        KeyName: keyName,
        SecurityGroupIds: [SEC_GROUP],
        UserData: Buffer.from(await userDataScript(server.userId, server.id)).toString('base64')
      };

      const data = await EC2.runInstances(instanceParams).promise();
      const instanceId = data.Instances[0].InstanceId;
      server.instanceId = instanceId;
      await server.save();

      console.log(`Launched instance ${instanceId}`);
    } catch (err) {
      console.log(`Error launching instance: ${err.message}`);
    }
}

As before, this launches, yet, when after I SSH and echo $USER_ID the response remains empty.

Edit (more details):
Additional user scripts were attempted under approaches 1 & 2, as below, and all failed regardless of the destination to profile, environment, and .bashrc:

function userDataScript(userId, serverId) {
  return `
  #!/usr/bin/bash
  echo "export USER_ID=${userId}" >> ~/.bashrc  # also failed for /etc/environment and /etc/profile
  echo "export SERVER_ID=${serverId}" >> ~/.bashrc
  source ~/.bashrc  # with and without this line
  `;
}
function userDataScript(userId, serverId) {
  return `
  #!/usr/bin/bash
  echo "export USER_ID=${userId}" | sudo tee -a /etc/profile  # also tried for /etc/environment
  echo "export SERVER_ID=${serverId}" | sudo tee -a /etc/profile
  source /etc/environment  # with and without this line
  `;
}

The following AWS-CLI commands also failed (no value on echo $USER_ID after SSHing in to instance after its creation), which is the same behavior as all of the above, indicating that either the script is not called or that its changes are not persisted:

aws ec2 run-instances --image-id $AMI --count 1 --instance-type t2.micro --key-name $KP --security-group-ids $SEC_GRP --user-data '#!/bin/bash
echo export USER_ID=user123 >> /etc/profile
echo export SERVER_ID=server123 >> /etc/profile
'

aws ec2 run-instances --image-id $AMI --count 1 --instance-type t2.micro --key-name $KP --security-group-ids $SEC_GRP --user-data '#!/bin/bash
echo "export USER_ID=user123" | sudo tee -a /etc/profile
echo "export SERVER_ID=serv123" | sudo tee -a /etc/profile
'
aug2uag
  • 3,379
  • 3
  • 32
  • 53
  • Changing the environment variables of the script that's run during cloudinit's lifecycle isn't enough, you need to modify one of the scripts that's run when a new shell is started. [This answer shows such a technique](https://stackoverflow.com/a/43665255) – Anon Coward Apr 05 '23 at 22:55
  • under "approach 1" I tried the echo out to /etc/profile and /etc/environment with tee, sudo, and to .bashrc-- none of these resulted in the presence of the variables in the instance – aug2uag Apr 05 '23 at 23:11
  • @AnonCoward was there a cloud init configuration I missed? my AMI is a custom OVA export from VirtualBox – aug2uag Apr 06 '23 at 00:05
  • 1
    If it's a custom image, you need to ensure cloud-init is installed, properly configured for the OS, and configured to run user-data scripts on every boot. – Anon Coward Apr 06 '23 at 00:24

0 Answers0