Skip to content

Commit

Permalink
Built grafana for multiple architectures and distributes them as a ma…
Browse files Browse the repository at this point in the history
…nifest list. Cleaned up the Dockerfile to be based on busybox => much smaller. Improved the Makefile and README.md. Added a Go binary that does the pushing of the .json files to grafana instead of using a bash script
  • Loading branch information
luxas committed Jan 5, 2017
1 parent e66fb88 commit a153e4a
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 92 deletions.
16 changes: 5 additions & 11 deletions grafana/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
80 changes: 73 additions & 7 deletions grafana/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
36 changes: 25 additions & 11 deletions grafana/README.md
Original file line number Diff line number Diff line change
@@ -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
```
7 changes: 7 additions & 0 deletions grafana/RELEASES.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
74 changes: 11 additions & 63 deletions grafana/run.sh
Original file line number Diff line number Diff line change
@@ -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
143 changes: 143 additions & 0 deletions grafana/setup_grafana.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}

0 comments on commit a153e4a

Please sign in to comment.