Initial commit
This commit is contained in:
commit
7724a7614e
24 changed files with 1653 additions and 0 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal 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
10
.gitlab-ci.yml
Normal 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
49
README.md
Normal 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
3
app/.dockerignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
.gitignore
|
||||||
|
.gitlab-ci.yml
|
||||||
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
||||||
34
app/.gitlab-ci.yml
Normal file
34
app/.gitlab-ci.yml
Normal 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
17
app/Dockerfile
Normal 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
0
app/README.md
Normal file
4
app/helm/Chart.yaml
Normal file
4
app/helm/Chart.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v2
|
||||||
|
name: ad-auth
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
1
app/helm/templates/NOTES.txt
Normal file
1
app/helm/templates/NOTES.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[Public endpoint] NGINX Ingress auth URL: https://{{ .Values.host }}
|
||||||
44
app/helm/templates/deployment.yml
Normal file
44
app/helm/templates/deployment.yml
Normal 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
|
||||||
16
app/helm/templates/ingress.yml
Normal file
16
app/helm/templates/ingress.yml
Normal 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
|
||||||
11
app/helm/templates/secret-docker.yml
Normal file
11
app/helm/templates/secret-docker.yml
Normal 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 }}
|
||||||
13
app/helm/templates/service.yml
Normal file
13
app/helm/templates/service.yml
Normal 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
6
app/helm/values.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
image:
|
||||||
|
host:
|
||||||
|
sentry:
|
||||||
|
dsn:
|
||||||
|
dockerRegistry:
|
||||||
|
dockerRegistryAuth:
|
||||||
1177
app/package-lock.json
generated
Normal file
1177
app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
17
app/package.json
Normal file
17
app/package.json
Normal 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
38
app/server.js
Normal 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
3
infra/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.terraform
|
||||||
|
terraform.tfstate
|
||||||
|
*.backup
|
||||||
49
infra/Makefile
Normal file
49
infra/Makefile
Normal 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
0
infra/README.md
Normal file
17
infra/k8s/longhorn/ingress.yml
Normal file
17
infra/k8s/longhorn/ingress.yml
Normal 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
107
infra/terraform.tf
Normal 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
23
infra/versions.tf
Normal 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
|
||||||
|
}
|
||||||
Reference in a new issue