<p>I have deployed Grafana using Helm chart with subpath /grafana. Now trying to access using service nodeport, like this <a href="http://192.168.16.15:30837/grafana/" rel="nofollow noreferrer">http://192.168.16.15:30837/grafana/</a></p>

<p>But Grafana has failed to load its application files. How can I make this work with nodeport.</p>

<p><strong>log message.</strong></p>

<pre><code>lvl=info msg="HTTP Server Listen" logger=http.server address=0.0.0.0:3000 protocol=http subUrl=/grafana socket=
                                                                                                                                                              ··
t=2018-12-03T02:35:44+0000 lvl=info msg="Request Completed" logger=context userId=0 orgId=0 uname= method=GET path=/grafana/ status=404 t=2018-12-03T02:35:44+0000 lvl=info msg="Request Completed" logger=context userId=0 orgId=0 uname= method=GETpath=/grafana/public/build/app.d1f313cb0bbe86ea2│·······
d5f.js status=404
</code></pre>

<p><strong>Config file</strong></p>

<pre><code>$ cat /etc/grafana/grafana.ini  
[analytics]                                                                        
check_for_updates = true                                                           
[grafana_net]                                                                      
url = https://grafana.net                                                          
[log]                                                                              
mode = console                                                                     
[paths]                                                                            
data = /var/lib/grafana/data                                                       
logs = /var/log/grafana                                                            
plugins = /var/lib/grafana/plugins                                                 
provisioning = /etc/grafana/provisioning                                           
[server]                                                                           
root_url = http://localhost:3000/grafana/ 
</code></pre>

<p>Also tired root url with this value, didn't work also.</p>

<pre><code>  server:
    root_url: http://192.168.16.15:30837/grafana
</code></pre>

<p><strong>Browser message.</strong></p>

<p><a href="https://i.stack.imgur.com/YbOo5.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/YbOo5.png" alt="enter image description here"></a>    </p>

<p>Thanks
SR                                </p>

<p>adding values.yaml file</p>

<pre><code>rbac:
  create: true
  pspEnabled: true
serviceAccount:
  create: true
  name:

replicas: 1

deploymentStrategy: RollingUpdate

readinessProbe:
  httpGet:
    path: /api/health
    port: 3000

livenessProbe:
  httpGet:
    path: /api/health
    port: 3000
  initialDelaySeconds: 60
  timeoutSeconds: 30
  failureThreshold: 10

image:
  repository: grafana/grafana
  tag: 5.3.4
  pullPolicy: IfNotPresent

  ## Optionally specify an array of imagePullSecrets.
  ## Secrets must be manually created in the namespace.
  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
  ##
  # pullSecrets:
  #   - myRegistrKeySecretName

securityContext:
  runAsUser: 472
  fsGroup: 472

## Assign a PriorityClassName to pods if set
# priorityClassName:

downloadDashboardsImage:
  repository: appropriate/curl
  tag: latest
  pullPolicy: IfNotPresent

## Pod Annotations
# podAnnotations: {}

## Deployment annotations
# annotations: {}

## Expose the grafana service to be accessed from outside the cluster (LoadBalancer service).
## or access it from within the cluster (ClusterIP service). Set the service type and the port to serve it.
## ref: http://kubernetes.io/docs/user-guide/services/
##
service:
  type: NodePort
  port: 80
  annotations: {}
  labels: {}

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  labels: {}
  path: /
  hosts:
    - chart-example.local
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

resources: {}
#  limits:
#    cpu: 100m
#    memory: 128Mi
#  requests:
#    cpu: 100m
#    memory: 128Mi

## Node labels for pod assignment
## ref: https://kubernetes.io/docs/user-guide/node-selection/
#
nodeSelector: {}

## Tolerations for pod assignment
## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
##
tolerations: []

## Affinity for pod assignment
## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
##
affinity: {}

