talos/pkg/kubernetes/kubernetes.go
Andrew Rynhard 90c91807bd refactor: restructure the project layout
This change moves packages into more appropriate places.

Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
2019-08-01 22:19:42 -07:00

155 lines
4.1 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 kubernetes
import (
"log"
"sync"
"time"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// Helper represents a set of helper methods for interacting with the
// Kubernetes API.
type Helper struct {
client *kubernetes.Clientset
}
// NewHelper initializes and returns a Helper.
func NewHelper() (helper *Helper, err error) {
kubeconfig := "/etc/kubernetes/kubelet.conf"
var config *restclient.Config
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, err
}
var clientset *kubernetes.Clientset
clientset, err = kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &Helper{clientset}, nil
}
// CordonAndDrain cordons and drains a node in one call.
func (h *Helper) CordonAndDrain(node string) (err error) {
if err = h.Cordon(node); err != nil {
return err
}
return h.Drain(node)
}
// Cordon marks a node as unschedulable.
func (h *Helper) Cordon(name string) error {
node, err := h.client.CoreV1().Nodes().Get(name, meta.GetOptions{})
if err != nil {
return errors.Wrapf(err, "failed to get node %s", name)
}
if node.Spec.Unschedulable {
return nil
}
node.Spec.Unschedulable = true
if _, err := h.client.CoreV1().Nodes().Update(node); err != nil {
return errors.Wrapf(err, "failed to cordon node %s", node.GetName())
}
return nil
}
// Uncordon marks a node as schedulable.
func (h *Helper) Uncordon(name string) error {
node, err := h.client.CoreV1().Nodes().Get(name, meta.GetOptions{})
if err != nil {
return errors.Wrapf(err, "failed to get node %s", name)
}
if node.Spec.Unschedulable {
node.Spec.Unschedulable = false
if _, err := h.client.CoreV1().Nodes().Update(node); err != nil {
return errors.Wrapf(err, "failed to uncordon node %s", node.GetName())
}
}
return nil
}
// Drain evicts all pods on a given node.
func (h *Helper) Drain(node string) error {
opts := meta.ListOptions{
FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": node}).String(),
}
pods, err := h.client.CoreV1().Pods(meta.NamespaceAll).List(opts)
if err != nil {
return errors.Wrapf(err, "cannot get pods for node %s", node)
}
var wg sync.WaitGroup
wg.Add(len(pods.Items))
// Evict each pod.
for _, pod := range pods.Items {
go func(p corev1.Pod) {
defer wg.Done()
if err := h.evict(p, int64(60)); err != nil {
log.Printf("WARNING: failed to evict pod: %v", err)
}
}(pod)
}
wg.Wait()
return nil
}
func (h *Helper) evict(p corev1.Pod, gracePeriod int64) error {
for {
pol := &policy.Eviction{
ObjectMeta: meta.ObjectMeta{Namespace: p.GetNamespace(), Name: p.GetName()},
DeleteOptions: &meta.DeleteOptions{GracePeriodSeconds: &gracePeriod},
}
err := h.client.CoreV1().Pods(p.GetNamespace()).Evict(pol)
switch {
case apierrors.IsTooManyRequests(err):
time.Sleep(5 * time.Second)
case apierrors.IsNotFound(err):
return nil
case err != nil:
return errors.Wrapf(err, "failed to evict pod %s/%s", p.GetNamespace(), p.GetName())
default:
if err = h.waitForPodDeleted(&p); err != nil {
return errors.Wrapf(err, "failed waiting on pod %s/%s to be deleted", p.GetNamespace(), p.GetName())
}
}
}
}
func (h *Helper) waitForPodDeleted(p *corev1.Pod) error {
return wait.PollImmediate(1*time.Second, 60*time.Second, func() (bool, error) {
pod, err := h.client.CoreV1().Pods(p.GetNamespace()).Get(p.GetName(), meta.GetOptions{})
if apierrors.IsNotFound(err) {
return true, nil
}
if err != nil {
return false, errors.Wrapf(err, "failed to get pod %s/%s", p.GetNamespace(), p.GetName())
}
if pod.GetUID() != p.GetUID() {
return true, nil
}
return false, nil
})
}