mirror of https://github.com/grafana/grafana
FrontendService: Local dev setup (#108082)
* wip * docker compose dev setup * commit new tilt stuff * move files into own dir * reset files back to main * use just one nginx container for both gateway and cdn * update proxy service name * make it all work when in subdir * rename more things * reset more changes * fix config * add makefile command, fix ws upgrade * add local check script * tidy * tidy up, comments, readyme * codeowners * change cdn host to localhost to avoid adding host.docker.internal to /etc/hosts * route POST /login to backend * Build nginx container with config baked in so it can be live_update-ed * fix headerspull/108191/head
parent
a0085b6cab
commit
7a88a64121
@ -0,0 +1,18 @@ |
||||
# frontend-service local dev |
||||
|
||||
This directory contains a docker compose + Tilt setup for running a full frontend service stack locally. It contains: |
||||
|
||||
- frontend-service |
||||
- backend api |
||||
- static asset cdn. |
||||
|
||||
## Getting started |
||||
|
||||
On top of the main Grafana development dependencies, you will need installed: |
||||
|
||||
- [Docker](https://docs.docker.com/get-started/get-docker/) |
||||
- [Tilt](https://docs.tilt.dev/install.html). At the moment we're not using Kubernetes locally, so you shouldn't need to follow the instructions to install kubectl or kind. |
||||
|
||||
To start the stack, from the root of the Grafana project run `make frontend-service-up`. Tilt will orchestrate the webpack and docker builds, and then run the services with Docker compose. You can monitor it's progress and see logs with the URL to the Tilt console. Once done, you can access Grafana at `http://localhost:3000`. |
||||
|
||||
Quitting the process will not stop the service from running. Run `make frontend-service-down` when done to shut down the docker containers. |
@ -0,0 +1,80 @@ |
||||
# --- Frontend processes |
||||
local_resource( |
||||
'yarn install', |
||||
cmd='yarn install', |
||||
deps=[ |
||||
'yarn.lock', |
||||
], |
||||
labels=["frontend"] |
||||
) |
||||
|
||||
local_resource( |
||||
'yarn start', |
||||
cmd='rm -rf public/build/assets-manifest.json', |
||||
serve_cmd='yarn start:noLint', |
||||
resource_deps=['yarn install'], |
||||
readiness_probe=probe( |
||||
initial_delay_secs=5, # wait for the assets-manifest.json to first be deleted |
||||
period_secs=1, |
||||
exec=exec_action(["bash", "-c", "cat public/build/assets-manifest.json | grep entrypoints"]) |
||||
), |
||||
allow_parallel=True, |
||||
labels=["frontend"] |
||||
) |
||||
|
||||
# --- Docker Compose |
||||
docker_compose("./docker-compose.yaml") |
||||
|
||||
# First argument is the name of the service from the docker-compose file. |
||||
dc_resource("backend", resource_deps=["yarn start"], labels=["backend"]) |
||||
dc_resource("frontend-service", resource_deps=["yarn start"], labels=["backend"]) |
||||
dc_resource("proxy", resource_deps=["backend", "frontend-service"], labels=["ingress"]) |
||||
|
||||
docker_build('grafana-backend', '../..', |
||||
dockerfile='backend.dockerfile', |
||||
|
||||
# Only these paths will be in the docker context, and will trigger a rebuild. |
||||
# This must include all the files that are COPY'd in backend.dockerfile |
||||
only=[ |
||||
"./Makefile", |
||||
"./devenv/frontend-service/build-grafana.sh", |
||||
|
||||
"./apps", |
||||
"./pkg", |
||||
"./scripts", |
||||
|
||||
"./go.sum", |
||||
"./go.mod", |
||||
"./go.work", |
||||
"./go.work.sum", |
||||
|
||||
"./kinds", |
||||
"./kindsv2", |
||||
"./public/api-merged.json", |
||||
"./package.json", |
||||
|
||||
"./conf/defaults.ini", |
||||
"./conf/ldap.toml", |
||||
"./conf/ldap_multiple.toml", |
||||
"./public/emails", |
||||
"./public/views", |
||||
"./public/dashboards", |
||||
"./public/build/assets-manifest.json", |
||||
], |
||||
live_update = [ |
||||
sync('./public/build/assets-manifest.json', '/grafana/public/build/assets-manifest.json'), |
||||
restart_container() |
||||
] |
||||
) |
||||
|
||||
|
||||
docker_build('grafana-proxy', '.', |
||||
dockerfile='proxy.dockerfile', |
||||
only=[ |
||||
"./nginx.conf", |
||||
], |
||||
live_update = [ |
||||
sync('./nginx.conf', '/etc/nginx/conf.d/default.conf'), |
||||
restart_container() |
||||
] |
||||
) |
@ -0,0 +1,92 @@ |
||||
ARG BASE_IMAGE=alpine:3.21 |
||||
ARG GO_IMAGE=golang:1.24.4-alpine |
||||
|
||||
# ----- Go build stage |
||||
FROM ${GO_IMAGE} AS go-dev-builder |
||||
|
||||
RUN apk add --no-cache \ |
||||
binutils-gold \ |
||||
bash \ |
||||
gcc g++ make git jq findutils |
||||
|
||||
WORKDIR /build-grafana |
||||
|
||||
RUN go env GOCACHE |
||||
RUN go env GOPATH |
||||
|
||||
# All files COPY'd here must be included in the `only` list in Tiltfile |
||||
# otherwise the image will not build with Tilt. |
||||
|
||||
COPY Makefile devenv/frontend-service/build-grafana.sh ./ |
||||
|
||||
# Copy go mod files first |
||||
# $: ls -1 {pkg,scripts,apps}**/go.{mod,sum} | sed 's#\(.*\)/go\.\(mod\|sum\)#COPY \1/go.* \1/#' | sort -u |
||||
COPY apps/advisor/go.* apps/advisor/ |
||||
COPY apps/alerting/notifications/go.* apps/alerting/notifications/ |
||||
COPY apps/dashboard/go.* apps/dashboard/ |
||||
COPY apps/folder/go.* apps/folder/ |
||||
COPY apps/iam/go.* apps/iam/ |
||||
COPY apps/investigations/go.* apps/investigations/ |
||||
COPY apps/playlist/go.* apps/playlist/ |
||||
COPY apps/secret/go.* apps/secret/ |
||||
COPY pkg/aggregator/go.* pkg/aggregator/ |
||||
COPY pkg/apimachinery/go.* pkg/apimachinery/ |
||||
COPY pkg/apis/secret/go.* pkg/apis/secret/ |
||||
COPY pkg/apiserver/go.* pkg/apiserver/ |
||||
COPY pkg/build/go.* pkg/build/ |
||||
COPY pkg/build/wire/go.* pkg/build/wire/ |
||||
COPY pkg/codegen/go.* pkg/codegen/ |
||||
COPY pkg/plugins/codegen/go.* pkg/plugins/codegen/ |
||||
COPY pkg/promlib/go.* pkg/promlib/ |
||||
COPY pkg/semconv/go.* pkg/semconv/ |
||||
COPY scripts/go-workspace/go.* scripts/go-workspace/ |
||||
COPY scripts/modowners/go.* scripts/modowners/ |
||||
|
||||
COPY go.* ./ |
||||
|
||||
# Install dependencies |
||||
RUN --mount=type=cache,target=/go/pkg/mod \ |
||||
--mount=type=cache,target=/root/.cache/go-build \ |
||||
go mod download |
||||
|
||||
# Copy source files |
||||
COPY kinds kinds |
||||
COPY kindsv2 kindsv2 |
||||
COPY public/api-merged.json public/api-merged.json |
||||
COPY apps apps |
||||
COPY pkg pkg |
||||
COPY package.json package.json |
||||
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \ |
||||
--mount=type=cache,target=/root/.cache/go-build \ |
||||
bash build-grafana.sh |
||||
|
||||
|
||||
# ----- Runtime stage |
||||
FROM ${BASE_IMAGE} |
||||
RUN apk add --no-cache ca-certificates tzdata musl-utils bash |
||||
|
||||
EXPOSE 3000 |
||||
|
||||
WORKDIR /grafana |
||||
|
||||
RUN mkdir -p "conf/provisioning/datasources" \ |
||||
"conf/provisioning/dashboards" \ |
||||
"conf/provisioning/notifiers" \ |
||||
"conf/provisioning/plugins" \ |
||||
"conf/provisioning/access-control" \ |
||||
"conf/provisioning/alerting" |
||||
|
||||
# Copy config files |
||||
COPY conf/defaults.ini conf/ldap.toml conf/ldap_multiple.toml conf/ |
||||
|
||||
COPY public/emails public/emails |
||||
COPY public/views public/views |
||||
COPY public/dashboards public/dashboards |
||||
|
||||
# Copy the Go binary from the go-dev-builder stage |
||||
COPY --from=go-dev-builder /build-grafana/bin/grafana /grafana/bin/grafana |
||||
|
||||
COPY public/build/assets-manifest.json public/build/assets-manifest.json |
||||
|
||||
ENTRYPOINT ["bin/grafana", "server"] |
@ -0,0 +1,12 @@ |
||||
#!/bin/bash |
||||
|
||||
echo "Go mod cache: $(go env GOMODCACHE), $(ls -1 $(go env GOMODCACHE) | wc -l) items" |
||||
echo "Go build cache: $(go env GOCACHE), $(ls -1 $(go env GOCACHE) | wc -l) items" |
||||
|
||||
# Need to build version into the binary so plugin compatibility works correctly |
||||
VERSION=$(jq -r .version package.json) |
||||
|
||||
go build \ |
||||
-ldflags "-X main.version=${VERSION}" \ |
||||
-gcflags "all=-N -l" \ |
||||
-o ./bin/grafana ./pkg/cmd/grafana |
@ -0,0 +1,45 @@ |
||||
name: grafana-fs-dev |
||||
|
||||
services: |
||||
proxy: |
||||
image: grafana-proxy |
||||
build: |
||||
context: . |
||||
dockerfile: proxy.dockerfile |
||||
volumes: |
||||
- ../../public/build:/cdn/public/build |
||||
- ../../public/fonts:/cdn/public/fonts |
||||
ports: |
||||
- '3000:80' # Gateway |
||||
- '3010:81' # CDN |
||||
|
||||
backend: |
||||
image: grafana-backend |
||||
build: |
||||
context: . |
||||
dockerfile: backend.dockerfile |
||||
entrypoint: ['bin/grafana', 'server'] |
||||
volumes: |
||||
- backend-data:/grafana/data |
||||
- ../../public/app/plugins:/grafana/public/app/plugins |
||||
environment: |
||||
GF_FEATURE_TOGGLES_ENABLE: multiTenantFrontend |
||||
GF_SERVER_CDN_URL: http://localhost:3010 |
||||
ports: |
||||
- '3011:3000' |
||||
|
||||
frontend-service: |
||||
image: grafana-backend |
||||
build: |
||||
dockerfile: backend.dockerfile |
||||
entrypoint: ['bin/grafana', 'server', 'target'] |
||||
ports: |
||||
- '3012:3000' |
||||
environment: |
||||
GF_DEFAULT_APP_MODE: development |
||||
GF_DEFAULT_TARGET: frontend-server |
||||
GF_SECURITY_CONTENT_SECURITY_POLICY: false |
||||
GF_SERVER_CDN_URL: http://localhost:3010 |
||||
|
||||
volumes: |
||||
backend-data: |
@ -0,0 +1,26 @@ |
||||
#!/bin/bash |
||||
|
||||
# Used to check if the local dev environment is set up correctly |
||||
|
||||
IS_OKAY=true |
||||
|
||||
if ! docker ps &> /dev/null; then |
||||
echo "Error: docker is not installed or running" |
||||
echo "See https://docs.docker.com/get-docker/ for installation instructions" |
||||
echo "" |
||||
IS_OKAY=false |
||||
fi |
||||
|
||||
if ! tilt version &> /dev/null; then |
||||
echo "Error: tilt is not installed" |
||||
echo "See https://docs.tilt.dev/install.html for installation instructions." |
||||
echo "For frontend-service, you can ignore the kubernetes instructions." |
||||
echo "" |
||||
IS_OKAY=false |
||||
fi |
||||
|
||||
|
||||
if [ "$IS_OKAY" = false ]; then |
||||
echo "Please fix the above errors before continuing" |
||||
exit 1 |
||||
fi |
@ -0,0 +1,92 @@ |
||||
### |
||||
# Instance |
||||
### |
||||
upstream backend { |
||||
server backend:3000; |
||||
} |
||||
|
||||
upstream frontend { |
||||
server frontend-service:3000; |
||||
} |
||||
|
||||
server { |
||||
listen 80; |
||||
server_name _; |
||||
|
||||
# Special‐case POST /login to backend, GET to frontend |
||||
location = /login { |
||||
proxy_set_header Host $host; |
||||
proxy_set_header X-Real-IP $remote_addr; |
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
proxy_set_header X-Forwarded-Proto $scheme; |
||||
|
||||
if ($request_method = POST) { |
||||
proxy_pass http://backend; |
||||
break; |
||||
} |
||||
|
||||
if ($request_method = GET) { |
||||
proxy_pass http://frontend; |
||||
break; |
||||
} |
||||
|
||||
return 405; |
||||
} |
||||
|
||||
# API calls go to the backend |
||||
# Cheat with app plugin paths and route them to the backend. These should come from |
||||
# the Plugin CDN |
||||
location ~ ^/(api|apis|bootdata|public\/plugins\/grafana\-\w+\-app) { |
||||
proxy_pass http://backend; |
||||
proxy_set_header Host $host; |
||||
proxy_set_header X-Real-IP $remote_addr; |
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
proxy_set_header X-Forwarded-Proto $scheme; |
||||
proxy_set_header Upgrade $http_upgrade; |
||||
proxy_set_header Connection "upgrade"; |
||||
} |
||||
|
||||
# Everything else to the frontend |
||||
location / { |
||||
proxy_pass http://frontend; |
||||
proxy_set_header Host $host; |
||||
proxy_set_header X-Real-IP $remote_addr; |
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
||||
proxy_set_header X-Forwarded-Proto $scheme; |
||||
} |
||||
} |
||||
|
||||
|
||||
### |
||||
# CDN |
||||
### |
||||
map $request $loggable { |
||||
default 1; |
||||
"~^\x16\x03" 0; |
||||
} |
||||
|
||||
server { |
||||
listen 81; |
||||
server_name localhost; |
||||
root /cdn; |
||||
|
||||
# Enable directory listing |
||||
autoindex on; |
||||
autoindex_exact_size off; |
||||
autoindex_format html; |
||||
autoindex_localtime on; |
||||
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate" always; |
||||
add_header Pragma "no-cache" always; |
||||
add_header Expires "0" always; |
||||
add_header Access-Control-Allow-Origin "*" always; |
||||
add_header Access-Control-Allow-Headers "X-Grafana-Device-Id" always; |
||||
|
||||
# Suppress access log for malformed HTTPS requests (those starting with a TLS handshake) |
||||
access_log /var/log/nginx/access.log main if=$loggable; |
||||
|
||||
# This serves paths like /grafana/12.1.0-88106/public/build/foo |
||||
location ~ ^/grafana[^/]*/[^/]+/(.*)$ { |
||||
alias /cdn/$1; |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
# Runtime stage - just a simple nginx to serve static files |
||||
# We bake the config into the image so we can live-update it with Tilt when it changes |
||||
FROM nginx:alpine |
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf |
Loading…
Reference in new issue