Skip to content
This repository has been archived by the owner on Sep 7, 2018. It is now read-only.

Image improvements #146

Merged
merged 18 commits into from
Apr 13, 2018
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 30 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
FROM debian:jessie
FROM debian:stretch-slim

ARG DOWNLOAD_URL="https://s3-us-west-2.amazonaws.com/grafana-releases/master/grafana_latest_amd64.deb"
ARG GRAFANA_VERSION="latest"
ARG GRAFANA_URL="https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-$GRAFANA_VERSION.linux-x64.tar.gz"
ARG GF_UID="472"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason this needs to be hardcoded in the image? Why not nouser:nogroup? In Kubernetes when using PodSecurityPolicies or SecurityContextConstraints (OpenShift), then users are not able to choose a user, this would mean that the Grafana container must be run as a more permissive container than others, for no reason.

Copy link
Contributor Author

@xlson xlson Mar 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When starting the remake I used nobody:nogroup (by nouser, do you mean nobody?) but one of the other reviewers was skeptical and preferred a specific grafana user (see earlier review). I don't really have a strong opinion myself on specifically what user it should be. The important thing (as I see it) is that the it should be pinned id and that it should be possible to set a different id at boot time, which it wasn't previously.

With this container it's possible to boot as a different user than the one specified but you will have to make sure /var/lib/grafana is writable by that user (and /usr/share/grafana if you wish to map in aws credentials).

To be honest I don't fully understand how nobody:nogroup is better than grafana:grafana. Could you please explain it to me? :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nobody:nogroup is typically used when you explicitly want to make sure that anybody/nobody owns something, for example in "public" folders that should be shared by multiple users of the same computer.

In the case of the container ensuring that things are "owned" by nobody:nogroup, means that a user can pick the UID/GID at creation/runtime time of the container rather than it being hard coded in to the image.

In the Kubernetes case, Kubernetes makes sure that the volumes created for the container to be consumed have the UID/GID that the container will have.

From the security point of view it's that administrators want to disallow users from picking UID/GID, as if you allow users to pick any UID/GID they can choose 0 aka root, which would be a privilege escalation. Kubernetes has a mode where UID/GID is picked by Kubernetes, which however requires that the image indeed can run with any UID/GID. It seems odd at best if we have to add an exception to the otherwise restrictedly running containers for Grafana, simply because the UID and GID are hardcoded.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nobody:nogroup is typically used when you explicitly want to make sure that anybody/nobody owns something, for example in "public" folders that should be shared by multiple users of the same computer.

In the case of the container ensuring that things are "owned" by nobody:nogroup, means that a user can pick the UID/GID at creation/runtime time of the container rather than it being hard coded in to the image.

I don't understand how nobody:nogroup solves the permissions issue. As far as I'm aware the only way to make sure you can set user easily at create/runtime is to chmod a+rw on both /var/lib/grafana and /usr/share/grafana/.aws at build time.

In the Kubernetes case, Kubernetes makes sure that the volumes created for the container to be consumed have the UID/GID that the container will have.

From the security point of view it's that administrators want to disallow users from picking UID/GID, as if you allow users to pick any UID/GID they can choose 0 aka root, which would be a privilege escalation. Kubernetes has a mode where UID/GID is picked by Kubernetes, which however requires that the image indeed can run with any UID/GID. It seems odd at best if we have to add an exception to the otherwise restrictedly running containers for Grafana, simply because the UID and GID are hardcoded.

But isn't setting the user and group to nobody:nogroup just another way to hardcode them? Is nobody handled differently in Kubernetes somehow? Or is using nobody:nogroup something that is beneficial specifially due to how you run kubernetes?

Perhaps you'd be willing to create a pull request that shows off how you would like the image to look like and that would make it easier for me to understand what the benefits are. When googling nobody:nogrup and containers people seem to have warying opinions (prometheus/prometheus#3441). I'm not really opposed to using nobody:nogroup, but I'd like to understand what the benefits are :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the container is built with nouser:nogroup, it means that whatever UID/GID a user chooses to run the container with can write the files/directories within the container, as the operating system specifically allows this for directories/files with nouser:nogroup. Volumes mounted from the outside are then prepared by the user to have exactly the UID/GID they choose instead of it having to be what is hard coded in the image.

At the end of the day it means whether people will have to create special cases for handling any container that hardcodes a UID/GID vs. a generic way of handling all containers, because they can all run with any user. Most containers out there can indeed run as any user.

Copy link
Contributor Author

@xlson xlson Mar 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any examples of this behaviour? It sounds great but I haven't been able to reproduce it. I tried switching to nobody:nogroup and running it on my Ubuntu 16.04 install and when switching to another user than nobody I can no longer write to /var/lib/grafana.

Example using the grafana container in the kube-prometheus repo on my machine:

