Skip to content

Commit

Permalink
Cluster / InstanceGroup File Assets
Browse files Browse the repository at this point in the history
The current implementation does not make it ease to fully customize nodes before kube install. This PR adds the ability to include file assets in the cluster and instaneGroup spec which can be consumed by nodeup. Allowing those whom need (i.e. me :-)) greater flexibilty around their nodes. @note, nothing is enforced, so unless you've specified anything everything is as the same

- updated the cluster_spec.md to reflect the changes
- permit users to place inline files into the cluster and instance group specs
- added the ability to template the files, the Cluster and InstanceGroup specs are passed into context
- cleaned up and missed comment, unordered imports etc along the journey
  • Loading branch information
gambol99 committed Aug 2, 2017
1 parent 366f38b commit 5e7c7a9
Show file tree
Hide file tree
Showing 18 changed files with 616 additions and 57 deletions.
36 changes: 36 additions & 0 deletions docs/cluster_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,42 @@ spec:
image: busybox
```

### fileAssets

FileAssets permit you to place inline file content into the cluster and instanceGroup specification, which can be consumed on the nodes

```yaml
spec:
fileAssets:
- name: iptable-restore
path: /var/lib/iptables/rules-save
mode: 0440
content: |
some file content
# you can also template the file, the Cluster, InstanceGroup is passed to the template context,
- name: iptable-restore
path: /var/lib/iptables/rules-save
templated: true
mode: 0440
content: |
some file content
*filter
:INPUT ACCEPT [0:0]
-A INPUT -p tcp -m state -s {{ .Cluster.NonMasqueradeCIDR }} --dport 22 --state NEW -j REJECT
:FORWARD ACCEPT [0:0]
-A FORWARD -i docker0 -p tcp -m tcp -d 169.254.169.254/32 --dport 80 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp --dport 2379 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp -d {{ .Cluster.NetworkCIDR }} --dport 22 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp -d {{ .Cluster.NetworkCIDR }} --dport 10250 -m state --state NEW -j REJECT
{{ if .Master "true" }}
# add something for the master nodes etc
{{- end }}
:OUTPUT ACCEPT [0:0]
COMMIT
```


### cloudConfig

If you are using aws as `cloudProvider`, you can disable authorization of ELB security group to Kubernetes Nodes security group. In other words, it will not add security group rule.
Expand Down
5 changes: 3 additions & 2 deletions nodeup/pkg/model/cloudconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ package model
import (
"bufio"
"fmt"
"os"
"strings"

"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/util"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
"os"
"strings"
)

const CloudConfigFilePath = "/etc/kubernetes/cloud.config"
Expand Down
154 changes: 154 additions & 0 deletions nodeup/pkg/model/file_assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"

"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/model"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
)

// FileAssetsBuilder configures the hooks
type FileAssetsBuilder struct {
*NodeupModelContext
}

var _ fi.ModelBuilder = &FileAssetsBuilder{}

// Build is responsible for writing out the file assets from cluster and instanceGroup
func (f *FileAssetsBuilder) Build(c *fi.ModelBuilderContext) error {
// used to keep track of previous file, so a instanceGroup can override a cluster wide one
tracker := make(map[string]bool, 0)
// do we have any instanceGroup file assets
if f.InstanceGroup.Spec.FileAssets != nil {
if err := f.buildFileAssets(c, f.InstanceGroup.Spec.FileAssets, tracker); err != nil {
return err
}
}
if f.Cluster.Spec.FileAssets != nil {
if err := f.buildFileAssets(c, f.Cluster.Spec.FileAssets, tracker); err != nil {
return err
}
}

return nil
}

// buildFileAssets is responsible for rendering the file assets to disk
func (f *FileAssetsBuilder) buildFileAssets(c *fi.ModelBuilderContext, assets []*kops.FileAssetSpec, tracker map[string]bool) error {
for _, asset := range assets {
if err := validateFileAsset(asset); err != nil {
return fmt.Errorf("The file asset is invalid, name: %s, error: %q", asset.Name, err)
}
// @check if the file has already been done
if _, found := tracker[asset.Path]; found {
continue
}
tracker[asset.Path] = true // update the tracker

// fill in the defaults for the file perms
if asset.Mode == "" {
asset.Mode = "0400"
}
// @check is the contents requires decoding
content := asset.Content
if asset.IsBase64 {
decoded, err := base64.RawStdEncoding.DecodeString(content)
if err != nil {
return fmt.Errorf("Failed on file asset: %s is invalid, unable to decode base64, error: %q", asset.Name, err)
}
content = string(decoded)
}

// @check if the directory structure exist or create it
if err := os.MkdirAll(filepath.Dir(asset.Path), 0755); err != nil {
return fmt.Errorf("Failed on file asset: %s, unable to ensure directory: %s, error: %q", asset.Name,
filepath.Dir(asset.Path), err)
}

// @check the file permissions
perms := asset.Mode
if !strings.HasPrefix(perms, "0") {
perms = fmt.Sprintf("%d%s", 0, perms)
}

var resource fi.Resource
var err error
switch asset.Templated {
case true:
resource, err = f.getRenderedResource(content)
if err != nil {
return fmt.Errorf("Failed on file assets: %s, build rendered resource, error: %q", asset.Name, err)
}
default:
resource = fi.NewStringResource(content)
}

c.AddTask(&nodetasks.File{
Contents: resource,
Mode: s(perms),
Path: asset.Path,
Type: nodetasks.FileType_File,
})
}

return nil
}

// @perhaps a path finder?
var templateFuncs = template.FuncMap{
"split": strings.Split,
"join": strings.Join,
}

// getRenderedResource is responsible for rendering the content if templated
func (f *FileAssetsBuilder) getRenderedResource(content string) (fi.Resource, error) {
context := map[string]interface{}{
"Cluster": f.Cluster.Spec,
"InstanceGroup": f.InstanceGroup.Spec,
"Master": fmt.Sprintf("%t", f.IsMaster),
"Name": f.InstanceGroup.Name,
}

resource, err := model.NewTemplateResource("FileAsset", content, templateFuncs, context)
if err != nil {
return nil, err
}

return resource, nil
}

// validateFileAsset performs some basic validation on the asset
func validateFileAsset(asset *kops.FileAssetSpec) error {
if asset.Path == "" {
return errors.New("does not have a path")
}
if asset.Content == "" {
return errors.New("does not have any contents")
}

return nil
}
123 changes: 123 additions & 0 deletions nodeup/pkg/model/file_assets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
"io/ioutil"
"testing"

"k8s.io/kops/pkg/apis/kops"
)

func TestFileAssetRenderOK(t *testing.T) {
cs := []struct {
Content string
Expected string
}{
{},
{
Content: "{{ .Cluster.NetworkCIDR }}",
Expected: "10.79.0.0/24",
},
{
Content: `
*filter
:INPUT ACCEPT [0:0]
-A INPUT -i docker0 -p tcp -m tcp --dport 22 -m state --state NEW -j REJECT
-A INPUT -i docker0 -p tcp -m tcp --dport 10250 -m state --state NEW -j REJECT
-A INPUT -p tcp -m state -s {{ .Cluster.NonMasqueradeCIDR }} --dport 22 --state NEW -j REJECT
:FORWARD ACCEPT [0:0]
-A FORWARD -i docker0 -p tcp -m tcp -d 169.254.169.254/32 --dport 80 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp --dport 2379 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp -d {{ .Cluster.NetworkCIDR }} --dport 22 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp -d {{ .Cluster.NetworkCIDR }} --dport 10250 -m state --state NEW -j REJECT
:OUTPUT ACCEPT [0:0]
COMMIT`,
Expected: `
*filter
:INPUT ACCEPT [0:0]
-A INPUT -i docker0 -p tcp -m tcp --dport 22 -m state --state NEW -j REJECT
-A INPUT -i docker0 -p tcp -m tcp --dport 10250 -m state --state NEW -j REJECT
-A INPUT -p tcp -m state -s 10.100.0.0/16 --dport 22 --state NEW -j REJECT
:FORWARD ACCEPT [0:0]
-A FORWARD -i docker0 -p tcp -m tcp -d 169.254.169.254/32 --dport 80 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp --dport 2379 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp -d 10.79.0.0/24 --dport 22 -m state --state NEW -j REJECT
-A FORWARD -i docker0 -p tcp -m tcp -d 10.79.0.0/24 --dport 10250 -m state --state NEW -j REJECT
:OUTPUT ACCEPT [0:0]
COMMIT`,
},
}
cluster := makeTestCluster()
group := makeTestInstanceGroup()

for i, x := range cs {
fb := &FileAssetsBuilder{
NodeupModelContext: &NodeupModelContext{Cluster: cluster, InstanceGroup: group},
}
resource, err := fb.getRenderedResource(x.Content)
if err != nil {
t.Errorf("case %d failed to create resource. error: %s", i, err)
continue
}
reader, err := resource.Open()
if err != nil {
t.Errorf("case %d failed to render resource, error: %s", i, err)
continue
}
rendered, err := ioutil.ReadAll(reader)
if err != nil {
t.Errorf("case %d failed to read reander, error: %s", i, err)
continue
}

if string(rendered) != x.Expected {
t.Errorf("case %d, expected: %s. got: %s", i, x.Expected, rendered)
}
}
}

func makeTestCluster() *kops.Cluster {
return &kops.Cluster{
Spec: kops.ClusterSpec{
CloudProvider: "aws",
KubernetesVersion: "1.7.0",
Subnets: []kops.ClusterSubnetSpec{
{Name: "test", Zone: "eu-west-1a"},
},
NonMasqueradeCIDR: "10.100.0.0/16",
EtcdClusters: []*kops.EtcdClusterSpec{
{
Name: "main",
Members: []*kops.EtcdMemberSpec{
{
Name: "test",
InstanceGroup: s("master-1"),
},
},
},
},
NetworkCIDR: "10.79.0.0/24",
},
}
}

func makeTestInstanceGroup() *kops.InstanceGroup {
return &kops.InstanceGroup{
Spec: kops.InstanceGroupSpec{},
}
}
7 changes: 3 additions & 4 deletions nodeup/pkg/model/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ limitations under the License.
package model

import (
"github.com/golang/glog"
"k8s.io/kops/nodeup/pkg/distros"
"k8s.io/kops/pkg/systemd"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"

"github.com/golang/glog"
)

// FirewallBuilder configures the firewall (iptables)
Expand All @@ -31,10 +32,10 @@ type FirewallBuilder struct {

var _ fi.ModelBuilder = &FirewallBuilder{}

// Build is responsible for generating any node firewall rules
func (b *FirewallBuilder) Build(c *fi.ModelBuilderContext) error {
if b.Distribution == distros.DistributionContainerOS {
c.AddTask(b.buildFirewallScript())

c.AddTask(b.buildSystemdService())
}

Expand All @@ -46,11 +47,9 @@ func (b *FirewallBuilder) buildSystemdService() *nodetasks.Service {
manifest.Set("Unit", "Description", "Configure iptables for kubernetes")
manifest.Set("Unit", "Documentation", "https://github.com/kubernetes/kops")
manifest.Set("Unit", "Before", "network.target")

manifest.Set("Service", "Type", "oneshot")
manifest.Set("Service", "RemainAfterExit", "yes")
manifest.Set("Service", "ExecStart", "/home/kubernetes/bin/iptables-setup")

manifest.Set("Install", "WantedBy", "basic.target")

manifestString := manifest.Render()
Expand Down
6 changes: 4 additions & 2 deletions nodeup/pkg/model/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ package model

import (
"fmt"
"github.com/blang/semver"
"github.com/golang/glog"

"k8s.io/client-go/pkg/api/v1"
"k8s.io/kops/nodeup/pkg/distros"
"k8s.io/kops/pkg/apis/kops"
Expand All @@ -29,6 +28,9 @@ import (
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks"
"k8s.io/kops/upup/pkg/fi/utils"

"github.com/blang/semver"
"github.com/golang/glog"
)

// KubeletBuilder install kubelet
Expand Down
Loading

0 comments on commit 5e7c7a9

Please sign in to comment.