Skip to content

Commit 16d2497

Browse files
authored
Merge pull request #1620 from buildkite/SUP-5013-metadata-svc-fixes
Fix failure scenarios when EC2 metadata service unavailable
2 parents efbcde2 + 5e214aa commit 16d2497

3 files changed

Lines changed: 92 additions & 9 deletions

File tree

packer/linux/stack/conf/bin/bk-install-elastic-stack.sh

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,31 @@ on_error() {
1111
echo "${BASH_SOURCE[0]} errored with exit code ${exit_code} on line ${error_line}."
1212

1313
if [[ $exit_code != 0 ]]; then
14-
if ! aws autoscaling set-instance-health \
15-
--instance-id "$INSTANCE_ID" \
16-
--health-status Unhealthy; then
17-
echo Failed to set instance health to unhealthy.
14+
# Only try to set instance health if we have an instance ID
15+
# (metadata service must be available for this to work)
16+
if [[ -n "${INSTANCE_ID:-}" ]]; then
17+
if ! aws autoscaling set-instance-health \
18+
--instance-id "$INSTANCE_ID" \
19+
--health-status Unhealthy; then
20+
echo Failed to set instance health to unhealthy.
21+
fi
22+
else
23+
echo "Skipping instance health check - INSTANCE_ID not available (metadata service may be unavailable)"
1824
fi
1925
fi
2026

21-
cfn-signal \
27+
# Attempt to signal CloudFormation failure
28+
# This may fail if metadata service is unavailable, but we try anyway
29+
if ! cfn-signal \
2230
--region "$AWS_REGION" \
2331
--stack "$BUILDKITE_STACK_NAME" \
2432
--reason "Error on line $error_line: $(tail -n 1 /var/log/elastic-stack.log)" \
2533
--resource "AgentAutoScaleGroup" \
26-
--exit-code "$exit_code"
34+
--exit-code "$exit_code"; then
35+
echo "Failed to send cfn-signal (this is expected if metadata service is unavailable)"
36+
fi
2737

38+
# The OnFailure=poweroff.target in cloud-final.service.d will handle instance termination
2839
exit "$exit_code"
2940
}
3041

@@ -43,9 +54,76 @@ exec > >(tee -a /var/log/elastic-stack.log | logger -t user-data -s 2>/dev/conso
4354
echo "Starting ${BASH_SOURCE[0]}..."
4455

4556
# This needs to happen first so that the error reporting works
46-
token=$(curl -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60" --fail --silent --show-error --location http://169.254.169.254/latest/api/token)
47-
INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $token" --fail --silent --show-error --location http://169.254.169.254/latest/meta-data/instance-id)
48-
echo "Detected INSTANCE_ID=$INSTANCE_ID"
57+
# Fetch metadata with retry logic to handle transient metadata service failures
58+
fetch_metadata_with_retry() {
59+
local max_attempts=5
60+
local timeout=2
61+
local wait_time=1
62+
local max_wait=30
63+
local attempt=1
64+
65+
echo "Fetching EC2 metadata with retry (max attempts: $max_attempts)..."
66+
67+
# Temporarily disable exit on error for retry logic
68+
set +e
69+
70+
while [[ $attempt -le $max_attempts ]]; do
71+
echo "Attempt $attempt of $max_attempts to fetch metadata..."
72+
73+
# Try to get the token
74+
local token
75+
token=$(curl -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60" \
76+
--max-time "$timeout" \
77+
--fail --silent --show-error \
78+
--location http://169.254.169.254/latest/api/token 2>&1)
79+
local token_result=$?
80+
81+
if [[ $token_result -eq 0 ]]; then
82+
# Try to get the instance ID
83+
INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $token" \
84+
--max-time "$timeout" \
85+
--fail --silent --show-error \
86+
--location http://169.254.169.254/latest/meta-data/instance-id 2>&1)
87+
local instance_id_result=$?
88+
89+
if [[ $instance_id_result -eq 0 ]]; then
90+
echo "Successfully fetched metadata on attempt $attempt"
91+
echo "Detected INSTANCE_ID=$INSTANCE_ID"
92+
# Re-enable exit on error
93+
set -e
94+
return 0
95+
else
96+
echo "Failed to fetch instance ID: $INSTANCE_ID"
97+
fi
98+
else
99+
echo "Failed to fetch metadata token: $token"
100+
fi
101+
102+
if [[ $attempt -lt $max_attempts ]]; then
103+
echo "Waiting ${wait_time}s before retry..."
104+
sleep "$wait_time"
105+
# Exponential backoff with cap at 30 seconds
106+
wait_time=$((wait_time * 2))
107+
if [[ $wait_time -gt $max_wait ]]; then
108+
wait_time=$max_wait
109+
fi
110+
fi
111+
112+
attempt=$((attempt + 1))
113+
done
114+
115+
# Re-enable exit on error before returning failure
116+
set -e
117+
118+
echo "ERROR: Failed to fetch EC2 metadata after $max_attempts attempts"
119+
echo "This likely indicates the EC2 metadata service is unavailable or this instance has connectivity issues"
120+
121+
# Return failure - this will trigger the error trap and call on_error()
122+
return 1
123+
}
124+
125+
# Fetch metadata or fail (which triggers on_error and poweroff)
126+
fetch_metadata_with_retry
49127

50128
# This script is run on every boot so that we can gracefully recover from hard failures (eg. kernel panics) during
51129
# any previous attempts. If a previous run is detected as started but not complete then we will fail this run and mark
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[Unit]
2+
OnFailure=poweroff.target
3+
OnFailureJobMode=replace-irreversibly

packer/linux/stack/scripts/install-buildkite-agent.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ sudo cp /tmp/conf/buildkite-agent/systemd/buildkite-agent.service /etc/systemd/s
6262
echo "Adding cloud-init failure safety check..."
6363
sudo mkdir -p /etc/systemd/system/cloud-final.service.d/
6464
sudo cp /tmp/conf/buildkite-agent/systemd/cloud-final.service.d/10-power-off-on-failure.conf /etc/systemd/system/cloud-final.service.d/10-power-off-on-failure.conf
65+
sudo mkdir -p /etc/systemd/system/cloud-init.service.d/
66+
sudo cp /tmp/conf/buildkite-agent/systemd/cloud-init.service.d/10-power-off-on-failure.conf /etc/systemd/system/cloud-init.service.d/10-power-off-on-failure.conf
6567

6668
echo "Adding termination scripts..."
6769
sudo cp /tmp/conf/buildkite-agent/scripts/stop-agent-gracefully /usr/local/bin/stop-agent-gracefully

0 commit comments

Comments
 (0)