Add packet metadata support, with bonding

Use the packet metadata to set up bonding, also get ssh keys and hostname.

This does not yet do anything with disk metadata.

Userdata is not used if it has been used for ipxe, but is otherwise available.

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
Justin Cormack 2017-08-18 20:53:59 +01:00
parent d01438acd4
commit 92451cf2e4
3 changed files with 190 additions and 4 deletions

View File

@ -1,12 +1,12 @@
FROM linuxkit/alpine:87a0cd10449d72f374f950004467737dbf440630 AS mirror
RUN apk add --no-cache go musl-dev
RUN apk add --no-cache go musl-dev linux-headers
ENV GOPATH=/go PATH=$PATH:/go/bin
COPY . /go/src/metadata/
RUN go-compile.sh /go/src/metadata
RUN mkdir -p out/tmp out/var out/dev out/etc
RUN mkdir -p out/tmp out/var out/dev out/etc out/etc/ssl/certs
FROM scratch
ENTRYPOINT []
@ -15,4 +15,4 @@ WORKDIR /
COPY --from=mirror /go/bin/metadata /usr/bin/metadata
COPY --from=mirror /out/ /
CMD ["/usr/bin/metadata"]
LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var", "/etc/resolv.conf:/etc/resolv.conf"], "tmpfs": ["/tmp"], "readonly": true, "capabilities": ["CAP_SYS_ADMIN"]}'
LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var", "/sys:/sys", "/etc/resolv.conf:/etc/resolv.conf", "/etc/ssl/certs:/etc/ssl/certs"], "tmpfs": ["/tmp"], "readonly": true, "capabilities": ["CAP_SYS_ADMIN", "CAP_NET_ADMIN"]}'

View File

