Report back to EC2 that UserData Script Failed - windows

If my UserData Powershell script for an EC2 instance fails how do I report back to EC2 that the machine was not provisioned correct and should be rebuilt?
The EC2 instance is part of an AutoScale Group, and I have found this Set-ASInstanceHealth command but I've found nothing in the documentation to tell me if it's correct to set this within the UserData?

After talking with the AWS Support it seems to call Set-ASInstanceHealth at this point is completely valid. Below is a snippet of PowerShell code explaining the usage in case it's useful to anyone else.
function Get-CurrentEC2InstanceId() {
return Invoke-RestMethod http://169.254.169.254/latest/meta-data/instance-id
}
Try {
# Not do something stupid
}
Catch {
$instanceId = Get-CurrentEC2InstanceId
Set-ASInstanceHealth -InstanceId $instanceId -HealthStatus "Unhealthy"
}

Related

AWS EC2 and AMI tags with spaces using CLI

I try to create an instance using CLI, first like described in the doc (https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/run-instances.html, Example 4):
aws ec2 run-instances ... --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=val}]' 'ResourceType=volume,Tags=[{Key=Name,Value=val}]'
and it works well. The problem starts when I try to move whole tag-specifications in a separate bash variable, I need it because in fact there are many tags and they are built during the script run. So I do, but first use :
tags="--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=val}]' 'ResourceType=volume,Tags=[{Key=Name,Value=val}]'"
aws ec2 run-instances ... $tags
and it fails:
Error parsing parameter '--tag-specifications': Expected: '=', received: ''' for input:
'ResourceType=instance,Tags=[{Key=Name,Value=val}]'
^
If I remove single quotes then it works:
tags="--tag-specifications ResourceType=instance,Tags=[{Key=Name,Value=val_no_spaces}] ResourceType=volume,Tags=[{Key=Name,Value=val_no_spaces}]" # just works.
Despite it works, it's not good to me because if the values have spaces it stops working again:
tags="--tag-specifications ResourceType=instance,Tags=[{Key=Name,Value=val with spaces}] ResourceType=volume,Tags=[{Key=Name,Value=val with spaces}]"
Error parsing parameter '--tag-specifications': Expected: ',', received: 'EOF' for input:
ResourceType=instance,Tags=[{Key=Name,Value=val
^
I tried to wrap val into \", \' - neither works to me.
The same behavior is if you run very similar command aws ec2 create-image.
So, how can I add tags with spaces in the value and having them in a separate variable?
I had the same problem with aws cli. I solved it using arrays. In your case I would do the following:
tags=(
--tag-specifications
'ResourceType=instance,Tags=[{Key=Name,Value=val}]'
'ResourceType=volume,Tags=[{Key=Name,Value=val with spaces}]'
)
aws ec2 run-instances ... "${tags[#]}"

Disable scheduling on second instance of same project on AWS

I have 2 instances of the same deployment/project on AWS Elastic Beanstalk.
Both contain a Laravel project which contains scheduling code which runs various commands which can be found in the schedule method/function of the Kernel.php class within 'app/Console' - the problem I have is that if a command runs from one instance then it will also run the command from the second instance which is not what I want to happen.
What I would like to happen is that the commands get run from only one instance and not the other. How do I achieve this in the easiest way possible?
Is there a Laravel package which could help me achieve this?
From Laravel 5.6:
Laravel provides a onOneServer method which you can use if your applications share a single cache server. You could use something like ElastiCache to host Redis or Memcached and use it as your cache server for both of your application instances. Then you would be able to use the onOneServer method like this:
$schedule->command('report:generate')
->fridays()
->at('17:00')
->onOneServer();
For older versions of Laravel:
You could use the jdavidbakr/multi-server-event package. Once you have it set up you should be able to use it like:
$schedule->command('inspire')
->daily()
->withoutOverlappingMultiServer();
I had the same issue to run some cronjobs (nothing related to Laravel) and I found a nice solution (don't remember where I found it)
What I do is check if the instance running the code is the first instance on the Auto Scaling Group, if it's the first then I execute the command otherwise just exit.
This is the way it's implemented:
#!/bin/bash
INSTANCE_ID=`curl http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null`
REGION=`curl -s http://169.254.169.254/latest/dynamic/instance-identity/document 2>/dev/null | jq -r .region`
# Find the Auto Scaling Group name from the Elastic Beanstalk environment
ASG=`aws ec2 describe-tags --filters "Name=resource-id,Values=$INSTANCE_ID" \
--region $REGION --output json | jq -r '.[][] | select(.Key=="aws:autoscaling:groupName") | .Value'`
# Find the first instance in the Auto Scaling Group
FIRST=`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ASG \
--region $REGION --output json | \
jq -r '.AutoScalingGroups[].Instances[] | select(.LifecycleState=="InService") | .InstanceId' | sort | head -1`
# If the instance ids are the same exit 0
[ "$FIRST" = "$INSTANCE_ID" ]
Try implementing those calls using PHP and it should work.

Issue with Set-CMTaskSequenceDeployment

Seems that New-CMTaskSequenceDeployment / Set-CMTaskSequenceDeployment cmdlet option -DeploymentOption does not work as expected.
I'm trying to automate a Task Sequence Deployment via Powershell. I use New-CMTaskSequenceDeployment cmdlet to create the deployment. The content of the TS should be downloaded before the start of the TS.
Works ok, but the -DeploymentOption DownloadAllContentLocallyBeforeStartingTaskSequence seems not to have any effect, when I check the deployment after the script ran, the option "pre-download content for this task sequence" isn't checked.
Same issue when I try Set-CMTaskSequenceDeployment.
Any hint from the community what I'm doing wrong?
...
# Create deployment for all waves now
foreach ($StrCollectionName in $ArrCollectionName)
{
$SchedulePhase2 = New-CMSchedule -Nonrecurring -Start $DateScheduleStartPhase2
Try {
$Deployment = New-CMTaskSequenceDeployment -CollectionName $StrCollectionName -TaskSequencePackageId $StrTaskSequenceID -DeployPurpose Required -AvailableDateTime $DateAvailablePhase1 -DeploymentOption DownloadAllContentLocallyBeforeStartingTaskSequence -SoftwareInstallation $False -SystemRestart $False -Schedule $SchedulePhase2 -RerunBehavior RerunIfFailedPreviousAttempt -AllowUsersRunIndependently $True -SendWakeUpPacket $True
Write-Host "Success - Deployment $Deployment created!"
}
Catch {
Write-Host "Error - Exception caught in creating deployment : $error[0]"
Exit
}
}
...
Looks like unfortunately (and unexpected) the pre-download behavior is different for package/program deployment and task sequence deployment.
In case of a package/program deployment the content download will start after start time in case the deployment has a mandatory time defined.
A TS deployment behaves different. It will start download when mandatory time (schedule) has been reached. Start time will be ignored.
This difference is independently from how the deployment has been started (Console or powershell cmdlet) therefore it is not an issue of the cmdlet.
First of all, you can check the picture below to make sure not to confuse these two options.
Difference between Preload content checkbox and Download all content locally before starting TS
Once done Here is my proposition :
Just by clicking, try to retrieve the property of you TSDeployment before and after you clicked the checkbox. You will see that one property changed. AdvertFlags
PS MUZ:\> (Get-CMTaskSequenceDeployment -DeploymentID MUZ200C5).AdvertFlags
[Convert]::ToString((Get-CMTaskSequenceDeployment -DeploymentID MUZ200C5).AdvertFlags,2)
Output :
34275328
10000010110000000000000000
From there, you can read from the MS doc : https://learn.microsoft.com/en-us/configmgr/develop/reference/core/servers/configure/sms_advertisement-server-wmi-class
From this, I learn that I need to change the 12th bit like this :
$advertflag = Get-CMTaskSequenceDeployment -DeploymentID MUZ200C5
$advertflag.AdvertFlags = $advertflag.AdvertFlags -bor "0x00001000"
$advertflag.put()
I hope it will help someone someday :)

How to run shell script on multiple EC2 instances using AWS CLI?

I'm trying to do a simple AWS CLI command that can run a shell command to multiple instances.
I know first I need to get the list of instances ids:
aws ec2 describe-instances --filter "Name=tag:Group,Values=Development" --query 'Reservations[].Instances[].[InstanceId]' --output text
I then will have to assign them to an array. Then loop through each instance id and send the command.
Do we have an option for aws to send a shell command to an instance with a specific id?
Something like this:
aws ssm send-command --instance-ids "i-xxxxxxxxxxxxxxxx" --document-name "shellscript"
I keep getting this error:
An error occurred (InvalidInstanceId) when calling the SendCommand operation:
I've made sure that the SSM agent is running on that specific instance and made sure everything is correct according to these docs pages.
You can use ssm send-command.
A sample command to see ip address of instance:
aws ssm send-command --instance-ids "your id's" --document-name "AWS-RunShellScript" --comment "IP config" --parameters "commands=ifconfig" --output text
Modify command as per your needs.
In case you've got the error, this can happen when you don't have SSM setup on the instance you're trying to access. For a list of instances where you can run SSM commands, run:
aws ssm describe-instance-information --output text
See: InvalidInstanceId: An error occurred (InvalidInstanceId) when calling the SendCommand operation.
I was able to create a script with Python using Boto3.
import boto3
import botocore
import paramiko
tagkey = 'Environment'
tagvalue = 'DEV'
# list_instances functions returns a list of ip addresses containing a set of tags
def list_instances(tagkey, tagvalue):
ec2client = boto3.client('ec2')
response = ec2client.describe_instances(
Filters=[
{
'Name': 'tag:'+tagkey,
'Values': [tagvalue]
}
]
)
instancelist = []
for reservation in (response["Reservations"]):
for instance in reservation["Instances"]:
instancelist.append(instance["PublicDnsName"])
return instancelist
# Results of the function get stored in a list.
list = list_instances(tagkey, tagvalue)
key = paramiko.RSAKey.from_private_key_file("/home/ec2-user/key.pem")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Looping through all the instannces in the list
for instance_ip in list[:]:
# Connect/ssh to an instance
try:
# Here 'ec2-user' is user name and 'instance_ip' is public IP of EC2
client.connect(hostname=instance_ip, username="ec2-user", pkey=key)
# Execute a command after connecting/ssh to an instance
stdin, stdout, stderr = client.exec_command("touch test")
# close the client connection once the job is done
print "Command sent:",instance_ip
except Exception, e:
print e

How to add tags to Buildbot EC2LatentBuildSlave

I am using EC2LatentBuildSlave to sawn EC2 instances running the build slaves. I would like to tag the slaves so a tag is visible in the EC2 dashboard Tags tab.
I am passing
tags={'Key':'BuildbotType', 'Value':'slaveName'}
but I can’t see the tags in the spawned EC2 instance.
Have I misunderstood this parameter?
Thank for your help.
This seems like a bug in buildbot version 0.8.12. Unfortunately we can't upgrade at this point. Instead I installed a init script (service) on the base AMI that reads user_data and tags its self. I pass the tag details using the user_data field in EC2LatentBuildSlave.
login_aws() {
# Some code to configer aws access keys.
}
get_userdata() {
AWS_USERDATA=$(curl -s http://169.254.169.254/latest/user-data)
echo $AWS_USERDATA
}
tag_self_for_identification() {
login_aws
INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
if [ -z "$INSTANCE_ID" ]; then
echo "Error unable to obtain instance id"
return 0
fi
USERDATA=get_userdata
set $USERDATA
NAME="$2 $3"
if [ ! -z "$NAME" ]; then
echo "aws ec2 create-tags --resources $INSTANCE_ID --tags Key=Name,Value=$NAME"
aws ec2 create-tags --resources $INSTANCE_ID --tags Key=Name,Value="$NAME" \
Key=Buildbot,Value=Latentslave
fi
}
tag_self_for_identification
This is an extremely over engineered solution. However I need tags to work for some lambda functions to be able to identify these latentlsaves

Categories

Resources