This article shows how to develop a service for generating globally unique IDs on a Kubernetes cluster. IDs will be generated similarly as with Twitter's Snowflake service, making them suitable for distributed systems where auto-incremental IDs fail and 128 bits for UUIDs is too inefficient. These IDs can also be "roughly" sorted by creation time, simply by sorting them lexicographically.

For the example given, IDs will be generated inside a container running on multiple pods, exposed by a REST API service. Ideally, this mechanism would be tightly coupled inside independent services, making it highly available and not needing any centralized coordination.

Getting started

Configure kubectl to use a running Kubernetes cluster, preferably with Minikube. Also install Docker.

Write the service

This example uses Sonyflake package, which works similarly to Twitter's Snowflake.

Create main.go file and initialize Sonyflake inside main function.

func main ( ) { st := sonyflake . Settings { } st . MachineID = machineID sf := sonyflake . NewSonyflake ( st ) if sf == nil { log . Fatal ( "failed to initialize sonyflake" ) } }

Write the machineID function, which generates a unique value by using the IP address provided with an environment variable MY_IP .

func machineID ( ) ( uint16 , error ) { ipStr := os . Getenv ( "MY_IP" ) if len ( ipStr ) == 0 { return 0 , errors . New ( "'MY_IP' environment variable not set" ) } ip := net . ParseIP ( ipStr ) if len ( ip ) < 4 { return 0 , errors . New ( "invalid IP" ) } return uint16 ( ip [ 2 ] ) << 8 + uint16 ( ip [ 3 ] ) , nil }

Back in the main function, set up a router with Gin web framework. It handles only one endpoint, which returns generated ID. It's returned as string to ensure no data is lost if parsing the result in JavaScript.

r := gin . Default ( ) r . GET ( "/" , func ( c * gin . Context ) { id , err := sf . NextID ( ) if err != nil { c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ( ) , } ) } else { c . JSON ( http . StatusOK , gin . H { "id" : fmt . Sprint ( id ) , } ) } } ) if err := r . Run ( ":3000" ) ; err != nil { log . Fatal ( "failed to run server: " , err ) }

Build Docker image

Create a Dockerfile file.

FROM golang : 1.9.2 WORKDIR /go/src/app "main.go" file COPY main.go . RUN go - wrapper download RUN go - wrapper install EXPOSE 3000 CMD [ "app" ]

If you're using Minikube, switch to its Docker daemon, otherwise you'll have to push the image to some other registry.

eval $( minikube docker-env )

Build the image. Here it is named local/unique-id .

docker build -t local/unique-id .

Deploy to Kubernetes

Create a configuration deployment.yaml file for the Kubernetes Deployment object.

apiVersion : apps/v1beta2 kind : Deployment metadata : name : unique - id labels : app : unique - id spec : selector : matchLabels : app : unique - id replicas : 3 template : metadata : labels : app : unique - id spec : containers : - name : unique - id image : local/unique - id imagePullPolicy : Never ports : - containerPort : 3000 env : - name : MY_IP valueFrom : fieldRef : fieldPath : status.podIP

The image field is set to local/unique-id and imagePullPolicy is set to Never , which makes Kubernetes use the local image from the Minikube's Docker registry. The MY_IP environment variable is set to pod's IP using Downward API.

Declare a service inside service.yaml , which targets pods labelled unique-id .

apiVersion : v1 kind : Service metadata : name : unique - id spec : type : LoadBalancer selector : app : unique - id ports : - port : 3000

Create resources.

kubectl create -f deployment.yaml kubectl create -f service.yaml

Get the URL of the service.

minikube service unique-id --url

Finally, try calling it a couple of times.

curl $( minikube service unique-id --url ) { "id" : "168514248039727104" }

Wrapping up

Following this approach for each separate service that depends on globally unique IDs, allows for perfectly distributed generation of IDs. You can find slightly modified version of the example above on GitHub.