Compare commits

...

28 Commits

Author SHA1 Message Date
Stefan Prodan
b501abd1f0 Merge pull request #470 from stefanprodan/release-6.11.2
Release 6.11.2
2026-03-31 22:52:14 +03:00
Stefan Prodan
e0a79a4ddd Release 6.11.2
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-31 22:47:19 +03:00
Stefan Prodan
be8baac695 Merge pull request #468 from stefanprodan/dependabot/github_actions/actions-6b017b3799
build(deps): bump the actions group across 1 directory with 4 updates
2026-03-31 22:36:36 +03:00
dependabot[bot]
f539517440 build(deps): bump the actions group across 1 directory with 4 updates
Bumps the actions group with 4 updates in the / directory: [azure/setup-helm](https://github.com/azure/setup-helm), [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer), [fluxcd/flux2](https://github.com/fluxcd/flux2) and [azure/setup-kubectl](https://github.com/azure/setup-kubectl).


Updates `azure/setup-helm` from 4 to 5
- [Release notes](https://github.com/azure/setup-helm/releases)
- [Changelog](https://github.com/Azure/setup-helm/blob/main/CHANGELOG.md)
- [Commits](https://github.com/azure/setup-helm/compare/v4...v5)

Updates `sigstore/cosign-installer` from 4.0.0 to 4.1.1
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v4.0.0...v4.1.1)

Updates `fluxcd/flux2` from 2.8.1 to 2.8.3
- [Release notes](https://github.com/fluxcd/flux2/releases)
- [Commits](https://github.com/fluxcd/flux2/compare/v2.8.1...v2.8.3)

Updates `azure/setup-kubectl` from 4 to 5
- [Release notes](https://github.com/azure/setup-kubectl/releases)
- [Changelog](https://github.com/Azure/setup-kubectl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/azure/setup-kubectl/compare/v4...v5)

---
updated-dependencies:
- dependency-name: azure/setup-helm
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: fluxcd/flux2
  dependency-version: 2.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: azure/setup-kubectl
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 19:32:43 +00:00
Stefan Prodan
01219a196e Merge pull request #469 from stefanprodan/pin-actions
ci: Pin actions and enable release attentions
2026-03-31 22:30:33 +03:00
Stefan Prodan
b9acae4064 ci: Pin actions and enable release attentions
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-31 22:27:07 +03:00
Stefan Prodan
64a8da1836 Merge pull request #467 from stefanprodan/dependabot/go_modules/google.golang.org/grpc-1.79.3
build(deps): bump google.golang.org/grpc from 1.79.1 to 1.79.3
2026-03-31 19:08:11 +03:00
dependabot[bot]
420d0db8bf build(deps): bump google.golang.org/grpc from 1.79.1 to 1.79.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.1 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.1...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 02:41:57 +00:00
Stefan Prodan
6b67f2bdd6 Merge pull request #454 from hansbogert/master
feat: add otlp logging support
2026-03-17 09:22:56 +02:00
Hans van den Bogert
095b1cd251 feat: add otlp logging support
- Adds a loggerprovider based on otlp logger
- In demo directory of oltp:
  - Added grafana for unified view of both traces and logs
  - tracing now uses oltp from the collector to the jaeger instance

Signed-off-by: Hans van den Bogert <hansbogert@gmail.com>
2026-03-14 22:38:14 +01:00
Stefan Prodan
0a27dbe40c Merge pull request #465 from stefanprodan/release-6.11.1
Release 6.11.1
2026-03-14 15:27:35 +02:00
Stefan Prodan
2da74a4ec2 Release 6.11.1
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-14 15:18:19 +02:00
Stefan Prodan
c7ffdba3bd Merge pull request #461 from stefanprodan/dependabot/github_actions/actions-1590fac0fc
build(deps): bump the actions group with 5 updates
2026-03-14 15:10:39 +02:00
Stefan Prodan
06f7cd3777 Merge pull request #464 from stefanprodan/fix-store-path-traversal
Fix path traversal in `/store` endpoint
2026-03-14 15:08:52 +02:00
Stefan Prodan
620b9b7e2c Fix path traversal in /store endpoint
Validate that the hash URL parameter matches the expected SHA1 hex
format (40 lowercase hex characters) before using it in file path
operations.

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-14 15:02:25 +02:00
Stefan Prodan
83deb7fcb7 Merge pull request #463 from stefanprodan/fix-CVE-2025-70849
Fix XSS in `/store` endpoint (CVE-2025-70849)
2026-03-14 14:58:53 +02:00
Stefan Prodan
550ee9f7b9 Fix stored XSS in /store endpoint (CVE-2025-70849)
Set Content-Type to application/octet-stream in storeReadHandler
to prevent Go's content sniffing from serving HTML payloads as
text/html. Add X-Content-Type-Options: nosniff to prevent browsers
from overriding Content-Type via MIME sniffing, and
Content-Security-Policy: default-src 'none' to block script
execution as defense-in-depth.

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-14 14:40:55 +02:00
dependabot[bot]
dd185df435 build(deps): bump the actions group with 5 updates
Bumps the actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `3` | `4` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `3` | `4` |
| [docker/login-action](https://github.com/docker/login-action) | `3` | `4` |
| [docker/metadata-action](https://github.com/docker/metadata-action) | `5` | `6` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `6` | `7` |


Updates `docker/setup-qemu-action` from 3 to 4
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

Updates `docker/setup-buildx-action` from 3 to 4
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

Updates `docker/login-action` from 3 to 4
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

Updates `docker/metadata-action` from 5 to 6
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

Updates `docker/build-push-action` from 6 to 7
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 06:46:46 +00:00
Stefan Prodan
07a524ba01 Merge pull request #460 from stefanprodan/release-6.11.0
Release 6.11.0
2026-03-06 19:50:57 +00:00
Stefan Prodan
5d97df9c89 Release 6.11.0
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-06 21:43:50 +02:00
Stefan Prodan
a8cadef09b Merge pull request #459 from stefanprodan/cosign-v3
Sign release artifacts with cosign v3
2026-03-06 19:32:20 +00:00
Stefan Prodan
32f6e3d8c9 Sign release artifacts with cosign v3
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
2026-03-06 21:31:06 +02:00
Stefan Prodan
77dc46241d Merge pull request #458 from matheuscscp/grpcroute
Introduce GRPCRoute in the Helm chart
2026-03-06 19:23:43 +00:00
Matheus Pimenta
3a31e973c0 Introduce GRPCRoute in the Helm chart
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-03-06 03:44:28 +00:00
Stefan Prodan
e15511a92d Merge pull request #456 from matheuscscp/check-grpc-tls
Introduce `--tls` flag for command `check grpc`
2026-03-03 08:36:06 +02:00
Matheus Pimenta
4656ca0517 Introduce --tls flag for command check grpc
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-03-03 03:02:20 +00:00
Stefan Prodan
1f66430364 Merge pull request #455 from matheuscscp/ws-check
Introduce podcli check ws command
2026-03-02 20:46:52 +02:00
Matheus Pimenta
117533e329 Introduce podcli check ws command
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
2026-03-02 17:38:35 +00:00
31 changed files with 444 additions and 85 deletions

View File

@@ -16,10 +16,10 @@ jobs:
govulncheck: govulncheck:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/runner-cleanup - uses: ./.github/actions/runner-cleanup
- name: Vulnerability scan - name: Vulnerability scan
id: govulncheck id: govulncheck
uses: golang/govulncheck-action@v1 uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
with: with:
repo-checkout: false repo-checkout: false

View File

@@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Disk Cleanup - name: Disk Cleanup
uses: ./.github/actions/runner-cleanup uses: ./.github/actions/runner-cleanup
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@v1.14.0 uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with: with:
cluster_name: kind cluster_name: kind
- name: Build container image - name: Build container image
@@ -26,7 +26,7 @@ jobs:
./test/build.sh ./test/build.sh
kind load docker-image test/podinfo:latest kind load docker-image test/podinfo:latest
- name: Setup Helm - name: Setup Helm
uses: azure/setup-helm@v4 uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with: with:
version: v4.1.0 version: v4.1.0
- name: Deploy - name: Deploy
@@ -49,12 +49,12 @@ jobs:
PODINFO_MODULE_URL: "oci://localhost:5000/podinfo" PODINFO_MODULE_URL: "oci://localhost:5000/podinfo"
PODINFO_VERSION: "0.0.0-devel" PODINFO_VERSION: "0.0.0-devel"
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/runner-cleanup - uses: ./.github/actions/runner-cleanup
- name: Setup Timoni - name: Setup Timoni
uses: stefanprodan/timoni/actions/setup@main uses: stefanprodan/timoni/actions/setup@c68e33a34f17c7ca93c7fc6717d61a14819276dc # v0.26.0
- name: Setup Kubernetes - name: Setup Kubernetes
uses: helm/kind-action@v1.14.0 uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with: with:
cluster_name: kind cluster_name: kind
- name: Build container - name: Build container

View File

@@ -15,16 +15,15 @@ jobs:
contents: write # needed to write releases contents: write # needed to write releases
id-token: write # needed for keyless signing id-token: write # needed for keyless signing
packages: write # needed for ghcr access packages: write # needed for ghcr access
attestations: write # needed for provenance
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/runner-cleanup - uses: ./.github/actions/runner-cleanup
- uses: sigstore/cosign-installer@v4.0.0 - uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
with: - uses: fluxcd/flux2/action@871be9b40d53627786d3a3835a3ddba1e3234bd2 # v2.8.3
cosign-release: v2.6.1 - uses: stefanprodan/timoni/actions/setup@c68e33a34f17c7ca93c7fc6717d61a14819276dc # v0.26.0
- uses: fluxcd/flux2/action@v2.8.1
- uses: stefanprodan/timoni/actions/setup@v0.26.0
- name: Setup Notation CLI - name: Setup Notation CLI
uses: notaryproject/notation-action/setup@v1 uses: notaryproject/notation-action/setup@b6fee73110795d6793253c673bd723f12bcf9bbb # v1.2.2
with: with:
version: "1.1.0" version: "1.1.0"
- name: Setup Notation signing keys - name: Setup Notation signing keys
@@ -36,28 +35,28 @@ jobs:
env: env:
NOTATION_KEY: ${{ secrets.NOTATION_SIGNING_KEY }} NOTATION_KEY: ${{ secrets.NOTATION_SIGNING_KEY }}
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with: with:
go-version: 1.26.x go-version: 1.26.x
- name: Setup Helm - name: Setup Helm
uses: azure/setup-helm@v4 uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with: with:
version: v4.1.1 version: v4.1.1
- name: Setup QEMU - name: Setup QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
with: with:
platforms: all platforms: all
- name: Setup Docker Buildx - name: Setup Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
@@ -73,7 +72,7 @@ jobs:
echo "REVISION=${GITHUB_SHA}" >> $GITHUB_OUTPUT echo "REVISION=${GITHUB_SHA}" >> $GITHUB_OUTPUT
- name: Generate images meta - name: Generate images meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with: with:
images: | images: |
docker.io/stefanprodan/podinfo docker.io/stefanprodan/podinfo
@@ -82,7 +81,7 @@ jobs:
type=raw,value=${{ steps.prep.outputs.VERSION }} type=raw,value=${{ steps.prep.outputs.VERSION }}
type=raw,value=latest type=raw,value=latest
- name: Publish multi-arch image - name: Publish multi-arch image
uses: docker/build-push-action@v6 uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with: with:
sbom: true sbom: true
provenance: true provenance: true
@@ -125,7 +124,7 @@ jobs:
cosign sign ghcr.io/stefanprodan/charts/podinfo:${{ steps.prep.outputs.VERSION }} --yes cosign sign ghcr.io/stefanprodan/charts/podinfo:${{ steps.prep.outputs.VERSION }} --yes
cosign sign ghcr.io/stefanprodan/manifests/podinfo:${{ steps.prep.outputs.VERSION }} --yes cosign sign ghcr.io/stefanprodan/manifests/podinfo:${{ steps.prep.outputs.VERSION }} --yes
- name: Publish base image - name: Publish base image
uses: docker/build-push-action@v6 uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with: with:
push: true push: true
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}
@@ -134,7 +133,7 @@ jobs:
file: ./Dockerfile.base file: ./Dockerfile.base
tags: docker.io/stefanprodan/podinfo-base:latest tags: docker.io/stefanprodan/podinfo-base:latest
- name: Publish helm chart - name: Publish helm chart
uses: stefanprodan/helm-gh-pages@master uses: stefanprodan/helm-gh-pages@0ad2bb377311d61ac04ad9eb6f252fb68e207260 # v1.7.0
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Publish config artifact - name: Publish config artifact
@@ -160,9 +159,13 @@ jobs:
notation sign --signature-format cose ghcr.io/stefanprodan/podinfo-deploy:${{ steps.prep.outputs.VERSION }} notation sign --signature-format cose ghcr.io/stefanprodan/podinfo-deploy:${{ steps.prep.outputs.VERSION }}
notation sign --signature-format cose ghcr.io/stefanprodan/podinfo-deploy:latest notation sign --signature-format cose ghcr.io/stefanprodan/podinfo-deploy:latest
- name: Publish release - name: Publish release
uses: goreleaser/goreleaser-action@v7 uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with: with:
version: latest version: latest
args: release --skip=validate args: release --skip=validate
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Attest release
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-checksums: ./dist/podinfo_${{ steps.prep.outputs.VERSION }}_checksums.txt

View File

@@ -17,29 +17,29 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/runner-cleanup - uses: ./.github/actions/runner-cleanup
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with: with:
go-version: 1.26.x go-version: 1.26.x
cache-dependency-path: | cache-dependency-path: |
**/go.sum **/go.sum
**/go.mod **/go.mod
- name: Setup kubectl - name: Setup kubectl
uses: azure/setup-kubectl@v4 uses: azure/setup-kubectl@15650b3ad78fff148532a140b8a4c821796b2d7b # v5.0.0
with: with:
version: v${{ env.KUBERNETES_VERSION }} version: v${{ env.KUBERNETES_VERSION }}
- name: Setup kubeconform - name: Setup kubeconform
uses: ./.github/actions/kubeconform uses: ./.github/actions/kubeconform
- name: Setup Helm - name: Setup Helm
uses: azure/setup-helm@v4 uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with: with:
version: v${{ env.HELM_VERSION }} version: v${{ env.HELM_VERSION }}
- name: Setup CUE - name: Setup CUE
uses: cue-lang/setup-cue@v1.0.1 uses: cue-lang/setup-cue@a93fa358375740cd8b0078f76355512b9208acb1 # v1.0.1
- name: Setup Timoni - name: Setup Timoni
uses: stefanprodan/timoni/actions/setup@v0.26.0 uses: stefanprodan/timoni/actions/setup@c68e33a34f17c7ca93c7fc6717d61a14819276dc # v0.26.0
- name: Run unit tests - name: Run unit tests
run: make test run: make test
- name: Validate Helm chart - name: Validate Helm chart

View File

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

View File

@@ -0,0 +1,42 @@
{{- if .Values.grpcRoute.enabled -}}
{{- $fullName := include "podinfo.fullname" . -}}
{{- $grpcPort := .Values.service.grpcPort -}}
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: {{ $fullName }}
namespace: {{ include "podinfo.namespace" . }}
labels:
{{- include "podinfo.labels" . | nindent 4 }}
{{- with .Values.grpcRoute.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.grpcRoute.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRefs:
{{- with .Values.grpcRoute.parentRefs }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.grpcRoute.hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
{{- range .Values.grpcRoute.rules }}
- backendRefs:
- name: {{ $fullName }}
port: {{ $grpcPort }}
weight: 1
{{- with .matches }}
matches:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .filters }}
filters:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -8,7 +8,7 @@ backends: []
image: image:
repository: ghcr.io/stefanprodan/podinfo repository: ghcr.io/stefanprodan/podinfo
tag: 6.10.2 tag: 6.11.2
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
ui: ui:

View File

@@ -8,7 +8,7 @@ backends: []
image: image:
repository: ghcr.io/stefanprodan/podinfo repository: ghcr.io/stefanprodan/podinfo
tag: 6.10.2 tag: 6.11.2
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
pullSecrets: [] pullSecrets: []
@@ -232,6 +232,28 @@ httpRoute:
type: PathPrefix type: PathPrefix
value: / value: /
# -- Expose the gRPC service via Gateway GRPCRoute
# Requires a Gateway controller with GRPCRoute support
# Docs https://gateway-api.sigs.k8s.io/guides/grpc-routing/
grpcRoute:
# GRPCRoute enabled.
enabled: false
# Add additional labels to the GRPCRoute.
additionalLabels: {}
# GRPCRoute annotations.
annotations: {}
# Which Gateways this Route is attached to.
parentRefs:
- name: gateway
sectionName: http
# namespace: default
# Hostnames matching HTTP header.
hostnames:
- podinfo.local
# List of rules applied.
rules:
- {}
# create Prometheus Operator monitor # create Prometheus Operator monitor
serviceMonitor: serviceMonitor:
enabled: false enabled: false

View File

@@ -12,10 +12,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/gorilla/websocket"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@@ -27,6 +30,7 @@ var (
body string body string
timeout time.Duration timeout time.Duration
grpcServiceName string grpcServiceName string
grpcTLS bool
) )
var checkCmd = &cobra.Command{ var checkCmd = &cobra.Command{
@@ -63,6 +67,13 @@ var checkgRPCCmd = &cobra.Command{
RunE: runCheckgPRC, RunE: runCheckgPRC,
} }
var checkWsCmd = &cobra.Command{
Use: `ws [address]`,
Short: "WebSocket round-trip health check",
Example: ` check ws ws://localhost:9898/ws/echo --retry=1 --delay=2s --timeout=5s`,
RunE: runCheckWs,
}
func init() { func init() {
checkUrlCmd.Flags().StringVar(&method, "method", "GET", "HTTP method") checkUrlCmd.Flags().StringVar(&method, "method", "GET", "HTTP method")
checkUrlCmd.Flags().StringVar(&body, "body", "", "HTTP POST/PUT content") checkUrlCmd.Flags().StringVar(&body, "body", "", "HTTP POST/PUT content")
@@ -80,10 +91,16 @@ func init() {
checkgRPCCmd.Flags().DurationVar(&retryDelay, "delay", 1*time.Second, "wait duration between retries") checkgRPCCmd.Flags().DurationVar(&retryDelay, "delay", 1*time.Second, "wait duration between retries")
checkgRPCCmd.Flags().DurationVar(&timeout, "timeout", 5*time.Second, "timeout") checkgRPCCmd.Flags().DurationVar(&timeout, "timeout", 5*time.Second, "timeout")
checkgRPCCmd.Flags().StringVar(&grpcServiceName, "service", "", "gRPC service name") checkgRPCCmd.Flags().StringVar(&grpcServiceName, "service", "", "gRPC service name")
checkgRPCCmd.Flags().BoolVar(&grpcTLS, "tls", false, "use TLS for gRPC connection")
checkCmd.AddCommand(checkgRPCCmd) checkCmd.AddCommand(checkgRPCCmd)
checkCmd.AddCommand(checkCertCmd) checkCmd.AddCommand(checkCertCmd)
checkWsCmd.Flags().IntVar(&retryCount, "retry", 0, "times to retry the WebSocket check")
checkWsCmd.Flags().DurationVar(&retryDelay, "delay", 1*time.Second, "wait duration between retries")
checkWsCmd.Flags().DurationVar(&timeout, "timeout", 5*time.Second, "timeout")
checkCmd.AddCommand(checkWsCmd)
rootCmd.AddCommand(checkCmd) rootCmd.AddCommand(checkCmd)
} }
@@ -262,6 +279,72 @@ func fmtContentLength(b int64) string {
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
} }
func runCheckWs(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 ws wss://localhost:9898/ws/echo")
}
address := args[0]
if !strings.HasPrefix(address, "ws://") && !strings.HasPrefix(address, "wss://") {
return fmt.Errorf("address must start with ws:// or wss://")
}
for n := 0; n <= retryCount; n++ {
if n != 0 {
time.Sleep(retryDelay)
}
dialer := websocket.Dialer{
HandshakeTimeout: timeout,
}
conn, _, err := dialer.Dial(address, nil)
if err != nil {
logger.Info("check failed",
zap.String("address", address),
zap.Error(err))
continue
}
msg := "podinfo-check"
start := time.Now()
conn.SetWriteDeadline(start.Add(timeout))
if err := conn.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
conn.Close()
logger.Info("check failed",
zap.String("address", address),
zap.Error(err))
continue
}
conn.SetReadDeadline(time.Now().Add(timeout))
_, resp, err := conn.ReadMessage()
if err != nil {
conn.Close()
logger.Info("check failed",
zap.String("address", address),
zap.Error(err))
continue
}
rtt := time.Since(start)
conn.Close()
logger.Info("check succeed",
zap.String("address", address),
zap.Duration("round-trip", rtt),
zap.Int("response size", len(resp)))
os.Exit(0)
}
os.Exit(1)
return nil
}
func runCheckgPRC(cmd *cobra.Command, args []string) error { func runCheckgPRC(cmd *cobra.Command, args []string) error {
if retryCount < 0 { if retryCount < 0 {
return fmt.Errorf("--retry is required") return fmt.Errorf("--retry is required")
@@ -271,12 +354,19 @@ func runCheckgPRC(cmd *cobra.Command, args []string) error {
} }
address := args[0] address := args[0]
var creds grpc.DialOption
if grpcTLS {
creds = grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
} else {
creds = grpc.WithTransportCredentials(insecure.NewCredentials())
}
for n := 0; n <= retryCount; n++ { for n := 0; n <= retryCount; n++ {
if n != 1 { if n != 0 {
time.Sleep(retryDelay) time.Sleep(retryDelay)
} }
conn, err := grpc.Dial(address, grpc.WithInsecure()) conn, err := grpc.NewClient(address, creds)
if err != nil { if err != nil {
logger.Info("check failed", logger.Info("check failed",
zap.String("address", address), zap.String("address", address),
@@ -291,13 +381,14 @@ func runCheckgPRC(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
if stat, ok := status.FromError(err); ok && stat.Code() == codes.Unimplemented { if stat, ok := status.FromError(err); ok && stat.Code() == codes.Unimplemented {
logger.Info("gPRC health protocol not implemented") logger.Info("gRPC health protocol not implemented")
os.Exit(1) os.Exit(1)
} else { } else {
logger.Info("check failed", logger.Info("check failed",
zap.String("address", address), zap.String("address", address),
zap.Error(err)) zap.Error(err))
} }
conn.Close()
continue continue
} }
@@ -305,7 +396,6 @@ func runCheckgPRC(cmd *cobra.Command, args []string) error {
logger.Info("check succeed", logger.Info("check succeed",
zap.String("status", resp.GetStatus().String())) zap.String("status", resp.GetStatus().String()))
os.Exit(0) os.Exit(0)
} }
os.Exit(1) os.Exit(1)

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@@ -10,6 +11,11 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.opentelemetry.io/contrib/bridges/otelzap"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
sdklog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
@@ -53,7 +59,7 @@ func main() {
fs.Int("stress-cpu", 0, "number of CPU cores with 100 load") fs.Int("stress-cpu", 0, "number of CPU cores with 100 load")
fs.Int("stress-memory", 0, "MB of data to load into memory") fs.Int("stress-memory", 0, "MB of data to load into memory")
fs.String("cache-server", "", "Redis address in the format 'tcp://<host>:<port>'") fs.String("cache-server", "", "Redis address in the format 'tcp://<host>:<port>'")
fs.String("otel-service-name", "", "service name for reporting to open telemetry address, when not set tracing is disabled") fs.String("otel-service-name", "", "service name for OpenTelemetry, when not set tracing and log export are disabled")
versionFlag := fs.BoolP("version", "v", false, "get version number") versionFlag := fs.BoolP("version", "v", false, "get version number")
@@ -93,8 +99,18 @@ func main() {
} }
} }
// initialize OTel log provider if service name is set
var loggerProvider *sdklog.LoggerProvider
if otelServiceName := viper.GetString("otel-service-name"); otelServiceName != "" {
var err error
loggerProvider, err = initLoggerProvider(context.Background(), otelServiceName)
if err != nil {
fmt.Fprintf(os.Stderr, "Error initializing OTel log provider: %s\n", err.Error())
}
}
// configure logging // configure logging
logger, _ := initZap(viper.GetString("level")) logger, _ := initZap(viper.GetString("level"), loggerProvider)
defer logger.Sync() defer logger.Sync()
stdLog := zap.RedirectStdLog(logger) stdLog := zap.RedirectStdLog(logger)
defer stdLog() defer stdLog()
@@ -163,10 +179,29 @@ func main() {
// graceful shutdown // graceful shutdown
stopCh := signals.SetupSignalHandler() stopCh := signals.SetupSignalHandler()
sd, _ := signals.NewShutdown(srvCfg.ServerShutdownTimeout, logger) sd, _ := signals.NewShutdown(srvCfg.ServerShutdownTimeout, logger)
sd.SetLoggerProvider(loggerProvider)
sd.Graceful(stopCh, httpServer, httpsServer, grpcServer, healthy, ready) sd.Graceful(stopCh, httpServer, httpsServer, grpcServer, healthy, ready)
} }
func initZap(logLevel string) (*zap.Logger, error) { func initLoggerProvider(ctx context.Context, serviceName string) (*sdklog.LoggerProvider, error) {
exporter, err := otlploggrpc.New(ctx)
if err != nil {
return nil, fmt.Errorf("creating OTLP log exporter: %w", err)
}
provider := sdklog.NewLoggerProvider(
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
sdklog.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(serviceName),
semconv.ServiceVersionKey.String(version.VERSION),
)),
)
return provider, nil
}
func initZap(logLevel string, loggerProvider *sdklog.LoggerProvider) (*zap.Logger, error) {
level := zap.NewAtomicLevelAt(zapcore.InfoLevel) level := zap.NewAtomicLevelAt(zapcore.InfoLevel)
switch logLevel { switch logLevel {
case "debug": case "debug":
@@ -210,7 +245,21 @@ func initZap(logLevel string) (*zap.Logger, error) {
ErrorOutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"},
} }
return zapConfig.Build() logger, err := zapConfig.Build()
if err != nil {
return nil, err
}
if loggerProvider != nil {
otelCore := otelzap.NewCore("github.com/stefanprodan/podinfo",
otelzap.WithLoggerProvider(loggerProvider),
)
logger = logger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
return zapcore.NewTee(core, otelCore)
}))
}
return logger, nil
} }
var stressMemoryPayload []byte var stressMemoryPayload []byte

View File

@@ -23,7 +23,7 @@ spec:
spec: spec:
containers: containers:
- name: backend - name: backend
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http

View File

@@ -23,7 +23,7 @@ spec:
restartPolicy: Never restartPolicy: Never
containers: containers:
- name: backup - name: backup
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- /bin/sh - /bin/sh

View File

@@ -23,7 +23,7 @@ spec:
restartPolicy: OnFailure restartPolicy: OnFailure
containers: containers:
- name: healthcheck - name: healthcheck
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- /bin/sh - /bin/sh

View File

@@ -23,7 +23,7 @@ spec:
restartPolicy: OnFailure restartPolicy: OnFailure
containers: containers:
- name: healthcheck - name: healthcheck
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- /bin/sh - /bin/sh

View File

@@ -25,7 +25,7 @@ spec:
serviceAccountName: database serviceAccountName: database
containers: containers:
- name: database - name: database
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: db - name: db

View File

@@ -22,7 +22,7 @@ spec:
serviceAccountName: database serviceAccountName: database
containers: containers:
- name: database - name: database
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: db - name: db

View File

@@ -23,7 +23,7 @@ spec:
spec: spec:
containers: containers:
- name: frontend - name: frontend
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http

View File

@@ -25,7 +25,7 @@ spec:
serviceAccountName: webapp serviceAccountName: webapp
containers: containers:
- name: backend - name: backend
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http

View File

@@ -25,7 +25,7 @@ spec:
serviceAccountName: webapp serviceAccountName: webapp
containers: containers:
- name: frontend - name: frontend
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http

6
go.mod
View File

@@ -16,6 +16,7 @@ require (
github.com/spf13/viper v1.21.0 github.com/spf13/viper v1.21.0
github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.16.6 github.com/swaggo/swag v1.16.6
go.opentelemetry.io/contrib/bridges/otelzap v0.15.0
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.65.0 go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.65.0
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.65.0 go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.65.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0
@@ -24,13 +25,15 @@ require (
go.opentelemetry.io/contrib/propagators/jaeger v1.40.0 go.opentelemetry.io/contrib/propagators/jaeger v1.40.0
go.opentelemetry.io/contrib/propagators/ot v1.40.0 go.opentelemetry.io/contrib/propagators/ot v1.40.0
go.opentelemetry.io/otel v1.40.0 go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0
go.opentelemetry.io/otel/sdk v1.40.0 go.opentelemetry.io/otel/sdk v1.40.0
go.opentelemetry.io/otel/sdk/log v0.16.0
go.opentelemetry.io/otel/trace v1.40.0 go.opentelemetry.io/otel/trace v1.40.0
go.uber.org/zap v1.27.1 go.uber.org/zap v1.27.1
golang.org/x/net v0.51.0 golang.org/x/net v0.51.0
google.golang.org/grpc v1.79.1 google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
) )
@@ -66,6 +69,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggo/files v1.0.1 // indirect github.com/swaggo/files v1.0.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/log v0.16.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect

16
go.sum
View File

@@ -117,6 +117,8 @@ github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/bridges/otelzap v0.15.0 h1:x4qzjKkTl2hXmLl+IviSXvzaTyCJSYvpFZL5SRVLBxs=
go.opentelemetry.io/contrib/bridges/otelzap v0.15.0/go.mod h1:h7dZHJgqkzUiKFXCTJBrPWH0LEZaZXBFzKWstjWBRxw=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.65.0 h1:LIMn2KWRS0jRDDHYyIEYgKWsMwufA9GXusJiwik0u64= go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.65.0 h1:LIMn2KWRS0jRDDHYyIEYgKWsMwufA9GXusJiwik0u64=
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.65.0/go.mod h1:JwJa4o3Wq+4Yz2BjlYFGWyx2h0Fw1lnoj5kpsaTI97o= go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.65.0/go.mod h1:JwJa4o3Wq+4Yz2BjlYFGWyx2h0Fw1lnoj5kpsaTI97o=
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.65.0 h1:ab5U7DpTjjN8pNgwqlA/s0Csb+N2Raqo9eTSDhfg4Z8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.65.0 h1:ab5U7DpTjjN8pNgwqlA/s0Csb+N2Raqo9eTSDhfg4Z8=
@@ -133,16 +135,26 @@ go.opentelemetry.io/contrib/propagators/ot v1.40.0 h1:Lon8J5SPmWaL1Ko2TIlCNHJ42/
go.opentelemetry.io/contrib/propagators/ot v1.40.0/go.mod h1:dKWtJTlp1Yj+8Cneye5idO46eRPIbi23qVuJYKjNnvY= go.opentelemetry.io/contrib/propagators/ot v1.40.0/go.mod h1:dKWtJTlp1Yj+8Cneye5idO46eRPIbi23qVuJYKjNnvY=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8=
go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4=
go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes=
go.opentelemetry.io/otel/log/logtest v0.16.0 h1:jr1CG3Z6FD9pwUaL/D0s0X4lY2ZVm1jP3JfCtzGxUmE=
go.opentelemetry.io/otel/log/logtest v0.16.0/go.mod h1:qeeZw+cI/rAtCzZ03Kq1ozq6C4z/PCa+K+bb0eJfKNs=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI=
go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4=
go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4=
go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
@@ -205,8 +217,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -23,7 +23,7 @@ spec:
spec: spec:
containers: containers:
- name: podinfod - name: podinfod
image: ghcr.io/stefanprodan/podinfo:6.10.2 image: ghcr.io/stefanprodan/podinfo:6.11.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http

View File

@@ -1,27 +1,34 @@
# Tracing Demo # Tracing & Logging Demo
The directory contains sample [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector) The directory contains sample [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector)
and [Jaeger](https://www.jaegertracing.io) configurations for a tracing demo. and [Jaeger](https://www.jaegertracing.io) / [Loki](https://grafana.com/oss/loki/) configurations for a tracing and logging demo.
## Configuration ## Configuration
The provided [docker-compose.yaml](docker-compose.yaml) sets up 4 Containers The provided [docker-compose.yaml](docker-compose.yaml) sets up 6 containers:
1. PodInfo Frontend on port 9898 1. PodInfo Frontend on port 9898
2. PodInfo Backend on port 9899 2. PodInfo Backend on port 9899
3. OpenTelemetry Collector listening on port 4317 for GRPC 3. OpenTelemetry Collector listening on port 4317 for GRPC
4. Jaeger all-in-one listening on multiple ports 4. Jaeger all-in-one with UI on port 16686
5. Loki on port 3100
6. Grafana on port 3000
## How does it work? ## How does it work?
The frontend pods are configured to call onto the backend pods. Both the podinfo The frontend pod is configured to call the backend pod. Both podinfo pods send traces
pods are configured to send traces over to the collector at port 4317 using GRPC. and logs to the collector at port 4317 using OTLP gRPC.
The collector forwards all received spans to Jaeger over port 14250 and Jaeger
exposes a UI over port `16686`. The collector forwards:
- **Traces** to Jaeger via OTLP gRPC on port 4317
- **Logs** to Loki via OTLP HTTP on port 3100
Jaeger exposes its UI on port `16686`. Grafana exposes its UI on port `3000` and is
pre-configured with both Jaeger and Loki as datasources.
## Running it locally ## Running it locally
1. Start all the Containers 1. Start all the containers
```shell ```shell
make run make run
``` ```
@@ -30,8 +37,9 @@ make run
curl -v http://localhost:9898/status/200 curl -v http://localhost:9898/status/200
curl -X POST -v http://localhost:9898/api/echo curl -X POST -v http://localhost:9898/api/echo
``` ```
3. Visit `http://localhost:16686/` to see the spans 3. Visit `http://localhost:16686/` to see traces in Jaeger
4. Stop all the containers 4. Visit `http://localhost:3000/` to explore logs in Grafana (Explore → Loki) and traces (Explore → Jaeger)
5. Stop all the containers
```shell ```shell
make stop make stop
``` ```

View File

@@ -5,31 +5,38 @@ services:
build: .. build: ..
command: ./podinfo --backend-url http://podinfo_backend:9899/status/200 --otel-service-name=podinfo_frontend command: ./podinfo --backend-url http://podinfo_backend:9899/status/200 --otel-service-name=podinfo_frontend
environment: environment:
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel:4317 - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel:4317
ports: ports:
- "9898:9898" - "9898:9898"
podinfo_backend: podinfo_backend:
build: .. build: ..
command: ./podinfo --port 9899 --otel-service-name=podinfo_backend command: ./podinfo --port 9899 --otel-service-name=podinfo_backend
environment: environment:
- OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel:4317 - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel:4317
ports: ports:
- "9899:9899" - "9899:9899"
otel: otel:
command: --config otel-config.yaml command: --config otel-config.yaml
image: otel/opentelemetry-collector:0.41.0 image: otel/opentelemetry-collector-contrib:0.116.1
ports: ports:
- "4317:4317" - "4317:4317"
volumes: volumes:
- ${PWD}/otel-config.yaml:/otel-config.yaml - ${PWD}/otel-config.yaml:/otel-config.yaml
jaeger: loki:
image: jaegertracing/all-in-one:1.29.0 image: grafana/loki:3.0.0
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
grafana:
image: grafana/grafana:10.4.0
ports:
- "3000:3000"
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
volumes:
- ${PWD}/grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml
jaeger:
image: jaegertracing/all-in-one:1.57.0
ports: ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686" - "16686:16686"
- "14268:14268"
- "14250:14250"
- "9411:9411"

View File

@@ -0,0 +1,10 @@
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://loki:3100
isDefault: true
- name: Jaeger
type: jaeger
url: http://jaeger:16686

View File

@@ -2,15 +2,18 @@ receivers:
otlp: otlp:
protocols: protocols:
grpc: grpc:
endpoint: 0.0.0.0:4317
http: http:
processors: processors:
exporters: exporters:
jaeger: otlp/jaeger:
endpoint: jaeger:14250 endpoint: jaeger:4317
tls: tls:
insecure: true insecure: true
otlphttp/loki:
endpoint: http://loki:3100/otlp
extensions: extensions:
health_check: health_check:
@@ -23,4 +26,8 @@ service:
traces: traces:
receivers: [otlp] receivers: [otlp]
processors: [] processors: []
exporters: [jaeger] exporters: [otlp/jaeger]
logs:
receivers: [otlp]
processors: []
exporters: [otlphttp/loki]

View File

@@ -7,11 +7,14 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"go.uber.org/zap" "go.uber.org/zap"
) )
var validHash = regexp.MustCompile(`^[a-f0-9]{40}$`)
// Store godoc // Store godoc
// @Summary Upload file // @Summary Upload file
// @Description writes the posted content to disk at /data/hash and returns the SHA1 hash of the content // @Description writes the posted content to disk at /data/hash and returns the SHA1 hash of the content
@@ -54,12 +57,19 @@ func (s *Server) storeReadHandler(w http.ResponseWriter, r *http.Request) {
defer span.End() defer span.End()
hash := mux.Vars(r)["hash"] hash := mux.Vars(r)["hash"]
if !validHash.MatchString(hash) {
s.ErrorResponse(w, r, span, "invalid hash", http.StatusBadRequest)
return
}
content, err := os.ReadFile(path.Join(s.config.DataPath, hash)) content, err := os.ReadFile(path.Join(s.config.DataPath, hash))
if err != nil { if err != nil {
s.logger.Warn("reading file failed", zap.Error(err), zap.String("file", path.Join(s.config.DataPath, hash))) s.logger.Warn("reading file failed", zap.Error(err), zap.String("file", path.Join(s.config.DataPath, hash)))
s.ErrorResponse(w, r, span, "reading file failed", http.StatusInternalServerError) s.ErrorResponse(w, r, span, "reading file failed", http.StatusInternalServerError)
return return
} }
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Content-Security-Policy", "default-src 'none'")
w.WriteHeader(http.StatusAccepted) w.WriteHeader(http.StatusAccepted)
w.Write([]byte(content)) w.Write([]byte(content))
} }

View File

@@ -0,0 +1,82 @@
package http
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gorilla/mux"
)
func TestStoreReadHandler_ContentType(t *testing.T) {
dataDir := t.TempDir()
srv := NewMockServer()
srv.config.DataPath = dataDir
// Write an HTML payload to the store.
writeReq, err := http.NewRequest("POST", "/store", strings.NewReader("<html><script>alert(1)</script></html>"))
if err != nil {
t.Fatal(err)
}
writeRR := httptest.NewRecorder()
http.HandlerFunc(srv.storeWriteHandler).ServeHTTP(writeRR, writeReq)
if writeRR.Code != http.StatusAccepted {
t.Fatalf("store write returned status %d, want %d", writeRR.Code, http.StatusAccepted)
}
// Read it back and verify Content-Type is application/octet-stream, not text/html.
hash := hash("<html><script>alert(1)</script></html>")
readReq, err := http.NewRequest("GET", "/store/"+hash, nil)
if err != nil {
t.Fatal(err)
}
readReq = mux.SetURLVars(readReq, map[string]string{"hash": hash})
readRR := httptest.NewRecorder()
http.HandlerFunc(srv.storeReadHandler).ServeHTTP(readRR, readReq)
if readRR.Code != http.StatusAccepted {
t.Fatalf("store read returned status %d, want %d", readRR.Code, http.StatusAccepted)
}
expectedHeaders := map[string]string{
"Content-Type": "application/octet-stream",
"X-Content-Type-Options": "nosniff",
"Content-Security-Policy": "default-src 'none'",
}
for header, want := range expectedHeaders {
if got := readRR.Header().Get(header); got != want {
t.Errorf("%s = %q, want %q", header, got, want)
}
}
}
func TestStoreReadHandler_PathTraversal(t *testing.T) {
srv := NewMockServer()
srv.config.DataPath = t.TempDir()
traversalPaths := []string{
"../../../../etc/passwd",
"../../../etc/shadow",
"..%2f..%2f..%2fetc%2fpasswd",
"abc123",
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzg", // 40 chars but not hex
}
for _, tp := range traversalPaths {
req, err := http.NewRequest("GET", "/store/"+tp, nil)
if err != nil {
t.Fatal(err)
}
req = mux.SetURLVars(req, map[string]string{"hash": tp})
rr := httptest.NewRecorder()
http.HandlerFunc(srv.storeReadHandler).ServeHTTP(rr, req)
if !strings.Contains(rr.Body.String(), "invalid hash") {
t.Errorf("path %q: expected 'invalid hash' error, got %q", tp, rr.Body.String())
}
}
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/gomodule/redigo/redis" "github.com/gomodule/redigo/redis"
"github.com/spf13/viper" "github.com/spf13/viper"
sdklog "go.opentelemetry.io/otel/sdk/log"
sdktrace "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -17,9 +18,14 @@ type Shutdown struct {
logger *zap.Logger logger *zap.Logger
pool *redis.Pool pool *redis.Pool
tracerProvider *sdktrace.TracerProvider tracerProvider *sdktrace.TracerProvider
loggerProvider *sdklog.LoggerProvider
serverShutdownTimeout time.Duration serverShutdownTimeout time.Duration
} }
func (s *Shutdown) SetLoggerProvider(lp *sdklog.LoggerProvider) {
s.loggerProvider = lp
}
func NewShutdown(serverShutdownTimeout time.Duration, logger *zap.Logger) (*Shutdown, error) { func NewShutdown(serverShutdownTimeout time.Duration, logger *zap.Logger) (*Shutdown, error) {
srv := &Shutdown{ srv := &Shutdown{
logger: logger, logger: logger,
@@ -62,6 +68,13 @@ func (s *Shutdown) Graceful(stopCh <-chan struct{}, httpServer *http.Server, htt
} }
} }
// stop OpenTelemetry logger provider
if s.loggerProvider != nil {
if err := s.loggerProvider.Shutdown(ctx); err != nil {
s.logger.Warn("stopping logger provider", zap.Error(err))
}
}
// determine if the GRPC was started // determine if the GRPC was started
if grpcServer != nil { if grpcServer != nil {
s.logger.Info("Shutting down GRPC server", zap.Duration("timeout", s.serverShutdownTimeout)) s.logger.Info("Shutting down GRPC server", zap.Duration("timeout", s.serverShutdownTimeout))

View File

@@ -1,4 +1,4 @@
package version package version
var VERSION = "6.10.2" var VERSION = "6.11.2"
var REVISION = "unknown" var REVISION = "unknown"

View File

@@ -9,7 +9,7 @@ package main
values: { values: {
image: { image: {
repository: "ghcr.io/stefanprodan/podinfo" repository: "ghcr.io/stefanprodan/podinfo"
tag: "6.10.2" tag: "6.11.2"
digest: "" digest: ""
} }
test: image: { test: image: {