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 headers
pull/108191/head
Josh Hunt 3 days ago committed by GitHub
parent a0085b6cab
commit 7a88a64121
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      .github/CODEOWNERS
  2. 12
      Makefile
  3. 2
      conf/defaults.ini
  4. 18
      devenv/frontend-service/README.md
  5. 80
      devenv/frontend-service/Tiltfile
  6. 92
      devenv/frontend-service/backend.dockerfile
  7. 12
      devenv/frontend-service/build-grafana.sh
  8. 45
      devenv/frontend-service/docker-compose.yaml
  9. 26
      devenv/frontend-service/local-init.sh
  10. 92
      devenv/frontend-service/nginx.conf
  11. 5
      devenv/frontend-service/proxy.dockerfile

@ -305,6 +305,7 @@
/devenv/jsonnet/ @grafana/dataviz-squad /devenv/jsonnet/ @grafana/dataviz-squad
/devenv/local_cdn/ @grafana/frontend-ops /devenv/local_cdn/ @grafana/frontend-ops
/devenv/local-npm/ @grafana/frontend-ops /devenv/local-npm/ @grafana/frontend-ops
/devenv/frontend-service/ @grafana/grafana-frontend-platform
/devenv/setup.sh @grafana/grafana-backend-services-squad /devenv/setup.sh @grafana/grafana-backend-services-squad
/devenv/plugins.yaml @grafana/plugins-platform-frontend /devenv/plugins.yaml @grafana/plugins-platform-frontend

@ -279,6 +279,18 @@ run-frontend: deps-js ## Fetch js dependencies and watch frontend for rebuild
run-air: ## [Experimental] Build and run backend, and watch for changes. See .air.toml for configuration. Check https://github.com/air-verse/air for installation instructions. run-air: ## [Experimental] Build and run backend, and watch for changes. See .air.toml for configuration. Check https://github.com/air-verse/air for installation instructions.
air -c .air.toml air -c .air.toml
.PHONY: frontend-service-check
frontend-service-check:
./devenv/frontend-service/local-init.sh
.PHONY: frontend-service-up
frontend-service-up: frontend-service-check
tilt up -f devenv/frontend-service/Tiltfile
.PHONY: frontend-service-down
frontend-service-down: frontend-service-check
tilt down -f devenv/frontend-service/Tiltfile
##@ Testing ##@ Testing
.PHONY: test-go .PHONY: test-go

@ -9,6 +9,8 @@ app_mode = production
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty # instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
instance_name = ${HOSTNAME} instance_name = ${HOSTNAME}
target =
#################################### Paths ############################### #################################### Paths ###############################
[paths] [paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)

@ -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…
Cancel
Save