@ -41,7 +41,7 @@ var netProviders []Provider
var cdromProviders []Provider
func main() {
providers := []string{"aws", "gcp", "vultr", "cdrom"}
providers := []string{"aws", "gcp", "vultr", "packet", "cdrom"}
if len(os.Args) > 1 {
providers = os.Args[1:]
}
@ -51,6 +51,8 @@ func main() {
netProviders = append(netProviders, NewAWS())
case "gcp":
netProviders = append(netProviders, NewGCP())
case "packet":
netProviders = append(netProviders, NewPacket())
case "vultr":
netProviders = append(netProviders, NewVultr())
case "cdrom":

View File

@ -0,0 +1,184 @@
package main
import (
"fmt"
"io/ioutil"
"net"
"os"
"path"
"strconv"
"strings"
"github.com/packethost/packngo/metadata"
"github.com/vishvananda/netlink"
)
// ProviderPacket is the type implementing the Provider interface for Packet.net
type ProviderPacket struct {
metadata *metadata.CurrentDevice
err error
}
// NewPacket returns a new ProviderPacket
func NewPacket() *ProviderPacket {
return &ProviderPacket{}
}
func (p *ProviderPacket) String() string {
return "Packet"
}
// Probe checks if we are running on Packet
func (p *ProviderPacket) Probe() bool {
// Unfortunately the host is resolveable globally, so no easy test
p.metadata, p.err = metadata.GetMetadata()
return p.err == nil
}
// Extract gets both the Packet specific and generic userdata
func (p *ProviderPacket) Extract() ([]byte, error) {
// do not retrieve if we Probed
if p.metadata == nil && p.err == nil {
p.metadata, p.err = metadata.GetMetadata()
if p.err != nil {
return nil, p.err
}
} else if p.err != nil {
return nil, p.err
}
if err := ioutil.WriteFile(path.Join(ConfigPath, Hostname), []byte(p.metadata.Hostname), 0644); err != nil {
return nil, fmt.Errorf("Packet: Failed to write hostname: %s", err)
}
if err := os.MkdirAll(path.Join(ConfigPath, SSH), 0755); err != nil {
return nil, fmt.Errorf("Failed to create %s: %s", SSH, err)
}
sshKeys := strings.Join(p.metadata.SSHKeys, "\n")
if err := ioutil.WriteFile(path.Join(ConfigPath, SSH, "authorized_keys"), []byte(sshKeys), 0600); err != nil {
return nil, fmt.Errorf("Failed to write ssh keys: %s", err)
}
if err := networkConfig(p.metadata.Network); err != nil {
return nil, err
}
userData, err := metadata.GetUserData()
if err != nil {
return nil, fmt.Errorf("Packet: failed to get userdata: %s", err)
}
if len(userData) == 0 {
return nil, nil
}
if len(userData) > 6 && string(userData[0:6]) == "#!ipxe" {
// if you use the userdata for ipxe boot, no use as userdata
return nil, nil
}
return userData, nil
}
// networkConfig handles Packet network configuration, primarily bonding
func networkConfig(ni metadata.NetworkInfo) error {
// rename interfaces to match what the metadata calls them
links, err := netlink.LinkList()
if err != nil {
return fmt.Errorf("Failed to list links: %v", err)
}
for _, link := range links {
attrs := link.Attrs()
mac := attrs.HardwareAddr.String()
for _, iface := range ni.Interfaces {
if iface.MAC == mac {
// remove existing addresses from link
addresses, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return fmt.Errorf("Cannot list addresses on interface: %v", err)
}
for _, addr := range addresses {
if err := netlink.AddrDel(link, &addr); err != nil {
return fmt.Errorf("Cannot remove address from interface: %v", err)
}
}
// links need to be down to be bonded
if err := netlink.LinkSetDown(link); err != nil {
return fmt.Errorf("Cannot down link: %v", err)
}
// see if we need to rename interface
if iface.Name != attrs.Name {
if err := netlink.LinkSetName(link, iface.Name); err != nil {
return fmt.Errorf("Interface rename failed: %v", err)
}
}
}
}
}
// set up bonding
la := netlink.LinkAttrs{Name: "bond0"}
bond := &netlink.GenericLink{la, "bond"}
if err := netlink.LinkAdd(bond); err != nil {
// weirdly creating a bind always seems to return EEXIST
fmt.Fprintf(os.Stderr, "Error adding bond0: %v (ignoring)", err)
}
if err := ioutil.WriteFile("/sys/class/net/bond0/bonding/mode", []byte(strconv.Itoa(int(ni.Bonding.Mode))), 0); err != nil {
return fmt.Errorf("Cannot write to /sys/class/net/bond0/bonding/mode: %v", err)
}
if err := netlink.LinkSetUp(bond); err != nil {
return fmt.Errorf("Failed to bring bond0 up: %v", err)
}
for _, iface := range ni.Interfaces {
link, err := netlink.LinkByName(iface.Name)
if err != nil {
return fmt.Errorf("Cannot find interface %s: %v", iface.Name, err)
}
if err := netlink.LinkSetMasterByIndex(link, bond.Attrs().Index); err != nil {
return fmt.Errorf("Cannot join %s to bond0: %v", iface.Name, err)
}
}
// set up addresses and routes
for _, address := range ni.Addresses {
cidr := "/" + strconv.Itoa(address.NetworkBits)
addr, err := netlink.ParseAddr(address.Address.String() + cidr)
if err != nil {
return err
}
if err := netlink.AddrAdd(bond, addr); err != nil {
return fmt.Errorf("Failed to add address to bonded interface: %v", err)
}
var additionalNet string
switch {
case address.Public && address.Family == metadata.IPv4:
additionalNet = "0.0.0.0/0"
case address.Public && address.Family == metadata.IPv6:
additionalNet = "::/0"
case !address.Public && address.Family == metadata.IPv4:
// add a gateway for eg 10.0.0.0/8
mask := address.Address.DefaultMask()
masked := address.Address.Mask(mask)
network := &net.IPNet{IP: masked, Mask: mask}
additionalNet = network.String()
}
if additionalNet != "" {
_, network, err := net.ParseCIDR(additionalNet)
if err != nil {
return err
}
route := netlink.Route{
LinkIndex: bond.Attrs().Index,
Gw: address.Gateway,
Dst: network,
}
if err := netlink.RouteAdd(&route); err != nil {
return fmt.Errorf("Failed to add route: %v", err)
}
}
}
return nil
}