go/{mirko,statusz}: better status, kubernetes client
Change-Id: I66753a79eaf36529aee508d2b7782aab00de1498
diff --git a/go/mirko/BUILD.bazel b/go/mirko/BUILD.bazel
index 59ed755..89b40ed 100644
--- a/go/mirko/BUILD.bazel
+++ b/go/mirko/BUILD.bazel
@@ -2,13 +2,18 @@
go_library(
name = "go_default_library",
- srcs = ["mirko.go"],
+ srcs = [
+ "kubernetes.go",
+ "mirko.go",
+ ],
importpath = "code.hackerspace.pl/hscloud/go/mirko",
visibility = ["//visibility:public"],
deps = [
"//go/pki:go_default_library",
"//go/statusz:go_default_library",
"@com_github_golang_glog//:go_default_library",
+ "@io_k8s_client_go//kubernetes:go_default_library",
+ "@io_k8s_client_go//rest:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//reflection:go_default_library",
"@org_golang_x_net//trace:go_default_library",
diff --git a/go/mirko/kubernetes.go b/go/mirko/kubernetes.go
new file mode 100644
index 0000000..c3a0bb3
--- /dev/null
+++ b/go/mirko/kubernetes.go
@@ -0,0 +1,23 @@
+package mirko
+
+import (
+ "github.com/golang/glog"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
+)
+
+func (m *Mirko) kubernetesConnect() {
+ config, err := rest.InClusterConfig()
+ if err != nil {
+ glog.Errorf("mirko.KubernetesClientSet: %v", err)
+ return
+ }
+
+ clientset, err := kubernetes.NewForConfig(config)
+ if err != nil {
+ glog.Errorf("kubernetes.NewForConfig: %v", err)
+ return
+ }
+
+ m.kubernetesCS = clientset
+}
diff --git a/go/mirko/mirko.go b/go/mirko/mirko.go
index 01766db..78dbda1 100644
--- a/go/mirko/mirko.go
+++ b/go/mirko/mirko.go
@@ -8,14 +8,18 @@
"net/http"
"os"
"os/signal"
+ "sort"
+ "strings"
"time"
- "code.hackerspace.pl/hscloud/go/pki"
- "code.hackerspace.pl/hscloud/go/statusz"
"github.com/golang/glog"
"golang.org/x/net/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
+ "k8s.io/client-go/kubernetes"
+
+ "code.hackerspace.pl/hscloud/go/pki"
+ "code.hackerspace.pl/hscloud/go/statusz"
)
var (
@@ -38,6 +42,8 @@
httpServer *http.Server
httpMux *http.ServeMux
+ kubernetesCS *kubernetes.Clientset
+
ctx context.Context
cancel context.CancelFunc
}
@@ -113,6 +119,12 @@
http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
})
+ m.kubernetesConnect()
+
+ debugParts := strings.Split(flagDebugAddress, ":")
+ debugPort := debugParts[len(debugParts)-1]
+ statusz.PublicAddress = fmt.Sprintf("http://%s:%s/", m.Address().String(), debugPort)
+
m.httpListen = httpLis
m.httpServer = &http.Server{
Addr: flagDebugAddress,
@@ -196,3 +208,98 @@
return err
}
}
+
+// Address returns a linkable address where this service is running, sans port.
+// If running within kubernetes, this will return the pod IP.
+// Otherwise, this will guess the main, 'external' IP address of the machine it's running on.
+// On failures, returns loopback address.
+func (m *Mirko) Address() net.IP {
+ // If we're not running in Kubernetes and binding to 127.0.0.1, return loopback.
+ if m.kubernetesCS == nil && strings.HasPrefix(flagListenAddress, "127.0.0.1:") {
+ return net.ParseIP("127.0.0.1")
+ }
+
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ glog.Errorf("net.Interface(): %v", err)
+ return net.ParseIP("127.0.0.1")
+ }
+
+ addrmap := make(map[string]net.IP)
+
+ for _, iface := range ifaces {
+ addrs, err := iface.Addrs()
+ if err != nil {
+ glog.Errorf("iface(%q).Addrs(): %v", iface.Name, err)
+ continue
+ }
+
+ for _, addr := range addrs {
+ var ip net.IP
+ switch v := addr.(type) {
+ case *net.IPNet:
+ ip = v.IP
+ case *net.IPAddr:
+ ip = v.IP
+ default:
+ continue
+ }
+
+ if strings.HasPrefix(ip.String(), "fe80:") {
+ continue
+ }
+ addrmap[iface.Name] = ip
+ }
+ }
+
+ if m.kubernetesCS != nil {
+ addr, ok := addrmap["eth0"]
+ if !ok {
+ glog.Errorf("Running on Kubernetes but no eth0! Available interfaces: %v", addrmap)
+ return net.ParseIP("127.0.0.1")
+ }
+
+ return addr
+ }
+
+ if len(addrmap) == 0 {
+ glog.Errorf("No interfaces found!")
+ return net.ParseIP("127.0.0.1")
+ }
+
+ // Heuristics ahoy!
+ prioritized := []*ifaceWithPriority{}
+ for iface, addr := range addrmap {
+ prio := &ifaceWithPriority{
+ iface: iface,
+ addr: addr,
+ }
+ switch {
+ case strings.HasPrefix(iface, "lo"):
+ prio.priority = -10
+ case strings.HasPrefix(iface, "tap"):
+ prio.priority = -5
+ case strings.HasPrefix(iface, "tun"):
+ prio.priority = -5
+ case strings.HasPrefix(iface, "veth"):
+ prio.priority = 5
+ case strings.HasPrefix(iface, "wl"):
+ prio.priority = 5
+ case strings.HasPrefix(iface, "enp"):
+ prio.priority = 10
+ case strings.HasPrefix(iface, "eth"):
+ prio.priority = 10
+ }
+
+ prioritized = append(prioritized, prio)
+ }
+
+ sort.Slice(prioritized, func(i, j int) bool { return prioritized[i].priority > prioritized[j].priority })
+ return prioritized[0].addr
+}
+
+type ifaceWithPriority struct {
+ iface string
+ addr net.IP
+ priority int
+}
diff --git a/go/statusz/BUILD.bazel b/go/statusz/BUILD.bazel
index f051d72..4434b95 100644
--- a/go/statusz/BUILD.bazel
+++ b/go/statusz/BUILD.bazel
@@ -12,4 +12,10 @@
"@com_github_golang_glog//:go_default_library",
"@com_github_shirou_gopsutil//load:go_default_library",
],
+ x_defs = {
+ "code.hackerspace.pl/hscloud/go/statusz.GitCommit": "{STABLE_GIT_COMMIT}",
+ "code.hackerspace.pl/hscloud/go/statusz.GitVersion": "{STABLE_GIT_VERSION}",
+ "code.hackerspace.pl/hscloud/go/statusz.Builder": "{STABLE_BUILDER}",
+ "code.hackerspace.pl/hscloud/go/statusz.BuildTimestamp": "{BUILD_TIMESTAMP}",
+ },
)
diff --git a/go/statusz/statusz.go b/go/statusz/statusz.go
index 2aa76e4..20d5151 100644
--- a/go/statusz/statusz.go
+++ b/go/statusz/statusz.go
@@ -42,6 +42,7 @@
"os"
"os/user"
"path/filepath"
+ "strconv"
"sync"
"time"
@@ -60,6 +61,13 @@
tmpl = template.Must(reparse(nil))
funcs = make(template.FuncMap)
+ GitCommit = "unknown"
+ GitVersion = "unknown"
+ Builder = "unknown"
+ BuildTimestamp = "0"
+ // mirko populates this
+ PublicAddress = "#"
+
DefaultMux = true
)
@@ -75,15 +83,18 @@
<title>Status for {{.BinaryName}}</title>
<style>
body {
-font-family: sans-serif;
background: #fff;
}
h1 {
+font-family: sans-serif;
clear: both;
width: 100%;
text-align: center;
font-size: 120%;
+padding-top: 0.3em;
+padding-bottom: 0.3em;
background: #eeeeff;
+margin-top: 1em;
}
.lefthand {
float: left;
@@ -97,16 +108,19 @@
<h1>Status for {{.BinaryName}}</h1>
<div>
<div class=lefthand>
-Started at {{.StartTime}}<br>
-Current time {{.CurrentTime}}<br>
+Started: {{.StartTime}} -- up {{.Up}}<br>
+Built on {{.BuildTime}}<br>
+Built at {{.Builder}}<br>
+Built from git checkout <a href="https://gerrit.hackerspace.pl/plugins/gitiles/hscloud/+/{{.GitCommit}}">{{.GitVersion}}</a><br>
SHA256 {{.BinaryHash}}<br>
</div>
<div class=righthand>
-Running as {{.Username}} on {{.Hostname}}<br>
+Running as {{.Username}} on <a href="{{.PublicAddress}}">{{.Hostname}}</a><br>
Load {{.LoadAvg}}<br>
View <a href=/debug/status>status</a>,
<a href=/debug/requests>requests</a>
</div>
+<div style="clear: both;"> </div>
</div>`
func reparse(sections []section) (*template.Template, error) {
@@ -133,24 +147,39 @@
lock.Lock()
defer lock.Unlock()
+ buildTime := time.Unix(0, 0)
+ if buildTimeNum, err := strconv.Atoi(BuildTimestamp); err == nil {
+ buildTime = time.Unix(int64(buildTimeNum), 0)
+ }
+
data := struct {
- Sections []section
- BinaryName string
- BinaryHash string
- Hostname string
- Username string
- StartTime string
- CurrentTime string
- LoadAvg string
+ Sections []section
+ BinaryName string
+ BinaryHash string
+ GitVersion string
+ GitCommit string
+ Builder string
+ BuildTime string
+ Hostname string
+ Username string
+ StartTime string
+ Up string
+ LoadAvg string
+ PublicAddress string
}{
- Sections: sections,
- BinaryName: binaryName,
- BinaryHash: binaryHash,
- Hostname: hostname,
- Username: username,
- StartTime: serverStart.Format(time.RFC1123),
- CurrentTime: time.Now().Format(time.RFC1123),
- LoadAvg: loadAverage(),
+ Sections: sections,
+ BinaryName: binaryName,
+ BinaryHash: binaryHash,
+ GitVersion: GitVersion,
+ GitCommit: GitCommit,
+ Builder: Builder,
+ BuildTime: fmt.Sprintf("%s (%d)", buildTime.Format(time.RFC1123), buildTime.Unix()),
+ Hostname: hostname,
+ Username: username,
+ StartTime: serverStart.Format(time.RFC1123),
+ Up: time.Since(serverStart).String(),
+ LoadAvg: loadAverage(),
+ PublicAddress: PublicAddress,
}
if err := tmpl.ExecuteTemplate(w, "status", data); err != nil {