talos/internal/pkg/upgrade/upgrade.go
Andrew Rynhard ca35b85300 refactor: improve installation reliability
This change aims to make installations more unified and reliable. It
introduces the concept of a mountpoint manager that is capable of
mounting, unmounting, and moving a set of mountpoints in the correct
order.

Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
2019-08-01 11:44:40 -07:00

194 lines
4.4 KiB
Go

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package upgrade
import (
"context"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/pkg/errors"
"github.com/talos-systems/talos/internal/pkg/constants"
"github.com/talos-systems/talos/internal/pkg/installer/bootloader/syslinux"
"github.com/talos-systems/talos/internal/pkg/installer/manifest"
"github.com/talos-systems/talos/internal/pkg/kernel"
"github.com/talos-systems/talos/internal/pkg/kubernetes"
"github.com/talos-systems/talos/pkg/userdata"
"go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/pkg/transport"
yaml "gopkg.in/yaml.v2"
)
// NewUpgrade initiates a Talos upgrade
// nolint: gocyclo
func NewUpgrade(url string) (err error) {
var hostname string
if hostname, err = os.Hostname(); err != nil {
return
}
data, err := userdata.Open(constants.UserDataPath)
if err != nil {
return err
}
if data, err = data.Upgrade(); err != nil {
return err
}
dataBytes, err := yaml.Marshal(data)
if err != nil {
return err
}
if err = ioutil.WriteFile(constants.UserDataPath, dataBytes, 0400); err != nil {
return err
}
if err = upgradeBoot(url); err != nil {
return err
}
// cordon/drain
var kubeHelper *kubernetes.Helper
if kubeHelper, err = kubernetes.NewHelper(); err != nil {
return err
}
if err = kubeHelper.CordonAndDrain(hostname); err != nil {
return err
}
if data.Services.Kubeadm.IsControlPlane() {
var hostname string
if hostname, err = os.Hostname(); err != nil {
return
}
if err = leaveEtcd(hostname); err != nil {
return err
}
if err = os.RemoveAll("/var/lib/etcd"); err != nil {
return err
}
}
return err
}
func upgradeBoot(url string) error {
bootTarget := manifest.Target{
Label: constants.BootPartitionLabel,
MountPoint: constants.BootMountPoint,
Assets: []*manifest.Asset{},
}
// Kernel
bootTarget.Assets = append(bootTarget.Assets, &manifest.Asset{
Source: url + "/" + constants.KernelAsset,
Destination: filepath.Join("/", "default", constants.KernelAsset),
})
// Initramfs
bootTarget.Assets = append(bootTarget.Assets, &manifest.Asset{
Source: url + "/" + constants.InitramfsAsset,
Destination: filepath.Join("/", "default", constants.InitramfsAsset),
})
var err error
if err = bootTarget.Save(); err != nil {
return err
}
// TODO: Figure out a method to update kernel args
nextCmdline := kernel.NewCmdline(kernel.ProcCmdline().String())
// Set the initrd kernel paramaeter.
initParam := kernel.NewParameter("initrd")
initParam.Append(filepath.Join("/", "default", constants.InitramfsAsset))
if initrd := nextCmdline.Get("initrd"); initrd == nil {
nextCmdline.Append("initrd", *(initParam.First()))
} else {
nextCmdline.Set("initrd", initParam)
}
// Create bootloader config
syslinuxcfg := &syslinux.Cfg{
Default: "default",
Labels: []*syslinux.Label{
{
Root: "default",
Kernel: filepath.Join("/", "default", constants.KernelAsset),
Initrd: filepath.Join("/", "default", constants.InitramfsAsset),
Append: nextCmdline.String(),
},
},
}
return syslinux.Install(constants.BootMountPoint, syslinuxcfg)
}
// Reset calls kubeadm reset to clean up a kubernetes installation
func Reset() (err error) {
// TODO find some way to flex on debug info
// can add in -v10 for additional debugging
cmd := exec.Command(
"kubeadm",
"reset",
"--force",
)
return cmd.Run()
}
func leaveEtcd(hostname string) (err error) {
tlsInfo := transport.TLSInfo{
CertFile: constants.KubeadmEtcdPeerCert,
KeyFile: constants.KubeadmEtcdPeerKey,
TrustedCAFile: constants.KubeadmEtcdCACert,
}
tlsConfig, err := tlsInfo.ClientConfig()
if err != nil {
return err
}
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: 5 * time.Second,
TLS: tlsConfig,
})
if err != nil {
return err
}
// nolint: errcheck
defer cli.Close()
resp, err := cli.MemberList(context.Background())
if err != nil {
return err
}
var id *uint64
for _, member := range resp.Members {
if member.Name == hostname {
id = &member.ID
}
}
if id == nil {
return errors.Errorf("failed to find %q in list of etcd members", hostname)
}
log.Println("leaving etcd cluster")
_, err = cli.MemberRemove(context.Background(), *id)
if err != nil {
return err
}
return nil
}