Compare commits

...

33 Commits
2.1.0 ... 3.1.1

Author SHA1 Message Date
Stefan Prodan
087da02dbb Merge pull request #37 from stefanprodan/chart-fixes
Fix Helm tests when running inside a service mesh
2019-09-27 16:18:46 +03:00
stefanprodan
7d00f68180 Bump version to 3.1.1 2019-09-27 16:10:22 +03:00
stefanprodan
87c9bb8ba2 Exclude Helm test pods for service mesh 2019-09-27 16:09:24 +03:00
Stefan Prodan
5fb970b526 Merge pull request #36 from stefanprodan/backends
Add support for multiple backends
2019-09-27 12:16:28 +03:00
stefanprodan
56b404bd84 Release v3.1.0 2019-09-27 12:10:29 +03:00
stefanprodan
a12d0a1ed7 Add support for multiple backends
When calling /echo, the backends requests will be run in parallel and the results are aggregated and returned to the caller as a json array
2019-09-27 11:52:22 +03:00
Stefan Prodan
51979787b0 Fix Helm repo address 2019-09-26 12:29:49 +03:00
Stefan Prodan
8b37756118 Merge pull request #35 from eladb/patch-1
remove duplicate "ingress" entries in readme
2019-09-14 09:20:06 +02:00
Elad Ben-Israel
1eb1da110b remove duplicate "ingress" entries in readme 2019-09-11 14:10:06 +03:00
stefanprodan
d1ed907f1e Make tests work with Helm v3 2019-09-05 20:53:20 +03:00
stefanprodan
8e6eccecda Fix chart for old Helm versions 2019-09-05 16:01:34 +03:00
Stefan Prodan
f3db1adb27 Merge pull request #34 from stefanprodan/ingress-fix
Allow ingress with no hosts set
2019-09-05 14:48:49 +03:00
stefanprodan
7f3e11c1ce Allow ingress with no hosts set 2019-09-05 14:42:36 +03:00
Stefan Prodan
a7eb7e4995 Merge pull request #33 from stefanprodan/prep-3.0.0
Release v3.0.0
2019-09-05 12:23:02 +03:00
stefanprodan
43194bb342 Release v3.0.0 2019-09-05 12:14:18 +03:00
Stefan Prodan
c7d21968e7 Merge pull request #32 from stefanprodan/gprc-health
Implement gRPC health endpoint
2019-09-05 11:43:13 +03:00
stefanprodan
214a19fb0f Add gRPC service name flag to check command 2019-09-05 09:41:02 +03:00
stefanprodan
82ea2fa993 Update Kubernetes Kind to v0.5.1 2019-09-05 09:29:51 +03:00
stefanprodan
d84913c31e Add gPRC Helm test 2019-09-05 09:22:49 +03:00
stefanprodan
6bac5ffaa2 Add gPRC port and service name to chart 2019-09-05 01:08:26 +03:00
stefanprodan
eacf909c4a Add gPRC health check to CLI 2019-09-05 00:29:09 +03:00
stefanprodan
f7c1669125 Run gPRC health server if grpc-port flag is set 2019-09-05 00:28:32 +03:00
stefanprodan
158d6e82da Add gRPC health server 2019-09-05 00:20:28 +03:00
Stefan Prodan
4d890382e5 Merge pull request #30 from stefanprodan/service-account
Add service account to Helm chart
2019-08-13 12:30:04 +03:00
stefanprodan
83842e01f7 Rename service account create to enabled 2019-08-13 12:12:52 +03:00
stefanprodan
37b453fbbc Release v2.1.3 2019-08-13 12:08:50 +03:00
stefanprodan
53c6b472de Fix ClusterIP creation 2019-08-13 12:02:32 +03:00
stefanprodan
c759f958c0 e2e: print logs after tests finished 2019-08-13 11:53:28 +03:00
stefanprodan
5d14183809 Add service account to Helm chart 2019-08-13 11:37:10 +03:00
stefanprodan
ab74d6ef0b Release v2.1.2
Make the ClusterIP service optional in helm chart (should be disabled when using Flagger)
2019-08-13 10:50:03 +03:00
stefanprodan
fefcae34c1 Rename test config 2019-08-09 18:00:41 +03:00
stefanprodan
ed81a06a82 Release v2.1.1
Use Docker Hub instead of Quay
2019-08-09 17:53:15 +03:00
stefanprodan
633982b0e5 Use Docker Hub repo 2019-08-09 17:21:19 +03:00
27 changed files with 410 additions and 129 deletions

