Skip to content
This repository was archived by the owner on Sep 30, 2020. It is now read-only.

Commit 0dc583e

Browse files
authored
Plugin System (#791)
This is an initial implementation of the plugin system #509 as proposed in #751. Not all but most of knobs mentioned in the proposal except pre/post-cluster-creation validations are implemented. Basically, it allows the user to define a set of customizations to various aspects of resources created and managed by kube-aws as a "kube-aws plugin" and reuse it. The set of customizations is defined in a separate file other than a `cluster.yaml` for reusability. More concretely, provide `<your project root>/plugins/<your-plugin-name>/plugin.yaml` like seen in test/integration/plugin_test.go to extend a kube-aws cluster from many aspects including: - additional iam policy statements per node role(worker/controller/etcd) - additional cfn stack template resources per stack(root/control-plane/node-pool) - additional systemd units/custom files per node role(worker/controller/etcd) - additional kubelet feature gates for worker kubelets - additional node labels for worker/controller kubelets and so on. The new plugin system is not used to implement core features of kube-aws yet. Therefore, I believe we don't need to worry much about breaking things via this change. At least one core feature implemented as a plugin is planned in the next version of kube-aws v0.9.9, as noted in our roadmap. Changes: * Plugin System: Add support for node labels * Plugin System: Add support for feature gates * plugin-system: Add support for k8s manifests and helm releases * plugin-system: Add support for kube-apiserver server options * plugin-system: Add support for custom files * plugin-system: Add support for custom IAM policy statements * Rename plugin/api to plugin/pluginapi to better differentiate what the api is for * Move the test helper for plugin to a seperate go file * Extract a type representing the file uploaded to a kube-aws node into a separate go file * plugin-system: Seperate logics from api * plugin-system: Separate cluster extensions by plugins from cluster and plugins * plugin-system: More separation of api and logic * plugin-system: Move apply-kube-aws-plugins script for easier merging with master * plugin-system: Rename pluginapi to pluginmodel * plugin-system: Remove unused types and files * plugin-system: Comment about `values` in plugin.yaml * Reliability improvement to cloud-config-controller * Fix occasional kube-node-label, cfn-signal errors * Fix install-kube-system and apply-kube-aws-plugins services to better scheduled in order without spamming journal
1 parent 4e57852 commit 0dc583e

38 files changed

+2059
-73
lines changed

core/controlplane/cluster/cluster.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/kubernetes-incubator/kube-aws/cfnstack"
1414
"github.com/kubernetes-incubator/kube-aws/core/controlplane/config"
1515
"github.com/kubernetes-incubator/kube-aws/model"
16+
"github.com/kubernetes-incubator/kube-aws/plugin/clusterextension"
17+
"github.com/kubernetes-incubator/kube-aws/plugin/pluginmodel"
1618
)
1719

1820
// VERSION set by build script
@@ -119,21 +121,56 @@ func (c *ClusterRef) validateExistingVPCState(ec2Svc ec2Service) error {
119121
return nil
120122
}
121123

122-
func NewCluster(cfg *config.Cluster, opts config.StackTemplateOptions, awsDebug bool) (*Cluster, error) {
123-
cluster := NewClusterRef(cfg, awsDebug)
124+
func NewCluster(cfg *config.Cluster, opts config.StackTemplateOptions, plugins []*pluginmodel.Plugin, awsDebug bool) (*Cluster, error) {
125+
clusterRef := NewClusterRef(cfg, awsDebug)
124126
// TODO Do this in a cleaner way e.g. in config.go
125-
cluster.KubeResourcesAutosave.S3Path = model.NewS3Folders(opts.S3URI, cluster.ClusterName).ClusterBackups().Path()
126-
stackConfig, err := cluster.StackConfig(opts)
127+
clusterRef.KubeResourcesAutosave.S3Path = model.NewS3Folders(opts.S3URI, clusterRef.ClusterName).ClusterBackups().Path()
128+
129+
stackConfig, err := clusterRef.StackConfig(opts, plugins)
127130
if err != nil {
128131
return nil, err
129132
}
130133

131134
c := &Cluster{
132-
ClusterRef: cluster,
135+
ClusterRef: clusterRef,
133136
StackConfig: stackConfig,
134137
}
135138

139+
// Notes:
140+
// * `c.StackConfig.CustomSystemdUnits` results in an `ambiguous selector ` error
141+
// * `c.Controller.CustomSystemdUnits = controllerUnits` and `c.ClusterRef.Controller.CustomSystemdUnits = controllerUnits` results in modifying invisible/duplicate CustomSystemdSettings
142+
extras := clusterextension.NewExtrasFromPlugins(plugins, c.PluginConfigs)
143+
144+
extraStack, err := extras.ControlPlaneStack()
145+
if err != nil {
146+
return nil, fmt.Errorf("failed to load control-plane stack extras from plugins: %v", err)
147+
}
148+
c.StackConfig.ExtraCfnResources = extraStack.Resources
149+
150+
extraController, err := extras.Controller()
151+
if err != nil {
152+
return nil, fmt.Errorf("failed to load controller node extras from plugins: %v", err)
153+
}
154+
c.StackConfig.Config.APIServerFlags = append(c.StackConfig.Config.APIServerFlags, extraController.APIServerFlags...)
155+
c.StackConfig.Config.APIServerVolumes = append(c.StackConfig.Config.APIServerVolumes, extraController.APIServerVolumes...)
156+
c.StackConfig.Controller.CustomSystemdUnits = append(c.StackConfig.Controller.CustomSystemdUnits, extraController.SystemdUnits...)
157+
c.StackConfig.Controller.CustomFiles = append(c.StackConfig.Controller.CustomFiles, extraController.Files...)
158+
c.StackConfig.Controller.IAMConfig.Policy.Statements = append(c.StackConfig.Controller.IAMConfig.Policy.Statements, extraController.IAMPolicyStatements...)
159+
160+
for k, v := range extraController.NodeLabels {
161+
c.StackConfig.Controller.NodeLabels[k] = v
162+
}
163+
164+
extraEtcd, err := extras.Etcd()
165+
if err != nil {
166+
return nil, fmt.Errorf("failed to load controller node extras from plugins: %v", err)
167+
}
168+
c.StackConfig.Etcd.CustomSystemdUnits = append(c.StackConfig.Etcd.CustomSystemdUnits, extraEtcd.SystemdUnits...)
169+
c.StackConfig.Etcd.CustomFiles = append(c.StackConfig.Etcd.CustomFiles, extraEtcd.Files...)
170+
c.StackConfig.Etcd.IAMConfig.Policy.Statements = append(c.StackConfig.Etcd.IAMConfig.Policy.Statements, extraEtcd.IAMPolicyStatements...)
171+
136172
c.assets, err = c.buildAssets()
173+
137174
return c, err
138175
}
139176

core/controlplane/cluster/cluster_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strings"
1919
"testing"
2020

21+
"github.com/kubernetes-incubator/kube-aws/plugin/pluginmodel"
2122
yaml "gopkg.in/yaml.v2"
2223
)
2324

@@ -477,7 +478,7 @@ stackTags:
477478
S3URI: "s3://test-bucket/foo/bar",
478479
}
479480

480-
cluster, err := NewCluster(clusterConfig, stackTemplateOptions, false)
481+
cluster, err := NewCluster(clusterConfig, stackTemplateOptions, []*pluginmodel.Plugin{}, false)
481482
if !assert.NoError(t, err) {
482483
return
483484
}
@@ -741,7 +742,7 @@ func newDefaultClusterWithDeps(opts config.StackTemplateOptions) (*Cluster, erro
741742
if err := cluster.Load(); err != nil {
742743
return &Cluster{}, err
743744
}
744-
return NewCluster(cluster, opts, false)
745+
return NewCluster(cluster, opts, []*pluginmodel.Plugin{}, false)
745746
}
746747

747748
func TestRenderStackTemplate(t *testing.T) {

core/controlplane/config/config.go

Lines changed: 172 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ package config
66
//go:generate gofmt -w files.go
77

88
import (
9+
"encoding/json"
910
"errors"
1011
"fmt"
1112
"io/ioutil"
1213
"net"
14+
"path/filepath"
1315
"regexp"
1416
"sort"
1517
"strings"
@@ -21,6 +23,8 @@ import (
2123
"github.com/kubernetes-incubator/kube-aws/model"
2224
"github.com/kubernetes-incubator/kube-aws/model/derived"
2325
"github.com/kubernetes-incubator/kube-aws/netutil"
26+
"github.com/kubernetes-incubator/kube-aws/node"
27+
"github.com/kubernetes-incubator/kube-aws/plugin/pluginmodel"
2428
yaml "gopkg.in/yaml.v2"
2529
)
2630

@@ -141,6 +145,7 @@ func NewDefaultCluster() *Cluster {
141145
KubeReschedulerImage: model.Image{Repo: "gcr.io/google-containers/rescheduler", Tag: "v0.3.1", RktPullDocker: false},
142146
DnsMasqMetricsImage: model.Image{Repo: "gcr.io/google_containers/k8s-dns-sidecar-amd64", Tag: "1.14.4", RktPullDocker: false},
143147
ExecHealthzImage: model.Image{Repo: "gcr.io/google_containers/exechealthz-amd64", Tag: "1.2", RktPullDocker: false},
148+
HelmImage: model.Image{Repo: "quay.io/kube-aws/helm", Tag: "v2.5.1", RktPullDocker: false},
144149
HeapsterImage: model.Image{Repo: "gcr.io/google_containers/heapster", Tag: "v1.4.0", RktPullDocker: false},
145150
AddonResizerImage: model.Image{Repo: "gcr.io/google_containers/addon-resizer", Tag: "2.0", RktPullDocker: false},
146151
KubeDashboardImage: model.Image{Repo: "gcr.io/google_containers/kubernetes-dashboard-amd64", Tag: "v1.6.3", RktPullDocker: false},
@@ -221,7 +226,7 @@ func ConfigFromBytes(data []byte) (*Config, error) {
221226
if err != nil {
222227
return nil, err
223228
}
224-
cfg, err := c.Config()
229+
cfg, err := c.Config([]*pluginmodel.Plugin{})
225230
if err != nil {
226231
return nil, err
227232
}
@@ -450,6 +455,7 @@ type DeploymentSettings struct {
450455
KubeReschedulerImage model.Image `yaml:"kubeReschedulerImage,omitempty"`
451456
DnsMasqMetricsImage model.Image `yaml:"dnsMasqMetricsImage,omitempty"`
452457
ExecHealthzImage model.Image `yaml:"execHealthzImage,omitempty"`
458+
HelmImage model.Image `yaml:"helmImage,omitempty"`
453459
HeapsterImage model.Image `yaml:"heapsterImage,omitempty"`
454460
AddonResizerImage model.Image `yaml:"addonResizerImage,omitempty"`
455461
KubeDashboardImage model.Image `yaml:"kubeDashboardImage,omitempty"`
@@ -487,20 +493,22 @@ type FlannelSettings struct {
487493
PodCIDR string `yaml:"podCIDR,omitempty"`
488494
}
489495

496+
// Cluster is the container of all the configurable parameters of a kube-aws cluster, customizable via cluster.yaml
490497
type Cluster struct {
491498
KubeClusterSettings `yaml:",inline"`
492499
DeploymentSettings `yaml:",inline"`
493500
DefaultWorkerSettings `yaml:",inline"`
494501
ControllerSettings `yaml:",inline"`
495502
EtcdSettings `yaml:",inline"`
496503
FlannelSettings `yaml:",inline"`
497-
AdminAPIEndpointName string `yaml:"adminAPIEndpointName,omitempty"`
498-
ServiceCIDR string `yaml:"serviceCIDR,omitempty"`
499-
CreateRecordSet bool `yaml:"createRecordSet,omitempty"`
500-
RecordSetTTL int `yaml:"recordSetTTL,omitempty"`
501-
TLSCADurationDays int `yaml:"tlsCADurationDays,omitempty"`
502-
TLSCertDurationDays int `yaml:"tlsCertDurationDays,omitempty"`
503-
HostedZoneID string `yaml:"hostedZoneId,omitempty"`
504+
AdminAPIEndpointName string `yaml:"adminAPIEndpointName,omitempty"`
505+
ServiceCIDR string `yaml:"serviceCIDR,omitempty"`
506+
CreateRecordSet bool `yaml:"createRecordSet,omitempty"`
507+
RecordSetTTL int `yaml:"recordSetTTL,omitempty"`
508+
TLSCADurationDays int `yaml:"tlsCADurationDays,omitempty"`
509+
TLSCertDurationDays int `yaml:"tlsCertDurationDays,omitempty"`
510+
HostedZoneID string `yaml:"hostedZoneId,omitempty"`
511+
PluginConfigs model.PluginConfigs `yaml:"kubeAwsPlugins,omitempty"`
504512
ProvidedEncryptService EncryptService
505513
// SSHAccessAllowedSourceCIDRs is network ranges of sources you'd like SSH accesses to be allowed from, in CIDR notation
506514
SSHAccessAllowedSourceCIDRs model.CIDRRanges `yaml:"sshAccessAllowedSourceCIDRs,omitempty"`
@@ -711,8 +719,22 @@ func (c KubeClusterSettings) K8sNetworkPlugin() string {
711719
return "cni"
712720
}
713721

714-
func (c Cluster) Config() (*Config, error) {
715-
config := Config{Cluster: c}
722+
func (c Cluster) Config(extra ...[]*pluginmodel.Plugin) (*Config, error) {
723+
pluginMap := map[string]*pluginmodel.Plugin{}
724+
plugins := []*pluginmodel.Plugin{}
725+
if len(extra) > 0 {
726+
plugins = extra[0]
727+
for _, p := range plugins {
728+
pluginMap[p.SettingKey()] = p
729+
}
730+
}
731+
732+
config := Config{
733+
Cluster: c,
734+
KubeAwsPlugins: pluginMap,
735+
APIServerFlags: pluginmodel.APIServerFlags{},
736+
APIServerVolumes: pluginmodel.APIServerVolumes{},
737+
}
716738

717739
if c.AmiId == "" {
718740
var err error
@@ -784,11 +806,18 @@ type StackTemplateOptions struct {
784806
SkipWait bool
785807
}
786808

787-
func (c Cluster) StackConfig(opts StackTemplateOptions) (*StackConfig, error) {
809+
func (c Cluster) StackConfig(opts StackTemplateOptions, extra ...[]*pluginmodel.Plugin) (*StackConfig, error) {
810+
plugins := []*pluginmodel.Plugin{}
811+
if len(extra) > 0 {
812+
plugins = extra[0]
813+
}
814+
788815
var err error
789-
stackConfig := StackConfig{}
816+
stackConfig := StackConfig{
817+
ExtraCfnResources: map[string]interface{}{},
818+
}
790819

791-
if stackConfig.Config, err = c.Config(); err != nil {
820+
if stackConfig.Config, err = c.Config(plugins); err != nil {
792821
return nil, err
793822
}
794823

@@ -829,15 +858,23 @@ func (c Cluster) StackConfig(opts StackTemplateOptions) (*StackConfig, error) {
829858
return &stackConfig, nil
830859
}
831860

861+
// Config contains configuration parameters available when rendering userdata injected into a controller or an etcd node from golang text templates
832862
type Config struct {
833863
Cluster
834864

835865
AdminAPIEndpoint derived.APIEndpoint
836866
APIEndpoints derived.APIEndpoints
837867

868+
// EtcdNodes is the golang-representation of etcd nodes, which is used to differentiate unique etcd nodes
869+
// This is used to simplify templating of the control-plane stack template.
838870
EtcdNodes []derived.EtcdNode
839871

840872
AssetsConfig *CompactAssets
873+
874+
KubeAwsPlugins map[string]*pluginmodel.Plugin
875+
876+
APIServerVolumes pluginmodel.APIServerVolumes
877+
APIServerFlags pluginmodel.APIServerFlags
841878
}
842879

843880
// StackName returns the logical name of a CloudFormation stack resource in a root stack template
@@ -1460,6 +1497,128 @@ func (c *Config) ManagedELBLogicalNames() []string {
14601497
return c.APIEndpoints.ManagedELBLogicalNames()
14611498
}
14621499

1500+
type kubernetesManifestPlugin struct {
1501+
Manifests []pluggedInKubernetesManifest
1502+
}
1503+
1504+
func (p kubernetesManifestPlugin) ManifestListFile() node.UploadedFile {
1505+
paths := []string{}
1506+
for _, m := range p.Manifests {
1507+
paths = append(paths, m.ManifestFile.Path)
1508+
}
1509+
bytes := []byte(strings.Join(paths, "\n"))
1510+
return node.UploadedFile{
1511+
Path: p.listFilePath(),
1512+
Content: node.NewUploadedFileContent(bytes),
1513+
}
1514+
}
1515+
1516+
func (p kubernetesManifestPlugin) listFilePath() string {
1517+
return "/srv/kube-aws/plugins/kubernetes-manifests"
1518+
}
1519+
1520+
func (p kubernetesManifestPlugin) Directory() string {
1521+
return filepath.Dir(p.listFilePath())
1522+
}
1523+
1524+
type pluggedInKubernetesManifest struct {
1525+
ManifestFile node.UploadedFile
1526+
}
1527+
1528+
type helmReleasePlugin struct {
1529+
Releases []pluggedInHelmRelease
1530+
}
1531+
1532+
func (p helmReleasePlugin) ReleaseListFile() node.UploadedFile {
1533+
paths := []string{}
1534+
for _, r := range p.Releases {
1535+
paths = append(paths, r.ReleaseFile.Path)
1536+
}
1537+
bytes := []byte(strings.Join(paths, "\n"))
1538+
return node.UploadedFile{
1539+
Path: p.listFilePath(),
1540+
Content: node.NewUploadedFileContent(bytes),
1541+
}
1542+
}
1543+
1544+
func (p helmReleasePlugin) listFilePath() string {
1545+
return "/srv/kube-aws/plugins/helm-releases"
1546+
}
1547+
1548+
func (p helmReleasePlugin) Directory() string {
1549+
return filepath.Dir(p.listFilePath())
1550+
}
1551+
1552+
type pluggedInHelmRelease struct {
1553+
ValuesFile node.UploadedFile
1554+
ReleaseFile node.UploadedFile
1555+
}
1556+
1557+
func (c *Config) KubernetesManifestPlugin() kubernetesManifestPlugin {
1558+
manifests := []pluggedInKubernetesManifest{}
1559+
for pluginName, _ := range c.PluginConfigs {
1560+
plugin, ok := c.KubeAwsPlugins[pluginName]
1561+
if !ok {
1562+
panic(fmt.Errorf("Plugin %s is requested but not loaded. Probably a typo in the plugin name inside cluster.yaml?", pluginName))
1563+
}
1564+
for _, manifestConfig := range plugin.Configuration.Kubernetes.Manifests {
1565+
bytes := []byte(manifestConfig.Contents.Inline)
1566+
m := pluggedInKubernetesManifest{
1567+
ManifestFile: node.UploadedFile{
1568+
Path: filepath.Join("/srv/kube-aws/plugins", plugin.Metadata.Name, manifestConfig.Name),
1569+
Content: node.NewUploadedFileContent(bytes),
1570+
},
1571+
}
1572+
manifests = append(manifests, m)
1573+
}
1574+
}
1575+
p := kubernetesManifestPlugin{
1576+
Manifests: manifests,
1577+
}
1578+
return p
1579+
}
1580+
1581+
func (c *Config) HelmReleasePlugin() helmReleasePlugin {
1582+
releases := []pluggedInHelmRelease{}
1583+
for pluginName, _ := range c.PluginConfigs {
1584+
plugin := c.KubeAwsPlugins[pluginName]
1585+
for _, releaseConfig := range plugin.Configuration.Helm.Releases {
1586+
valuesFilePath := filepath.Join("/srv/kube-aws/plugins", plugin.Metadata.Name, "helm", "releases", releaseConfig.Name, "values.yaml")
1587+
valuesFileContent, err := json.Marshal(releaseConfig.Values)
1588+
if err != nil {
1589+
panic(fmt.Errorf("Unexpected error in HelmReleasePlugin: %v", err))
1590+
}
1591+
releaseFileData := map[string]interface{}{
1592+
"values": map[string]string{
1593+
"file": valuesFilePath,
1594+
},
1595+
"chart": map[string]string{
1596+
"name": releaseConfig.Name,
1597+
"version": releaseConfig.Version,
1598+
},
1599+
}
1600+
releaseFilePath := filepath.Join("/srv/kube-aws/plugins", plugin.Metadata.Name, "helm", "releases", releaseConfig.Name, "release.json")
1601+
releaseFileContent, err := json.Marshal(releaseFileData)
1602+
if err != nil {
1603+
panic(fmt.Errorf("Unexpected error in HelmReleasePlugin: %v", err))
1604+
}
1605+
r := pluggedInHelmRelease{
1606+
ValuesFile: node.UploadedFile{
1607+
Path: valuesFilePath,
1608+
Content: node.NewUploadedFileContent(valuesFileContent),
1609+
},
1610+
ReleaseFile: node.UploadedFile{
1611+
Path: releaseFilePath,
1612+
Content: node.NewUploadedFileContent(releaseFileContent),
1613+
},
1614+
}
1615+
releases = append(releases, r)
1616+
}
1617+
}
1618+
p := helmReleasePlugin{}
1619+
return p
1620+
}
1621+
14631622
func WithTrailingDot(s string) string {
14641623
if s == "" {
14651624
return s

core/controlplane/config/stack_config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import (
99
"github.com/kubernetes-incubator/kube-aws/model"
1010
)
1111

12+
// StackConfig contains configuration parameters available when rendering CFN stack template from golang text templates
1213
type StackConfig struct {
1314
*Config
1415
StackTemplateOptions
1516
UserDataController model.UserData
1617
UserDataEtcd model.UserData
1718
ControllerSubnetIndex int
19+
ExtraCfnResources map[string]interface{}
1820
}
1921

2022
func (c *StackConfig) s3Folders() model.S3Folders {

0 commit comments

Comments
 (0)