diff --git a/grafana/Dockerfile b/grafana/Dockerfile index ac5765ffd0..9a55537d32 100644 --- a/grafana/Dockerfile +++ b/grafana/Dockerfile @@ -1,14 +1,8 @@ -# -# Stock Grafana + a few custom dashboards -# - -FROM grafana/grafana:3.1.1 - -RUN apt-get update && \ - apt-get install -y curl +FROM BASEIMAGE +ADD grafana.tar / COPY dashboards /dashboards -COPY run.sh /run.sh +COPY run.sh / +COPY setup_grafana /usr/bin/ -EXPOSE 3000 -ENTRYPOINT /run.sh +ENTRYPOINT ["/run.sh"] diff --git a/grafana/Makefile b/grafana/Makefile index eaf8f75fbd..2b437d6140 100644 --- a/grafana/Makefile +++ b/grafana/Makefile @@ -12,13 +12,79 @@ # See the License for the specific language governing permissions and # limitations under the License. -TAG = v3.1.1 -PREFIX = gcr.io/google_containers +# Build the influxdb image for amd64, arm, arm64 and ppc64le +# +# Usage: +# [PREFIX=gcr.io/google_containers] [ARCH=amd64] make (build|push) + +all: build + +VERSION?=v4.0.2 +PREFIX?=gcr.io/google_containers +ARCH?=amd64 +TEMP_DIR:=$(shell mktemp -d) +LDFLAGS=-w -X main.version=$(VERSION) -X main.commit=unknown-dev -X main.timestamp=0 -extldflags '-static' +DEB_BUILD=4.0.2-1481203731 +KUBE_CROSS_IMAGE=gcr.io/google_containers/kube-cross:v1.7.3-0 + +ALL_ARCHITECTURES=amd64 arm arm64 ppc64le s390x +ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64,linux/ppc64le,linux/s390x + +# Set default base image dynamically for each arch +ifeq ($(ARCH),amd64) + BASEIMAGE?=busybox + CC=gcc +endif +ifeq ($(ARCH),arm) + BASEIMAGE?=armhf/busybox + CC=arm-linux-gnueabi-gcc +endif +ifeq ($(ARCH),arm64) + BASEIMAGE?=aarch64/busybox + CC=aarch64-linux-gnu-gcc +endif +ifeq ($(ARCH),ppc64le) + BASEIMAGE?=ppc64le/busybox + CC=powerpc64le-linux-gnu-gcc +endif +ifeq ($(ARCH),s390x) + BASEIMAGE?=s390x/busybox + CC=s390x-linux-gnu-gcc +endif + +build: + # Copy the whole directory to a temporary dir and set the base image + cp -r ./* $(TEMP_DIR) + + cd $(TEMP_DIR) && sed -i "s|BASEIMAGE|$(BASEIMAGE)|g" Dockerfile + + # This script downloads the official grafana deb package, compiles grafana for the right architecture which replaces the built-in, dynamically linked binaries + # Then the rootfs will be compressed into a tarball again, in order to be ADDed in the Dockerfile. + # Lastly, it compiles the go helper + docker run --rm -it -v $(TEMP_DIR):/build -w /go/src/github.com/grafana/grafana $(KUBE_CROSS_IMAGE) /bin/bash -c "\ + curl -sSL https://github.com/grafana/grafana/archive/$(VERSION).tar.gz | tar -xz --strip-components=1 \ + && curl -sSL https://grafanarel.s3.amazonaws.com/builds/grafana_$(DEB_BUILD)_amd64.deb > /tmp/grafana.deb \ + && mkdir /tmp/grafanarootfs && dpkg -x /tmp/grafana.deb /tmp/grafanarootfs \ + && CGO_ENABLED=1 GOARCH=$(ARCH) CC=$(CC) go build --ldflags=\"$(LDFLAGS)\" -o /tmp/grafanarootfs/usr/sbin/grafana-server ./pkg/cmd/grafana-server \ + && CGO_ENABLED=1 GOARCH=$(ARCH) CC=$(CC) go build --ldflags=\"$(LDFLAGS)\" -o /tmp/grafanarootfs/usr/sbin/grafana-cli ./pkg/cmd/grafana-cli \ + && cd /tmp/grafanarootfs && tar -cf /build/grafana.tar . \ + && cd /build && CGO_ENABLED=0 GOARCH=$(ARCH) go build -o setup_grafana setup_grafana.go" + + docker build -t $(PREFIX)/heapster-grafana-$(ARCH):$(VERSION) $(TEMP_DIR) + + rm -rf $(TEMP_DIR) -all: container +push: ./manifest-tool $(addprefix sub-push-,$(ALL_ARCHITECTURES)) + ./manifest-tool push from-args --platforms $(ML_PLATFORMS) --template $(PREFIX)/heapster-grafana-ARCH:$(VERSION) --target $(PREFIX)/heapster-grafana:$(VERSION) -container: - docker build -t $(PREFIX)/heapster_grafana:$(TAG) . +sub-push-%: + $(MAKE) ARCH=$* PREFIX=$(PREFIX) VERSION=$(VERSION) build +ifeq ($(findstring gcr.io,$(PREFIX)),gcr.io) + gcloud docker push $(PREFIX)/heapster-grafana-$*:$(VERSION) +else + docker push $(PREFIX)/heapster-grafana-$*:$(VERSION) +endif -push: - gcloud docker -- push $(PREFIX)/heapster_grafana:$(TAG) +./manifest-tool: + curl -sSL https://github.com/luxas/manifest-tool/releases/download/v0.3.0/manifest-tool > manifest-tool + chmod +x manifest-tool diff --git a/grafana/README.md b/grafana/README.md index 1eb7fe1795..118073c185 100644 --- a/grafana/README.md +++ b/grafana/README.md @@ -1,17 +1,31 @@ # Grafana Image For Heapster/InfluxDB -## What's In It -* Stock Grafana. -* Create a datasource for InfluxDB. -* Create a couple of dashboards during startup. These dashboards leverage templating and repeating of panels features in Grafana 2.0, to discover nodes, pods, and containers automatically. +## What's in it: + - Grafana 4 + - A Go binary that: + - creates a datasource for InfluxDB + - creates a couple of dashboards during startup. + - these dashboards leverage templating and repeating of panels features in Grafana, to discover nodes, pods, and containers automatically. -## How To Use It -* InfluxDB service URL can be passed in via the environment variable __INFLUXDB_SERVICE_URL__. -* If __INFLUXDB_SERVICE_URL__ isn't defined, it will discover and use the external service URL, if available. -* Otherwise, it will fall back to http://monitoring-influxdb:8086. +## How to use it: + - InfluxDB service URL can be passed in via the environment variable `INFLUXDB_SERVICE_URL`. + - Otherwise, it will fall back to http://monitoring-influxdb:8086. -## How To Build It +## How to build: -cd $GOPATH/src/k8s.io/heapster/grafana +```console +$ ARCH=${ARCH} make build +``` -make all +## How to release: + +This image supports multiple architecures, which means the Makefile cross-compiles and builds docker images for all architectures automatically when pushing. +If you are releasing a new version, please bump the `VERSION` value in the `Makefile` before building the images. + +How to build and push all images: + +```console +# Optional: Set PREFIX if you want to push to a temporary user or another registry for testing +# This command will build images and push for all architectures +$ make push +``` diff --git a/grafana/RELEASES.md b/grafana/RELEASES.md index 339384902f..c146063ad4 100644 --- a/grafana/RELEASES.md +++ b/grafana/RELEASES.md @@ -1,5 +1,12 @@ # Release Notes for Grafana container. +## 4.0.2 (4.1.2016) +- Formalized the image name for every arch to `gcr.io/google_containers/heapster-grafana-ARCH:VERSION` +- Now this image is released for multiple architectures, including amd64, arm, arm64, ppc64le and s390x +- The `gcr.io/google_containers/heapster-grafana:VERSION` image is a manifest list, which means docker will pull the right image for the right arch automatically +- Grafana v4.0.2 +- Enhanced the Makefile and the README + ## 3.1.1 (24-11-2016) - Support Grafana 3.1.1. diff --git a/grafana/run.sh b/grafana/run.sh index 11d2d19ff9..2734b52230 100755 --- a/grafana/run.sh +++ b/grafana/run.sh @@ -1,69 +1,17 @@ -#!/bin/bash - -HEADER_CONTENT_TYPE="Content-Type: application/json" -HEADER_ACCEPT="Accept: application/json" - -GRAFANA_USER=${GRAFANA_USER:-admin} -GRAFANA_PASSWD=${GRAFANA_PASSWD:-admin} -GRAFANA_PORT=${GRAFANA_PORT:-3000} - -INFLUXDB_HOST=${INFLUXDB_HOST:-"monitoring-influxdb"} -INFLUXDB_DATABASE=${INFLUXDB_DATABASE:-k8s} -INFLUXDB_PASSWORD=${INFLUXDB_PASSWORD:-root} -INFLUXDB_PORT=${INFLUXDB_PORT:-8086} -INFLUXDB_USER=${INFLUXDB_USER:-root} - -DASHBOARD_LOCATION=${DASHBOARD_LOCATION:-"/dashboards"} +#!/bin/sh # Allow access to dashboards without having to log in +# Export these variables so grafana picks them up export GF_AUTH_ANONYMOUS_ENABLED=${GF_AUTH_ANONYMOUS_ENABLED:-true} export GF_SERVER_HTTP_PORT=${GRAFANA_PORT} +export GF_SERVER_PROTOCOL=${GF_SERVER_PROTOCOL:-http} -GF_SERVER_PROTOCOL=${GF_SERVER_PROTOCOL:-http} - -BACKEND_ACCESS_MODE=${BACKEND_ACCESS_MODE:-proxy} -INFLUXDB_SERVICE_URL=${INFLUXDB_SERVICE_URL} -if [ -n "$INFLUXDB_SERVICE_URL" ]; then - echo "Influxdb service URL is provided." -else - INFLUXDB_SERVICE_URL="http://${INFLUXDB_HOST}:${INFLUXDB_PORT}" -fi - -echo "Using the following URL for InfluxDB: ${INFLUXDB_SERVICE_URL}" -echo "Using the following backend access mode for InfluxDB: ${BACKEND_ACCESS_MODE}" - -set -m -echo "Starting Grafana in the background" -exec /usr/sbin/grafana-server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini cfg:default.paths.data=/var/lib/grafana cfg:default.paths.logs=/var/log/grafana & - -echo "Waiting for Grafana to come up..." -until $(curl -k --fail --output /dev/null --silent ${GF_SERVER_PROTOCOL}://${GRAFANA_USER}:${GRAFANA_PASSWD}@localhost:${GRAFANA_PORT}/api/org); do - printf "." - sleep 2 -done -echo "Grafana is up and running." -echo "Creating default influxdb datasource..." -curl -k -i -XPOST -H "${HEADER_ACCEPT}" -H "${HEADER_CONTENT_TYPE}" "${GF_SERVER_PROTOCOL}://${GRAFANA_USER}:${GRAFANA_PASSWD}@localhost:${GRAFANA_PORT}/api/datasources" -d ' -{ - "name": "influxdb-datasource", - "type": "influxdb", - "access": "'"${BACKEND_ACCESS_MODE}"'", - "isDefault": true, - "url": "'"${INFLUXDB_SERVICE_URL}"'", - "password": "'"${INFLUXDB_PASSWORD}"'", - "user": "'"${INFLUXDB_USER}"'", - "database": "'"${INFLUXDB_DATABASE}"'" -}' - -echo "" -echo "Importing default dashboards..." -for filename in ${DASHBOARD_LOCATION}/*.json; do - echo "Importing ${filename} ..." - curl -k -i -XPOST --data "@${filename}" -H "${HEADER_ACCEPT}" -H "${HEADER_CONTENT_TYPE}" "${GF_SERVER_PROTOCOL}://${GRAFANA_USER}:${GRAFANA_PASSWD}@localhost:${GRAFANA_PORT}/api/dashboards/db" - echo "" - echo "Done importing ${filename}" -done -echo "" -echo "Bringing Grafana back to the foreground" -fg +echo "Starting a utility program that will configure Grafana" +setup_grafana >/dev/stdout 2>/dev/stderr & +echo "Starting Grafana in foreground mode" +exec /usr/sbin/grafana-server \ + --homepath=/usr/share/grafana \ + --config=/etc/grafana/grafana.ini \ + cfg:default.paths.data=/var/lib/grafana \ + cfg:default.paths.logs=/var/log/grafana diff --git a/grafana/setup_grafana.go b/grafana/setup_grafana.go new file mode 100644 index 0000000000..0fba92fc67 --- /dev/null +++ b/grafana/setup_grafana.go @@ -0,0 +1,143 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +// How many seconds the program should wait before trying to connect to the dashboard again +const RetryTimeout = 5 + +type grafanaConfig struct { + Name string `json:"name"` + Type string `json:"type"` + Access string `json:"access"` + IsDefault bool `json:"isDefault"` + URL string `json:"url"` + Password string `json:"password"` + User string `json:"user"` + Database string `json:"database"` +} + +func main() { + + envParams := map[string]string{ + "grafana_user": "admin", + "grafana_passwd": "admin", + "grafana_port": "3000", + "influxdb_host": "monitoring-influxdb", + "influxdb_port": "8086", + "influxdb_database": "k8s", + "influxdb_user": "root", + "influxdb_password": "root", + "influxdb_service_url": "", + "dashboard_location": "/dashboards", + "gf_auth_anonymous_enabled": "true", + "gf_server_protocol": "http", + "backend_access_mode": "proxy", + } + + for k := range envParams { + if v := os.Getenv(strings.ToUpper(k)); v != "" { + envParams[k] = v + } + } + + if envParams["influxdb_service_url"] == "" { + envParams["influxdb_service_url"] = fmt.Sprintf("http://%s:%s", envParams["influxdb_host"], envParams["influxdb_port"]) + } + + cfg := grafanaConfig{ + Name: "influxdb-datasource", + Type: "influxdb", + Access: envParams["backend_access_mode"], + IsDefault: true, + URL: envParams["influxdb_service_url"], + User: envParams["influxdb_user"], + Password: envParams["influxdb_password"], + Database: envParams["influxdb_database"], + } + + grafanaURL := fmt.Sprintf("%s://%s:%s@localhost:%s", envParams["gf_server_protocol"], envParams["grafana_user"], envParams["grafana_passwd"], envParams["grafana_port"]) + + for { + res, err := http.Get(grafanaURL + "/api/org") + if err != nil { + fmt.Printf("Can't access the Grafana dashboard. Error: %v. Retrying after %d seconds...\n", err, RetryTimeout) + time.Sleep(RetryTimeout * time.Second) + continue + } + + _, err = ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + fmt.Printf("Can't access the Grafana dashboard. Error: %v. Retrying after %d seconds...\n", err, RetryTimeout) + time.Sleep(RetryTimeout * time.Second) + continue + } + + fmt.Println("Connected to the Grafana dashboard.") + break + } + + b := new(bytes.Buffer) + json.NewEncoder(b).Encode(cfg) + + for { + _, err := http.Post(grafanaURL+"/api/datasources", "application/json; charset=utf-8", b) + if err != nil { + fmt.Printf("Failed to configure the Grafana dashboard. Error: %v. Retrying after %d seconds...\n", err, RetryTimeout) + time.Sleep(RetryTimeout * time.Second) + continue + } + + fmt.Println("The datasource for the Grafana dashboard is now set.") + break + } + + dashboardDir := envParams["dashboard_location"] + files, err := ioutil.ReadDir(dashboardDir) + if err != nil { + fmt.Printf("Failed to read the the directory the json files should be in. Exiting... Error: %v\n", err) + os.Exit(1) + } + for _, file := range files { + if file.IsDir() { + continue + } + + filePath := filepath.Join(dashboardDir, file.Name()) + jsonbytes, err := ioutil.ReadFile(filePath) + if err != nil { + fmt.Printf("Failed to read the json file: %s. Proceeding with the next one. Error: %v\n", filePath, err) + continue + } + + _, err = http.Post(grafanaURL+"/api/dashboards/db", "application/json; charset=utf-8", bytes.NewReader(jsonbytes)) + if err != nil { + fmt.Printf("Failed to post the json file: %s. Proceeding with the next one. Error: %v\n", filePath, err) + continue + } + } +}