View File

@@ -10,7 +10,7 @@ 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
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
test:
GO111MODULE=on go test -v -race ./...

View File

@@ -44,6 +44,10 @@ Web API:
* `GET /chunked/{seconds}` uses `transfer-encoding` type `chunked` to give a partial response and then waits for the specified period
* `GET /swagger.json` returns the API Swagger docs, used for Linkerd service profiling and Gloo routes discovery
gRPC API:
* `/grpc.health.v1.Health/Check` health checking
Web UI:
![podinfo-ui](https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/screens/podinfo-ui.png)

View File

@@ -1,6 +1,6 @@
apiVersion: v1
version: 2.1.0
appVersion: 2.1.0
version: 3.1.1
appVersion: 3.1.1
name: podinfo
engine: gotpl
description: Podinfo Helm chart for Kubernetes

View File

@@ -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
```
@@ -32,8 +32,8 @@ The following tables lists the configurable parameters of the podinfo chart and
Parameter | Description | Default
--- | --- | ---
`affinity` | node/pod affinities | None
`color` | UI color | blue
`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`
@@ -41,11 +41,9 @@ 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 | `0.0.1`
`image.tag` | image tag | `<VERSION>`
`ingress.enabled` | enables ingress | `false`
`ingress.annotations` | ingress annotations | None
`ingress.hosts` | ingress accepted hostnames | None
@@ -57,11 +55,17 @@ Parameter | Description | Default
`resources.requests/memory` | pod memory request | `16Mi`
`resources.limits/cpu` | pod CPU limit | None
`resources.limits/memory` | pod memory limit | None
`service.externalPort` | external port for the service | `9898`
`service.internalPort` | internal port for the service | `9898`
`service.nodePort` | node port for the service | `31198`
`service.enabled` | create Kubernetes service (should be disabled when using Flagger) | `true`
`service.metricsPort` | Prometheus metrics endpoint port | `9797`
`service.externalPort` | ClusterIP HTTP port | `9898`
`service.httpPort` | container HTTP port | `9898`
`service.nodePort` | NodePort for the HTTP endpoint | `31198`
`service.grpcPort` | ClusterIP gPRC port | `9999`
`service.grpcService` | gPRC service name | `podinfo`
`service.type` | type of service | `ClusterIP`
`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
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,

View File

@@ -30,3 +30,14 @@ Create chart name and version as used by the chart label.
{{- define "podinfo.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "podinfo.serviceAccountName" -}}
{{- if .Values.serviceAccount.enabled -}}
{{ default (include "podinfo.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

View File

@@ -8,7 +8,9 @@ metadata:
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
{{- if not .Values.hpa.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
strategy:
type: RollingUpdate
rollingUpdate:
@@ -23,22 +25,36 @@ spec:
app: {{ template "podinfo.name" . }}
release: {{ .Release.Name }}
annotations:
prometheus.io/scrape: 'true'
prometheus.io/scrape: "true"
prometheus.io/port: "{{ .Values.service.httpPort }}"
spec:
terminationGracePeriodSeconds: 30
{{- if .Values.serviceAccount.enabled }}
serviceAccountName: {{ template "podinfo.serviceAccountName" . }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- ./podinfo
- --port={{ .Values.service.containerPort }}
- --port={{ .Values.service.httpPort | default 9898 }}
{{- if .Values.service.metricsPort }}
- --port-metrics={{ .Values.service.metricsPort }}
{{- end }}
{{- if .Values.service.grpcPort }}
- --grpc-port={{ .Values.service.grpcPort }}
{{- end }}
{{- 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 }}
env:
- name: PODINFO_UI_COLOR
value: {{ .Values.color }}
{{- if .Values.message }}
- name: PODINFO_UI_MESSAGE
value: {{ .Values.message }}
@@ -49,15 +65,25 @@ spec:
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.containerPort }}
containerPort: {{ .Values.service.httpPort | default 9898 }}
protocol: TCP
{{- if .Values.service.metricsPort }}
- name: http-metrics
containerPort: {{ .Values.service.metricsPort }}
protocol: TCP
{{- end }}
{{- if .Values.service.grpcPort }}
- name: grpc
containerPort: {{ .Values.service.grpcPort }}
protocol: TCP
{{- end }}
livenessProbe:
exec:
command:
- podcli
- check
- http
- localhost:{{ .Values.service.containerPort }}/healthz
- localhost:{{ .Values.service.httpPort | default 9898 }}/healthz
initialDelaySeconds: 1
timeoutSeconds: 5
readinessProbe:
@@ -66,7 +92,7 @@ spec:
- podcli
- check
- http
- localhost:{{ .Values.service.containerPort }}/readyz
- localhost:{{ .Values.service.httpPort | default 9898 }}/readyz
initialDelaySeconds: 1
timeoutSeconds: 5
volumeMounts:

View File

@@ -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
@@ -36,4 +35,12 @@ spec:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- if not .Values.ingress.hosts }}
- http:
paths:
- path: {{ $ingressPath }}
backend:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- end }}

View File

@@ -1,3 +1,4 @@
{{- if .Values.service.enabled -}}
apiVersion: v1
kind: Service
metadata:
@@ -17,6 +18,13 @@ spec:
{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
{{- if .Values.service.grpcPort }}
- port: {{ .Values.service.grpcPort }}
targetPort: grpc
protocol: TCP
name: grpc
{{- end }}
selector:
app: {{ template "podinfo.name" . }}
release: {{ .Release.Name }}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{- if .Values.serviceAccount.enabled -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "podinfo.serviceAccountName" . }}
labels:
app: {{ template "podinfo.name" . }}
chart: {{ template "podinfo.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
{{- end -}}

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Pod
metadata:
name: {{ template "podinfo.fullname" . }}-grpc-test-{{ randAlphaNum 5 | lower }}
labels:
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
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 }}']
restartPolicy: Never

View File

@@ -9,35 +9,21 @@ 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
image: giantswarm/tiny-tools
command: ["/bin/sh", "/scripts/test.sh"]
command:
- sh
- -c
- |
TOKEN=$(curl -sd 'test' ${PODINFO_SVC}/token | jq -r .token) &&
curl -H "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test
env:
- name: PODINFO_SVC
value: {{ template "podinfo.fullname" . }}:{{ .Values.service.externalPort }}
volumeMounts:
- name: scripts
mountPath: /scripts
restartPolicy: Never
volumes:
- name: scripts
configMap:
name: {{ template "podinfo.fullname" . }}-storage-cfg
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "podinfo.fullname" . }}-storage-cfg
labels:
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "podinfo.name" . }}
data:
test.sh: |
#!/bin/sh
echo "testing ${PODINFO_SVC}/token"
TOKEN=$(curl -sd 'test' ${PODINFO_SVC}/token | jq -r .token) && \
curl -H "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test

View File

@@ -9,10 +9,20 @@ 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
- |
TOKEN=$(curl -sd 'test' ${PODINFO_SVC}/token | jq -r .token) &&
curl -H "Authorization: Bearer ${TOKEN}" ${PODINFO_SVC}/token/validate | grep test
env:
- name: PODINFO_SVC
value: {{ template "podinfo.fullname" . }}:{{ .Values.service.externalPort }}
restartPolicy: Never

View File

