Allow configurable RootDeviceSize & RootDeviceType

This allows for a larger EBS root volume (and we now default to 20GB,
just like kube-up did).

We remove the BlockDeviceMappings support because it wasn't used and
made things a lot more complicated.  We always map the ephemeral
devices.

Issue #24
This commit is contained in:
Justin Santa Barbara 2016-07-08 01:11:14 -04:00
parent b7fa910cb8
commit 13b8e81bd6
6 changed files with 159 additions and 57 deletions

View File

@ -10,6 +10,8 @@ launchConfiguration/{{ $m.Name }}.masters.{{ ClusterName }}:
instanceType: {{ $m.Spec.MachineType }}
associatePublicIP: true
userData: resources/nodeup.sh _kubernetes_master
rootVolumeSize: {{ or $m.Spec.RootVolumeSize "20" }}
rootVolumeType: {{ or $m.Spec.RootVolumeType "gp2" }}
autoscalingGroup/{{ $m.Name}}.masters.{{ ClusterName }}:
minSize: 1

View File

@ -54,6 +54,8 @@ launchConfiguration/{{ $nodeset.Name }}.{{ ClusterName }}:
instanceType: {{ $nodeset.Spec.MachineType }}
associatePublicIP: true
userData: resources/nodeup.sh _kubernetes_pool
rootVolumeSize: {{ or $nodeset.Spec.RootVolumeSize "20" }}
rootVolumeType: {{ or $nodeset.Spec.RootVolumeType "gp2" }}
autoscalingGroup/{{ $nodeset.Name }}.{{ ClusterName }}:
launchConfiguration: launchConfiguration/{{ $nodeset.Name }}.{{ ClusterName }}

View File

@ -35,6 +35,11 @@ type InstanceGroupSpec struct {
MachineType string `json:"machineType,omitempty"`
//NodeTag string `json:",omitempty"`
// RootVolumeSize is the size of the EBS root volume to use, in GB
RootVolumeSize *int `json:"rootVolumeSize",omitempty`
// RootVolumeType is the type of the EBS root volume to use (e.g. gp2)
RootVolumeType *string `json:"rootVolumeType",omitempty`
Zones []string `json:"zones,omitempty"`
}

View File

@ -9,11 +9,20 @@ import (
type BlockDeviceMapping struct {
VirtualName *string
EbsDeleteOnTermination *bool
EbsVolumeSize *int64
EbsVolumeType *string
}
func BlockDeviceMappingFromEC2(i *ec2.BlockDeviceMapping) (string, *BlockDeviceMapping) {
o := &BlockDeviceMapping{}
o.VirtualName = i.VirtualName
if i.Ebs != nil {
o.EbsDeleteOnTermination = i.Ebs.DeleteOnTermination
o.EbsVolumeSize = i.Ebs.VolumeSize
o.EbsVolumeType = i.Ebs.VolumeType
}
return aws.StringValue(i.DeviceName), o
}
@ -21,12 +30,23 @@ func (i *BlockDeviceMapping) ToEC2(deviceName string) *ec2.BlockDeviceMapping {
o := &ec2.BlockDeviceMapping{}
o.DeviceName = aws.String(deviceName)
o.VirtualName = i.VirtualName
if i.EbsDeleteOnTermination != nil || i.EbsVolumeSize != nil || i.EbsVolumeType != nil {
o.Ebs = &ec2.EbsBlockDevice{}
o.Ebs.DeleteOnTermination = i.EbsDeleteOnTermination
o.Ebs.VolumeSize = i.EbsVolumeSize
o.Ebs.VolumeType = i.EbsVolumeType
}
return o
}
func BlockDeviceMappingFromAutoscaling(i *autoscaling.BlockDeviceMapping) (string, *BlockDeviceMapping) {
o := &BlockDeviceMapping{}
o.VirtualName = i.VirtualName
if i.Ebs != nil {
o.EbsDeleteOnTermination = i.Ebs.DeleteOnTermination
o.EbsVolumeSize = i.Ebs.VolumeSize
o.EbsVolumeType = i.Ebs.VolumeType
}
return aws.StringValue(i.DeviceName), o
}
@ -34,6 +54,14 @@ func (i *BlockDeviceMapping) ToAutoscaling(deviceName string) *autoscaling.Block
o := &autoscaling.BlockDeviceMapping{}
o.DeviceName = aws.String(deviceName)
o.VirtualName = i.VirtualName
if i.EbsDeleteOnTermination != nil || i.EbsVolumeSize != nil || i.EbsVolumeType != nil {
o.Ebs = &autoscaling.Ebs{}
o.Ebs.DeleteOnTermination = i.EbsDeleteOnTermination
o.Ebs.VolumeSize = i.EbsVolumeSize
o.Ebs.VolumeType = i.EbsVolumeType
}
return o
}

View File