$ docker run -p 3000:3000 --user 105 quay.io/coreos/monitoring-grafana:5.0.3
t=2018-03-26T12:24:07+0000 lvl=info msg="Starting Grafana" logger=server version=5.0.3 commit=91162f3 compiled=2018-03-16T16:11:16+0000
t=2018-03-26T12:24:07+0000 lvl=info msg="Config loaded from" logger=settings file=/grafana/conf/defaults.ini
t=2018-03-26T12:24:07+0000 lvl=info msg="Config loaded from" logger=settings file=/grafana/conf/config.ini
t=2018-03-26T12:24:07+0000 lvl=info msg="Path Home" logger=settings path=/grafana
t=2018-03-26T12:24:07+0000 lvl=info msg="Path Data" logger=settings path=/data
t=2018-03-26T12:24:07+0000 lvl=info msg="Path Logs" logger=settings path=/data/log
t=2018-03-26T12:24:07+0000 lvl=info msg="Path Plugins" logger=settings path=/data/plugins
t=2018-03-26T12:24:07+0000 lvl=info msg="Path Provisioning" logger=settings path=/grafana/conf/provisioning
t=2018-03-26T12:24:07+0000 lvl=info msg="App mode production" logger=settings
t=2018-03-26T12:24:07+0000 lvl=info msg="Initializing DB" logger=sqlstore dbtype=sqlite3
t=2018-03-26T12:24:07+0000 lvl=info msg="Starting DB migration" logger=migrator
t=2018-03-26T12:24:07+0000 lvl=eror msg="Fail to initialize orm engine" logger=sqlstore error="Sqlstore::Migration failed err: unable to open database file\n"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume I'm missing something obvious 🤷‍♂️

Copy link
Contributor

@DanCech DanCech Mar 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brancz as long as the files within the container that the container process needs to access all have their permissions set to be readable by all users (and executable as needed) then the container can be run as any user and still work. As far as I can see you don't gain anything by making the files within the container owned by nobody vs the proposed arrangement. The only thing that matters is that if you are using a volume to provide a writable filesystem then that needs to have appropriate permissions for whatever uid the process is running as inside the container. By specifying a uid that doesn't change as the default the user can do that either by using chown 472:472 or by running the container with whatever uid they prefer.

Copy link
Contributor Author

@xlson xlson Mar 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brancz The image still uses grafana:grafana but permissions has been modified so that it can be started as any user. Does this iteration of the image solve your problem?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to give this a test run, but I believe it should work.

ARG GF_GID="472"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO you can add build argument for architecture - see https://github.com/monitoringartist/grafana-xxl/pull/8/files. Then you can build also arm images easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jangaraj It's a bit out of scope for this PR but that's definitely something to look into when we have inhouse ARM builds of Grafana.

RUN apt-get update && \
apt-get -y --no-install-recommends install libfontconfig curl ca-certificates && \
apt-get clean && \
curl ${DOWNLOAD_URL} > /tmp/grafana.deb && \
dpkg -i /tmp/grafana.deb && \
rm /tmp/grafana.deb && \
curl -L https://github.com/tianon/gosu/releases/download/1.10/gosu-amd64 > /usr/sbin/gosu && \
chmod +x /usr/sbin/gosu && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
GF_PATHS_CONFIG="/etc/grafana/grafana.ini" \
GF_PATHS_DATA="/var/lib/grafana" \
GF_PATHS_HOME="/usr/share/grafana" \
GF_PATHS_LOGS="/var/log/grafana" \
GF_PATHS_PLUGINS="/var/lib/grafana/plugins" \
GF_PATHS_PROVISIONING="/etc/grafana/provisioning"

VOLUME ["/var/lib/grafana", "/var/log/grafana", "/etc/grafana"]
RUN apt-get update && apt-get install -qq -y tar sqlite libfontconfig curl ca-certificates && \
mkdir -p "$GF_PATHS_HOME/.aws" && \
curl "$GRAFANA_URL" | tar xfvz - --strip-components=1 -C "$GF_PATHS_HOME" && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* && \
groupadd -r -g $GF_GID grafana && \
useradd -r -u $GF_UID -g grafana grafana && \
mkdir -p "$GF_PATHS_PROVISIONING/datasources" \
"$GF_PATHS_PROVISIONING/dashboards" \
"$GF_PATHS_LOGS" \
"$GF_PATHS_PLUGINS" \
"$GF_PATHS_DATA" && \
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" && \
chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS"

EXPOSE 3000

COPY ./run.sh /run.sh

USER grafana
WORKDIR /

ENTRYPOINT ["/run.sh"]
ENTRYPOINT [ "/run.sh" ]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rather use CMD then ENTRYPOINT, as I find it always cumbersome to debug, as one can not simply use docker run -ti --rm grafana/grafana-docker bash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm like that too. Will have to check if there are any good reasons to keep it as an entrypoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree that CMD would have been a better option it would be a breaking change and as such I don't think its enough of an improvement to warrant it.

