Compare commits

..

22 Commits
3.0.0 ... 3.1.3

Author SHA1 Message Date
Stefan Prodan
3d6d0bed69 Merge pull request #40 from stefanprodan/linkerd-profile
Add Linkerd service profile to Helm chart
2019-10-17 13:56:39 +03:00
stefanprodan
b213e0af0a Release v3.1.3 2019-10-17 13:50:48 +03:00
stefanprodan
42ad3faf5a Add Linkerd service profile to chart 2019-10-17 13:47:44 +03:00
stefanprodan
939fd5b24d Add Go report card 2019-10-17 13:35:54 +03:00
stefanprodan
36ec3ef378 Fix UI parallax img 2019-10-13 09:52:33 +03:00
Stefan Prodan
287e005129 Merge pull request #39 from stefanprodan/ui-logo
Make UI logo URL configurable
2019-10-12 18:10:40 +03:00
stefanprodan
0b3e88d6de Add release namespace to Helm tests 2019-10-12 18:00:14 +03:00
stefanprodan
10139749da Turn off CircleCI docker_layer_caching 2019-10-12 17:50:12 +03:00
stefanprodan
f891e0683b Release v3.1.2 2019-10-12 17:45:54 +03:00
stefanprodan
647b4cba04 Add UI settings to Helm chart 2019-10-12 17:44:37 +03:00
stefanprodan
c5df50c774 Make UI logo URL configurable 2019-10-12 17:41:21 +03:00
Stefan Prodan
2b1d325343 Merge pull request #38 from stefanprodan/go1.13
Update to go 1.13
2019-09-27 17:43:52 +03:00
stefanprodan
319d57cb68 Update to go 1.13 2019-09-27 17:04:39 +03:00
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
23 changed files with 234 additions and 85 deletions

View File

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

View File

@@ -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 ./...

View File

@@ -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 ./...

View File

@@ -1,10 +1,10 @@
# podinfo
[![CircleCI](https://circleci.com/gh/stefanprodan/podinfo.svg?style=svg)](https://circleci.com/gh/stefanprodan/podinfo)
[![Go Report Card](https://goreportcard.com/badge/github.com/stefanprodan/podinfo)](https://goreportcard.com/report/github.com/stefanprodan/podinfo)
[![Docker Pulls](https://img.shields.io/docker/pulls/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,6 +13,7 @@ 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)

View File

@@ -1,6 +1,6 @@
apiVersion: v1
version: 3.0.0
appVersion: 3.0.0
version: 3.1.3
appVersion: 3.1.3
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
```
@@ -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,

View File

@@ -48,13 +48,24 @@ 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 }}
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

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

View 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 }}

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,12 @@
replicaCount: 1
logLevel: info
backend: #http://backend-podinfo:9898/echo
message: #UI greetings
backends: []
ui:
color: "cyan"
message: ""
logo: ""
faults:
delay: false
@@ -11,7 +16,7 @@ faults:
image:
repository: stefanprodan/podinfo
tag: 3.0.0
tag: 3.1.3
pullPolicy: IfNotPresent
service:
@@ -42,6 +47,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: {}

View File

@@ -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,7 +36,8 @@ 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("random-delay", false, "between 0 and 5 seconds random delay")
fs.Bool("random-error", false, "1/3 chances of a random response error")
@@ -64,6 +65,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)

2
go.mod
View File

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

View File

@@ -25,7 +25,7 @@ spec:
spec:
containers:
- name: podinfod
image: stefanprodan/podinfo:3.0.0
image: stefanprodan/podinfo:3.1.3
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:

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

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

View File

@@ -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"`

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,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"`

View File

@@ -1,4 +1,4 @@
package version
var VERSION = "3.0.0"
var VERSION = "3.1.3"
var REVISION = "unknown"

View File

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