1
0
Fork 0

Initial commit

This commit is contained in:
Vojtěch Mareš 2023-09-26 21:43:36 +00:00
commit 7724a7614e
24 changed files with 1653 additions and 0 deletions

13
.editorconfig Normal file
View file

@ -0,0 +1,13 @@
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
max_line_length = null
[Makefile]
indent_style = tab

10
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,10 @@
stages:
- triggers
trigger:app:
stage: triggers
trigger:
include: app/.gitlab-ci.yml
only:
changes:
- "app/*"

49
README.md Normal file
View file

@ -0,0 +1,49 @@
# Sentry Kubernetes Demo
This demo is made for a NodeJS app, but the same applies to other languages and runtimes.
# Infra
This repository contains Terraform definitions for Kubernetes on Digital Ocean.
DNS is provided by Cloudflare with your domain.
## Kubernetes
- 3 node cluster (each node is 2 CPU and 4Gi RAM)
# App
Simple NodeJS (Express) application with a couple endpoints to demonstrate Sentry basics - see [NodeJS example app for Sentry]()
## Endpoints
### /hello
- Status code: `200`
- Response: `World`
- No Sentry entry
### /exception
Caught exception
- Status code: `500`
- No response
- Caught exception in Sentry
### /uncaught-exception
Uncaught exception
- Status code: `500`
- No response
- Uncaught exception in Sentry
### /error-message
A custom message sent to Sentry
- Status code: `500`
- No response
- Custom message in Sentry

3
app/.dockerignore Normal file
View file

@ -0,0 +1,3 @@
node_modules
.gitignore
.gitlab-ci.yml

1
app/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

34
app/.gitlab-ci.yml Normal file
View file

@ -0,0 +1,34 @@
default:
image: sikalabs/ci
variables:
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA
before_script:
- cd app
stages:
- build
- deploy
docker:build:
stage: build
script:
- echo $CI_REGISTRY_PASSWORD | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
- docker build -t $IMAGE .
- docker push $IMAGE
deploy:
stage: deploy
environment:
name: live
url: https://sentry-demo.trz.sikademo.com
script:
- helm upgrade --install sentry-demo
./helm
--wait
--set image=$IMAGE
--set host=sentry-demo.trz.sikademo.com
--set dockerRegistry=$CI_REGISTRY
--set dockerRegistryAuth=$DOCKER_REGISTRY_AUTH

17
app/Dockerfile Normal file
View file

@ -0,0 +1,17 @@
FROM node:lts as build
WORKDIR /app
COPY package.json .
COPY package-lock.json .
RUN npm install
COPY . .
FROM node:lts
COPY --from=build /app /app
EXPOSE 3000
CMD ["node", "/app/server.js"]

0
app/README.md Normal file
View file

4
app/helm/Chart.yaml Normal file
View file

@ -0,0 +1,4 @@
apiVersion: v2
name: ad-auth
type: application
version: 0.1.0

View file

@ -0,0 +1 @@
[Public endpoint] NGINX Ingress auth URL: https://{{ .Values.host }}

View file

@ -0,0 +1,44 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: sentry-demo-app
labels:
app: sentry-demo-app
spec:
replicas: 1
selector:
matchLabels:
app: sentry-demo-app
template:
metadata:
labels:
app: sentry-demo-app
spec:
{{- if and (.Values.dockerRegistry) (.Values.dockerRegistryAuth) }}
imagePullSecrets:
- name: sentry-demo-app-docker
{{ end }}
containers:
- name: sentry-demo-app
image: {{ required "Missing required value: image" .Values.image }}
imagePullPolicy: Always
ports:
- containerPort: 3000
protocol: TCP
livenessProbe:
initialDelaySeconds: 5
timeoutSeconds: 30
tcpSocket:
port: 3000
env:
- name: PORT
value: "3000"
- name: SENTRY_DSN
value: {{ .Vaslues.sentry.dsn }}
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 400m
memory: 1024Mi

View file

@ -0,0 +1,16 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sentry-demo-app
spec:
rules:
- host: {{ .Values.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: sentry-demo-app
port:
number: 3000

View file

@ -0,0 +1,11 @@
{{- if and (.Values.dockerRegistry) (.Values.dockerRegistryAuth) }}
apiVersion: v1
kind: Secret
type: kubernetes.io/dockerconfigjson
metadata:
name: sentry-demo-app-docker
labels:
release: {{ .Release.Name }}
stringData:
.dockerconfigjson: '{"auths": {"{{ .Values.dockerRegistry }}": {"auth": "{{ .Values.dockerRegistryAuth }}"}}}'
{{ end }}

View file

@ -0,0 +1,13 @@
kind: Service
apiVersion: v1
metadata:
name: sentry-demo-app
labels:
app: sentry-demo-app
spec:
ports:
- name: http
port: 3000
targetPort: 3000
selector:
app: sentry-demo-app

6
app/helm/values.yaml Normal file
View file

@ -0,0 +1,6 @@
image:
host:
sentry:
dsn:
dockerRegistry:
dockerRegistryAuth:

1177
app/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

17
app/package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "sentry-demo",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "Vojtech Mares <iam@vojtechmares.com>",
"license": "MIT",
"dependencies": {
"@sentry/node": "^6.2.5",
"@sentry/tracing": "^6.2.5",
"express": "^4.17.1"
}
}

38
app/server.js Normal file
View file

@ -0,0 +1,38 @@
const express = require('express')
const sentry = require('@sentry/node')
const tracing = require('@sentry/tracing')
const app = express()
const PORT = process.env.PORT || 3000
const SENTRY_DSN = process.env.SENTRY_DSN
sentry.init({
dsn: SENTRY_DSN,
tracesSampleRate: 1.0,
})
app.get('/hello', (req, res) => {
res.send('World')
})
app.get('/exception', (req, res) => {
try {
throw new Error('Something flew out of a window')
} catch(e) {
sentry.captureException(e)
res.status(500).end()
}
})
app.get('/uncaught-exception', (req, res) => {
throw new Error('Something flew out of a window')
})
app.get('/error-message', (req, res) => {
sentry.captureMessage('Something went wrong')
req.status(500).end()
})
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server listening on http://0.0.0.0:${PORT}`)
})