36 changes: 19 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CircleCI](https://circleci.com/gh/grafana/grafana-docker.svg?style=svg)](https://circleci.com/gh/grafana/grafana-docker)

This project builds a Docker image with the latest master build of Grafana.
This project builds a Docker image for Grafana.

## Running your Grafana container

Expand All @@ -14,7 +14,7 @@ docker run -d --name=grafana -p 3000:3000 grafana/grafana

Try it out, default admin user is admin/admin.

In case port 3000 is closed for external clients or you there is no access
In case port 3000 is closed for external clients or there is no access
to the browser - you may test it by issuing:
curl -i localhost:3000/login
Make sure that you are getting "...200 OK" in response.
Expand Down Expand Up @@ -43,19 +43,23 @@ More information in the grafana configuration documentation: http://docs.grafana
## Grafana container with persistent storage (recommended)

```
# create /var/lib/grafana as persistent volume storage
docker run -d -v /var/lib/grafana --name grafana-storage busybox:latest
# create a persistent volume for your data in /var/lib/grafana (database and plugins)
docker volume create grafana-storage

# start grafana
docker run \
-d \
-p 3000:3000 \
--name=grafana \
--volumes-from grafana-storage \
-v grafana-storage:/var/lib/grafana \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably have :z as an argument as well, in case of SELinux?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never seen that one before. Will look into it. Thanks for reviewing the container, much appreciated.

grafana/grafana
```

## Installing plugins for Grafana 3
Note: An unnamed volume will be created for you when you boot Grafana,
using `docker volume create grafana-storage` just makes it easier to find
by giving it a name.

## Installing plugins for Grafana

Pass the plugins you want installed to docker with the `GF_INSTALL_PLUGINS` environment variable as a comma seperated list. This will pass each plugin name to `grafana-cli plugins install ${plugin}`.

Expand All @@ -70,24 +74,19 @@ docker run \

## Building a custom Grafana image with pre-installed plugins

Dockerfile:
```Dockerfile
FROM grafana/grafana:5.0.0
ENV GF_PATHS_PLUGINS=/opt/grafana-plugins
RUN mkdir -p $GF_PATHS_PLUGINS
RUN grafana-cli --pluginsDir $GF_PATHS_PLUGINS plugins install grafana-clock-panel
```

Add lines with `RUN grafana-cli ...` for each plugin you wish to install in your custom image. Don't forget to specify what version of Grafana you wish to build from (replace 5.0.0 in the example).
The `custom/` folder includes a `Dockerfile` that can be used to build a custom Grafana image. It accepts `GRAFANA_VERSION` and `GF_INSTALL_PLUGINS` as build arguments.

Example of how to build and run:
```bash
docker build -t grafana:5.0.0-custom .
cd custom
docker build -t grafana:latest-with-plugins \
--build-arg "GRAFANA_VERSION=latest" \
--build-arg "GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource" .
docker run \
-d \
-p 3000:3000 \
--name=grafana \
grafana:5.0.0-custom
grafana:latest-with-plugins
```

## Running specific version of Grafana
Expand Down Expand Up @@ -126,6 +125,9 @@ Supported variables:

## Changelog

### v5.1.0
* Complete overhaul

### v4.2.0
* Plugins are now installed into ${GF_PATHS_PLUGINS}
* Building the container now requires a full url to the deb package instead of just version
Expand Down
9 changes: 3 additions & 6 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
#!/bin/bash

_grafana_tag=$1
_grafana_version=${_grafana_tag:1}
_grafana_version=$1

_docker_repo=${2:-grafana/grafana}

if [ "$_grafana_version" != "" ]; then
echo "Building version ${_grafana_version}"
echo "Download url: https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${_grafana_version}_amd64.deb"
docker build \
--build-arg DOWNLOAD_URL=https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${_grafana_version}_amd64.deb \
--build-arg GRAFANA_VERSION=${_grafana_version} \
--tag "${_docker_repo}:${_grafana_version}" \
--no-cache=true .
docker tag ${_docker_repo}:${_grafana_version} ${_docker_repo}:latest

else
echo "Building latest for master"
docker build \
--tag "grafana/grafana:master" \
--no-cache=true .
.
fi
16 changes: 16 additions & 0 deletions custom/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ARG GRAFANA_VERSION="latest"

FROM grafana/grafana:${GRAFANA_VERSION}

USER grafana

ARG GF_INSTALL_PLUGINS=""

RUN if [ ! -z "${GF_INSTALL_PLUGINS}" ]; then \
OLDIFS=$IFS; \
IFS=','; \
for plugin in ${GF_INSTALL_PLUGINS}; do \
IFS=$OLDIFS; \
grafana-cli --pluginsDir "$GF_PATHS_PLUGINS" plugins install ${plugin}; \
done; \
fi
62 changes: 39 additions & 23 deletions run.sh
Original file line number Diff line number Diff line change
@@ -1,51 +1,67 @@
#!/bin/bash -e

: "${GF_PATHS_CONFIG:=/etc/grafana/grafana.ini}"
: "${GF_PATHS_DATA:=/var/lib/grafana}"
: "${GF_PATHS_LOGS:=/var/log/grafana}"
: "${GF_PATHS_PLUGINS:=/var/lib/grafana/plugins}"
: "${GF_PATHS_PROVISIONING:=/etc/grafana/provisioning}"
PERMISSIONS_OK=0

if [ ! -r "$GF_PATHS_CONFIG" ]; then
echo "GF_PATHS_CONFIG='$GF_PATHS_CONFIG' is not readable."
PERMISSIONS_OK=1
fi

if [ ! -w "$GF_PATHS_DATA" ]; then
echo "GF_PATHS_DATA='$GF_PATHS_DATA' is not writable."
PERMISSIONS_OK=1
fi

if [ ! -r "$GF_PATHS_HOME" ]; then
echo "GF_PATHS_HOME='$GF_PATHS_HOME' is not readable."
PERMISSIONS_OK=1
fi

if [ $PERMISSIONS_OK -eq 1 ]; then
echo "You may have issues with file permissions, more information here: http://docs.grafana.org/installation/docker/#migration-from-a-previous-version-of-the-docker-container-to-5-1-or-later"
fi

if [ ! -d "$GF_PATHS_PLUGINS" ]; then
mkdir "$GF_PATHS_PLUGINS"
fi

chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_LOGS" || true

if [ ! -z ${GF_AWS_PROFILES+x} ]; then
mkdir -p ~grafana/.aws/
> ~grafana/.aws/credentials
> "$GF_PATHS_HOME/.aws/credentials"

for profile in ${GF_AWS_PROFILES}; do
access_key_varname="GF_AWS_${profile}_ACCESS_KEY_ID"
secret_key_varname="GF_AWS_${profile}_SECRET_ACCESS_KEY"
region_varname="GF_AWS_${profile}_REGION"

if [ ! -z "${!access_key_varname}" -a ! -z "${!secret_key_varname}" ]; then
echo "[${profile}]" >> ~grafana/.aws/credentials
echo "aws_access_key_id = ${!access_key_varname}" >> ~grafana/.aws/credentials
echo "aws_secret_access_key = ${!secret_key_varname}" >> ~grafana/.aws/credentials
echo "[${profile}]" >> "$GF_PATHS_HOME/.aws/credentials"
echo "aws_access_key_id = ${!access_key_varname}" >> "$GF_PATHS_HOME/.aws/credentials"
echo "aws_secret_access_key = ${!secret_key_varname}" >> "$GF_PATHS_HOME/.aws/credentials"
if [ ! -z "${!region_varname}" ]; then
echo "region = ${!region_varname}" >> ~grafana/.aws/credentials
echo "region = ${!region_varname}" >> "$GF_PATHS_HOME/.aws/credentials"
fi
fi
done

chown grafana:grafana -R ~grafana/.aws
chmod 600 ~grafana/.aws/credentials
chmod 600 "$GF_PATHS_HOME/.aws/credentials"
fi

if [ ! -z "${GF_INSTALL_PLUGINS}" ]; then
OLDIFS=$IFS
IFS=','
for plugin in ${GF_INSTALL_PLUGINS}; do
IFS=$OLDIFS
gosu grafana grafana-cli --pluginsDir "${GF_PATHS_PLUGINS}" plugins install ${plugin}
grafana-cli --pluginsDir "${GF_PATHS_PLUGINS}" plugins install ${plugin}
done
fi

exec gosu grafana /usr/sbin/grafana-server \
--homepath=/usr/share/grafana \
--config="$GF_PATHS_CONFIG" \
cfg:default.log.mode="console" \
cfg:default.paths.data="$GF_PATHS_DATA" \
cfg:default.paths.logs="$GF_PATHS_LOGS" \
cfg:default.paths.plugins="$GF_PATHS_PLUGINS" \
cfg:default.paths.provisioning=$GF_PATHS_PROVISIONING \
exec grafana-server \
--homepath="$GF_PATHS_HOME" \
--config="$GF_PATHS_CONFIG" \
cfg:default.log.mode="console" \
cfg:default.paths.data="$GF_PATHS_DATA" \
cfg:default.paths.logs="$GF_PATHS_LOGS" \
cfg:default.paths.plugins="$GF_PATHS_PLUGINS" \
cfg:default.paths.provisioning="$GF_PATHS_PROVISIONING" \
"$@"