Class: Kontena::Machine::Aws::NodeProvisioner

Inherits:
Object
  • Object
show all
Includes:
Cli::ShellSpinner, Common, RandomName
Defined in:
lib/kontena/machine/aws/node_provisioner.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Common

#default_subnet, #default_vpc, #resolve_ami, #resolve_security_groups_to_ids

Constructor Details

#initialize(api_client, access_key_id, secret_key, region) ⇒ NodeProvisioner

Returns a new instance of NodeProvisioner.

Parameters:

  • api_client (Kontena::Client)

    Kontena api client

  • access_key_id (String)

    aws_access_key_id

  • secret_key (String)

    aws_secret_access_key

  • region (String)


18
19
20
21
22
23
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 18

def initialize(api_client, access_key_id, secret_key, region)
  @api_client = api_client
  @ec2 = ::Aws::EC2::Resource.new(
    region: region, credentials: ::Aws::Credentials.new(access_key_id, secret_key)
  )
end

Instance Attribute Details

#api_clientObject (readonly)

Returns the value of attribute api_client.



12
13
14
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 12

def api_client
  @api_client
end

#ec2Object (readonly)

Returns the value of attribute ec2.



12
13
14
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 12

def ec2
  @ec2
end

Instance Method Details

#aws_dns_supported?(vpc_id) ⇒ Boolean

Parameters:

  • vpc_id (String)

Returns:

  • (Boolean)


210
211
212
213
214
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 210

def aws_dns_supported?(vpc_id)
  vpc = ec2.vpc(vpc_id)
  response = vpc.describe_attribute({attribute: 'enableDnsSupport'})
  response.enable_dns_support
end

#create_security_group(name, vpc_id) ⇒ Aws::EC2::SecurityGroup

creates security_group and authorizes default port ranges

Parameters:

  • name (String)
  • vpc_id (String)

Returns:

  • (Aws::EC2::SecurityGroup)


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 139

def create_security_group(name, vpc_id)
  sg = ec2.create_security_group({
    group_name: name,
    description: "Kontena Grid",
    vpc_id: vpc_id
  })

  sg.authorize_ingress({ # SSH
    ip_protocol: 'tcp', from_port: 22, to_port: 22, cidr_ip: '0.0.0.0/0'
  })
  sg.authorize_ingress({ # HTTP
    ip_protocol: 'tcp', from_port: 80, to_port: 80, cidr_ip: '0.0.0.0/0'
  })
  sg.authorize_ingress({ # HTTPS
    ip_protocol: 'tcp', from_port: 443, to_port: 443, cidr_ip: '0.0.0.0/0'
  })
  sg.authorize_ingress({ # OpenVPN
    ip_protocol: 'udp', from_port: 1194, to_port: 1194, cidr_ip: '0.0.0.0/0'
  })
  sg.authorize_ingress({ # Overlay / Weave network
    ip_permissions: [
      {
        from_port: 6783, to_port: 6783, ip_protocol: 'tcp',
        user_id_group_pairs: [
          { group_id: sg.group_id, vpc_id: vpc_id }
        ]
      },
      {
        from_port: 6783, to_port: 6784, ip_protocol: 'udp',
        user_id_group_pairs: [
          { group_id: sg.group_id, vpc_id: vpc_id }
        ]
      }
    ]
  })

  sg
end

#ensure_security_group(grid, vpc_id) ⇒ Array

Returns Security group id in array.

Parameters:

  • grid (String)

Returns:

  • (Array)

    Security group id in array



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 120

def ensure_security_group(grid, vpc_id)
  group_name = "kontena_grid_#{grid}"
  group_id = resolve_security_groups_to_ids(group_name, vpc_id)

  if group_id.empty?
    spinner "Creating AWS security group" do
      sg = create_security_group(group_name, vpc_id)
      group_id = [sg.group_id]
    end
  end
  group_id
end

#erb(template, vars) ⇒ Object



196
197
198
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 196

def erb(template, vars)
  ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
