mirror of
https://github.com/stefanprodan/podinfo.git
synced 2026-04-07 11:36:50 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d65044ff2e | ||
|
|
18c63ad7f7 | ||
|
|
a8260081d9 | ||
|
|
0ff49e5057 | ||
|
|
79cfe56484 | ||
|
|
7e36892e26 | ||
|
|
3d6d0bed69 | ||
|
|
b213e0af0a | ||
|
|
42ad3faf5a | ||
|
|
939fd5b24d | ||
|
|
36ec3ef378 | ||
|
|
287e005129 | ||
|
|
0b3e88d6de | ||
|
|
10139749da | ||
|
|
f891e0683b | ||
|
|
647b4cba04 | ||
|
|
c5df50c774 | ||
|
|
2b1d325343 | ||
|
|
319d57cb68 | ||
|
|
087da02dbb | ||
|
|
7d00f68180 | ||
|
|
87c9bb8ba2 | ||
|
|
5fb970b526 | ||
|
|
56b404bd84 | ||
|
|
a12d0a1ed7 | ||
|
|
51979787b0 | ||
|
|
8b37756118 | ||
|
|
1eb1da110b |
@@ -19,12 +19,12 @@ jobs:
|
||||
|
||||
push-container:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
- image: circleci/golang:1.13
|
||||
working_directory: ~/build
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker:
|
||||
docker_layer_caching: true
|
||||
docker_layer_caching: false
|
||||
- run: make build-container
|
||||
- run: |
|
||||
if [ -z "$CIRCLE_TAG" ]; then
|
||||
@@ -37,14 +37,14 @@ jobs:
|
||||
|
||||
push-binary:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
- image: circleci/golang:1.13
|
||||
steps:
|
||||
- checkout
|
||||
- run: curl -sL https://git.io/goreleaser | bash
|
||||
|
||||
push-helm-charts:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
- image: circleci/golang:1.13
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
|
||||
51
.github/policy/kubernetes.rego
vendored
Normal file
51
.github/policy/kubernetes.rego
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package kubernetes
|
||||
|
||||
name = input.metadata.name
|
||||
|
||||
kind = input.kind
|
||||
|
||||
is_service {
|
||||
input.kind = "Service"
|
||||
}
|
||||
|
||||
is_deployment {
|
||||
input.kind = "Deployment"
|
||||
}
|
||||
|
||||
is_pod {
|
||||
input.kind = "Pod"
|
||||
}
|
||||
|
||||
split_image(image) = [image, "latest"] {
|
||||
not contains(image, ":")
|
||||
}
|
||||
|
||||
split_image(image) = [image_name, tag] {
|
||||
[image_name, tag] = split(image, ":")
|
||||
}
|
||||
|
||||
pod_containers(pod) = all_containers {
|
||||
keys = {"containers", "initContainers"}
|
||||
all_containers = [c | keys[k]; c = pod.spec[k][_]]
|
||||
}
|
||||
|
||||
containers[container] {
|
||||
pods[pod]
|
||||
all_containers = pod_containers(pod)
|
||||
container = all_containers[_]
|
||||
}
|
||||
|
||||
containers[container] {
|
||||
all_containers = pod_containers(input)
|
||||
container = all_containers[_]
|
||||
}
|
||||
|
||||
pods[pod] {
|
||||
is_deployment
|
||||
pod = input.spec.template
|
||||
}
|
||||
|
||||
pods[pod] {
|
||||
is_pod
|
||||
pod = input
|
||||
}
|
||||
43
.github/policy/rules.rego
vendored
Normal file
43
.github/policy/rules.rego
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import data.kubernetes
|
||||
|
||||
name = input.metadata.name
|
||||
|
||||
# Deny containers with latest image tag
|
||||
deny[msg] {
|
||||
kubernetes.containers[container]
|
||||
[image_name, "latest"] = kubernetes.split_image(container.image)
|
||||
msg = sprintf("%s in the %s %s has an image %s, using the latest tag", [container.name, kubernetes.kind, kubernetes.name, image_name])
|
||||
}
|
||||
|
||||
# Deny services without app label selector
|
||||
service_labels {
|
||||
input.spec.selector["app"]
|
||||
}
|
||||
deny[msg] {
|
||||
kubernetes.is_service
|
||||
not service_labels
|
||||
msg = sprintf("Service %s should set app label selector", [name])
|
||||
}
|
||||
|
||||
# Deny deployments without app label selector
|
||||
match_labels {
|
||||
input.spec.selector.matchLabels["app"]
|
||||
}
|
||||
deny[msg] {
|
||||
kubernetes.is_deployment
|
||||
not match_labels
|
||||
msg = sprintf("Service %s should set app label selector", [name])
|
||||
}
|
||||
|
||||
# Warn if deployments have no prometheus pod annotations
|
||||
annotations {
|
||||
input.spec.template.metadata.annotations["prometheus.io/scrape"]
|
||||
input.spec.template.metadata.annotations["prometheus.io/port"]
|
||||
}
|
||||
warn[msg] {
|
||||
kubernetes.is_deployment
|
||||
not annotations
|
||||
msg = sprintf("Deployment %s should set prometheus.io/scrape and prometheus.io/port pod annotations", [name])
|
||||
}
|
||||
17
.github/workflows/test.yml
vendored
Normal file
17
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
on: [push, pull_request]
|
||||
name: kustomize
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: kubeval
|
||||
uses: stefanprodan/kube-tools@v1
|
||||
with:
|
||||
command: |
|
||||
kustomize build ./kustomize | kubeval --strict
|
||||
- name: conftest
|
||||
uses: stefanprodan/kube-tools@v1
|
||||
with:
|
||||
command: |
|
||||
kustomize build ./kustomize | conftest test -p .github/policy -
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.12 as builder
|
||||
FROM golang:1.13 as builder
|
||||
|
||||
RUN mkdir -p /podinfo/
|
||||
|
||||
@@ -6,7 +6,7 @@ WORKDIR /podinfo
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN GOPROXY=https://proxy.golang.org go mod download
|
||||
RUN go mod download
|
||||
|
||||
RUN go test -v -race ./...
|
||||
|
||||
|
||||
4
Makefile
4
Makefile
@@ -10,7 +10,9 @@ GIT_COMMIT:=$(shell git describe --dirty --always)
|
||||
VERSION:=$(shell grep 'VERSION' pkg/version/version.go | awk '{ print $$4 }' | tr -d '"')
|
||||
|
||||
run:
|
||||
GO111MODULE=on go run -ldflags "-s -w -X github.com/stefanprodan/podinfo/pkg/version.REVISION=$(GIT_COMMIT)" cmd/podinfo/* --level=debug --grpc-port=9999
|
||||
GO111MODULE=on go run -ldflags "-s -w -X github.com/stefanprodan/podinfo/pkg/version.REVISION=$(GIT_COMMIT)" cmd/podinfo/* \
|
||||
--level=debug --grpc-port=9999 --backend-url=https://httpbin.org/status/401 --backend-url=https://httpbin.org/status/500 \
|
||||
--ui-logo=https://media.giphy.com/media/l0ExbNdlJFGRphMR2/giphy.gif
|
||||
|
||||
test:
|
||||
GO111MODULE=on go test -v -race ./...
|
||||
|
||||
13
README.md
13
README.md
@@ -1,10 +1,11 @@
|
||||
# podinfo
|
||||
|
||||
[](https://circleci.com/gh/stefanprodan/podinfo)
|
||||
[](https://github.com/stefanprodan/podinfo/blob/master/.github/workflows/test.yml)
|
||||
[](https://goreportcard.com/report/github.com/stefanprodan/podinfo)
|
||||
[](https://hub.docker.com/r/stefanprodan/podinfo)
|
||||
|
||||
Podinfo is a tiny web application made with Go
|
||||
that showcases best practices of running microservices in Kubernetes.
|
||||
Podinfo is a tiny web application made with Go that showcases best practices of running microservices in Kubernetes.
|
||||
|
||||
Specifications:
|
||||
|
||||
@@ -13,12 +14,14 @@ Specifications:
|
||||
* File watcher for secrets and configmaps
|
||||
* Instrumented with Prometheus
|
||||
* Tracing with Istio and Jaeger
|
||||
* Linkerd service profile
|
||||
* Structured logging with zap
|
||||
* 12-factor app with viper
|
||||
* Fault injection (random errors and latency)
|
||||
* Swagger docs
|
||||
* Helm and Kustomize installers
|
||||
* End-to-End testing with Kubernetes Kind and Helm
|
||||
* Kustomize testing with GitHub Actions and Open Policy Agent
|
||||
|
||||
Web API:
|
||||
|
||||
@@ -66,20 +69,20 @@ To access the Swagger UI open `<podinfo-host>/swagger/index.html` in a browser.
|
||||
Helm:
|
||||
|
||||
```bash
|
||||
helm repo add sp https://stefanprodan.github.io/podinfo
|
||||
helm repo add podinfo https://stefanprodan.github.io/podinfo
|
||||
|
||||
helm upgrade --install --wait frontend \
|
||||
--namespace test \
|
||||
--set replicaCount=2 \
|
||||
--set backend=http://backend-podinfo:9898/echo \
|
||||
sp/podinfo
|
||||
podinfo/podinfo
|
||||
|
||||
helm test frontend --cleanup
|
||||
|
||||
helm upgrade --install --wait backend \
|
||||
--namespace test \
|
||||
--set hpa.enabled=true \
|
||||
sp/podinfo
|
||||
podinfo/podinfo
|
||||
```
|
||||
|
||||
Kustomize:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
apiVersion: v1
|
||||
version: 3.0.0
|
||||
appVersion: 3.0.0
|
||||
version: 3.1.4
|
||||
appVersion: 3.1.4
|
||||
name: podinfo
|
||||
engine: gotpl
|
||||
description: Podinfo Helm chart for Kubernetes
|
||||
|
||||
@@ -8,7 +8,7 @@ that showcases best practices of running microservices in Kubernetes.
|
||||
To install the chart with the release name `my-release`:
|
||||
|
||||
```console
|
||||
$ helm repo add sp https://stefanprodan.github.io/k8s-podinfo
|
||||
$ helm repo add sp https://stefanprodan.github.io/podinfo
|
||||
$ helm upgrade my-release --install sp/podinfo
|
||||
```
|
||||
|
||||
@@ -33,6 +33,7 @@ Parameter | Description | Default
|
||||
--- | --- | ---
|
||||
`affinity` | node/pod affinities | None
|
||||
`backend` | echo backend URL | None
|
||||
`backends` | echo backend URL array | None
|
||||
`faults.delay` | random HTTP response delays between 0 and 5 seconds | `false`
|
||||
`faults.error` | 1/3 chances of a random HTTP response error | `false`
|
||||
`hpa.enabled` | enables HPA | `false`
|
||||
@@ -40,8 +41,6 @@ Parameter | Description | Default
|
||||
`hpa.memory` | target memory usage per pod | None
|
||||
`hpa.requests` | target requests per second per pod | None
|
||||
`hpa.maxReplicas` | maximum pod replicas | `10`
|
||||
`ingress.hosts` | ingress accepted hostnames | None
|
||||
`ingress.tls` | ingress TLS configuration | None:
|
||||
`image.pullPolicy` | image pull policy | `IfNotPresent`
|
||||
`image.repository` | image repository | `stefanprodan/podinfo`
|
||||
`image.tag` | image tag | `<VERSION>`
|
||||
@@ -67,6 +66,7 @@ Parameter | Description | Default
|
||||
`tolerations` | list of node taints to tolerate | `[]`
|
||||
`serviceAccount.enabled` | specifies whether a service account should be created | `false`
|
||||
`serviceAccount.name` | the name of the service account to use, if not set and create is true, a name is generated using the fullname template | None
|
||||
`linkerd.profile.enabled` | create Linkerd service profile | `false`
|
||||
|
||||
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
|
||||
|
||||
|
||||
@@ -48,13 +48,27 @@ spec:
|
||||
{{- if .Values.service.grpcService }}
|
||||
- --grpc-service-name={{ .Values.service.grpcService }}
|
||||
{{- end }}
|
||||
{{- range .Values.backends }}
|
||||
- --backend-url={{ . }}
|
||||
{{- end }}
|
||||
- --level={{ .Values.logLevel }}
|
||||
- --random-delay={{ .Values.faults.delay }}
|
||||
- --random-error={{ .Values.faults.error }}
|
||||
{{- if .Values.h2c.enabled }}
|
||||
- --h2c
|
||||
{{- end }}
|
||||
env:
|
||||
{{- if .Values.message }}
|
||||
{{- if .Values.ui.message }}
|
||||
- name: PODINFO_UI_MESSAGE
|
||||
value: {{ .Values.message }}
|
||||
value: {{ .Values.ui.message }}
|
||||
{{- end }}
|
||||
{{- if .Values.ui.logo }}
|
||||
- name: PODINFO_UI_LOGO
|
||||
value: {{ .Values.ui.logo }}
|
||||
{{- end }}
|
||||
{{- if .Values.ui.color }}
|
||||
- name: PODINFO_UI_COLOR
|
||||
value: {{ .Values.ui.color }}
|
||||
{{- end }}
|
||||
{{- if .Values.backend }}
|
||||
- name: PODINFO_BACKEND_URL
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "podinfo.fullname" . -}}
|
||||
{{- $servicePort := .Values.service.port -}}
|
||||
{{- $ingressPath := .Values.ingress.path -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
|
||||
88
charts/podinfo/templates/linkerd.yaml
Normal file
88
charts/podinfo/templates/linkerd.yaml
Normal file
@@ -0,0 +1,88 @@
|
||||
{{- if .Values.linkerd.profile.enabled -}}
|
||||
apiVersion: linkerd.io/v1alpha2
|
||||
kind: ServiceProfile
|
||||
metadata:
|
||||
name: {{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local
|
||||
spec:
|
||||
routes:
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /
|
||||
name: GET /
|
||||
- condition:
|
||||
method: POST
|
||||
pathRegex: /api/echo
|
||||
name: POST /api/echo
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /api/info
|
||||
name: GET /api/info
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /chunked/[^/]*
|
||||
name: GET /chunked/{seconds}
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /delay/[^/]*
|
||||
name: GET /delay/{seconds}
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /env
|
||||
name: GET /env
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /headers
|
||||
name: GET /headers
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /healthz
|
||||
name: GET /healthz
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /metrics
|
||||
name: GET /metrics
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /panic
|
||||
name: GET /panic
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /readyz
|
||||
name: GET /readyz
|
||||
- condition:
|
||||
method: POST
|
||||
pathRegex: /readyz/disable
|
||||
name: POST /readyz/disable
|
||||
- condition:
|
||||
method: POST
|
||||
pathRegex: /readyz/enable
|
||||
name: POST /readyz/enable
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /status/[^/]*
|
||||
name: GET /status/{code}
|
||||
- condition:
|
||||
method: POST
|
||||
pathRegex: /store
|
||||
name: POST /store
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /store/[^/]*
|
||||
name: GET /store/{hash}
|
||||
- condition:
|
||||
method: POST
|
||||
pathRegex: /token
|
||||
name: POST /token
|
||||
- condition:
|
||||
method: POST
|
||||
pathRegex: /token/validate
|
||||
name: POST /token/validate
|
||||
- condition:
|
||||
method: GET
|
||||
pathRegex: /version
|
||||
name: GET /version
|
||||
- condition:
|
||||
method: POST
|
||||
pathRegex: /ws/echo
|
||||
name: POST /ws/echo
|
||||
{{- end }}
|
||||
@@ -9,10 +9,13 @@ metadata:
|
||||
app: {{ template "podinfo.name" . }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
sidecar.istio.io/inject: "false"
|
||||
linkerd.io/inject: disabled
|
||||
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
|
||||
spec:
|
||||
containers:
|
||||
- name: grpc-health-probe
|
||||
image: stefanprodan/grpc_health_probe:v0.3.0
|
||||
command: ['grpc_health_probe']
|
||||
args: ['-addr={{ template "podinfo.fullname" . }}:{{ .Values.service.grpcPort }}']
|
||||
args: ['-addr={{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.grpcPort }}']
|
||||
restartPolicy: Never
|
||||
|
||||
@@ -9,6 +9,9 @@ metadata:
|
||||
app: {{ template "podinfo.name" . }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
sidecar.istio.io/inject: "false"
|
||||
linkerd.io/inject: disabled
|
||||
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
|
||||
spec:
|
||||
containers:
|
||||
- name: tools
|
||||
@@ -18,9 +21,9 @@ spec:
|
||||
- -c
|
||||
- |
|
||||
TOKEN=$(curl -sd 'test' ${PODINFO_SVC}/token | jq -r .token) &&
|
||||
curl -H "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test
|
||||
curl -sH "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test
|
||||
env:
|
||||
- name: PODINFO_SVC
|
||||
value: {{ template "podinfo.fullname" . }}:{{ .Values.service.externalPort }}
|
||||
value: {{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.externalPort }}
|
||||
restartPolicy: Never
|
||||
|
||||
|
||||
@@ -9,10 +9,19 @@ metadata:
|
||||
app: {{ template "podinfo.name" . }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
sidecar.istio.io/inject: "false"
|
||||
linkerd.io/inject: disabled
|
||||
appmesh.k8s.aws/sidecarInjectorWebhook: disabled
|
||||
spec:
|
||||
containers:
|
||||
- name: curl
|
||||
image: radial/busyboxplus:curl
|
||||
command: ['curl']
|
||||
args: ['{{ template "podinfo.fullname" . }}:{{ .Values.service.externalPort }}']
|
||||
image: giantswarm/tiny-tools
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
curl -s ${PODINFO_SVC}/api/info | grep version
|
||||
env:
|
||||
- name: PODINFO_SVC
|
||||
value: {{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.externalPort }}
|
||||
restartPolicy: Never
|
||||
|
||||
@@ -3,15 +3,23 @@
|
||||
replicaCount: 1
|
||||
logLevel: info
|
||||
backend: #http://backend-podinfo:9898/echo
|
||||
message: #UI greetings
|
||||
backends: []
|
||||
|
||||
ui:
|
||||
color: "cyan"
|
||||
message: ""
|
||||
logo: ""
|
||||
|
||||
faults:
|
||||
delay: false
|
||||
error: false
|
||||
|
||||
h2c:
|
||||
enabled: false
|
||||
|
||||
image:
|
||||
repository: stefanprodan/podinfo
|
||||
tag: 3.0.0
|
||||
tag: 3.1.4
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
@@ -42,6 +50,10 @@ serviceAccount:
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name:
|
||||
|
||||
linkerd:
|
||||
profile:
|
||||
enabled: false
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
|
||||
@@ -28,7 +28,7 @@ func main() {
|
||||
fs.Int("grpc-port", 0, "gRPC port")
|
||||
fs.String("grpc-service-name", "podinfo", "gPRC service name")
|
||||
fs.String("level", "info", "log level debug, info, warn, error, flat or panic")
|
||||
fs.String("backend-url", "", "backend service URL")
|
||||
fs.StringSlice("backend-url", []string{}, "backend service URL")
|
||||
fs.Duration("http-client-timeout", 2*time.Minute, "client timeout duration")
|
||||
fs.Duration("http-server-timeout", 30*time.Second, "server read and write timeout duration")
|
||||
fs.Duration("http-server-shutdown-timeout", 5*time.Second, "server graceful shutdown timeout duration")
|
||||
@@ -36,8 +36,10 @@ func main() {
|
||||
fs.String("config-path", "", "config dir path")
|
||||
fs.String("config", "config.yaml", "config file name")
|
||||
fs.String("ui-path", "./ui", "UI local path")
|
||||
fs.String("ui-color", "blue", "UI color")
|
||||
fs.String("ui-logo", "", "UI logo")
|
||||
fs.String("ui-color", "cyan", "UI color")
|
||||
fs.String("ui-message", fmt.Sprintf("greetings from podinfo v%v", version.VERSION), "UI message")
|
||||
fs.Bool("h2c", false, "Allow upgrading to H2C")
|
||||
fs.Bool("random-delay", false, "between 0 and 5 seconds random delay")
|
||||
fs.Bool("random-error", false, "1/3 chances of a random response error")
|
||||
fs.Int("stress-cpu", 0, "Number of CPU cores with 100 load")
|
||||
@@ -64,6 +66,7 @@ func main() {
|
||||
viper.RegisterAlias("backendUrl", "backend-url")
|
||||
hostname, _ := os.Hostname()
|
||||
viper.SetDefault("jwt-secret", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
|
||||
viper.SetDefault("ui-logo", "https://d33wubrfki0l68.cloudfront.net/33a12d8be0bc50be4738443101616e968c7afb8f/cba76/images/scalable.png")
|
||||
viper.Set("hostname", hostname)
|
||||
viper.Set("version", version.VERSION)
|
||||
viper.Set("revision", version.REVISION)
|
||||
|
||||
4
go.mod
4
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/stefanprodan/podinfo
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
@@ -47,6 +47,6 @@ require (
|
||||
go.uber.org/atomic v1.3.2 // indirect
|
||||
go.uber.org/multierr v1.1.0 // indirect
|
||||
go.uber.org/zap v1.9.1
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||
google.golang.org/grpc v1.23.0
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: podinfod
|
||||
image: stefanprodan/podinfo:3.0.0
|
||||
image: stefanprodan/podinfo:3.1.4
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
@@ -48,7 +48,7 @@ spec:
|
||||
- --random-error=false
|
||||
env:
|
||||
- name: PODINFO_UI_COLOR
|
||||
value: blue
|
||||
value: cyan
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
|
||||
109
pkg/api/echo.go
109
pkg/api/echo.go
@@ -3,8 +3,10 @@ package api
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/stefanprodan/podinfo/pkg/version"
|
||||
"go.uber.org/zap"
|
||||
@@ -26,59 +28,68 @@ func (s *Server) echoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if len(s.config.BackendURL) > 0 {
|
||||
backendReq, err := http.NewRequest("POST", s.config.BackendURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
s.logger.Error("backend call failed", zap.Error(err), zap.String("url", s.config.BackendURL))
|
||||
s.ErrorResponse(w, r, "backend call failed", http.StatusInternalServerError)
|
||||
return
|
||||
result := make([]string, len(s.config.BackendURL))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(s.config.BackendURL))
|
||||
for i, b := range s.config.BackendURL {
|
||||
go func(index int, backend string) {
|
||||
defer wg.Done()
|
||||
backendReq, err := http.NewRequest("POST", backend, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
s.logger.Error("backend call failed", zap.Error(err), zap.String("url", backend))
|
||||
return
|
||||
}
|
||||
|
||||
// forward headers
|
||||
copyTracingHeaders(r, backendReq)
|
||||
|
||||
backendReq.Header.Set("X-API-Version", version.VERSION)
|
||||
backendReq.Header.Set("X-API-Revision", version.REVISION)
|
||||
|
||||
ctx, cancel := context.WithTimeout(backendReq.Context(), s.config.HttpClientTimeout)
|
||||
defer cancel()
|
||||
|
||||
// call backend
|
||||
resp, err := http.DefaultClient.Do(backendReq.WithContext(ctx))
|
||||
if err != nil {
|
||||
s.logger.Error("backend call failed", zap.Error(err), zap.String("url", backend))
|
||||
result[index] = fmt.Sprintf("backend %v call failed %v", backend, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// copy error status from backend and exit
|
||||
if resp.StatusCode >= 400 {
|
||||
s.logger.Error("backend call failed", zap.Int("status", resp.StatusCode), zap.String("url", backend))
|
||||
result[index] = fmt.Sprintf("backend %v response status code %v", backend, resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
// forward the received body
|
||||
rbody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"reading the backend request body failed",
|
||||
zap.Error(err),
|
||||
zap.String("url", backend))
|
||||
result[index] = fmt.Sprintf("backend %v call failed %v", backend, err)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Debug(
|
||||
"payload received from backend",
|
||||
zap.String("response", string(rbody)),
|
||||
zap.String("url", backend))
|
||||
|
||||
result[index] = string(rbody)
|
||||
}(i, b)
|
||||
}
|
||||
|
||||
// forward headers
|
||||
copyTracingHeaders(r, backendReq)
|
||||
|
||||
backendReq.Header.Set("X-API-Version", version.VERSION)
|
||||
backendReq.Header.Set("X-API-Revision", version.REVISION)
|
||||
|
||||
ctx, cancel := context.WithTimeout(backendReq.Context(), s.config.HttpClientTimeout)
|
||||
defer cancel()
|
||||
|
||||
// call backend
|
||||
resp, err := http.DefaultClient.Do(backendReq.WithContext(ctx))
|
||||
if err != nil {
|
||||
s.logger.Error("backend call failed", zap.Error(err), zap.String("url", s.config.BackendURL))
|
||||
s.ErrorResponse(w, r, "backend call failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
// copy error status from backend and exit
|
||||
if resp.StatusCode >= 400 {
|
||||
s.ErrorResponse(w, r, "backend error", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
// forward the received body
|
||||
rbody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"reading the backend request body failed",
|
||||
zap.Error(err),
|
||||
zap.String("url", s.config.BackendURL))
|
||||
s.ErrorResponse(w, r, "backend call failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Debug(
|
||||
"payload received from backend",
|
||||
zap.String("response", string(rbody)),
|
||||
zap.String("url", s.config.BackendURL))
|
||||
wg.Wait()
|
||||
|
||||
w.Header().Set("X-Color", s.config.UIColor)
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
w.Write(rbody)
|
||||
s.JSONResponse(w, r, result)
|
||||
|
||||
} else {
|
||||
w.Header().Set("X-Color", s.config.UIColor)
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
|
||||
@@ -23,8 +23,10 @@ func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
data := struct {
|
||||
Title string
|
||||
Logo string
|
||||
}{
|
||||
Title: s.config.Hostname,
|
||||
Logo: s.config.UILogo,
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(w, data); err != nil {
|
||||
|
||||
@@ -22,6 +22,7 @@ func (s *Server) infoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Hostname: s.config.Hostname,
|
||||
Version: version.VERSION,
|
||||
Revision: version.REVISION,
|
||||
Logo: s.config.UILogo,
|
||||
Color: s.config.UIColor,
|
||||
Message: s.config.UIMessage,
|
||||
GOOS: runtime.GOOS,
|
||||
@@ -39,6 +40,7 @@ type RuntimeResponse struct {
|
||||
Version string `json:"version"`
|
||||
Revision string `json:"revision"`
|
||||
Color string `json:"color"`
|
||||
Logo string `json:"logo"`
|
||||
Message string `json:"message"`
|
||||
GOOS string `json:"goos"`
|
||||
GOARCH string `json:"goarch"`
|
||||
|
||||
@@ -12,7 +12,7 @@ func NewMockServer() *Server {
|
||||
Port: "9898",
|
||||
HttpServerShutdownTimeout: 5 * time.Second,
|
||||
HttpServerTimeout: 30 * time.Second,
|
||||
BackendURL: "",
|
||||
BackendURL: []string{},
|
||||
ConfigPath: "/config",
|
||||
DataPath: "/data",
|
||||
HttpClientTimeout: 30 * time.Second,
|
||||
|
||||
@@ -4,6 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/swaggo/swag"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
@@ -40,11 +43,17 @@ var (
|
||||
watcher *fscache.Watcher
|
||||
)
|
||||
|
||||
type FluxConfig struct {
|
||||
GitUrl string `mapstructure:"git-url"`
|
||||
GitBranch string `mapstructure:"git-branch"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
HttpClientTimeout time.Duration `mapstructure:"http-client-timeout"`
|
||||
HttpServerTimeout time.Duration `mapstructure:"http-server-timeout"`
|
||||
HttpServerShutdownTimeout time.Duration `mapstructure:"http-server-shutdown-timeout"`
|
||||
BackendURL string `mapstructure:"backend-url"`
|
||||
BackendURL []string `mapstructure:"backend-url"`
|
||||
UILogo string `mapstructure:"ui-logo"`
|
||||
UIMessage string `mapstructure:"ui-message"`
|
||||
UIColor string `mapstructure:"ui-color"`
|
||||
UIPath string `mapstructure:"ui-path"`
|
||||
@@ -53,6 +62,7 @@ type Config struct {
|
||||
Port string `mapstructure:"port"`
|
||||
PortMetrics int `mapstructure:"port-metrics"`
|
||||
Hostname string `mapstructure:"hostname"`
|
||||
H2C bool `mapstructure:"h2c"`
|
||||
RandomDelay bool `mapstructure:"random-delay"`
|
||||
RandomError bool `mapstructure:"random-error"`
|
||||
JWTSecret string `mapstructure:"jwt-secret"`
|
||||
@@ -135,12 +145,19 @@ func (s *Server) ListenAndServe(stopCh <-chan struct{}) {
|
||||
s.registerHandlers()
|
||||
s.registerMiddlewares()
|
||||
|
||||
var handler http.Handler
|
||||
if s.config.H2C {
|
||||
handler = h2c.NewHandler(s.router, &http2.Server{})
|
||||
} else {
|
||||
handler = s.router
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":" + s.config.Port,
|
||||
WriteTimeout: s.config.HttpServerTimeout,
|
||||
ReadTimeout: s.config.HttpServerTimeout,
|
||||
IdleTimeout: 2 * s.config.HttpServerTimeout,
|
||||
Handler: s.router,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
//s.printRoutes()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package version
|
||||
|
||||
var VERSION = "3.0.0"
|
||||
var VERSION = "3.1.4"
|
||||
var REVISION = "unknown"
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
<v-app dark>
|
||||
<v-content>
|
||||
<section>
|
||||
<v-parallax id="parallax-hero" src="#" :class="info.color">
|
||||
<v-parallax id="parallax-hero" src="https://upload.wikimedia.org/wikipedia/commons/c/ca/1x1.png" :class="info.color">
|
||||
<v-layout
|
||||
column
|
||||
align-center
|
||||
justify-center
|
||||
class="white--text"
|
||||
>
|
||||
<img src="https://d33wubrfki0l68.cloudfront.net/33a12d8be0bc50be4738443101616e968c7afb8f/cba76/images/scalable.png" alt="kubernetes" height="200">
|
||||
<img :src="info.logo" alt="kubernetes" height="200">
|
||||
<h1 class="white--text mb-2 display-1 text-xs-center">${ info.message }</h1>
|
||||
<div class="subheading mb-3 text-xs-center">Served by <b>${ info.hostname }</b></div>
|
||||
<v-btn
|
||||
@@ -108,7 +108,8 @@
|
||||
}
|
||||
}
|
||||
self.info = data
|
||||
self.info.color = 'cyan'
|
||||
self.info.color = data.color
|
||||
self.info.logo = data.logo
|
||||
document.title = data.hostname
|
||||
let verColor = (parseInt(data.version.split('.').reverse()[0], 10) % 2 === 0)
|
||||
? 'blue'
|
||||
|
||||
Reference in New Issue
Block a user