⚡ Batteries-included Golang microservice template ⚡️
Last updated: 06/11/2024
It includes:
Makefilethat is used for run, test, build, deploy actionsDockerfilefor building a Docker image (alpinewith multi-stage build)docker-compose.ymlfor local dev- Github workflows for PR and release automation
- Sane code layout [1]
- Structured logging
- Good health-checking practices (uses async health-checking)
- Sample kubernetes deploy configs
- Configurable profiling support (pprof)
- Pre-instrumented with New Relic APM
- DigitalOcean container registry support
It uses:
Go 1.22julienschmidt/httprouterfor the HTTP routeruber/zapfor structured, light-weight loggingalecthomas/kongfor CLI args + ENV parsingnewrelic/go-agentfor APM (with logging)streamdal/rabbitfor reliable RabbitMQonsi/ginkgoandonsi/gomegafor BDD-style testing
[1] main.go for entrypoint, deps/deps.go for dependency setup + simple
dependency injection in tests, backends and services abstraction for business
logic.
All actions are performed via make - run make help to see list of available make args (targets).
For example:
- To run the service, run
make run - To build + push a docker img, run
make docker/build - To deploy to staging, run
make k8s/deploy/stage<- make sure to switch Kube context to staging!!! - To deploy to production, run
make k8s/deploy/prod<- make sure to switch Kube context to production!!!
Secrets are stored in K8S using their native Secret resource.
You can create them via kubectl:
kubectl create secret generic my-secret --from-literal=secret-key=secret-valueYou can then edit it: kubectl edit secret my-secret
NOTE: That the secret values are base64 encoded - when copy/pasting, make sure to decode them first:
❯ echo "dG9vdAo=" | base64 -D
tootThe secrets can be referenced as follows in the deploy config:
env:
- name: MY_SECRET
valueFrom:
secretKeyRef:
name: my-secret
key: secret-keyThis service uses a custom logger that wraps uber/zap in order to provide a
structured logging interface. While NR is able to collect logs written via uber/zap,
it does not include any "initial fields" set on the logger.
This makes it very difficult to create temporary loggers with base values that
are re-used throughout a method. For example: In method A that is 100 lines
long, we may want to create a logger with a base field "method" set to "A".
That would allow us to use the same logger throughout the method and not have to always include "method=A" attributes in each log message - the field will be included automatically.
The custom log wrapper provides this functionality.
PR and release automation is done via GitHub Actions.
When a PR is opened, a PR workflow is triggered.
When a PR is merged, a Release workflow is triggered. This workflow will build a docker image and push it to the DigitalOcean registry.
Deployment is manual. This is done for one primary reason:
A deployment is a critical operation that should be handled with care.
Or in other words, we do not throw deployments over the wall. Just because we can automate them, does not mean we should or will.
Deployments are performed via make k8s/deploy/stage and make k8s/deploy/prod.
Deployments are just kubectl apply -f deploy.stage.yaml under the hood. The image
the deployment will use is the CURRENT short git sha in the repo!
- Click "Use this template" in Github to create a new repo
- Clone newly created repo
- Find & replace:
go-svc-template-> lower case, dash separated service nameGO_SVC_TEMPLATE-> upper case, underscore separated service name (for ENV vars)your_org-> your Github org name
find . -maxdepth 3 -type f -exec sed -i "" 's/go-svc-template/service-name/g' {} \; find . -maxdepth 3 -type f -exec sed -i "" 's/GO_SVC_TEMPLATE/SERVICE_NAME/g' {} \; find . -maxdepth 3 -type f -exec sed -i "" 's/your_org/your-org-name/g' {} \; mv .github.rename .github
This template vendors packages by default to ensure reproducible builds + allow
local dev without an internet connection. Vendor can introduce its own headaches
though - if you want to remove it, remove -mod=vendor in the Makefile.