Grafana接入单点登陆cas实践
grafana是一款比较流行且好用的可视化制图工具,在实战过程中,往往需要与公司内已有系统做打通,势必带来的问题是如何和内部系统做联动
对grafana添加auth.proxy属性
在grafana.ini配置文件中添加如下配置:
[auth.proxy]
enabled=true
header_name=X-WEBAUTH-USER
header_property= username
auto_sign_up= true
编写grafana-cas,通过golang实现反向代理
package main
import (
"flag"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/go-cas/cas"
"github.com/go-chi/chi"
"github.com/golang/glog"
)
var (
casURL string
grafanaAddr string
serviceAddr string
)
func init() {
flag.StringVar(&casURL, "cas-url", "sso.example.com", "cas url")
flag.StringVar(&grafanaAddr, "grafana-addr", "localhost:3000", "grafana addr")
flag.StringVar(&serviceAddr, "service-addr", ":8080", "grafana proxy addr")
flag.Parse()
}
func main() {
url, _ := url.Parse(casURL)
client := cas.NewClient(&cas.Options{URL: url})
httpClient = createHTTPClient()
root := chi.NewRouter()
root.Use(client.Handler)
root.HandleFunc("/*", handle)
server := &http.Server{
Addr: serviceAddr,
Handler: client.Handle(root),
}
if err := server.ListenAndServe(); err != nil {
glog.Fatal(err)
}
}
var (
httpClient *http.Client
)
const (
MaxIdleConnections int = 20
RequestTimeout int = 30
)
// 重用httpclient
func createHTTPClient() *http.Client {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 10 * time.Second,
}).Dial,
IdleConnTimeout: 10 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
MaxIdleConns: 50,
MaxIdleConnsPerHost: MaxIdleConnections,
},
Timeout: time.Duration(RequestTimeout) * time.Second,
}
return client
}
func handle(w http.ResponseWriter, r *http.Request) {
// 通过cas登陆的结果获取userName
user := strings.Split(cas.Username(r), "@")[0]
r.Header.Add("X-WEBAUTH-USER", user)
r.Host, r.URL.Host = grafanaAddr, grafanaAddr
r.RequestURI, r.URL.Scheme = "", "https"
// 设置连接池
transport := http.DefaultTransport
outReq := new(http.Request)
*outReq = *r
res, err := transport.RoundTrip(outReq)
if err != nil {
w.WriteHeader(http.StatusBadGateway)
return
}
// 将登陆后的header传递给代理对象
for key, value := range res.Header {
for _, v := range value {
w.Header().Add(key, v)
}
}
w.WriteHeader(res.StatusCode)
// 将返回结果copy
io.Copy(w, res.Body)
res.Body.Close()
}
使用grafana-cas镜像
Dockerfile
FROM golang:1.14.1
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct \
GIN_MODE=release \
PORT=8080
WORKDIR /app
COPY . /app
RUN go build -o /app/grafana-cas -gcflags "all=-N -l" grafana-cas.go
RUN chmod +x ./grafana-cas
EXPOSE 8080
ENTRYPOINT ["./grafana-cas"]
与k8s做集成
在helm中添加grafanaCas配置
grafanaCas:
enabled: true
image:
repository: {{imageRepository}}
tag: {{imageTag}}
pullPolicy: IfNotPresent
replicas: 1
service:
type: ClusterIP
root_url: {{grafana的真实地址}}
port: 80
targetPort: 8080
annotations: {}
labels: {}
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
labels: {}
path: /
hosts:
- {{对外提供的grafana的地址}}
tls:
- secretName: {{tls secret}}
hosts:
- {{对外提供的grafana的地址}}
deploymentStrategy:
type: RollingUpdate
maxSurge: 25%
maxUnavailable: 25%
readinessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 20
timeoutSeconds: 300
failureThreshold: 10
添加deployment,ingress,svc
deployment
{{- if .Values.grafanaCas.enabled -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "grafanaCas.fullname" . }}
labels:
app: {{ template "grafanaCas.name" . }}
chart: {{ template "grafanaCas.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
replicas: {{ .Values.grafanaCas.replicas }}
{{- with .Values.deploymentStrategy }}
strategy:
{{ toYaml . | trim | indent 4 }}
{{- end }}
selector:
matchLabels:
app: {{ template "grafanaCas.name" . }}
release: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ template "grafanaCas.name" . }}
chart: {{ template "grafanaCas.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
containers:
- name: {{ template "grafanaCas.name" . }}
image: "{{ .Values.grafanaCas.image.repository }}:{{ .Values.grafanaCas.image.tag }}"
imagePullPolicy: {{ .Values.grafanaCas.image.pullPolicy }}
command: ["./grafana-cas"]
ports:
- name: http
containerPort: {{ .Values.grafanaCas.service.targetPort}}
protocol: TCP
livenessProbe:
tcpSocket:
port: http
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 1
readinessProbe:
tcpSocket:
port: http
initialDelaySeconds: 50
periodSeconds: 20
timeoutSeconds: 1
resources:
{{- toYaml .Values.grafanaCas.resources | nindent 12 }}
{{- end }}
ingress
{{- if and .Values.grafanaCas.enabled .Values.grafanaCas.ingress.enabled -}}
{{- $fullName := include "grafanaCas.fullname" . -}}
{{- $servicePort := .Values.grafanaCas.service.port -}}
{{- $ingressPath := .Values.grafanaCas.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
namespace: {{ .Release.Namespace }}
labels:
app: {{ template "grafanaCas.name" . }}
chart: {{ template "grafanaCas.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.grafanaCas.ingress.labels }}
{{ toYaml .Values.grafanaCas.ingress.labels | indent 4 }}
{{- end }}
{{- with .Values.grafanaCas.ingress.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.grafanaCas.ingress.tls }}
tls:
{{- range .Values.grafanaCas.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.grafanaCas.ingress.hosts }}
- host: {{ . }}
http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
servicePort: {{ $servicePort }}
{{- end }}
{{- end }}
svc
{{- if .Values.grafanaCas.enabled -}}
apiVersion: v1
kind: Service
metadata:
name: {{ template "grafanaCas.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
app: {{ template "grafanaCas.name" . }}
chart: {{ template "grafanaCas.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- if .Values.grafanaCas.service.labels }}
{{ toYaml .Values.grafanaCas.service.labels | indent 4 }}
{{- end }}
{{- with .Values.grafanaCas.service.annotations }}
annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if (or (eq .Values.grafanaCas.service.type "ClusterIP") (empty .Values.grafanaCas.service.type)) }}
type: ClusterIP
{{- if .Values.grafanaCas.service.clusterIP }}
clusterIP: {{ .Values.grafanaCas.service.clusterIP }}
{{end}}
{{- else if eq .Values.grafanaCas.service.type "LoadBalancer" }}
type: {{ .Values.grafanaCas.service.type }}
{{- if .Values.grafanaCas.service.loadBalancerIP }}
loadBalancerIP: {{ .Values.grafanaCas.service.loadBalancerIP }}
{{- end }}
{{- if .Values.grafanaCas.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{ toYaml .Values.grafanaCas.service.loadBalancerSourceRanges | indent 4 }}
{{- end -}}
{{- else }}
type: {{ .Values.grafanaCas.service.type }}
{{- end }}
{{- if .Values.grafanaCas.service.externalIPs }}
externalIPs:
{{ toYaml .Values.grafanaCas.service.externalIPs | indent 4 }}
{{- end }}
ports:
- name: service
port: {{ .Values.grafanaCas.service.port }}
protocol: TCP
targetPort: {{ .Values.grafanaCas.service.targetPort }}
{{ if (and (eq .Values.grafanaCas.service.type "NodePort") (not (empty .Values.grafanaCas.service.nodePort))) }}
nodePort: {{.Values.grafanaCas.service.nodePort}}
{{ end }}
selector:
app: {{ template "grafanaCas.name" . }}
release: {{ .Release.Name }}
{{- end }}