Skip to content

Commit

Permalink
gossip: Seed /etc/hosts in nodeup
Browse files Browse the repository at this point in the history
In some scenarios (e.g. cilium), we rely on the internal DNS name
being available, but this isn't the case with gossip clusters.

nodeup can seed /etc/hosts for the control-plane nodes, breaking the
deadlock.
  • Loading branch information
justinsb committed Oct 19, 2021
1 parent f8a8c01 commit 71264d5
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 32 deletions.
14 changes: 14 additions & 0 deletions nodeup/pkg/model/dns/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions nodeup/pkg/model/dns/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Copyright 2021 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 dns

import (
"k8s.io/kops/nodeup/pkg/model"
"k8s.io/kops/pkg/dns"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks/dnstasks"
)

// GossipBuilder seeds some hostnames into /etc/hosts, avoiding some circular dependencies.
type GossipBuilder struct {
*model.NodeupModelContext
}

var _ fi.ModelBuilder = &GossipBuilder{}

// Build is responsible for configuring the gossip DNS tasks.
func (b *GossipBuilder) Build(c *fi.ModelBuilderContext) error {
useGossip := dns.IsGossipHostname(b.Cluster.Spec.MasterInternalName)
if !useGossip {
return nil
}

if b.IsMaster {
task := &dnstasks.UpdateEtcHostsTask{
Name: "control-plane-bootstrap",
}

if b.Cluster.Spec.MasterInternalName != "" {
task.Records = append(task.Records, dnstasks.HostRecord{
Hostname: b.Cluster.Spec.MasterInternalName,
Addresses: []string{"127.0.0.1"},
})
}
if b.Cluster.Spec.MasterPublicName != "" {
task.Records = append(task.Records, dnstasks.HostRecord{
Hostname: b.Cluster.Spec.MasterPublicName,
Addresses: []string{"127.0.0.1"},
})
}

if len(task.Records) != 0 {
c.AddTask(task)
}
}

return nil
}
30 changes: 18 additions & 12 deletions protokube/pkg/gossip/dns/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,29 @@ var _ DNSTarget = &HostsFile{}
func (h *HostsFile) Update(snapshot *DNSViewSnapshot) error {
klog.V(2).Infof("Updating hosts file with snapshot version %v", snapshot.version)

addrToHosts := make(map[string][]string)
mutator := func(existing []string) (*hosts.HostMap, error) {
hostMap := &hosts.HostMap{}
badLines := hostMap.Parse(existing)
if len(badLines) != 0 {
klog.Warningf("ignoring unexpected lines in /etc/hosts: %v", badLines)
}

zones := snapshot.ListZones()
for _, zone := range zones {
records := snapshot.RecordsForZone(zone)
zones := snapshot.ListZones()
for _, zone := range zones {
records := snapshot.RecordsForZone(zone)

for _, record := range records {
if record.RrsType != "A" {
klog.Warningf("skipping record of unhandled type: %v", record)
continue
}
for _, record := range records {
if record.RrsType != "A" {
klog.Warningf("skipping record of unhandled type: %v", record)
continue
}

for _, addr := range record.Rrdatas {
addrToHosts[addr] = append(addrToHosts[addr], record.Name)
hostMap.ReplaceRecords(record.Name, record.Rrdatas)
}
}

return hostMap, nil
}

return hosts.UpdateHostsFileWithRecords(h.Path, addrToHosts)
return hosts.UpdateHostsFileWithRecords(h.Path, mutator)
}
86 changes: 84 additions & 2 deletions protokube/pkg/gossip/dns/hosts/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,77 @@ const (

var hostsFileMutex sync.Mutex

func UpdateHostsFileWithRecords(p string, addrToHosts map[string][]string) error {
// HostMap holds a set of host to address mappings, a simplification of /etc/hosts.
type HostMap struct {
records []hostMapRecord
}

// ParseHostMap parses lines from /etc/hosts (expected to be our guarded block) into a HostMap structure.
// It parses as much as it can, and returns invalid lines (which should ideally be empty)
func (m *HostMap) Parse(existing []string) []string {
var badLines []string

for _, line := range existing {
tokens := strings.Fields(line)
if len(tokens) == 0 {
continue
}
if strings.HasPrefix(tokens[0], "#") {
// Comments shouldn't really happen in our guarded block
if line == GUARD_BEGIN || line == GUARD_END {
klog.Warningf("ignoring extra guard line in /etc/hosts: %q", line)
} else {
badLines = append(badLines, line)
}
continue
}

if len(tokens) == 1 {
badLines = append(badLines, line)
continue
}

address := tokens[0]
for _, hostname := range tokens[1:] {
m.records = append(m.records, hostMapRecord{
Address: address,
Hostname: hostname,
})
}
}

return badLines
}

// hostMap holds a single host-name to address mapping.
type hostMapRecord struct {
Hostname string
Address string
}

// ReplaceRecords replaces all the addresses for the given hostname.
func (m *HostMap) ReplaceRecords(hostname string, addresses []string) {
var newRecords []hostMapRecord

for _, address := range addresses {
newRecords = append(newRecords, hostMapRecord{
Hostname: hostname,
Address: address,
})
}

for _, record := range m.records {
if record.Hostname == hostname {
continue
}
newRecords = append(newRecords, record)
}

m.records = newRecords
}

// UpdateHostsFileWithRecords updates /etc/hosts by applying the given mutation function.
func UpdateHostsFileWithRecords(p string, mutator func(guarded []string) (*HostMap, error)) error {
// For safety / sanity, we avoid concurrent updates from one process
hostsFileMutex.Lock()
defer hostsFileMutex.Unlock()
Expand All @@ -52,6 +122,7 @@ func UpdateHostsFileWithRecords(p string, addrToHosts map[string][]string) error
return fmt.Errorf("error reading file %q: %v", p, err)
}

var guarded []string
var out []string
inGuardBlock := false
for _, line := range strings.Split(string(data), "\n") {
Expand All @@ -63,7 +134,9 @@ func UpdateHostsFileWithRecords(p string, addrToHosts map[string][]string) error
inGuardBlock = true
}

if !inGuardBlock {
if inGuardBlock {
guarded = append(guarded, line)
} else {
out = append(out, line)
}

Expand Down Expand Up @@ -92,7 +165,16 @@ func UpdateHostsFileWithRecords(p string, addrToHosts map[string][]string) error
}
out = append(out, "")

hosts, err := mutator(guarded)
if err != nil {
return err
}

var block []string
addrToHosts := make(map[string][]string)
for _, record := range hosts.records {
addrToHosts[record.Address] = append(addrToHosts[record.Address], record.Hostname)
}
for addr, hosts := range addrToHosts {
sort.Strings(hosts)
block = append(block, addr+"\t"+strings.Join(hosts, " "))
Expand Down
44 changes: 27 additions & 17 deletions protokube/pkg/gossip/dns/hosts/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

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

func TestRemovesDuplicateGuardedBlocks(t *testing.T) {
in := `
foo 10.2.3.4
10.2.3.4 foo
# Begin host entries managed by etcd-manager[etcd] - do not edit
# End host entries managed by etcd-manager[etcd]
Expand All @@ -53,17 +52,17 @@ foo 10.2.3.4
`

expected := `
foo 10.2.3.4
10.2.3.4 foo
# Begin host entries managed by etcd-manager[etcd] - do not edit
# End host entries managed by etcd-manager[etcd]
# Begin host entries managed by etcd-manager[etcd] - do not edit
# End host entries managed by etcd-manager[etcd]
# Begin host entries managed by kops - do not edit
a\t10.0.1.1 10.0.1.2
b\t10.0.2.1
c\t
10.0.1.1 a
10.0.1.2 a
10.0.2.1 b
# End host entries managed by kops
`

Expand All @@ -72,7 +71,7 @@ c\t

func TestRecoversFromBadNesting(t *testing.T) {
in := `
foo 10.2.3.4
10.2.3.4 foo
# End host entries managed by kops
# Begin host entries managed by kops - do not edit
Expand All @@ -94,28 +93,26 @@ foo 10.2.3.4
# Begin host entries managed by kops - do not edit
# End host entries managed by kops
bar 10.1.2.3
10.1.2.3 bar
`

expected := `
foo 10.2.3.4
10.2.3.4 foo
bar 10.1.2.3
10.1.2.3 bar
# Begin host entries managed by kops - do not edit
a\t10.0.1.1 10.0.1.2
b\t10.0.2.1
c\t
10.0.1.1 a
10.0.1.2 a
10.0.2.1 b
# End host entries managed by kops
`

runTest(t, in, expected)
}

func runTest(t *testing.T, in string, expected string) {
expected = strings.Replace(expected, "\\t", "\t", -1)

dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
Expand All @@ -128,7 +125,7 @@ func runTest(t *testing.T, in string, expected string) {
}()

p := filepath.Join(dir, "hosts")
addrToHosts := map[string][]string{
namesToAddresses := map[string][]string{
"a": {"10.0.1.2", "10.0.1.1"},
"b": {"10.0.2.1"},
"c": {},
Expand All @@ -140,7 +137,20 @@ func runTest(t *testing.T, in string, expected string) {

// We run it repeatedly to make sure we don't change it accidentally
for i := 0; i < 100; i++ {
if err := UpdateHostsFileWithRecords(p, addrToHosts); err != nil {
mutator := func(existing []string) (*HostMap, error) {
hostMap := &HostMap{}
badLines := hostMap.Parse(existing)
if len(badLines) != 0 {
t.Errorf("unexpected lines in /etc/hosts: %v", badLines)
}

for name, addresses := range namesToAddresses {
hostMap.ReplaceRecords(name, addresses)
}

return hostMap, nil
}
if err := UpdateHostsFileWithRecords(p, mutator); err != nil {
t.Fatalf("error updating hosts file: %v", err)
}

Expand Down
1 change: 1 addition & 0 deletions upup/pkg/fi/nodeup/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions upup/pkg/fi/nodeup/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/aws/aws-sdk-go/service/kms"
"k8s.io/kops/nodeup/pkg/model"
"k8s.io/kops/nodeup/pkg/model/dns"
"k8s.io/kops/nodeup/pkg/model/networking"
api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/registry"
Expand Down Expand Up @@ -300,6 +301,7 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
}

loader := &Loader{}
loader.Builders = append(loader.Builders, &dns.GossipBuilder{NodeupModelContext: modelContext})
loader.Builders = append(loader.Builders, &model.NTPBuilder{NodeupModelContext: modelContext})
loader.Builders = append(loader.Builders, &model.MiscUtilsBuilder{NodeupModelContext: modelContext})
loader.Builders = append(loader.Builders, &model.DirectoryBuilder{NodeupModelContext: modelContext})
Expand Down
1 change: 1 addition & 0 deletions upup/pkg/fi/nodeup/nodetasks/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions upup/pkg/fi/nodeup/nodetasks/dnstasks/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 71264d5

Please sign in to comment.