Use cached IP Address for amazonec2 driver

`GetIP` gets called every time docker-machine wants to send an ssh
command. For the amazonec2 driver it is not using the saved IPAddress
but calling the aws API every time which leads to an excessive amount of
IP calls which exhaust the API limit. Before sending a request for the
IP check if it's populated, the IPAddress is being populated for the
first time when the machine starts in

Reading [amazon
documentation]( on weather a public IP can change during
the lifecycle of a machine it does not seem like it will effect
docker-machine in any way.

This seems to effect other drivers like the `google` driver, but drivers
like `digitalocean` are not effected since the use the
which does not call the API. Also on drivers like
it is doing the same check before calling the API.

We can see significant reduction of API calls:

**Before Patch:**
Number of DescribeInstances: 34
Number of GetSSHHostname: 17

**After Patch:**
Number of DescribeInstances: 13
Number of GetSSHHostname: 17

More investigation of this issue can be found in gitlab-org/gitlab-runner#3424 (comment 231357594)

reference gitlab-org/gitlab-runner#3424
parent 8bf0b23c
Pipeline #93525631 passed with stages
in 7 minutes and 15 seconds
......@@ -18,6 +18,7 @@ import (
......@@ -791,6 +792,10 @@ func (d *Driver) GetURL() (string, error) {
func (d *Driver) GetIP() (string, error) {
if d.IPAddress != "" {
return d.IPAddress, nil
inst, err := d.getInstance()
if err != nil {
return "", err
package amazonec2
import (
const (
......@@ -558,3 +557,31 @@ func TestBase64UserDataIsCorrectWhenFileProvided(t *testing.T) {
assert.NoError(t, ud_err)
assert.Equal(t, contentBase64, userdata)
func TestGetIP(t *testing.T) {
privateIPAddress := ""
publicIPAddress := ""
describeInstanceRecorder := fakeEC2DescribeInstance{
ReturnInstance: ec2.Instance{
PrivateIpAddress: &privateIPAddress,
PublicIpAddress: &publicIPAddress,
defer describeInstanceRecorder.AssertExpectations(t)
describeInstanceRecorder.On("DescribeInstances", mock.Anything).Once()
driver := NewCustomTestDriver(&describeInstanceRecorder)
// Called the first time
ip, err := driver.GetIP()
assert.NoError(t, err)
assert.Equal(t, publicIPAddress, ip)
// Set IP Address, to use cached version
driver.IPAddress = publicIPAddress
ip, err = driver.GetIP()
assert.NoError(t, err)
assert.Equal(t, publicIPAddress, ip)
......@@ -132,6 +132,27 @@ func (f *fakeEC2SecurityGroupTestRecorder) AuthorizeSecurityGroupIngress(input *
return value, err
type fakeEC2DescribeInstance struct {
ReturnInstance ec2.Instance
func (f *fakeEC2DescribeInstance) DescribeInstances(input *ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error) {
return &ec2.DescribeInstancesOutput{
NextToken: nil,
Reservations: []*ec2.Reservation{
Instances: []*ec2.Instance{
}, nil
func NewTestDriver() *Driver {
driver := NewDriver("machineFoo", "path")
driver.clientFactory = func() Ec2Client {
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment