Skip to content

Instantly share code, notes, and snippets.

@electron0zero

electron0zero/README.md

Last active May 10, 2020
Embed
What would you like to do?
Deploy sentry-kubernetes in multiple Kubernetes (gke) clusters

Update: turned it into a post, see https://suraj.dev/blog/sending-kubernetes-events-to-sentry

Setup sentry-kubernetes for multiple clusters

  • build image
  • configure RBAC in all target clusters using setup-access.yaml
  • deploy it (use deploy.rb)

How does deploy.rb works

  • get list of all clusters from clusters.yaml
  • get base deployment template from kubernetes-sentry.yaml
  • build new deployment file with CLUSTER_NAME env var, with value from clusters.yaml
  • apply that new generated deployment in that cluster

now it will start sending events, assuming everything is configured correctly

NOTE: don't run more then one replica, otherwise we will end up with duplicate events in sentry

#!/usr/bin/env bash
# This script builds docker image
set -ex
repo_path="https://github.com/getsentry/sentry-kubernetes.git"
image_name="sentry-kubernetes"
gcr_image="<docker-repo>/${image_name}"
docker build -t ${image_name}:latest ${repo_path}
docker tag ${image_name} ${gcr_image}
docker push ${gcr_image}
clusters:
- context: services
enabled: true
- context: services-v2
enabled: true
- context: staging
enabled: true
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/keys'
require 'yaml'
require 'tempfile'
require 'awesome_print'
class Deploy
CLUSTER_CONFIG = 'clusters.yaml'.freeze
DEPLOYMENT = 'kubernetes-sentry'.freeze
def initialize(args)
options = {}
option_parser = init_option_parser(options)
# Parse the command-line. Remember there are two forms
# of the parse method. The 'parse' method simply parses
# args, while the 'parse!' method parses args and removes
# any options found there, as well as any parameters for
# the options.
option_parser.parse!(args)
process(options)
end
def process(options)
if options[:all]
# deploy to all clusters
deploy_all
else
# deploy to single context
context = options[:context]
# select current-context if no context is passed
context = `kubectl config current-context`.strip if context.blank?
deploy(context)
end
end
private
def deploy_all
clusters = YAML.load_file(CLUSTER_CONFIG)
clusters.deep_symbolize_keys!
clusters[:clusters].each do |config|
if config[:enabled]
deploy(config[:context])
else
delete(config[:context])
end
end
end
def deploy(context)
scope = "--context=#{context}"
puts "Deploying #{DEPLOYMENT} on context: #{context}"
# check for `phpa-service-account`
# NOTE: we need phpa-service-account, if not found create it using
# 'kubectl apply -f setup-access.yaml'
exec("kubectl get serviceaccount kubernetes-sentry-service-account #{scope}")
deployment_file = build_yaml(context)
# deleting old deployment and apply
exec("kubectl delete -f #{deployment_file} #{scope}", exit_on_nonzero: false)
exec("kubectl apply -f #{deployment_file} #{scope}")
# check deployment status
exec("kubectl rollout status deployment #{DEPLOYMENT} #{scope}")
end
def delete(context)
scope = "--context=#{context}"
puts "Cleanup #{DEPLOYMENT} on context: #{context}"
exec("kubectl delete -f #{DEPLOYMENT}.yaml #{scope} || true")
end
def build_yaml(context)
deployment = YAML.load_file("#{DEPLOYMENT}.yaml")
cluster = { "name" => "CLUSTER_NAME", "value" => context }
deployment['spec']['template']['spec']['containers'].first['env'] << cluster
file = Tempfile.new(["#{context}-", ".yaml"])
file.write(deployment.to_yaml)
file.rewind
return file.path
end
def exec(command, exit_on_nonzero: true)
system(command)
result = $?
return if result.exitstatus.zero?
# fail if command exits with non-zero exit code
puts result
puts "Exit status code: #{result.exitstatus}"
raise "Failed to run command: '#{command}'" if exit_on_nonzero
end
# ============= CLI Option Parsing ==============
def init_option_parser(options)
option_parser = OptionParser.new do |parser|
# Set a banner displayed at top of help screen
parser.banner = 'USAGE: deploy.rb [options]'
parser.separator 'example: ./deploy.rb -c context'
parser.separator "If context is not passed it will deploy in current context"
context_option(parser, options)
all_option(parser, options)
help_option(parser)
end
return option_parser
end
# option definition
def help_option(parser)
parser.on('-h', '--help', 'Display this screen') do
puts parser
exit
end
end
def all_option(parser, options)
options[:all] = false
help = "Deploy on all clusters defined in #{CLUSTER_CONFIG}"
parser.on('--all', help) do
options[:all] = true
end
end
def context_option(parser, options)
help = 'Deploy in a specific context'
parser.on('-c', '--context CONTEXT', help) do |context|
options[:context] = context
end
end
end
Deploy.new(ARGV)
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubernetes-sentry
labels:
app: kubernetes-sentry
spec:
replicas: 1
selector:
matchLabels:
app: kubernetes-sentry
template:
metadata:
labels:
app: kubernetes-sentry
spec:
serviceAccountName: kubernetes-sentry-service-account
containers:
- name: kubernetes-sentry
image: <docker-registry>/sentry-kubernetes:latest
imagePullPolicy: Always
resources:
limits:
memory: "100Mi"
cpu: "100m"
env:
- name: DSN
value: "<DSN-KEY>"
# Setup Access so we can collect events
# https://suraj.dev/blog/access-control-RBAC-in-kubernetes
#
# NOTE: to apply these user need to be cluster-admin
# for GKE run this
# kubectl create clusterrolebinding cluster-admin-binding-<your name> --clusterrole=cluster-admin --user=$(gcloud config get-value core/account)
---
# service account will be used by PHPA
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubernetes-sentry-service-account
---
# used to control operations
# see this for big list https://github.com/kubernetes/kubernetes/blob/master/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/cluster-roles.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: kubernetes-sentry-cluster-role
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["watch"]
---
# link ClusterRole to ServiceAccount using ClusterRoleBinding
# it will allow cluster level operations to our service account
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: kubernetes-sentry-cluster-role-binding
subjects:
- kind: ServiceAccount
name: kubernetes-sentry-service-account
namespace: default
roleRef:
kind: ClusterRole
name: kubernetes-sentry-cluster-role
apiGroup: rbac.authorization.k8s.io
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.