@@ -2,8 +2,8 @@
replicaCount: 1
logLevel: info
color: blue
backend: #http://backend-podinfo:9898/echo
backends: []
message: #UI greetings
faults:
@@ -11,14 +11,18 @@ faults:
error: false
image:
repository: quay.io/stefanprodan/podinfo
tag: 2.1.0
repository: stefanprodan/podinfo
tag: 3.1.1
pullPolicy: IfNotPresent
service:
enabled: true
type: ClusterIP
metricsPort: 9797
httpPort: 9898
externalPort: 9898
containerPort: 9898
grpcPort: 9999
grpcService: podinfo
nodePort: 31198
# metrics-server add-on required
@@ -32,14 +36,21 @@ hpa:
# average http requests per second per pod (k8s-prometheus-adapter)
requests:
serviceAccount:
# Specifies whether a service account should be created
enabled: false
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name:
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- podinfo.local
path: /*
hosts: []
# - podinfo.local
tls: []
# - secretName: chart-example-tls
# hosts:

View File

@@ -14,14 +14,19 @@ import (
"github.com/spf13/cobra"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
)
var (
retryCount int
retryDelay time.Duration
method string
body string
timeout time.Duration
retryCount int
retryDelay time.Duration
method string
body string
timeout time.Duration
grpcServiceName string
)
var checkCmd = &cobra.Command{
@@ -51,6 +56,13 @@ var checkCertCmd = &cobra.Command{
RunE: runCheckCert,
}
var checkgRPCCmd = &cobra.Command{
Use: `grpc [address]`,
Short: "gRPC health check",
Example: ` check grpc localhost:8080 --service=podinfo --retry=1 --delay=2s --timeout=2s`,
RunE: runCheckgPRC,
}
func init() {
checkUrlCmd.Flags().StringVar(&method, "method", "GET", "HTTP method")
checkUrlCmd.Flags().StringVar(&body, "body", "", "HTTP POST/PUT content")
@@ -64,6 +76,12 @@ func init() {
checkTcpCmd.Flags().DurationVar(&timeout, "timeout", 5*time.Second, "timeout")
checkCmd.AddCommand(checkTcpCmd)
checkgRPCCmd.Flags().IntVar(&retryCount, "retry", 0, "times to retry the TCP check")
checkgRPCCmd.Flags().DurationVar(&retryDelay, "delay", 1*time.Second, "wait duration between retries")
checkgRPCCmd.Flags().DurationVar(&timeout, "timeout", 5*time.Second, "timeout")
checkgRPCCmd.Flags().StringVar(&grpcServiceName, "service", "", "gRPC service name")
checkCmd.AddCommand(checkgRPCCmd)
checkCmd.AddCommand(checkCertCmd)
rootCmd.AddCommand(checkCmd)
@@ -243,3 +261,53 @@ func fmtContentLength(b int64) string {
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}
func runCheckgPRC(cmd *cobra.Command, args []string) error {
if retryCount < 0 {
return fmt.Errorf("--retry is required")
}
if len(args) < 1 {
return fmt.Errorf("address is required! example: check grpc localhost:8080")
}
address := args[0]
for n := 0; n <= retryCount; n++ {
if n != 1 {
time.Sleep(retryDelay)
}
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
logger.Info("check failed",
zap.String("address", address),
zap.Error(err))
continue
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
resp, err := grpc_health_v1.NewHealthClient(conn).Check(ctx, &grpc_health_v1.HealthCheckRequest{
Service: grpcServiceName,
})
cancel()
if err != nil {
if stat, ok := status.FromError(err); ok && stat.Code() == codes.Unimplemented {
logger.Info("gPRC health protocol not implemented")
os.Exit(1)
} else {
logger.Info("check failed",
zap.String("address", address),
zap.Error(err))
}
continue
}
conn.Close()
logger.Info("check succeed",
zap.String("status", resp.GetStatus().String()))
os.Exit(0)
}
os.Exit(1)
return nil
}

View File

@@ -2,28 +2,33 @@ package main
import (
"fmt"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stefanprodan/podinfo/pkg/api"
"github.com/stefanprodan/podinfo/pkg/signals"
"github.com/stefanprodan/podinfo/pkg/version"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/stefanprodan/podinfo/pkg/api"
"github.com/stefanprodan/podinfo/pkg/grpc"
"github.com/stefanprodan/podinfo/pkg/signals"
"github.com/stefanprodan/podinfo/pkg/version"
)
func main() {
// flags definition
fs := pflag.NewFlagSet("default", pflag.ContinueOnError)
fs.Int("port", 9898, "port")
fs.Int("port", 9898, "HTTP port")
fs.Int("port-metrics", 0, "metrics port")
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")
@@ -90,6 +95,18 @@ func main() {
viper.Set("port", strconv.Itoa(port))
}
// load gRPC server config
var grpcCfg grpc.Config
if err := viper.Unmarshal(&grpcCfg); err != nil {
logger.Panic("config unmarshal failed", zap.Error(err))
}
// start gRPC server
if grpcCfg.Port > 0 {
grpcSrv, _ := grpc.NewServer(&grpcCfg, logger)
go grpcSrv.ListenAndServe()
}
// load HTTP server config
var srvCfg api.Config
if err := viper.Unmarshal(&srvCfg); err != nil {

View File

@@ -3,7 +3,7 @@
set -o errexit
REPO_ROOT=$(git rev-parse --show-toplevel)
KIND_VERSION=v0.4.0
KIND_VERSION=v0.5.1
if [[ "$1" ]]; then
KIND_VERSION=$1

View File

@@ -5,8 +5,12 @@ set -o errexit
REPO_ROOT=$(git rev-parse --show-toplevel)
export KUBECONFIG="$(kind get kubeconfig-path --name="kind")"
function finish {
echo '>>> Test logs'
kubectl logs -l app=podinfo
}
trap finish EXIT
echo '>>> Testing'
helm test podinfo
echo '>>> Test logs'
kubectl logs -l app=podinfo

4
go.mod
View File

@@ -3,7 +3,6 @@ module github.com/stefanprodan/podinfo
go 1.12
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/aws/aws-sdk-go v1.15.63 // indirect
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
@@ -14,7 +13,7 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fatih/color v1.7.0
github.com/fsnotify/fsnotify v1.4.7
github.com/golang/protobuf v1.2.0 // indirect
github.com/go-chi/chi v4.0.2+incompatible // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.0
github.com/hashicorp/go-cleanhttp v0.5.0 // indirect
@@ -49,4 +48,5 @@ require (
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
google.golang.org/grpc v1.23.0
)

18
go.sum
View File

@@ -1,3 +1,4 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@@ -20,6 +21,7 @@ github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375 h1:JVe1zduaiPlSLOu
github.com/chzyer/readline v0.0.0-20160726135117-62c6fe619375/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -31,6 +33,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
@@ -40,8 +44,12 @@ github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
@@ -123,19 +131,29 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468 h1:fTfk6GjmihJbK0mSUFgPPgYpsdmApQ86Mcd4GuKax9U=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -5,7 +5,7 @@ metadata:
labels:
app: podinfo
spec:
minReadySeconds: 5
minReadySeconds: 3
revisionHistoryLimit: 5
progressDeadlineSeconds: 60
strategy:
@@ -19,20 +19,30 @@ spec:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9797"
labels:
app: podinfo
spec:
containers:
- name: podinfod
image: quay.io/stefanprodan/podinfo:2.1.0
image: stefanprodan/podinfo:3.1.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9898
name: http
- name: http
containerPort: 9898
protocol: TCP
- name: http-metrics
containerPort: 9797
protocol: TCP
- name: grpc
containerPort: 9999
protocol: TCP
command:
- ./podinfo
- --port=9898
- --port-metrics=9797
- --grpc-port=9999
- --grpc-service-name=podinfo
- --level=info
- --random-delay=false
- --random-error=false

View File

@@ -2,4 +2,3 @@ resources:
- hpa.yaml
- deployment.yaml
- service.yaml

View File

@@ -13,3 +13,7 @@ spec:
port: 9898
protocol: TCP
targetPort: http
- port: 9999
targetPort: grpc
protocol: TCP
name: grpc

View File

@@ -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)

View File

@@ -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,

View File

@@ -40,11 +40,16 @@ 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"`
UIMessage string `mapstructure:"ui-message"`
UIColor string `mapstructure:"ui-color"`
UIPath string `mapstructure:"ui-path"`

46
pkg/grpc/server.go Normal file
View File

@@ -0,0 +1,46 @@
package grpc
import (
"fmt"
"net"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
)
type Server struct {
logger *zap.Logger
config *Config
}
type Config struct {
Port int `mapstructure:"grpc-port"`
ServiceName string `mapstructure:"grpc-service-name"`
}
func NewServer(config *Config, logger *zap.Logger) (*Server, error) {
srv := &Server{
logger: logger,
config: config,
}
return srv, nil
}
func (s *Server) ListenAndServe() {
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", s.config.Port))
if err != nil {
s.logger.Fatal("failed to listen", zap.Int("port", s.config.Port))
}
srv := grpc.NewServer()
server := health.NewServer()
grpc_health_v1.RegisterHealthServer(srv, server)
server.SetServingStatus(s.config.ServiceName, grpc_health_v1.HealthCheckResponse_SERVING)
if err := srv.Serve(listener); err != nil {
s.logger.Fatal("failed to serve", zap.Error(err))
}
}

View File

@@ -1,4 +1,4 @@
package version
var VERSION = "2.1.0"
var VERSION = "3.1.1"
var REVISION = "unknown"