@ -26,13 +26,12 @@ type Instance struct {
Name *string
Tags map[string]string
ImageID *string
InstanceType *string
SSHKey *SSHKey
SecurityGroups []*SecurityGroup
AssociatePublicIP *bool
BlockDeviceMappings map[string]*BlockDeviceMapping
IAMInstanceProfile *IAMInstanceProfile
ImageID *string
InstanceType *string
SSHKey *SSHKey
SecurityGroups []*SecurityGroup
AssociatePublicIP *bool
IAMInstanceProfile *IAMInstanceProfile
}
var _ fi.CompareWithID = &Instance{}
@ -164,13 +163,9 @@ func nameFromIAMARN(arn *string) *string {
}
func (e *Instance) Run(c *fi.Context) error {
c.Cloud.(*awsup.AWSCloud).AddTags(e.Name, e.Tags)
cloud := c.Cloud.(*awsup.AWSCloud)
blockDeviceMappings, err := addEphemeralDevices(e.InstanceType, e.BlockDeviceMappings)
if err != nil {
return err
}
e.BlockDeviceMappings = blockDeviceMappings
cloud.AddTags(e.Name, e.Tags)
return fi.DefaultDeltaRunMethod(e, c)
}
@ -220,9 +215,16 @@ func (_ *Instance) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Instance) err
},
}
if e.BlockDeviceMappings != nil {
// Build up the actual block device mappings
// TODO: Support RootVolumeType & RootVolumeSize (see launchconfiguration)
blockDeviceMappings, err := buildEphemeralDevices(e.InstanceType)
if err != nil {
return err
}
if len(blockDeviceMappings) != 0 {
request.BlockDeviceMappings = []*ec2.BlockDeviceMapping{}
for deviceName, bdm := range e.BlockDeviceMappings {
for deviceName, bdm := range blockDeviceMappings {
request.BlockDeviceMappings = append(request.BlockDeviceMappings, bdm.ToEC2(deviceName))
}
}

View File

@ -10,7 +10,6 @@ import (
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"reflect"
"strings"
)
@ -20,13 +19,17 @@ type LaunchConfiguration struct {
UserData *fi.ResourceHolder
ImageID *string
InstanceType *string
SSHKey *SSHKey
SecurityGroups []*SecurityGroup
AssociatePublicIP *bool
BlockDeviceMappings map[string]*BlockDeviceMapping
IAMInstanceProfile *IAMInstanceProfile
ImageID *string
InstanceType *string
SSHKey *SSHKey
SecurityGroups []*SecurityGroup
AssociatePublicIP *bool
IAMInstanceProfile *IAMInstanceProfile
// RootVolumeSize is the size of the EBS root volume to use, in GB
RootVolumeSize *int64
// RootVolumeType is the type of the EBS root volume to use (e.g. gp2)
RootVolumeType *string
ID *string
}
@ -90,11 +93,16 @@ func (e *LaunchConfiguration) Find(c *fi.Context) (*LaunchConfiguration, error)
}
actual.SecurityGroups = securityGroups
actual.BlockDeviceMappings = make(map[string]*BlockDeviceMapping)
// Find the root volume
for _, b := range lc.BlockDeviceMappings {
deviceName, bdm := BlockDeviceMappingFromAutoscaling(b)
actual.BlockDeviceMappings[deviceName] = bdm
if b.Ebs == nil || b.Ebs.SnapshotId != nil {
// Not the root
continue
}
actual.RootVolumeSize = b.Ebs.VolumeSize
actual.RootVolumeType = b.Ebs.VolumeType
}
userData, err := base64.StdEncoding.DecodeString(*lc.UserData)
if err != nil {
return nil, fmt.Errorf("error decoding UserData: %v", err)
@ -121,7 +129,7 @@ func (e *LaunchConfiguration) Find(c *fi.Context) (*LaunchConfiguration, error)
return actual, nil
}
func addEphemeralDevices(instanceTypeName *string, blockDeviceMappings map[string]*BlockDeviceMapping) (map[string]*BlockDeviceMapping, error) {
func buildEphemeralDevices(instanceTypeName *string) (map[string]*BlockDeviceMapping, error) {
// TODO: Any reason not to always attach the ephemeral devices?
if instanceTypeName == nil {
return nil, fi.RequiredField("InstanceType")
@ -130,34 +138,39 @@ func addEphemeralDevices(instanceTypeName *string, blockDeviceMappings map[strin
if err != nil {
return nil, err
}
if blockDeviceMappings == nil {
blockDeviceMappings = make(map[string]*BlockDeviceMapping)
}
blockDeviceMappings := make(map[string]*BlockDeviceMapping)
for _, ed := range instanceType.EphemeralDevices() {
m := &BlockDeviceMapping{VirtualName: fi.String(ed.VirtualName)}
existing := blockDeviceMappings[ed.DeviceName]
if existing == nil {
blockDeviceMappings[ed.DeviceName] = m
} else {
if !reflect.DeepEqual(existing, m) {
// We might actually be calling Run again, if we are retrying the operation
glog.Warningf("not attaching ephemeral device - found duplicate device mapping: %q", ed.DeviceName)
}
}
blockDeviceMappings[ed.DeviceName] = m
}
return blockDeviceMappings, nil
}
func (e *LaunchConfiguration) Run(c *fi.Context) error {
// TODO: Only on first creation (i.e. we need stricter phases)
blockDeviceMappings, err := addEphemeralDevices(e.InstanceType, e.BlockDeviceMappings)
func (e *LaunchConfiguration) buildRootDevice(cloud *awsup.AWSCloud) (map[string]*BlockDeviceMapping, error) {
imageID := fi.StringValue(e.ImageID)
image, err := cloud.ResolveImage(imageID)
if err != nil {
return err
return nil, fmt.Errorf("unable to resolve image: %q: %v", imageID, err)
} else if image == nil {
return nil, fmt.Errorf("unable to resolve image: %q: not found", imageID)
}
e.BlockDeviceMappings = blockDeviceMappings
rootDeviceName := aws.StringValue(image.RootDeviceName)
blockDeviceMappings := make(map[string]*BlockDeviceMapping)
rootDeviceMapping := &BlockDeviceMapping{
EbsDeleteOnTermination: aws.Bool(true),
EbsVolumeSize: e.RootVolumeSize,
EbsVolumeType: e.RootVolumeType,
}
blockDeviceMappings[rootDeviceName] = rootDeviceMapping
return blockDeviceMappings, nil
}
func (e *LaunchConfiguration) Run(c *fi.Context) error {
return fi.DefaultDeltaRunMethod(e, c)
}
@ -202,10 +215,27 @@ func (_ *LaunchConfiguration) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *La
}
request.SecurityGroups = securityGroupIDs
request.AssociatePublicIpAddress = e.AssociatePublicIP
if e.BlockDeviceMappings != nil {
request.BlockDeviceMappings = []*autoscaling.BlockDeviceMapping{}
for device, bdm := range e.BlockDeviceMappings {
request.BlockDeviceMappings = append(request.BlockDeviceMappings, bdm.ToAutoscaling(device))
// Build up the actual block device mappings
{
rootDevices, err := e.buildRootDevice(t.Cloud)
if err != nil {
return err
}
ephemeralDevices, err := buildEphemeralDevices(e.InstanceType)
if err != nil {
return err
}
if len(rootDevices) != 0 || len(ephemeralDevices) != 0 {
request.BlockDeviceMappings = []*autoscaling.BlockDeviceMapping{}
for device, bdm := range rootDevices {
request.BlockDeviceMappings = append(request.BlockDeviceMappings, bdm.ToAutoscaling(device))
}
for device, bdm := range ephemeralDevices {
request.BlockDeviceMappings = append(request.BlockDeviceMappings, bdm.ToAutoscaling(device))
}
}
}
@ -239,13 +269,20 @@ type terraformLaunchConfiguration struct {
SecurityGroups []*terraform.Literal `json:"security_groups,omitempty"`
AssociatePublicIpAddress *bool `json:"associate_public_ip_address,omitempty"`
UserData *terraform.Literal `json:"user_data,omitempty"`
RootBlockDevice *terraformBlockDevice `json:"root_block_device,omitempty"`
EphemeralBlockDevice []*terraformBlockDevice `json:"ephemeral_block_device,omitempty"`
Lifecycle *terraformLifecycle `json:"lifecycle,omitempty"`
}
type terraformBlockDevice struct {
// For ephemeral devices
DeviceName *string `json:"device_name"`
VirtualName *string `json:"virtual_name"`
// For root
VolumeType *string `json:"volume_type"`
VolumeSize *int64 `json:"volume_size"`
DeleteOnTermination *bool `json:"delete_on_termination"`
}
type terraformLifecycle struct {
@ -278,13 +315,39 @@ func (_ *LaunchConfiguration) RenderTerraform(t *terraform.TerraformTarget, a, e
}
tf.AssociatePublicIpAddress = e.AssociatePublicIP
if e.BlockDeviceMappings != nil {
tf.EphemeralBlockDevice = []*terraformBlockDevice{}
for deviceName, bdm := range e.BlockDeviceMappings {
tf.EphemeralBlockDevice = append(tf.EphemeralBlockDevice, &terraformBlockDevice{
VirtualName: bdm.VirtualName,
DeviceName: fi.String(deviceName),
})
{
rootDevices, err := e.buildRootDevice(cloud)
if err != nil {
return err
}
ephemeralDevices, err := buildEphemeralDevices(e.InstanceType)
if err != nil {
return err
}
if len(rootDevices) != 0 {
if len(rootDevices) != 1 {
return fmt.Errorf("unexpectedly found multiple root devices")
}
for _, bdm := range rootDevices {
tf.RootBlockDevice = &terraformBlockDevice{
VolumeType: bdm.EbsVolumeType,
VolumeSize: bdm.EbsVolumeSize,
DeleteOnTermination: fi.Bool(true),
}
}
}
if len(ephemeralDevices) != 0 {
tf.EphemeralBlockDevice = []*terraformBlockDevice{}
for deviceName, bdm := range ephemeralDevices {
tf.EphemeralBlockDevice = append(tf.EphemeralBlockDevice, &terraformBlockDevice{
VirtualName: bdm.VirtualName,
DeviceName: fi.String(deviceName),
})
}
}
}