## Enable persistence using Persistent Volume Claims
## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/
##
persistence:
  enabled: false
  # storageClassName: default
  # accessModes:
  #   - ReadWriteOnce
  # size: 10Gi
  # annotations: {}
  # subPath: ""
  # existingClaim:

adminUser: admin
# adminPassword: strongpassword

## Use an alternate scheduler, e.g. "stork".
## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/
##
# schedulerName:

## Extra environment variables that will be pass onto deployment pods
env: {}

## The name of a secret in the same kubernetes namespace which contain values to be added to the environment
## This can be useful for auth tokens, etc
envFromSecret: ""

## Additional grafana server secret mounts
# Defines additional mounts with secrets. Secrets must be manually created in the namespace.
extraSecretMounts: []
  # - name: secret-files
  #   mountPath: /etc/secrets
  #   secretName: grafana-secret-files
  #   readOnly: true

## Pass the plugins you want installed as a list.
##
plugins: []
  # - digrich-bubblechart-panel
  # - grafana-clock-panel

## Configure grafana datasources
## ref: http://docs.grafana.org/administration/provisioning/#datasources
##
datasources: {}
#  datasources.yaml:
#    apiVersion: 1
#    datasources:
#    - name: Prometheus
#      type: prometheus
#      url: http://prometheus-prometheus-server
#      access: proxy
#      isDefault: true

## Configure grafana dashboard providers
## ref: http://docs.grafana.org/administration/provisioning/#dashboards
##
## `path` must be /var/lib/grafana/dashboards/&lt;provider_name&gt;
##
dashboardProviders: {}
#  dashboardproviders.yaml:
#    apiVersion: 1
#    providers:
#    - name: 'default'
#      orgId: 1
#      folder: ''
#      type: file
#      disableDeletion: false
#      editable: true
#      options:
#        path: /var/lib/grafana/dashboards/default

## Configure grafana dashboard to import
## NOTE: To use dashboards you must also enable/configure dashboardProviders
## ref: https://grafana.com/dashboards
##
## dashboards per provider, use provider name as key.
##
dashboards: {}
#  default:
#    some-dashboard:
#      json: |
#        $RAW_JSON
#    prometheus-stats:
#      gnetId: 2
#      revision: 2
#      datasource: Prometheus
#    local-dashboard:
#      url: https://example.com/repository/test.json

## Reference to external ConfigMap per provider. Use provider name as key and ConfiMap name as value.
## A provider dashboards must be defined either by external ConfigMaps or in values.yaml, not in both.
## ConfigMap data example:
##
## data:
##   example-dashboard.json: |
##     RAW_JSON
##
dashboardsConfigMaps: {}
#  default: ""

## Grafana's primary configuration
## NOTE: values in map will be converted to ini format
## ref: http://docs.grafana.org/installation/configuration/
##
grafana.ini:
  server:
    root_url: http://192.168.16.15:30837/grafana
  paths:
    data: /var/lib/grafana/data
    logs: /var/log/grafana
    plugins: /var/lib/grafana/plugins
    provisioning: /etc/grafana/provisioning
  analytics:
    check_for_updates: true
  log:
    mode: console
  grafana_net:
    url: https://grafana.net
## LDAP Authentication can be enabled with the following values on grafana.ini
## NOTE: Grafana will fail to start if the value for ldap.toml is invalid
  # auth.ldap:
  #   enabled: true
  #   allow_sign_up: true
  #   config_file: /etc/grafana/ldap.toml

## Grafana's LDAP configuration
## Templated by the template in _helpers.tpl
## NOTE: To enable the grafana.ini must be configured with auth.ldap.enabled
## ref: http://docs.grafana.org/installation/configuration/#auth-ldap
## ref: http://docs.grafana.org/installation/ldap/#configuration
ldap:
  # `existingSecret` is a reference to an existing secret containing the ldap configuration
  # for Grafana in a key `ldap-toml`.
  existingSecret: ""
  # `config` is the content of `ldap.toml` that will be stored in the created secret
  config: ""
  # config: |-
  #   verbose_logging = true

  #   [[servers]]
  #   host = "my-ldap-server"
  #   port = 636
  #   use_ssl = true
  #   start_tls = false
  #   ssl_skip_verify = false
  #   bind_dn = "uid=%s,ou=users,dc=myorg,dc=com"

