docker run -d redis
docker run -d --pid=container:{redis_container_hash} -p 8080:8080 brendanburns/topz:db0fa58 /server --addr=0.0.0.0:8080
visit: http://localhost:8080/topzapiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: sharded-redis
spec:
serviceName: "redis"
replicas: 3
template:
metadata:
labels:
app: redis
spec:
terminationGracePeriodSeconds: 10
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
name: redisapiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
spec:
ports:
- port: 6379
name: redis
clusterIP: None
selector:
app: redisredis:
listen: 127.0.0.1:6379
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
timeout: 400
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- sharded-redis-0.redis:6379:1
- sharded-redis-1.redis:6379:1
- sharded-redis-2.redis:6379:1apiVersion: v1
kind: Pod
metadata:
name: ambassador-example
spec:
containers:
# This is where the application container would go, for example
# - name: nginx
# image: nginx
# This is the ambassador container
- name: twemproxy
image: ganomede/twemproxy
command:
- "nutcracker"
- "-c"
- "/etc/config/nutcracker.yaml"
- "-v"
- "7"
- "-s"
- "6222"
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: twem-configkubectl create -f redis-shards.yaml
kubectl get pods
kubectl create -f redis-service.yaml
kubectl create configmap twem-config --from-file=./nutcracker.yaml
kubectl create -f ambassador-example.yamlworker_processes 5;
error_log error.log;
pid nginx.pid;
worker_rlimit_nofile 8192;
events {
worker_connections 1024;
}
http {
upstream backend {
ip_hash;
server web weight=9;
server experiment;
}
server {
listen localhost:80;
location / {
proxy_pass http://backend;
}
}
# This is the 'experiment' service
apiVersion: v1
kind: Service
metadata:
name: experiment
labels:
app: experiment
spec:
ports:
- port: 80
name: web
selector:
# Change this selector to match your application's labels
app: experiment
---
# This is the 'prod' service
apiVersion: v1
kind: Service
metadata:
name: web
labels:
app: web
spec:
ports:
- port: 80
name: web
selector:
# Change this selector to match your application's labels
app: webapiVersion: v1
kind: Pod
metadata:
name: experiment-example
spec:
containers:
# This is where the application container would go, for example
# - name: some-name
# image: some-image
# This is the ambassador container
- name: nginx
image: nginx
volumeMounts:
- name: config-volume
mountPath: /etc/nginx
volumes:
- name: config-volume
configMap:
name: experiment-configHands On: Using Prometheus for Monitoring
apiVersion: v1
kind: Pod
metadata:
name: adapter-example
namespace: default
spec:
containers:
- image: redis
name: redisapiVersion: v1
kind: Pod
metadata:
name: adapter-example
namespace: default
spec:
containers:
- image: redis
name: redis
# Provide an adapter that implements the Prometheus interface
- image: oliver006/redis_exporter
name: adapterHands On: Normalizing Different Logging Formats with Fluentd
<source>
type redis_slowlog
host localhost
port 6379
tag redis.slowlog
</source><source>
type storm
tag storm
url http://localhost:8080
window 600
sys 0
</source>Hands On: Adding Rich Health Monitoring for MySQL
package main
import (
"database/sql"
"flag"
"fmt"
"net/http"
_ "github.com/go-sql-driver/mysql"
)
var (
user = flag.String("user", "", "The database user name")
passwd = flag.String("password", "", "The database password")
db = flag.String("database", "", "The database to connect to")
query = flag.String("query", "", "The test query")
addr = flag.String("address", "localhost:8080",
"The address to listen on")
)
// Basic usage:
// db-check --query="SELECT * from my-cool-table" \
// --user=bdburns \
// --passwd="you wish"
//
func main() {
flag.Parse()
db, err := sql.Open("localhost",
fmt.Sprintf("%s:%s@/%s", *user, *passwd, *db))
if err != nil {
fmt.Printf("Error opening database: %v", err)
}
// Simple web handler that runs the query
http.HandleFunc("", func(res http.ResponseWriter, req *http.Request) {
_, err := db.Exec(*query)
if err != nil {
res.WriteHeader(http.StatusInternalServerError)
res.Write([]byte(err.Error()))
return
}
res.WriteHeader(http.StatusOK)
res.Write([]byte("OK"))
return
})
// Startup the server
http.ListenAndServe(*addr, nil)
}
apiVersion: v1
kind: Pod
metadata:
name: adapter-example-health
namespace: default
spec:
containers:
- image: mysql
name: mysql
- image: brendanburns/mysql-adapter
name: adapterdocker run -p 8080:8080 brendanburns/dictionary-server
http://localhost:8080/dog
kubectl create -f dictionary-deploy.yaml
kubectl create -f dictionary-service.yamlkubectl create configmap varnish-config --from-file=default.vcl
kubectl create -f varnish-deploy.yaml
kubectl create -f varnish-service.yamlkubectl create secret tls ssl --cert=server.crt --key=server.key
kubectl create configmap nginx-conf --from-file=nginx.conf
kubectl create -f nginx-deploy.yaml
kubectl create -f nginx-service.yamlkubectl create -f memcached-shards.yaml
kubectl create -f memcached-service.yaml
kubectl create configmap --from-file=nutcracker.yaml twem-config
kubectl create -f memcached-ambassador-pod.yaml
kubectl create configmap --from-file=shared-nutcracker.yaml shared-twem-config
kubectl create -f shared-twemproxy-deploy.yaml
kubectl create -f shard-router-service.yamlThe hash function has two important characteristics for our sharding:
Determinism
The output should always be the same for a unique input.
Uniformity
The distribution of outputs across the output space should be equal.
worker_processes 5;
error_log error.log;
pid nginx.pid;
worker_rlimit_nofile 8192;
events {
worker_connections 1024;
}
http {
# define a named 'backend' that we can use in the proxy directive
# below.
upstream backend {
# Has the full URI of the request and use a consistent hash
hash $request_uri consistent
server web-shard-1.web;
server web-shard-2.web;
server web-shard-3.web;
}
server {
listen localhost:80;
location / {
proxy_pass http://backend;
}
}
}It is often quite difficult to obtain a comprehensive view of your service, determine how the various functions integrate with one another, and understand when things go wrong, and why they go wrong. As an example, consider the following functions:
# Simple handler function for adding default values
def handler(context):
# Get the input value
obj = context.json
# If the 'name' field is not present, set it randomly
if obj.get("name", None) is None:
obj["name"] = random_name()
# If the 'color' field is not present, set it to 'blue'
if obj.get("color", None) is None:
obj["color"] = "blue"
# Call the actual API, potentially with the new default
# values, and return the result
return call_my_api(obj)kubeless function deploy add-defaults \
--runtime python27 \
--handler defaults.handler \
--from-file defaults.py \
--trigger-http
kubeless function call add-defaults --data '{"name": "foo"}'def two_factor(context):
# Generate a random six digit code
code = random.randint(100000, 999999)
# Register the code with the login service
user = context.json["user"]
register_code_with_login_service(user, code)
# Use the twillio library to send texts
account = "my-account-sid"
token = "my-token"
client = twilio.rest.Client(account, token)
user_number = context.json["phoneNumber"]
msg = "Hello {} your authentication code is: {}.".format(user, code)
message = client.api.account.messages.create(to=user_number,
from_="+12065251212",
body=msg)
return {"status": "ok"}kubeless function deploy add-two-factor \
--runtime python27 \
--handler two_factor.two_factor \
--from-file two_factor.py \
--trigger-httpdef create_user(context):
# For required event handlers, call them universally
for key, value in required.items():
call_function(value.webhook, context.json)
# For optional event handlers, check and call them
# conditionally
for key, value in optional.items():
if context.json.get(key, None) is not None:
call_function(value.webhook, context.json)def email_user(context):
# Get the user name
user = context.json['username']
msg = 'Hello {} thanks for joining my awesome service!".format(user)
send_email(msg, contex.json['email])
def subscribe_user(context):
# Get the user name
email = context.json['email']
subscribe_user(email)var lock = sync.Mutex{}
var store = map[string]string{}
func compareAndSwap(key, nextValue, currentValue string) (bool, error) {
lock.Lock()
defer lock.Unlock()
_, containsKey := store[key]
if !containsKey {
if len(currentValue) == 0 {
store[key] = nextValue
return true, nil
}
return false, fmt.Errorf("Expected value %s for key %s, but
found empty", currentValue, key)
}
if store[key] == currentValue {
store[key] = nextValue
return true, nil
}
return false, nil
}func (Lock l) simpleLock() boolean {
// compare and swap "1" for "0"
locked, _ = compareAndSwap(l.lockName, "1", "0")
return locked
}func (Lock l) simpleLock() boolean {
// compare and swap "1" for "0"
locked, error = compareAndSwap(l.lockName, "1", "0")
// lock doesn't exist, try to write "1" with a previous value of
// non-existent
if error != nil {
locked, _ = compareAndSwap(l.lockName, "1", nil)
}
return locked
}func (Lock l) lock() {
while (!l.simpleLock()) {
sleep(2)
}
}func (Lock l) lock() {
while (!l.simpleLock()) {
waitForChanges(l.lockName)
}
}func (Lock l) unlock() {
compareAndSwap(l.lockName, "0", "1")
}func (Lock l) simpleLock() boolean {
// compare and swap "1" for "0"
locked, error = compareAndSwap(l.lockName, "1", "0", l.ttl)
// lock doesn't exist, try to write "1" with a previous value of
// non-existent
if error != nil {
locked, _ = compareAndSwap(l.lockName, "1", nil, l.ttl)
}
return locked
}Consider the following scenario:
func (Lock l) simpleLock() boolean {
// compare and swap "1" for "0"
locked, l.version, error = compareAndSwap(l.lockName, "1", "0", l.ttl)
// lock doesn't exist, try to write "1" with a previous value of
// non-existent
if error != null {
locked, l.version, _ = compareAndSwap(l.lockName, "1", null, l.ttl)
}
return locked
}
func (Lock l) unlock() {
compareAndSwap(l.lockName, "0", "1", l.version)
}func (Lock l) renew() boolean {
locked, _ = compareAndSwap(l.lockName, "1", "1", l.version, ttl)
return locked
}
for {
if !l.renew() {
handleLockLost()
}
sleep(ttl/2)
}