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