## Grafana's SMTP configuration
## NOTE: To enable, grafana.ini must be configured with smtp.enabled
## ref: http://docs.grafana.org/installation/configuration/#smtp
smtp:
  # `existingSecret` is a reference to an existing secret containing the smtp configuration
  # for Grafana in keys `user` and `password`.
  existingSecret: ""

## Sidecars that collect the configmaps with specified label and stores the included files them into the respective folders
## Requires at least Grafana 5 to work and can't be used together with parameters dashboardProviders, datasources and dashboards
sidecar:
  image: kiwigrid/k8s-sidecar:0.0.6
  imagePullPolicy: IfNotPresent
  resources:
#   limits:
#     cpu: 100m
#     memory: 100Mi
#   requests:
#     cpu: 50m
#     memory: 50Mi
  dashboards:
    enabled: false
    # label that the configmaps with dashboards are marked with
    label: grafana_dashboard
    # folder in the pod that should hold the collected dashboards
    folder: /tmp/dashboards
    # If specified, the sidecar will search for dashboard config-maps inside this namespace.
    # Otherwise the namespace in which the sidecar is running will be used.
    # It's also possible to specify ALL to search in all namespaces
    searchNamespace: null
  datasources:
    enabled: false
    # label that the configmaps with datasources are marked with
    label: grafana_datasource
    # If specified, the sidecar will search for datasource config-maps inside this namespace.
    # Otherwise the namespace in which the sidecar is running will be used.
    # It's also possible to specify ALL to search in all namespaces
    searchNamespace: null
</code></pre>

sfgroups
<p>I dont know specifically how to solve it for when exposing grafana using NodePort, but my experience below might help point you to a solution, where i have used Istio. You can also replicate the same using a regular nginx kubernetes ingress as well.</p>

<p>I had a similar issue with exposing grafana under a subpath in kubernetes. I was trying to expose the services via an Istio VirtualService. My Istio config is below</p>

<pre><code>apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: prometheus-vs
spec:
  hosts:
  - "my.cluster.fqdn"
  gateways:
  - prometheus-gateway 
  http:
  - match:
    - uri:
        prefix: "/monitoring/grafana"
    route:
    - destination:
        port:
          number: 80
        host: prometheus-operator-grafana
</code></pre>

<p>And this is the snippet from my values.yml</p>

<pre><code>  grafana.ini:
    paths:
      data: /var/lib/grafana/data
      logs: /var/log/grafana
      plugins: /var/lib/grafana/plugins
      provisioning: /etc/grafana/provisioning
    analytics:
      check_for_updates: true
    log:
      mode: console
    grafana_net:
      url: https://grafana.net
    server:
      domain: my.cluster.fqdn
      root_url: "http://my.cluster.fqdn/monitoring/grafana"
</code></pre>

<p><strong>I stumbled upon <a href="https://github.com/istio/istio/issues/585" rel="nofollow noreferrer">https://github.com/istio/istio/issues/585</a>, which in summary mentions that we need to rewrite the path, before grafana sees it. It also shows an example of an Ingress config, which does a rewrite</strong> </p>

<p>In my case i modified my Istio Virtual Service to </p>

<pre><code>kind: VirtualService
metadata:
  name: prometheus-vs
spec:
  hosts:
  - "my.cluster.fqdn"
  gateways:
  - prometheus-gateway 
  http:
  - match:
    - uri:
        prefix: "/monitoring/grafana/"
    rewrite:
      uri: /
    route:
    - destination:
        port:
          number: 80
        host: prometheus-operator-grafana
</code></pre>

<p>and that made it work!</p>
