forked from ryanb/dotfiles
-
Notifications
You must be signed in to change notification settings - Fork 0
/
aws-docker-console
executable file
·151 lines (123 loc) · 5.39 KB
/
aws-docker-console
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/bin/bash
set -eo pipefail
BINDIR=$(dirname $0)
SSH="${BINDIR}/ssh-ec2-connect"
INSTALL_JQ_REMOTE_CMD="yum install jq -y"
if [ "${USE_SSM}" != "" ] ; then
SSH="${SSH} --ssm"
fi
##
# Use this annotated script a base for launching an interactive console task on Amazon ECS
#
# more info: https://engineering.loyaltylion.com/running-an-interactive-console-on-amazon-ecs-c692f321b14d
#
# Requirements:
# - `jq` must be installed on both the client and server
##
# the ECS cluster in which we'll launch the console `default` or `staging`
cluster="$1"
# the name of the task definition to use when launching the console task
taskDefinition="$2"
cliContainerName="$3"
# The username to use for running the docker shell
attachUser="${4:-root}"
# depending on your requirement, you may want to use an existing task definition and then use the
# `containerOverrides`, or you might have a completely separate task definition that is designed
# for running a console
#
# the override approach is reasonable, but note that you can't override most things; in particular
# you can't add overrides for cpu/mem constraints. Using the same task definition as another app
# may also make it harder to identify console tasks in ECS, although the `started-by` field does
# help there
#
# whichever approach you use, because ECS does not support interactive tasks yet, you'll need to
# start the container with a "dummy" sleep command; this will stop the container from exiting
# immediately allowing you to docker exec a console in later
overrides='{"containerOverrides": [{"name": "'${cliContainerName}'", "command": ["sleep", "86400"]}]}'
echo '-> Starting task...'
# run the new task using the ECS scheduler. By default, ECS will place the task on any instance
# that has enough cpu/mem available. You can also specify placement constraints or a strategy here
#
# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html
#
response=$(aws ecs run-task \
--cluster "$cluster" \
--task-definition "$taskDefinition" \
--overrides "$overrides" \
--started-by "user-console/$(whoami)")
taskArn=$(echo "$response" | jq -r '.tasks [0] .taskArn')
containerInstanceArn=$(echo "$response" | jq -r '.tasks [0] .containerInstanceArn')
if [ "${taskArn}" = "null" ] ; then
echo "Unable to start task!"
echo ${response}
exit 1
fi
function stop_task () {
ERR_CODE=$1
if [ $ERR_CODE -ne 0 ] ; then
echo "-> Error caught running '$BASH_COMMAND' code: $ERR_CODE"
fi
# after we close the tty we'll stop the task so it doesn't sit around wasting resources
echo "-> Stopping task ${taskArn}..."
response=$(aws ecs stop-task \
--cluster "$cluster" \
--task "$taskArn" \
--reason 'user-console/stop')
echo "-> Task stopped"
}
trap 'stop_task $?' EXIT
# once the task has been placed, translate the containerInstanceArn into an EC2 instance from which
# we can find the public IP address so we can connect to the server
#
# note: in production, your ECS container instances are probably not exposed to the internet, so
# you'll need to adapt things from here (e.g. require VPN access, or go through an SSH gateway)
#
# https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeContainerInstances.html
# https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html
#
instanceId=$(aws ecs describe-container-instances \
--cluster "$cluster" \
--container-instances "$containerInstanceArn" \
| jq -r '.containerInstances [0] .ec2InstanceId')
instance=$(aws ec2 describe-instances \
--instance-ids "$instanceId" \
| jq '.Reservations [0] .Instances [0]')
instanceName=$(echo "$instance" | jq -r '.Tags[] | select(.Key == "Name") .Value')
instanceIp=$(echo "$instance" | jq -r '.PublicIpAddress')
instanceId=$(echo "$instance" | jq -r '.InstanceId')
# once we have the address of the container instance we then need the Docker container id
#
# ECS container instances all have a handy introspection API on port 51678 which can be used to
# translate a taskArn into a Docker container
#
# Even though the task have been placed by this point, it may take some time for the container to
# actually start, so we'll loop this until we have a container id
getDockerId() {
dockerId=$($SSH --instance-id=${instanceId} -- -qt \
"curl http://localhost:51678/v1/tasks?taskarn=$taskArn" \
| jq -r '.Containers [0] .DockerId')
#dockerId=$(ssh -qt "ec2-user@$instanceIp" \
# "curl http://localhost:51678/v1/tasks?taskarn=$taskArn" \
# | jq -r '.Containers [0] .DockerId')
}
echo "-> Waiting for container ${taskArn} to start on instance ${instanceId} (IP: ${instanceIp})..."
getDockerId
while [ "$dockerId" == 'null' ] || [ -z "$dockerId" ]; do
getDockerId
sleep 1
done
# with the instance address and docker container, we now have all we need to launch into an
# interactive console using `ssh -t`, which launches a pseudo-tty that lasts until we close the
# session
#
# note: this example assumes an executable `./console` in the docker work dir; for Rails this would
# probably be `bundle exec rails console`
# Ensure jq is installed
set +e
echo "-> Ensuring jq is installed on the ECS host..."
$SSH --instance-id=${instanceId} -- -qt sudo ${INSTALL_JQ_REMOTE_CMD}
set -e
echo "-> Loading console and attaching to task..."
$SSH --instance-id=${instanceId} -- -qt docker exec -ti -u ${attachUser} "$dockerId" /bin/bash
# Pause for a moment and then exit, triggering the cleanup trap above automatiocally
sleep 0.5