3
infra/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.terraform
terraform.tfstate
*.backup

49
infra/Makefile Normal file
View file

@ -0,0 +1,49 @@
LONGHORN_VERSION = v1.1.1
helm-repos:
helm repo add sentry https://sentry-kubernetes.github.io/charts
helm repo update
install-ingress:
kubectl apply -f https://raw.githubusercontent.com/sikalabs/sikalabs-kubernetes-toolkit/master/k8s/ingress/ingress-traefik.yml
uninstall-ingress:
kubectl delete -f https://raw.githubusercontent.com/sikalabs/sikalabs-kubernetes-toolkit/master/k8s/ingress/ingress-traefik.yml
install-sentry:
kubectl create namespace sentry || true
helm upgrade --install \
sentry \
sentry/sentry \
-n sentry \
--set ingress.regexPathStyle=traefik \
--set ingress.enabled=true \
--set ingress.hostname=sentry.example.com
uninstall-sentry:
helm uninstall sentry
kubectl delete namespace sentry
install-longhorn:
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/$(LONGHORN_VERSION)/deploy/longhorn.yaml
uninstall-longhorn:
kubectl delete -f https://raw.githubusercontent.com/longhorn/longhorn/$(LONGHORN_VERSION)/deploy/longhorn.yaml
do-make-longhorn-default-storageclass:
kubectl patch storageclass do-block-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
kubectl patch storageclass longhorn -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
install-longhorn-ingress:
kubectl apply -f k8s/longhorn/ingress.yml
uninstall-longhorn-ingress:
kubectl delete -f k8s/longhorn/ingress.yml
install:
make helm-repos
make install-ingress
make install-longhorn && make install-longhorn-ingress
sleep 30
make do-make-longhorn-default-storageclass
make install-sentry

0
infra/README.md Normal file
View file

View file

@ -0,0 +1,17 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: longhorn-frontend
namespace: longhorn-system
spec:
rules:
- host: longhorn.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: longhorn-frontend
port:
number: 80

107
infra/terraform.tf Normal file
View file

@ -0,0 +1,107 @@
locals {
zone_id = "" // Your Cloudflare Zone ID
}
resource "digitalocean_kubernetes_cluster" "k8s" {
name = "sentry-demo"
region = "fra1"
version = "1.20.7-do.0"
node_pool {
name = "sentry-demo"
size = "s-2vcpu-4gb"
node_count = 3
}
}
resource "digitalocean_loadbalancer" "lb" {
name = "sentry-demo"
region = "fra1" // Change to your nearest DigitalOcean region
droplet_tag = "k8s:${digitalocean_kubernetes_cluster.k8s.id}"
healthcheck {
port = 30001
protocol = "tcp"
}
forwarding_rule {
entry_port = 80
target_port = 30001
entry_protocol = "tcp"
target_protocol = "tcp"
}
forwarding_rule {
entry_port = 80
target_port = 30001
entry_protocol = "tcp"
target_protocol = "tcp"
}
forwarding_rule {
entry_port = 443
target_port = 30002
entry_protocol = "tcp"
target_protocol = "tcp"
}
forwarding_rule {
entry_port = 8080
target_port = 30003
entry_protocol = "tcp"
target_protocol = "tcp"
}
forwarding_rule {
entry_port = 25
target_port = 30025
entry_protocol = "tcp"
target_protocol = "tcp"
}
forwarding_rule {
entry_port = 4036
target_port = 30002
entry_protocol = "tcp"
target_protocol = "tcp"
}
}
resource "cloudflare_record" "k8s" {
zone_id = local.zone_id
name = "trz"
value = digitalocean_loadbalancer.lb.ip
type = "A"
proxied = false
}
resource "cloudflare_record" "k8s_wildcard" {
zone_id = local.zone_id
name = "*.${cloudflare_record.k8s.name}"
value = cloudflare_record.k8s.hostname
type = "CNAME"
proxied = false
}
resource "cloudflare_record" "sentry" {
zone_id = local.zone_id
name = "sentry"
value = cloudflare_record.k8s.hostname
type = "CNAME"
proxied = false
}
output "ip" {
value = digitalocean_loadbalancer.lb.ip
}
output "kubeconfig" {
value = digitalocean_kubernetes_cluster.k8s.kube_config.0.raw_config
sensitive = true
}
output "sentry_hostname" {
value = cloudflare_record.sentry.hostname
}

23
infra/versions.tf Normal file
View file

@ -0,0 +1,23 @@
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "2.9.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "2.21.0"
}
}
}
provider "digitalocean" {
token = var.do_token
}
provider "cloudflare" {
api_token = var.cf_api_token
# OR
# api_key = var.cf_api_key
# email = var.cf_email
}