Skip to content
This repository has been archived by the owner on Aug 19, 2020. It is now read-only.

Commit

Permalink
Merge pull request #71 from steven-sheehy/health-check
Browse files Browse the repository at this point in the history
Add health check
  • Loading branch information
aledbf authored Dec 18, 2018
2 parents 42c936b + 8b2a4bd commit 34a46af
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ chart/kube-keepalived-vip/Chart.yaml
chart/kube-keepalived-vip/values.yaml
# Ignore backup files
*~
*.bak
.DS_Store
.idea/

8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ all: push
TAG = 0.30
HAPROXY_TAG = 0.1
# Helm uses SemVer2 versioning
CHART_VERSION = 0.1.1
CHART_VERSION = 0.2.0
PREFIX = aledbf/kube-keepalived-vip
BUILD_IMAGE = build-keepalived
PKG = github.com/aledbf/kube-keepalived-vip
Expand Down Expand Up @@ -35,8 +35,9 @@ chart: chart/kube-keepalived-vip-$(CHART_VERSION).tgz
.PHONY: chart-subst
chart-subst: chart/kube-keepalived-vip/Chart.yaml.tmpl chart/kube-keepalived-vip/values.yaml.tmpl
for file in Chart.yaml values.yaml; do cp -f "chart/kube-keepalived-vip/$$file.tmpl" "chart/kube-keepalived-vip/$$file"; done
sed -i -e 's|%%TAG%%|$(TAG)|g' -e 's|%%HAPROXY_TAG%%|$(HAPROXY_TAG)|g' chart/kube-keepalived-vip/values.yaml
sed -i -e 's|%%CHART_VERSION%%|$(CHART_VERSION)|g' chart/kube-keepalived-vip/Chart.yaml
sed -i'.bak' -e 's|%%TAG%%|$(TAG)|g' -e 's|%%HAPROXY_TAG%%|$(HAPROXY_TAG)|g' chart/kube-keepalived-vip/values.yaml
sed -i'.bak' -e 's|%%CHART_VERSION%%|$(CHART_VERSION)|g' chart/kube-keepalived-vip/Chart.yaml
rm -f chart/kube-keepalived-vip/*.bak

# Requires helm
chart/kube-keepalived-vip-$(CHART_VERSION).tgz: chart-subst $(shell which helm) $(shell find chart/kube-keepalived-vip -type f)
Expand Down Expand Up @@ -74,3 +75,4 @@ dep-ensure:
dep ensure -v
dep prune -v
find vendor -name '*_test.go' -delete

7 changes: 7 additions & 0 deletions chart/kube-keepalived-vip/templates/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ spec:
- name: {{ template "kube-keepalived-vip.name" . }}-{{ .Values.keepalived.name }}
image: "{{ .Values.keepalived.image.repository }}:{{ .Values.keepalived.image.tag }}"
imagePullPolicy: "{{ .Values.keepalived.image.pullPolicy }}"
livenessProbe:
httpGet:
path: /health
port: {{ .Values.httpPort }}
initialDelaySeconds: 15
timeoutSeconds: 3
securityContext:
privileged: true
volumeMounts:
Expand Down Expand Up @@ -64,6 +70,7 @@ spec:
- --use-unicast={{ .Values.keepalived.useUnicast }}
- --vrid={{ .Values.keepalived.vrid }}
- --logtostderr
- --http-port={{ .Values.httpPort }}
{{- if .Values.haproxy.enabled }}
- --proxy-protocol-mode=true
{{- end }}
Expand Down
3 changes: 3 additions & 0 deletions chart/kube-keepalived-vip/values.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ haproxy:
# Resource allocations for the HAProxy container
resources: {}

# Health check port
httpPort: 8080

fullnameOverride: ""
nameOverride: ""

Expand Down
8 changes: 7 additions & 1 deletion pkg/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ var (

iface = flags.String("iface", "", `network interface to listen on. If undefined, the nodes
default interface will be used instead`)

httpPort = flags.Int("http-port", 8080, `The HTTP port to use for health checks`)
)

func main() {
Expand All @@ -95,6 +97,10 @@ func main() {
glog.Fatalf("Please specify --services-configmap")
}

if *httpPort < 0 || *httpPort > 65535 {
glog.Fatalf("Invalid HTTP port %d, only values between 0 and 65535 are allowed.", httpPort)
}

if *vrid < 0 || *vrid > 255 {
glog.Fatalf("Error using VRID %d, only values between 0 and 255 are allowed.", vrid)
}
Expand Down Expand Up @@ -131,7 +137,7 @@ func main() {
}

glog.Info("starting LVS configuration")
ipvsc := controller.NewIPVSController(kubeClient, *watchNamespace, *useUnicast, *configMapName, *vrid, *proxyMode, *iface)
ipvsc := controller.NewIPVSController(kubeClient, *watchNamespace, *useUnicast, *configMapName, *vrid, *proxyMode, *iface, *httpPort)

ipvsc.Start()
}
Expand Down
54 changes: 51 additions & 3 deletions pkg/controller/keepalived.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ limitations under the License.
package controller

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"syscall"
"text/template"

Expand All @@ -31,9 +34,10 @@ import (
)

const (
iptablesChain = "KUBE-KEEPALIVED-VIP"
keepalivedCfg = "/etc/keepalived/keepalived.conf"
haproxyCfg = "/etc/haproxy/haproxy.cfg"
iptablesChain = "KUBE-KEEPALIVED-VIP"
keepalivedCfg = "/etc/keepalived/keepalived.conf"
haproxyCfg = "/etc/haproxy/haproxy.cfg"
keepalivedState = "/var/run/keepalived.state"
)

var (
Expand Down Expand Up @@ -175,6 +179,50 @@ func (k *keepalived) Reload() error {
return nil
}

// Whether keepalived child process is currently running
func (k *keepalived) Healthy() error {
b, err := ioutil.ReadFile(keepalivedState)
if err != nil {
return err
}

master := false
state := strings.TrimSpace(string(b))
if strings.Contains(state, "MASTER") {
master = true
}

var out bytes.Buffer
cmd := exec.Command("ip", "-brief", "address", "show", k.iface, "up")
cmd.Stderr = os.Stderr
cmd.Stdout = &out
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}

err = cmd.Run()
if err != nil {
return err
}

ips := out.String()
glog.V(3).Infof("Status of %s interface: %s", state, ips)

for _, vip := range k.vips {
containsVip := strings.Contains(ips, fmt.Sprintf(" %s/32 ", vip))

if master && !containsVip {
return fmt.Errorf("Missing VIP %s on %s", vip, state)
} else if !master && containsVip {
return fmt.Errorf("%s should not contain VIP %s", state, vip)
}
}

// All checks successful
return nil
}

func (k *keepalived) Cleanup() {
glog.Infof("Cleanup: %s", k.vips)
for _, vip := range k.vips {
Expand Down
26 changes: 25 additions & 1 deletion pkg/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"reflect"
Expand Down Expand Up @@ -125,6 +126,8 @@ type ipvsControllerController struct {
configMapName string
configMapResourceVersion string

httpPort int

ruMD5 string

// stopLock is used to enforce only a single call to Stop is active.
Expand Down Expand Up @@ -308,6 +311,14 @@ func (ipvsc *ipvsControllerController) Start() {
runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
}

go func() {
glog.Infof("Starting HTTP server on port %d", ipvsc.httpPort)
err := http.ListenAndServe(fmt.Sprintf(":%d", ipvsc.httpPort), nil)
if err != nil {
glog.Error(err.Error())
}
}()

glog.Info("starting keepalived to announce VIPs")
ipvsc.keepalived.Start()
}
Expand Down Expand Up @@ -347,11 +358,12 @@ func (ipvsc *ipvsControllerController) Stop() error {
}

// NewIPVSController creates a new controller from the given config.
func NewIPVSController(kubeClient *kubernetes.Clientset, namespace string, useUnicast bool, configMapName string, vrid int, proxyMode bool, iface string) *ipvsControllerController {
func NewIPVSController(kubeClient *kubernetes.Clientset, namespace string, useUnicast bool, configMapName string, vrid int, proxyMode bool, iface string, httpPort int) *ipvsControllerController {
ipvsc := ipvsControllerController{
client: kubeClient,
reloadRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.5, 1),
configMapName: configMapName,
httpPort: httpPort,
stopCh: make(chan struct{}),
}

Expand Down Expand Up @@ -441,6 +453,18 @@ func NewIPVSController(kubeClient *kubernetes.Clientset, namespace string, useUn
cache.NewListWatchFromClient(ipvsc.client.CoreV1().RESTClient(), "configmaps", namespace, fields.Everything()),
&apiv1.ConfigMap{}, resyncPeriod, mapEventHandler)

http.HandleFunc("/health", func(rw http.ResponseWriter, req *http.Request) {
err := ipvsc.keepalived.Healthy()
if err != nil {
glog.Errorf("Health check unsuccessful: %v", err)
http.Error(rw, fmt.Sprintf("keepalived not healthy: %v", err), 500)
return
}

glog.V(3).Info("Health check successful")
fmt.Fprint(rw, "OK")
})

return &ipvsc
}

Expand Down
9 changes: 9 additions & 0 deletions rootfs/keepalived-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

TYPE="$1"
NAME="$2"
STATE="$3"

echo -n "${STATE}" > /var/run/keepalived.state
exit 0

2 changes: 2 additions & 0 deletions rootfs/keepalived.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ vrrp_instance vips {
{{ . }}{{ end }}
}

notify /keepalived-check.sh

{{ if .proxyMode }}
# In proxy mode there is no need to create virtual servers
track_script {
Expand Down
6 changes: 6 additions & 0 deletions vip-daemonset-proxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ spec:
mountPath: /etc/haproxy
- image: aledbf/kube-keepalived-vip:0.30
name: kube-keepalived-vip
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
timeoutSeconds: 3
securityContext:
privileged: true
volumeMounts:
Expand Down
8 changes: 7 additions & 1 deletion vip-daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ spec:
containers:
- image: aledbf/kube-keepalived-vip:0.30
name: kube-keepalived-vip
imagePullPolicy: Always
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
timeoutSeconds: 3
securityContext:
privileged: true
volumeMounts:
Expand Down

0 comments on commit 34a46af

Please sign in to comment.