end

#generate_nameObject



188
189
190
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 188

def generate_name
  "#{super}-#{rand(1..99)}"
end

#instance_exists_in_grid?(grid, name) ⇒ Boolean

Returns:

  • (Boolean)


192
193
194
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 192

def instance_exists_in_grid?(grid, name)
  api_client.get("grids/#{grid}/nodes")['nodes'].find{|n| n['name'] == name}
end

#regionString

Returns:

  • (String)


179
180
181
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 179

def region
  ec2.client.config.region
end

#run!(opts) ⇒ Object

Parameters:

  • opts (Hash)


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
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 26

def run!(opts)
  if opts[:ami]
    ami = opts[:ami]
  else
    ami = resolve_ami(region)
    abort('No valid AMI found for region') unless ami
  end

  opts[:vpc] = default_vpc.vpc_id unless opts[:vpc]

  security_groups = opts[:security_groups] ?
      resolve_security_groups_to_ids(opts[:security_groups], opts[:vpc]) :
      ensure_security_group(opts[:grid], opts[:vpc])

  raise "Missing :subnet option" if opts[:subnet].nil?
  subnet = ec2.subnet(opts[:subnet])
  abort('Failed to find subnet!') unless subnet

  dns_server = aws_dns_supported?(opts[:vpc]) ? '169.254.169.253' : '8.8.8.8'
  instances = []
  opts[:count].to_i.times do |i|
    if opts[:name] && opts[:count].to_i > 1
      name = "#{opts[:name]}-#{i+1}"
    elsif opts[:name]
      name = opts[:name]
    else
      name = generate_name
    end

    userdata_vars = {
      name: name,
      version: opts[:version],
      master_uri: opts[:master_uri],
      grid_token: opts[:grid_token],
      dns_server: dns_server
    }

    ec2_instance = ec2.create_instances({
      image_id: ami,
      min_count: 1,
      max_count: 1,
      instance_type: opts[:type],
      key_name: opts[:key_pair],
      user_data: Base64.encode64(user_data(userdata_vars)),
      block_device_mappings: [
        {
          device_name: '/dev/xvda',
          virtual_name: 'Root',
          ebs: {
            volume_size: opts[:storage],
            volume_type: 'gp2'
          }
        }
      ],
      network_interfaces: [
       {
         device_index: 0,
         subnet_id: subnet.subnet_id,
         groups: security_groups,
         associate_public_ip_address: opts[:associate_public_ip],
         delete_on_termination: true
       }
      ]
    }).first
    ec2_instance.create_tags({
      tags: [
        {key: 'Name', value: name},
        {key: 'kontena_grid', value: opts[:grid]}
      ]
    })

    spinner "Creating AWS instance #{name.colorize(:cyan)} " do
      sleep 1 until ec2_instance.reload.state.name == 'running'
    end
    instances << name
  end
  instances.each do |instance_name|
    node = nil
    spinner "Waiting for node #{instance_name.colorize(:cyan)} join to grid #{opts[:grid].colorize(:cyan)} " do
      sleep 1 until node = instance_exists_in_grid?(opts[:grid], instance_name)
    end
    labels = [
      "region=#{region}",
      "az=#{opts[:zone]}",
      "provider=aws",
      "type=#{opts[:type]}"
    ]
    set_labels(node, labels)
  end
end

#set_labels(node, labels) ⇒ Object

Parameters:

  • node (Hash)
  • labels (Array<String>)


202
203
204
205
206
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 202

def set_labels(node, labels)
  data = {}
  data[:labels] = labels
  api_client.put("nodes/#{node['id']}", data, {}, {'Kontena-Grid-Token' => node['grid']['token']})
end

#user_data(vars) ⇒ Object



183
184
185
186
# File 'lib/kontena/machine/aws/node_provisioner.rb', line 183

def user_data(vars)
  cloudinit_template = File.join(__dir__ , '/cloudinit.yml')
  erb(File.read(cloudinit_template), vars)
end